Code to read the STAT OpenType table

This commit is contained in:
Kovid Goyal
2024-04-26 11:25:12 +05:30
parent 1669e099dc
commit 48ed369342
5 changed files with 200 additions and 29 deletions

View File

@@ -819,10 +819,17 @@ get_best_name(CTFace *self, PyObject *nameid) {
static PyObject*
get_variable_data(CTFace *self) {
if (!ensure_name_table(self)) return NULL;
RAII_PyObject(output, PyDict_New());
if (!output) 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);
if (!read_fvar_font_table(table, table_len, self->name_lookup_table, output)) return NULL;
RAII_CoreFoundation(CFDataRef, stable, CTFontCopyTable(self->ct_font, kCTFontTableSTAT, kCTFontTableOptionNoOptions));
table = stable ? CFDataGetBytePtr(stable) : NULL;
table_len = stable ? CFDataGetLength(stable) : 0;
if (!read_STAT_font_table(table, table_len, self->name_lookup_table, output)) return NULL;
Py_INCREF(output); return output;
}
static PyObject*

View File

@@ -119,13 +119,106 @@ 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) {
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))
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;
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;
@@ -181,8 +274,11 @@ read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_look
PyTuple_SET_ITEM(named_styles, i, ns);
}
if (_PyTuple_Resize(&named_styles, i) == -1) return NULL;
return retval;
#undef retval
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

View File

@@ -48,8 +48,10 @@ 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);
bool
read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output);
bool
read_STAT_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output);
static inline void
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {

View File

@@ -1,5 +1,5 @@
from enum import Enum, IntEnum, auto
from typing import Callable, Dict, NamedTuple, Tuple, TypedDict, Union
from typing import Callable, Dict, List, Literal, NamedTuple, Tuple, TypedDict, Union
from kitty.typing import CoreTextFont, FontConfigPattern
@@ -28,10 +28,58 @@ class NamedStyle(TypedDict):
psname: str # can be empty string when not present
class DesignValue1(TypedDict):
format: Literal[1]
flags: int
name: str
value: float
class DesignValue2(TypedDict):
format: Literal[2]
flags: int
name: str
value: float
minimum: float
maximum: float
class DesignValue3(TypedDict):
format: Literal[3]
flags: int
name: str
value: float
linked_value: float
DesignValue = Union[DesignValue1, DesignValue2, DesignValue3]
class DesignAxis(TypedDict):
name: str
ordering: int
tag: str
values: List[DesignValue]
class AxisValue(TypedDict):
design_index: int
value: float
class MultiAxisStyle(TypedDict):
flags: int
name: str
values: Tuple[AxisValue, ...]
class VariableData(TypedDict):
axes: Tuple[VariableAxis, ...]
named_styles: Tuple[NamedStyle, ...]
variations_postscript_name_prefix: str
elided_fallback_name: str
design_axes: Tuple[DesignAxis, ...]
multi_axis_styles: Tuple[MultiAxisStyle, ...]
class FontFeature:

View File

@@ -804,27 +804,45 @@ convert_axis_to_python(Face *face, const FT_Var_Axis *src, FT_UInt flags) {
static PyObject*
get_variable_data(Face *self, PyObject *a UNUSED) {
RAII_FTMMVar(mm);
FT_Error err = FT_Get_MM_Var(self->face, &mm);
if (err) { set_freetype_error("Failed to get variable axis data from font with error:", err); return NULL; }
RAII_PyObject(axes, PyTuple_New(mm->num_axis));
RAII_PyObject(named_styles, PyTuple_New(mm->num_namedstyles));
if (!ensure_name_table(self)) return NULL;
RAII_PyObject(output, PyDict_New()); if (!output) return NULL;
RAII_PyObject(axes, PyTuple_New(0));
RAII_PyObject(named_styles, PyTuple_New(0));
if (!axes || !named_styles) return NULL;
for (FT_UInt i = 0; i < mm->num_namedstyles; i++) {
PyObject *s = convert_named_style_to_python(self, mm->namedstyle + i, mm->axis, mm->num_axis);
if (!s) return NULL;
PyTuple_SET_ITEM(named_styles, i, s);
}
FT_Error err;
FT_ULong length = 0;
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('S', 'T', 'A', 'T'), 0, NULL, &length)) == 0) {
RAII_ALLOC(uint8_t, table, malloc(length));
if (!table) return PyErr_NoMemory();
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('S', 'T', 'A', 'T'), 0, table, &length))) {
set_freetype_error("Failed to load the STAT table from font with error:", err); return NULL;
}
if (!read_STAT_font_table(table, length, self->name_lookup_table, output)) return NULL;
} else if (!read_STAT_font_table(NULL, 0, self->name_lookup_table, output)) return NULL;
if (self->is_variable) {
RAII_FTMMVar(mm);
if ((err = FT_Get_MM_Var(self->face, &mm))) { set_freetype_error("Failed to get variable axis data from font with error:", err); return NULL; }
if (_PyTuple_Resize(&axes, mm->num_axis) == -1) return NULL;
if (_PyTuple_Resize(&named_styles, mm->num_namedstyles) == -1) return NULL;
for (FT_UInt i = 0; i < mm->num_namedstyles; i++) {
PyObject *s = convert_named_style_to_python(self, mm->namedstyle + i, mm->axis, mm->num_axis);
if (!s) return NULL;
PyTuple_SET_ITEM(named_styles, i, s);
}
for (FT_UInt i = 0; i < mm->num_axis; i++) {
FT_UInt flags;
FT_Get_Var_Axis_Flags(mm, i, &flags);
PyObject *s = convert_axis_to_python(self, mm->axis + i, flags);
for (FT_UInt i = 0; i < mm->num_axis; i++) {
FT_UInt flags;
FT_Get_Var_Axis_Flags(mm, i, &flags);
PyObject *s = convert_axis_to_python(self, mm->axis + i, flags);
if (!s) return NULL;
PyTuple_SET_ITEM(axes, i, s);
if (!s) return NULL;
PyTuple_SET_ITEM(axes, i, s);
}
}
return Py_BuildValue("{sO sO sN}", "axes", axes, "named_styles", named_styles, "variations_postscript_name_prefix", _get_best_name(self, 25));
if (PyDict_SetItemString(output, "variations_postscript_name_prefix", _get_best_name(self, 25)) != 0) return NULL;
if (PyDict_SetItemString(output, "axes", axes) != 0) return NULL;
if (PyDict_SetItemString(output, "named_styles", named_styles) != 0) return NULL;
Py_INCREF(output); return output;
}
StringCanvas