Special handling for IOSevka's future ligatures
See https://github.com/be5invis/Iosevka/issues/1007
This commit is contained in:
parent
97440d45d6
commit
46a0566e2e
179
kitty/fonts.c
179
kitty/fonts.c
@ -53,7 +53,7 @@ typedef struct {
|
|||||||
static SymbolMap *symbol_maps = NULL;
|
static SymbolMap *symbol_maps = NULL;
|
||||||
static size_t num_symbol_maps = 0;
|
static size_t num_symbol_maps = 0;
|
||||||
|
|
||||||
typedef enum { SPACER_STRATEGY_UNKNOWN, SPACERS_BEFORE, SPACERS_AFTER } SpacerStrategy;
|
typedef enum { SPACER_STRATEGY_UNKNOWN, SPACERS_BEFORE, SPACERS_AFTER, SPACERS_IOSEVKA } SpacerStrategy;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject *face;
|
PyObject *face;
|
||||||
@ -807,12 +807,20 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline LigatureType
|
static inline LigatureType
|
||||||
ligature_type_from_glyph_name(const char *glyph_name) {
|
ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
|
||||||
const char *p = strrchr(glyph_name, '_');
|
const char *p, *m, *s, *e;
|
||||||
if (!p) return LIGATURE_UNKNOWN;
|
if (strategy == SPACERS_IOSEVKA) {
|
||||||
if (strcmp(p, "_middle.seq") == 0) return INFINITE_LIGATURE_MIDDLE;
|
p = strrchr(glyph_name, '.');
|
||||||
if (strcmp(p, "_start.seq") == 0) return INFINITE_LIGATURE_START;
|
m = ".join-m"; s = ".join-l"; e = ".join-r";
|
||||||
if (strcmp(p, "_end.seq") == 0) return INFINITE_LIGATURE_END;
|
} else {
|
||||||
|
p = strrchr(glyph_name, '_');
|
||||||
|
m = "_middle.seq"; s = "_start.seq"; e = "_end.seq";
|
||||||
|
}
|
||||||
|
if (p) {
|
||||||
|
if (strcmp(p, m) == 0) return INFINITE_LIGATURE_MIDDLE;
|
||||||
|
if (strcmp(p, s) == 0) return INFINITE_LIGATURE_START;
|
||||||
|
if (strcmp(p, e) == 0) return INFINITE_LIGATURE_END;
|
||||||
|
}
|
||||||
return LIGATURE_UNKNOWN;
|
return LIGATURE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,19 +838,116 @@ detect_spacer_strategy(hb_font_t *hbf, Font *font) {
|
|||||||
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
|
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
|
||||||
if (is_empty) font->spacer_strategy = SPACERS_AFTER;
|
if (is_empty) font->spacer_strategy = SPACERS_AFTER;
|
||||||
}
|
}
|
||||||
|
shape(cpu_cells, gpu_cells, 2, hbf, font, false);
|
||||||
|
if (G(num_glyphs)) {
|
||||||
|
char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0;
|
||||||
|
for (unsigned i = 0; i < G(num_glyphs); i++) {
|
||||||
|
glyph_index glyph_id = G(info)[i].codepoint;
|
||||||
|
hb_font_glyph_to_string(hbf, glyph_id, glyph_name, arraysz(glyph_name)-1);
|
||||||
|
char *dot = strrchr(glyph_name, '.');
|
||||||
|
if (dot && (!strcmp(dot, ".join-l") || !strcmp(dot, ".join-r") || !strcmp(dot, ".join-m"))) {
|
||||||
|
font->spacer_strategy = SPACERS_IOSEVKA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static LigatureType
|
||||||
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature) {
|
ligature_type_for_glyph(hb_font_t *hbf, glyph_index glyph_id, SpacerStrategy strategy) {
|
||||||
hb_font_t *hbf = harfbuzz_font_for_face(font->face);
|
static char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0;
|
||||||
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font);
|
hb_font_glyph_to_string(hbf, glyph_id, glyph_name, arraysz(glyph_name)-1);
|
||||||
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature);
|
return ligature_type_from_glyph_name(glyph_name, strategy);
|
||||||
#if 0
|
}
|
||||||
static char dbuf[1024];
|
|
||||||
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
|
#define L INFINITE_LIGATURE_START
|
||||||
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
|
#define M INFINITE_LIGATURE_MIDDLE
|
||||||
printf("\n%s\n", dbuf);
|
#define R INFINITE_LIGATURE_END
|
||||||
#endif
|
#define I LIGATURE_UNKNOWN
|
||||||
|
static bool
|
||||||
|
is_iosevka_lig_starter(LigatureType before, LigatureType current, LigatureType after) {
|
||||||
|
return (current == R || (current == I && (after == L || after == M))) \
|
||||||
|
&& \
|
||||||
|
!(before == R || before == M);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_iosevka_lig_ender(LigatureType before, LigatureType current, LigatureType after) {
|
||||||
|
return (current == L || (current == I && (before == R || before == M))) \
|
||||||
|
&& \
|
||||||
|
!(after == L || after == M);
|
||||||
|
}
|
||||||
|
#undef L
|
||||||
|
#undef M
|
||||||
|
#undef R
|
||||||
|
#undef I
|
||||||
|
|
||||||
|
static LigatureType *ligature_types = NULL;
|
||||||
|
static size_t ligature_types_sz = 0;
|
||||||
|
|
||||||
|
static void
|
||||||
|
group_iosevka(Font *font, hb_font_t *hbf) {
|
||||||
|
// Group as per algorithm discussed in: https://github.com/be5invis/Iosevka/issues/1007
|
||||||
|
if (ligature_types_sz <= G(num_glyphs)) {
|
||||||
|
ligature_types_sz = G(num_glyphs) + 16;
|
||||||
|
ligature_types = realloc(ligature_types, ligature_types_sz * sizeof(ligature_types[0]));
|
||||||
|
if (!ligature_types) fatal("Out of memory allocating ligature types array");
|
||||||
|
}
|
||||||
|
for (size_t i = G(glyph_idx); i < G(num_glyphs); i++) {
|
||||||
|
ligature_types[i] = ligature_type_for_glyph(hbf, G(info)[i].codepoint, font->spacer_strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cluster, next_cluster;
|
||||||
|
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
|
||||||
|
cluster = G(info)[G(glyph_idx)].cluster;
|
||||||
|
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);
|
||||||
|
if (is_last_glyph) {
|
||||||
|
num_codepoints_used_by_glyph = UINT32_MAX;
|
||||||
|
next_cluster = 0;
|
||||||
|
} else {
|
||||||
|
next_cluster = G(info)[G(glyph_idx) + 1].cluster;
|
||||||
|
// RTL languages like Arabic have decreasing cluster numbers
|
||||||
|
if (next_cluster != cluster) num_codepoints_used_by_glyph = cluster > next_cluster ? cluster - next_cluster : next_cluster - cluster;
|
||||||
|
}
|
||||||
|
const LigatureType before = G(glyph_idx) ? ligature_types[G(glyph_idx - 1)] : LIGATURE_UNKNOWN;
|
||||||
|
const LigatureType current = ligature_types[G(glyph_idx)];
|
||||||
|
const LigatureType after = is_last_glyph ? LIGATURE_UNKNOWN : ligature_types[G(glyph_idx + 1)];
|
||||||
|
bool end_current_group = false;
|
||||||
|
if (current_group->num_glyphs && is_iosevka_lig_ender(before, current, after)) {
|
||||||
|
end_current_group = true;
|
||||||
|
}
|
||||||
|
if (!current_group->num_glyphs++) {
|
||||||
|
if (is_iosevka_lig_starter(before, current, after)) current_group->has_special_glyph = true;
|
||||||
|
else end_current_group = true;
|
||||||
|
current_group->first_glyph_idx = G(glyph_idx);
|
||||||
|
current_group->first_cell_idx = G(cell_idx);
|
||||||
|
}
|
||||||
|
if (is_last_glyph) {
|
||||||
|
// soak up all remaining cells
|
||||||
|
if (G(cell_idx) < G(num_cells)) {
|
||||||
|
unsigned int num_left = G(num_cells) - G(cell_idx);
|
||||||
|
current_group->num_cells += num_left;
|
||||||
|
G(cell_idx) += num_left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsigned int num_cells_consumed = 0;
|
||||||
|
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
|
||||||
|
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell));
|
||||||
|
G(cell_idx) += w;
|
||||||
|
num_cells_consumed += w;
|
||||||
|
num_codepoints_used_by_glyph--;
|
||||||
|
}
|
||||||
|
current_group->num_cells += num_cells_consumed;
|
||||||
|
}
|
||||||
|
if (end_current_group) G(group_idx)++;
|
||||||
|
G(glyph_idx)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
group_normal(Font *font, hb_font_t *hbf) {
|
||||||
/* Now distribute the glyphs into groups of cells
|
/* Now distribute the glyphs into groups of cells
|
||||||
* Considerations to keep in mind:
|
* Considerations to keep in mind:
|
||||||
* Group sizes should be as small as possible for best performance
|
* Group sizes should be as small as possible for best performance
|
||||||
@ -863,12 +968,10 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
|
|||||||
*/
|
*/
|
||||||
uint32_t cluster, next_cluster;
|
uint32_t cluster, next_cluster;
|
||||||
bool add_to_current_group;
|
bool add_to_current_group;
|
||||||
char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0;
|
|
||||||
bool prev_glyph_was_inifinte_ligature_end = false;
|
bool prev_glyph_was_inifinte_ligature_end = false;
|
||||||
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
|
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
|
||||||
glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint;
|
glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint;
|
||||||
hb_font_glyph_to_string(hbf, glyph_id, glyph_name, arraysz(glyph_name)-1);
|
LigatureType ligature_type = ligature_type_for_glyph(hbf, glyph_id, font->spacer_strategy);
|
||||||
LigatureType ligature_type = ligature_type_from_glyph_name(glyph_name);
|
|
||||||
cluster = G(info)[G(glyph_idx)].cluster;
|
cluster = G(info)[G(glyph_idx)].cluster;
|
||||||
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
|
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
|
||||||
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
|
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
|
||||||
@ -898,15 +1001,14 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
|
|||||||
add_to_current_group = !G(prev_was_special) || !current_group->num_cells;
|
add_to_current_group = !G(prev_was_special) || !current_group->num_cells;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static const bool debug_grouping = false;
|
#if 0
|
||||||
if (debug_grouping) {
|
char ch[8] = {0};
|
||||||
char ch[8] = {0};
|
encode_utf8(G(current_cell_data).current_codepoint, ch);
|
||||||
encode_utf8(G(current_cell_data).current_codepoint, ch);
|
printf("\x1b[32m→ %s\x1b[m glyph_idx: %zu glyph_id: %u (%s) group_idx: %zu cluster: %u -> %u is_special: %d\n"
|
||||||
printf("\x1b[32m→ %s\x1b[m glyph_idx: %zu glyph_id: %u (%s) group_idx: %zu cluster: %u -> %u is_special: %d\n"
|
" num_codepoints_used_by_glyph: %u current_group: (%u cells, %u glyphs) add_to_current_group: %d\n",
|
||||||
" num_codepoints_used_by_glyph: %u current_group: (%u cells, %u glyphs) add_to_current_group: %d\n",
|
ch, G(glyph_idx), glyph_id, glyph_name, G(group_idx), cluster, next_cluster, is_special,
|
||||||
ch, G(glyph_idx), glyph_id, glyph_name, G(group_idx), cluster, next_cluster, is_special,
|
num_codepoints_used_by_glyph, current_group->num_cells, current_group->num_glyphs, add_to_current_group);
|
||||||
num_codepoints_used_by_glyph, current_group->num_cells, current_group->num_glyphs, add_to_current_group);
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
if (!add_to_current_group) { current_group = G(groups) + ++G(group_idx); }
|
if (!add_to_current_group) { current_group = G(groups) + ++G(group_idx); }
|
||||||
if (!current_group->num_glyphs++) {
|
if (!current_group->num_glyphs++) {
|
||||||
@ -953,6 +1055,22 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature) {
|
||||||
|
hb_font_t *hbf = harfbuzz_font_for_face(font->face);
|
||||||
|
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font);
|
||||||
|
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature);
|
||||||
|
if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf);
|
||||||
|
else group_normal(font, hbf);
|
||||||
|
#if 0
|
||||||
|
static char dbuf[1024];
|
||||||
|
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
|
||||||
|
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
|
||||||
|
printf("\n%s\n", dbuf);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
merge_groups_for_pua_space_ligature(void) {
|
merge_groups_for_pua_space_ligature(void) {
|
||||||
while (G(group_idx) > 0) {
|
while (G(group_idx) > 0) {
|
||||||
@ -1300,6 +1418,7 @@ finalize(void) {
|
|||||||
Py_CLEAR(descriptor_for_idx);
|
Py_CLEAR(descriptor_for_idx);
|
||||||
Py_CLEAR(font_feature_settings);
|
Py_CLEAR(font_feature_settings);
|
||||||
free_font_groups();
|
free_font_groups();
|
||||||
|
free(ligature_types);
|
||||||
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
|
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
|
||||||
free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0;
|
free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0;
|
||||||
free(global_glyph_render_scratch.glyphs);
|
free(global_glyph_render_scratch.glyphs);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user