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>
103 lines
3.3 KiB
Python
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
|