Implement parsing of fvar table

Cant rely on CoreText for this as it has incomplete and buggy APIs
This commit is contained in:
Kovid Goyal
2024-04-24 07:40:48 +05:30
parent ba1a268de0
commit 3ca20ebc4e
3 changed files with 129 additions and 109 deletions

View File

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

View File

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

View File

@@ -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) {