Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5b7c1c1
capi: add face metadata, table enumeration, and Unicode scripts
behdad Oct 7, 2026
ef6fe13
capi: support batched nominal glyph callbacks
behdad Oct 7, 2026
791961d
Format face regression tests
behdad Oct 7, 2026
3605e70
Format face regression tests
behdad Oct 7, 2026
ad164e5
Format face regression tests
behdad Oct 7, 2026
def02d8
Format batch regression tests
behdad Oct 7, 2026
b78ef5e
Format batch regression tests
behdad Oct 7, 2026
055c51d
capi: report HarfBuzz compatibility version and split OpenType aliases
behdad Oct 7, 2026
970e3d8
Merge branch 'capi/skia-face-unicode' into capi/skia-nominal-glyphs
behdad Oct 7, 2026
3c4540d
Merge branch 'capi/skia-nominal-glyphs' into capi/skia-compatibility
behdad Oct 7, 2026
17945d0
capi: add Skera-backed subset library sharing HarfRust handles
behdad Oct 7, 2026
7d5633e
Merge branch 'main' of github.com:harfbuzz/harfrust into capi/skia-su…
behdad Oct 8, 2026
d332cc4
capi: integrate subsetting behind an optional subset feature
behdad Oct 8, 2026
7a842aa
Add optional Skrifa-backed C drawing and painting APIs
behdad Oct 8, 2026
fff6fef
Fix rendering test assertions for Clippy 1.99
behdad Oct 8, 2026
a42b678
capi: share core color definitions with paint callbacks
behdad Oct 8, 2026
d63310d
Merge main into capi/skia-subset to update the C API stack
behdad Oct 9, 2026
fc9928a
Merge updated subset base into capi/skrifa-draw-paint
behdad Oct 9, 2026
e491622
Merge main into capi/skia-subset
behdad Oct 16, 2026
392710a
Merge capi/skia-subset into capi/skrifa-draw-paint
behdad Oct 16, 2026
0fb88cd
Merge main and released C API stack into draw and paint
behdad Oct 16, 2026
dbd0430
Merge updated capi/released-skia-runtime into capi/skrifa-draw-paint
behdad Oct 17, 2026
8d18a34
Merge updated capi/released-skia-runtime into capi/skrifa-draw-paint
behdad Oct 17, 2026
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
33 changes: 33 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,39 @@ jobs:
- name: Rustfmt
run: cargo fmt --all -- --check

- name: Draw and paint C API
run: |
cargo build -p harfrust_capi --features draw --target-dir target/capi-draw
cargo test -p harfrust_capi --features draw
cargo +1.85 check -p harfrust_capi --features draw,paint
cargo build -p harfrust_capi --features subset,paint
cargo test -p harfrust_capi --features subset,paint
python3 - <<'PY'
import ctypes, sys
suffix = "dylib" if sys.platform == "darwin" else "so"
for directory, draw, paint, subset in [
("capi-default", False, False, False),
("capi-draw", True, False, False),
(None, True, True, True),
]:
base = f"target/{directory}/debug" if directory else "target/debug"
lib = ctypes.CDLL(f"{base}/libharfrust_c.{suffix}")
assert hasattr(lib, "hr_shape")
assert hasattr(lib, "hr_font_draw_glyph_or_fail") == draw
assert hasattr(lib, "hr_font_paint_glyph_or_fail") == paint
assert hasattr(lib, "hr_subset_or_fail") == subset
PY
cc -std=c99 -pedantic-errors -Wall -Wextra -Werror \
-Iharfrust_capi/include harfrust_capi/examples/rendering.c \
-Ltarget/debug -lharfrust_c -Wl,-rpath,"$PWD/target/debug" \
-o /tmp/hr-rendering
/tmp/hr-rendering harfrust_capi/tests/fonts/Rendering.ttf
c++ -x c++ -std=c++11 -pedantic-errors -Wall -Wextra -Werror \
-Iharfrust_capi/include harfrust_capi/examples/rendering.c \
-Ltarget/debug -lharfrust_c -Wl,-rpath,"$PWD/target/debug" \
-o /tmp/hr-rendering-cpp
/tmp/hr-rendering-cpp harfrust_capi/tests/fonts/Rendering.ttf

