From ad35cf8c0a30c7ca3d58efab6d8bfb4c3b7be610 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Sun, 15 Sep 2024 15:31:35 +0300 Subject: [PATCH] Bind hb-ot-var APIs --- src/uharfbuzz/_harfbuzz.pyx | 79 ++++++++++++++++++++++++++++++++++++- src/uharfbuzz/charfbuzz.pxd | 52 ++++++++++++++++++++++++ tests/test_uharfbuzz.py | 26 ++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/src/uharfbuzz/_harfbuzz.pyx b/src/uharfbuzz/_harfbuzz.pyx index 424329d..6114dfa 100644 --- a/src/uharfbuzz/_harfbuzz.pyx +++ b/src/uharfbuzz/_harfbuzz.pyx @@ -8,7 +8,7 @@ from libc.stdlib cimport free, malloc, calloc from libc.string cimport const_char from collections import namedtuple from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid -from typing import Callable, Dict, List, Sequence, Tuple, Union +from typing import Callable, Dict, List, Sequence, Tuple, Union, NamedTuple from pathlib import Path @@ -421,6 +421,26 @@ cdef class Blob: return self._data +class OTVarAxisFlags(IntFlag): + HIDDEN = HB_OT_VAR_AXIS_FLAG_HIDDEN + + +class OTVarAxisInfo(NamedTuple): + axis_index: int + tag: str + name_id: int + flags: OTVarAxisFlags + min_value: float + default_value: float + max_value: float + + +class OTVarNamedInstance(NamedTuple): + subfamily_name_id: int + postscript_name_id: int + design_coords: List[float] + + cdef hb_user_data_key_t k @@ -557,6 +577,63 @@ cdef class Face: hb_face_collect_variation_unicodes(self._hb_face, variation_selector, s._hb_set) return s + @property + def has_var_data(self) -> bool: + return hb_ot_var_has_data(self._hb_face) + + @property + def axis_infos(self) -> list[OTVarAxisInfo]: + cdef unsigned int axis_count = STATIC_ARRAY_SIZE + cdef hb_ot_var_axis_info_t axis_array[STATIC_ARRAY_SIZE] + cdef list infos = [] + cdef char cstr[5] + cdef bytes packed + cdef unsigned int i + cdef unsigned int start_offset = 0 + while axis_count == STATIC_ARRAY_SIZE: + hb_ot_var_get_axis_infos( + self._hb_face, start_offset, &axis_count, axis_array) + for i in range(axis_count): + hb_tag_to_string(axis_array[i].tag, cstr) + cstr[4] = b'\0' + packed = cstr + infos.append( + OTVarAxisInfo( + axis_index=axis_array[i].axis_index, + tag=packed.decode(), + name_id=axis_array[i].name_id, + flags=axis_array[i].flags, + min_value=axis_array[i].min_value, + default_value=axis_array[i].default_value, + max_value=axis_array[i].max_value + ) + ) + start_offset += axis_count + return infos + + @property + def named_instances(self) -> list[OTVarNamedInstance]: + instances = [] + cdef hb_face_t* face = self._hb_face + cdef unsigned int instance_count = hb_ot_var_get_named_instance_count(face) + cdef unsigned int axis_count = hb_ot_var_get_axis_count(face) + cdef hb_ot_name_id_t subfamily_name_id + cdef hb_ot_name_id_t postscript_name_id + cdef float* coords = malloc(axis_count * sizeof(float)) + cdef unsigned int coord_length + for i in range(instance_count): + coord_length = axis_count + hb_ot_var_named_instance_get_design_coords(face, i, &coord_length, coords) + instances.append( + OTVarNamedInstance( + subfamily_name_id=hb_ot_var_named_instance_get_subfamily_name_id(face, i), + postscript_name_id=hb_ot_var_named_instance_get_postscript_name_id(face, i), + design_coords=[coords[j] for j in range(coord_length)], + ) + ) + free(coords) + return instances + # typing.NamedTuple doesn't seem to work with cython GlyphExtents = namedtuple( diff --git a/src/uharfbuzz/charfbuzz.pxd b/src/uharfbuzz/charfbuzz.pxd index 79a04c2..e8ba59b 100644 --- a/src/uharfbuzz/charfbuzz.pxd +++ b/src/uharfbuzz/charfbuzz.pxd @@ -1211,6 +1211,58 @@ cdef extern from "hb-ot.h": hb_font_t *font, hb_ot_metrics_tag_t metrics_tag) + # hb-ot-var.h + ctypedef enum hb_ot_var_axis_flags_t: + HB_OT_VAR_AXIS_FLAG_HIDDEN + _HB_OT_VAR_AXIS_FLAG_MAX_VALUE + + ctypedef struct hb_ot_var_axis_info_t: + unsigned int axis_index + hb_tag_t tag + hb_ot_name_id_t name_id + hb_ot_var_axis_flags_t flags + float min_value + float default_value + float max_value + unsigned int reserved + + hb_bool_t hb_ot_var_has_data(hb_face_t *face) + unsigned int hb_ot_var_get_axis_count(hb_face_t *face) + unsigned int hb_ot_var_get_axis_infos( + hb_face_t *face, + unsigned int start_offset, + unsigned int *axes_count, # IN/OUT + hb_ot_var_axis_info_t *axes_array) # OUT + hb_bool_t hb_ot_var_find_axis_info( + hb_face_t *face, + hb_tag_t tag, + hb_ot_var_axis_info_t *axis_info) + + unsigned int hb_ot_var_get_named_instance_count(hb_face_t *face) + hb_ot_name_id_t hb_ot_var_named_instance_get_subfamily_name_id( + hb_face_t *face, + unsigned int instance_index) + hb_ot_name_id_t hb_ot_var_named_instance_get_postscript_name_id( + hb_face_t *face, + unsigned int instance_index) + unsigned int hb_ot_var_named_instance_get_design_coords( + hb_face_t *face, + unsigned int instance_index, + unsigned int *coords_length, # IN/OUT + float *coords) # OUT + + void hb_ot_var_normalize_variations( + hb_face_t *face, + const hb_variation_t *variations, # IN + unsigned int variations_length, + int *coords, # OUT + unsigned int coords_length) + void hb_ot_var_normalize_coords( + hb_face_t *face, + unsigned int coords_length, + const float *design_coords, # IN + int *normalized_coords) # OUT + cdef extern from "hb-subset-repacker.h": ctypedef struct hb_link_t: unsigned int width diff --git a/tests/test_uharfbuzz.py b/tests/test_uharfbuzz.py index 6e7a33b..a6951c0 100644 --- a/tests/test_uharfbuzz.py +++ b/tests/test_uharfbuzz.py @@ -260,6 +260,32 @@ def test_properties(self, blankfont): assert face.variation_selectors == hb.Set() assert face.variation_unicodes(1) == hb.Set() + def test_has_var_data(self, blankfont, mutatorsans): + assert blankfont.face.has_var_data == False + assert mutatorsans.face.has_var_data == True + + def test_axis_infos(self, mutatorsans): + face = mutatorsans.face + assert face.axis_infos == [ + (0, "wdth", 256, 0, 0.0, 0.0, 1000.0), + (1, "wght", 257, 0, 0.0, 0.0, 1000.0), + ] + + def test_named_instances(self, mutatorsans): + face = mutatorsans.face + assert face.named_instances == [ + (258, 259, [0.0, 0.0]), + (260, 261, [0.0, 1000.0]), + (262, 263, [1000.0, 0.0]), + (264, 265, [1000.0, 1000.0]), + (266, 267, [327.0, 500.0]), + (268, 269, [569.0780029296875, 1000.0]), + (270, 271, [1000.0, 500.0]), + (272, 273, [794.52197265625, 775.6090087890625]), + (274, 65535, [93.052001953125, 658.5969848632812]), + (275, 276, [328.0, 500.0]), + ] + class TestFont: def test_get_glyph_extents(self, opensans):