Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer)

A render mode that references each glyph by its SMuFL codepoint via `<text>`,
drawn from an `@font-face`-embedded Bravura subset, alongside the default inline
`<path>` outlines. The SVG stays self-contained (the font travels in it) and the
text is selectable, at a larger file size.

  - GlyphMode::EmbeddedFont: the SVG declares the font once via
    <defs><style>@font-face{...}</style></defs>, then emits one
    <text transform="translate(x y) scale(1 -1)" font-size="4" ...>&#xNNNN;</text>
    per glyph. Geometry is consistent with PathOutline by construction: same
    origin, a per-glyph counter-flip cancels the outer y-flip, and the SMuFL em is
    four staff spaces. A new `text_count` stat; unbundled glyphs still fall through
    to the visible bbox rect + diagnostic. The metadata comment declares which mode
    produced the SVG (and, on the empty canvas too, via a shared `glyph_note`).
    Path mode stays the byte-golden-locked, pixel-verified reference; the embedded
    mode is structurally tested (well-formed XML — also under `xmllint`; one
    @font-face; one <text>/codepoint per glyph; provenance preserved; determinism).

  - The subset is a GENERATED artifact, not a vendored binary: a deterministic
    base64 OTF emitted into src/font_subset_generated.rs by
    `tools/extract_bravura_outlines.py --font-out` (the same SHA-pinned 1.392 font
    the outlines come from). `recalcTimestamp=False` keeps the source font's fixed
    head.modified so the bytes are reproducible across runs, not just within one.

  - OFL compliance: the subset is a Modified Version, so its PRIMARY font name is
    renamed off the Reserved Font Name "Bravura" to "EpiphanyBravuraSubset" in BOTH
    naming structures an OTF carries — the SFNT `name` table AND the CFF (Name INDEX
    + top-dict FullName/FamilyName). The copyright/trademark/license records, which
    name Bravura as attribution, are kept; the renderer references the renamed
    family in @font-face and <text>. The generator reparses the saved bytes and
    fails if the reserved name leaks into a primary record, and validates the cmap
    covers every glyph.

  - Machine-locked payload: the generator emits the decoded length and a BLAKE3-256
    (the workspace's sole hash) of the font bytes; a render-svg test base64-decodes
    the payload (no new runtime dep) and asserts the length, the OTTO signature, the
    BLAKE3, and — parsing the SFNT name table and CFF Name INDEX — that neither
    primary name is the reserved name. Adds an epiphany-determinism dev-dep.

The demo example gains `--glyph-mode=path|embedded`; lib/README/DECISIONS document
the two modes, the subset's OFL rename, and the regeneration command (--font-out +
the blake3 dependency). PathOutline goldens are byte-unchanged; the outlines stay
byte-identical. Full gate green: build, fmt, clippy, 585 tests, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-06-27 11:09:34 -04:00
parent 41cd8bf58b
commit 6293734fa0
14 changed files with 603 additions and 104 deletions

1
Cargo.lock generated
View File

@ -125,6 +125,7 @@ name = "epiphany-render-svg"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"epiphany-core", "epiphany-core",
"epiphany-determinism",
"epiphany-engrave", "epiphany-engrave",
"epiphany-layout-ir", "epiphany-layout-ir",
"epiphany-testkit", "epiphany-testkit",

View File

@ -5,7 +5,7 @@ edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "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 as a genuine Bravura SMuFL outline <path>. A renderer, not an engraver — it makes SVG-encoding choices only, never engraving-semantic ones." description = "Agent I's SVG renderer behind the Epiphany RenderIR interface (spec Chapter 7): turns a ResolvedLayoutIR into well-formed SVG 1.1 using genuine Bravura SMuFL path outlines or an embedded-font text mode. A renderer, not an engraver — it makes SVG-encoding choices only, never engraving-semantic ones."
[dependencies] [dependencies]
# The renderer consumes the Chapter 7 ResolvedLayoutIR / RenderIR and the glyph # The renderer consumes the Chapter 7 ResolvedLayoutIR / RenderIR and the glyph
@ -21,3 +21,5 @@ epiphany-engrave.workspace = true
# their input from. # their input from.
epiphany-testkit.workspace = true epiphany-testkit.workspace = true
epiphany-core.workspace = true epiphany-core.workspace = true
# BLAKE3 (the workspace's sole hash) for the embedded-font content-integrity test.
epiphany-determinism.workspace = true

View File

