"""Split a PDF along its cumulative visible page height, in reading order.""" import math from pathlib import Path import pymupdf from copienator.commands.splitting_int import _prepare_split_pages # Percentages emitted by the GUI have six decimal places. Recover exact page # boundaries despite rounding, without turning nearby in-page cuts into breaks. BOUNDARY_TOLERANCE_PERCENT = 0.000001 def cut_position(heights: list[float], percent: float) -> tuple[int, float]: """Return (page index, offset); offset zero denotes an exact page break.""" if not heights or any(height <= 0 for height in heights): raise ValueError("Le PDF doit contenir des pages non vides.") if not math.isfinite(percent) or not 0 < percent < 100: raise ValueError("Le pourcentage de coupe doit ĂȘtre strictement entre 0 et 100.") total = sum(heights) position = total * percent / 100 start = 0.0 for index, height in enumerate(heights): if index and abs(percent - start / total * 100) <= BOUNDARY_TOLERANCE_PERCENT: return index, 0.0 end = start + height if index + 1 < len(heights) and abs(percent - end / total * 100) <= BOUNDARY_TOLERANCE_PERCENT: return index + 1, 0.0 if position < end: return index, position - start start = end raise ValueError("Coupe hors du document.") def split_pdf(source: Path, percent: float, first_path: Path, second_path: Path) -> None: """Preserve whole pages; clip only the page actually crossed by the cut.""" with pymupdf.open(source) as document, pymupdf.open() as first, pymupdf.open() as second: index, offset = cut_position([page.rect.height for page in document], percent) if index: first.insert_pdf(document, from_page=0, to_page=index - 1) if offset == 0: second.insert_pdf(document, from_page=index) else: with pymupdf.open() as page_document: page_document.insert_pdf(document, from_page=index, to_page=index) visible = _prepare_split_pages(page_document)[0] for target, clip in ( (first, pymupdf.Rect(visible.x0, visible.y0, visible.x1, visible.y0 + offset)), (second, pymupdf.Rect(visible.x0, visible.y0 + offset, visible.x1, visible.y1)), ): page = target.new_page(width=clip.width, height=clip.height) page.show_pdf_page(page.rect, page_document, 0, clip=clip) if index + 1 < len(document): second.insert_pdf(document, from_page=index + 1) first.save(first_path) second.save(second_path)