backup: WIP Windows avant repart propre (GUI core installer splash spec)
This commit is contained in:
@@ -17,6 +17,7 @@ Dépendances : PyMuPDF (pymupdf), Pillow, PyYAML
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
@@ -31,7 +32,12 @@ from PIL import Image, ImageTk
|
||||
import fitz # PyMuPDF
|
||||
import yaml
|
||||
|
||||
APP_TITLE = "PDF Mask Designer (Standalone)"
|
||||
from manual_masking import (
|
||||
DEFAULT_MASK_OUTPUT_DIRNAME,
|
||||
DEFAULT_MASK_PREVIEW_DIRNAME,
|
||||
)
|
||||
|
||||
APP_TITLE = "Éditeur de masques PDF"
|
||||
TEMPLATE_VERSION = 1
|
||||
|
||||
# ----------------------------- Data structures -----------------------------
|
||||
@@ -167,7 +173,16 @@ def apply_template_raster(pdf_in: Path, pdf_out: Path, tpl: Template, dpi: int,
|
||||
# ----------------------------- GUI ------------------------------
|
||||
|
||||
class MaskDesignerApp:
|
||||
def __init__(self, root: tk.Tk):
|
||||
def __init__(
|
||||
self,
|
||||
root: tk.Tk,
|
||||
*,
|
||||
initial_pdf: Optional[Path] = None,
|
||||
initial_template: Optional[Path] = None,
|
||||
templates_dir: Optional[Path] = None,
|
||||
output_dir_name: str = DEFAULT_MASK_OUTPUT_DIRNAME,
|
||||
preview_dir_name: str = DEFAULT_MASK_PREVIEW_DIRNAME,
|
||||
):
|
||||
self.root = root
|
||||
self.root.title(APP_TITLE)
|
||||
self.root.geometry("1280x900")
|
||||
@@ -181,11 +196,18 @@ class MaskDesignerApp:
|
||||
self.template_name = tk.StringVar(value="template_masks")
|
||||
self.status = tk.StringVar(value="Prêt.")
|
||||
self.raster_dpi = tk.IntVar(value=200)
|
||||
self.templates_dir = templates_dir
|
||||
self.output_dir_name = output_dir_name
|
||||
self.preview_dir_name = preview_dir_name
|
||||
|
||||
self.is_drawing = False
|
||||
self.start_xy: Optional[Tuple[int,int]] = None
|
||||
|
||||
self._build_ui()
|
||||
if initial_pdf:
|
||||
self.open_pdf_path(initial_pdf)
|
||||
if initial_template:
|
||||
self.load_template_path(initial_template)
|
||||
|
||||
# UI layout
|
||||
def _build_ui(self):
|
||||
@@ -228,14 +250,17 @@ class MaskDesignerApp:
|
||||
def open_pdf(self):
|
||||
path = filedialog.askopenfilename(filetypes=[("PDF", "*.pdf")])
|
||||
if not path: return
|
||||
self.open_pdf_path(Path(path))
|
||||
|
||||
def open_pdf_path(self, path: Path):
|
||||
try:
|
||||
self.doc = fitz.open(path)
|
||||
self.doc = fitz.open(str(path))
|
||||
self.doc_path = Path(path)
|
||||
self.curr_page = 0
|
||||
self.masks.clear()
|
||||
self.template_name.set(self.doc_path.stem + "_template")
|
||||
self.refresh()
|
||||
self.status.set(f"PDF ouvert : {Path(path).name} — {len(self.doc)} page(s)")
|
||||
self.status.set(f"PDF ouvert : {self.doc_path.name} — {len(self.doc)} page(s)")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Erreur", f"Impossible d'ouvrir le PDF : {e}")
|
||||
|
||||
@@ -244,7 +269,7 @@ class MaskDesignerApp:
|
||||
img = page_pix(self.doc, self.curr_page, self.zoom)
|
||||
# overlay current page masks
|
||||
rects = self.masks.get(self.curr_page, [])
|
||||
img_o = draw_overlay(img, rects, 1.0, self.curr_page)
|
||||
img_o = draw_overlay(img, rects, self.zoom, self.curr_page)
|
||||
self.curr_image = img_o
|
||||
self.tk_image = ImageTk.PhotoImage(img_o)
|
||||
self.canvas.delete("all")
|
||||
@@ -269,19 +294,25 @@ class MaskDesignerApp:
|
||||
def on_down(self, ev):
|
||||
if not self.doc: return
|
||||
self.is_drawing = True
|
||||
self.start_xy = (ev.x, ev.y)
|
||||
self._preview_rect = self.canvas.create_rectangle(ev.x, ev.y, ev.x, ev.y, outline="#000", width=2)
|
||||
x = self.canvas.canvasx(ev.x)
|
||||
y = self.canvas.canvasy(ev.y)
|
||||
self.start_xy = (x, y)
|
||||
self._preview_rect = self.canvas.create_rectangle(x, y, x, y, outline="#000", width=2)
|
||||
|
||||
def on_drag(self, ev):
|
||||
if not self.doc or not self.is_drawing: return
|
||||
sx, sy = self.start_xy
|
||||
self.canvas.coords(self._preview_rect, sx, sy, ev.x, ev.y)
|
||||
x = self.canvas.canvasx(ev.x)
|
||||
y = self.canvas.canvasy(ev.y)
|
||||
self.canvas.coords(self._preview_rect, sx, sy, x, y)
|
||||
|
||||
def on_up(self, ev):
|
||||
if not self.doc or not self.is_drawing: return
|
||||
self.is_drawing = False
|
||||
sx, sy = self.start_xy
|
||||
x0, y0, x1, y1 = rect_norm(sx, sy, ev.x, ev.y)
|
||||
x = self.canvas.canvasx(ev.x)
|
||||
y = self.canvas.canvasy(ev.y)
|
||||
x0, y0, x1, y1 = rect_norm(sx, sy, x, y)
|
||||
# convert screen px to PDF points
|
||||
page = self.doc[self.curr_page]
|
||||
# we rendered with zoom, but here current image is at display resolution (zoom applied in page_pix)
|
||||
@@ -311,9 +342,12 @@ class MaskDesignerApp:
|
||||
tpl = self._current_template()
|
||||
except Exception as e:
|
||||
messagebox.showwarning("Info", str(e)); return
|
||||
path = filedialog.asksaveasfilename(defaultextension=".yml",
|
||||
filetypes=[("YAML", "*.yml *.yaml"), ("JSON", "*.json")],
|
||||
initialfile=f"{tpl.name}.yml")
|
||||
path = filedialog.asksaveasfilename(
|
||||
defaultextension=".yml",
|
||||
filetypes=[("YAML", "*.yml *.yaml"), ("JSON", "*.json")],
|
||||
initialdir=str(self._template_initialdir()),
|
||||
initialfile=f"{tpl.name}.yml",
|
||||
)
|
||||
if not path: return
|
||||
p = Path(path)
|
||||
try:
|
||||
@@ -326,8 +360,14 @@ class MaskDesignerApp:
|
||||
messagebox.showerror("Erreur", f"Impossible d'écrire le template : {e}")
|
||||
|
||||
def load_template(self):
|
||||
path = filedialog.askopenfilename(filetypes=[("YAML/JSON", "*.yml *.yaml *.json")])
|
||||
path = filedialog.askopenfilename(
|
||||
filetypes=[("YAML/JSON", "*.yml *.yaml *.json")],
|
||||
initialdir=str(self._template_initialdir()),
|
||||
)
|
||||
if not path: return
|
||||
self.load_template_path(Path(path))
|
||||
|
||||
def load_template_path(self, path: Path):
|
||||
p = Path(path)
|
||||
try:
|
||||
if p.suffix.lower() in (".yml", ".yaml"):
|
||||
@@ -351,6 +391,14 @@ class MaskDesignerApp:
|
||||
self.refresh()
|
||||
self.status.set(f"Masques de la page {self.curr_page+1} supprimés.")
|
||||
|
||||
def _template_initialdir(self) -> Path:
|
||||
if self.templates_dir is not None:
|
||||
self.templates_dir.mkdir(parents=True, exist_ok=True)
|
||||
return self.templates_dir
|
||||
if self.doc_path is not None:
|
||||
return self.doc_path.parent
|
||||
return Path.cwd()
|
||||
|
||||
# Preview / Apply
|
||||
def _build_template_from_state(self) -> Optional[Template]:
|
||||
if not self.doc:
|
||||
@@ -365,7 +413,7 @@ class MaskDesignerApp:
|
||||
if not samp: return
|
||||
for i, s in enumerate(samp[:2], start=1):
|
||||
pdf_in = Path(s)
|
||||
out_dir = pdf_in.parent / "masked_preview"
|
||||
out_dir = pdf_in.parent / self.preview_dir_name
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
pdf_out = out_dir / f"{pdf_in.stem}.preview_vector.pdf"
|
||||
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
|
||||
@@ -373,7 +421,10 @@ class MaskDesignerApp:
|
||||
apply_template_vector(pdf_in, pdf_out, tpl, audit)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Erreur", f"Prévisualisation vectorielle échouée sur {pdf_in.name} : {e}")
|
||||
messagebox.showinfo("Prévisualisation", "Terminé (vectoriel). Ouvrez le dossier 'masked_preview'.")
|
||||
messagebox.showinfo(
|
||||
"Prévisualisation",
|
||||
f"Terminé (vectoriel). Ouvrez le dossier '{self.preview_dir_name}'.",
|
||||
)
|
||||
|
||||
def preview_raster(self):
|
||||
tpl = self._build_template_from_state()
|
||||
@@ -383,7 +434,7 @@ class MaskDesignerApp:
|
||||
dpi = int(self.raster_dpi.get())
|
||||
for i, s in enumerate(samp[:2], start=1):
|
||||
pdf_in = Path(s)
|
||||
out_dir = pdf_in.parent / "masked_preview"
|
||||
out_dir = pdf_in.parent / self.preview_dir_name
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
pdf_out = out_dir / f"{pdf_in.stem}.preview_raster.pdf"
|
||||
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
|
||||
@@ -391,7 +442,10 @@ class MaskDesignerApp:
|
||||
apply_template_raster(pdf_in, pdf_out, tpl, dpi, audit)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Erreur", f"Prévisualisation raster échouée sur {pdf_in.name} : {e}")
|
||||
messagebox.showinfo("Prévisualisation", "Terminé (raster). Ouvrez le dossier 'masked_preview'.")
|
||||
messagebox.showinfo(
|
||||
"Prévisualisation",
|
||||
f"Terminé (raster). Ouvrez le dossier '{self.preview_dir_name}'.",
|
||||
)
|
||||
|
||||
def apply_vector_batch(self):
|
||||
tpl = self._build_template_from_state()
|
||||
@@ -400,7 +454,7 @@ class MaskDesignerApp:
|
||||
if not files: return
|
||||
for s in files:
|
||||
pdf_in = Path(s)
|
||||
out_dir = pdf_in.parent / "masked"
|
||||
out_dir = pdf_in.parent / self.output_dir_name
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
pdf_out = out_dir / f"{pdf_in.stem}.masked_vector.pdf"
|
||||
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
|
||||
@@ -418,7 +472,7 @@ class MaskDesignerApp:
|
||||
dpi = int(self.raster_dpi.get())
|
||||
for s in files:
|
||||
pdf_in = Path(s)
|
||||
out_dir = pdf_in.parent / "masked"
|
||||
out_dir = pdf_in.parent / self.output_dir_name
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
pdf_out = out_dir / f"{pdf_in.stem}.masked_raster.pdf"
|
||||
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
|
||||
@@ -430,9 +484,27 @@ class MaskDesignerApp:
|
||||
|
||||
# ----------------------------- Main ------------------------------
|
||||
|
||||
def main():
|
||||
def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Editeur de masques PDF reutilisables")
|
||||
parser.add_argument("--pdf", type=Path, help="PDF de reference a ouvrir au demarrage")
|
||||
parser.add_argument("--template", type=Path, help="Template YAML/JSON a charger au demarrage")
|
||||
parser.add_argument("--templates-dir", type=Path, help="Dossier par defaut pour sauver/charger les templates")
|
||||
parser.add_argument("--output-dir-name", default=DEFAULT_MASK_OUTPUT_DIRNAME, help="Nom du dossier de sortie pour l'application des masques")
|
||||
parser.add_argument("--preview-dir-name", default=DEFAULT_MASK_PREVIEW_DIRNAME, help="Nom du dossier de sortie pour les previsualisations")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Optional[List[str]] = None):
|
||||
args = build_arg_parser().parse_args(argv)
|
||||
root = tk.Tk()
|
||||
app = MaskDesignerApp(root)
|
||||
app = MaskDesignerApp(
|
||||
root,
|
||||
initial_pdf=args.pdf,
|
||||
initial_template=args.template,
|
||||
templates_dir=args.templates_dir,
|
||||
output_dir_name=args.output_dir_name,
|
||||
preview_dir_name=args.preview_dir_name,
|
||||
)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user