mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +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);
|
||||
}
|
||||
|
||||
// Name table
|
||||
|
||||
static uint16_t byteswap(uint16_t x) { return (x << 8) | (x >> 8); }
|
||||
// Font tables {{{
|
||||
|
||||
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
|
||||
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
|
||||
self->name_lookup_table = read_name_font_table(table, table_len);
|
||||
return !!self->name_lookup_table;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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*
|
||||
get_variable_data(CTFace *self) {
|
||||
uint8_t tag_buf[5] = {0};
|
||||
RAII_CoreFoundation(CFArrayRef, cfaxes, CTFontCopyVariationAxes(self->ct_font));
|
||||
if (!cfaxes) return Py_BuildValue("{sN sN}", "axes", PyTuple_New(0), "named_styles", PyDict_New()); //, "named_styles", named_styles);
|
||||
RAII_PyObject(axes, PyTuple_New(CFArrayGetCount(cfaxes)));
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(cfaxes); i++) {
|
||||
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));
|
||||
if (!ensure_name_table(self)) return NULL;
|
||||
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableFvar, kCTFontTableOptionNoOptions));
|
||||
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
|
||||
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
|
||||
return read_fvar_font_table(table, table_len, self->name_lookup_table);
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
||||
@@ -70,3 +70,119 @@ get_best_name_from_name_table(PyObject *table, PyObject *name_id) {
|
||||
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);
|
||||
PyObject*
|
||||
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
|
||||
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
||||
|
||||
Reference in New Issue
Block a user