diff --git a/kitty/core_text.m b/kitty/core_text.m index 0ba6751b4..f39e773a5 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -5,6 +5,7 @@ * Distributed under terms of the GPL3 license. */ +#include "state.h" #include "fonts.h" #include #include @@ -111,13 +112,13 @@ free_font(void *f) { } static inline PyObject* -ft_face(CTFontRef font, float pt_sz, float xdpi, float ydpi) { +ft_face(CTFontRef font) { const char *psname = convert_cfstring(CTFontCopyPostScriptName(font), 1); NSURL *url = (NSURL*)CTFontCopyAttribute(font, kCTFontURLAttribute); PyObject *path = PyUnicode_FromString([[url path] UTF8String]); [url release]; if (path == NULL) { CFRelease(font); return NULL; } - PyObject *ans = ft_face_from_path_and_psname(path, psname, (void*)font, free_font, true, 3, pt_sz, xdpi, ydpi, CTFontGetLeading(font)); + PyObject *ans = ft_face_from_path_and_psname(path, psname, (void*)font, free_font, true, 3, CTFontGetLeading(font)); Py_DECREF(path); if (ans == NULL) { CFRelease(font); } return ans; @@ -141,43 +142,43 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) { return NULL; } -static PyObject* -face_for_text(PyObject UNUSED *self, PyObject UNUSED *args) { - char *text; - PyObject *lp; - float pt_sz, xdpi, ydpi; - int bold, italic; - if (!PyArg_ParseTuple(args, "sO!fffpp", &text, &PyLong_Type, &lp, &pt_sz, &xdpi, &ydpi, &bold, &italic)) return NULL; +PyObject* +create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) { + PyObject *lp = PyObject_CallMethod(base_face, "extra_data", NULL); + if (lp == NULL) return NULL; CTFontRef font = PyLong_AsVoidPtr(lp); + Py_CLEAR(lp); + static char text[128]; + cell_as_utf8(cell, true, text, ' '); CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); if (str == NULL) return PyErr_NoMemory(); CTFontRef new_font = find_substitute_face(str, font); CFRelease(str); if (new_font == NULL) return NULL; - return ft_face(new_font, pt_sz, xdpi, ydpi); + return ft_face(new_font); } -static PyObject* -create_face(PyObject UNUSED *self, PyObject *args) { - PyObject *descriptor; - float point_sz, xdpi, ydpi; - if(!PyArg_ParseTuple(args, "Offf", &descriptor, &point_sz, &xdpi, &ydpi)) return NULL; - +PyObject* +face_from_descriptor(PyObject *descriptor) { CTFontDescriptorRef desc = font_descriptor_from_python(descriptor); if (!desc) return NULL; - float scaled_point_sz = ((xdpi + ydpi) / 144.0) * point_sz; + float scaled_point_sz = ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts; CTFontRef font = CTFontCreateWithFontDescriptor(desc, scaled_point_sz, NULL); CFRelease(desc); desc = NULL; if (!font) { PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); return NULL; } - return ft_face(font, point_sz, xdpi, ydpi); + return ft_face(font); +} + +PyObject* +specialize_font_descriptor(PyObject *base_descriptor) { + Py_INCREF(base_descriptor); + return base_descriptor; } // Boilerplate {{{ static PyMethodDef module_methods[] = { METHODB(coretext_all_fonts, METH_NOARGS), - METHODB(face_for_text, METH_VARARGS), - METHODB(create_face, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index b80b5b403..ac1906a15 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -5,7 +5,9 @@ * Distributed under terms of the GPL3 license. */ -#include "data-types.h" +#include "state.h" +#include "lineops.h" +#include "fonts.h" #include #ifndef FC_COLOR #define FC_COLOR "color" @@ -117,16 +119,16 @@ end: return ans; } +static Py_UCS4 char_buf[1024]; + static inline void -add_charset(PyObject *characters, FcPattern *pat) { +add_charset(FcPattern *pat, size_t num) { FcCharSet *charset = NULL; - if (PyUnicode_READY(characters) != 0) goto end; - if (PyUnicode_GET_LENGTH(characters) > 0) { + if (num) { charset = FcCharSetCreate(); if (charset == NULL) { PyErr_NoMemory(); goto end; } - int kind = PyUnicode_KIND(characters); void *data = PyUnicode_DATA(characters); - for (int i = 0; i < PyUnicode_GET_LENGTH(characters); i++) { - if (!FcCharSetAddChar(charset, PyUnicode_READ(kind, data, i))) { + for (size_t i = 0; i < num; i++) { + if (!FcCharSetAddChar(charset, char_buf[i])) { PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); goto end; } @@ -142,11 +144,10 @@ fc_match(PyObject UNUSED *self, PyObject *args) { char *family = NULL; int bold = 0, italic = 0, allow_bitmapped_fonts = 0; double size_in_pts = 0, dpi = 0; - PyObject *characters = NULL; FcPattern *pat = NULL; PyObject *ans = NULL; - if (!PyArg_ParseTuple(args, "|zpppdO!d", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &PyUnicode_Type, &characters, &dpi)) return NULL; + if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); @@ -159,7 +160,6 @@ fc_match(PyObject UNUSED *self, PyObject *args) { if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); } if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } - if (characters) add_charset(characters, pat); ans = _fc_match(pat); end: @@ -167,22 +167,36 @@ end: return ans; } -static PyObject* -fc_font(PyObject UNUSED *self, PyObject *args) { - double size_in_pts, dpi; - int index; - char *path; - PyObject *ans = NULL, *chars = NULL; - if (!PyArg_ParseTuple(args, "ddsi|O!", &size_in_pts, &dpi, &path, &index, &PyUnicode_Type, &chars)) return NULL; +PyObject* +specialize_font_descriptor(PyObject *base_descriptor) { + PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL; + PyObject *idx = PyDict_GetItemString(base_descriptor, "index"); + if (p == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no path"); return NULL; } + if (idx == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no index"); return NULL; } FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); - if (size_in_pts > 0) { AP(FcPatternAddDouble, FC_SIZE, size_in_pts, "size"); } - if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); } - AP(FcPatternAddString, FC_FILE, (const FcChar8*)path, "path"); - AP(FcPatternAddInteger, FC_INDEX, index, "index"); - if (chars) add_charset(chars, pat); + AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path"); + AP(FcPatternAddInteger, FC_INDEX, PyLong_AsLong(idx), "index"); + AP(FcPatternAddDouble, FC_SIZE, global_state.font_sz_in_pts, "size"); + AP(FcPatternAddDouble, FC_DPI, (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.0, "dpi"); ans = _fc_match(pat); +end: + if (pat != NULL) FcPatternDestroy(pat); + return ans; +} +PyObject* +create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic) { + PyObject *ans = NULL; + FcPattern *pat = FcPatternCreate(); + if (pat == NULL) return PyErr_NoMemory(); + AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family"); + if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } + if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } + size_t num = cell_as_unicode(cell, true, char_buf, ' '); + add_charset(pat, num); + PyObject *d = _fc_match(pat); + if (d) { ans = face_from_descriptor(d); Py_CLEAR(d); } end: if (pat != NULL) FcPatternDestroy(pat); return ans; @@ -192,7 +206,6 @@ end: static PyMethodDef module_methods[] = { METHODB(fc_list, METH_VARARGS), METHODB(fc_match, METH_VARARGS), - METHODB(fc_font, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fonts.c b/kitty/fonts.c index 1e3b74e59..e73bd97ed 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -149,12 +149,24 @@ sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height) { sprite_tracker.x = 0; sprite_tracker.y = 0; sprite_tracker.z = 0; } +static inline PyObject* +desc_to_face(PyObject *desc) { + PyObject *d = specialize_font_descriptor(desc); + if (d == NULL) return NULL; + PyObject *ans = face_from_descriptor(d); + Py_DECREF(d); + return ans; +} + static inline bool -alloc_font(Font *f, PyObject *face, bool bold, bool italic) { - f->face = face; Py_INCREF(face); +alloc_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face) { + PyObject *face; + if (is_face) { face = descriptor; Py_INCREF(face); } + else { face = desc_to_face(descriptor); if (face == NULL) return false; } + f->face = face; f->hb_font = harfbuzz_font_for_face(face); - if (f->hb_font == NULL) return false; + if (f->hb_font == NULL) { PyErr_NoMemory(); return false; } f->bold = bold; f->italic = italic; return true; } @@ -178,7 +190,6 @@ clear_font(Font *f) { static Font medium_font = {0}, bold_font = {0}, italic_font = {0}, bi_font = {0}, box_font = {0}; static Font fallback_fonts[256] = {{0}}; -static PyObject *get_fallback_font = NULL; typedef enum { FONT, BLANK_FONT, BOX_FONT, MISSING_FONT } FontType; typedef struct { @@ -206,16 +217,9 @@ python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) static inline PyObject* -update_cell_metrics(float pt_sz, float xdpi, float ydpi) { -#define CALL(f) { if ((f)->face) { if(!set_size_for_face((f)->face, pt_sz, xdpi, ydpi)) return NULL; (f)->hb_font = harfbuzz_font_for_face((f)->face); } clear_sprite_map((f)); } - CALL(&medium_font); CALL(&bold_font); CALL(&italic_font); CALL(&bi_font); CALL(&box_font); - for (size_t i = 0; fallback_fonts[i].face != NULL; i++) { - CALL(fallback_fonts + i); - } - for (size_t i = 0; i < symbol_map_fonts_count; i++) { - CALL(symbol_map_fonts + i); - } -#undef CALL +update_cell_metrics() { +#define CALL(f, desired_height) { if ((f)->face) { if(!set_size_for_face((f)->face, desired_height)) return NULL; (f)->hb_font = harfbuzz_font_for_face((f)->face); } clear_sprite_map((f)); } + CALL(&medium_font, 0); CALL(&bold_font, 0); CALL(&italic_font, 0); CALL(&bi_font, 0); CALL(&box_font, 0); cell_metrics(medium_font.face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness); if (!cell_width) { PyErr_SetString(PyExc_ValueError, "Failed to calculate cell width for the specified font."); return NULL; } if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px); @@ -227,14 +231,20 @@ update_cell_metrics(float pt_sz, float xdpi, float ydpi) { global_state.cell_width = cell_width; global_state.cell_height = cell_height; free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height); if (canvas == NULL) return PyErr_NoMemory(); + for (size_t i = 0; fallback_fonts[i].face != NULL; i++) { + CALL(fallback_fonts + i, cell_height); + } + for (size_t i = 0; i < symbol_map_fonts_count; i++) { + CALL(symbol_map_fonts + i, cell_height); + } return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness); +#undef CALL } static PyObject* set_font_size(PyObject UNUSED *m, PyObject *args) { - float pt_sz, xdpi, ydpi; - if (!PyArg_ParseTuple(args, "fff", &pt_sz, &xdpi, &ydpi)) return NULL; - return update_cell_metrics(pt_sz, xdpi, ydpi); + if (!PyArg_ParseTuple(args, "f", &global_state.font_sz_in_pts)) return NULL; + return update_cell_metrics(); } static inline bool @@ -260,13 +270,16 @@ fallback_font(Cell *cell) { return fallback_fonts + i; } } - if (get_fallback_font == NULL || i == (sizeof(fallback_fonts)/sizeof(fallback_fonts[0])-1)) return NULL; - Py_UCS4 buf[10]; - size_t n = cell_as_unicode(cell, true, buf, ' '); - PyObject *face = PyObject_CallFunction(get_fallback_font, "NOO", PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n), bold ? Py_True : Py_False, italic ? Py_True : Py_False); + if (i == (sizeof(fallback_fonts)/sizeof(fallback_fonts[0])-1)) { return NULL; } + Font* base_font; + if (bold) base_font = italic ? &bi_font : &bold_font; + else base_font = italic ? &italic_font : &medium_font; + if (!base_font->face) base_font = &medium_font; + PyObject *face = create_fallback_face(base_font->face, cell, bold, italic); if (face == NULL) { PyErr_Print(); return NULL; } if (face == Py_None) { Py_DECREF(face); return NULL; } - if (!alloc_font(fallback_fonts + i, face, bold, italic)) { Py_DECREF(face); fatal("Out of memory"); } + if (!alloc_font(fallback_fonts + i, face, bold, italic, true)) { Py_DECREF(face); fatal("Out of memory"); } + set_size_for_face(face, cell_height); Py_DECREF(face); return fallback_fonts + i; } @@ -526,15 +539,14 @@ render_line(Line *line) { static PyObject* set_font(PyObject UNUSED *m, PyObject *args) { PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL; - float xdpi, ydpi, pt_sz; - Py_CLEAR(get_fallback_font); Py_CLEAR(box_drawing_function); - if (!PyArg_ParseTuple(args, "OOO!O!fffO|OOO", &get_fallback_font, &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &pt_sz, &xdpi, &ydpi, &medium, &bold, &italic, &bi)) return NULL; - Py_INCREF(get_fallback_font); Py_INCREF(box_drawing_function); + Py_CLEAR(box_drawing_function); + if (!PyArg_ParseTuple(args, "OO!O!fO|OOO", &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL; + Py_INCREF(box_drawing_function); clear_font(&medium_font); clear_font(&bold_font); clear_font(&italic_font); clear_font(&bi_font); clear_sprite_map(&box_font); - if (!alloc_font(&medium_font, medium, false, false)) return PyErr_NoMemory(); - if (bold && !alloc_font(&bold_font, bold, true, false)) return PyErr_NoMemory(); - if (italic && !alloc_font(&italic_font, italic, false, true)) return PyErr_NoMemory(); - if (bi && !alloc_font(&bi_font, bi, true, true)) return PyErr_NoMemory(); + if (!alloc_font(&medium_font, medium, false, false, false)) return NULL; + if (bold && !alloc_font(&bold_font, bold, true, false, false)) return NULL; + if (italic && !alloc_font(&italic_font, italic, false, true, false)) return NULL; + if (bi && !alloc_font(&bi_font, bi, true, true, false)) return NULL; for (size_t i = 0; fallback_fonts[i].face != NULL; i++) clear_font(fallback_fonts + i); for (size_t i = 0; symbol_map_fonts_count; i++) free_font(symbol_map_fonts + i); free(symbol_maps); free(symbol_map_fonts); symbol_maps = NULL; symbol_map_fonts = NULL; @@ -551,7 +563,7 @@ set_font(PyObject UNUSED *m, PyObject *args) { PyObject *face; int bold, italic; if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL; - if (!alloc_font(symbol_map_fonts + i, face, bold != 0, italic != 0)) return PyErr_NoMemory(); + if (!alloc_font(symbol_map_fonts + i, face, bold != 0, italic != 0, false)) return NULL; } for (size_t i = 0; i < symbol_maps_count; i++) { unsigned int left, right, font_idx; @@ -559,14 +571,13 @@ set_font(PyObject UNUSED *m, PyObject *args) { symbol_maps[i].left = left; symbol_maps[i].right = right; symbol_maps[i].font_idx = font_idx; } } - return update_cell_metrics(pt_sz, xdpi, ydpi); + return update_cell_metrics(); } static void finalize(void) { Py_CLEAR(python_send_to_gpu_impl); free(canvas); - Py_CLEAR(get_fallback_font); Py_CLEAR(box_drawing_function); free_font(&medium_font); free_font(&bold_font); free_font(&italic_font); free_font(&bi_font); free_font(&box_font); for (size_t i = 0; fallback_fonts[i].face != NULL; i++) free_font(fallback_fonts + i); @@ -685,6 +696,25 @@ error: #undef SET } +static PyObject* +get_fallback_font(PyObject UNUSED *self, PyObject *args) { + PyObject *text; + int bold, italic; + if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; + static Py_UCS4 char_buf[16]; + if (!PyUnicode_AsUCS4(text, char_buf, sizeof(char_buf)/sizeof(char_buf[0]), 1)) return NULL; + Cell cell = {0}; + cell.ch = char_buf[0]; + if (PyUnicode_GetLength(text) > 1) cell.cc |= char_buf[1] & CC_MASK; + if (PyUnicode_GetLength(text) > 2) cell.cc |= (char_buf[2] & CC_MASK) << 16; + if (bold) cell.attrs |= 1 << BOLD_SHIFT; + if (italic) cell.attrs |= 1 << ITALIC_SHIFT; + Font *ans = fallback_font(&cell); + if (ans == NULL) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } + return ans->face; +} + + static PyMethodDef module_methods[] = { METHODB(set_font_size, METH_VARARGS), METHODB(set_font, METH_VARARGS), @@ -696,6 +726,7 @@ static PyMethodDef module_methods[] = { METHODB(set_send_sprite_to_gpu, METH_O), METHODB(current_fonts, METH_NOARGS), METHODB(test_render_line, METH_VARARGS), + METHODB(get_fallback_font, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fonts.h b/kitty/fonts.h index 68a528a27..9c8e889fc 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -16,7 +16,7 @@ bool face_has_codepoint(PyObject *, char_type); hb_font_t* harfbuzz_font_for_face(PyObject*); -bool set_size_for_face(PyObject*, float, float, float); +bool set_size_for_face(PyObject*, unsigned int); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z); bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline); @@ -24,5 +24,8 @@ void render_line(Line *line); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height); typedef void (*free_extra_data_func)(void*); -PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float); -PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float); +PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float); +PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float); +PyObject* specialize_font_descriptor(PyObject *base_descriptor); +PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic); +PyObject* face_from_descriptor(PyObject*); diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index f5d00dacf..389b6d053 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -4,9 +4,8 @@ import re import sys -from collections import namedtuple -from kitty.fast_data_types import coretext_all_fonts, create_face, face_for_text +from kitty.fast_data_types import coretext_all_fonts from kitty.utils import safe_print attr_map = {(False, False): 'font_family', @@ -43,7 +42,7 @@ def list_fonts(): yield {'family': f, 'full_name': fn, 'is_monospace': is_mono} -def find_best_match(family, bold, italic): +def find_best_match(family, bold=False, italic=False): q = re.sub(r'\s+', ' ', family.lower()) font_map = all_fonts_map() @@ -77,7 +76,7 @@ def find_best_match(family, bold, italic): } -def resolve_family(f, main_family, bold, italic): +def resolve_family(f, main_family, bold=False, italic=False): if (bold or italic) and f == 'auto': f = main_family if f.lower() == 'monospace': @@ -85,67 +84,20 @@ def resolve_family(f, main_family, bold, italic): return f -FaceDescription = namedtuple( - 'FaceDescription', 'resolved_family family bold italic' -) - - -def face_description(family, main_family, bold=False, italic=False): - return FaceDescription( - resolve_family(family, main_family, bold, italic), family, bold, italic - ) - - -def get_face(family, font_size, xdpi, ydpi, bold=False, italic=False): - descriptor = find_best_match(family, bold, italic) - return create_face(descriptor, font_size, xdpi, ydpi) - - def get_font_files(opts): ans = {} for (bold, italic), attr in attr_map.items(): - face = face_description( - getattr(opts, attr), opts.font_family, bold, italic - ) + face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic) key = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'}[(bold, italic)] ans[key] = face if key == 'medium': - save_medium_face.family = face.resolved_family + get_font_files.medium_family = face['family'] return ans -def face_from_font(font, pt_sz=11.0, xdpi=72.0, ydpi=72.0): - return get_face(font.resolved_family, pt_sz, xdpi, ydpi, bold=font.bold, italic=font.italic) - - -def save_medium_face(face): - save_medium_face.face = face - - -def font_for_text(text, current_font_family, pt_sz, xdpi, ydpi, bold=False, italic=False): - return face_for_text(text, save_medium_face.face.extra_data(), pt_sz, xdpi, ydpi, bold, italic) - - def font_for_family(family): - ans = face_description(family, save_medium_face.family) - return ans, ans.bold, ans.italic - - -def test_font_matching( - name='Menlo', bold=False, italic=False, dpi=72.0, font_size=11.0 -): - all_fonts = create_font_map(coretext_all_fonts()) - face = get_face(all_fonts, name, 'Menlo', font_size, dpi, dpi, bold, italic) - return face - - -def test_family_matching(name='Menlo', dpi=72.0, font_size=11.0): - for bold in (False, True): - for italic in (False, True): - face = get_face( - name, font_size, dpi, dpi, bold, italic - ) - print(bold, italic, face) + ans = find_best_match(resolve_family(family, get_font_files.medium_family)) + return ans, ans['bold'], ans['italic'] diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index fb7b3be0f..f10d2f35c 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -6,8 +6,8 @@ import re from functools import lru_cache from kitty.fast_data_types import ( - FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, Face, - fc_list, fc_match, fc_font + FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, + fc_list, fc_match, ) attr_map = {(False, False): 'font_family', @@ -66,21 +66,12 @@ def find_best_match(family, bold=False, italic=False, monospaced=True): return fc_match(family, bold, italic) -def face_from_font(font, pt_sz=11.0, xdpi=96.0, ydpi=96.0): - font = fc_font(pt_sz, (xdpi + ydpi) / 2.0, font['path'], font.get('index', 0)) - return Face(font['path'], font.get('index', 0), font.get('hinting', False), font.get('hint_style', 0), pt_sz, xdpi, ydpi) - - def resolve_family(f, main_family, bold, italic): if (bold or italic) and f == 'auto': f = main_family return f -def save_medium_face(face): - pass - - def get_font_files(opts): ans = {} for (bold, italic), attr in attr_map.items(): @@ -91,18 +82,9 @@ def get_font_files(opts): (False, True): 'italic', (True, True): 'bi'}[(bold, italic)] ans[key] = font - if key == 'medium': - save_medium_face.medium_font = font return ans def font_for_family(family): ans = find_best_match(family, monospaced=False) return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN - - -def font_for_text(text, current_font_family='monospace', pt_sz=11.0, xdpi=96.0, ydpi=96.0, bold=False, italic=False): - ans = fc_match('monospace', bold, italic, False, pt_sz, str(text), (xdpi + ydpi) / 2.0) - if ans is not None: - ans = face_from_font(ans, pt_sz, xdpi, ydpi) - return ans diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 42b8608f2..82ce93e31 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -10,21 +10,16 @@ from math import ceil, floor, pi, sin, sqrt from kitty.config import defaults from kitty.constants import isosx from kitty.fast_data_types import ( - Screen, change_wcwidth, send_prerendered_sprites, set_font, set_font_size, - set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line + Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites, + set_font, set_font_size, set_logical_dpi, set_send_sprite_to_gpu, + sprite_map_set_limits, test_render_line ) from kitty.fonts.box_drawing import render_box_char, render_missing_glyph -from kitty.utils import get_logical_dpi if isosx: - from .core_text import get_font_files, font_for_text, face_from_font, font_for_family, save_medium_face + from .core_text import get_font_files, font_for_family else: - from .fontconfig import get_font_files, font_for_text, face_from_font, font_for_family, save_medium_face - - -def create_face(font): - s = set_font_family.state - return face_from_font(font, s.pt_sz, s.xdpi, s.ydpi) + from .fontconfig import get_font_files, font_for_family def create_symbol_map(opts): @@ -34,58 +29,42 @@ def create_symbol_map(opts): for family in val.values(): if family not in family_map: font, bold, italic = font_for_family(family) - o = create_face(font) family_map[family] = len(faces) - faces.append((o, bold, italic)) + faces.append((font, bold, italic)) sm = tuple((a, b, family_map[f]) for (a, b), f in val.items()) return sm, tuple(faces) FontState = namedtuple( 'FontState', - 'family pt_sz xdpi ydpi cell_width cell_height baseline underline_position underline_thickness' + 'family pt_sz cell_width cell_height baseline underline_position underline_thickness' ) -def get_fallback_font(text, bold, italic): - state = set_font_family.state - return font_for_text( - text, state.family, state.pt_sz, state.xdpi, state.ydpi, bold, - italic - ) - - -def set_font_family(opts=None, override_font_size=None, override_dpi=None): +def set_font_family(opts=None, override_font_size=None): opts = opts or defaults sz = override_font_size or opts.font_size - xdpi, ydpi = get_logical_dpi(override_dpi) - set_font_family.state = FontState('', sz, xdpi, ydpi, 0, 0, 0, 0, 0) font_map = get_font_files(opts) - faces = [create_face(font_map['medium'])] - save_medium_face(faces[0]) + faces = [font_map['medium']] for k in 'bold italic bi'.split(): if k in font_map: - faces.append(create_face(font_map[k])) - sm, sfaces = create_symbol_map(opts) + faces.append(font_map[k]) + sm, sfonts = create_symbol_map(opts) cell_width, cell_height, baseline, underline_position, underline_thickness = set_font( - get_fallback_font, render_box_drawing, sm, sfaces, sz, xdpi, ydpi, *faces + render_box_drawing, sm, sfonts, sz, *faces ) set_font_family.state = FontState( - opts.font_family, sz, xdpi, ydpi, cell_width, cell_height, baseline, + opts.font_family, sz, cell_width, cell_height, baseline, underline_position, underline_thickness ) return cell_width, cell_height -def resize_fonts(new_sz, xdpi=None, ydpi=None): +def resize_fonts(new_sz): s = set_font_family.state - xdpi = xdpi or s.xdpi - ydpi = ydpi or s.ydpi - cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size( - new_sz, xdpi, ydpi - ) + cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz) set_font_family.state = FontState( - s.family, new_sz, xdpi, ydpi, cell_width, cell_height, baseline, + s.family, new_sz, cell_width, cell_height, baseline, underline_position, underline_thickness ) return cell_width, cell_height @@ -175,6 +154,7 @@ def render_box_drawing(codepoint): def setup_for_testing(family='monospace', size=11.0, dpi=96.0): + from kitty.utils import get_logical_dpi opts = defaults._replace(font_family=family) sprites = {} @@ -183,7 +163,9 @@ def setup_for_testing(family='monospace', size=11.0, dpi=96.0): sprite_map_set_limits(100000, 100) set_send_sprite_to_gpu(send_to_gpu) - cell_width, cell_height = set_font_family(opts, override_dpi=(dpi, dpi), override_font_size=size) + set_logical_dpi(dpi, dpi) + get_logical_dpi((dpi, dpi)) + cell_width, cell_height = set_font_family(opts, override_font_size=size) prerender() return sprites, cell_width, cell_height @@ -230,8 +212,9 @@ def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi def test_fallback_font(qtext=None, bold=False, italic=False): - set_font_family(override_dpi=(96.0, 96.0)) - trials = (qtext,) if qtext else ('你好', 'He\u0347\u0305', '\U0001F929') + set_logical_dpi(96.0, 96.0) + set_font_family() + trials = (qtext,) if qtext else ('你', 'He\u0347\u0305', '\U0001F929') for text in trials: f = get_fallback_font(text, bold, italic) try: diff --git a/kitty/freetype.c b/kitty/freetype.c index ac045961a..ffd3c32e3 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -116,12 +116,12 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt } bool -set_size_for_face(PyObject *s, float pt_sz, float xdpi, float ydpi) { +set_size_for_face(PyObject *s, unsigned int UNUSED desired_height) { Face *self = (Face*)s; - FT_UInt w = (FT_UInt)(ceilf(pt_sz * 64)); - if (self->char_width == w && self->char_height == w && self->xdpi == (FT_UInt)xdpi && self->ydpi == (FT_UInt)ydpi) return true; - ((Face*)self)->size_in_pts = pt_sz; - return set_font_size(self, w, w, (FT_UInt)xdpi, (FT_UInt) ydpi); + FT_UInt w = (FT_UInt)(ceilf(global_state.font_sz_in_pts * 64)); + if (self->char_width == w && self->char_height == w && self->xdpi == (FT_UInt)global_state.logical_dpi_x && self->ydpi == (FT_UInt)global_state.logical_dpi_x) return true; + ((Face*)self)->size_in_pts = global_state.font_sz_in_pts; + return set_font_size(self, w, w, self->xdpi, self->ydpi); } static inline int @@ -136,13 +136,13 @@ get_load_flags(int hinting, int hintstyle, int base) { static inline bool -init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi) { +init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) { #define CPY(n) self->n = self->face->n; CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); #undef CPY self->is_scalable = FT_IS_SCALABLE(self->face); self->hinting = hinting; self->hintstyle = hintstyle; - if (!set_size_for_face((PyObject*)self, size_in_pts, xdpi, ydpi)) return false; + if (!set_size_for_face((PyObject*)self, 0)) return false; self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); @@ -154,12 +154,12 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, float size_ } PyObject* -ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float apple_leading) { +ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) { Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); if (ans == NULL) return NULL; int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face); if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; } - if (!init_ft_face(ans, path, hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(ans); return NULL; } + if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; } ans->extra_data = extra_data; ans->free_extra_data = fed; ans->apple_leading = apple_leading; @@ -186,35 +186,40 @@ load_from_path_and_psname(const char *path, const char* psname, Face *ans) { } PyObject* -ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float apple_leading) { +ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float apple_leading) { if (PyUnicode_READY(path) != 0) return NULL; Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); if (!ans) return NULL; if (!load_from_path_and_psname(PyUnicode_AsUTF8(path), psname, ans)) { Py_CLEAR(ans); return NULL; } - if (!init_ft_face(ans, path, hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(ans); return NULL; } + if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; } ans->extra_data = extra_data; ans->free_extra_data = fed; ans->apple_leading = apple_leading; return (PyObject*)ans; } -static PyObject* -new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { - Face *self; +#ifndef __APPLE__ +PyObject* +face_from_descriptor(PyObject *descriptor) { +#define D(key, conv) { PyObject *t = PyDict_GetItemString(descriptor, #key); if (t == NULL) return NULL; key = conv(t); t = NULL; } char *path; - int error, hinting, hintstyle; long index; - float size_in_pts, xdpi, ydpi; - if (!PyArg_ParseTuple(args, "sliifff", &path, &index, &hinting, &hintstyle, &size_in_pts, &xdpi, &ydpi)) return NULL; - - self = (Face *)type->tp_alloc(type, 0); + bool hinting; + long hint_style; + D(path, PyUnicode_AsUTF8); + D(index, PyLong_AsLong); + D(hinting, PyObject_IsTrue); + D(hint_style, PyLong_AsLong); +#undef D + Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0); if (self != NULL) { - error = FT_New_Face(library, path, index, &(self->face)); + int error = FT_New_Face(library, path, index, &(self->face)); if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; } - if (!init_ft_face(self, PyTuple_GET_ITEM(args, 0), hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(self); return NULL; } + if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style)) { Py_CLEAR(self); return NULL; } } return (PyObject*)self; } +#endif static void dealloc(Face* self) { @@ -439,7 +444,6 @@ PyTypeObject Face_Type = { .tp_doc = "FreeType Font face", .tp_methods = methods, .tp_members = members, - .tp_new = new, .tp_repr = (reprfunc)repr, }; diff --git a/kitty/line.c b/kitty/line.c index 17b59bf7c..89fc2ad0a 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -181,6 +181,23 @@ cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char) return n; } +size_t +cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type zero_char) { + size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf); + if (include_cc) { + char_type cc = cell->cc; + Py_UCS4 cc1 = cc & CC_MASK, cc2; + if (cc1) { + n += encode_utf8(cc1, buf + n); + cc2 = cc >> 16; + if (cc2) { n += encode_utf8(cc2, buf + n); } + } + } + buf[n] = 0; + return n; +} + + PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) { size_t n = 0; diff --git a/kitty/lineops.h b/kitty/lineops.h index 309bcb88f..b8a90c2ef 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -55,6 +55,7 @@ index_type line_url_end_at(Line *self, index_type x); index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen); unsigned int line_length(Line *self); size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type); +size_t cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type); PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char); void linebuf_init_line(LineBuf *, index_type); diff --git a/kitty/state.h b/kitty/state.h index bff465fe1..16ff3c5f9 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -72,6 +72,7 @@ typedef struct { bool application_focused; double cursor_blink_zero_time, last_mouse_activity_at; double logical_dpi_x, logical_dpi_y; + float font_sz_in_pts; double mouse_x, mouse_y; bool mouse_button_pressed[20]; int viewport_width, viewport_height; diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index f23f29090..8129c6d6b 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -6,11 +6,12 @@ from collections import OrderedDict from kitty.constants import isosx from kitty.fast_data_types import ( - set_send_sprite_to_gpu, sprite_map_set_layout, sprite_map_set_limits, - test_render_line, test_sprite_position_for, wcwidth + set_logical_dpi, set_send_sprite_to_gpu, 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, set_font_family, prerender +from kitty.fonts.render import prerender, render_string, set_font_family +from kitty.utils import get_logical_dpi from . import BaseTest @@ -25,7 +26,9 @@ class Rendering(BaseTest): self.sprites[(x, y, z)] = data set_send_sprite_to_gpu(send_to_gpu) - self.cell_width, self.cell_height = set_font_family(override_dpi=(96.0, 96.0)) + set_logical_dpi(96.0, 96.0) + get_logical_dpi((96.0, 96.0)) + self.cell_width, self.cell_height = set_font_family() prerender() self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4])