Files
Jules e3dfd90933 Initial commit — Copydev Imposing Tool
Outil d'imposition PDF desktop (PyQt6) remplacant Quite Imposing Plus.
9 fonctions : livret, n-up, step & repeat, imposition manuelle,
jointure pages, pages blanches, numerotation, masquage, fonds perdus.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:54:02 +01:00

103 lines
3.3 KiB
Python

"""Moteur N-Up : placer plusieurs pages par feuille."""
from pypdf import PdfReader, PdfWriter, PageObject, Transformation
from .pdf_utils import get_page_size, create_blank_page, mm_to_pt, smart_crop_marks
import io
def nup_pages(input_path, output_path, cols=2, rows=2, sheet_size=(841.89, 595.28),
margin_top=0, margin_bottom=0, margin_left=0, margin_right=0,
gutter_h=0, gutter_v=0, scale_mode="fit", crop_marks=False,
background_path=None):
"""Placer plusieurs pages sur chaque feuille.
cols, rows: grille
sheet_size: (largeur, hauteur) en points
margins: en points
gutter_h, gutter_v: espacement horizontal/vertical entre pages
scale_mode: "fit" (ajuster) ou "none" (taille reelle)
crop_marks: ajouter des reperes de coupe
background_path: chemin vers un PDF de fond
"""
reader = PdfReader(input_path)
pages = reader.pages
sheet_w, sheet_h = sheet_size
# Zone utile
usable_w = sheet_w - margin_left - margin_right - gutter_h * (cols - 1)
usable_h = sheet_h - margin_top - margin_bottom - gutter_v * (rows - 1)
cell_w = usable_w / cols
cell_h = usable_h / rows
# Fond
bg_page = None
if background_path:
bg_reader = PdfReader(background_path)
if bg_reader.pages:
bg_page = bg_reader.pages[0]
writer = PdfWriter()
pages_per_sheet = cols * rows
for sheet_idx in range(0, len(pages), pages_per_sheet):
sheet = PageObject.create_blank_page(width=sheet_w, height=sheet_h)
if bg_page:
sheet.merge_page(bg_page)
grid_positions = []
for slot in range(pages_per_sheet):
page_idx = sheet_idx + slot
if page_idx >= len(pages):
break
page = pages[page_idx]
page_w, page_h = get_page_size(page)
col = slot % cols
row = slot // cols
grid_positions.append((col, row))
# Position de la cellule
x = margin_left + col * (cell_w + gutter_h)
y = sheet_h - margin_top - (row + 1) * cell_h - row * gutter_v
# Calcul du scale
if scale_mode == "fit":
scale_x = cell_w / page_w
scale_y = cell_h / page_h
scale = min(scale_x, scale_y)
else:
scale = 1.0
# Centrage dans la cellule
scaled_w = page_w * scale
scaled_h = page_h * scale
offset_x = x + (cell_w - scaled_w) / 2
offset_y = y + (cell_h - scaled_h) / 2
# Placer la page
overlay = PageObject.create_blank_page(width=sheet_w, height=sheet_h)
overlay.merge_page(page)
overlay.add_transformation(
Transformation().scale(scale, scale).translate(offset_x, offset_y)
)
sheet.merge_page(overlay)
# Reperes de coupe
if crop_marks and grid_positions:
marks = smart_crop_marks(
sheet_w, sheet_h, grid_positions,
cell_w + gutter_h, cell_h + gutter_v,
margin_left, margin_top
)
sheet.merge_page(marks)
writer.add_page(sheet)
with open(output_path, "wb") as f:
writer.write(f)
return output_path