@ -1,23 +1,21 @@
# epiphany-render-svg — decisions and Pass 12 candidates # epiphany-render-svg — decisions and Pass 12 candidates
This file records (a) the Phase-2 QUICKSTART decisions Agent I made for the This file records (a) the QUICKSTART decisions Agent I made for the renderer,
renderer, and (b) ambiguities batched as **Pass 12 candidates** and (b) ambiguities batched as **Pass 12 candidates** (`spec/PASS12_BATCH.md`)
(`spec/PASS12_BATCH.md`) rather than improvised in code. rather than improvised in code.
## Scope and phase status ## Scope and status
`epiphany-render-svg` is one renderer behind the Chapter 7 `RenderIR` interface: `epiphany-render-svg` is the SVG renderer behind the Chapter 7 `RenderIR`
it turns a `ResolvedLayoutIR` into well-formed **SVG 1.1**, drawing each glyph as interface: it turns a `ResolvedLayoutIR` into well-formed **SVG 1.1**, drawing
a genuine Bravura SMuFL outline `<path>`. Per the QUICKSTART development pattern glyphs from genuine Bravura SMuFL data either as inline outline `<path>`s
it is built and **golden-locked against the stub solver's output first**, before (`GlyphMode::PathOutline`) or as `<text>` set in an embedded subset font
the real engraving solver and the score→real-notation engraving pass land. The (`GlyphMode::EmbeddedFont`). Per the QUICKSTART development pattern it was
stub returns the constrained IR's geometry verbatim — a structural projection, golden-locked against the stub solver's output first, then against the real
not yet real notation (each layout object becomes one arbitrary glyph in a row) — `epiphany-engrave` solver once the score→real-notation pass and re-spacing
so this phase proves the renderer is *correct and faithful* (genuine outlines, landed. The renderer consumes any solver's `ResolvedLayoutIR`; it proves
provenance preserved, output XML-valid and deterministic), independent of renderer faithfulness — resolved geometry preserved, provenance traced,
engraving quality. The renderer already consumes any solver's `ResolvedLayoutIR`, output XML-valid and deterministic — independent of engraving quality.
so when the real `epiphany-engrave` solver lands, the visible result improves with
no renderer change (the demo binary's `--solver=stub|real` flag exercises both).
## The non-overreach rule (Chapter 7 / QUICKSTART, Agent I) ## The non-overreach rule (Chapter 7 / QUICKSTART, Agent I)
@ -46,13 +44,25 @@ glyph and one provenance trace per drawn element.
### Local decisions ### Local decisions
- **Glyph rendering — inline genuine Bravura outline `<path>`s - **Glyph rendering — two self-contained modes; inline outlines
(`GlyphMode::PathOutline`), the default and only mode this phase.** The (`GlyphMode::PathOutline`) is the default and the verified reference.** Path
QUICKSTART's recommendation: path outlines make the SVG self-contained (it outlines make the SVG self-contained (it renders in any browser, image tool, or
renders in any browser, image tool, or print pipeline with no font installed), print pipeline with no font installed) and are byte-golden-locked, at the cost of
at the cost of file size. An embedded-`@font-face` mode is a documented future file size — the QUICKSTART's recommendation. `GlyphMode::EmbeddedFont` instead
option, intentionally **not stubbed** so the interface does not lie about a references each glyph by SMuFL codepoint with a `<text>` element drawn from an
capability that is absent. `@font-face`-embedded Bravura *subset* (only the ~33 named glyphs), so the SVG is
still self-contained (the font travels in it) and the text is selectable, at a
larger file size. The two modes anchor glyphs at the same origin (em = 4 staff
spaces), so placement is consistent by construction; the embedded mode is
structurally tested rather than byte-golden-locked, and exact rasterisation is
the consumer's font renderer's, so path mode remains the pixel-verified one.
- **Embedded-font subset — generated, not a vendored binary.** The subset is a
deterministic base64 OTF emitted into `src/font_subset_generated.rs` by
`tools/extract_bravura_outlines.py --font-out`, keeping the "only generated
artifacts committed" rule (no font binary is vendored). It retains the font's
OFL copyright/license name records (belt-and-suspenders with `tools/OFL.txt`).
Caveat: unlike the geometry-only outlines, the binary subset's exact bytes
depend on the fontTools version, which the generated header records.
- **Outline source — the official OFL `Bravura.otf`, extracted reproducibly.** - **Outline source — the official OFL `Bravura.otf`, extracted reproducibly.**
`tools/extract_bravura_outlines.py` fetches the font + SMuFL `glyphnames.json` `tools/extract_bravura_outlines.py` fetches the font + SMuFL `glyphnames.json`
and emits `src/outlines_generated.rs`. The font is **not vendored**; only the and emits `src/outlines_generated.rs`. The font is **not vendored**; only the
@ -82,12 +92,12 @@ glyph and one provenance trace per drawn element.
See `spec/PASS12_BATCH.md` (rows P12-I1, P12-I2, P12-I3). Most relevant here: See `spec/PASS12_BATCH.md` (rows P12-I1, P12-I2, P12-I3). Most relevant here:
- **P12-I1** — the constrained IR is a structural placeholder, so the rendered - **P12-I1 (resolved by I-1/I-3)** — the original stub-only renderer output was
stub output is *not yet recognizable notation*. The QUICKSTART's human-review a structural placeholder, so the human-review visual-acceptance gate ("the SVG
visual-acceptance gate ("the SVG visually parses as standard music notation") visually parses as standard music notation") was deferred until real engraving
is therefore a **next-phase** gate, met once real engraving lands; this phase's landed. The real notation pass and real-Engraver goldens now close that gate;
gate is renderer correctness/faithfulness. Recorded so the visual gate is not the stub path remains locked as an interface/reference mode, not the visual
mistaken for already-met. deliverable.
- **P12-I2** — stable layout-object id derivation (`MUSCLOID`, Pass-11 item 2.6, - **P12-I2** — stable layout-object id derivation (`MUSCLOID`, Pass-11 item 2.6,
deferred to Agent I) is still unwired: the determinism crate exposes no deferred to Agent I) is still unwired: the determinism crate exposes no
`MUSCLOID` tag and is frozen. The renderer traces provenance by the existing `MUSCLOID` tag and is frozen. The renderer traces provenance by the existing

View File

@ -2,19 +2,22 @@
Agent I's **SVG renderer** behind the Epiphany `RenderIR` interface (spec Agent I's **SVG renderer** behind the Epiphany `RenderIR` interface (spec
Chapter 7): turns a `ResolvedLayoutIR` into well-formed **SVG 1.1**, drawing each Chapter 7): turns a `ResolvedLayoutIR` into well-formed **SVG 1.1**, drawing each
glyph as a **genuine Bravura SMuFL outline** `<path>`. It is the visible end of glyph from **genuine Bravura SMuFL** data — inline outline `<path>`s by default
the v0 `Score → layout IR` pipeline. (`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: renderer against the stub solver ## Status
This phase builds and golden-locks the renderer against the **stub solver's** The `Score → layout IR → SVG` pipeline renders **recognizable notation** — clefs,
output (the QUICKSTART development pattern), before the real engraving solver and noteheads at clef-relative staff positions, accidentals, key/time signatures,
the score→real-notation engraving pass land. The stub returns the IR geometry rests, barlines, and the staff lines and stems that connect them. Output is
verbatim — a structural projection (each object becomes one arbitrary glyph in a golden-locked against **both** the interface-only stub solver and Agent I's real
row), not yet recognizable notation — so what is proven here is **renderer `epiphany-engrave` solver (whose horizontal spacing pass re-spaces the glyphs),
correctness and faithfulness**: real outlines, provenance preserved, output and the layout round-trip (criterion 6) runs through both. What the renderer
XML-valid and deterministic. The renderer consumes any solver's output, so the itself guarantees, independent of engraving quality: real Bravura glyphs,
picture improves with no renderer change once `epiphany-engrave` lands. provenance preserved to the score graph, output XML-valid and deterministic. The
renderer consumes any solver's `ResolvedLayoutIR`.
## Demo ## Demo
@ -26,6 +29,10 @@ cargo run -p epiphany-render-svg --example render_fixture -- \
# Drive Agent I's engrave solver instead, to bisect renderer-vs-solver: # Drive Agent I's engrave solver instead, to bisect renderer-vs-solver:
cargo run -p epiphany-render-svg --example render_fixture -- \ cargo run -p epiphany-render-svg --example render_fixture -- \
ten_measure_single_staff --solver=real > out.svg 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 Fixtures: `ten_measure_single_staff`, `valid_score_rich`, `valid_score`. Stats and
@ -42,19 +49,29 @@ println!("{}", out.svg);
``` ```
`render` is pure and deterministic. `RenderOptions` controls SVG-encoding choices `render` is pure and deterministic. `RenderOptions` controls SVG-encoding choices
only (display scale, margin, provenance attributes) — nothing that changes only (display scale, margin, provenance attributes, and `glyph_mode` — inline
engraving. `PathOutline` vs `EmbeddedFont`) — nothing that changes engraving.
## Bundled outlines ## Bundled Bravura data
The glyph outlines in `src/outlines_generated.rs` are extracted from the official Two generated artifacts come from the official OFL `Bravura.otf` via
OFL `Bravura.otf` by `tools/extract_bravura_outlines.py`. The font is not `tools/extract_bravura_outlines.py` — the font is **not vendored**, only the
vendored; only the generated Rust is committed. Bravura is © Steinberg Media generated Rust is committed:
Technologies GmbH under the SIL Open Font License 1.1 (`tools/OFL.txt`); the
extracted outlines are redistributed under the same license. To regenerate: - `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):
```sh ```sh
cd crates/epiphany-render-svg/tools cd crates/epiphany-render-svg/tools
python3 -m venv .venv && . .venv/bin/activate && pip install fonttools python3 -m venv .venv && . .venv/bin/activate && pip install fonttools blake3
python3 extract_bravura_outlines.py > ../src/outlines_generated.rs python3 extract_bravura_outlines.py --font-out ../src/font_subset_generated.rs \
> ../src/outlines_generated.rs
``` ```

