Commit Graph
5 Commits
Author SHA1 Message Date
Esa Kataja bc19eae111 Add the editor: page view, cut editing, discard, levels, metadata
QGraphicsView for the viewport, with cuts and the content rectangle
manipulated by hit-testing in the view rather than as movable items —
the geometry is normalised, so what the screen shows and what the
renderer uses are the same numbers at a different zoom.

Cuts are edited as polylines: double-click adds one, drag moves it,
Ctrl-click inserts a vertex, right-click deletes a vertex or the whole
cut. That is how a straight cut becomes the stepped cut Engel needs.

Slice boundaries are drawn, not just cut lines, so a trim anomaly is
visible before export rather than after. Discarded slices are shaded.

Pages preview at 1800px regardless of source resolution, cached per
page, because re-reading a 4959x7017 vector render on every slider move
is unusable.

Autosave is debounced at 800ms and also fires on close.

Closes #12
Closes #14
Closes #15
Closes #16
Closes #17
Closes #18
Closes #19
Closes #20
Closes #26
2026-07-28 23:03:49 +03:00
Esa Kataja 974c91a727 Add the render pipeline and bundle export
Project state plus PDF in, finished slice images out. Slices are cut as
polygons rather than row ranges, so a stepped cut yields a slice with a
transparent notch instead of one that covers its neighbour.

Masking paints white, which the ink-to-alpha step turns into full
transparency — the same outcome the spec asks for, one step earlier.

Scale normalises every slice to the median staff height before fitting
the song to 1920px, so a rescanned page sits at the same note size as
its neighbours. The cap only ever shrinks: a song narrower than 1920
stays narrower.

Alpha quantisation rounds to 16 values spanning 0-255 inclusive.
Flooring, as first written, capped full ink at 240 and left every note
6% transparent — caught by decoding an exported slice rather than by
reading the code.

Ketun joululaulu exports 24 slices at a uniform 1489px, under the cap
and correctly not upscaled from its 200 DPI source; Feliz Navidad 20;
Elaman nalka 18.

Closes #21
Closes #22
Closes #23
Closes #24
Closes #25
Closes #27
2026-07-28 23:00:53 +03:00
Esa Kataja 9ed38323ef Add project state with cuts, discards and atomic save
Everything the human decides, in normalised page coordinates so the
file is independent of DPI and of which renderer produced it. The
bundle will be generated from this, which is what makes re-export
possible without repeating human work.

Cuts are polylines from the start, two points being the ordinary
straight case, so the stepped cuts Engel needs are a data question
rather than a migration. Adding a cut splits a slice and copies its
discard flag to both halves; removing one merges them.

Boundary cuts belong here rather than in detection: detection emits
cuts only between systems, so a page would have exactly as many slices
as systems, with the header and footer inside the first and last.
Isolating and discarding them is a slicing decision.

Saves are write-then-rename, so a crash mid-save cannot destroy the
previous state. The PDF is hashed, not copied, so an edit underneath is
reported rather than silently re-cut.

Ketun joululaulu now yields 24 kept slices over 12 pages and Feliz
Navidad 20 over 4, with headers and footers discarded on every page.

Closes #10, #11, #13
2026-07-28 22:49:51 +03:00
Esa Kataja 6ff956a622 Add detection: skew, system anchors, ink extents, cuts, staff height
Systems anchor on the vertical bracket and expand over nearby ink, per
ADR 0006. Two corrections the corpus forced:

- Expansion measures distance from the bracket, never from the growing
  extent. A chaining expansion hops between the closely-stacked lines of
  a title block and walks the whole way up the page — on Engel p1 it
  swallowed the title into system 1 and the copyright footer into
  system 3.
- Reach is 1.5 staff heights, which takes in the lyrics below the last
  staff and the tempo mark and INTRO box above the first, while leaving
  the title block and footer out.

Verified against the corpus. Ketun joululaulu: 2 systems per page except
p7 (3) and p12 (1), staff 48px throughout, skew -2.7 to +1.2 per page,
and p2 system 1 spans 179-1071 where its bracket is 177-994 — the
difference being the bottom voice's lyric line. Engel: 2-3 systems per
page, staff 70-72px. Elaman nalka: 3 systems per page, 0 skew, staff
118px at 600 DPI.

The overlay dump is what made both bugs visible, and stays as the tool
for diagnosing a page that comes out wrong.

Closes #4, #5, #6, #7, #8, #9
2026-07-28 22:38:50 +03:00
Esa Kataja e2f3e8fbdc Add package skeleton, source classification and raster loading
The pdf module is the whole of M1 except the debug overlay, which needs
detection results to draw.

Source type is detected per PDF by looking for a page-covering image,
and is always reported for confirmation rather than applied silently
(ADR 0004). Rasters are extracted via Pixmap(doc, xref) rather than by
decoding extract_image() bytes, because MuPDF handles JBIG2 and CCITT
scans that no image library will.

Scanned pages are extracted at the embedded image's native resolution;
only vector pages are rendered, at 600 DPI. Verified against the corpus:
Elaman nalka (vector) renders 4959x7017, Ketun joululaulu (scan) loads
1653x2332, Engel (scan) 2552x3504 — and Engel's page 2 is 2480 wide
where page 1 is 2552, so scan width varies within one PDF.

tests/test_pdf.py builds its own PDFs so the check runs without corpus
files, which are copyrighted and gitignored.

Closes #1, #2, #3
2026-07-28 22:33:26 +03:00