epiphany/crates/epiphany-render-svg
Levi Neuwirth beb61fca2f P13-I3: delete the hand-written notehead anchors; teach the extractor
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>
2026-07-09 14:53:47 -04:00
..
examples Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo 2026-07-02 21:55:26 -04:00
src Let the band model carry staff attribution instead of inferring it 2026-07-09 10:37:27 -04:00
tests Slurs: side from the stems, endpoints on the notes, apex clear of both 2026-07-09 13:58:56 -04:00
tools P13-I3: delete the hand-written notehead anchors; teach the extractor 2026-07-09 14:53:47 -04:00
Cargo.toml Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer) 2026-06-27 11:09:34 -04:00
DECISIONS.md Schema major 2 Phase E2: slur curves + cubic-bézier primitive 2026-07-08 15:33:00 -04:00
README.md Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer) 2026-06-27 11:09:34 -04:00

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) for GlyphMode::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