View File

@ -10,20 +10,22 @@
//! //!
//! ```text //! ```text
//! cargo run -p epiphany-render-svg --example render_fixture -- \ //! cargo run -p epiphany-render-svg --example render_fixture -- \
//! ten_measure_single_staff [--solver=stub|real] [--seed=N] [--no-provenance] \ //! ten_measure_single_staff [--solver=stub|real] [--seed=N] \
//! > out.svg //! [--glyph-mode=path|embedded] [--no-provenance] > out.svg
//! ``` //! ```
//! //!
//! The `--solver` flag selects the interface-only stub (`stub`, the default this //! The `--solver` flag selects the interface-only stub (`stub`, the default this
//! phase) or Agent I's engrave solver (`real`); keeping the renderer working //! phase) or Agent I's engrave solver (`real`); keeping the renderer working
//! against both is how a renderer-vs-solver bug is bisected with a one-flag //! against both is how a renderer-vs-solver bug is bisected with a one-flag
//! change (QUICKSTART, Agent I, "Development pattern"). //! change (QUICKSTART, Agent I, "Development pattern"). The `--glyph-mode` flag
//! selects inline outline `<path>`s (`path`, default) or the embedded-font
//! `<text>` mode (`embedded`).
use std::process::ExitCode; use std::process::ExitCode;
use epiphany_engrave::Engraver; use epiphany_engrave::Engraver;
use epiphany_layout_ir::{to_constrained, to_logical, ConstraintSolver, SolverConfig, StubSolver}; use epiphany_layout_ir::{to_constrained, to_logical, ConstraintSolver, SolverConfig, StubSolver};
use epiphany_render_svg::{render, RenderOptions}; use epiphany_render_svg::{render, GlyphMode, RenderOptions};
const FIXTURES: &str = "ten_measure_single_staff, valid_score_rich, valid_score"; const FIXTURES: &str = "ten_measure_single_staff, valid_score_rich, valid_score";
const SOLVERS: &str = "stub, real"; const SOLVERS: &str = "stub, real";
@ -33,6 +35,7 @@ fn main() -> ExitCode {
let mut solver = String::from("stub"); let mut solver = String::from("stub");
let mut seed: u64 = 0x000A_11CE; let mut seed: u64 = 0x000A_11CE;
let mut emit_provenance = true; let mut emit_provenance = true;
let mut glyph_mode = GlyphMode::PathOutline;
for arg in std::env::args().skip(1) { for arg in std::env::args().skip(1) {
if let Some(v) = arg.strip_prefix("--solver=") { if let Some(v) = arg.strip_prefix("--solver=") {
@ -42,6 +45,16 @@ fn main() -> ExitCode {
Ok(n) => seed = n, Ok(n) => seed = n,
Err(_) => return fail(&format!("invalid --seed value: {v}")), Err(_) => return fail(&format!("invalid --seed value: {v}")),
} }
} else if let Some(v) = arg.strip_prefix("--glyph-mode=") {
glyph_mode = match v {
"path" => GlyphMode::PathOutline,
"embedded" => GlyphMode::EmbeddedFont,
other => {
return fail(&format!(
"unknown --glyph-mode {other:?}; known: path, embedded"
))
}
};
} else if arg == "--no-provenance" { } else if arg == "--no-provenance" {
emit_provenance = false; emit_provenance = false;
} else if arg == "--help" || arg == "-h" { } else if arg == "--help" || arg == "-h" {
@ -80,6 +93,7 @@ fn main() -> ExitCode {
&report.layout, &report.layout,
&RenderOptions { &RenderOptions {
emit_provenance, emit_provenance,
glyph_mode,
..RenderOptions::default() ..RenderOptions::default()
}, },
); );
@ -89,9 +103,10 @@ fn main() -> ExitCode {
report.status report.status
); );
eprintln!( eprintln!(
"glyphs={} paths={} fallback_rects={} provenance={} layers={} hard_constraints={} well_formed={}", "glyphs={} paths={} texts={} fallback_rects={} provenance={} layers={} hard_constraints={} well_formed={}",
out.stats.glyph_count, out.stats.glyph_count,
out.stats.path_count, out.stats.path_count,
out.stats.text_count,
out.stats.fallback_rect_count, out.stats.fallback_rect_count,
out.stats.provenance_count, out.stats.provenance_count,
out.stats.layer_count, out.stats.layer_count,
@ -108,9 +123,11 @@ fn main() -> ExitCode {
fn usage() { fn usage() {
eprintln!( eprintln!(
"usage: render_fixture <fixture> [--solver=stub|real] [--seed=N] [--no-provenance]\n\ "usage: render_fixture <fixture> [--solver=stub|real] [--seed=N] \
[--glyph-mode=path|embedded] [--no-provenance]\n\
fixtures: {FIXTURES}\n\ fixtures: {FIXTURES}\n\
solvers: {SOLVERS} (default: stub)" solvers: {SOLVERS} (default: stub)\n\
glyph-mode: path, embedded (default: path)"
); );
} }

File diff suppressed because one or more lines are too long

View File

@ -4,22 +4,20 @@
//! Agent I's **SVG renderer** behind the Epiphany `RenderIR` interface (spec //! Agent I's **SVG renderer** behind the Epiphany `RenderIR` interface (spec
//! **Chapter 7** §"RenderIR"): it turns a //! **Chapter 7** §"RenderIR"): it turns a
//! [`ResolvedLayoutIR`](epiphany_layout_ir::ResolvedLayoutIR) into well-formed //! [`ResolvedLayoutIR`](epiphany_layout_ir::ResolvedLayoutIR) into well-formed
//! **SVG 1.1**, drawing each glyph as a **genuine Bravura SMuFL outline** //! **SVG 1.1**, drawing each glyph from **genuine Bravura SMuFL** data: inline
//! `<path>`. It is the visible end of the v0 `Score → layout IR` pipeline: from a //! outline `<path>`s by default, or `<text>` set in an `@font-face`-embedded
//! subset. It is the visible end of the v0 `Score → layout IR` pipeline: from a
//! resolved layout, produce an image a musician would recognise. //! resolved layout, produce an image a musician would recognise.
//! //!
//! ## Scope of this phase (renderer-against-stub) //! ## Scope and status
//! //!
//! Per the QUICKSTART development pattern (`spec/PHASE2_QUICKSTART.md`, Agent I), //! Per the QUICKSTART development pattern (`spec/PHASE2_QUICKSTART.md`, Agent I),
//! the renderer is built and golden-locked against the **stub solver's** output //! the renderer was golden-locked against the **stub solver's** output first, then
//! first, before the real engraving solver lands. The stub returns the //! against the real [`epiphany_engrave`](../epiphany_engrave/index.html) solver
//! constrained IR's geometry verbatim — a structural projection, not yet real //! once real notation and re-spacing landed. The renderer consumes any solver's
//! notation — so this phase proves the renderer is *correct and faithful* (every //! [`ResolvedLayoutIR`](epiphany_layout_ir::ResolvedLayoutIR): it preserves the
//! glyph drawn from its real Bravura outline, provenance preserved, output //! resolved geometry, provenance traces, XML validity, deterministic output, and
//! XML-valid and deterministic), independently of engraving quality. The real //! glyph-mode choice without making engraving-semantic decisions.
//! [`epiphany_engrave`](../epiphany_engrave/index.html) solver and the
//! score→real-notation engraving pass are the next phase; the renderer already
//! consumes any solver's `ResolvedLayoutIR`.
//! //!
//! ## What it draws, and the non-overreach rule //! ## What it draws, and the non-overreach rule
//! //!
@ -31,12 +29,20 @@
//! //!
//! ## Font availability //! ## Font availability
//! //!
//! The default and only mode this phase is [`GlyphMode::PathOutline`] — inline //! Two self-contained modes ([`GlyphMode`]):
//! outlines, so the SVG is self-contained and needs no font installed //!
//! (QUICKSTART, Agent I, recommendation). An embedded-`@font-face` mode is a //! * [`GlyphMode::PathOutline`] (default) inlines genuine Bravura outlines as
//! future option; it is intentionally not implemented yet rather than stubbed //! `<path>`s — no font dependency, byte-golden-locked, the pixel-verified
//! dishonestly. //! reference (QUICKSTART, Agent I, recommendation).
//! * [`GlyphMode::EmbeddedFont`] references glyphs by SMuFL codepoint via a
//! `<text>` element and an `@font-face`-embedded Bravura *subset* (the same
//! SHA-pinned font the outlines come from, base64 in `font_subset_generated`,
//! regenerated by `tools/extract_bravura_outlines.py --font-out`). Still
//! self-contained — the font travels in the SVG — and text-selectable, at the
//! cost of a larger file; glyph placement is consistent with the path mode by
//! construction, while exact rasterisation is the consumer's font renderer's.
mod font_subset_generated;
mod outline; mod outline;
mod outlines_generated; mod outlines_generated;
mod svg; mod svg;

View File

@ -16,8 +16,8 @@ pub fn bundled_glyph_count() -> usize {
BRAVURA_OUTLINES.len() BRAVURA_OUTLINES.len()
} }
/// The SMuFL codepoint of a bundled glyph name, if bundled. Useful for a future /// The SMuFL codepoint of a bundled glyph name, if bundled. Used by the
/// embedded-font rendering mode (which references glyphs by codepoint) and for /// embedded-font render mode (which references glyphs by codepoint) and for
/// debugging glyph identity. /// debugging glyph identity.
pub fn smufl_codepoint(name: &str) -> Option<u32> { pub fn smufl_codepoint(name: &str) -> Option<u32> {
outline(name).map(|o| o.codepoint) outline(name).map(|o| o.codepoint)
@ -74,6 +74,151 @@ mod tests {
} }
} }
/// A minimal RFC-4648 base64 decoder for the integrity test (the crate has no
/// base64 dependency); skips non-alphabet bytes, stops at padding.
fn decode_base64(s: &str) -> Vec<u8> {
fn val(c: u8) -> Option<u8> {
match c {
b'A'..=b'Z' => Some(c - b'A'),
b'a'..=b'z' => Some(c - b'a' + 26),
b'0'..=b'9' => Some(c - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
}
let mut out = Vec::new();
let (mut buf, mut bits) = (0u32, 0u32);
for &c in s.as_bytes() {
if c == b'=' {
break;
}
let Some(v) = val(c) else { continue };
buf = (buf << 6) | u32::from(v);
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((buf >> bits) as u8);
}
}
out
}
#[test]
fn embedded_font_payload_is_a_locked_valid_otf() {
use crate::font_subset_generated::{
BRAVURA_SUBSET_BLAKE3, BRAVURA_SUBSET_LEN, BRAVURA_SUBSET_OTF_BASE64,
};
let bytes = decode_base64(BRAVURA_SUBSET_OTF_BASE64);
// Length + signature: a truncated or non-OTF payload fails here, not later
// in a consumer's font engine.
assert_eq!(
bytes.len(),
BRAVURA_SUBSET_LEN,
"embedded font length changed; regenerate font_subset_generated.rs"
);
assert_eq!(
&bytes[..4],
b"OTTO",
"embedded font is not a CFF OpenType (OTTO) font"
);
// Content lock: any byte-level corruption flips the BLAKE3 (the workspace's
// sole hash), even one that preserves the length.
let digest = epiphany_determinism::blake3_256(&bytes);
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
hex, BRAVURA_SUBSET_BLAKE3,
"embedded font content hash changed; regenerate font_subset_generated.rs"
);
}
/// Reads an sfnt table slice by 4-byte tag from a decoded OTF.
fn sfnt_table<'a>(font: &'a [u8], tag: &[u8; 4]) -> Option<&'a [u8]> {
let num_tables = u16::from_be_bytes(font.get(4..6)?.try_into().ok()?) as usize;
for i in 0..num_tables {
let rec = 12 + i * 16; // after the 12-byte sfnt header
if font.get(rec..rec + 4)? == tag {
let off =
u32::from_be_bytes(font.get(rec + 8..rec + 12)?.try_into().ok()?) as usize;
let len =
u32::from_be_bytes(font.get(rec + 12..rec + 16)?.try_into().ok()?) as usize;
return font.get(off..off + len);
}
}
None
}
/// The SFNT `name` table's family record (nameID 1), decoded from the first
/// record carrying it (UTF-16BE for Windows/Unicode platforms, Latin-1 for Mac).
fn sfnt_family_name(name_table: &[u8]) -> Option<String> {
let count = u16::from_be_bytes(name_table.get(2..4)?.try_into().ok()?) as usize;
let storage = u16::from_be_bytes(name_table.get(4..6)?.try_into().ok()?) as usize;
for i in 0..count {
let r = 6 + i * 12;
let platform = u16::from_be_bytes(name_table.get(r..r + 2)?.try_into().ok()?);
let name_id = u16::from_be_bytes(name_table.get(r + 6..r + 8)?.try_into().ok()?);
if name_id != 1 {
continue;
}
let len = u16::from_be_bytes(name_table.get(r + 8..r + 10)?.try_into().ok()?) as usize;
let off = u16::from_be_bytes(name_table.get(r + 10..r + 12)?.try_into().ok()?) as usize;
let raw = name_table.get(storage + off..storage + off + len)?;
return Some(if platform == 1 {
raw.iter().map(|&b| b as char).collect()
} else {
raw.chunks_exact(2)
.filter_map(|p| char::from_u32(u32::from(u16::from_be_bytes([p[0], p[1]]))))
.collect()
});
}
None
}
/// The CFF table's font name — the first entry of its Name INDEX (whose offsets
/// are 1-based from the byte preceding the object data).
fn cff_font_name(cff: &[u8]) -> Option<String> {
let hdr_size = *cff.get(2)? as usize; // CFF header: major, minor, hdrSize, offSize
let count = u16::from_be_bytes(cff.get(hdr_size..hdr_size + 2)?.try_into().ok()?) as usize;
if count == 0 {
return None;
}
let off_size = usize::from(*cff.get(hdr_size + 2)?);
let off_base = hdr_size + 3;
let read = |i: usize| -> Option<usize> {
let s = off_base + i * off_size;
let mut v = 0usize;
for k in 0..off_size {
v = (v << 8) | usize::from(*cff.get(s + k)?);
}
Some(v)
};
let data_base = off_base + (count + 1) * off_size - 1;
let s = cff.get(data_base + read(0)?..data_base + read(1)?)?;
Some(String::from_utf8_lossy(s).into_owned())
}
#[test]
fn embedded_font_presents_no_reserved_primary_name() {
use crate::font_subset_generated::BRAVURA_SUBSET_OTF_BASE64;
let bytes = decode_base64(BRAVURA_SUBSET_OTF_BASE64);
// An OTF carries two naming structures; the OFL restricts the *primary name*
// of a Modified Version, so both must be the non-reserved subset family, never
// the bare Reserved Font Name "Bravura". (Attribution records may, and do,
// still name Bravura — those are not the primary name.)
let name_tbl = sfnt_table(&bytes, b"name").expect("name table present");
assert_eq!(
sfnt_family_name(name_tbl).as_deref(),
Some("EpiphanyBravuraSubset"),
"SFNT family name (nameID 1) must be the non-reserved subset family"
);
let cff = sfnt_table(&bytes, b"CFF ").expect("CFF table present");
assert_eq!(
cff_font_name(cff).as_deref(),
Some("EpiphanyBravuraSubset"),
"CFF Name INDEX must be the non-reserved subset family"
);
}
#[test] #[test]
fn outlines_have_finite_bounds_and_nonempty_paths() { fn outlines_have_finite_bounds_and_nonempty_paths() {
for o in BRAVURA_OUTLINES { for o in BRAVURA_OUTLINES {

View File

@ -41,7 +41,10 @@ use std::fmt::Write as _;
use epiphany_layout_ir::{BoundingBox, Provenance, ResolvedGlyph, ResolvedLayoutIR, Transform2D}; use epiphany_layout_ir::{BoundingBox, Provenance, ResolvedGlyph, ResolvedLayoutIR, Transform2D};
use crate::outline::outline; use crate::font_subset_generated::{
BRAVURA_SUBSET_FAMILY, BRAVURA_SUBSET_MIME, BRAVURA_SUBSET_OTF_BASE64,
};
use crate::outline::{outline, smufl_codepoint};
use crate::xml::{check_well_formed, escape_attr}; use crate::xml::{check_well_formed, escape_attr};
/// How glyphs are drawn. /// How glyphs are drawn.
@ -50,9 +53,18 @@ pub enum GlyphMode {
/// Inline genuine Bravura outline `<path>`s (default). Self-contained: the /// Inline genuine Bravura outline `<path>`s (default). Self-contained: the
/// SVG renders in any viewer with no font dependency (QUICKSTART, Agent I: /// SVG renders in any viewer with no font dependency (QUICKSTART, Agent I:
/// "inline path outlines for golden fixtures and the demonstrable /// "inline path outlines for golden fixtures and the demonstrable
/// deliverable"). /// deliverable"). This is the byte-golden-locked, pixel-verified reference
/// mode.
#[default] #[default]
PathOutline, PathOutline,
/// Reference each glyph by its SMuFL codepoint with a `<text>` element, drawn
/// from an `@font-face`-embedded subset of Bravura (the same SHA-pinned font
/// the outlines come from, base64 in `font_subset_generated`). The result is
/// self-contained — the font travels in the SVG — and text-selectable, at the
/// cost of a larger file. Glyph placement is consistent with
/// [`GlyphMode::PathOutline`] by construction (same origin, em = 4 staff
/// spaces); exact glyph rasterisation is then the consumer's font renderer's.
EmbeddedFont,
} }
/// Renderer configuration. SVG-encoding choices only — nothing here changes /// Renderer configuration. SVG-encoding choices only — nothing here changes
@ -157,8 +169,12 @@ pub struct Diagnostic {
pub struct RenderStats { pub struct RenderStats {
/// Glyphs in the resolved layout (the renderer's input objects). /// Glyphs in the resolved layout (the renderer's input objects).
pub glyph_count: usize, pub glyph_count: usize,
/// `<path>` elements emitted (glyphs drawn from a bundled outline). /// `<path>` elements emitted (glyphs drawn from a bundled outline, the
/// default [`GlyphMode::PathOutline`]).
pub path_count: usize, pub path_count: usize,
/// `<text>` elements emitted (glyphs set in the embedded font, the
/// [`GlyphMode::EmbeddedFont`] mode). Zero in the default path mode.
pub text_count: usize,
/// Fallback `<rect>` elements emitted (glyphs with no bundled outline). /// Fallback `<rect>` elements emitted (glyphs with no bundled outline).
pub fallback_rect_count: usize, pub fallback_rect_count: usize,
/// `<line>` elements emitted (one per resolved stroke: staff line, stem, …). /// `<line>` elements emitted (one per resolved stroke: staff line, stem, …).
@ -209,13 +225,14 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
Some(b) => b, Some(b) => b,
// Empty layout: a minimal, valid, honest empty canvas. // Empty layout: a minimal, valid, honest empty canvas.
None => { None => {
let svg = empty_svg(options.emit_provenance); let svg = empty_svg(options.glyph_mode, options.emit_provenance);
let well_formed = check_well_formed(&svg).is_ok(); let well_formed = check_well_formed(&svg).is_ok();
debug_assert!(well_formed); debug_assert!(well_formed);
return RenderOutput { return RenderOutput {
stats: RenderStats { stats: RenderStats {
glyph_count: 0, glyph_count: 0,
path_count: 0, path_count: 0,
text_count: 0,
fallback_rect_count: 0, fallback_rect_count: 0,
stroke_count: 0, stroke_count: 0,
provenance_count: 0, provenance_count: 0,
@ -248,6 +265,7 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
.collect(); .collect();
let mut path_count = 0; let mut path_count = 0;
let mut text_count = 0;
let mut fallback_rect_count = 0; let mut fallback_rect_count = 0;
let mut stroke_count = 0; let mut stroke_count = 0;
let mut provenance_count = 0; let mut provenance_count = 0;
@ -263,15 +281,26 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
num(height), num(height),
); );
// Declared metadata wrapper (a comment — honest about what this is, including // Declared metadata wrapper (a comment — honest about what this is, including
// whether provenance traces are present: suppressing them is an explicit // how glyphs are drawn and whether provenance traces are present: suppressing
// display-only choice the output announces rather than dropping silently). // them is an explicit display-only choice the output announces, not drops).
let _ = writeln!( let _ = writeln!(
s, s,
" <!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; \ " <!-- epiphany-render-svg: {}; geometry is the resolved layout \
geometry is the resolved layout verbatim, no engraving performed here; \ verbatim, no engraving performed here; {} -->",
{} -->", glyph_note(options.glyph_mode),
provenance_note(options.emit_provenance), provenance_note(options.emit_provenance),
); );
// In embedded-font mode, declare the Bravura subset once via `@font-face`; the
// `<text>` glyphs below reference it by its (non-reserved) family name (see the
// font subset's own header for its provenance and the OFL terms it carries).
if options.glyph_mode == GlyphMode::EmbeddedFont {
let _ = writeln!(
s,
" <defs><style>@font-face {{ font-family: \"{}\"; \
src: url(\"data:{};base64,{}\") format(\"opentype\"); }}</style></defs>",
BRAVURA_SUBSET_FAMILY, BRAVURA_SUBSET_MIME, BRAVURA_SUBSET_OTF_BASE64,
);
}
// The single y-flip wrapper: staff-space/y-up world -> SVG y-down. // The single y-flip wrapper: staff-space/y-up world -> SVG y-down.
let _ = writeln!( let _ = writeln!(
s, s,
@ -338,21 +367,42 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
} else { } else {
String::new() String::new()
}; };
match outline(name) { // The drawn element depends on the mode: an inline outline `<path>`
Some(o) => { // (the self-contained default) or a `<text>` referencing the embedded
// Bravura by SMuFL codepoint. Both anchor at the same `(x, y)` origin,
// so the two modes are geometrically consistent. `None` (no bundled
// outline / no codepoint) falls through to the visible bbox rect.
let element = match options.glyph_mode {
GlyphMode::PathOutline => outline(name).map(|o| {
path_count += 1; path_count += 1;
let _ = writeln!( format!(
s, "<path d=\"{}\" transform=\"{}\" fill=\"{}\"{}{}/>",
" <path d=\"{}\" transform=\"{}\" fill=\"{}\"{}{}/>",
o.path, placement, fill, opacity, prov, o.path, placement, fill, opacity, prov,
); )
}),
GlyphMode::EmbeddedFont => smufl_codepoint(name).map(|cp| {
text_count += 1;
// The font glyph is drawn upright by a per-glyph counter-flip
// (`scale(1 -1)`, innermost) cancelling the outer y-flip; the
// em is four staff spaces (SMuFL), so `font-size="4"`.
format!(
"<text transform=\"{placement} scale(1 -1)\" \
font-family=\"{BRAVURA_SUBSET_FAMILY}\" font-size=\"4\" \
fill=\"{fill}\"{opacity}{prov}>&#x{cp:X};</text>",
)
}),
};
match element {
Some(el) => {
let _ = writeln!(s, " {el}");
} }
None => { None => {
// No outline: surface it and draw the IR bounding box so the // Unrenderable in this mode: surface it and draw the IR
// missing glyph is visible, not silently absent. // bounding box so the missing glyph is visible, not silent.
fallback_rect_count += 1; fallback_rect_count += 1;
diagnostics.push(Diagnostic { diagnostics.push(Diagnostic {
message: "no bundled Bravura outline; drew bounding-box fallback" message: "no bundled Bravura glyph for this name; drew \
bounding-box fallback"
.to_owned(), .to_owned(),
glyph: Some(name.to_owned()), glyph: Some(name.to_owned()),
}); });
@ -383,6 +433,7 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
stats: RenderStats { stats: RenderStats {
glyph_count: resolved.glyphs.len(), glyph_count: resolved.glyphs.len(),
path_count, path_count,
text_count,
fallback_rect_count, fallback_rect_count,
stroke_count, stroke_count,
provenance_count, provenance_count,
@ -572,6 +623,16 @@ fn colour(rgba: u32) -> (String, String) {
(fill, opacity) (fill, opacity)
} }
/// The glyph-mode clause of the metadata comment: which drawing strategy produced
/// the SVG. Shared by the main render and [`empty_svg`] so the declared mode
/// boundary is the same on the empty path.
fn glyph_note(mode: GlyphMode) -> &'static str {
match mode {
GlyphMode::PathOutline => "glyphs are genuine Bravura SMuFL outlines inlined as paths",
GlyphMode::EmbeddedFont => "glyphs are Bravura SMuFL codepoints set in the embedded font",
}
}
/// The provenance-state clause of the metadata comment. Archival mode declares /// The provenance-state clause of the metadata comment. Archival mode declares
/// traces present; display-only mode declares them suppressed — so a trace-free /// traces present; display-only mode declares them suppressed — so a trace-free
/// SVG (including the empty canvas) announces itself rather than passing as /// SVG (including the empty canvas) announces itself rather than passing as
@ -585,13 +646,15 @@ fn provenance_note(emit_provenance: bool) -> &'static str {
} }
/// A minimal, valid empty SVG for a layout with nothing to draw — still declaring /// A minimal, valid empty SVG for a layout with nothing to draw — still declaring
/// its provenance state, so an empty trace-free render is honest like a full one. /// its glyph mode and provenance state, so an empty render is honest like a full
fn empty_svg(emit_provenance: bool) -> String { /// one (the metadata is the same declared boundary on both paths).
fn empty_svg(glyph_mode: GlyphMode, emit_provenance: bool) -> String {
format!( format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\" viewBox=\"0 0 1 1\">\n\ <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\" viewBox=\"0 0 1 1\">\n\
\x20\x20<!-- epiphany-render-svg: empty resolved layout (no glyphs); {} -->\n\ \x20\x20<!-- epiphany-render-svg: empty resolved layout (no glyphs); {}; {} -->\n\
</svg>\n", </svg>\n",
glyph_note(glyph_mode),
provenance_note(emit_provenance), provenance_note(emit_provenance),
) )
} }
@ -780,6 +843,77 @@ mod tests {
); );
} }
#[test]
fn embedded_font_mode_sets_text_from_the_embedded_subset() {
let layout = stub_layout(11);
let out = render(
&layout,
&RenderOptions {
glyph_mode: GlyphMode::EmbeddedFont,
..RenderOptions::default()
},
);
assert!(
out.is_well_formed(),
"embedded-font SVG must be well-formed"
);
// The font is declared exactly once via @font-face with the base64 subset,
// under its non-reserved family name (OFL: not the Reserved Font Name).
assert_eq!(out.svg.matches("@font-face").count(), 1);
assert!(out.svg.contains("font-family: \"EpiphanyBravuraSubset\""));
assert!(!out.svg.contains("font-family: \"Bravura\""));
assert!(out.svg.contains("data:font/otf;base64,"));
// Every glyph is a `<text>` (the stub names only bundled glyphs), none a
// path or a fallback rect, and each carries a SMuFL codepoint reference.
assert_eq!(out.stats.text_count, layout.glyphs.len());
assert_eq!(out.stats.path_count, 0);
assert_eq!(out.stats.fallback_rect_count, 0);
assert!(out.diagnostics.is_empty());
assert_eq!(out.svg.matches("<text ").count(), layout.glyphs.len());
assert_eq!(out.svg.matches("&#x").count(), layout.glyphs.len());
// The metadata comment declares the embedded-font mode, and provenance is
// preserved exactly as in path mode.
assert!(out.svg.contains("set in the embedded font"));
assert_eq!(
out.stats.provenance_count,
layout.glyphs.len() + layout.strokes.len()
);
assert!(out.svg.contains("data-prov="));
}
#[test]
fn embedded_font_mode_is_deterministic_and_draws_every_glyph() {
let layout = stub_layout(7);
let a = render(
&layout,
&RenderOptions {
glyph_mode: GlyphMode::EmbeddedFont,
..RenderOptions::default()
},
);
let b = render(
&layout,
&RenderOptions {
glyph_mode: GlyphMode::EmbeddedFont,
..RenderOptions::default()
},
);
assert_eq!(a.svg, b.svg, "embedded-font render must be deterministic");
// Every glyph is accounted for (text or fallback rect), none dropped — the
// same no-silent-drop contract as path mode.
assert_eq!(
a.stats.text_count + a.stats.fallback_rect_count,
a.stats.glyph_count
);
// The default path mode draws no `<text>`; the modes do not bleed.
let path = render(&layout, &RenderOptions::default());
assert_eq!(path.stats.text_count, 0);
assert!(!path.svg.contains("@font-face"));
}
#[test] #[test]
fn empty_layout_renders_a_valid_empty_canvas() { fn empty_layout_renders_a_valid_empty_canvas() {
let layout = ResolvedLayoutIR { let layout = ResolvedLayoutIR {
@ -806,6 +940,17 @@ mod tests {
); );
assert!(suppressed.is_well_formed()); assert!(suppressed.is_well_formed());
assert!(suppressed.svg.contains("provenance traces suppressed")); assert!(suppressed.svg.contains("provenance traces suppressed"));
// The empty canvas also declares its glyph mode (the same boundary as a
// full render), so an empty embedded render is not mistaken for a path one.
let empty_embedded = render(
&layout,
&RenderOptions {
glyph_mode: GlyphMode::EmbeddedFont,
..RenderOptions::default()
},
);
assert!(empty_embedded.svg.contains("set in the embedded font"));
} }
#[test] #[test]

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="824.5643" height="110.24" viewBox="0 0 82.4564 11.024"> <svg xmlns="http://www.w3.org/2000/svg" width="824.5643" height="110.24" viewBox="0 0 82.4564 11.024">
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source --> <!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
<g transform="translate(3.0599 7.392) scale(1 -1)"> <g transform="translate(3.0599 7.392) scale(1 -1)">
<g data-layer="0"> <g data-layer="0">
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-kind="stroke"/> <line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-kind="stroke"/>

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="888.7701" height="110.24" viewBox="0 0 88.877 11.024"> <svg xmlns="http://www.w3.org/2000/svg" width="888.7701" height="110.24" viewBox="0 0 88.877 11.024">
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source --> <!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
<g transform="translate(3.065 7.392) scale(1 -1)"> <g transform="translate(3.065 7.392) scale(1 -1)">
<g data-layer="0"> <g data-layer="0">
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-kind="stroke"/> <line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-kind="stroke"/>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="279.5357" height="110.24" viewBox="0 0 27.9536 11.024"> <svg xmlns="http://www.w3.org/2000/svg" width="279.5357" height="110.24" viewBox="0 0 27.9536 11.024">
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source --> <!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
<g transform="translate(3.0599 7.392) scale(1 -1)"> <g transform="translate(3.0599 7.392) scale(1 -1)">
<g data-layer="0"> <g data-layer="0">
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-kind="stroke"/> <line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-kind="stroke"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="393.3" height="110.24" viewBox="0 0 39.33 11.024"> <svg xmlns="http://www.w3.org/2000/svg" width="393.3" height="110.24" viewBox="0 0 39.33 11.024">
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source --> <!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
<g transform="translate(3.065 7.392) scale(1 -1)"> <g transform="translate(3.065 7.392) scale(1 -1)">
<g data-layer="0"> <g data-layer="0">
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-kind="stroke"/> <line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-kind="stroke"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -60,7 +60,85 @@ def load():
verify(names_bytes, NAMES_SHA256, "glyphnames.json") verify(names_bytes, NAMES_SHA256, "glyphnames.json")
font = TTFont(io.BytesIO(font_bytes)) font = TTFont(io.BytesIO(font_bytes))
names = json.loads(names_bytes) names = json.loads(names_bytes)
return font, names return font, names, font_bytes
# The subset is a Modified Version under the OFL, so its primary user-facing name
# must NOT be the Reserved Font Name "Bravura" (OFL §"Reserved Font Name"). The
# copyright/trademark/license records (which name Bravura as attribution) are kept.
SUBSET_FAMILY = "EpiphanyBravuraSubset"
RESERVED_FONT_NAME = "Bravura"
def subset_font_b64(font_bytes, codepoints):
"""A deterministic, OFL-renamed base64 OTF subset of `font_bytes`.
For the renderer's `GlyphMode::EmbeddedFont` `@font-face` data-URI. SMuFL is
accessed by codepoint (no shaping), so layout features are dropped; the result
is small and byte-stable for a given fontTools version. Returns
`(family, b64, decoded_len, blake3_hex)`.
"""
import io, base64, blake3
from fontTools.ttLib import TTFont
from fontTools.subset import Subsetter, Options
# `recalcTimestamp=False` keeps the source font's fixed `head.modified` instead
# of stamping "now" on save, so the subset bytes are reproducible across runs
# (not just within one process), making the BLAKE3 lock stable per fontTools
# version.
sub = TTFont(io.BytesIO(font_bytes), recalcTimestamp=False)
opts = Options()
opts.layout_features = [] # codepoint access only; no GSUB/GPOS shaping
opts.name_IDs = ["*"] # keep name records incl. the OFL copyright/license
opts.notdef_outline = True
opts.recalc_bounds = True
ss = Subsetter(options=opts)
cps = sorted(set(codepoints))
ss.populate(unicodes=cps)
ss.subset(sub)
# OFL reserved-name compliance: rename the primary user-facing name off the
# Reserved Font Name in BOTH naming structures an OTF carries — the SFNT `name`
# table AND the CFF table's own name (the CFF Name INDEX and the top dict's
# FullName/FamilyName). Copyright/trademark/license records (which name Bravura
# as attribution) are left intact.
name = sub["name"]
for rec in list(name.names):
if rec.nameID in (1, 4, 6, 16):
name.setName(SUBSET_FAMILY, rec.nameID, rec.platformID, rec.platEncID, rec.langID)
elif rec.nameID == 3:
name.setName(rec.toUnicode().replace(RESERVED_FONT_NAME, SUBSET_FAMILY),
3, rec.platformID, rec.platEncID, rec.langID)
cff = sub["CFF "].cff
cff.fontNames[0] = SUBSET_FAMILY # the CFF Name INDEX
topdict = cff.topDictIndex[0]
for key in ("FullName", "FamilyName"): # CFF top-dict display names
if key in topdict.rawDict:
setattr(topdict, key, SUBSET_FAMILY)
# Coverage guard: the subset cmap MUST map every requested codepoint, or the
# embedded font would render tofu for a glyph the pipeline names.
cmap = sub.getBestCmap()
missing = [f"U+{cp:04X}" for cp in cps if cp not in cmap]
if missing:
sys.exit(f"subset cmap is missing codepoints: {missing}")
buf = io.BytesIO()
sub.save(buf)
raw = buf.getvalue()
# Compliance guard: reparse the *saved* bytes and confirm no primary name in
# either structure is still the Reserved Font Name (the copyright/trademark
# attribution may, and should, still mention Bravura).
check = TTFont(io.BytesIO(raw))
primary = [check["name"].getDebugName(i) for i in (1, 4, 6, 16)]
primary.append(check["CFF "].cff.fontNames[0])
ctop = check["CFF "].cff.topDictIndex[0]
primary += [getattr(ctop, k, None) for k in ("FullName", "FamilyName")]
if any(p == RESERVED_FONT_NAME for p in primary):
sys.exit(f"reserved font name leaked into a primary name record: {primary}")
return (SUBSET_FAMILY, base64.b64encode(raw).decode("ascii"),
len(raw), blake3.blake3(raw).hexdigest())
def round_d(d, nd=4): def round_d(d, nd=4):
def r(m): def r(m):
@ -73,7 +151,7 @@ def main():
from fontTools.pens.svgPathPen import SVGPathPen from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen from fontTools.pens.transformPen import TransformPen
from fontTools.pens.boundsPen import BoundsPen from fontTools.pens.boundsPen import BoundsPen
font, glyphnames = load() font, glyphnames, font_bytes = load()
upm = font["head"].unitsPerEm upm = font["head"].unitsPerEm
sp = upm / 4.0 # font units per staff space (SMuFL em = 4 staff spaces) sp = upm / 4.0 # font units per staff space (SMuFL em = 4 staff spaces)
scale = 1.0 / sp scale = 1.0 / sp
@ -151,5 +229,50 @@ def main():
sys.stdout.write("\n".join(o) + "\n") sys.stdout.write("\n".join(o) + "\n")
print(f"// extracted {len(rows)}/{len(NAMES)} glyphs", file=sys.stderr) print(f"// extracted {len(rows)}/{len(NAMES)} glyphs", file=sys.stderr)
# Optionally emit the embedded-font subset (renderer GlyphMode::EmbeddedFont).
if "--font-out" in sys.argv:
import fontTools
out_path = sys.argv[sys.argv.index("--font-out") + 1]
family, b64, raw_len, digest = subset_font_b64(font_bytes, [cp for _, cp, _, _ in rows])
f = []
f.append("//! GENERATED by `tools/extract_bravura_outlines.py --font-out` — "
"do not edit by hand.")
f.append("//!")
f.append("//! A subset of the OFL `Bravura.otf` holding exactly the glyphs the v0")
f.append("//! layout pipeline can name (the `BRAVURA_METRICS` / `NAMES` set), base64")
f.append("//! OTF for the renderer's `GlyphMode::EmbeddedFont` `@font-face` data-URI.")
f.append("//!")
f.append("//! As a Modified Version under the OFL, the subset's primary font name is")
f.append(f"//! `{family}`, NOT the Reserved Font Name; the copyright/trademark/license")
f.append("//! name records are retained as attribution (and `tools/OFL.txt` ships the")
f.append("//! full license). The cmap is verified at generation to cover every glyph.")
f.append("//!")
f.append(f"//! Source (pinned + SHA-256 verified): Bravura {FONT_TAG}, "
f"steinbergmedia/bravura @ {FONT_REF}.")
f.append(f"//! Subsetted with fontTools {fontTools.version}. Unlike the geometry-only")
f.append("//! outlines, the binary subset's exact bytes depend on the fontTools")
f.append("//! version recorded here, so regeneration is reproducible per version.")
f.append("")
f.append("/// The subset's font-family name (non-reserved; see the module note).")
f.append(f'pub(crate) const BRAVURA_SUBSET_FAMILY: &str = "{family}";')
f.append("")
f.append("/// MIME type for the embedded-font data-URI.")
f.append('pub(crate) const BRAVURA_SUBSET_MIME: &str = "font/otf";')
f.append("")
f.append("/// Decoded length, in bytes, of the embedded OTF (integrity lock).")
f.append("#[allow(dead_code)] // consumed only by the integrity test")
f.append(f"pub(crate) const BRAVURA_SUBSET_LEN: usize = {raw_len};")
f.append("")
f.append("/// BLAKE3-256 (hex) of the decoded OTF bytes (content-integrity lock).")
f.append("#[allow(dead_code)] // consumed only by the integrity test")
f.append(f'pub(crate) const BRAVURA_SUBSET_BLAKE3: &str = "{digest}";')
f.append("")
f.append("/// The Bravura subset (OTF/CFF outlines), base64-encoded.")
f.append(f'pub(crate) const BRAVURA_SUBSET_OTF_BASE64: &str = "{b64}";')
with open(out_path, "w") as fh:
fh.write("\n".join(f) + "\n")
print(f"// wrote {out_path} ({len(b64)} b64 chars, {raw_len} bytes, "
f"blake3 {digest[:16]}…)", file=sys.stderr)
if __name__ == "__main__": if __name__ == "__main__":
main() main()