V3.3.0 — Edition contenu, actions CLI, menu contextuel enrichi

- Edition de contenu PDF (Annotations > Editer le contenu) :
  - Onglet Texte : liste le texte detecte, masquer des lignes
  - Onglet Images : liste les images, remplacer ou masquer
- Actions en ligne de commande (--action booklet_a4|booklet_a3|nup_2x2|unbooklet)
- Menu contextuel Windows enrichi :
  - Ouvrir, Livret A4, Livret A3, 2x2 par feuille, Delivretiser
- Bouton Editer dans la barre d'outils droite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 16:33:43 +01:00
parent 202a4fc536
commit 2fcc92dbd9
7 changed files with 324 additions and 7 deletions

View File

@@ -41,7 +41,9 @@ 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.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
from app.dialogs.edit_content_dialog import EditContentDialog
class MainWindow(QMainWindow):
@@ -236,6 +238,8 @@ class MainWindow(QMainWindow):
# Annotations
annot_menu = menubar.addMenu("Annotations")
self._add_action(annot_menu, "Editer le contenu...", None, self._do_edit_content)
annot_menu.addSeparator()
self._add_action(annot_menu, "Ajouter du texte...", None, self._do_add_text)
self._add_action(annot_menu, "Ajouter une image...", None, self._do_add_image)
annot_menu.addSeparator()
@@ -331,6 +335,7 @@ class MainWindow(QMainWindow):
# Section Annotations
rtb.addWidget(self._make_section_label("ANNOTATIONS"))
rtb.addAction("Editer", self._do_edit_content)
rtb.addAction("Texte", self._do_add_text)
rtb.addAction("Image", self._do_add_image)
rtb.addAction("Numeros", self._do_page_numbers)
@@ -812,6 +817,72 @@ class MainWindow(QMainWindow):
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
def _do_edit_content(self):
if not self._require_file():
return
cur = self._viewer.current_page
texts = extract_text_from_page(self._current_file, cur)
images = extract_images_from_page(self._current_file, cur)
dlg = EditContentDialog(text_items=texts, image_items=images, parent=self)
if dlg.exec():
actions = dlg.get_actions()
if not actions:
return
self._show_progress()
from pypdf import PdfReader, PdfWriter
from app.engine.pdf_utils import get_page_size
from reportlab.pdfgen import canvas as rl_canvas
import io
reader = PdfReader(self._current_file)
writer = PdfWriter()
page = reader.pages[cur]
w, h = get_page_size(page)
# Appliquer les masquages de texte
mask_lines = [a["line_index"] for a in actions if a["type"] == "mask_text"]
if mask_lines:
packet = io.BytesIO()
c = rl_canvas.Canvas(packet, pagesize=(w, h))
c.setFillColorRGB(1, 1, 1)
line_h = h / max(len(texts), 1)
for li in mask_lines:
y = h - (li + 1) * line_h
c.rect(0, y, w, line_h, fill=1, stroke=0)
c.showPage()
c.save()
packet.seek(0)
overlay = PdfReader(packet)
page.merge_page(overlay.pages[0])
# Remplacer les images
for a in actions:
if a["type"] == "replace_image" and a.get("new_path"):
from app.engine.extract_content import replace_image_in_page
# On fait un write intermediaire
tmp = self._make_temp()
for ii, pp in enumerate(reader.pages):
writer.add_page(pp)
with open(tmp, "wb") as f:
writer.write(f)
replace_image_in_page(tmp, tmp, cur, a["name"], a["new_path"])
reader = PdfReader(tmp)
writer = PdfWriter()
for pp in reader.pages:
writer.add_page(pp)
output = self._make_temp()
with open(output, "wb") as f:
writer.write(f)
self._show_result(output)
return
for i, p in enumerate(reader.pages):
writer.add_page(p)
output = self._make_temp()
with open(output, "wb") as f:
writer.write(f)
self._show_result(output)
self._status_label.setText("Contenu edite")
def _do_add_text(self):
if not self._require_file():
return