Skip to content

Commit

Permalink
Bind hb-ot-name APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
khaledhosny committed Nov 23, 2024
1 parent d0d3807 commit 4d4989d
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 2 deletions.
80 changes: 79 additions & 1 deletion src/uharfbuzz/_harfbuzz.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ from cpython.pycapsule cimport PyCapsule_GetPointer, PyCapsule_IsValid
from cpython.unicode cimport (
PyUnicode_1BYTE_DATA, PyUnicode_2BYTE_DATA, PyUnicode_4BYTE_DATA,
PyUnicode_1BYTE_KIND, PyUnicode_2BYTE_KIND, PyUnicode_4BYTE_KIND,
PyUnicode_KIND, PyUnicode_GET_LENGTH
PyUnicode_KIND, PyUnicode_GET_LENGTH, PyUnicode_FromKindAndData
)
from typing import Callable, Dict, List, Sequence, Tuple, Union, NamedTuple
from pathlib import Path
Expand Down Expand Up @@ -517,6 +517,40 @@ cdef hb_blob_t* _reference_table_func(
table, len(table), HB_MEMORY_MODE_READONLY, NULL, NULL)


class OTNameIdPredefined(IntEnum):
COPYRIGHT = HB_OT_NAME_ID_COPYRIGHT
FONT_FAMILY = HB_OT_NAME_ID_FONT_FAMILY
FONT_SUBFAMILY = HB_OT_NAME_ID_FONT_SUBFAMILY
UNIQUE_ID = HB_OT_NAME_ID_UNIQUE_ID
FULL_NAME = HB_OT_NAME_ID_FULL_NAME
VERSION_STRING = HB_OT_NAME_ID_VERSION_STRING
POSTSCRIPT_NAME = HB_OT_NAME_ID_POSTSCRIPT_NAME
TRADEMARK = HB_OT_NAME_ID_TRADEMARK
MANUFACTURER = HB_OT_NAME_ID_MANUFACTURER
DESIGNER = HB_OT_NAME_ID_DESIGNER
DESCRIPTION = HB_OT_NAME_ID_DESCRIPTION
VENDOR_URL = HB_OT_NAME_ID_VENDOR_URL
DESIGNER_URL = HB_OT_NAME_ID_DESIGNER_URL
LICENSE = HB_OT_NAME_ID_LICENSE
LICENSE_URL = HB_OT_NAME_ID_LICENSE_URL
TYPOGRAPHIC_FAMILY = HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY
TYPOGRAPHIC_SUBFAMILY = HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY
MAC_FULL_NAME = HB_OT_NAME_ID_MAC_FULL_NAME
SAMPLE_TEXT = HB_OT_NAME_ID_SAMPLE_TEXT
CID_FINDFONT_NAME = HB_OT_NAME_ID_CID_FINDFONT_NAME
WWS_FAMILY = HB_OT_NAME_ID_WWS_FAMILY
WWS_SUBFAMILY = HB_OT_NAME_ID_WWS_SUBFAMILY
LIGHT_BACKGROUND = HB_OT_NAME_ID_LIGHT_BACKGROUND
DARK_BACKGROUND = HB_OT_NAME_ID_DARK_BACKGROUND
VARIATIONS_PS_PREFIX = HB_OT_NAME_ID_VARIATIONS_PS_PREFIX
INVALID = HB_OT_NAME_ID_INVALID


class OTNameEntry(NamedTuple):
name_id: OTNameIdPredefined | int | None
language: str | None


cdef class Face:
cdef hb_face_t* _hb_face
cdef object _reference_table_func
Expand Down Expand Up @@ -881,6 +915,50 @@ cdef class Face:
start_offset += script_count
return tags

def list_names(self) -> List[OTNameEntry]:
cdef list ret = []
cdef unsigned int num_entries
cdef const hb_ot_name_entry_t* entries
cdef unsigned int i
cdef const_char *cstr
cdef bytes packed

entries = hb_ot_name_list_names(self._hb_face, &num_entries)
for i in range(num_entries):
cstr = hb_language_to_string(entries[i].language)
if cstr is NULL:
language = None
else:
packed = cstr
language = packed.decode()
if entries[i].name_id in iter(OTNameIdPredefined):
name_id = OTNameIdPredefined(entries[i].name_id)
else:
name_id = entries[i].name_id
ret.append(OTNameEntry(name_id=name_id, language=language))
return ret

def get_name(self, name_id: OTNameIdPredefined | int, language: str | None = None) -> str | None:
cdef bytes packed
cdef hb_language_t lang
cdef uint32_t *text
cdef unsigned int length

if language is None:
lang = <hb_language_t>0 # HB_LANGUAGE_INVALID
else:
packed = language.encode()
lang = hb_language_from_string(<char*>packed, -1)

length = hb_ot_name_get_utf32(self._hb_face, name_id, lang, NULL, NULL)
if length:
length += 1 # for the null terminator
text = <uint32_t*>malloc(length * sizeof(uint32_t))
hb_ot_name_get_utf32(self._hb_face, name_id, lang, &length, text)
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, text, length)
return None


class GlyphExtents(NamedTuple):
x_bearing: int
y_bearing: int
Expand Down
26 changes: 26 additions & 0 deletions src/uharfbuzz/charfbuzz.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,32 @@ cdef extern from "hb-ot.h":
HB_OT_NAME_ID_VARIATIONS_PS_PREFIX
HB_OT_NAME_ID_INVALID

