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)