# Contract: Editor T4-pre W2 — the shared typed glyph-asset seam Repo root `/home/jeans/Repos/active/epiphany`. Governed by `spec/CONTRACT_EDITOR_T4PRE_IR.md` §W2 and `spec/PLAN_EDITOR_APP.md` §3.7 / Ruling A, which names "a shared typed glyph-asset seam" as a T4 prerequisite: a canvas tessellator needs typed vector paths from a crate both the renderer and the app can depend on. W1 landed at `dd33b34`. Execution model as T1a/T2/T3/W1: Sonnet subagent, coordinator line-level review with independent mutation re-runs, user deep-dives at contract sign-off, any new golden baseline, and the final report. Mutation discipline throughout: anchor-assert before substituting, restore by reversing, never `git checkout`. **Parallel safety.** The genesis-operation tranche is in flight on the other track, laddered as G1/G2/G3 in `spec/PLAN_GENESIS_OPS.md` under `spec/RULING_GENESIS_PERSISTENCE.md`. Across its rungs it owns `epiphany-core`, `epiphany-ops`, all `.tex` (so the requirement counts move), testkit's requirement-label constants, vectors and fuzz generators, and — at G2 — `epiphany-bundle` for the `OperationEnvelopeBlock` accept-set raise. **This packet touches none of them**, at any rung. Report observed requirement actuals; never assert stale numbers. --- ## The verified starting point Confirm these as you go; they make the packet far smaller than §3.7's wording suggests: * **The typed path type already exists, in `layout-ir`.** `PathCommand`, `GlyphRenderData { outline: Vec, bitmap: Option }`, and `GlyphCatalog::render_data(&self, name) -> Option` are all defined (`layout-ir/src/glyph.rs:~285-315`). **W2 populates a seam that is already designed; it does not design one.** `BravuraCatalog::render_data` returns `None` today by deliberate, documented honesty — "reporting `Some` would claim render data that does not exist." * **The outlines exist, as SVG `d` strings**, in `render-svg/src/outlines_generated.rs`: `BravuraOutline { name, codepoint, path: &'static str, bbox: [f32; 4] }`, staff-space units, y-up, 4 decimals, sorted by name. `outline()` is `pub(crate)`; `bundled_glyph_count()` and `smufl_codepoint()` are `pub` but have **no callers outside `render-svg`** (verified workspace-wide). * **`Bravura.otf` is NOT in the tree.** `render-svg/tools/` holds only `extract_bravura_outlines.py` and `OFL.txt`; the generated header pins source SHA-256s verified *at extraction time*. Re-running the extractor therefore requires fetching the font and **cannot be assumed**. This determines pin 4. * **The metrics table is conformance identity.** `BRAVURA_METRICS` lives in `layout-ir/src/glyph.rs`; `metrics_hash_for` hashes `(name, metrics)` pairs with the *values* participating (the crate's own note at `glyph.rs:372-375` exists precisely to prove a field participates), and `GlyphCatalogIdentity` is encoded into `ResolvedLayoutIR`'s canonical bytes. ## Design pins 1. **A new crate, `epiphany-glyphs`**, owns the glyph assets: the moved `outlines_generated.rs`, the extractor tool, and **`OFL.txt`, which travels with the redistributed outlines** (the generated file's license header stays intact — Bravura is SIL OFL 1.1 and the notice is a redistribution condition, not decoration). It depends on `epiphany-layout-ir` and exposes a catalog type implementing `GlyphCatalog` with a **real `render_data`**. No cycle: glyphs → layout-ir, never the reverse. 2. **`render-svg` keeps its public API and its exact output.** It depends on `epiphany-glyphs`, and `bundled_glyph_count`/`smufl_codepoint` remain exported from `render-svg` (delegating or re-exporting) even though nothing outside calls them — an API that costs one `pub use` is not worth breaking. `font_subset_generated.rs` stays in `render-svg`: an embeddable font subset is a renderer concern, not a shared asset. 3. **The metrics table does not move and does not change.** Out of bounds entirely. Any edit to it churns every conformance byte in the repo. 4. **Typed paths are derived from the `d` strings by an in-crate parser, not by re-running the extractor.** The font is not in the tree, so regeneration is not reproducible here. The parser covers exactly the grammar the generator emits — absolute `M`/`L`/`C`/`Z`, decimal coordinates — and the crate stays **dependency-free** (it is in the MSRV closure; no new dependency, no `build.rs`). 5. **Equivalence is proven by round-trip, not asserted.** Parse every bundled glyph's `d`, re-emit it in the generator's exact formatting, and compare to the original string **byte-for-byte**. If all bundled glyphs round-trip, the typed form provably describes the same geometry and no geometric spot-checking is needed. *Sanctioned fallback if the generator's number formatting cannot be reproduced exactly:* parse both the original and the re-emitted string and compare the full coordinate sequences as exact `f32`. **Report which was used and why** — do not silently take the weaker one. 6. **`render_data` is deterministic and side-effect-free.** Caching (a one-time lazily built table) is permitted; it must not change results, and two calls must return equal data. ## Tests + minimum mutations * **(g1) parser round-trip** over every bundled glyph (pin 5) — the packet's load-bearing test. *Mutation:* perturb one parsed coordinate → dies. * **(g2) `Close` survives parsing** — *mutation:* drop the trailing `Z` → dies. * **(g3) every pipeline glyph has render data** — the `BRAVURA_METRICS` name set all resolve through the new catalog's `render_data`, mirroring `render-svg`'s existing `every_pipeline_glyph_has_a_bundled_outline`. *Mutation:* return `None` for one bundled glyph → dies. * **(g4) outline ink fits the declared metrics bbox** — the cross-table consistency the tree asserts in prose and tests nowhere: `glyph.rs:107-109` claims the metrics and outlines come from the same release "so reserved advances/bboxes and the drawn ink agree." Compare each glyph's outline extent (staff spaces) against its `BRAVURA_METRICS` bbox (font units, 250 per staff space), with a stated tolerance for the outlines' 4-decimal rounding. **Report the actual worst-case deviation.** *Mutation:* inflate one parsed coordinate past the declared box → dies. **If a real glyph fails, that is a finding to report — not a reason to widen the tolerance.** It would mean engraving reserves the wrong space for that glyph, the same bug class that twice bit the vertical metric. * **(g5) `render-svg`'s bytes do not move** — *mutation:* emit the `d` from the typed form instead of the stored string → the goldens die. * **(g6) absolute, not relative** — *mutation:* treat a `C` as relative → round-trip dies. ## Blast radius New `crates/epiphany-glyphs/**` (including the moved `outlines_generated.rs`, `tools/extract_bravura_outlines.py`, `tools/OFL.txt`, and a `DECISIONS.md`); `crates/epiphany-render-svg/src/{lib.rs, outline.rs, svg.rs}` and its `DECISIONS.md`; the workspace `Cargo.toml` member list. Nothing else — no `layout-ir` edit (the seam is already there), no `epiphany-core`, no `epiphany-ops`, no `epiphany-bundle`, no `.tex`, no CI change (the MSRV job runs `--workspace --exclude epiphany-editor-gui`, so a new crate joins automatically — which is exactly why it must be MSRV-clean and dependency-free). ## Gate (actual output) The standard six; conformance **9/9 with `--features golden-gate`, 8/8 without** (both run); requirement labels reported as observed. Plus the two W1 byte locks, run as before/after comparisons against the base commit and reported with their actual sizes: * **Layout canonical bytes byte-identical** for the reference-suite fixtures plus `ten_measure_single_staff(0)` and `ten_measure_with_slurs(0)`. (W1's numbers, for reference: 21615 / 6948 / 3839 / 5602 / 2299 / 2299 / 21615 / 22119.) * **All five GUI goldens byte-identical.** Nothing here may change a pixel; no golden is re-blessed in this packet. * **`cargo tree -p epiphany-glyphs` shows no third-party dependency.** ## Coordinator deliverable, alongside `spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md` — W3, the text-run primitive decision (Ruling A criterion 3: shaping, fallback, bidi, and metrics consistent across canvas, SVG/PDF export, hit testing, and the accessibility tree). A decision document, not an implementation, written by Fable while W2 runs, because getting it wrong forecloses the toolkit choice T4's spike exists to make freely. ## Report Per packet, as every tranche: files + summary, exact asserted values (including g4's worst-case deviation), every mutation with kill evidence, gate output, deviations flagged explicitly.