diff --git a/kitty/cleanup.h b/kitty/cleanup.h index 0e1cec4bf..58ae26608 100644 --- a/kitty/cleanup.h +++ b/kitty/cleanup.h @@ -12,11 +12,11 @@ typedef enum { STATE_CLEANUP_FUNC, GLFW_CLEANUP_FUNC, DESKTOP_CLEANUP_FUNC, - FREETYPE_CLEANUP_FUNC, CORE_TEXT_CLEANUP_FUNC, COCOA_CLEANUP_FUNC, PNG_READER_CLEANUP_FUNC, FONTCONFIG_CLEANUP_FUNC, + FREETYPE_CLEANUP_FUNC, SYSTEMD_CLEANUP_FUNC, NUM_CLEANUP_FUNCS diff --git a/kitty/core_text.m b/kitty/core_text.m index 385624d61..aae74ec8b 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -300,7 +300,7 @@ is_last_resort_font(CTFontRef new_font) { return ans; } -static CTFontDescriptorRef _nerd_font_descriptor = NULL; +static CTFontDescriptorRef _nerd_font_descriptor = NULL, builtin_nerd_font_descriptor = NULL; static CTFontRef nerd_font(CGFloat sz) { static bool searched = false; @@ -320,7 +320,9 @@ static CTFontRef nerd_font(CGFloat sz) { } CFRelease(fonts); } - return _nerd_font_descriptor ? CTFontCreateWithFontDescriptor(_nerd_font_descriptor, sz, NULL) : NULL; + if (_nerd_font_descriptor) return CTFontCreateWithFontDescriptor(_nerd_font_descriptor, sz, NULL); + if (builtin_nerd_font_descriptor) return CTFontCreateWithFontDescriptor(builtin_nerd_font_descriptor, sz, NULL); + return NULL; } static bool ctfont_has_codepoint(const void *ctfont, char_type cp) { return glyph_id_for_codepoint_ctfont(ctfont, cp) > 0; } @@ -353,6 +355,13 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) { CFRelease(new_font); } CFRelease(fonts); + if (!ans) { + CTFontRef nf = nerd_font(CTFontGetSize(current_font)); + if (nf) { + if (font_can_render_cell(nf, cell)) ans = nf; + else CFRelease(nf); + } + } return ans; } @@ -684,6 +693,8 @@ finalize(void) { if (window_title_font) CFRelease(window_title_font); window_title_font = nil; if (_nerd_font_descriptor) CFRelease(_nerd_font_descriptor); + if (builtin_nerd_font_descriptor) CFRelease(builtin_nerd_font_descriptor); + _nerd_font_descriptor = NULL; builtin_nerd_font_descriptor = NULL; } @@ -1102,7 +1113,7 @@ repr(CTFace *self) { static PyObject* -coretext_add_font_file(PyObject UNUSED *_self, PyObject *args) { +add_font_file(PyObject UNUSED *_self, PyObject *args) { const unsigned char *path = NULL; Py_ssize_t sz; if (!PyArg_ParseTuple(args, "s#", &path, &sz)) return NULL; RAII_CoreFoundation(CFURLRef, url, CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, sz, false)); @@ -1110,9 +1121,27 @@ coretext_add_font_file(PyObject UNUSED *_self, PyObject *args) { Py_RETURN_FALSE; } +static PyObject* +set_builtin_nerd_font(PyObject UNUSED *self, PyObject *pypath) { + if (!PyUnicode_Check(pypath)) { PyErr_SetString(PyExc_TypeError, "path must be a string"); return NULL; } + const char *path = NULL; Py_ssize_t sz; + path = PyUnicode_AsUTF8AndSize(pypath, &sz); + RAII_CoreFoundation(CFURLRef, url, CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const unsigned char*)path, sz, false)); + RAII_CoreFoundation(CFArrayRef, descriptors, CTFontManagerCreateFontDescriptorsFromURL(url)); + if (!descriptors || CFArrayGetCount(descriptors) == 0) { + PyErr_SetString(PyExc_OSError, "Failed to create descriptor from nerd font path"); + return NULL; + } + if (builtin_nerd_font_descriptor) CFRelease(builtin_nerd_font_descriptor); + builtin_nerd_font_descriptor = CFArrayGetValueAtIndex(descriptors, 0); + CFRetain(builtin_nerd_font_descriptor); + Py_RETURN_NONE; +} + static PyMethodDef module_methods[] = { METHODB(coretext_all_fonts, METH_O), - METHODB(coretext_add_font_file, METH_VARARGS), + METHODB(add_font_file, METH_VARARGS), + METHODB(set_builtin_nerd_font, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/data-types.c b/kitty/data-types.c index c0414d2dd..64e3dbfb8 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -463,13 +463,18 @@ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; +static void +free_fast_data_types_module(void *m UNUSED) { + run_at_exit_cleanup_functions(); +} static struct PyModuleDef module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "fast_data_types", /* name of module */ .m_doc = NULL, .m_size = -1, - .m_methods = module_methods + .m_methods = module_methods, + .m_free = free_fast_data_types_module, }; @@ -531,10 +536,6 @@ PyInit_fast_data_types(void) { m = PyModule_Create(&module); if (m == NULL) return NULL; - if (Py_AtExit(run_at_exit_cleanup_functions) != 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to register the atexit cleanup handler"); - return NULL; - } init_monotonic(); if (!init_logging(m)) return NULL; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 5411d5c7a..ed6d1e442 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -425,8 +425,8 @@ def fc_match_postscript_name( pass -def fc_add_font_file(path: str) -> bool: ... -def coretext_add_font_file(path: str) -> bool: ... +def add_font_file(path: str) -> bool: ... +def set_builtin_nerd_font(path: str) -> None: ... class FeatureData(TypedDict): diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 152c0bee7..2dd061ae6 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -18,6 +18,7 @@ static bool initialized = false; static void* libfontconfig_handle = NULL; +static struct {PyObject *face, *descriptor;} builtin_nerd_font = {0}; #define FcInit dynamically_loaded_fc_symbol.Init #define FcFini dynamically_loaded_fc_symbol.Fini @@ -133,6 +134,8 @@ ensure_initialized(void) { static void finalize(void) { if (initialized) { + Py_CLEAR(builtin_nerd_font.face); + Py_CLEAR(builtin_nerd_font.descriptor); FcFini(); dlclose(libfontconfig_handle); libfontconfig_handle = NULL; @@ -472,37 +475,83 @@ PyObject* create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { ensure_initialized(); PyObject *ans = NULL; + RAII_PyObject(d, NULL); FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); + bool glyph_found = false; AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji_presentation ? "emoji" : "monospace"), "family"); if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); } size_t num = cell_as_unicode_for_fallback(cell, char_buf); add_charset(pat, num); - PyObject *d = _fc_match(pat); + d = _fc_match(pat); +face_from_descriptor: if (d) { ssize_t idx = -1; PyObject *q; while ((q = iter_fallback_faces(fg, &idx))) { - if (face_equals_descriptor(q, d)) { ans = PyLong_FromSsize_t(idx); Py_CLEAR(d); goto end; } + if (face_equals_descriptor(q, d)) { + ans = PyLong_FromSsize_t(idx); + if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, cell, false); + goto end; + } } ans = face_from_descriptor(d, fg); - Py_CLEAR(d); + if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, ans, cell, false); } end: - if (pat != NULL) FcPatternDestroy(pat); - if (ans && !has_cell_text(face_has_codepoint, ans, cell, global_state.debug_font_fallback)) { - Py_CLEAR(ans); - Py_RETURN_NONE; + Py_CLEAR(d); + if (pat != NULL) { FcPatternDestroy(pat); pat = NULL; } + if (!glyph_found && !PyErr_Occurred()) { + if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, cell, false)) { + Py_CLEAR(ans); + d = builtin_nerd_font.descriptor; Py_INCREF(d); glyph_found = true; goto face_from_descriptor; + } else { + if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, cell, true); + Py_CLEAR(ans); ans = Py_None; Py_INCREF(ans); + } } return ans; } -#undef AP static PyObject* -fc_add_font_file(PyObject UNUSED *self, PyObject *args) { +set_builtin_nerd_font(PyObject UNUSED *self, PyObject *pypath) { + if (!PyUnicode_Check(pypath)) { PyErr_SetString(PyExc_TypeError, "path must be a string"); return NULL; } + ensure_initialized(); + const char *path = PyUnicode_AsUTF8(pypath); + FcPattern *pat = FcPatternCreate(); + if (pat == NULL) return PyErr_NoMemory(); + Py_CLEAR(builtin_nerd_font.face); + Py_CLEAR(builtin_nerd_font.descriptor); + + builtin_nerd_font.face = face_from_path(path, 0, NULL); + if (builtin_nerd_font.face) { + // Copy whatever hinting settings fontconfig returns for the nerd font postscript name + AP(FcPatternAddString, FC_POSTSCRIPT_NAME, (const unsigned char*)postscript_name_for_face(builtin_nerd_font.face), "postscript_name"); + RAII_PyObject(d, _fc_match(pat)); + if (!d) goto end; + builtin_nerd_font.descriptor = PyDict_New(); + if (!builtin_nerd_font.descriptor) goto end; +#define copy(key) { PyObject *t = PyDict_GetItemString(d, #key); if (t) { if (PyDict_SetItemString(builtin_nerd_font.descriptor, #key, t) != 0)goto end; } } + copy(hinting); copy(hint_style); +#undef copy + if (PyDict_SetItemString(builtin_nerd_font.descriptor, "path", pypath) != 0) goto end; + } +end: + if (pat) FcPatternDestroy(pat); + if (PyErr_Occurred()) { + Py_CLEAR(builtin_nerd_font.face); + Py_CLEAR(builtin_nerd_font.descriptor); + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +add_font_file(PyObject UNUSED *self, PyObject *args) { ensure_initialized(); const char *path = NULL; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; @@ -511,11 +560,14 @@ fc_add_font_file(PyObject UNUSED *self, PyObject *args) { } +#undef AP + static PyMethodDef module_methods[] = { {"fc_list", (PyCFunction)(void (*) (void))(fc_list), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(fc_match, METH_VARARGS), METHODB(fc_match_postscript_name, METH_VARARGS), - METHODB(fc_add_font_file, METH_VARARGS), + METHODB(add_font_file, METH_VARARGS), + METHODB(set_builtin_nerd_font, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 5e8d4342a..9734149ce 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -16,6 +16,7 @@ from kitty.fast_data_types import ( current_fonts, get_fallback_font, get_options, + set_builtin_nerd_font, set_font_data, set_options, set_send_sprite_to_gpu, @@ -33,12 +34,8 @@ from kitty.utils import log_error from .common import get_font_files if is_macos: - from kitty.fast_data_types import coretext_add_font_file as add_font_file - from .core_text import font_for_family as font_for_family_macos else: - from kitty.fast_data_types import fc_add_font_file as add_font_file - from .fontconfig import font_for_family as font_for_family_fontconfig FontObject = Union[CoreTextFont, FontConfigPattern] @@ -202,8 +199,9 @@ def add_application_fonts() -> None: for font in ('SymbolsNerdFontMono-Regular.ttf',): path = os.path.join(fonts_dir, font) if os.path.exists(path): - if not add_font_file(path): - log_error(f'Failed to add application font: {path}') + set_builtin_nerd_font(path) + else: + log_error(f'No builtin NERD font found in {fonts_dir}') if TYPE_CHECKING: