mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-14 12:34:44 +02:00
Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen
We ameliorate the performance hit by storing a hash table mapping cell text to the loaded fallback font index so that lookups for previously seen text are still fast. Fixes #6865
This commit is contained in:
@@ -61,6 +61,8 @@ Detailed list of changes
|
|||||||
|
|
||||||
- macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons (:iss:`6883`)
|
- macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons (:iss:`6883`)
|
||||||
|
|
||||||
|
- Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen (:iss:`6865`)
|
||||||
|
|
||||||
|
|
||||||
0.31.0 [2023-11-08]
|
0.31.0 [2023-11-08]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) {
|
|||||||
new_font = manually_search_fallback_fonts(old_font, cpu_cell);
|
new_font = manually_search_fallback_fonts(old_font, cpu_cell);
|
||||||
if (new_font) return new_font;
|
if (new_font) return new_font;
|
||||||
}
|
}
|
||||||
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
|
PyErr_Format(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font for: %s", [(NSString *)str UTF8String]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return new_font;
|
return new_font;
|
||||||
@@ -270,7 +270,22 @@ create_fallback_face(PyObject *base_face, CPUCell* cell, bool UNUSED bold, bool
|
|||||||
}
|
}
|
||||||
else { search_for_fallback(); }
|
else { search_for_fallback(); }
|
||||||
if (new_font == NULL) return NULL;
|
if (new_font == NULL) return NULL;
|
||||||
return (PyObject*)ct_face(new_font, fg);
|
NSURL *url = (NSURL*)CTFontCopyAttribute(new_font, kCTFontURLAttribute);
|
||||||
|
const char *font_path = [[url path] UTF8String];
|
||||||
|
ssize_t idx = -1;
|
||||||
|
PyObject *q, *ans = NULL;
|
||||||
|
while ((q = iter_fallback_faces(fg, &idx))) {
|
||||||
|
CTFace *qf = (CTFace*)q;
|
||||||
|
const char *qpath;
|
||||||
|
if (qf->path && (qpath = PyUnicode_AsUTF8(qf->path)) && strcmp(qpath, font_path) == 0) {
|
||||||
|
ans = PyLong_FromSsize_t(idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[url release];
|
||||||
|
if (ans == NULL) return (PyObject*)ct_face(new_font, fg);
|
||||||
|
CFRelease(new_font);
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int
|
unsigned int
|
||||||
|
|||||||
@@ -450,7 +450,15 @@ create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool
|
|||||||
size_t num = cell_as_unicode_for_fallback(cell, char_buf);
|
size_t num = cell_as_unicode_for_fallback(cell, char_buf);
|
||||||
add_charset(pat, num);
|
add_charset(pat, num);
|
||||||
PyObject *d = _fc_match(pat);
|
PyObject *d = _fc_match(pat);
|
||||||
if (d) { ans = face_from_descriptor(d, fg); Py_CLEAR(d); }
|
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; }
|
||||||
|
}
|
||||||
|
ans = face_from_descriptor(d, fg);
|
||||||
|
Py_CLEAR(d);
|
||||||
|
}
|
||||||
end:
|
end:
|
||||||
if (pat != NULL) FcPatternDestroy(pat);
|
if (pat != NULL) FcPatternDestroy(pat);
|
||||||
return ans;
|
return ans;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "unicode-data.h"
|
#include "unicode-data.h"
|
||||||
#include "charsets.h"
|
#include "charsets.h"
|
||||||
#include "glyph-cache.h"
|
#include "glyph-cache.h"
|
||||||
|
#include "kitty-uthash.h"
|
||||||
|
|
||||||
#define MISSING_GLYPH (NUM_UNDERLINE_STYLES + 2)
|
#define MISSING_GLYPH (NUM_UNDERLINE_STYLES + 2)
|
||||||
#define MAX_NUM_EXTRA_GLYPHS_PUA 4u
|
#define MAX_NUM_EXTRA_GLYPHS_PUA 4u
|
||||||
@@ -71,6 +72,12 @@ typedef struct Canvas {
|
|||||||
unsigned current_cells, alloced_cells;
|
unsigned current_cells, alloced_cells;
|
||||||
} Canvas;
|
} Canvas;
|
||||||
|
|
||||||
|
typedef struct fallback_font_map {
|
||||||
|
const char *cell_text;
|
||||||
|
size_t font_idx;
|
||||||
|
UT_hash_handle hh;
|
||||||
|
} fallback_font_map_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FONTS_DATA_HEAD
|
FONTS_DATA_HEAD
|
||||||
id_type id;
|
id_type id;
|
||||||
@@ -80,6 +87,7 @@ typedef struct {
|
|||||||
Font *fonts;
|
Font *fonts;
|
||||||
Canvas canvas;
|
Canvas canvas;
|
||||||
GPUSpriteTracker sprite_tracker;
|
GPUSpriteTracker sprite_tracker;
|
||||||
|
fallback_font_map_t *fallback_font_map;
|
||||||
} FontGroup;
|
} FontGroup;
|
||||||
|
|
||||||
static FontGroup* font_groups = NULL;
|
static FontGroup* font_groups = NULL;
|
||||||
@@ -152,6 +160,15 @@ static void
|
|||||||
del_font_group(FontGroup *fg) {
|
del_font_group(FontGroup *fg) {
|
||||||
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
|
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
|
||||||
fg->sprite_map = free_sprite_map(fg->sprite_map);
|
fg->sprite_map = free_sprite_map(fg->sprite_map);
|
||||||
|
if (fg->fallback_font_map) {
|
||||||
|
fallback_font_map_t *current, *tmp;
|
||||||
|
HASH_ITER(hh, fg->fallback_font_map, current, tmp) {
|
||||||
|
free((void*)current->cell_text);
|
||||||
|
HASH_DEL(fg->fallback_font_map, current);
|
||||||
|
free(current);
|
||||||
|
}
|
||||||
|
fg->fallback_font_map = NULL;
|
||||||
|
}
|
||||||
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
|
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
|
||||||
free(fg->fonts); fg->fonts = NULL;
|
free(fg->fonts); fg->fonts = NULL;
|
||||||
}
|
}
|
||||||
@@ -451,6 +468,16 @@ output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_pres
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx) {
|
||||||
|
FontGroup *fg = (FontGroup*)fgh;
|
||||||
|
if (*idx + 1 < (ssize_t)fg->fallback_fonts_count) {
|
||||||
|
*idx += 1;
|
||||||
|
return fg->fonts[fg->first_fallback_font_idx + *idx].face;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
|
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
|
||||||
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
|
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
|
||||||
@@ -464,6 +491,7 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em
|
|||||||
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
|
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
|
||||||
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
|
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
|
||||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face, true);
|
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face, true);
|
||||||
|
if (PyLong_Check(face)) { ssize_t ans = fg->first_fallback_font_idx + PyLong_AsSsize_t(face); Py_DECREF(face); return ans; }
|
||||||
set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg);
|
set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg);
|
||||||
|
|
||||||
ensure_space_for(fg, fonts, Font, fg->fonts_count + 1, fonts_capacity, 5, true);
|
ensure_space_for(fg, fonts, Font, fg->fonts_count + 1, fonts_capacity, 5, true);
|
||||||
@@ -495,17 +523,24 @@ fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
|
|||||||
bool bold = gpu_cell->attrs.bold;
|
bool bold = gpu_cell->attrs.bold;
|
||||||
bool italic = gpu_cell->attrs.italic;
|
bool italic = gpu_cell->attrs.italic;
|
||||||
bool emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
|
bool emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
|
||||||
|
char cell_text[6 + arraysz(cpu_cell->cc_idx) * 4];
|
||||||
// Check if one of the existing fallback fonts has this text
|
const size_t cell_text_len = cell_as_utf8(cpu_cell, true, cell_text, ' ');
|
||||||
for (size_t i = 0, j = fg->first_fallback_font_idx; i < fg->fallback_fonts_count; i++, j++) {
|
if (fg->fallback_font_map) {
|
||||||
Font *ff = fg->fonts +j;
|
fallback_font_map_t *s;
|
||||||
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cpu_cell)) {
|
HASH_FIND_STR(fg->fallback_font_map, cell_text, s);
|
||||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cpu_cell, bold, italic, emoji_presentation, ff->face, false);
|
/* printf("cache %s\n", (s ? "hit" : "miss")); */
|
||||||
return j;
|
if (s) return s->font_idx;
|
||||||
|
}
|
||||||
|
ssize_t idx = load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
|
||||||
|
fallback_font_map_t *ffm = calloc(1, sizeof(fallback_font_map_t));
|
||||||
|
if (ffm) {
|
||||||
|
ffm->font_idx = idx;
|
||||||
|
ffm->cell_text = strndup(cell_text, cell_text_len);
|
||||||
|
if (ffm->cell_text) {
|
||||||
|
HASH_ADD_KEYPTR(hh, fg->fallback_font_map, ffm->cell_text, cell_text_len, ffm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return idx;
|
||||||
return load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bo
|
|||||||
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
|
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
|
||||||
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
|
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
|
||||||
PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE);
|
PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE);
|
||||||
|
PyObject* iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx);
|
||||||
|
bool face_equals_descriptor(PyObject *face_, PyObject *descriptor);
|
||||||
const char* postscript_name_for_face(const PyObject*);
|
const char* postscript_name_for_face(const PyObject*);
|
||||||
|
|
||||||
void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z);
|
void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z);
|
||||||
|
|||||||
@@ -218,6 +218,17 @@ set_load_error(const char *path, int error) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
||||||
|
Face *face = (Face*)face_;
|
||||||
|
PyObject *t = PyDict_GetItemString(descriptor, "path");
|
||||||
|
if (!t) return false;
|
||||||
|
if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false;
|
||||||
|
t = PyDict_GetItemString(descriptor, "index");
|
||||||
|
if (t && PyLong_AsLong(t) != face->index) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
||||||
#define D(key, conv, missing_ok) { \
|
#define D(key, conv, missing_ok) { \
|
||||||
@@ -285,7 +296,6 @@ repr(Face *self) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const char*
|
const char*
|
||||||
postscript_name_for_face(const PyObject *face_) {
|
postscript_name_for_face(const PyObject *face_) {
|
||||||
const Face *self = (const Face*)face_;
|
const Face *self = (const Face*)face_;
|
||||||
@@ -723,7 +733,6 @@ static PyMethodDef methods[] = {
|
|||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
PyTypeObject Face_Type = {
|
PyTypeObject Face_Type = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
.tp_name = "fast_data_types.Face",
|
.tp_name = "fast_data_types.Face",
|
||||||
|
|||||||
Reference in New Issue
Block a user