A canvas tessellator needs typed vector paths, and the outlines were private to render-svg as SVG `d` strings. They move to a new epiphany-glyphs crate, which implements layout-ir's GlyphCatalog with a real render_data. The seam was already designed and merely unpopulated: PathCommand, GlyphRenderData, and GlyphCatalog::render_data all existed, and BravuraCatalog returned None by deliberate documented honesty -- reporting Some would claim render data that does not exist. Like W1, this packet fills a structure rather than building one. Typed paths are parsed from the `d` strings rather than regenerated, because Bravura.otf is not vendored -- tools/ carries only the extractor and its OFL notice, and the generated header pins source hashes verified at extraction time. Equivalence is therefore proven rather than assumed: parse every bundled glyph, re-emit, compare byte-for-byte. All thirty-seven round-trip exactly, so the sanctioned coordinate-comparison fallback was not needed. The real grammar is wider than the contract's summary claimed -- absolute V/H lineto shorthand appears in twenty-three of thirty-seven glyphs, and numbers carry nought to three fractional digits with trailing zeros stripped, never a fixed four. The parser lowers V/H to LineTo and the emitter reconstructs the shorthand from geometry. Nothing rendered moves. The outline data is byte-identical to its previous home (the only edit is BravuraOutline's visibility), render-svg keeps emitting the stored strings and re-exports bundled_glyph_count and smufl_codepoint unchanged, and BRAVURA_METRICS is untouched -- it feeds metrics_hash and hence GlyphCatalogIdentity, which is inside the resolved layout's canonical bytes. Verified: all eight reference fixtures produce byte-identical canonical layouts, and all five GUI goldens are unchanged. The cross-table test locks what glyph.rs asserted only in prose: every glyph's declared metrics bbox contains its drawn ink, re-derived through true cubic-bezier extrema rather than control points. Worst-case deviation across the whole table is exactly zero, at noteheadBlack's left edge. Stated honestly, this is a rounding-chain lock rather than an independent cross-check -- the same extractor emits both tables and deliberately rounds the metric box outward so it always contains the ink -- but it catches a unit-conversion error, a regeneration mismatch, or a future inward rounding, none of which had a test before. Five of six mutations killed, all coordinator-re-verified independently. The sixth is an honest survivor and a defect in the contract rather than the work: it asked for proof that re-emitting from the typed form breaks the goldens, which the round-trip pin makes a no-op. The goldens were re-proven live a different way -- perturbing one glyph's stored path by one digit flips one pixel of 279,992 and fails all five. Gate: 35 suites / 1351 tests / 0 failed, conformance 9/9 with golden-gate and 8/8 without, requirement labels 6/6, clippy 0, cargo tree showing exactly one dependency and no third-party crate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| examples | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-render-svg
Agent I's SVG renderer behind the Epiphany RenderIR interface (spec
Chapter 7): turns a ResolvedLayoutIR into well-formed SVG 1.1, drawing each
glyph from genuine Bravura SMuFL data — inline outline <path>s by default
(GlyphMode::PathOutline), or <text> set in an @font-face-embedded Bravura
subset (GlyphMode::EmbeddedFont). It is the visible end of the v0
Score → layout IR pipeline.
Status
The Score → layout IR → SVG pipeline renders recognizable notation — clefs,
noteheads at clef-relative staff positions, accidentals, key/time signatures,
rests, barlines, and the staff lines and stems that connect them. Output is
golden-locked against both the interface-only stub solver and Agent I's real
epiphany-engrave solver (whose horizontal spacing pass re-spaces the glyphs),
and the layout round-trip (criterion 6) runs through both. What the renderer
itself guarantees, independent of engraving quality: real Bravura glyphs,
provenance preserved to the score graph, output XML-valid and deterministic. The
renderer consumes any solver's ResolvedLayoutIR.
Demo
# Render a fixture to SVG (stub solver by default):
cargo run -p epiphany-render-svg --example render_fixture -- \
ten_measure_single_staff > out.svg
# Drive Agent I's engrave solver instead, to bisect renderer-vs-solver:
cargo run -p epiphany-render-svg --example render_fixture -- \
ten_measure_single_staff --solver=real > out.svg
# Use the embedded-font glyph mode (<text> + @font-face) instead of inline paths:
cargo run -p epiphany-render-svg --example render_fixture -- \
ten_measure_single_staff --glyph-mode=embedded > out.svg
Fixtures: ten_measure_single_staff, valid_score_rich, valid_score. Stats and
diagnostics go to stderr; the SVG goes to stdout.
Library
use epiphany_render_svg::{render, RenderOptions};
let out = render(&resolved_layout_ir, &RenderOptions::default());
assert!(out.is_well_formed());
println!("{}", out.svg);
render is pure and deterministic. RenderOptions controls SVG-encoding choices
only (display scale, margin, provenance attributes, and glyph_mode — inline
PathOutline vs EmbeddedFont) — nothing that changes engraving.
Bundled Bravura data
Two generated artifacts come from the official OFL Bravura.otf via
epiphany-glyphs's tools/extract_bravura_outlines.py — the font is not
vendored, only the generated Rust is committed. The outlines moved to
epiphany-glyphs in T4-pre W2 (a canvas renderer needs them too, so they are
a shared asset rather than a renderer-private one); the font subset stayed here,
because an embeddable OTF is a renderer concern:
epiphany-glyphs/src/outlines_generated.rs— the inline glyph outlines (geometry-only, so byte-stable across fontTools versions). This crate reaches them throughepiphany_glyphs::outline, and re-exportsbundled_glyph_count/smufl_codepointunchanged;src/font_subset_generated.rs— a base64 OTF subset (just the pipeline's glyphs) forGlyphMode::EmbeddedFont. As a Modified Version, its primary font name is renamed off the Reserved Font Name "Bravura" per the OFL; a content BLAKE3 + decoded length are committed alongside as an integrity lock.
Bravura is © Steinberg Media Technologies GmbH under the SIL Open Font License 1.1
(../epiphany-glyphs/tools/OFL.txt, which travels with the redistributed
outlines); both artifacts are redistributed under the same license. To
regenerate both (the subset step also needs the blake3 package):
cd crates/epiphany-glyphs/tools
python3 -m venv .venv && . .venv/bin/activate && pip install fonttools blake3
python3 extract_bravura_outlines.py \
--font-out ../../epiphany-render-svg/src/font_subset_generated.rs \
> ../src/outlines_generated.rs