mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Code to read the STAT OpenType table
This commit is contained in:
@@ -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*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user