- name: AAT C++ source compatibility
run: |
c++ -std=c++11 -pedantic-errors -Wall -Wextra -Werror \
Expand Down
3 changes: 3 additions & 0 deletions harfrust_capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ categories.workspace = true
[features]
default = []
subset = ["dep:skera", "dep:write-fonts"]
draw = ["dep:skrifa"]
paint = ["draw"]

[dependencies]
harfrust = { workspace = true, features = ["std"] }
read-fonts.workspace = true
skrifa = { version = "0.48.0", default-features = false, features = ["std"], optional = true }
skera = { version = "0.8.0", optional = true }
write-fonts = { version = "0.54.0", default-features = false, features = ["read"], optional = true }

Expand Down
57 changes: 56 additions & 1 deletion harfrust_capi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,62 @@ cbindgen --config harfrust_capi/cbindgen.toml \

Regenerate it after changing any `pub extern "C"` item.

## Optional drawing and painting

Enable `draw` for Skrifa-backed outline extraction, or `paint` for color glyph
painting (`paint` also enables `draw`):

```sh
cargo build -p harfrust_capi --release --features draw,paint
```

These features are off by default. They use the same `hr_font_t`, `hr_face_t`
and C library as shaping. Include `hr-draw.h` or `hr-paint.h` for native names,
or `hr-hb-draw.h` / `hr-hb-paint.h` for HarfBuzz source compatibility. The paint
header includes drawing. Subsetting can be enabled alongside either feature.

`hr_font_draw_glyph_or_fail` extracts unhinted glyf, CFF, CFF2 and VARC outlines
at the font's current normalized variation coordinates and independent X/Y
scales. Draw callbacks receive HarfBuzz-style contour state, with quadratic
to cubic fallback. Both the legacy custom font callbacks and callbacks that
return success are supported, including inheritance from a parent font with
different scales.

`hr_font_paint_glyph_or_fail` traverses COLRv0/v1 paint graphs, including
palettes, gradients, transforms, clips, composites and cached color glyph
callbacks. SVG documents and CBDT/sbix PNG or BGRA images are delivered to the
client's image callback. The client renders those formats and can retain an
image with `hr_blob_reference`; gradient color lines are only valid during
their callback. Missing `fill_glyph` and `push_group_for` callbacks use the
HarfBuzz defaults. Malformed paint graphs return false and unwind open
transforms, clips and groups.

Rendering reads known tables directly and caches an assembled SFNT once per
face. Callback faces work without table enumeration. This cache copies the
rendering tables, so the feature has a per-face memory cost beyond shaping.

Skrifa's outlines are geometrically compatible with HarfBuzz; redundant
closing lines and CFF composite contour order can differ. VARC transform
precision and bitmap extent rounding can also differ slightly. Hinting,
bitmap masks, draw shape helpers, configurable draw/paint budgets, gradient
preprocessing/tiling helpers and
the deprecated `hb_font_get_glyph_shape` names are not exposed. Skrifa's own
paint traversal limits remain in effect.

Regenerate these headers and their aliases after changing the feature APIs:

```sh
cbindgen --config harfrust_capi/cbindgen-draw.toml \
harfrust_capi/src/draw.rs --output harfrust_capi/include/hr-draw.h
cbindgen --config harfrust_capi/cbindgen-paint.toml \
harfrust_capi/src/paint.rs --output harfrust_capi/include/hr-paint.h
python3 scripts/gen-hb-compat-header.py
```

[`examples/rendering.c`](examples/rendering.c) exercises both features as a
C99 consumer. `tests/compare_draw_hb.py` and `tests/compare_paint_hb.py` take
HarfRust and HarfBuzz shared library paths for differential checks.

Packed colors use `hr_color_t` and `HR_COLOR(b, g, r, a)`, with channel
accessors `hr_color_get_blue/green/red/alpha`. These common definitions are
available without enabling painting.
Expand Down Expand Up @@ -145,7 +201,6 @@ same cache.

The following HarfBuzz APIs are not exposed:

- Drawing and painting callbacks (`hb_draw_funcs_t`, `hb_paint_funcs_t`).
- Layout queries beyond the available GSUB/GPOS presence, lookup count, and
glyph collection functions.
- Custom Unicode callbacks (`hb_unicode_funcs_t`); HarfRust's own Unicode data
Expand Down
15 changes: 15 additions & 0 deletions harfrust_capi/cbindgen-draw.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language = "C"
include_guard = "HARFRUST_DRAW_H"
cpp_compat = true
documentation = true
style = "both"
tab_width = 2
includes = ["hr.h"]
header = "/* Generated by cbindgen from harfrust_capi/src/draw.rs. */"

