Implement postscript variation name prefix for CoreText as well

This commit is contained in:
Kovid Goyal
2024-04-23 11:36:29 +05:30
parent 98dac1020d
commit ba1a268de0
4 changed files with 138 additions and 58 deletions

View File

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

72
kitty/font-names.c Normal file
View File

@@ -0,0 +1,72 @@
/*
* font-names.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* 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("");
}

View File

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

View File

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