Skip to content

Commit

Permalink
Use Harfbuzz for shaping (celiagg#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwiggins authored Mar 30, 2021
1 parent 517ecb2 commit 0081a38
Show file tree
Hide file tree
Showing 17 changed files with 570 additions and 219 deletions.
28 changes: 26 additions & 2 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,34 @@ env:

jobs:

# Test against EDM packages on Linux
test-edm-linux:
strategy:
matrix:
build-args: ['', '--install-option --no-text-rendering']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies for Linux
run: |
sudo apt-get update
sudo apt-get install libharfbuzz-dev libfreetype-dev
- name: Setup EDM
uses: enthought/setup-edm-action@v1
with:
edm-version: ${{ env.INSTALL_EDM_VERSION }}
- name: Install test environment
run: |
edm --config ci/.edm.yaml install -y cython numpy
edm run -- pip install -e . ${{ matrix.build-args }}
- name: Run tests
run: edm run -- python -m unittest discover -v celiagg


test-with-edm:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
os: [macos-latest, windows-latest]
build-args: ['', '--install-option --no-text-rendering']
runs-on: ${{ matrix.os }}
env:
Expand All @@ -27,7 +51,7 @@ jobs:
edm-version: ${{ env.INSTALL_EDM_VERSION }}
- name: Install test environment
run: |
edm --config ci/.edm.yaml install -y cython freetype numpy
edm --config ci/.edm.yaml install -y cython freetype harfbuzz numpy
edm run -- pip install -e . ${{ matrix.build-args }}
- name: Run tests
run: edm run -- python -m unittest discover -v celiagg
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Dependencies
* Numpy
* Cython (build-time only)
* Freetype2 (optional)
* Harfbuzz (optional)

Contributing
------------
Expand Down
7 changes: 5 additions & 2 deletions agg-svn/agg-2.4/font_freetype/agg_font_freetype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -912,9 +912,12 @@ namespace agg


//------------------------------------------------------------------------
bool font_engine_freetype_base::prepare_glyph(unsigned glyph_code)
bool font_engine_freetype_base::prepare_glyph(unsigned glyph_code, bool is_glyph_index)
{
m_glyph_index = FT_Get_Char_Index(m_cur_face, glyph_code);
m_glyph_index = glyph_code;
// If a raw glyph index is not passed, we ask the font
if (!is_glyph_index) m_glyph_index = FT_Get_Char_Index(m_cur_face, glyph_code);

m_last_error = FT_Load_Glyph(m_cur_face,
m_glyph_index,
m_hinting ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING);
Expand Down
3 changes: 2 additions & 1 deletion agg-svn/agg-2.4/font_freetype/agg_font_freetype.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@ namespace agg
bool hinting() const { return m_hinting; }
bool flip_y() const { return m_flip_y; }

FT_Face face() const { return m_cur_face; }

// Interface mandatory to implement for font_cache_manager
//--------------------------------------------------------------------
const char* font_signature() const { return m_signature; }
int change_stamp() const { return m_change_stamp; }

bool prepare_glyph(unsigned glyph_code);
bool prepare_glyph(unsigned glyph_code, bool is_glyph_index=false);
unsigned glyph_index() const { return m_glyph_index; }
unsigned data_size() const { return m_data_size; }
glyph_data_type data_type() const { return m_data_type; }
Expand Down
4 changes: 3 additions & 1 deletion agg-svn/agg-2.4/font_win32_tt/agg_font_win32_tt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ namespace agg


//------------------------------------------------------------------------
bool font_engine_win32_tt_base::prepare_glyph(unsigned glyph_code)
bool font_engine_win32_tt_base::prepare_glyph(unsigned glyph_code, bool is_glyph_index)
{
if(m_dc && m_cur_font)
{
Expand All @@ -620,6 +620,8 @@ namespace agg
#define GGO_UNHINTED 0x0100
#endif
if(!m_hinting) format |= GGO_UNHINTED;
// In case a raw glyph index is passed
if(is_glyph_index) format |= GGO_GLYPH_INDEX;

GLYPHMETRICS gm;
int total_size = GetGlyphOutlineX(m_dc,
Expand Down
3 changes: 2 additions & 1 deletion agg-svn/agg-2.4/font_win32_tt/agg_font_win32_tt.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,14 @@ namespace agg
bool hinting() const { return m_hinting; }
bool flip_y() const { return m_flip_y; }

HFONT font() const { return m_cur_font; }

// Interface mandatory to implement for font_cache_manager
//--------------------------------------------------------------------
const char* font_signature() const { return m_signature; }
int change_stamp() const { return m_change_stamp; }

bool prepare_glyph(unsigned glyph_code);
bool prepare_glyph(unsigned glyph_code, bool is_glyph_index=false);
unsigned glyph_index() const { return m_glyph_index; }
unsigned data_size() const { return m_data_size; }
glyph_data_type data_type() const { return m_data_type; }
Expand Down
4 changes: 2 additions & 2 deletions agg-svn/agg-2.4/include/agg_font_cache_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ namespace agg
}

//--------------------------------------------------------------------
const glyph_cache* glyph(unsigned glyph_code)
const glyph_cache* glyph(unsigned glyph_code, bool is_glyph_index=false)
{
synchronize();
const glyph_cache* gl = m_fonts.find_glyph(glyph_code);
Expand All @@ -293,7 +293,7 @@ namespace agg
}
else
{
if(m_engine.prepare_glyph(glyph_code))
if(m_engine.prepare_glyph(glyph_code, is_glyph_index))
{
m_prev_glyph = m_last_glyph;
m_last_glyph = m_fonts.cache_glyph(glyph_code,
Expand Down
5 changes: 2 additions & 3 deletions celiagg/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@

#include "canvas_impl.h"
#include "font_cache.h"
#include "glyph_iter.h"
#include "graphics_state.h"
#include "image.h"
#include "paint.h"
Expand Down Expand Up @@ -145,14 +144,14 @@ class canvas : public canvas_base
const GraphicsState& gs,
base_renderer_t& renderer);
template<typename base_renderer_t>
void _draw_text_raster(GlyphIterator& iterator,
void _draw_text_raster(const char* text,
Font& font,
const agg::trans_affine& transform,
Paint& fillPaint,
const GraphicsState& gs,
base_renderer_t& renderer);
template<typename base_renderer_t>
void _draw_text_vector(GlyphIterator& iterator,
void _draw_text_vector(const char* text,
Font& font,
const agg::trans_affine& transform,
Paint& linePaint, Paint& fillPaint,
Expand Down
61 changes: 36 additions & 25 deletions celiagg/canvas_text.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,17 @@ void canvas<pixfmt_t>::_draw_text_internal(const char* text, Font& font,
const bool font_flip = font.flip();
font.flip(!m_bottom_up);

GlyphIterator iterator(text, m_font_cache, true);
if (gs.text_drawing_mode() == GraphicsState::TextDrawRaster)
{
// Raster text only uses the fill paint!
_draw_text_raster(iterator, font, transform, fillPaint, gs, renderer);
_draw_text_raster(text, font, transform, fillPaint, gs, renderer);
}
else
{
// Pick the correct drawing mode for the glyph paths
GraphicsState copy_state(gs);
copy_state.drawing_mode(_convert_text_mode(gs.text_drawing_mode()));
_draw_text_vector(iterator, font, transform, linePaint, fillPaint, copy_state, renderer);
_draw_text_vector(text, font, transform, linePaint, fillPaint, copy_state, renderer);
}

// Restore the font's flip state to whatever it was
Expand All @@ -54,16 +53,14 @@ void canvas<pixfmt_t>::_draw_text_internal(const char* text, Font& font,

template<typename pixfmt_t>
template<typename base_renderer_t>
void canvas<pixfmt_t>::_draw_text_raster(GlyphIterator& iterator,
void canvas<pixfmt_t>::_draw_text_raster(const char* text,
Font& font, const agg::trans_affine& transform, Paint& fillPaint,
const GraphicsState& gs, base_renderer_t& renderer)
{
#ifdef _ENABLE_TEXT_RENDERING
typedef FontCache::FontCacheManager::gray8_adaptor_type font_rasterizer_t;
typedef FontCache::FontCacheManager::gray8_scanline_type scanline_t;

const bool eof = (gs.drawing_mode() & GraphicsState::DrawEofFill) == GraphicsState::DrawEofFill;
m_rasterizer.filling_rule(eof ? agg::fill_even_odd : agg::fill_non_zero);
typedef FontCache::shaper_type shaper_t;

// Strip the translation out of the transformation when activating the font
// so that the cached glyphs aren't associated with a specific translation,
Expand All @@ -73,52 +70,66 @@ void canvas<pixfmt_t>::_draw_text_raster(GlyphIterator& iterator,
transform_array[4] = 0.0; transform_array[5] = 0.0;
m_font_cache.activate(font, agg::trans_affine(transform_array), FontCache::k_GlyphTypeRaster);

// Determine the starting glyph position from the transform and initialize
// the iterator.
double start_x = 0.0, start_y = 0.0;
transform.transform(&start_x, &start_y);
iterator.offset(start_x, start_y);

// Rendery bits from the font cache
font_rasterizer_t& ras = m_font_cache.manager().gray8_adaptor();
scanline_t& scanline = m_font_cache.manager().gray8_scanline();
shaper_t& shaper = m_font_cache.shaper();

// Determine the starting glyph position from the transform
double start_x = 0.0, start_y = 0.0;
transform.transform(&start_x, &start_y);

// Shape the text and initialize the starting position
shaper.shape(text);
// Set cursor position last, because shape() resets it
shaper.cursor(start_x, start_y);

// Draw the glyphs one at a time
GlyphIterator::StepAction action = GlyphIterator::k_StepActionInvalid;
while (action != GlyphIterator::k_StepActionEnd)
Shaper::StepAction action = Shaper::k_StepActionSkip;
while (action != Shaper::k_StepActionEnd)
{
if (action == GlyphIterator::k_StepActionDraw)
action = shaper.step();
if (action == Shaper::k_StepActionDraw)
{
const agg::glyph_cache* glyph = m_font_cache.manager().last_glyph();
m_font_cache.manager().init_embedded_adaptors(glyph, shaper.cursor_x(), shaper.cursor_y());
fillPaint.render<pixfmt_t, font_rasterizer_t, scanline_t, base_renderer_t>(ras, scanline, renderer, transform);
}
action = iterator.step();
}
#endif
}

template<typename pixfmt_t>
template<typename base_renderer_t>
void canvas<pixfmt_t>::_draw_text_vector(GlyphIterator& iterator,
void canvas<pixfmt_t>::_draw_text_vector(const char* text,
Font& font, const agg::trans_affine& transform, Paint& linePaint, Paint& fillPaint,
const GraphicsState& gs, base_renderer_t& renderer)
{
#ifdef _ENABLE_TEXT_RENDERING
PathSource shape;
typedef FontCache::shaper_type shaper_t;

PathSource glyphs_path;
shaper_t& shaper = m_font_cache.shaper();

// Activate the font with an identity transform. The passed in transform
// will be applied later when drawing the generated path.
m_font_cache.activate(font, agg::trans_affine(), FontCache::k_GlyphTypeVector);

GlyphIterator::StepAction action = GlyphIterator::k_StepActionInvalid;
while (action != GlyphIterator::k_StepActionEnd)
// Shape the text
shaper.shape(text);

Shaper::StepAction action = Shaper::k_StepActionSkip;
while (action != Shaper::k_StepActionEnd)
{
if (action == GlyphIterator::k_StepActionDraw)
action = shaper.step();
if (action == Shaper::k_StepActionDraw)
{
shape.concat_path(m_font_cache.manager().path_adaptor());
const agg::glyph_cache* glyph = m_font_cache.manager().last_glyph();
m_font_cache.manager().init_embedded_adaptors(glyph, shaper.cursor_x(), shaper.cursor_y());
glyphs_path.concat_path(m_font_cache.manager().path_adaptor());
}
action = iterator.step();
}
_draw_shape_internal(shape, transform, linePaint, fillPaint, gs, renderer);
_draw_shape_internal(glyphs_path, transform, linePaint, fillPaint, gs, renderer);
#endif
}

Expand Down
29 changes: 23 additions & 6 deletions celiagg/font_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
// Author: John Wiggins

#include "font_cache.h"
#include "glyph_iter.h"

#ifndef _ENABLE_TEXT_RENDERING
// This is what happens when you disable font support!
Expand All @@ -45,7 +44,11 @@ FontCache::FontCache()
: m_font_engine()
#endif
, m_font_cache_manager(m_font_engine)
{}
, m_shaper(m_font_cache_manager)
{
// This doesn't change
m_font_engine.resolution(72);
}

FontCache::~FontCache()
{
Expand All @@ -68,7 +71,11 @@ FontCache::activate(const Font& font, const agg::trans_affine& transform, GlyphT
m_font_engine.flip_y(font.flip());
m_font_engine.hinting(font.hinting());
m_font_engine.height(font.height());
m_font_engine.width(font.height()); // reusing height as width
m_font_engine.transform(transform);

// Tell the shaper about the font we just activated
m_shaper.init_font(m_font_engine.face(), transform, m_font_engine.font_signature());
#else
// Set the font aspects _before_ calling create_font to work around the
// Windows font engine's lack of cache signature updating.
Expand All @@ -80,18 +87,22 @@ FontCache::activate(const Font& font, const agg::trans_affine& transform, GlyphT
agg::glyph_ren_outline :
agg::glyph_ren_agg_gray8,
font.height(),
0.0,
font.height(), // reusing height as width
font.weight(),
font.italic());

// Tell the shaper about the font we just activated
m_shaper.init_font(m_font_engine.font(), transform, m_font_engine.font_signature());
#endif
}

double
FontCache::measure_width(char const* str)
{
GlyphIterator iterator(str, *this);
while (iterator.step() != GlyphIterator::k_StepActionEnd) {}
return iterator.x_offset();
// calling shape() resets the cursor position
m_shaper.shape(str);
while (m_shaper.step() != Shaper::k_StepActionEnd) {}
return m_shaper.cursor_x();
}

FontCache::FontCacheManager&
Expand All @@ -100,4 +111,10 @@ FontCache::manager()
return m_font_cache_manager;
}

FontCache::shaper_type&
FontCache::shaper()
{
return m_shaper;
}

#endif
12 changes: 12 additions & 0 deletions celiagg/font_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
#else
#include <agg_font_win32_tt.h>
#endif
#ifdef _USE_HARFBUZZ
#include "harfbuzz_shaper.h"
#else
#include "null_shaper.h"
#endif
#endif

#include "font.h"
Expand All @@ -58,6 +63,11 @@ class FontCache
typedef agg::font_engine_win32_tt_int32 FontEngine;
#endif
typedef agg::font_cache_manager<FontEngine> FontCacheManager;
#ifdef _USE_HARFBUZZ
typedef HarbuzzShaper<FontEngine> shaper_type;
#else
typedef NullShaper<FontEngine> shaper_type;
#endif
#endif

FontCache();
Expand All @@ -70,6 +80,7 @@ class FontCache

#ifdef _ENABLE_TEXT_RENDERING
FontCacheManager& manager();
shaper_type& shaper();
#endif

private:
Expand All @@ -80,6 +91,7 @@ class FontCache
#endif
FontEngine m_font_engine;
FontCacheManager m_font_cache_manager;
shaper_type m_shaper;
#endif
};

Expand Down
Loading

0 comments on commit 0081a38

Please sign in to comment.