From 78d1275601ebe7f42dd47d288710e21754dd290a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 29 Jun 2024 14:22:04 +0530 Subject: [PATCH] macOS: Fix rendering of the unicode hyphen (U+2010) character when using a font that does not include a glyph for it The problem was caused by CoreText falling back to the glyph for the ASCII hyphen U+00AD when the font does not contain a glyph for U+2010. However, HarfBuzz does not do this automatic fallback (see https://github.com/harfbuzz/harfbuzz/issues/517). This leads to the character not being rendered. To fix this we specialize HarfBuzz glyph lookup for this character to follow CoreText. HarfBuzz should really do this automatically when the hb_font is based on a CTFontRef, but I dont have the time/energy to argue with its maintainers. Note that HarfBuzz already does this automatic fallback for U+2011. Hopefully, there aren't many more such special cases in CoreText. Fixes #7525 --- docs/changelog.rst | 2 ++ kitty/core_text.m | 48 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f979ae296..199726c55 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -80,6 +80,8 @@ Detailed list of changes - Speed up ``kitty --version`` and ``kitty --single-instance`` (for all subsequent instances). They are now the fastest of all terminal emulators with similar functionality. +- macOS: Fix rendering of the unicode hyphen (U+2010) character when using a font that does not include a glyph for it (:iss:`7525`) + 0.35.2 [2024-06-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/core_text.m b/kitty/core_text.m index 8ba1aef0f..88ec9bda3 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -512,16 +512,56 @@ set_size(CTFace *self, PyObject *args) { Py_RETURN_NONE; } +// CoreText delegates U+2010 to U+00AD if the font is missing U+2010. Example +// of such a font is Fira Code. So we specialize HarfBuzz glyph lookup to take +// this into account. +static hb_bool_t +get_nominal_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t *glyph, void *user_data) { + hb_font_t *parent_font = font_data; (void)user_data; (void)font; + hb_bool_t ans = hb_font_get_nominal_glyph(parent_font, unicode, glyph); + if (!ans && unicode == 0x2010) { + CTFontRef ct_font = hb_coretext_font_get_ct_font(parent_font); + unsigned int gid = glyph_id_for_codepoint_ctfont(ct_font, unicode); + if (gid > 0) { + ans = true; *glyph = gid; + } + } + return ans; +} + +static hb_bool_t +get_variation_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data) { + hb_font_t *parent_font = font_data; (void)user_data; (void)font; + hb_bool_t ans = hb_font_get_variation_glyph(parent_font, unicode, variation, glyph); + if (!ans && unicode == 0x2010) { + CTFontRef ct_font = hb_coretext_font_get_ct_font(parent_font); + unsigned int gid = glyph_id_for_codepoint_ctfont(ct_font, unicode); + if (gid > 0) { + ans = true; *glyph = gid; + } + } + return ans; +} + + hb_font_t* harfbuzz_font_for_face(PyObject* s) { CTFace *self = (CTFace*)s; if (!self->hb_font) { - self->hb_font = hb_coretext_font_create(self->ct_font); - if (!self->hb_font) fatal("Failed to create hb_font"); + hb_font_t *hb = hb_coretext_font_create(self->ct_font); + if (!hb) fatal("Failed to create hb_font_t"); // dunno if we need this, harfbuzz docs say it is used by CoreText // for optical sizing which changes the look of glyphs at small and large sizes - hb_font_set_ptem(self->hb_font, self->scaled_point_sz); - hb_ot_font_set_funcs(self->hb_font); + hb_font_set_ptem(hb, self->scaled_point_sz); + // Setup CoreText compatible glyph lookup functions + self->hb_font = hb_font_create_sub_font(hb); + if (!self->hb_font) fatal("Failed to create sub hb_font_t"); + hb_font_funcs_t *ffunctions = hb_font_funcs_create(); + hb_font_set_funcs(self->hb_font, ffunctions, hb, NULL); + hb_font_funcs_set_nominal_glyph_func(ffunctions, get_nominal_glyph, NULL, NULL); + hb_font_funcs_set_variation_glyph_func(ffunctions, get_variation_glyph, NULL, NULL); + hb_font_funcs_destroy(ffunctions); // sub font retains a reference to this + hb_font_destroy(hb); // the sub font retains a reference to the parent font } return self->hb_font; }