diff --git a/kitty/core_text.m b/kitty/core_text.m index 568487116..0ba6751b4 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -123,6 +123,24 @@ ft_face(CTFontRef font, float pt_sz, float xdpi, float ydpi) { return ans; } +static inline CTFontRef +find_substitute_face(CFStringRef str, CTFontRef old_font) { + // CTFontCreateForString returns the original font when there are combining + // diacritics in the font and the base character is in the original font, + // so we have to check each character individually + CFIndex len = CFStringGetLength(str), start = 0, amt = len; + while (start < len) { + CTFontRef new_font = CTFontCreateForString(old_font, str, CFRangeMake(start, amt)); + if (amt == len && len != 1) amt = 1; + else start++; + if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; } + if (new_font == old_font) { CFRelease(new_font); continue; } + return new_font; + } + PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font"); + return NULL; +} + static PyObject* face_for_text(PyObject UNUSED *self, PyObject UNUSED *args) { char *text; @@ -133,9 +151,9 @@ face_for_text(PyObject UNUSED *self, PyObject UNUSED *args) { CTFontRef font = PyLong_AsVoidPtr(lp); CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); if (str == NULL) return PyErr_NoMemory(); - CFRange range = CFRangeMake(0, CFStringGetLength(str)); - CTFontRef new_font = CTFontCreateForString(font, str, range); - if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); CFRelease(str); return NULL; } + CTFontRef new_font = find_substitute_face(str, font); + CFRelease(str); + if (new_font == NULL) return NULL; return ft_face(new_font, pt_sz, xdpi, ydpi); } diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index f3681c9da..42b8608f2 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -231,13 +231,13 @@ def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi def test_fallback_font(qtext=None, bold=False, italic=False): set_font_family(override_dpi=(96.0, 96.0)) - for text in (qtext, '你好', 'He\u0347\u0305', '\U0001F929'): - if text: - f = get_fallback_font(text, bold, italic) - try: - print(text, f) - except UnicodeEncodeError: - sys.stdout.buffer.write((text + ' %s\n' % f).encode('utf-8')) + trials = (qtext,) if qtext else ('你好', 'He\u0347\u0305', '\U0001F929') + for text in trials: + f = get_fallback_font(text, bold, italic) + try: + print(text, f) + except UnicodeEncodeError: + sys.stdout.buffer.write((text + ' %s\n' % f).encode('utf-8')) def showcase():