BRAVURA_METRICS' NOTEHEAD_ANCHORS were hand-derived where every neighbouring number in the table is machine-extracted from the SHA-pinned font -- the same mistake as inferring band ownership downstream instead of reading it from the source that had it. And they were wrong under any reading: they named stemUpNW/stemDownSE, the corners a normal notehead's stems do not attach to and a pair Bravura's noteheadBlack does not define, with an x of 1180 that reads as 1.18 staff spaces written in thousandths rather than the table's 1/1024 units (1.18 sp = 1208). Nothing consumed them. Deleted rather than corrected. The font is not vendored, so the true values cannot be verified in-tree, and shipping data we cannot stand behind into a hash every conformance claim declares is worse than shipping none. Verified empirically before deciding, not reasoned about: changing the anchors breaks nothing in-tree -- 30/30 targets, zero golden churn, no pinned literal hash -- so GlyphCatalogIdentity moves once, now, while no claim declares the old one. extract_bravura_outlines.py gains --anchors, emitting them from the pinned bravura_metadata.json (anchors live in the SMuFL metadata, not the glyf bounds, which is why the outline extraction never covered them). SMuFL anchors are points, so they round to nearest -- unlike a bbox, which rounds outward so the metric box contains the ink. The metadata's SHA-256 is deliberately left UNPINNED and verify() now refuses an unpinned source, printing the digest to paste: the script cannot regenerate anchors until an operator with the font pins it in a reviewable commit, which is the discipline the other two sources already have. The test guarding the anchors proved nothing. anchors_participate_in_the_hash compared noteheadBlack (anchored) against noteheadWhole (not) and asserted they hash differently -- but their advance and bbox differ too, so it would have passed with the anchors ignored entirely. It now varies the anchors while holding every other field fixed (presence, one coordinate, one name), against synthetic metrics through a factored-out metrics_hash_of. Pass-13 Batch 2 CLOSED. All three candidates resolved; two grew when examined. 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