"""Moteur de creation de livrets.""" from pypdf import PdfReader, PdfWriter, PageObject, Transformation from .pdf_utils import get_page_size, create_blank_page, pad_to_multiple import io import math def create_booklet(input_path, output_path, binding="saddle_stitch", sheet_size=None, signature_size=32, alignment="center"): """Creer un livret a partir d'un PDF. binding: "continuous", "saddle_stitch", "perfect_bound", "cut_stacks" sheet_size: (largeur, hauteur) en points, ou None pour auto (2x page) signature_size: nombre de pages par cahier (perfect_bound uniquement) alignment: "bottom_left", "center", "pull_center" """ reader = PdfReader(input_path) if binding == "continuous": return _booklet_continuous(reader, output_path, sheet_size, alignment) elif binding == "saddle_stitch": return _booklet_saddle_stitch(reader, output_path, sheet_size, alignment) elif binding == "perfect_bound": return _booklet_perfect_bound(reader, output_path, sheet_size, signature_size, alignment) elif binding == "cut_stacks": return _booklet_cut_stacks(reader, output_path, sheet_size, alignment) def _get_sheet_size(reader, sheet_size): """Determiner la taille de la feuille.""" page_w, page_h = get_page_size(reader.pages[0]) if sheet_size: return sheet_size # Auto: feuille = 2x largeur de page return (page_w * 2, page_h) def _place_two_pages(left_page, right_page, sheet_w, sheet_h, page_w, page_h, alignment): """Placer deux pages cote a cote sur une feuille.""" sheet = PageObject.create_blank_page(width=sheet_w, height=sheet_h) half_w = sheet_w / 2 if alignment == "center": left_x = (half_w - page_w) / 2 right_x = half_w + (half_w - page_w) / 2 y = (sheet_h - page_h) / 2 elif alignment == "pull_center": left_x = half_w - page_w right_x = half_w y = (sheet_h - page_h) / 2 else: # bottom_left left_x = 0 right_x = half_w y = 0 if left_page: left_copy = PageObject.create_blank_page(width=sheet_w, height=sheet_h) left_copy.merge_page(left_page) left_copy.add_transformation(Transformation().translate(left_x, y)) sheet.merge_page(left_copy) if right_page: right_copy = PageObject.create_blank_page(width=sheet_w, height=sheet_h) right_copy.merge_page(right_page) right_copy.add_transformation(Transformation().translate(right_x, y)) sheet.merge_page(right_copy) return sheet def _booklet_continuous(reader, output_path, sheet_size, alignment): """Mode continu: pages 1+2, 3+4, etc.""" reader = pad_to_multiple(reader, 2) pages = reader.pages page_w, page_h = get_page_size(pages[0]) sheet_w, sheet_h = _get_sheet_size(reader, sheet_size) writer = PdfWriter() for i in range(0, len(pages), 2): left = pages[i] right = pages[i + 1] if i + 1 < len(pages) else None sheet = _place_two_pages(left, right, sheet_w, sheet_h, page_w, page_h, alignment) writer.add_page(sheet) with open(output_path, "wb") as f: writer.write(f) return output_path def _saddle_stitch_order(num_pages): """Calculer l'ordre des pages pour piqure a cheval. Pour N pages (multiple de 4): Feuille 1 recto: page N, page 1 Feuille 1 verso: page 2, page N-1 Feuille 2 recto: page N-2, page 3 etc. """ pairs = [] n = num_pages for i in range(n // 4): # Recto pairs.append((n - 1 - 2 * i, 2 * i)) # Verso pairs.append((2 * i + 1, n - 2 - 2 * i)) return pairs def _booklet_saddle_stitch(reader, output_path, sheet_size, alignment): """Piqure a cheval.""" reader = pad_to_multiple(reader, 4) pages = reader.pages num_pages = len(pages) page_w, page_h = get_page_size(pages[0]) sheet_w, sheet_h = _get_sheet_size(reader, sheet_size) pairs = _saddle_stitch_order(num_pages) writer = PdfWriter() for left_idx, right_idx in pairs: left = pages[left_idx] if left_idx < num_pages else None right = pages[right_idx] if right_idx < num_pages else None sheet = _place_two_pages(left, right, sheet_w, sheet_h, page_w, page_h, alignment) writer.add_page(sheet) with open(output_path, "wb") as f: writer.write(f) return output_path def _booklet_perfect_bound(reader, output_path, sheet_size, signature_size, alignment): """Dos carre colle: plusieurs cahiers de signature_size pages.""" reader = pad_to_multiple(reader, 4) pages = reader.pages num_pages = len(pages) page_w, page_h = get_page_size(pages[0]) sheet_w, sheet_h = _get_sheet_size(reader, sheet_size) writer = PdfWriter() # Decouper en signatures for sig_start in range(0, num_pages, signature_size): sig_end = min(sig_start + signature_size, num_pages) sig_pages = list(range(sig_start, sig_end)) # Padding de la signature while len(sig_pages) % 4 != 0: sig_pages.append(None) n = len(sig_pages) for i in range(n // 4): # Recto left_idx = sig_pages[n - 1 - 2 * i] right_idx = sig_pages[2 * i] left = pages[left_idx] if left_idx is not None and left_idx < num_pages else None right = pages[right_idx] if right_idx is not None and right_idx < num_pages else None sheet = _place_two_pages(left, right, sheet_w, sheet_h, page_w, page_h, alignment) writer.add_page(sheet) # Verso left_idx = sig_pages[2 * i + 1] right_idx = sig_pages[n - 2 - 2 * i] left = pages[left_idx] if left_idx is not None and left_idx < num_pages else None right = pages[right_idx] if right_idx is not None and right_idx < num_pages else None sheet = _place_two_pages(left, right, sheet_w, sheet_h, page_w, page_h, alignment) writer.add_page(sheet) with open(output_path, "wb") as f: writer.write(f) return output_path def _booklet_cut_stacks(reader, output_path, sheet_size, alignment): """Cut stacks: imprimer, couper en deux, empiler.""" reader = pad_to_multiple(reader, 4) pages = reader.pages num_pages = len(pages) page_w, page_h = get_page_size(pages[0]) sheet_w, sheet_h = _get_sheet_size(reader, sheet_size) writer = PdfWriter() half = num_pages // 2 for i in range(half): top_idx = i bottom_idx = half + i left = pages[top_idx] if top_idx < num_pages else None right = pages[bottom_idx] if bottom_idx < num_pages else None sheet = _place_two_pages(left, right, sheet_w, sheet_h, page_w, page_h, alignment) writer.add_page(sheet) with open(output_path, "wb") as f: writer.write(f) return output_path