miscs (Interro02) : horizontal cutting resolution
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
import pymupdf
|
||||
|
||||
from copienator import CliError, EvaluationWorkspace, atomic_write_json, read_json
|
||||
from copienator.commands.resolve_manual import parse_instruction_text, resolve_manual
|
||||
from copienator.pdf_cut import cut_position, split_pdf
|
||||
from copienator_gui.cut_helper import PAGE_GAP, cut_operator, percentage_at_y, y_at_percentage
|
||||
|
||||
|
||||
def make_pdf(path, heights=(300, 700), rotation=0, cropped=False):
|
||||
with pymupdf.open() as document:
|
||||
for index, height in enumerate(heights):
|
||||
page = document.new_page(width=200, height=height)
|
||||
page.draw_rect(pymupdf.Rect(0, 0, 200, height / 2), color=None, fill=(1, 0, 0))
|
||||
page.draw_rect(pymupdf.Rect(0, height / 2, 200, height), color=None, fill=(0, 0, 1))
|
||||
page.insert_text((30, 30), f"Page {index + 1}")
|
||||
if cropped:
|
||||
page.set_cropbox(pymupdf.Rect(10, 20, 180, height - 30))
|
||||
page.set_rotation(rotation)
|
||||
document.save(path)
|
||||
|
||||
|
||||
class ManualCutTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
|
||||
def test_parser_accepts_new_operators_and_rejects_invalid_cuts(self):
|
||||
for keep in (1, 2):
|
||||
for action in (">", "x"):
|
||||
item = parse_instruction_text(f"Copie16 Ex 4 : 1) c{{43.125}}{keep}{action} |Ex 4 : 2)")[0]
|
||||
self.assertEqual(item.cut, (43.125, keep))
|
||||
self.assertEqual(item.should_merge, action == ">")
|
||||
self.assertTrue(item.pipe_first)
|
||||
for operator in ("c{0}1>", "c{100}1>", "c{-1}1>", "c{101}1>", "c{43}3>",
|
||||
"c{43}1s", "c{NaN}1>"):
|
||||
with self.assertRaises(CliError):
|
||||
parse_instruction_text(f"Copie16 A {operator} B")
|
||||
with self.assertRaises(CliError):
|
||||
parse_instruction_text("Copie16 A c{43}1> A")
|
||||
|
||||
def test_page_boundary_preserves_whole_pages_without_clipping(self):
|
||||
source, first, second = [self.root / name for name in ("source.pdf", "first.pdf", "second.pdf")]
|
||||
make_pdf(source, heights=(100, 200), rotation=180)
|
||||
before = source.read_bytes()
|
||||
with patch("pymupdf.Page.show_pdf_page", side_effect=AssertionError("must not clip whole pages")):
|
||||
split_pdf(source, 33.333333, first, second)
|
||||
for path, height in ((first, 100), (second, 200)):
|
||||
with pymupdf.open(path) as pdf:
|
||||
self.assertEqual(len(pdf), 1)
|
||||
self.assertEqual(pdf[0].rect.height, height)
|
||||
self.assertEqual(pdf[0].rotation, 180)
|
||||
self.assertEqual(source.read_bytes(), before)
|
||||
|
||||
def test_in_page_cut_preserves_visible_pixels_for_cropped_rotated_pages(self):
|
||||
source, first, second = [self.root / name for name in ("source.pdf", "first.pdf", "second.pdf")]
|
||||
for rotation in (0, 90, 180, 270):
|
||||
with self.subTest(rotation=rotation):
|
||||
make_pdf(source, heights=(400,), rotation=rotation, cropped=True)
|
||||
with pymupdf.open(source) as pdf:
|
||||
pix = pdf[0].get_pixmap()
|
||||
expected = np.frombuffer(pix.samples, np.uint8).reshape(pix.height, pix.width, 3)
|
||||
split_pdf(source, 50, first, second)
|
||||
for path, pixels in ((first, expected[:len(expected)//2]), (second, expected[len(expected)//2:])):
|
||||
with pymupdf.open(path) as pdf:
|
||||
pix = pdf[0].get_pixmap()
|
||||
actual = np.frombuffer(pix.samples, np.uint8).reshape(pix.height, pix.width, 3)
|
||||
self.assertEqual(actual.shape, pixels.shape)
|
||||
self.assertLess(np.abs(actual.astype(float) - pixels).mean(), 0.1)
|
||||
|
||||
def test_cut_within_page_preserves_subsequent_pages(self):
|
||||
source, first, second = [self.root / name for name in ("source.pdf", "first.pdf", "second.pdf")]
|
||||
make_pdf(source)
|
||||
split_pdf(source, 15, first, second)
|
||||
with pymupdf.open(first) as pdf:
|
||||
self.assertEqual([page.rect.height for page in pdf], [150])
|
||||
with pymupdf.open(second) as pdf:
|
||||
self.assertEqual([page.rect.height for page in pdf], [150, 700])
|
||||
|
||||
def test_resolver_archives_source_and_recorrrects_both_labels(self):
|
||||
for keep in (1, 2):
|
||||
for mode in (">", "x"):
|
||||
for pipe_first in (False, True):
|
||||
with self.subTest(keep=keep, mode=mode, pipe_first=pipe_first):
|
||||
root = self.root / f"{keep}{mode == '>'}{pipe_first}"
|
||||
copies = root / "Copies" / "Copie16"
|
||||
copies.mkdir(parents=True)
|
||||
source, target = copies / "A.pdf", copies / "B.pdf"
|
||||
make_pdf(source)
|
||||
make_pdf(target, heights=(80,))
|
||||
original, destination = source.read_bytes(), target.read_bytes()
|
||||
atomic_write_json(root / "correction.json", {
|
||||
label: [[{"id": "16", "result": {}}]] for label in ("A", "B")
|
||||
})
|
||||
new_label = "|B" if pipe_first else "B|"
|
||||
(root / "manual_resolutions.txt").write_text(f"Copie16 A c{{30}}{keep}{mode} {new_label}\n")
|
||||
self.assertEqual(resolve_manual(EvaluationWorkspace(root)), 0)
|
||||
self.assertEqual((copies / "A_old.pdf").read_bytes(), original)
|
||||
self.assertEqual((copies / "B_old.pdf").read_bytes(), destination)
|
||||
retained, moved = (300, 700) if keep == 1 else (700, 300)
|
||||
with pymupdf.open(copies / "A_new.pdf") as pdf:
|
||||
self.assertEqual([page.rect.height for page in pdf], [retained])
|
||||
with pymupdf.open(copies / "B_new.pdf") as pdf:
|
||||
expected = ([moved, 80] if pipe_first else [80, moved]) if mode == ">" else [moved]
|
||||
self.assertEqual([page.rect.height for page in pdf], expected)
|
||||
self.assertEqual(read_json(root / "refaire.json"), [["Copie16", ["A", "B"]]])
|
||||
self.assertTrue(all(read_json(root / "correction.json")[label][0][0]["result"]["suffix"] == "_new" for label in ("A", "B")))
|
||||
self.assertFalse(list(copies.glob("temp_*.pdf")))
|
||||
|
||||
def test_helper_snaps_to_gap_and_round_trips_percentage(self):
|
||||
heights = [100, 200]
|
||||
for y in (95, 100, 100 + PAGE_GAP / 2, 100 + PAGE_GAP + 5):
|
||||
percent = percentage_at_y(y, heights, 1)
|
||||
self.assertEqual(cut_position(heights, percent), (1, 0))
|
||||
self.assertEqual(y_at_percentage(percent, heights, 1), 100 + PAGE_GAP / 2)
|
||||
self.assertEqual(cut_operator(43, 1, ">"), "c{43}1>")
|
||||
self.assertEqual(cut_operator(100/3, 2, "x"), "c{33.333333}2x")
|
||||
self.assertEqual(cut_position(heights, 33.333333), (1, 0))
|
||||
self.assertNotEqual(cut_position(heights, 33.3)[1], 0)
|
||||
Reference in New Issue
Block a user