The reviewer signed off on the inter-staff slice with one named residual risk: the 3+-staff cumulative shift cascade was documented and traced correct but had no fixture behind it -- valid_score_rich's "three staves" are three separate single-staff regions, so each lands in its own system and the cascade never runs. three_staff_close_content puts three staves in ONE region with deliberately asymmetric pressure: the upper pair collides hard (C1 against C7), the lower pair only gently. That asymmetry is what makes the fixture discriminating. Sizing each pair independently -- the plausible wrong implementation -- measures the lower pair against the middle staff's ORIGINAL position, hands the bottom staff only its own small correction, and drags it back up through the middle staff. Verified by mutation, not by assertion alone: with the cascade removed the bottom staff's shift collapses from 34.68 to 4.56 against the middle staff's 15.06, and both the shift ordering and the staff-line-gap assertions fail. two_staff_close_content still passes under that same mutation, which is precisely why the new fixture was needed. Writing the test also corrected a wrong mental model, now recorded in DECISIONS.md: a shift INCREMENT generally exceeds the lower pair's own raw correction, because the upper staff's descent has itself eaten into that pair's gap and must be undone. The first version of the test asserted the opposite and failed against a correct solve. The fixture additionally pins curve attribution against a three-band choice -- the slur must still find the bottom staff, not merely the nearer of two -- and carries its own render golden. No existing golden churns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| examples | ||
| src | ||
| tests | ||
| tools | ||
| 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
tools/extract_bravura_outlines.py — the font is not vendored, only the
generated Rust is committed:
src/outlines_generated.rs— the inline glyph outlines (geometry-only, so byte-stable across fontTools versions);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
(tools/OFL.txt); both artifacts are redistributed under the same license. To
regenerate both (the subset step also needs the blake3 package):
cd crates/epiphany-render-svg/tools
python3 -m venv .venv && . .venv/bin/activate && pip install fonttools blake3
python3 extract_bravura_outlines.py --font-out ../src/font_subset_generated.rs \
> ../src/outlines_generated.rs