trailer = """
#define HR_DRAW_STATE_DEFAULT {0, 0.f, 0.f, 0.f, 0.f, {0}}
"""

[export]
exclude = ["hr_bool_t", "hr_codepoint_t", "hr_destroy_func_t", "hr_user_data_key_t", "hr_font_t", "hr_font_funcs_t", "hr_blob_t", "hr_tag_t", "hr_glyph_extents_t"]
12 changes: 12 additions & 0 deletions harfrust_capi/cbindgen-paint.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language = "C"
include_guard = "HARFRUST_PAINT_H"
cpp_compat = true
documentation = true
style = "both"
tab_width = 2
includes = ["hr.h", "hr-draw.h"]
after_includes = "struct hr_color_line_t;"
header = "/* Generated by cbindgen from harfrust_capi/src/paint.rs. */"

[export]
exclude = ["hr_color_t", "hr_bool_t", "hr_codepoint_t", "hr_destroy_func_t", "hr_user_data_key_t", "hr_font_t", "hr_font_funcs_t", "hr_blob_t", "hr_tag_t", "hr_glyph_extents_t", "hr_draw_funcs_t"]
66 changes: 66 additions & 0 deletions harfrust_capi/examples/rendering.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* The same C99 consumer can be built against HarfRust or HarfBuzz. */
#ifdef USE_HARFBUZZ
#include <hb.h>
#else
#include <hr-hb-paint.h>
#include <hr-hb-draw.h>
#include <hr-hb-subset.h>
#endif
#include <assert.h>
#include <stddef.h>

typedef char draw_state_size[sizeof(hb_draw_state_t) == 48 ? 1 : -1];
typedef char color_stop_size[sizeof(hb_color_stop_t) == 12 ? 1 : -1];
typedef char color_line_size[sizeof(hb_color_line_t) == 13 * sizeof(void *) ? 1 : -1];

static void line(hb_draw_funcs_t *funcs, void *data, hb_draw_state_t *state,
float x, float y, void *user_data)
{
(void)funcs; (void)x; (void)y; (void)user_data;
assert(state->path_open);
++*(unsigned *)data;
}

static void color(hb_paint_funcs_t *funcs, void *data, hb_bool_t foreground,
hb_color_t value, void *user_data)
{
(void)funcs; (void)foreground; (void)user_data;
assert(hb_color_get_alpha(value) == 255);
++*(unsigned *)data;
}

int main(int argc, char **argv)
{
hb_blob_t *blob;
hb_face_t *face;
hb_font_t *font;
hb_draw_funcs_t *draw;
hb_paint_funcs_t *paint;
hb_draw_state_t state = HB_DRAW_STATE_DEFAULT;
unsigned lines = 0, colors = 0;
assert(argc == 2);
blob = hb_blob_create_from_file(argv[1]);
face = hb_face_create(blob, 0);
font = hb_font_create(face);
draw = hb_draw_funcs_create();
paint = hb_paint_funcs_create();
hb_draw_funcs_set_line_to_func(draw, line, NULL, NULL);
hb_paint_funcs_set_color_func(paint, color, NULL, NULL);
hb_draw_funcs_make_immutable(draw);
hb_paint_funcs_make_immutable(paint);
assert(hb_font_draw_glyph_or_fail(font, 1, draw, &lines));
assert(lines == 3);
assert(hb_font_paint_glyph_or_fail(font, 2, paint, &colors, 0,
HB_COLOR(0xaa, 0xbb, 0xcc, 0xff)));
assert(colors == 2);
hb_draw_move_to(draw, &lines, &state, 0, 0);
hb_draw_line_to(draw, &lines, &state, 1, 1);
hb_draw_close_path(draw, &lines, &state);
assert(!state.path_open && lines == 5);
hb_paint_funcs_destroy(paint);
hb_draw_funcs_destroy(draw);
hb_font_destroy(font);
hb_face_destroy(face);
hb_blob_destroy(blob);
return 0;
}
Loading
Loading