Opening an exported song starts a fresh session from detection instead of resuming: cuts, discards and metadata do not carry over, so a re-cut never inherits decisions that have already shipped. --resume overrides it on edit, export and project. This reverses what was agreed in planning and written into docs/spec.md and CONTEXT.md, which promised resume-across-sessions and re-export. Both are corrected. The cost is deliberate and worth stating: changing the width cap or adding the SVG renderer later now means re-cutting each song by hand rather than regenerating every bundle from its project file. Export records the flag in bundle.write, so no caller can forget it. Also removed --refit and the Auto-fit buttons, which were added without being asked for and whose only purpose - migrating projects made before the content rectangle was proposed - disappears once exported projects start fresh. Reset now restores detection's proposal rather than the whole page: clearing to full width would undo the thing the rectangle exists for, so one button covers it. open_project() replaces four copies of load-or-detect across the CLI and the editor.
89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
"""Runnable check for project state: round-trip, cut edits, discard pre-set."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from noteman_slicer.detect import PageDetection, System # noqa: E402
|
|
from noteman_slicer.project import Cut, Project, default_path # noqa: E402
|
|
|
|
|
|
def main() -> int:
|
|
tmp = Path(__file__).with_name("_tmp")
|
|
tmp.mkdir(exist_ok=True)
|
|
pdf = tmp / "song.pdf"
|
|
pdf.write_bytes(b"%PDF-1.7 not really a pdf, only its bytes are hashed")
|
|
|
|
height = 1000
|
|
detection = PageDetection(
|
|
skew=-1.1,
|
|
systems=[System(200, 400, 60.0), System(600, 800, 60.0)],
|
|
cuts=[500],
|
|
)
|
|
project = Project.from_detection(pdf, [detection], [height])
|
|
page = project.pages[0]
|
|
|
|
# One cut between the systems, plus a boundary cut above the first and
|
|
# below the last — so the header and footer become their own slices.
|
|
assert len(page.cuts) == 3, [c.points for c in page.cuts]
|
|
assert page.discards == [True, False, False, True], page.discards
|
|
assert page.slice_count == 4
|
|
assert project.kept_slices() == [(0, 1), (0, 2)], project.kept_slices()
|
|
|
|
# Geometry is normalised, so it survives any change of resolution.
|
|
assert all(0.0 <= y <= 1.0 for cut in page.cuts for _, y in cut.points)
|
|
assert page.cuts[1].straight_y == 0.5
|
|
|
|
# A straight cut is flat; a stepped one is not, and interpolates.
|
|
step = Cut([(0.0, 0.20), (0.35, 0.20), (0.35, 0.40), (1.0, 0.40)])
|
|
assert step.straight_y is None
|
|
assert step.y_at(0.0) == 0.20
|
|
assert step.y_at(1.0) == 0.40
|
|
assert step.y_at(0.35) == 0.20 or step.y_at(0.35) == 0.40
|
|
assert step.highest == 0.20 and step.lowest == 0.40
|
|
|
|
# Adding a cut splits a slice and keeps that slice's flag on both halves.
|
|
before = page.slice_count
|
|
index = page.add_cut(Cut.straight(0.65))
|
|
assert page.slice_count == before + 1
|
|
assert index == 2, index
|
|
assert page.discards == [True, False, False, False, True], page.discards
|
|
|
|
# Removing it merges them again.
|
|
page.remove_cut(index)
|
|
assert page.slice_count == before
|
|
assert page.discards == [True, False, False, True], page.discards
|
|
|
|
# Round-trip.
|
|
saved = project.save()
|
|
assert saved == default_path(pdf), saved
|
|
reloaded = Project.load(saved)
|
|
assert reloaded.pages[0].skew == -1.1
|
|
assert reloaded.pages[0].discards == page.discards
|
|
assert [c.points for c in reloaded.pages[0].cuts] == [c.points for c in page.cuts]
|
|
assert reloaded.source_hash == project.source_hash
|
|
assert not reloaded.source_changed()
|
|
|
|
# A project is spent once exported: reopening starts fresh.
|
|
assert not reloaded.exported
|
|
reloaded.exported = True
|
|
reloaded.save()
|
|
assert Project.load(saved).exported
|
|
|
|
# A PDF edited underneath must be reported, not silently re-cut.
|
|
pdf.write_bytes(b"%PDF-1.7 different bytes entirely")
|
|
assert reloaded.source_changed()
|
|
|
|
for f in (pdf, saved):
|
|
f.unlink()
|
|
tmp.rmdir()
|
|
print("ok")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|