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>
144 lines
4.2 KiB
Python
144 lines
4.2 KiB
Python
"""Utilitaires PDF communs pour Copydev Imposing Tool."""
|
|
|
|
from pypdf import PdfReader, PdfWriter, PageObject, Transformation
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.units import mm, inch
|
|
from reportlab.lib.pagesizes import A4, A3, letter
|
|
import io
|
|
import math
|
|
|
|
# Tailles de pages predefinies (largeur, hauteur en points)
|
|
PAGE_SIZES = {
|
|
"A4 Portrait": (595.28, 841.89),
|
|
"A4 Paysage": (841.89, 595.28),
|
|
"A3 Portrait": (841.89, 1190.55),
|
|
"A3 Paysage": (1190.55, 841.89),
|
|
"Letter Portrait": (612, 792),
|
|
"Letter Paysage": (792, 612),
|
|
"Legal Portrait": (612, 1008),
|
|
"Legal Paysage": (1008, 612),
|
|
}
|
|
|
|
# Conversion
|
|
PT_TO_MM = 25.4 / 72
|
|
MM_TO_PT = 72 / 25.4
|
|
|
|
|
|
def mm_to_pt(val_mm):
|
|
"""Convertir millimetres en points."""
|
|
return val_mm * MM_TO_PT
|
|
|
|
|
|
def pt_to_mm(val_pt):
|
|
"""Convertir points en millimetres."""
|
|
return val_pt * PT_TO_MM
|
|
|
|
|
|
def get_page_size(page):
|
|
"""Obtenir la taille d'une page (largeur, hauteur) en points."""
|
|
box = page.mediabox
|
|
w = float(box.width)
|
|
h = float(box.height)
|
|
return w, h
|
|
|
|
|
|
def create_blank_page(width, height):
|
|
"""Creer une page blanche avec les dimensions donnees (en points)."""
|
|
packet = io.BytesIO()
|
|
c = canvas.Canvas(packet, pagesize=(width, height))
|
|
c.showPage()
|
|
c.save()
|
|
packet.seek(0)
|
|
reader = PdfReader(packet)
|
|
return reader.pages[0]
|
|
|
|
|
|
def merge_page_onto(base_page, overlay_page, x=0, y=0, scale=1.0, rotation=0):
|
|
"""Fusionner une page overlay sur une page de base a une position donnee."""
|
|
if rotation:
|
|
overlay_page = overlay_page.rotate(rotation)
|
|
|
|
transform = Transformation().scale(scale, scale).translate(x, y)
|
|
overlay_page.add_transformation(transform)
|
|
base_page.merge_page(overlay_page)
|
|
return base_page
|
|
|
|
|
|
def create_crop_marks_page(width, height, positions, mark_length=14.17, mark_offset=7.09, line_width=0.25):
|
|
"""Creer une page avec des reperes de coupe aux positions donnees.
|
|
|
|
positions: liste de tuples (x, y) ou placer les reperes
|
|
mark_length: longueur des traits (defaut 5mm = 14.17pt)
|
|
mark_offset: distance du bord de la page (defaut 2.5mm = 7.09pt)
|
|
"""
|
|
packet = io.BytesIO()
|
|
c = canvas.Canvas(packet, pagesize=(width, height))
|
|
c.setLineWidth(line_width)
|
|
c.setStrokeColorRGB(0, 0, 0)
|
|
|
|
for x, y in positions:
|
|
# Trait horizontal gauche
|
|
c.line(x - mark_offset - mark_length, y, x - mark_offset, y)
|
|
# Trait horizontal droit
|
|
c.line(x + mark_offset, y, x + mark_offset + mark_length, y)
|
|
# Trait vertical bas
|
|
c.line(x, y - mark_offset - mark_length, x, y - mark_offset)
|
|
# Trait vertical haut
|
|
c.line(x, y + mark_offset, x, y + mark_offset + mark_length)
|
|
|
|
c.showPage()
|
|
c.save()
|
|
packet.seek(0)
|
|
reader = PdfReader(packet)
|
|
return reader.pages[0]
|
|
|
|
|
|
def smart_crop_marks(sheet_width, sheet_height, grid_positions, cell_width, cell_height,
|
|
margin_left=0, margin_top=0, mark_length=14.17, mark_offset=7.09):
|
|
"""Generer des reperes de coupe intelligents pour une grille n-up.
|
|
|
|
Evite les chevauchements avec le contenu des pages.
|
|
"""
|
|
positions = set()
|
|
|
|
for col, row in grid_positions:
|
|
x = margin_left + col * cell_width
|
|
y = sheet_height - margin_top - row * cell_height
|
|
|
|
# 4 coins de chaque cellule
|
|
corners = [
|
|
(x, y),
|
|
(x + cell_width, y),
|
|
(x, y - cell_height),
|
|
(x + cell_width, y - cell_height),
|
|
]
|
|
for cx, cy in corners:
|
|
positions.add((cx, cy))
|
|
|
|
return create_crop_marks_page(sheet_width, sheet_height, list(positions),
|
|
mark_length, mark_offset)
|
|
|
|
|
|
def pad_to_multiple(reader, multiple=4):
|
|
"""Ajouter des pages blanches pour atteindre un multiple donne."""
|
|
num_pages = len(reader.pages)
|
|
remainder = num_pages % multiple
|
|
if remainder == 0:
|
|
return reader
|
|
|
|
pages_to_add = multiple - remainder
|
|
writer = PdfWriter()
|
|
for page in reader.pages:
|
|
writer.add_page(page)
|
|
|
|
last_page = reader.pages[-1]
|
|
w, h = get_page_size(last_page)
|
|
|
|
for _ in range(pages_to_add):
|
|
writer.add_blank_page(w, h)
|
|
|
|
output = io.BytesIO()
|
|
writer.write(output)
|
|
output.seek(0)
|
|
return PdfReader(output)
|