Files
noteman-slicer/tests/test_editor.py
T
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

115 lines
3.6 KiB
Python

"""Runnable check that the editor builds and its edits reach project state.
Runs offscreen, so it verifies wiring rather than appearance: that the widgets
construct, that an edit changes the model, and that autosave and export work.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import numpy as np # noqa: E402
import pymupdf # noqa: E402
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from PySide6.QtWidgets import QApplication # noqa: E402
from noteman_slicer.detect import detect_page # noqa: E402
from noteman_slicer.editor import Editor # noqa: E402
from noteman_slicer.pdf import open_source, page_raster # noqa: E402
from noteman_slicer.project import Cut, Project, default_path # noqa: E402
W, H = 1200, 1600
def _scan_pdf(path: Path) -> None:
art = np.full((H, W), 255, np.uint8)
for top in (300, 800):
art[top : top + 200, 100:104] = 0
for staff in (top, top + 140):
for i in range(5):
art[staff + i * 15 : staff + i * 15 + 2, 110:1100] = 0
pix = pymupdf.Pixmap(pymupdf.csGRAY, W, H, bytearray(art.tobytes()), False)
doc = pymupdf.open()
doc.new_page(width=595, height=842).insert_image(pymupdf.Rect(0, 0, 595, 842), pixmap=pix)
doc.save(path)
def main() -> int:
tmp = Path(__file__).with_name("_tmp")
tmp.mkdir(exist_ok=True)
pdf = tmp / "scan.pdf"
_scan_pdf(pdf)
app = QApplication.instance() or QApplication(sys.argv[:1])
source = open_source(pdf)
gray = page_raster(source, 0)
project = Project.from_detection(pdf, [detect_page(gray)], [gray.shape[0]])
editor = Editor(source, project)
page = project.pages[0]
# Cuts.
before = page.slice_count
editor.view.selected_cut = page.add_cut(Cut.straight(0.5))
editor.view.redraw()
assert page.slice_count == before + 1
# A vertex turns a straight cut into a stepped one.
cut = page.cuts[editor.view.selected_cut]
cut.points.insert(1, (0.4, cut.y_at(0.4)))
cut.points[1] = (0.4, cut.points[1][1] + 0.03)
assert cut.straight_y is None, "the cut should no longer be straight"
editor.view.redraw()
# Discard.
editor.view.selected_slice = 1
was = page.discards[1]
editor.view.toggle_discard()
assert page.discards[1] != was
# Skew and levels reach the model and re-render without raising.
editor.skew.setValue(-1.4)
assert abs(page.skew + 1.4) < 1e-6
editor.black.setValue(40)
editor.white.setValue(210)
assert project.page_levels(0) == (40, 210)
# Metadata.
editor.metadata["title"].setText("Ketun joululaulu")
editor.metadata["composer"].setText("trad.")
assert project.metadata["title"] == "Ketun joululaulu"
# Content rectangle edits and reset.
page.content_rect = (0.05, 0.02, 0.95, 0.98)
assert project.page_content_rect(0) == (0.05, 0.02, 0.95, 0.98)
editor._reset_rect()
assert project.page_content_rect(0) == project.content_rect
# Autosave target, then a round-trip through disk.
editor._save()
saved = default_path(pdf)
assert saved.exists()
reloaded = Project.load(saved)
assert reloaded.metadata["title"] == "Ketun joululaulu"
assert reloaded.pages[0].skew == -1.4
assert reloaded.pages[0].levels == (40, 210)
assert [c.points for c in reloaded.pages[0].cuts] == [c.points for c in page.cuts]
editor.close()
source.close()
for f in (pdf, saved):
f.unlink()
tmp.rmdir()
print("ok")
return 0
if __name__ == "__main__":
sys.exit(main())