mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-21 16:05:02 +02:00
Implement parsing of fvar table
Cant rely on CoreText for this as it has incomplete and buggy APIs
This commit is contained in:
@@ -782,39 +782,16 @@ 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);
|
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
|
// Font tables {{{
|
||||||
|
|
||||||
static uint16_t byteswap(uint16_t x) { return (x << 8) | (x >> 8); }
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ensure_name_table(CTFace *self) {
|
ensure_name_table(CTFace *self) {
|
||||||
if (self->name_lookup_table) return true;
|
if (self->name_lookup_table) return true;
|
||||||
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableName, kCTFontTableOptionNoOptions));
|
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableName, kCTFontTableOptionNoOptions));
|
||||||
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
|
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
|
||||||
size_t table_len = 0;
|
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
|
||||||
if (!table || (table_len = CFDataGetLength(cftable)) < 9 * sizeof(uint16_t)) {
|
self->name_lookup_table = read_name_font_table(table, table_len);
|
||||||
self->name_lookup_table = PyDict_New();
|
return !!self->name_lookup_table;
|
||||||
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*
|
static PyObject*
|
||||||
@@ -823,90 +800,13 @@ get_best_name(CTFace *self, PyObject *nameid) {
|
|||||||
return get_best_name_from_name_table(self->name_lookup_table, nameid);
|
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;
|
|
||||||
bytes[1] = (tag >> 16) & 0xff;
|
|
||||||
bytes[2] = (tag >> 8) & 0xff;
|
|
||||||
bytes[3] = (tag) & 0xff;
|
|
||||||
bytes[4] = 0;
|
|
||||||
return (const char*)bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
convert_variation_to_python(const void *key, const void *value_, void *axis_values) {
|
|
||||||
unsigned long tag; float value;
|
|
||||||
CFNumberGetValue(key, kCFNumberLongType, &tag);
|
|
||||||
CFNumberGetValue(value_, kCFNumberFloatType, &value);
|
|
||||||
uint8_t tag_buf[5] = {0};
|
|
||||||
RAII_PyObject(val, PyFloat_FromDouble((double)value));
|
|
||||||
if (val) PyDict_SetItemString(axis_values, tag_to_string(tag, tag_buf), val);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
get_variable_data(CTFace *self) {
|
get_variable_data(CTFace *self) {
|
||||||
uint8_t tag_buf[5] = {0};
|
if (!ensure_name_table(self)) return NULL;
|
||||||
RAII_CoreFoundation(CFArrayRef, cfaxes, CTFontCopyVariationAxes(self->ct_font));
|
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableFvar, kCTFontTableOptionNoOptions));
|
||||||
if (!cfaxes) return Py_BuildValue("{sN sN}", "axes", PyTuple_New(0), "named_styles", PyDict_New()); //, "named_styles", named_styles);
|
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
|
||||||
RAII_PyObject(axes, PyTuple_New(CFArrayGetCount(cfaxes)));
|
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
|
||||||
for (CFIndex i = 0; i < CFArrayGetCount(cfaxes); i++) {
|
return read_fvar_font_table(table, table_len, self->name_lookup_table);
|
||||||
CFDictionaryRef a = (CFDictionaryRef)CFArrayGetValueAtIndex(cfaxes, i);
|
|
||||||
#define get_number(key, output, type, optional) { \
|
|
||||||
CFNumberRef value = (CFNumberRef)CFDictionaryGetValue(a, key); \
|
|
||||||
if (value) CFNumberGetValue(value, type, &output); \
|
|
||||||
else if (!optional) { PyErr_Format(PyExc_RuntimeError, "The %s key was not present in the varioation axis dictionary", #key); return NULL; } \
|
|
||||||
}
|
|
||||||
float minimum = 0, maximum = 0, def = 0; unsigned long tag = 0, hidden = 0;
|
|
||||||
get_number(kCTFontVariationAxisDefaultValueKey, def, kCFNumberFloatType, false);
|
|
||||||
get_number(kCTFontVariationAxisMaximumValueKey, maximum, kCFNumberFloatType, false);
|
|
||||||
get_number(kCTFontVariationAxisMinimumValueKey, minimum, kCFNumberFloatType, false);
|
|
||||||
get_number(kCTFontVariationAxisIdentifierKey, tag, kCFNumberLongType, false);
|
|
||||||
get_number(kCTFontVariationAxisHiddenKey, hidden, kCFNumberLongType, true);
|
|
||||||
#undef get_number
|
|
||||||
CFStringRef n = (CFStringRef)CFDictionaryGetValue(a, kCTFontVariationAxisNameKey);
|
|
||||||
PyObject *axis = Py_BuildValue("{sd sd sd ss sO sO}",
|
|
||||||
"minimum", minimum, "maximum", maximum, "default", def, "tag", tag_to_string(tag, tag_buf),
|
|
||||||
"hidden", hidden ? Py_True : Py_False, "strid", convert_cfstring(n, false)
|
|
||||||
);
|
|
||||||
if (!axis) return NULL;
|
|
||||||
PyTuple_SET_ITEM(axes, i, axis);
|
|
||||||
}
|
|
||||||
RAII_CoreFoundation(CFStringRef, font_family_name, CTFontCopyFamilyName(self->ct_font));
|
|
||||||
RAII_CoreFoundation(CTFontDescriptorRef, descriptor,
|
|
||||||
CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)@{(id)kCTFontFamilyNameAttribute: (__bridge NSString*)font_family_name}));
|
|
||||||
RAII_CoreFoundation(CTFontCollectionRef, collection,
|
|
||||||
CTFontCollectionCreateWithFontDescriptors((__bridge CFArrayRef)@[(__bridge id)descriptor], NULL));
|
|
||||||
RAII_CoreFoundation(CFArrayRef, descriptors, CTFontCollectionCreateMatchingFontDescriptors(collection));
|
|
||||||
RAII_PyObject(named_styles, PyTuple_New(CFArrayGetCount(descriptors)));
|
|
||||||
Py_ssize_t actual_num = 0;
|
|
||||||
RAII_CoreFoundation(CFURLRef, url, CTFontCopyAttribute(self->ct_font, kCTFontURLAttribute));
|
|
||||||
for (CFIndex i = 0; i < CFArrayGetCount(descriptors); i++) {
|
|
||||||
CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, i);
|
|
||||||
RAII_CoreFoundation(CFDictionaryRef, variation, CTFontDescriptorCopyAttribute(descriptor, kCTFontVariationAttribute));
|
|
||||||
if (!variation) continue;
|
|
||||||
RAII_CoreFoundation(CFURLRef, candidate_url, CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute));
|
|
||||||
if (!CFEqual(url, candidate_url)) continue;
|
|
||||||
RAII_CoreFoundation(CFStringRef, style, CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute));
|
|
||||||
RAII_CoreFoundation(CFStringRef, psname, CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute));
|
|
||||||
RAII_PyObject(axis_values, PyDict_New());
|
|
||||||
if (!axis_values) return NULL;
|
|
||||||
CFDictionaryApplyFunction(variation, convert_variation_to_python, axis_values);
|
|
||||||
PyObject *ns = Py_BuildValue("{sO sN sN}", "axis_values", axis_values, "name", convert_cfstring(style, false), "psname", convert_cfstring(psname, false));
|
|
||||||
if (!ns) return NULL;
|
|
||||||
PyTuple_SET_ITEM(named_styles, actual_num, ns); actual_num++;
|
|
||||||
}
|
|
||||||
_PyTuple_Resize(&named_styles, actual_num);
|
|
||||||
return Py_BuildValue("{sO sO sN}", "axes", axes, "named_styles", named_styles, "variations_postscript_name_prefix", _get_best_name(self, 25));
|
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
|||||||
@@ -70,3 +70,119 @@ get_best_name_from_name_table(PyObject *table, PyObject *name_id) {
|
|||||||
return PyUnicode_FromString("");
|
return PyUnicode_FromString("");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
get_best_name(PyObject *table, unsigned long name_id) {
|
||||||
|
RAII_PyObject(id, PyLong_FromUnsignedLong(name_id));
|
||||||
|
return get_best_name_from_name_table(table, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenType tables are big-endian for god knows what reason so need to byteswap
|
||||||
|
static uint16_t
|
||||||
|
byteswap(const uint16_t *p) {
|
||||||
|
const uint8_t *b = (const uint8_t*)p;
|
||||||
|
return (((uint16_t)b[0]) << 8) | b[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
byteswap32(const uint32_t *val) {
|
||||||
|
const uint8_t *p = (const uint8_t*)val;
|
||||||
|
return (((uint32_t)p[0]) << 24) | (((uint32_t)p[1]) << 16) | (((uint32_t)p[2]) << 8) | p[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
load_fixed(const uint32_t *p_) {
|
||||||
|
uint32_t p = byteswap32(p_);
|
||||||
|
static const double denom = 1 << 16;
|
||||||
|
return ((int32_t)p) / denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define next byteswap(p++)
|
||||||
|
#define next32 load_fixed(p32++)
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
read_name_font_table(const uint8_t *table, size_t table_len) {
|
||||||
|
if (!table || table_len < 9 * sizeof(uint16_t)) return PyDict_New();
|
||||||
|
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) return PyDict_New();
|
||||||
|
RAII_PyObject(ans, PyDict_New());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Py_INCREF(ans);
|
||||||
|
return ans;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table) {
|
||||||
|
RAII_PyObject(named_styles, PyTuple_New(0)); if (!named_styles) return NULL;
|
||||||
|
RAII_PyObject(axes, PyTuple_New(0)); if (!axes) return NULL;
|
||||||
|
#define retval Py_BuildValue("{sO sO sN}", "axes", axes, "named_styles", named_styles, "variations_postscript_name_prefix", get_best_name(name_lookup_table, 25))
|
||||||
|
|
||||||
|
if (!table || table_len < 14 * sizeof(uint16_t)) return retval;
|
||||||
|
const uint16_t *p = (uint16_t*)table;
|
||||||
|
p += 2;
|
||||||
|
const uint16_t offset_to_start_of_axis_array = next; next;
|
||||||
|
const uint16_t num_of_axis_records = next, size_of_axis_record = next, num_of_name_records = next, size_of_name_record = next;
|
||||||
|
const uint16_t size_of_coordinates = num_of_axis_records * sizeof(int32_t);
|
||||||
|
if (size_of_name_record < size_of_coordinates + 4) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "size of name record: %u too small", size_of_name_record); return NULL;
|
||||||
|
}
|
||||||
|
const bool has_postscript_name = size_of_name_record >= 3 * sizeof(uint16_t) + size_of_coordinates;
|
||||||
|
uint16_t i = 0;
|
||||||
|
if (size_of_axis_record < 20) { PyErr_Format(PyExc_ValueError, "size of axis record: %u too small", size_of_axis_record); return NULL; }
|
||||||
|
if (_PyTuple_Resize(&axes, num_of_axis_records) == -1) return NULL;
|
||||||
|
for (
|
||||||
|
const uint8_t *pos = table + offset_to_start_of_axis_array;
|
||||||
|
pos + size_of_axis_record <= table + table_len && i < num_of_axis_records;
|
||||||
|
i++, pos += size_of_axis_record
|
||||||
|
) {
|
||||||
|
uint32_t *p32 = (uint32_t*)(pos + 4);
|
||||||
|
const double minimum = next32, def = next32, maximum = next32;
|
||||||
|
p = (uint16_t*)(pos + 16);
|
||||||
|
int32_t flags = next, strid = next;
|
||||||
|
PyObject *axis = Py_BuildValue("{sd sd sd ss# sO sN}",
|
||||||
|
"minimum", minimum, "maximum", maximum, "default", def, "tag", pos, 4,
|
||||||
|
"hidden", (flags & 1) ? Py_True : Py_False, "strid", get_best_name(name_lookup_table, strid)
|
||||||
|
); if (!axis) return NULL;
|
||||||
|
PyTuple_SET_ITEM(axes, i, axis);
|
||||||
|
}
|
||||||
|
if (_PyTuple_Resize(&axes, i) == -1) return NULL;
|
||||||
|
char tag_buf[5] = {0};
|
||||||
|
i = 0;
|
||||||
|
if (_PyTuple_Resize(&named_styles, num_of_name_records) == -1) return NULL;
|
||||||
|
for (
|
||||||
|
const uint8_t *pos = table + offset_to_start_of_axis_array + num_of_axis_records * size_of_axis_record;
|
||||||
|
pos + size_of_name_record <= table + table_len && i < num_of_name_records;
|
||||||
|
i++, pos += size_of_name_record
|
||||||
|
) {
|
||||||
|
p = (uint16_t*)pos;
|
||||||
|
uint16_t name_id = next, psname_id = 0xffff; next;
|
||||||
|
const uint32_t *p32 = (uint32_t*)p;
|
||||||
|
RAII_PyObject(axis_values, PyDict_New());
|
||||||
|
if (!axis_values) return NULL;
|
||||||
|
for (uint16_t i = 0; i < num_of_axis_records; i++) {
|
||||||
|
const uint8_t *t = table + offset_to_start_of_axis_array + i * size_of_axis_record;
|
||||||
|
memcpy(tag_buf, t, 4);
|
||||||
|
RAII_PyObject(pval, PyFloat_FromDouble(next32));
|
||||||
|
if (!pval || PyDict_SetItemString(axis_values, tag_buf, pval) != 0) return NULL;
|
||||||
|
}
|
||||||
|
if (has_postscript_name) { p = (uint16_t*)p32; psname_id = next; }
|
||||||
|
PyObject *ns = Py_BuildValue("{sO sN sN}",
|
||||||
|
"axis_values", axis_values, "name", get_best_name(name_lookup_table, name_id),
|
||||||
|
"psname", (psname_id != 0xffff && psname_id ? get_best_name(name_lookup_table, psname_id) : PyUnicode_FromString("")));
|
||||||
|
if (!ns) return NULL;
|
||||||
|
PyTuple_SET_ITEM(named_styles, i, ns);
|
||||||
|
}
|
||||||
|
if (_PyTuple_Resize(&named_styles, i) == -1) return NULL;
|
||||||
|
return retval;
|
||||||
|
#undef retval
|
||||||
|
}
|
||||||
|
#undef next32
|
||||||
|
#undef next
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ 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);
|
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*
|
PyObject*
|
||||||
get_best_name_from_name_table(PyObject *table, PyObject *name_id);
|
get_best_name_from_name_table(PyObject *table, PyObject *name_id);
|
||||||
|
PyObject*
|
||||||
|
read_name_font_table(const uint8_t *table, size_t table_len);
|
||||||
|
PyObject*
|
||||||
|
read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
||||||
|
|||||||
Reference in New Issue
Block a user