Files
noteman-slicer/docs/adr/0002-raster-only-svg-renderer-deferred.md
Esa Kataja 15f64e4131 Record the spent-project reversal as ADR 0007
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.
2026-07-28 23:51:53 +03:00

3.1 KiB
Raw Permalink Blame History

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. 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.