Files
kitty/kitty/font-names.c
Kovid Goyal 0084f93e1b MacOS Intel: Fix a crash in the choose-fonts kitten when displaying previews of variable fonts
Looks like a bug in Py_BuildValue for s# values on that platform. Workaround by
constructing the string directly and passing to to Py_BuildValue.

Fixes #7734
2024-08-18 17:22:12 +05:30

370 lines
17 KiB
C

/*
* 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("");
}
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;
}
static bool is_digit(char x) { return '0' <= x && x <= '9'; }
static PyObject*
read_cv_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) {
RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL;
if (limit - table >= 12) {
uint16_t *p = (uint16_t*)(table + 2);
uint16_t name_id = next, tooltip_id = next, sample_id = next, num_params = next, first_value_id = next;
if (name_id) {
RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL;
if (PyDict_SetItemString(ans, "name", name) != 0) return NULL;
}
if (tooltip_id) {
RAII_PyObject(tooltip, get_best_name(name_lookup_table, tooltip_id)); if (!tooltip) return NULL;
if (PyDict_SetItemString(ans, "tooltip", tooltip) != 0) return NULL;
}
if (sample_id) {
RAII_PyObject(sample, get_best_name(name_lookup_table, sample_id)); if (!sample) return NULL;
if (PyDict_SetItemString(ans, "sample", sample) != 0) return NULL;
}
if (num_params && first_value_id) {
RAII_PyObject(params, PyTuple_New(num_params)); if (!params) return NULL;
for (uint16_t i = 0; i < num_params; i++) {
PyObject *pval = get_best_name(name_lookup_table, first_value_id + i); if (!pval) return NULL;
PyTuple_SET_ITEM(params, i, pval);
}
if (PyDict_SetItemString(ans, "params", params) != 0) return NULL;
}
}
Py_INCREF(ans); return ans;
}
static PyObject*
read_ss_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) {
RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL;
if (limit - table < 4) { Py_INCREF(ans); return ans; }
uint16_t *p = (uint16_t*)(table + 2);
uint16_t name_id = next;
if (name_id) {
RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL;
if (PyDict_SetItemString(ans, "name", name) != 0) return NULL;
}
Py_INCREF(ans); return ans;
}
bool
read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) {
if (table_len < 20) return true;
const uint16_t *p = (uint16_t*)table;
const uint8_t *limit = table + table_len;
uint16_t major_version = next, minor_version = next, script_list_offset = next, feature_list_offset = next;
(void)major_version; (void)minor_version; (void)script_list_offset;
const uint8_t *feature_list_table = table + feature_list_offset;
char tag_buf[5] = {0};
if (feature_list_table + 2 >= limit) return true;
p = (uint16_t*)feature_list_table;
uint16_t feature_count = next;
const uint8_t *pos = (uint8_t*)p;
for (uint16_t i = 0; i < feature_count && pos + 6 <= limit; pos += 6, i++) {
memcpy(tag_buf, pos, 4);
RAII_PyObject(tag, PyUnicode_FromString(tag_buf));
if (!tag) return false;
if (PyDict_Contains(output, tag) == 1) continue;
if (PyDict_SetItem(output, tag, Py_None) != 0) return false;
p = (uint16_t*)(pos + 4); uint16_t offset_to_feature_table = next;
const uint8_t *feature_table = feature_list_table + offset_to_feature_table;
if (feature_table + 2 > limit) continue;
p = (uint16_t*)(feature_table); uint16_t offset_to_feature_params_table = next;
if (tag_buf[0] == 'c' && tag_buf[1] == 'v' && is_digit(tag_buf[2]) && is_digit(tag_buf[3])) {
if (offset_to_feature_params_table) {
RAII_PyObject(cv, read_cv_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table));
if (!cv) return false;
if (PyDict_SetItem(output, tag, cv) != 0) return false;
}
} else if (tag_buf[0] == 's' && tag_buf[1] == 's' && '0' <= tag_buf[2] && tag_buf[2] <= '2' && is_digit(tag_buf[3])) {
if (offset_to_feature_params_table) {
RAII_PyObject(ss, read_ss_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table));
if (!ss) return false;
if (PyDict_SetItem(output, tag, ss) != 0) return false;
}
}
}
return true;
}
bool
read_STAT_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) {
RAII_PyObject(design_axes, PyTuple_New(0));
RAII_PyObject(multi_axis_styles, PyTuple_New(0));
if (!design_axes || !multi_axis_styles) return false;
if (table_len < 20) goto ok;
const uint16_t *p = (uint16_t*)table;
uint16_t major_version = next, minor_version = next, size_of_design_axis_entry = next, count_of_design_axis_entries = next;
const uint32_t *p32 = (uint32_t*)p;
uint32_t offset_to_start_of_design_axes_entries = byteswap32(p32++);
p = (uint16_t*)p32;
uint16_t count_of_axis_value_entries = next;
p32 = (uint32_t*)p;
uint32_t offset_to_start_of_axis_value_entries = byteswap32(p32++);
p = (uint16_t*)p32;
uint16_t elided_fallback_name_id = next;
if (major_version == 1 && minor_version < 1) elided_fallback_name_id = 0;
if (PyDict_SetItemString(output, "elided_fallback_name", elided_fallback_name_id ? get_best_name(name_lookup_table, elided_fallback_name_id) : PyUnicode_FromString("")) != 0) return false;
const uint8_t *table_limit = table + table_len;
size_t count = 0;
if (_PyTuple_Resize(&design_axes, count_of_design_axis_entries) == -1) return false;
for (
const uint8_t *pos = table + offset_to_start_of_design_axes_entries;
pos + size_of_design_axis_entry <= table_limit && count < count_of_design_axis_entries;
pos += size_of_design_axis_entry, count++
) {
p = (uint16_t*)(pos + 4);
uint16_t name_id = next, ordering = next;
PyObject *rec = Py_BuildValue("{ss# sN sH sN}", "tag", (char*)pos, 4, "name", get_best_name(name_lookup_table, name_id), "ordering", ordering, "values", PyList_New(0));
if (!rec) return false;
PyTuple_SET_ITEM(design_axes, count, rec);
}
if (_PyTuple_Resize(&design_axes, count) == -1) return false;
count = 0;
const uint8_t *start_of_axis_values_offsets_array = table + offset_to_start_of_axis_value_entries;
Py_ssize_t i = 0;
if (_PyTuple_Resize(&multi_axis_styles, count_of_axis_value_entries) == -1) return false;
for (
const uint8_t *pos = start_of_axis_values_offsets_array;
pos + 2 <= table_limit && count < count_of_axis_value_entries;
pos += 2, count++
) {
p = (uint16_t*)pos;
uint16_t offset = next;
const uint8_t *start_of_axis_values_table = start_of_axis_values_offsets_array + offset;
if (start_of_axis_values_table + 12 > table_limit) continue;
p = (uint16_t*)(start_of_axis_values_table);
uint16_t format = next, axis_index = next, flags = next, value_name_id = next;
p32 = (uint32_t*)p;
#define app(fmt, ...) { \
RAII_PyObject(v, Py_BuildValue("{sH sH sN " fmt "}", \
"format", format, "flags", flags, "name", get_best_name(name_lookup_table, value_name_id), __VA_ARGS__)); \
if (!v) return false; \
PyObject *l = PyDict_GetItemString(PyTuple_GET_ITEM(design_axes, axis_index), "values"); \
if (l && PyList_Append(l, v) != 0) return false; \
}
switch(format) {
case 1: if (p32 + 1 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) {
const double value = next32; app("sd", "value", value);
} break;
case 2: if (p32 + 3 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) {
const double value = next32, minimum = next32, maximum = next32;
app("sd sd sd", "value", value, "minimum", minimum, "maximum", maximum);
} break;
case 3: if (p32 + 2 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) {
const double value = next32, linked_value = next32;
app("sd sd", "value", value, "linked_value", linked_value);
} break;
case 4: if ((uint8_t*)(p) + 6 * axis_index <= table_limit) {
RAII_PyObject(values, PyTuple_New(axis_index));
if (!values) return false;
for (uint16_t n = 0; n < axis_index; n++, p += 3) {
uint16_t actual_axis_index = next;
p32 = (uint32_t*)p; double value = next32;
PyObject *e = Py_BuildValue("{sH sd}", "design_index", actual_axis_index, "value", value);
if (!e) return false;
PyTuple_SET_ITEM(values, n, e);
}
PyObject *e = Py_BuildValue("{sH sN sO}", "flags", flags,
"name", get_best_name(name_lookup_table, value_name_id), "values", values);
if (!e) return false;
PyTuple_SET_ITEM(multi_axis_styles, i++, e);
} break;
}
}
if (_PyTuple_Resize(&multi_axis_styles, i) == -1) return false;
ok:
if (PyDict_SetItemString(output, "design_axes", design_axes) != 0) return false;
if (PyDict_SetItemString(output, "multi_axis_styles", multi_axis_styles) != 0) return false;
return true;
}
bool
read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) {
RAII_PyObject(named_styles, PyTuple_New(0)); if (!named_styles) return false;
RAII_PyObject(axes, PyTuple_New(0)); if (!axes) return false;
if (!table || table_len < 14 * sizeof(uint16_t)) goto ok;
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 sN sO sN}",
"minimum", minimum, "maximum", maximum, "default", def, "tag", PyUnicode_FromStringAndSize((const char*)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;
ok:
if (PyDict_SetItemString(output, "variations_postscript_name_prefix", get_best_name(name_lookup_table, 25)) != 0) return false;
if (PyDict_SetItemString(output, "axes", axes) != 0) return false;
if (PyDict_SetItemString(output, "named_styles", named_styles) != 0) return false;
return true;
}
#undef next32
#undef next