V3.6.0 — Demonter pages, masquage visuel, regles, bleeds, corrections

- Demonter les pages : couper en 2 sans reordonner (sous Delivretiser)
- Masquage visuel : selection au curseur sur l'apercu + plage de pages
- Regles horizontale/verticale en mm (menu Affichage)
- Fonds perdus : filigrane rouge pour visualiser les bleeds
- Reperes de coupe : marques a l'exterieur (page agrandie), 2 traits/coin
- Numerotation : couleur configurable via selecteur
- Decalage : Trim & Shift renomme en francais, creep -> chasse
- Recadrages multiples cumulatifs (plages differentes)
- Fix spinbox format personnalise (fleches aleatoires)
- Fix combo livret : option Automatique preservee

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-29 22:12:54 +02:00
parent f7ad806b26
commit 202834a794
13 changed files with 675 additions and 113 deletions

View File

@@ -40,6 +40,7 @@ from app.engine.crop_marks import add_crop_marks, remove_crop_marks
from app.engine.crop import crop_pages
from app.engine.add_content import add_text_to_pages, add_image_to_pages
from app.engine.unbooklet import unbooklet
from app.engine.disassemble import disassemble_pages
from app.engine.trim_shift import trim_and_shift
from app.engine.extract_content import extract_text_from_page, extract_images_from_page
from app.dialogs.trim_shift_dialog import TrimShiftDialog
@@ -63,6 +64,7 @@ class MainWindow(QMainWindow):
self._result_file = None
self._original_file = None
self._crop_settings = {}
self._bleed_settings = None
self._undo_stack = [] # pile de fichiers pour annuler
self._redo_stack = []
@@ -221,11 +223,12 @@ class MainWindow(QMainWindow):
self._add_action(impose_menu, "Joindre deux pages", None, self._do_join_pages)
impose_menu.addSeparator()
self._add_action(impose_menu, "Delivretiser", None, self._do_unbooklet)
self._add_action(impose_menu, "Demonter les pages", None, self._do_disassemble)
# Pages
pages_menu = menubar.addMenu("Pages")
self._add_action(pages_menu, "Recadrer...", None, self._do_crop)
self._add_action(pages_menu, "Trim && Shift...", None, self._do_trim_shift)
self._add_action(pages_menu, "Decalage...", None, self._do_trim_shift)
pages_menu.addSeparator()
self._add_action(pages_menu, "Pivoter +90", None, lambda: self._rotate_current(90))
self._add_action(pages_menu, "Pivoter -90", None, lambda: self._rotate_current(-90))
@@ -236,6 +239,13 @@ class MainWindow(QMainWindow):
self._add_action(pages_menu, "Inserer des pages blanches", None, self._do_insert_blank)
self._add_action(pages_menu, "Creer un document test...", None, self._do_create_sample)
# Affichage
view_menu = menubar.addMenu("Affichage")
self._rulers_action = QAction("Afficher les regles", self)
self._rulers_action.setCheckable(True)
self._rulers_action.toggled.connect(self._toggle_rulers)
view_menu.addAction(self._rulers_action)
# Annotations
annot_menu = menubar.addMenu("Annotations")
self._add_action(annot_menu, "Ajouter du texte...", None, self._do_add_text)
@@ -319,6 +329,7 @@ class MainWindow(QMainWindow):
rtb.addWidget(self._make_section_label("IMPOSITION"))
rtb.addAction("Livret", self._do_booklet)
rtb.addAction("Delivretiser", self._do_unbooklet)
rtb.addAction("Demonter", self._do_disassemble)
rtb.addAction("N-Up", self._do_nup)
rtb.addAction("Repetition", self._do_step_repeat)
rtb.addAction("Manuel", self._do_manual_impose)
@@ -328,7 +339,7 @@ class MainWindow(QMainWindow):
# Section Pages
rtb.addWidget(self._make_section_label("PAGES"))
rtb.addAction("Recadrer", self._do_crop)
rtb.addAction("Trim/Shift", self._do_trim_shift)
rtb.addAction("Decalage", self._do_trim_shift)
rtb.addAction("Pg blanche", self._do_insert_blank)
rtb.addAction("Inverser", self._do_reverse_pages)
rtb.addAction("Fusionner", self._do_merge)
@@ -633,6 +644,22 @@ class MainWindow(QMainWindow):
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
def _do_disassemble(self):
if not self._require_file():
return
self._show_progress()
output = self._make_temp()
try:
disassemble_pages(self._current_file, output)
self._show_result(output)
self._status_label.setText("Demontage termine — pages coupees en 2")
except Exception as e:
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
def _toggle_rulers(self, checked):
self._viewer.set_rulers_visible(checked)
def _do_trim_shift(self):
if not self._require_file():
return
@@ -768,18 +795,51 @@ class MainWindow(QMainWindow):
def _do_masking(self):
if not self._require_file():
return
dlg = MaskingDialog(self)
if dlg.exec():
from pypdf import PdfReader
from app.engine.pdf_utils import get_page_size
reader = PdfReader(self._current_file)
cur_page = self._viewer.current_page
page = reader.pages[cur_page]
w, h = get_page_size(page)
self._mask_dlg = MaskingDialog(
page_width_pt=w, page_height_pt=h,
total_pages=len(reader.pages),
current_page=cur_page,
viewer=self._viewer,
parent=self
)
def on_mask_accepted():
if not hasattr(self, '_mask_dlg') or not self._mask_dlg:
return
dlg_ref = self._mask_dlg
self._mask_dlg = None
try:
settings = dlg_ref.get_settings()
except Exception:
return
if not settings.get("rectangles"):
return
self._show_progress()
settings = dlg.get_settings()
output = self._make_temp()
try:
apply_masking(self._current_file, output, **settings)
self._show_result(output)
self._status_label.setText(
f"Masquage applique — {len(settings['rectangles'])} zone(s)"
)
except Exception as e:
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
def on_mask_rejected():
self._mask_dlg = None
self._mask_dlg.accepted.connect(on_mask_accepted)
self._mask_dlg.rejected.connect(on_mask_rejected)
self._mask_dlg.show()
def _do_bleeds(self):
if not self._require_file():
return
@@ -791,6 +851,18 @@ class MainWindow(QMainWindow):
try:
define_bleeds(self._current_file, output, **settings)
self._show_result(output)
# Stocker et afficher l'overlay rouge sur le viewer
from app.engine.pdf_utils import mm_to_pt
self._bleed_settings = {
"top": mm_to_pt(settings["bleed_top"]),
"bottom": mm_to_pt(settings["bleed_bottom"]),
"left": mm_to_pt(settings["bleed_left"]),
"right": mm_to_pt(settings["bleed_right"]),
}
self._viewer.set_bleed_info(self._bleed_settings)
self._status_label.setText(
f"Fonds perdus definis — zone rouge = bleed"
)
except Exception as e:
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
@@ -804,15 +876,20 @@ class MainWindow(QMainWindow):
reader = PdfReader(self._current_file)
writer = PdfWriter()
for page in reader.pages:
if hasattr(page, 'trimbox'):
page.trimbox = page.mediabox
if hasattr(page, 'bleedbox'):
page.bleedbox = page.mediabox
# Remettre trimbox et bleedbox a la taille de la mediabox
page.trimbox.lower_left = page.mediabox.lower_left
page.trimbox.upper_right = page.mediabox.upper_right
page.bleedbox.lower_left = page.mediabox.lower_left
page.bleedbox.upper_right = page.mediabox.upper_right
writer.add_page(page)
output = self._make_temp()
with open(output, "wb") as f:
writer.write(f)
self._show_result(output)
# Supprimer l'overlay rouge
self._bleed_settings = None
self._viewer.set_bleed_info(None)
self._status_label.setText("Fonds perdus supprimes")
except Exception as e:
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
@@ -976,20 +1053,21 @@ class MainWindow(QMainWindow):
"crop_right": settings["crop_right"],
}
# Appliquer le crop depuis l'original
# Appliquer TOUS les crops cumules depuis l'original
self._show_progress()
source_file = self._original_file or self._current_file
output = self._make_temp()
try:
crop_pages(source_file, output, **settings)
crop_pages(source_file, output, per_page_settings=self._crop_settings)
self._result_file = output
self._current_file = output
self._viewer.load_pdf(output)
self._viewer.go_to_page(cur_page)
self._page_panel.load_thumbnails(self._viewer)
self._hide_progress()
total_cropped = len(self._crop_settings)
self._status_label.setText(
f"Recadrage applique — {len(settings['pages'])} page(s)"
f"Recadrage applique — {total_cropped} page(s) recadree(s) au total"
)
QTimer.singleShot(100, self._viewer.zoom_fit)
except Exception as e: