Ensure builtin NERD font is used only if glyph is not found by normal font fallback

This commit is contained in:
Kovid Goyal
2024-07-02 14:18:22 +05:30
parent 66034d54fb
commit a783e5d85a
6 changed files with 108 additions and 28 deletions

View File

@@ -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

View File

@@ -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 */
};

View File

@@ -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;

View File

@@ -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):

View File

@@ -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 */
};

View File

@@ -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: