Rust native text shaping

Status: accepted at commit e5c17ee. This slice implements native shaping over fixed evidence. It does not extract outlines, build render caches, discover system fonts, or expose a browser operation.

Dependency decision

The implementation uses harfrust 0.13.0, a maintained pure-Rust HarfBuzz-compatible shaper. HarfRust tracks HarfBuzz 14.3.0, forbids unsafe code in its library, exposes all three glyph safety flags, and assigns distinct buffer bits to unsafe-to-concat and safe-to-insert-tatweel. It was selected because the accepted a0 record requires those distinct outputs; candidates that omit or alias them cannot implement the contract without a fork or encoded-string workaround.

The committed oracle uses uharfbuzz/HarfBuzz 14.2.0. Exact agreement on the fixed corpus is required despite the one-minor upstream difference. Expanding scripts or font technologies requires adding pinned records rather than assuming blanket equivalence. Optional Arabic safety flags have one explicit version boundary: C HarfBuzz 14.2 classifies safe-tatweel clusters with additional unsafe flags that HarfRust 14.3 no longer sets. The evidence therefore requires exact glyph geometry and proves that both engines publish safe-tatweel, while a separate exact record proves unsafe-to-concat. It does not mislabel version-specific flag classification as exact parity.

Native boundary

shape_text_a0 consumes a generated ShapingInput and a separate caller-owned font byte slice. It verifies the exact SHA-256 and face index, indexes the face's variation axes once, rejects coordinates on static or unknown axes, applies variations, separate signed 32-bit HarfBuzz x/y scales, direction, script, language, feature ranges, cluster level, text-boundary state, default-ignorable policy, dotted-circle policy, and both optional glyph-flag requests. The result is a generated ShapedGlyph list plus units per em. Outline extraction remains the next independent gate.

Text enters both engines through their UTF-8 APIs: hb_buffer_add_utf8 in the Python oracle and UnicodeBuffer::push_str in HarfRust. Clusters therefore retain UTF-8 byte offsets. The corpus includes a combining sequence whose terminal cluster cannot be confused with a scalar or UTF-16 index, scaled RTL output, a non-global feature range whose boundaries differ from scalar indices, a supported variable-font axis whose gvar phantom-point delta changes the exact advance from 500 to 620, global features, and remove/preserve default-ignorable behavior. Shared Rust and generated Python semantics require unique feature tags. The Python oracle adapter then represents every accepted feature as its ordered (start, end, value) sequence and also fails closed rather than collapsing duplicate tags its mapping API could not preserve in C-array order.

Resource and failure policy

Caller-selected font, text, metadata, feature, and variation ceilings are checked before hashing or parsing. Defaults are permissive safety ceilings, not KiCad file-size restrictions. Font hash mismatch, invalid face, invalid contract input, HarfRust-incompatible variation values, and resource limits have stable native categories and publish no glyphs.

HarfRust owns its internal shaping allocation and has no caller allocation hook. max_glyphs is consequently a post-shape publication gate; internal work remains bounded indirectly by the already-preflighted font and UTF-8 input sizes. If hostile-font profiling shows unacceptable expansion, a process boundary or a shaper-level allocation hook is required rather than claiming the publication limit prevents the allocation.

Evidence and deferrals

scripts/generate_font_shaping_vectors.py regenerates the fixed uharfbuzz records from the tracked KiCad stroke TTF plus a deterministic in-house variable/GSUB fixture and checks both JSON and font bytes. Rack validates every contract record against the generated schema and decoder, validates the separately named versioned flag evidence, then runs the focused native Rust suite. System-font discovery, production variable-font and broad script expansion, outline extraction, final render-cache parity, and WASM packaging remain separate plan steps.