From 1ae7ae4a1d9eb2e8d2dcce155d7f1f5245a715d3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 13 Jan 2018 10:41:54 +0530 Subject: [PATCH] Fix consecutive separate ligatures being rendered in the same group Rendering multiple ligatures in the smae group is bad for performance and also causes incorrect rendering if the last ligature in the group does not fit into the number of cells in the group. --- kitty/core_text.m | 9 +++++++++ kitty/fonts.c | 32 ++++++++++++++++++++++++-------- kitty/fonts.h | 2 ++ kitty/freetype.c | 11 +++++++++++ kitty_tests/fonts.py | 1 + 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/kitty/core_text.m b/kitty/core_text.m index 18d93c2d9..906a9ab55 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -200,6 +200,15 @@ glyph_id_for_codepoint(PyObject *s, char_type ch) { return glyphs[0]; } +bool +is_glyph_empty(PyObject *s, glyph_index g) { + CTFace *self = (CTFace*)s; + CGGlyph gg = g; + CGRect bounds; + CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1); + return bounds.size.width <= 0; +} + static inline float scaled_point_sz() { return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts; diff --git a/kitty/fonts.c b/kitty/fonts.c index f808e0206..2a9b24da8 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -13,7 +13,6 @@ #define MISSING_GLYPH 4 #define MAX_NUM_EXTRA_GLYPHS 8 -typedef uint16_t glyph_index; typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*); send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; static PyObject *python_send_to_gpu_impl = NULL; @@ -39,6 +38,8 @@ struct SpritePosition { #define SPECIAL_FILLED_MASK 1 #define SPECIAL_VALUE_MASK 2 +#define EMPTY_FILLED_MASK 4 +#define EMPTY_VALUE_MASK 8 struct SpecialGlyphCache { SpecialGlyphCache *next; @@ -153,13 +154,13 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui return s; } -static SpecialGlyphCache* -special_glyph_cache_for(Font *font, glyph_index glyph) { +static inline SpecialGlyphCache* +special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t mask) { SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff); // Optimize for the common case of glyph under 1024 already in the cache - if (LIKELY(s->glyph == glyph && s->data & SPECIAL_FILLED_MASK)) return s; // Cache hit + if (LIKELY(s->glyph == glyph && s->data & mask)) return s; // Cache hit while(true) { - if (s->data & SPECIAL_FILLED_MASK) { + if (s->data & mask) { if (s->glyph == glyph) return s; // Cache hit } else { break; @@ -565,7 +566,7 @@ typedef struct { typedef struct { uint32_t previous_cluster; - bool prev_was_special; + bool prev_was_special, prev_was_empty; CellData current_cell_data; Group *groups; size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs; @@ -592,6 +593,7 @@ shape(Cell *first_cell, index_type num_cells, hb_font_t *font) { } group_state.previous_cluster = UINT32_MAX; group_state.prev_was_special = false; + group_state.prev_was_empty = false; group_state.current_cell_data.cell = first_cell; group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cell); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = first_cell->ch; memset(group_state.groups, 0, sizeof(Group) * group_state.groups_capacity); group_state.group_idx = 0; @@ -613,7 +615,7 @@ static inline bool is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { // A glyph is special if the codepoint it corresponds to matches a // different glyph in the font - SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id); + SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, SPECIAL_FILLED_MASK); if (s == NULL) return false; if (!(s->data & SPECIAL_FILLED_MASK)) { bool is_special = cell_data->current_codepoint ? ( @@ -626,6 +628,18 @@ is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { return s->data & SPECIAL_VALUE_MASK; } +static inline bool +is_empty_glyph(glyph_index glyph_id, Font *font) { + // A glyph is empty if its metrics have a width of zero + SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, EMPTY_FILLED_MASK); + if (s == NULL) return false; + if (!(s->data & EMPTY_FILLED_MASK)) { + uint8_t val = is_glyph_empty(font->face, glyph_id) ? EMPTY_VALUE_MASK : 0; + s->data |= val | EMPTY_FILLED_MASK; + } + return s->data & EMPTY_VALUE_MASK; +} + static inline unsigned int check_cell_consumed(CellData *cell_data, Cell *last_cell) { cell_data->codepoints_consumed++; @@ -676,6 +690,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) { glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint; cluster = G(info)[G(glyph_idx)].cluster; bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); + bool is_empty = is_special && is_empty_glyph(glyph_id, font); uint32_t num_codepoints_used_by_glyph = 0; bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1; Group *current_group = G(groups) + G(group_idx); @@ -689,7 +704,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) { add_to_current_group = true; } else { if (is_special) { - add_to_current_group = G(prev_was_special); + add_to_current_group = G(prev_was_empty); } else { add_to_current_group = !G(prev_was_special); } @@ -734,6 +749,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) { } G(prev_was_special) = is_special; + G(prev_was_empty) = is_empty; G(previous_cluster) = cluster; G(glyph_idx)++; } diff --git a/kitty/fonts.h b/kitty/fonts.h index b01a4816c..58616cc26 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -14,7 +14,9 @@ // API that font backends need to implement +typedef uint16_t glyph_index; unsigned int glyph_id_for_codepoint(PyObject *, char_type); +bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); diff --git a/kitty/freetype.c b/kitty/freetype.c index 051221ed5..c11199559 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -300,6 +300,17 @@ glyph_id_for_codepoint(PyObject *s, char_type cp) { return FT_Get_Char_Index(((Face*)s)->face, cp); } + +bool +is_glyph_empty(PyObject *s, glyph_index g) { + Face *self = (Face*)s; + if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; } +#define M self->face->glyph->metrics + /* printf("glyph: %u horiBearingX: %ld horiBearingY: %ld width: %ld height: %ld\n", g, M.horiBearingX, M.horiBearingY, M.width, M.height); */ + return M.width == 0; +#undef M +} + hb_font_t* harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; } diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index d2d6c8eae..f355ccf4a 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -80,6 +80,7 @@ class Rendering(BaseTest): self.ae(groups('abcd'), [(1, 1) for i in range(4)]) self.ae(groups('A=>>B!=C', path='kitty_tests/FiraCode-Medium.otf'), [(1, 1), (3, 3), (1, 1), (2, 2), (1, 1)]) + self.ae(groups('==!=<>==<><><>', path='kitty_tests/FiraCode-Medium.otf'), [(2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2)]) colon_glyph = shape_string('9:30', path='kitty_tests/FiraCode-Medium.otf')[1][2] self.assertNotEqual(colon_glyph, shape_string(':', path='kitty_tests/FiraCode-Medium.otf')[0][2]) self.ae(colon_glyph, 998)