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>
This commit is contained in:
143
app/engine/pdf_utils.py
Normal file
143
app/engine/pdf_utils.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user