""" Načte layout z layouts.json a aplikuje ho na 2 vstupní PDF soubory. Použití: python 27_ApplyLayout.py [--layout 2PuzzleOnA4] Skript si sám detekuje hranice každého puzzle (ray-cast), spočítá scale z aktuální velikosti vs. cílové velikosti v JSON a rozmístí je. """ import sys import json import argparse import fitz from pathlib import Path LAYOUTS_JSON = Path(__file__).parent.parent / "layouts.json" DEFAULT_LAYOUT = "2PuzzleOnA4" CROP_MARGIN_FALLBACK = 2 def detect_clip(page, crop_margin) -> fitz.Rect: paths = page.get_drawings() y_mid = page.mediabox.height / 2 hit_h = [(p["rect"], p.get("width") or 0) for p in paths if p["rect"].y0 <= y_mid <= p["rect"].y1] if not hit_h: raise ValueError("Ray-cast detekce selhala — zadne kresby na y_mid") rects = [r for r, _ in hit_h] x_left = min(r.x0 for r in rects) x_right = max(r.x1 for r in rects) top_cut = min(r.y0 for r in rects) bot_cut = max(r.y1 for r in rects) lw_l = next((lw for r, lw in hit_h if r.x0 == x_left), 0) lw_r = next((lw for r, lw in hit_h if r.x1 == x_right), 0) return fitz.Rect( x_left - lw_l / 2 - crop_margin, top_cut - crop_margin, x_right + lw_r / 2 + crop_margin, bot_cut + crop_margin, ) def mm_to_pt(mm): return mm / 25.4 * 72 def apply_2_vertical(doc_out, sources, layout): page_w = layout["page"]["width_pt"] page_h = layout["page"]["height_pt"] target_w_pt = mm_to_pt(layout["target_puzzle_width_mm"]) target_h_pt = mm_to_pt(layout["target_puzzle_height_mm"]) crop_margin = layout.get("crop_margin_pt", CROP_MARGIN_FALLBACK) page = doc_out.new_page(width=page_w, height=page_h) clips = [] for doc_src in sources: clip = detect_clip(doc_src[0], crop_margin) clips.append(clip) actual_w_mm = clip.width / 72 * 25.4 actual_h_mm = clip.height / 72 * 25.4 scale_w = target_w_pt / clip.width scale_h = target_h_pt / clip.height print(f" Puzzle: {actual_w_mm:.1f} x {actual_h_mm:.1f} mm -> scale {scale_w:.3f} x {scale_h:.3f}") # Pro každý puzzle spočítej scale individuálně positions = [] for clip in clips: pw = clip.width * (target_w_pt / clip.width) ph = clip.height * (target_h_pt / clip.height) positions.append((pw, ph)) # Vertikální rozmístění — equal gaps (předpokládáme stejnou výšku obou) ph0 = positions[0][1] ph1 = positions[1][1] gap0 = (page_h - ph0 - ph1) / 3 gap1 = gap0 y0 = gap0 y1 = gap0 + ph0 + gap1 for i, (doc_src, clip, (pw, ph)) in enumerate(zip(sources, clips, positions)): x0 = (page_w - pw) / 2 y_pos = y0 if i == 0 else y1 page.show_pdf_page( fitz.Rect(x0, y_pos, x0 + pw, y_pos + ph), doc_src, 0, clip=clip, ) side_mm = ((page_w - positions[0][0]) / 2) / 72 * 25.4 gap_mm = gap0 / 72 * 25.4 print(f" Misto po stranach: {side_mm:.1f} mm | Mezera: {gap_mm:.1f} mm") def main(): parser = argparse.ArgumentParser(description="Aplikuje layout na 2 puzzle PDF") parser.add_argument("pdf1", help="Prvni puzzle PDF") parser.add_argument("pdf2", help="Druhy puzzle PDF") parser.add_argument("vystup", help="Vystupni PDF") parser.add_argument("--layout", default=DEFAULT_LAYOUT, help=f"Nazev layoutu (default: {DEFAULT_LAYOUT})") args = parser.parse_args() if not LAYOUTS_JSON.exists(): print(f"CHYBA: {LAYOUTS_JSON} nenalezen. Spust nejdrive 26_SaveLayout.py.", file=sys.stderr) sys.exit(1) layouts = json.loads(LAYOUTS_JSON.read_text(encoding="utf-8")) if args.layout not in layouts: print(f"CHYBA: layout '{args.layout}' nenalezen v {LAYOUTS_JSON}", file=sys.stderr) print(f"Dostupne layouty: {list(layouts.keys())}", file=sys.stderr) sys.exit(1) layout = layouts[args.layout] print(f"Layout: {args.layout}") print(f"Cilova velikost: {layout['target_puzzle_width_mm']} x {layout['target_puzzle_height_mm']} mm") doc1 = fitz.open(args.pdf1) doc2 = fitz.open(args.pdf2) doc_out = fitz.open() apply_2_vertical(doc_out, [doc1, doc2], layout) doc_out.save(args.vystup) doc1.close() doc2.close() doc_out.close() print(f"Ulozeno: {args.vystup}") if __name__ == "__main__": main()