Files
copydev-imposing-tool/app/engine/pdf_utils.py
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

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)