Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ requires = [
"setuptools >= 36.4; platform_python_implementation != 'PyPy'",
"wheel",
"setuptools_scm >= 2.1",
"cython >= 0.28.1",
"cython >= 3.1",
"pkgconfig"
]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

[tool.cibuildwheel]
# Skip building PyPy 3.8 wheels, the build currently fails
# Also skip wheels for free-threaded CPython 3.14 (e.g. 'cp314t') until we
# actually support them. We currently aren't thread-safe as we define HB_NO_MT=1.
skip = ["pp38-*", "cp3??t-*"]
skip = ["cp38-*", "cp3??t-*"]
enable = ["pypy"]
test-requires = "pytest"
test-command = "pytest {project}/tests"
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cython>=0.29.1
cython>=3.1

# we need wheel >= 0.31.0 to support Markdown long_description
wheel>=0.31
Expand Down
3 changes: 0 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
[sdist]
formats = zip

[metadata]
license_file = LICENSE
23 changes: 20 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
from setuptools import Extension, setup


def bool_from_environ(key: str):
def bool_from_environ(key: str, default: bool = False):
value = os.environ.get(key)
if not value:
return False
return default
if value == "1":
return True
if value == "0":
Expand All @@ -32,6 +32,12 @@ def bool_from_environ(key: str):
use_system_libraries = bool_from_environ("USE_SYSTEM_LIBS")
use_cython_linetrace = bool_from_environ("CYTHON_LINETRACE")
use_cython_annotate = bool_from_environ("CYTHON_ANNOTATE")
# Python Limited API for stable ABI support is enabled by default.
# Set USE_PY_LIMITED_API=0 to turn it off.
# https://docs.python.org/3.14/c-api/stable.html#limited-c-api
use_py_limited_api = bool_from_environ("USE_PY_LIMITED_API", default=True)
# NOTE: this must be kept in sync with python_requires='>=3.9' below
limited_api_min_version = "0x03090000"


def _configure_extensions_with_system_libs() -> List[Extension]:
Expand All @@ -51,6 +57,9 @@ def _configure_extensions_with_system_libs() -> List[Extension]:
if use_cython_linetrace:
define_macros.append(("CYTHON_TRACE_NOGIL", "1"))

if use_py_limited_api:
define_macros.append(("Py_LIMITED_API", limited_api_min_version))

extension = Extension(
"uharfbuzz._harfbuzz",
define_macros=define_macros,
Expand All @@ -61,6 +70,7 @@ def _configure_extensions_with_system_libs() -> List[Extension]:
language="c++",
libraries=libraries,
library_dirs=library_dirs,
py_limited_api=use_py_limited_api,
)

extension_test = Extension(
Expand All @@ -74,6 +84,7 @@ def _configure_extensions_with_system_libs() -> List[Extension]:
language="c++",
libraries=libraries,
library_dirs=library_dirs,
py_limited_api=use_py_limited_api,
)

return [extension, extension_test]
Expand All @@ -87,6 +98,9 @@ def _configure_extensions_with_vendored_libs() -> List[Extension]:
if use_cython_linetrace:
define_macros.append(("CYTHON_TRACE_NOGIL", "1"))

if use_py_limited_api:
define_macros.append(("Py_LIMITED_API", limited_api_min_version))

extra_compile_args = []
extra_link_args = []
libraries = []
Expand Down Expand Up @@ -124,6 +138,7 @@ def _configure_extensions_with_vendored_libs() -> List[Extension]:
libraries=libraries,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
py_limited_api=use_py_limited_api,
)

extension_test = Extension(
Expand All @@ -138,6 +153,7 @@ def _configure_extensions_with_vendored_libs() -> List[Extension]:
libraries=libraries,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
py_limited_api=use_py_limited_api,
)

return [extension, extension_test]
Expand All @@ -164,10 +180,11 @@ def configure_extensions() -> List[Extension]:
packages=["uharfbuzz"],
zip_safe=False,
setup_requires=["setuptools_scm"],
python_requires=">=3.8",
python_requires=">=3.9",
ext_modules=cythonize(
configure_extensions(),
annotate=use_cython_annotate,
compiler_directives={"linetrace": use_cython_linetrace},
),
options={"bdist_wheel": {"py_limited_api": "cp39"}} if use_py_limited_api else {},
)
65 changes: 31 additions & 34 deletions src/uharfbuzz/_harfbuzz.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ from .charfbuzz cimport *
from libc.stdlib cimport free, malloc, calloc
from libc.string cimport const_char
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_FromKindAndData
)
from cpython.unicode cimport PyUnicode_GetLength, PyUnicode_AsUCS4Copy
from cpython.mem cimport PyMem_Free
from typing import Callable, Dict, List, Sequence, Tuple, Union, NamedTuple
from pathlib import Path
from functools import wraps

# Declare Limited API types and functions (Python 3.3+)
cdef extern from "Python.h":
ctypedef uint32_t Py_UCS4
object PyUnicode_DecodeUTF32(const char *s, Py_ssize_t size, const char *errors, int *byteorder)


DEF STATIC_ARRAY_SIZE = 128

Expand Down Expand Up @@ -341,38 +343,27 @@ cdef class Buffer:

def add_str(self, text: str,
item_offset: int = 0, item_length: int = -1) -> None:
cdef Py_UCS4* ucs4_buffer
cdef Py_ssize_t text_length

cdef Py_ssize_t length = PyUnicode_GET_LENGTH(text)
cdef int kind = PyUnicode_KIND(text)

if kind == PyUnicode_1BYTE_KIND:
hb_buffer_add_latin1(
self._hb_buffer,
<uint8_t*>PyUnicode_1BYTE_DATA(text),
length,
item_offset,
item_length,
)
elif kind == PyUnicode_2BYTE_KIND:
hb_buffer_add_utf16(
self._hb_buffer,
<uint16_t*>PyUnicode_2BYTE_DATA(text),
length,
item_offset,
item_length,
)
elif kind == PyUnicode_4BYTE_KIND:
ucs4_buffer = PyUnicode_AsUCS4Copy(text)
if ucs4_buffer == NULL:
raise MemoryError()
try:
text_length = PyUnicode_GetLength(text)
if text_length == -1:
raise ValueError("Invalid Unicode string")
hb_buffer_add_utf32(
self._hb_buffer,
<uint32_t*>PyUnicode_4BYTE_DATA(text),
length,
<uint32_t*>ucs4_buffer,
text_length,
item_offset,
item_length,
item_length
)
else:
raise AssertionError(kind)
if not hb_buffer_allocation_successful(self._hb_buffer):
raise MemoryError()
if not hb_buffer_allocation_successful(self._hb_buffer):
raise MemoryError()
finally:
PyMem_Free(ucs4_buffer)

def guess_segment_properties(self) -> None:
hb_buffer_guess_segment_properties(self._hb_buffer)
Expand Down Expand Up @@ -965,8 +956,14 @@ cdef class Face:
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)
if text == NULL:
raise MemoryError()
try:
hb_ot_name_get_utf32(self._hb_face, name_id, lang, &length, text)
result = PyUnicode_DecodeUTF32(<char*>text, length * sizeof(uint32_t), NULL, NULL)
return result
finally:
free(text)
return None


Expand Down
Loading