The project file existed to persist state, so a future reader finding the exported flag would otherwise re-litigate it. The ADR states plainly that this reverses an earlier decision, what the original reasoning was, and what changed in use. ADR 0002 deferred the SVG renderer partly because re-export made it free to add later. That no longer holds, so its 'not stranded' bullet is struck through and pointed at ADR 0007 rather than left standing to mislead whoever revisits the SVG question.
65 lines
3.1 KiB
Markdown
65 lines
3.1 KiB
Markdown
# Raster only in release 1; the SVG renderer is deferred
|
||
|
||
Vector PDFs are most of the newer corpus, and keeping them vector all the way to
|
||
the viewer was an early goal — sheet music is line art, and SVG stays crisp at any
|
||
tablet zoom. We measured it before building it, and decided to **rasterize vector
|
||
sources like everything else in release 1** and revisit the SVG renderer once
|
||
real songs have been cut.
|
||
|
||
## The measurement
|
||
|
||
One real vector song, 6 pages, 65 systems, rendered both ways:
|
||
|
||
| Approach | Total | vs WebP |
|
||
|---|---|---|
|
||
| WebP slices (600 DPI → 1920, ink→alpha, lossless) | 1.19 MB | 1× |
|
||
| SVG, naive `viewBox` + `clipPath` | 26.0 MB | 40× |
|
||
| SVG, `set_cropbox` per band | 26.5 MB | 41× |
|
||
| SVG, bounding-box cull + glyph subset | 3.09 MB | 2.6× |
|
||
|
||
- **The naive cut is unusable.** A `viewBox` + `clipPath` slice contains the
|
||
entire page's geometry and merely hides eleven-twelfths of it.
|
||
- **`set_cropbox` does not help.** MuPDF renders full page content regardless of
|
||
the crop, so there is no free version of the cull.
|
||
- **The cull works.** PyMuPDF emits a `<defs>` glyph table (111 KB of a 256 KB
|
||
page) referenced by `<use transform="matrix(...)">`, plus body `<path>`
|
||
elements. Filter both by y-extent, then keep only the glyphs the survivors
|
||
reference. Roughly 50 lines, 15× improvement.
|
||
|
||
## Why defer, given the cull works
|
||
|
||
**Not size.** At 3.1 MB vs 1.2 MB per song — 225 MB vs 87 MB across a 73-song
|
||
corpus — both are nothing on a homelab. The measurement killed the lazy
|
||
implementation, not the idea.
|
||
|
||
What defers it is risk and missing evidence:
|
||
|
||
- The cull is **heuristic parsing**: glyph extents bounded at baseline ±14pt,
|
||
path extents read from raw `d` coordinates. It is over-inclusive by design, so
|
||
it fails safe — but "fails safe" still means a slice quietly carrying a
|
||
neighbour's slur, or a hairline dropped because the y-window was wrong on some
|
||
publisher's output. That needs eyeballing per song, a QA loop the raster path
|
||
doesn't have.
|
||
- Rendering 65 complex SVGs in a scrolling column may be slower than 65 WebPs.
|
||
Unmeasured.
|
||
- **The deciding question is unanswerable from here**: does 1920px WebP actually
|
||
feel insufficient when pinch-zooming on a tablet? Cutting real songs answers
|
||
it; more measurement doesn't.
|
||
|
||
Vector PDFs are also the *clean* case for the raster path — deskew is a no-op,
|
||
detection works best, there are no scan artefacts — so rasterizing them is not a
|
||
degraded fallback.
|
||
|
||
## Consequences
|
||
|
||
- The geometry model stays **renderer-agnostic**, in normalised page coordinates,
|
||
so adding the SVG renderer later is an output stage rather than a redesign.
|
||
- ~~**Re-export from the project file** regenerates every song's bundle without
|
||
repeating human work, so songs cut before the SVG renderer exists are not
|
||
stranded.~~ **No longer true** — see
|
||
[ADR 0007](0007-a-project-is-spent-once-exported.md). A project is spent once
|
||
its song has been exported, so songs cut before the SVG renderer ships stay
|
||
WebP unless they are cut again by hand.
|
||
- noteman needs no SVG support (`image/svg+xml`, `.svg` content type, CSP header
|
||
on SVG responses) until the renderer ships.
|