From 912c46fc57362d3bd2a2f82b4b0a2ef939d6e1ec Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Feb 2021 10:36:07 +0530 Subject: [PATCH] Fix rendering of ligatures in the latest release of Cascadia code For some reason it puts empty glyphs after the ligature glyph rather than before it. There is a possibility this fix might break something else, we will see. Fixes #3313 --- docs/changelog.rst | 4 ++++ kitty/fonts.c | 7 +++++-- kitty_tests/fonts.py | 29 ++++++++++++++++++--------- 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 kitty_tests/CascadiaCode-Regular.otf diff --git a/docs/changelog.rst b/docs/changelog.rst index 52c523891..43e821b88 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -64,6 +64,10 @@ To update |kitty|, :doc:`follow the instructions `. - Fix OS window sizes under 100px resulting in scaled display (:iss:`3307`) +- Fix rendering of ligatures in the latest release of Cascadia code, which for + some reason puts empty glyphs after the ligature glyph rather than before it + (:iss:`3313`) + 0.19.3 [2020-12-19] ------------------- diff --git a/kitty/fonts.c b/kitty/fonts.c index 59ee6d692..702cbeb18 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -767,7 +767,7 @@ typedef struct { typedef struct { unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells; - bool has_special_glyph, is_space_ligature; + bool has_special_glyph, is_space_ligature, started_with_empty_glyph; } Group; typedef struct { @@ -905,6 +905,7 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells * Ligature fonts, take two common approaches: * 1. ABC becomes EMPTY, EMPTY, WIDE GLYPH this means we have to render N glyphs in N cells (example Fira Code) * 2. ABC becomes WIDE GLYPH this means we have to render one glyph in N cells (example Operator Mono Lig) + * 3. ABC becomes WIDE GLYPH, EMPTY, EMPTY this means we have to render N glyphs in N cells (example Cascadia Code) * * We rely on the cluster numbers from harfbuzz to tell us how many unicode codepoints a glyph corresponds to. * Then we check if the glyph is a ligature glyph (is_special_glyph) and if it is an empty glyph. These three @@ -933,7 +934,8 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells add_to_current_group = true; } else { if (is_special) { - add_to_current_group = G(prev_was_empty); + if (current_group->started_with_empty_glyph) add_to_current_group = G(prev_was_empty); + else add_to_current_group = is_empty; } else { add_to_current_group = !G(prev_was_special); } @@ -941,6 +943,7 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells if (current_group->num_glyphs >= MAX_GLYPHS_IN_GROUP || current_group->num_cells >= MAX_GLYPHS_IN_GROUP) add_to_current_group = false; if (!add_to_current_group) { G(group_idx)++; current_group = G(groups) + G(group_idx); } + if (is_empty && !current_group->num_glyphs) current_group->started_with_empty_glyph = true; if (!current_group->num_glyphs++) { current_group->first_glyph_idx = G(glyph_idx); current_group->first_cell_idx = G(cell_idx); diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index eea9feaa9..2052d011c 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -4,6 +4,7 @@ import sys import unittest +from functools import partial from kitty.constants import is_macos from kitty.fast_data_types import ( @@ -71,19 +72,29 @@ def test_font_rendering(self): def test_shaping(self): - def groups(text, path=None): - return [x[:2] for x in shape_string(text, path=path)] + def ss(text, font=None): + path = f'kitty_tests/{font}' if font else None + return shape_string(text, path=path) + + def groups(text, font=None): + return [x[:2] for x in ss(text, font)] 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('F--a--', path='kitty_tests/FiraCode-Medium.otf'), [(1, 1), (2, 2), (1, 1), (2, 2)]) - 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(groups('A=>>B!=C', font='FiraCode-Medium.otf'), [(1, 1), (3, 3), (1, 1), (2, 2), (1, 1)]) + self.ae(groups('==!=<>==<><><>', font='FiraCode-Medium.otf'), [(2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2)]) + + for font in ('FiraCode-Medium.otf', 'CascadiaCode-Regular.otf'): + g = partial(groups, font=font) + self.ae(g('A===B!=C'), [(1, 1), (3, 3), (1, 1), (2, 2), (1, 1)]) + self.ae(g('F--a--'), [(1, 1), (2, 2), (1, 1), (2, 2)]) + self.ae(g('===--<>=='), [(3, 3), (2, 2), (2, 2), (2, 2)]) + colon_glyph = ss('9:30', font='FiraCode-Medium.otf')[1][2] + self.assertNotEqual(colon_glyph, ss(':', font='FiraCode-Medium.otf')[0][2]) self.ae(colon_glyph, 998) - self.ae(groups('9:30', path='kitty_tests/FiraCode-Medium.otf'), [(1, 1), (1, 1), (1, 1), (1, 1)]) + self.ae(groups('9:30', font='FiraCode-Medium.otf'), [(1, 1), (1, 1), (1, 1), (1, 1)]) + self.ae(groups('|\U0001F601|\U0001F64f|\U0001F63a|'), [(1, 1), (2, 1), (1, 1), (2, 1), (1, 1), (2, 1), (1, 1)]) - self.ae(groups('He\u0347\u0305llo\u0337,', path='kitty_tests/LiberationMono-Regular.ttf'), + self.ae(groups('He\u0347\u0305llo\u0337,', font='LiberationMono-Regular.ttf'), [(1, 1), (1, 3), (1, 1), (1, 1), (1, 2), (1, 1)]) def test_emoji_presentation(self):