ctypedef struct hb_ot_name_entry_t:
hb_ot_name_id_t name_id
hb_language_t language

const hb_ot_name_entry_t *hb_ot_name_list_names(
hb_face_t *face,
unsigned int *num_entries) # OUT. May be NULL.
unsigned int hb_ot_name_get_utf16(
hb_face_t *face,
hb_ot_name_id_t name_id,
hb_language_t language,
unsigned int *text_size, # IN/OUT. May be NULL.
uint16_t *text) # OUT.
unsigned int hb_ot_name_get_utf32(
hb_face_t *face,
hb_ot_name_id_t name_id,
hb_language_t language,
unsigned int *text_size, # IN/OUT. May be NULL.
uint32_t *text) # OUT.
unsigned int hb_ot_name_get_utf8(
hb_face_t *face,
hb_ot_name_id_t name_id,
hb_language_t language,
unsigned int *text_size, # IN/OUT. May be NULL.
char *text) # OUT.

# hb-ot-color.h
hb_bool_t hb_ot_color_has_palettes(hb_face_t *face)
unsigned int hb_ot_color_palette_get_count(hb_face_t *face)
Expand Down
79 changes: 78 additions & 1 deletion tests/test_uharfbuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ def test_axis_infos(self, mutatorsans):

def test_named_instances(self, mutatorsans):
face = mutatorsans.face
assert face.named_instances == [
named_instances = face.named_instances
assert named_instances == [
(258, 259, [0.0, 0.0]),
(260, 261, [0.0, 1000.0]),
(262, 263, [1000.0, 0.0]),
Expand All @@ -286,6 +287,17 @@ def test_named_instances(self, mutatorsans):
(275, 276, [328.0, 500.0]),
]

assert face.get_name(named_instances[0].subfamily_name_id) == "LightCondensed"
assert (
face.get_name(named_instances[0].postscript_name_id)
== "MutatorMathTest-LightCondensed"
)
assert face.get_name(named_instances[-1].subfamily_name_id) == "Medium_Wide_I"
assert (
face.get_name(named_instances[-1].postscript_name_id)
== "MutatorMathTest-Medium_Narrow_I"
)

def test_has_math_data(self, blankfont, mathfont):
assert blankfont.face.has_math_data == False
assert mathfont.face.has_math_data == True
Expand Down Expand Up @@ -673,6 +685,71 @@ def test_has_layout_substitution(self, opensans):
def test_has_no_layout_substitution(self, mathfont):
assert mathfont.face.has_layout_substitution == False

@pytest.mark.parametrize(
"name_id, language, expected",
[
(
hb.OTNameIdPredefined.COPYRIGHT,
None,
"Copyright © 2013, 2015 Adobe Systems Incorporated (http://www.adobe.com/).",
),
(hb.OTNameIdPredefined.FONT_FAMILY, "en", "Adobe Blank"),
(hb.OTNameIdPredefined.FONT_SUBFAMILY, None, "Regular"),
(hb.OTNameIdPredefined.UNIQUE_ID, None, "1.045;ADBO;AdobeBlank;ADOBE"),
(hb.OTNameIdPredefined.FULL_NAME, None, "Adobe Blank"),
(
hb.OTNameIdPredefined.VERSION_STRING,
"en",
"Version 1.045;PS 1.045;hotconv 1.0.82;makeotf.lib2.5.63406",
),
(hb.OTNameIdPredefined.FULL_NAME, "ar", None),
],
)
def test_get_name(self, blankfont, name_id, language, expected):
assert blankfont.face.get_name(name_id, language) == expected

def test_list_names(self, blankfont):
assert blankfont.face.list_names() == [
(hb.OTNameIdPredefined.COPYRIGHT, "en"),
(hb.OTNameIdPredefined.FONT_FAMILY, "en"),
(hb.OTNameIdPredefined.FONT_SUBFAMILY, "en"),
(hb.OTNameIdPredefined.UNIQUE_ID, "en"),
(hb.OTNameIdPredefined.FULL_NAME, "en"),
(hb.OTNameIdPredefined.VERSION_STRING, "en"),
(hb.OTNameIdPredefined.POSTSCRIPT_NAME, "en"),
]

def test_list_names_with_user_names(self, mutatorsans):
assert mutatorsans.face.list_names() == [
(hb.OTNameIdPredefined.COPYRIGHT, "en"),
(hb.OTNameIdPredefined.FONT_SUBFAMILY, "en"),
(hb.OTNameIdPredefined.UNIQUE_ID, "en"),
(hb.OTNameIdPredefined.FULL_NAME, "en"),
(hb.OTNameIdPredefined.VERSION_STRING, "en"),
(hb.OTNameIdPredefined.POSTSCRIPT_NAME, "en"),
(256, "en"),
(257, "en"),
(258, "en"),
(259, "en"),
(260, "en"),
(261, "en"),
(262, "en"),
(263, "en"),
(264, "en"),
(265, "en"),
(266, "en"),
(267, "en"),
(268, "en"),
(269, "en"),
(270, "en"),
(271, "en"),
(272, "en"),
(273, "en"),
(274, "en"),
(275, "en"),
(276, "en"),
]


class TestFont:
def test_get_glyph_extents(self, opensans):
Expand Down

0 comments on commit 4d4989d

Please sign in to comment.