diff --git a/docs/changelog.rst b/docs/changelog.rst index 2f1618186..dfcd2eb21 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,10 @@ Changelog 0.12.1 [future] ------------------------------ +- macOS: Fix a character that cannot be rendered in any font causing + font fallback for all subsequent characters that cannot be rendered in the + main font to fail (:iss:`799`) + - Allow mapping shortcuts using the raw key code from the OS (:iss:`848`) - Allow mapping of individual keypresses without modifiers as shortcuts diff --git a/kitty/core_text.m b/kitty/core_text.m index 9f931eb85..6a3699002 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -171,6 +171,14 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) { 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; } + CFStringRef name = CTFontCopyPostScriptName(new_font); + CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0); + CFRelease(name); + if (cr == kCFCompareEqualTo) { + CFRelease(new_font); + PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font"); + return NULL; + } return new_font; } PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font"); diff --git a/kitty/fonts.c b/kitty/fonts.c index 32934b5a2..b0334da6a 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -1280,6 +1280,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) { if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT; FontGroup *fg = font_groups; ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell); + if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; } if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } return fg->fonts[ans].face; } diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index 95f7a5e8c..3f6ac04bc 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -2,10 +2,13 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal +import sys +import unittest + from kitty.constants import is_macos from kitty.fast_data_types import ( - DECAWM, sprite_map_set_layout, sprite_map_set_limits, test_render_line, - test_sprite_position_for, wcwidth + DECAWM, get_fallback_font, sprite_map_set_layout, sprite_map_set_limits, + test_render_line, test_sprite_position_for, wcwidth ) from kitty.fonts.box_drawing import box_chars from kitty.fonts.render import render_string, setup_for_testing, shape_string @@ -106,3 +109,16 @@ class Rendering(BaseTest): s.draw('\ufe0f') self.ae((s.cursor.x, s.cursor.y), (2, 4)) self.ae(str(s.line(s.cursor.y)), '\u2716\ufe0f') + + @unittest.skipUnless(is_macos, 'Only macOS has a Last Resort font') + def test_fallback_font_not_last_resort(self): + # Ensure that the LastResort font is not reported as a fallback font on + # macOS. See https://github.com/kovidgoyal/kitty/issues/799 + from io import StringIO + orig, buf = sys.stderr, StringIO() + sys.stderr = buf + try: + self.assertRaises(ValueError, get_fallback_font, '\U0010FFFF', False, False) + finally: + sys.stderr = orig + self.assertIn('LastResort', buf.getvalue())