diff --git a/kitty/core_text.m b/kitty/core_text.m index 9383a14be..02215e0d3 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -32,7 +32,7 @@ typedef struct { float ascent, descent, leading, underline_position, underline_thickness, point_sz, scaled_point_sz; CTFontRef ct_font; hb_font_t *hb_font; - PyObject *family_name, *full_name, *postscript_name, *path; + PyObject *family_name, *full_name, *postscript_name, *path, *name_lookup_table; } CTFace; PyTypeObject CTFace_Type; static CTFontRef window_title_font = nil; @@ -107,6 +107,7 @@ dealloc(CTFace* self) { self->hb_font = NULL; self->ct_font = NULL; Py_CLEAR(self->family_name); Py_CLEAR(self->full_name); Py_CLEAR(self->postscript_name); Py_CLEAR(self->path); + Py_CLEAR(self->name_lookup_table); Py_TYPE(self)->tp_free((PyObject*)self); } @@ -781,16 +782,57 @@ render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *inf return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph); } +// Name table +static uint16_t byteswap(uint16_t x) { return (x << 8) | (x >> 8); } -// Boilerplate {{{ +static bool +ensure_name_table(CTFace *self) { + if (self->name_lookup_table) return true; + RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableName, kCTFontTableOptionNoOptions)); + const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL; + size_t table_len = 0; + if (!table || (table_len = CFDataGetLength(cftable)) < 9 * sizeof(uint16_t)) { + self->name_lookup_table = PyDict_New(); + return true; + } + RAII_PyObject(ans, PyDict_New()); + // OpenType tables are big-endian for god knows what reason so need to byteswap +#define next byteswap(*(p++)) + uint16_t *p = (uint16_t*)table; p++; + uint16_t num_of_name_records = next, storage_offset = next; + const uint8_t *storage = table + storage_offset, *slimit = table + table_len; + if (storage >= slimit) { + self->name_lookup_table = PyDict_New(); + return true; + } + for (; num_of_name_records > 0 && p + 6 <= (uint16_t*)slimit; num_of_name_records--) { + uint16_t platform_id = next, encoding_id = next, language_id = next, name_id = next, length = next, offset = next; + const uint8_t *s = storage + offset; + if (s + length <= slimit && !add_font_name_record( + ans, platform_id, encoding_id, language_id, name_id, (const char*)(s), length)) return NULL; + } + self->name_lookup_table = ans; Py_INCREF(ans); + return true; +#undef next +} static PyObject* -display_name(CTFace *self) { - CFStringRef dn = CTFontCopyDisplayName(self->ct_font); - return convert_cfstring(dn, true); +get_best_name(CTFace *self, PyObject *nameid) { + if (!ensure_name_table(self)) return NULL; + return get_best_name_from_name_table(self->name_lookup_table, nameid); } +static PyObject* +_get_best_name(CTFace *self, unsigned long nameid) { + RAII_PyObject(key, PyLong_FromUnsignedLong(nameid)); + return key ? get_best_name(self, key) : NULL; +} + +// }}} + +// Variations {{{ + static const char* tag_to_string(uint32_t tag, uint8_t bytes[5]) { bytes[0] = (tag >> 24) & 0xff; @@ -864,12 +906,23 @@ get_variable_data(CTFace *self) { PyTuple_SET_ITEM(named_styles, actual_num, ns); actual_num++; } _PyTuple_Resize(&named_styles, actual_num); - return Py_BuildValue("{sO sO}", "axes", axes, "named_styles", named_styles); //, "named_styles", named_styles); + return Py_BuildValue("{sO sO sN}", "axes", axes, "named_styles", named_styles, "variations_postscript_name_prefix", _get_best_name(self, 25)); +} +// }}} + + +// Boilerplate {{{ + +static PyObject* +display_name(CTFace *self) { + CFStringRef dn = CTFontCopyDisplayName(self->ct_font); + return convert_cfstring(dn, true); } static PyMethodDef methods[] = { METHODB(display_name, METH_NOARGS), METHODB(get_variable_data, METH_NOARGS), + METHODB(get_best_name, METH_O), {NULL} /* Sentinel */ }; diff --git a/kitty/font-names.c b/kitty/font-names.c new file mode 100644 index 000000000..b4e0e529a --- /dev/null +++ b/kitty/font-names.c @@ -0,0 +1,72 @@ +/* + * font-names.c + * Copyright (C) 2024 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "fonts.h" + +static PyObject* +decode_name_record(PyObject *namerec) { +#define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) + unsigned long platform_id = d(0), encoding_id = d(1), language_id = d(2); +#undef d + const char *encoding = "unicode_escape"; + if ((platform_id == 3 && encoding_id == 1) || platform_id == 0) encoding = "utf-16-be"; + else if (platform_id == 1 && encoding_id == 0 && language_id == 0) encoding = "mac-roman"; + PyObject *b = PyTuple_GET_ITEM(namerec, 3); + return PyUnicode_Decode(PyBytes_AS_STRING(b), PyBytes_GET_SIZE(b), encoding, "replace"); +} + + +static bool +namerec_matches(PyObject *namerec, unsigned platform_id, unsigned encoding_id, unsigned language_id) { +#define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) + return d(0) == platform_id && d(1) == encoding_id && d(2) == language_id; +#undef d +} + +static PyObject* +find_matching_namerec(PyObject *namerecs, unsigned platform_id, unsigned encoding_id, unsigned language_id) { + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(namerecs); i++) { + PyObject *namerec = PyList_GET_ITEM(namerecs, i); + if (namerec_matches(namerec, platform_id, encoding_id, language_id)) return decode_name_record(namerec); + } + return NULL; +} + + +bool +add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len) { + RAII_PyObject(key, PyLong_FromUnsignedLong((unsigned long)name_id)); + if (!key) return false; + RAII_PyObject(list, PyDict_GetItem(table, key)); + if (list == NULL) { + list = PyList_New(0); + if (!list) return false; + if (PyDict_SetItem(table, key, list) != 0) return false; + } else Py_INCREF(list); + RAII_PyObject(value, Py_BuildValue("(H H H y#)", platform_id, encoding_id, language_id, string, (Py_ssize_t)string_len)); + if (!value) return false; + if (PyList_Append(list, value) != 0) return false; + return true; +} + +PyObject* +get_best_name_from_name_table(PyObject *table, PyObject *name_id) { + PyObject *namerecs = PyDict_GetItem(table, name_id); + if (namerecs == NULL) return PyUnicode_FromString(""); + if (PyList_GET_SIZE(namerecs) == 1) return decode_name_record(PyList_GET_ITEM(namerecs, 0)); +#define d(...) { PyObject *ans = find_matching_namerec(namerecs, __VA_ARGS__); if (ans != NULL || PyErr_Occurred()) return ans; } + d(3, 1, 1033); // Microsoft/Windows/US English + d(1, 0, 0); // Mac/Roman/English + d(0, 6, 0); // Unicode/SMP/* + d(0, 4, 0); // Unicode/SMP/* + d(0, 3, 0); // Unicode/BMP/* + d(0, 2, 0); // Unicode/10646-BMP/* + d(0, 1, 0); // Unicode/1.1/* +#undef d + return PyUnicode_FromString(""); + +} diff --git a/kitty/fonts.h b/kitty/fonts.h index 071442242..190f6bb1e 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -42,6 +42,11 @@ typedef void (*free_extra_data_func)(void*); StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline); StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text); +bool +add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len); +PyObject* +get_best_name_from_name_table(PyObject *table, PyObject *name_id); + static inline void right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { pixel *src; diff --git a/kitty/freetype.c b/kitty/freetype.c index 6d72b4309..eea92a208 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -705,18 +705,6 @@ extra_data(PyObject *self, PyObject *a UNUSED) { } // NAME table {{{ -static PyObject* -decode_name_record(PyObject *namerec) { -#define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) - unsigned long platform_id = d(0), encoding_id = d(1), language_id = d(2); -#undef d - const char *encoding = "unicode_escape"; - if ((platform_id == 3 && encoding_id == 1) || platform_id == 0) encoding = "utf-16-be"; - else if (platform_id == 1 && encoding_id == 0 && language_id == 0) encoding = "mac-roman"; - PyObject *b = PyTuple_GET_ITEM(namerec, 3); - return PyUnicode_Decode(PyBytes_AS_STRING(b), PyBytes_GET_SIZE(b), encoding, "replace"); -} - static bool ensure_name_table(Face *self) { if (self->name_lookup_table) return true; @@ -726,54 +714,16 @@ ensure_name_table(Face *self) { for (FT_UInt i = 0; i < FT_Get_Sfnt_Name_Count(self->face); i++) { FT_Error err = FT_Get_Sfnt_Name(self->face, i, &temp); if (err != 0) continue; - RAII_PyObject(key, PyLong_FromUnsignedLong((unsigned long)temp.name_id)); - if (!key) return false; - RAII_PyObject(list, PyDict_GetItem(ans, key)); - if (list == NULL) { - list = PyList_New(0); - if (!list) return false; - if (PyDict_SetItem(ans, key, list) != 0) return false; - } else Py_INCREF(list); - RAII_PyObject(value, Py_BuildValue("(H H H y#)", temp.platform_id, temp.encoding_id, temp.language_id, temp.string, (Py_ssize_t)temp.string_len)); - if (!value) return false; - if (PyList_Append(list, value) != 0) return false; + if (!add_font_name_record(ans, temp.platform_id, temp.encoding_id, temp.language_id, temp.name_id, (const char*)temp.string, temp.string_len)) return NULL; } self->name_lookup_table = ans; Py_INCREF(ans); return true; } -static bool -namerec_matches(PyObject *namerec, unsigned platform_id, unsigned encoding_id, unsigned language_id) { -#define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) - return d(0) == platform_id && d(1) == encoding_id && d(2) == language_id; -#undef d -} - -static PyObject* -find_matching_namerec(PyObject *namerecs, unsigned platform_id, unsigned encoding_id, unsigned language_id) { - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(namerecs); i++) { - PyObject *namerec = PyList_GET_ITEM(namerecs, i); - if (namerec_matches(namerec, platform_id, encoding_id, language_id)) return decode_name_record(namerec); - } - return NULL; -} - static PyObject* get_best_name(Face *self, PyObject *nameid) { if (!ensure_name_table(self)) return NULL; - PyObject *namerecs = PyDict_GetItem(self->name_lookup_table, nameid); - if (namerecs == NULL) return PyUnicode_FromString(""); - if (PyList_GET_SIZE(namerecs) == 1) return decode_name_record(PyList_GET_ITEM(namerecs, 0)); -#define d(...) { PyObject *ans = find_matching_namerec(namerecs, __VA_ARGS__); if (ans != NULL || PyErr_Occurred()) return ans; } - d(3, 1, 1033); // Microsoft/Windows/US English - d(1, 0, 0); // Mac/Roman/English - d(0, 6, 0); // Unicode/SMP/* - d(0, 4, 0); // Unicode/SMP/* - d(0, 3, 0); // Unicode/BMP/* - d(0, 2, 0); // Unicode/10646-BMP/* - d(0, 1, 0); // Unicode/1.1/* -#undef d - return PyUnicode_FromString(""); + return get_best_name_from_name_table(self->name_lookup_table, nameid); } static PyObject*