ResolvedLayoutIR carried a page/system tree and three flat primitive arrays with nothing joining them, so a canvas wanting to re-tessellate one damaged system had to infer ownership spatially — ambiguous exactly where it matters, at cross-system slurs and boundary-straddling strokes. The partition was never missing, though: casting-off computes it (system_of_slot, stroke_system, curve_system) and the fold into ResolvedLayoutIR dropped it. This publishes it instead of inferring it, so the answer is exact. ResolvedSystem gains PrimitiveIndices — u32 index lists into the layout's flat glyphs/strokes/curves — and ResolvedLayoutIR gains an unowned bucket of the same shape. No primitive is split, merged, reordered, or renumbered; the flat arrays are exactly what they were. The partition is total and disjoint: for each array, every system's list plus the unowned bucket covers 0..len exactly once, tested directly rather than assumed from construction. Unowned is a first-class bucket with a real producer. The stub solver resolves no per-system geometry — one default-rect system per region — so it publishes every primitive unowned rather than fabricating an attribution it never computed. Cross-system primitives need no special case: casting-off already splits a spanning stroke or curve into per-system segments carrying SYSTEM_CONTINUATION_SYNTHESIS provenance, and each segment is owned by the system it was split into, not by the source's. Ownership is excluded from the canonical encoding, for the reason vertical_band already is: it draws nothing, so two layouts differing only in it are the same rendered layout and hash alike. Encoding it would make the fingerprint more fragile than the rendering it fingerprints — a casting-off refactor that re-partitions without moving a pixel would become a byte-level break for something no renderer and no conformance claim can observe. Verified against main: all eight reference fixtures produce byte-identical canonical layouts, and all five GUI goldens are untouched. One derivation, structurally. The quality census consumed its own copy of the glyph-to-system rule, and so did vertical_raw's system_of_glyph closure; both now read the published glyph_system. That left CastLayout::system_of_slot with no consumer outside the casting pass, so it is removed from the published struct — a future consumer cannot grow a third copy of the rule, because the raw material is no longer there. The per-glyph answer travels; the derivation does not. Six mutations, each killed and coordinator-re-verified independently: dropping an index from a system's list, coercing unowned onto system 0, an off-by-one system index, publishing a constant attribution, attributing a continuation to its source's system, and encoding ownership into the canonical bytes. The fourth is the one that proves the single-derivation claim rather than asserting it — it kills three quality tests, two of which survived it before vertical_raw was migrated. Gate: 34 suites / 1341 tests / 0 failed, conformance 9/9 with golden-gate and 8/8 without, requirement labels 6/6 at 212/282/282, clippy 0, five goldens byte-identical. Co-Authored-By: Claude Opus 5 (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