Files
noteman-slicer/docs/adr/0002-raster-only-svg-renderer-deferred.md
T
Esa Kataja 46ae7e813a Replace the handoff notes with a durable spec and ADRs
The handoff was written as a message to relay information; several
decisions lived only there. Split into permanent homes:

- docs/spec.md — scope, geometry model, pipeline order, detection,
  levels, editor, project file, markers, bundle format, noteman's
  changes, reference values
- ADR 0002 — raster only in release 1; SVG slices measured at 40x WebP
  naive, 2.6x with a bounding-box cull, deferred on risk not size
- ADR 0003 — lossless WebP with levels and 16-level alpha; every lossy
  option and alternative format measured larger for line art
- ADR 0004 — detection proposes, the human disposes; no unattended mode
- ADR 0005 — PyMuPDF for all PDF access, accepting AGPL
- ADR 0006 — systems are found by vertical brackets, not row-darkness
  gaps, which miscount every page of a 6-voice score

Also from testing against the hardest score in the repertoire: scanned
PDFs carry their scan as an embedded image and must be extracted at
native resolution rather than re-rendered at 600 DPI, and per-page
deskew is mandatory (skew varies -2.6 to +1.2 within one PDF).
2026-07-28 22:05:09 +03:00

2.9 KiB
Raw 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.
  • noteman needs no SVG support (image/svg+xml, .svg content type, CSP header on SVG responses) until the renderer ships.