epiphany/spec/CONTRACT_EDITOR_T4PRE_W2_GL...

157 lines
8.6 KiB
Markdown

# 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<PathCommand>, bitmap: Option<GlyphBitmap> }`,
and `GlyphCatalog::render_data(&self, name) -> Option<GlyphRenderData>` 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.