epiphany/crates/epiphany-render-svg
Levi Neuwirth 92aaccf7e2 Pushes 1+3: fix the MUST-level violations, wire the types-only machinery
Two audit pushes whose code edits interleave line-by-line in the same
files (reduce.rs, bundle.rs, the DECISIONS logs), committed together so
the tree at every commit builds. Gate: 784 workspace tests pass, clippy
-D warnings clean, fmt clean.

Push 1 — the true MUST violations, all fixed:
- bundle: zstd read support on both read paths, output bounded by the
  declared uncompressed_length, typed decompression errors, explicit
  CompressedManifest rejection (zstd 0.13 workspace dep; write path
  stays uncompressed per the Phase-3 deferral).
- ops: system-derived counter collision check — mint registry seeded
  from the base graph, canonical-order pre-walk, halt via the new
  PendingReason::HaltedBySystemCollision (discriminant 4, additive)
  with transaction-atomicity and causal-dependent closure; neither
  input set occupies a collided counter. canonical_pitch_bytes made
  pub in core for the MUSCSPCH preimage.
- ops: Transpose skips tombstoned targets per the catalog; missing
  targets still refuse the whole operation.
- ops: marker re-anchoring recorded as a RepairRecord in the
  triggering operation's effect; ResolveConflict meta-conflicts name
  both resolvers; base-free pitch-id freshness; reserved effect
  vocabulary annotated.
- core: decomposition pre-pass honors authored attachments
  (resolve_decomposition, spec-default precedence); inversion
  tolerance typed as a TempoIntegration-class Tolerance.
- CONFORMANCE.md: the determinism conformance statement required by
  Appendix D — all seven declarations.

Push 3 — wiring the types-only machinery:
- layout-ir/engrave: to_constrained emits real constraints (successive
  notehead no-collision chains, per-glyph region containment, soft
  user-break constraints); ConstraintStrength{Required, Preferred}
  with strength-by-rule; Preferred violations surface as warnings, not
  failures; StubSolver reworked honest-but-renderable. SVG goldens
  byte-identical; snapshot constraint counts regenerated (0->90/15).
- layout-ir: to_logical projects user system/page breaks as anchored
  EngravingOverrides with paired UserOverride-sourced decisions
  (OverrideKind::SystemBreak/PageBreak carry TimeAnchor, ratified in
  the spec alongside).
- layout-ir/ops/editor-core: edit-barrier bridge — decode mirrors for
  the whole barrier tree (reject-never-normalize, NFC revalidation,
  MAX_CONDITION_DEPTH = 64), golden-locked blob codec for the
  ExtensionDeclaration fields, a barrier gate in apply and
  apply_transaction backed by a Score oracle and real containment
  contexts, and apply_unsafe recording the crossed extensions in
  extensions_requiring_tombstone() for the next bundle write.
- ops: ResolveEquivocation meta-operation per the newly ratified
  catalog entry — payload discriminant 3 (appended), set-level
  earliest-resolve-governs promotion, ResolveConflict-mirrored
  meta-conflicts, permutation-invariance fuzz; the missing golden
  locks on the OperationKind/OperationPayload wire tables added.
- ops/editor-core: validation modes — ValidationMode + a non-canonical
  advisory layer (validate.rs), an authoring gate before minting, and
  reduction pinned as replay mode by construction (canonical bytes
  untouched).
- bundle: the operation index (opindex.rs) — provisional golden-locked
  payload, binary-search locate, staleness defined as full-ChunkRef
  set equality against operation_roots, and the reject-and-rebuild
  discipline (a defective index is never bundle corruption).
- ops: re-anchoring rule table completed — the four-key "nearest"
  ordering computed from base-free ledger indices; markers re-anchor
  to the nearest live event in the same staff instance (replacing the
  Push-1 region-start stand-in); cue-source cascade; graphic-gesture
  Events/Range/Free rows; comment and analytical-annotation orphaning.
  Zero appended discriminants.

Spec enablers ratified with Push 3: catalog §ResolveEquivocation
(0.3.0 -> 0.4.0) and anchored break overrides; 16 new Pass-12 rows
filed (C1-C4, K5-K7, I4-I6, D1, E1-E5). The data-model payload
expansion (SlurKind, beam geometry, voltas, instrument bodies,
metadata) is deliberately staged to the Binary Format companion — the
positional graph codec has no value-level versioning, so filling those
structs is a schema-major break that should land once, with J.

Also carries the pre-existing editor-track increment: the atomic
tuplet overwrite (CascadeDeleteTuplets prunes decomposition
attachments naming the cascaded tuplet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 17:10:50 -04:00
..
examples Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer) 2026-06-27 11:09:34 -04:00
src Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer) 2026-06-27 11:09:34 -04:00
tests Pushes 1+3: fix the MUST-level violations, wire the types-only machinery 2026-07-02 17:10:50 -04:00
tools Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer) 2026-06-27 11:09:34 -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 Pass-12 batch: mark the resolved I-series rows done 2026-06-27 12:53:36 -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