mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-24 01:08:10 +02:00
Ensure builtin NERD font is used only if glyph is not found by normal font fallback
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user