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.
This commit is contained in:
Kovid Goyal 2018-01-13 10:41:54 +05:30
parent 404ca23de3
commit 1ae7ae4a1d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 47 additions and 8 deletions

View File

@ -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;

View File

@ -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)++;
}

View File

@ -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*);

View File

@ -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; }

View File

@ -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)