Repository: harfbuzz/uharfbuzz (language: python)

Issue:
coretext shaper causes segfault on "sparse" font
I have a font that only has GPOS, cmap, head, maxp and post tables, which works perfectly fine for the default (ot) shaper, but crashes hard when specifying the coretext shaper on macOS.

Relates to https://github.com/justvanrossum/fontgoggles/pull/144

(FontGoggles compiles this kind of minimal fonts on the fly to do shaping for UFO sources: metrics come from the source via hb callbacks, and outlines are directly drawn from the sources, too.)

I'll open a PR that reproduces the problem.
coretext shaper causes segfault on "sparse" font
I have a font that only has GPOS, cmap, head, maxp and post tables, which works perfectly fine for the default (ot) shaper, but crashes hard when specifying the coretext shaper on macOS.

Relates to https://github.com/justvanrossum/fontgoggles/pull/144

(FontGoggles compiles this kind of minimal fonts on the fly to do shaping for UFO sources: metrics come from the source via hb callbacks, and outlines are directly drawn from the sources, too.)

I'll open a PR that reproduces the problem.
coretext shaper causes segfault on "sparse" font
I have a font that only has GPOS, cmap, head, maxp and post tables, which works perfectly fine for the default (ot) shaper, but crashes hard when specifying the coretext shaper on macOS.

Relates to https://github.com/justvanrossum/fontgoggles/pull/144

(FontGoggles compiles this kind of minimal fonts on the fly to do shaping for UFO sources: metrics come from the source via hb callbacks, and outlines are directly drawn from the sources, too.)

I'll open a PR that reproduces the problem.


Discussion / hints:

Fix patch:
diff --git a/src/uharfbuzz/_harfbuzz.pyx b/src/uharfbuzz/_harfbuzz.pyx
index cebbe75..aa97a6f 100644
--- a/src/uharfbuzz/_harfbuzz.pyx
+++ b/src/uharfbuzz/_harfbuzz.pyx
@@ -21,7 +21,6 @@ cdef extern from "Python.h":
     int PyUnicode_1BYTE_KIND
     int PyUnicode_2BYTE_KIND
     int PyUnicode_4BYTE_KIND
-    char* PyUnicode_AsUTF8(object unicode)
 
 
 cdef int msgcallback(hb_buffer_t *buffer, hb_font_t *font, const char* message, void* userdata):
@@ -619,13 +618,6 @@ cdef class FontFuncs:
         self._nominal_glyph_func = func
 
 
-cdef const char ** to_cstring_array(list_str):
-    cdef const char **ret = <const char **>malloc(len(list_str) * sizeof(char *))
-    for i in range(len(list_str)):
-        ret[i] = PyUnicode_AsUTF8(list_str[i])
-    return ret
-
-
 def shape(font: Font, buffer: Buffer,
         features: Dict[str,Union[int,bool,Sequence[Tuple[int,int,Union[int,bool]]]]] = None,
         shapers: List[str] = None) -> None:
@@ -633,7 +625,7 @@ def shape(font: Font, buffer: Buffer,
     cdef hb_feature_t* hb_features
     cdef bytes packed
     cdef hb_feature_t feat
-    cdef const char **c_shapers
+    cdef const char* c_shapers[10]
     size = 0
     hb_features = NULL
     try:
@@ -662,9 +654,11 @@ def shape(font: Font, buffer: Buffer,
                         hb_features[i] = feat
                         i += 1
         if shapers:
-            c_shapers = to_cstring_array(shapers)
+            for i, shaper in enumerate(shapers[:9]):
+                packed = shaper.encode()
+                c_shapers[i] = packed
+            c_shapers[i + 1] = NULL
             hb_shape_full(font._hb_font, buffer._hb_buffer, hb_features, size, c_shapers)
-            free(c_shapers)
         else:
             hb_shape(font._hb_font, buffer._hb_buffer, hb_features, size)
     finally:


Test patch:
diff --git a/tests/data/SparseFont.ttf b/tests/data/SparseFont.ttf
new file mode 100644
index 0000000..6e769da
Binary files /dev/null and b/tests/data/SparseFont.ttf differ
diff --git a/tests/test_uharfbuzz.py b/tests/test_uharfbuzz.py
index 3f7f868..cad739a 100644
--- a/tests/test_uharfbuzz.py
+++ b/tests/test_uharfbuzz.py
@@ -1,5 +1,6 @@
 import uharfbuzz as hb
 from pathlib import Path
+import sys
 import pytest
 
 
@@ -7,6 +8,7 @@
 ADOBE_BLANK_TTF_PATH = TESTDATA / "AdobeBlank.subset.ttf"
 OPEN_SANS_TTF_PATH = TESTDATA / "OpenSans.subset.ttf"
 MUTATOR_SANS_TTF_PATH = TESTDATA / "MutatorSans-VF.subset.ttf"
+SPARSE_FONT_TTF_PATH = TESTDATA / "SparseFont.ttf"
 
 
 @pytest.fixture
@@ -50,6 +52,15 @@ def mutatorsans():
     return font
 
 
+@pytest.fixture
+def sparsefont():
+    """Return a font that only has a few tables:
+    GPOS, cmap, head, maxp and post"""
+    face = hb.Face(SPARSE_FONT_TTF_PATH.read_bytes())
+    font = hb.Font(face)
+    return font
+
+
 class TestBuffer:
     def test_init(self):
         buf = hb.Buffer()
@@ -492,3 +503,11 @@ def test_uharfbuzz_version():
     v = hb.__version__
     assert isinstance(v, str)
     assert "unknown" not in v
+
+
+@pytest.mark.skipif(sys.platform != "darwin", reason="requires macOS")
+def test_sparsefont_coretext(sparsefont):
+    buf = hb.Buffer()
+    buf.add_str("ABC")
+    buf.guess_segment_properties()
+    hb.shape(sparsefont, buf, shapers=["coretext"])