Code to read features from GSUB/GPOS tables

This commit is contained in:
Kovid Goyal
2024-05-23 10:39:42 +05:30
parent d7702547cf
commit 0f76d3e13b
6 changed files with 78 additions and 7 deletions

View File

@@ -946,6 +946,21 @@ get_variation(CTFace *self) {
return variation_to_python(src);
}
static PyObject*
get_features(CTFace *self, PyObject *a UNUSED) {
RAII_PyObject(output, PyFrozenSet_New(NULL)); if (!output) return NULL;
RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableGSUB, kCTFontTableOptionNoOptions));
const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL;
size_t table_len = cftable ? CFDataGetLength(cftable) : 0;
if (!read_features_from_font_table(table, table_len, output)) return NULL;
RAII_CoreFoundation(CFDataRef, cfpostable, CTFontCopyTable(self->ct_font, kCTFontTableGPOS, kCTFontTableOptionNoOptions));
table = cfpostable ? CFDataGetBytePtr(cfpostable) : NULL;
table_len = cfpostable ? CFDataGetLength(cfpostable) : 0;
if (!read_features_from_font_table(table, table_len, output)) return NULL;
Py_INCREF(output); return output;
}
static PyObject*
get_variable_data(CTFace *self) {
if (!ensure_name_table(self)) return NULL;
@@ -989,6 +1004,7 @@ static PyMethodDef methods[] = {
METHODB(display_name, METH_NOARGS),
METHODB(postscript_name, METH_NOARGS),
METHODB(get_variable_data, METH_NOARGS),
METHODB(get_features, METH_NOARGS),
METHODB(get_variation, METH_NOARGS),
METHODB(identify_for_debug, METH_NOARGS),
METHODB(set_size, METH_VARARGS),

View File

@@ -1,6 +1,6 @@
import termios
from ctypes import Array, c_ubyte
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload, FrozenSet
from kitty.boss import Boss
from kitty.fonts import FontFeature, VariableData
@@ -433,6 +433,7 @@ class Face:
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ...
def get_variation(self) -> Optional[Dict[str, float]]: ...
def get_features(self) -> FrozenSet[str]: ...
class CoreTextFont(TypedDict):
@@ -467,6 +468,7 @@ class CTFace:
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ...
def get_variation(self) -> Optional[Dict[str, float]]: ...
def get_features(self) -> FrozenSet[str]: ...
def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:

View File

@@ -119,6 +119,28 @@ read_name_font_table(const uint8_t *table, size_t table_len) {
}
bool
read_features_from_font_table(const uint8_t *table, size_t table_len, 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 + 4 < limit; pos += 6, i++) {
memcpy(tag_buf, pos, 4);
RAII_PyObject(tag, PyUnicode_FromString(tag_buf));
if (!tag) return false;
if (PySet_Add(output, tag) != 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));

View File

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

View File

@@ -470,16 +470,19 @@ def develop(family: str = '') -> None:
opts = Options()
opts.font_family = parse_font_spec(family)
ff = get_font_files(opts)
def s(d: Descriptor) -> str:
return str(face_from_descriptor(d))
def s(name: str, d: Descriptor) -> None:
f = face_from_descriptor(d)
print(name, str(f))
features = f.get_features()
print(' Features :', ' '.join(sorted(features)))
print('Medium :', s(ff['medium']))
s('Medium :', ff['medium'])
print()
print('Bold :', s(ff['bold']))
s('Bold :', ff['bold'])
print()
print('Italic :', s(ff['italic']))
s('Italic :', ff['italic'])
print()
print('Bold-Italic:', s(ff['bi']))
s('Bold-Italic:', ff['bi'])
if __name__ == '__main__':

View File

@@ -842,6 +842,31 @@ get_variation(Face *self, PyObject *a UNUSED) {
Py_INCREF(ans); return ans;
}
static PyObject*
get_features(Face *self, PyObject *a UNUSED) {
FT_Error err;
FT_ULong length = 0;
RAII_PyObject(output, PyFrozenSet_New(NULL)); if (!output) return NULL;
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'S', 'U', 'B'), 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('G', 'S', 'U', 'B'), 0, table, &length))) {
set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL;
}
if (!read_features_from_font_table(table, length, output)) return NULL;
}
length = 0;
if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'P', 'O', 'S'), 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('G', 'P', 'O', 'S'), 0, table, &length))) {
set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL;
}
if (!read_features_from_font_table(table, length, output)) return NULL;
}
Py_INCREF(output); return output;
}
static PyObject*
get_variable_data(Face *self, PyObject *a UNUSED) {
if (!ensure_name_table(self)) return NULL;
@@ -992,6 +1017,7 @@ static PyMethodDef methods[] = {
METHODB(identify_for_debug, METH_NOARGS),
METHODB(extra_data, METH_NOARGS),
METHODB(get_variable_data, METH_NOARGS),
METHODB(get_features, METH_NOARGS),
METHODB(get_variation, METH_NOARGS),
METHODB(get_best_name, METH_O),
METHODB(set_size, METH_VARARGS),