V2.1.0 — Formats personnalises + reperes de coupe

- Formats personnalises dans tous les menus deroulants de format :
  - "Personnalise..." : saisie libre largeur x hauteur en mm
  - "Ajouter un format..." : sauvegarder un format custom (persistant)
  - Formats custom affiches dans le dropdown avec separateur
- Reperes de coupe (Annotations > Reperes de coupe) :
  - Longueur, distance du bord, epaisseur configurables
  - Couleur : noir, gris, CMYK
  - Reperes de centrage optionnels
  - Appliquer a : toutes, paires, impaires, page courante
  - Enlever les reperes de coupe (menu)
- Step & Repeat : ajout des marges (haut, bas, gauche, droite)
- Tous les dialogues de format migres vers format_combo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 08:00:55 +01:00
parent 9cc256ff51
commit c9212a30ea
11 changed files with 457 additions and 69 deletions

85
app/engine/crop_marks.py Normal file
View File

@@ -0,0 +1,85 @@
"""Moteur Reperes de coupe autonomes."""
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from .pdf_utils import get_page_size
import io
def add_crop_marks(input_path, output_path, mark_length=14.17, mark_offset=7.09,
line_width=0.25, color=(0, 0, 0), center_marks=False,
apply_to="all", current_page=0):
"""Ajouter des reperes de coupe sur les pages."""
reader = PdfReader(input_path)
writer = PdfWriter()
for i, page in enumerate(reader.pages):
page_num = i + 1
should_apply = False
if apply_to == "all":
should_apply = True
elif apply_to == "even":
should_apply = page_num % 2 == 0
elif apply_to == "odd":
should_apply = page_num % 2 != 0
elif apply_to == "current":
should_apply = i == current_page
if should_apply:
w, h = get_page_size(page)
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=(w, h))
c.setLineWidth(line_width)
c.setStrokeColorRGB(*color)
# 4 coins
corners = [(0, 0), (w, 0), (0, h), (w, h)]
for cx, cy in corners:
# Traits horizontaux
if cx == 0:
c.line(cx - mark_offset - mark_length, cy, cx - mark_offset, cy)
else:
c.line(cx + mark_offset, cy, cx + mark_offset + mark_length, cy)
# Traits verticaux
if cy == 0:
c.line(cx, cy - mark_offset - mark_length, cx, cy - mark_offset)
else:
c.line(cx, cy + mark_offset, cx, cy + mark_offset + mark_length)
# Reperes de centrage
if center_marks:
mid_x = w / 2
mid_y = h / 2
ml = mark_length * 0.7
# Haut
c.line(mid_x, h + mark_offset, mid_x, h + mark_offset + ml)
# Bas
c.line(mid_x, -mark_offset, mid_x, -mark_offset - ml)
# Gauche
c.line(-mark_offset, mid_y, -mark_offset - ml, mid_y)
# Droite
c.line(w + mark_offset, mid_y, w + mark_offset + ml, mid_y)
c.showPage()
c.save()
packet.seek(0)
overlay = PdfReader(packet)
page.merge_page(overlay.pages[0])
writer.add_page(page)
with open(output_path, "wb") as f:
writer.write(f)
return output_path
def remove_crop_marks(input_path, output_path):
"""Enlever les reperes de coupe (remet mediabox = cropbox)."""
reader = PdfReader(input_path)
writer = PdfWriter()
for page in reader.pages:
page.cropbox = page.mediabox
writer.add_page(page)
with open(output_path, "wb") as f:
writer.write(f)
return output_path