epiphany/crates/epiphany-render-svg/DECISIONS.md

6.7 KiB

epiphany-render-svg — decisions and Pass 12 candidates

This file records (a) the QUICKSTART decisions Agent I made for the renderer, and (b) ambiguities batched as Pass 12 candidates (spec/PASS12_BATCH.md) rather than improvised in code.

Scope and status

epiphany-render-svg is the SVG renderer behind the Chapter 7 RenderIR interface: it turns a ResolvedLayoutIR into well-formed SVG 1.1, drawing glyphs from genuine Bravura SMuFL data either as inline outline <path>s (GlyphMode::PathOutline) or as <text> set in an embedded subset font (GlyphMode::EmbeddedFont). Per the QUICKSTART development pattern it was golden-locked against the stub solver's output first, then against the real epiphany-engrave solver once the score→real-notation pass and re-spacing landed. The renderer consumes any solver's ResolvedLayoutIR; it proves renderer faithfulness — resolved geometry preserved, provenance traced, output XML-valid and deterministic — independent of engraving quality.

The non-overreach rule (Chapter 7 / QUICKSTART, Agent I)

The renderer makes SVG-encoding choices only and no engraving-semantic choices. In the default archival mode every emitted element traces to a ResolvedGlyph (and thus a score-graph source, via data-prov/data-source-kind) or to a declared renderer wrapper (the <svg> root, the metadata comment, the y-flip <g>, a per-layer <g>). Traces can be turned off (RenderOptions::emit_provenance = false) for a smaller display-only SVG; that is an explicit mode the metadata comment declares (archival → "carries a data-prov trace", display-only → "provenance traces suppressed"), so a trace-free SVG — including the empty canvas — announces itself rather than passing as archival. A glyph with no bundled outline is surfaced as a diagnostic and drawn as a visible bounding-box fallback <rect> — never silently dropped and never invented. The acceptance harness (archival mode) asserts one drawn element per glyph and one provenance trace per drawn element.

Implementation decisions (QUICKSTART "Decisions you'll need to make")

  1. Spelling / 2. Solver architecture — N/A here (Agent H; epiphany-engrave).
  2. Renderer SVG dialect — SVG 1.1 + inline presentation attributes. Maximum portability for what is effectively a viewer/print artifact; no CSS, no SVG 2 features. (CSS styling can come later if needed.) Output validates under the system xmllint (libxml2) as well as the in-crate well-formedness checker.
  3. Catalog / 5. Binary Format versioning — N/A (Agents K, J).

Local decisions

  • Glyph rendering — two self-contained modes; inline outlines (GlyphMode::PathOutline) is the default and the verified reference. Path outlines make the SVG self-contained (it renders in any browser, image tool, or print pipeline with no font installed) and are byte-golden-locked, at the cost of file size — the QUICKSTART's recommendation. GlyphMode::EmbeddedFont instead references each glyph by SMuFL codepoint with a <text> element drawn from an @font-face-embedded Bravura subset (only the ~33 named glyphs), so the SVG is still self-contained (the font travels in it) and the text is selectable, at a larger file size. The two modes anchor glyphs at the same origin (em = 4 staff spaces), so placement is consistent by construction; the embedded mode is structurally tested rather than byte-golden-locked, and exact rasterisation is the consumer's font renderer's, so path mode remains the pixel-verified one.
  • Embedded-font subset — generated, not a vendored binary. The subset is a deterministic base64 OTF emitted into src/font_subset_generated.rs by tools/extract_bravura_outlines.py --font-out, keeping the "only generated artifacts committed" rule (no font binary is vendored). It retains the font's OFL copyright/license name records (belt-and-suspenders with tools/OFL.txt). Caveat: unlike the geometry-only outlines, the binary subset's exact bytes depend on the fontTools version, which the generated header records.
  • Outline source — the official OFL Bravura.otf, extracted reproducibly. tools/extract_bravura_outlines.py fetches the font + SMuFL glyphnames.json and emits src/outlines_generated.rs. The font is not vendored; only the generated Rust is committed (tools/OFL.txt carries the SIL Open Font License 1.1 under which the outlines are redistributed). Exactly the glyph set the v0 pipeline can name (layout-ir's BRAVURA_METRICS) is bundled; a test asserts every pipeline glyph has an outline (so the table cannot silently fall behind).
  • Coordinate system — staff spaces, y-up, one global flip. Outlines are extracted in staff-space units (SMuFL em = 4 staff spaces, Bravura unitsPerEm = 1000 ⇒ 1 staff space = 250 font units), y-up (musical convention). The whole document is wrapped in one translate(-min_x, max_y) scale(1, -1) group, so every glyph is placed with a plain translate(x, y) and the viewBox is 0 0 W H in staff spaces; width/height carry the px display scale. The renderer never bakes the flip into per-glyph data.
  • Determinism — fixed 4-decimal number formatting, -0 normalised to 0. Outline d data is fixed bundled text; every computed coordinate is formatted identically, so identical input yields byte-identical SVG. The acceptance harness golden-locks the full SVG and a machine snapshot (object/glyph/path/ provenance/layer/per-class/hard-constraint counts + XML validity).
  • No external XML dependency. The workspace is deliberately dependency-light; xml::check_well_formed is a hand-rolled validator for the subset the renderer emits, and the acceptance test additionally cross-checks with xmllint when present so the "XML-validates" claim rests on a real parser too.

Pass 12 candidates

See spec/PASS12_BATCH.md (rows P12-I1, P12-I2, P12-I3). Most relevant here:

  • P12-I1 (resolved by I-1/I-3) — the original stub-only renderer output was a structural placeholder, so the human-review visual-acceptance gate ("the SVG visually parses as standard music notation") was deferred until real engraving landed. The real notation pass and real-Engraver goldens now close that gate; the stub path remains locked as an interface/reference mode, not the visual deliverable.
  • P12-I2 (resolved) — the stable layout-object id derivation (MUSCLOID, Pass-11 item 2.6) is wired: epiphany-determinism reserves the built-in MUSCLOID tag and layout-ir provenance routes through it. The renderer traces provenance by the (now MUSCLOID-tagged) stable_id; only the data-prov hex in the goldens changed (the ids are non-canonical).