Rust font, shaping, and outline contracts

Status: accepted at commit 34c0424. This slice establishes TypeSpec-owned interchange before native shaping or outline extraction is implemented. The accepted boundary is consumed by the maintained HarfRust successor selected in Rust native text shaping. It does not promote a browser operation or require ambient font discovery.

Boundary

FontBundleManifestA0 carries metadata only. Font files are separate byte buffers indexed by numeric slots, so native callers retain ownership and a future browser ABI can accept separate Uint8Array values without base64 expansion. A valid bundle has unique nonempty IDs, unique in-range slots, exactly one entry per supplied buffer, exact lowercase SHA-256 hashes, finite unique variation axes, and no unreferenced extra buffers.

Callers provide resource ceilings. Every slot, per-font size, aggregate size, and metadata-string budget is preflighted before any font is hashed. The defaults are permissive safety ceilings, not product-size claims, and operations may select lower or higher values based on their trust boundary. Font face existence and supported variation axes require parsing the actual OpenType bytes and remain implementation gates for the shaping slice.

Deterministic resolution

Successful validation returns a private-field indexed bundle handle. An explicit font ID takes precedence over aliases. Without an ID, aliases must select exactly one entry; zero matches report missing_font and multiple matches report ambiguous_font. Native and generated Python resolvers use ID/alias indexes and caller-bounded request alias/string budgets, rather than rescanning every manifest alias. Invalid manifests cannot produce the handle. Browser callers will receive no system-font fallback: only embedded or caller-supplied bundle bytes are in scope.

Attributable text evidence

ShapingRecordA0 gives every oracle a stable case ID and exact comparison policy. It freezes the font hash, face index, ordered variations, scale, direction, script, language, feature ranges, glyph IDs, clusters, advances, offsets, safety flags, cluster level, text-boundary state, default-ignorable policy, dotted-circle policy, and requested output flags. No HarfBuzz buffer default remains implicit.

Text positions use UTF-8 byte offsets. The Rust implementation must feed the complete string through UnicodeBuffer::push_str, whose char_indices clusters are UTF-8 byte offsets, and pass each feature's half-open [start, end) range to HarfRust unchanged. Non-global endpoints must be UTF-8 code-point boundaries; the sole global sentinel is start=0, end=4294967295. Output glyph clusters use the same unit but only scalar-start offsets: the terminal byte length is a valid feature endpoint, never a glyph cluster, and empty text admits no glyphs. The structural combining-mark vector contains byte offsets larger than Unicode-scalar indices so implementations cannot silently substitute scalar or UTF-16 units.

OutlineVectorA0 separately freezes raw move, line, quadratic, cubic, and close commands in finite float64 font design units, paired with positive units_per_em, a nonempty stable case ID, and a coordinate-only absolute tolerance in those same units. Font hashes, font/case IDs, glyph IDs, command kinds/order, face indices, variations, and UPEM remain exact even when coordinates use a tolerance. Fractional coordinates are intentional so TrueType, CFF, and CFF2 outlines share one a0 representation; downstream KiCad scaling and rounding are not part of this record. Generated Rust finite-float newtypes prevent programmatic NaN/infinity construction through the public fields; generated Python decoding and semantic validators enforce the same finite boundary. Final render-cache comparison remains a third gate. Keeping all three layers separate makes a parity failure attributable to shaping, outline extraction, or placement/transformation.

Generation and evidence

The four JSON Schemas plus strict Python, TypeScript, and Rust DTOs are generated from font-text-a0.tsp. Rack L0_052 consumes one language-neutral vector file and checks strict roots, out-of-band bytes, slot/hash semantics, inclusive resource limits, deterministic resolution, and the independent shaping/outline records. Rack L0_049 retains reproducible generation, while L0_046 owns the parity registry and Rust hygiene gate.

Retained implementation gates