""" B3.6 visual verification script. Steps: 1. Re-translate the test PDF using a mock provider (cheap, no API cost). 2. Render both the OLD broken PDF and the NEW (B3.6) PDF as PNGs. 3. Inspect drawings: the blue "Avis important" box must still be present in the new PDF but was lost in the old PDF. 4. Inspect title block: it must not overlap the next block ("Version du document..."). 5. Open both PNGs side-by-side so we can eyeball the difference. Usage: python scripts/verify_b3_6_fix.py """ import sys import os from pathlib import Path from unittest.mock import MagicMock # Make repo root importable sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import fitz # PyMuPDF REPO_ROOT = Path(__file__).resolve().parent.parent OLD_PDF = REPO_ROOT / "sample_files" / "test_corpus" / "test_pdf_translated (1).pdf" ORIG_PDF = REPO_ROOT / "sample_files" / "test_corpus" / "test_pdf.pdf" NEW_PDF = REPO_ROOT / "sample_files" / "test_corpus" / "test_pdf_translated_fresh.pdf" RENDER_DIR = REPO_ROOT / "scripts" / "_renders" RENDER_DIR.mkdir(parents=True, exist_ok=True) def _render_pdf_page(pdf_path: Path, page_num: int = 0, out_png: Path = None, zoom: float = 1.5) -> Path: """Render a single PDF page as PNG.""" doc = fitz.open(str(pdf_path)) page = doc[page_num] mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat) if out_png is None: out_png = RENDER_DIR / f"{pdf_path.stem}_p{page_num + 1}.png" pix.save(str(out_png)) doc.close() return out_png def _translate_with_mock(input_path: Path, output_path: Path) -> Path: """Translate using a mock provider that returns the French text we want.""" from translators.pdf_translator import PDFTranslator translator = PDFTranslator() # Mock provider: a fake French translation that's longer than the English original # (this is the realistic case that triggered the overflow bug) def mock_translate(text, target_language, source_language): # Return a slightly longer French version to stress the layout if not text: return text # Replace common patterns with longer French to trigger overflow replacements = { "Technical Specification": "Spécification technique : Office Translator", "Document Version": "Version du document", "Table of Contents": "Table des matières", "1. Introduction": "1. Introduction", "2. Installation and Configuration": "2. Installation et configuration", "3. Translation Engine": "3. Moteur de traduction", "4. Format Preservation": "4. Préservation des formats", "5. Quality Assurance": "5. Assurance qualité", "6. Performance and Scaling": "6. Performances et mise à l'échelle", "7. API Reference": "7. Référence API", "8. Troubleshooting": "8. Résolution des problèmes", "Important Notice": "Avis important", "This is a preliminary document.": "Ceci est un document préliminaire.", "Subject to change without notice.": "Sujet à changement sans préavis.", "For the latest version of this document, visit:": "Pour la dernière version de ce document, visitez :", "http://docs.translator.example.com/v3": "http://docs.translator.example.com/v3", } result = text for eng, fr in replacements.items(): result = result.replace(eng, fr) return result mock = MagicMock() mock.translate_batch = MagicMock(side_effect=lambda texts, tgt, src: [mock_translate(t, tgt, src) for t in texts]) translator.set_provider(mock) translator.translate_file( input_path, output_path, target_language="fr", source_language="en", ) return output_path def _inspect_drawings(pdf_path: Path) -> dict: """Inspect page 1: count blue background drawings (the "Avis important" box).""" doc = fitz.open(str(pdf_path)) page = doc[0] drawings = page.get_drawings() # The "Avis important" box has fill (0.85, 0.92, 1.0) — light blue blue_boxes = [] for d in drawings: fill = d.get("fill") if fill is None: continue # Match light blue (R=0.85, G=0.92, B=1.0) if all(abs(fill[i] - target) < 0.05 for i, target in enumerate([0.85, 0.92, 1.0])): blue_boxes.append(d) doc.close() return { "total_drawings": len(drawings), "blue_boxes": len(blue_boxes), "blue_rects": [(round(d["rect"].x0, 1), round(d["rect"].y0, 1), round(d["rect"].x1, 1), round(d["rect"].y1, 1)) for d in blue_boxes], } def _inspect_title_overlap(pdf_path: Path) -> dict: """Check if the title block bottom overlaps the next block top.""" doc = fitz.open(str(pdf_path)) page = doc[0] text_dict = page.get_text("dict") # Find the title block (largest font on the page) title_block = None next_block = None sorted_blocks = sorted( [b for b in text_dict.get("blocks", []) if b.get("type") == 0], key=lambda b: -( max((s.get("size", 12) for line in b.get("lines", []) for s in line.get("spans", [])), default=12) ) ) if sorted_blocks: title_block = sorted_blocks[0] title_text = " ".join( s.get("text", "") for line in title_block.get("lines", []) for s in line.get("spans", []) ) title_bottom = title_block["bbox"][3] # Find the next block right below the title candidates = [ b for b in text_dict.get("blocks", []) if b.get("type") == 0 and b is not title_block and b["bbox"][1] >= title_bottom - 5 and b["bbox"][1] <= title_bottom + 50 ] if candidates: next_block = min(candidates, key=lambda b: b["bbox"][1]) result = { "title": " ".join( s.get("text", "") for line in (title_block or {}).get("lines", []) for s in line.get("spans", []) )[:60], "title_bbox": title_block["bbox"] if title_block else None, } if next_block: next_text = " ".join( s.get("text", "") for line in next_block.get("lines", []) for s in line.get("spans", []) )[:60] result["next_text"] = next_text result["next_bbox"] = next_block["bbox"] result["overlap_px"] = round(title_block["bbox"][3] - next_block["bbox"][1], 2) result["overlaps"] = result["overlap_px"] > 2 doc.close() return result def main(): print("=" * 60) print("B3.6 VERIFICATION — visual & structural") print("=" * 60) # 1. Translate the original with B3.6 fix print("\n[1] Re-translating test_pdf.pdf with B3.6 fix...") if not ORIG_PDF.exists(): print(f" ERROR: original PDF not found at {ORIG_PDF}") return 1 _translate_with_mock(ORIG_PDF, NEW_PDF) print(f" wrote {NEW_PDF.name}") # 2. Render both PDFs as PNG for visual comparison print("\n[2] Rendering PDFs to PNG...") old_png = _render_pdf_page(OLD_PDF) new_png = _render_pdf_page(NEW_PDF) orig_png = _render_pdf_page(ORIG_PDF) print(f" OLD broken: {old_png.relative_to(REPO_ROOT)}") print(f" NEW (B3.6): {new_png.relative_to(REPO_ROOT)}") print(f" ORIGINAL ref: {orig_png.relative_to(REPO_ROOT)}") # 3. Inspect drawings - blue box preservation print("\n[3] Inspecting drawings (blue 'Avis important' box)...") for label, path in [("OLD", OLD_PDF), ("NEW (B3.6)", NEW_PDF), ("ORIGINAL", ORIG_PDF)]: info = _inspect_drawings(path) verdict = "[OK]" if info["blue_boxes"] >= 1 else "[FAIL]" print(f" {verdict} {label:12} -> {info['blue_boxes']} blue box(es) of " f"{info['total_drawings']} total drawings") # 4. Inspect title overlap print("\n[4] Inspecting title-vs-next-block overlap...") for label, path in [("OLD", OLD_PDF), ("NEW (B3.6)", NEW_PDF)]: info = _inspect_title_overlap(path) if "overlaps" in info: verdict = "[FAIL OVERLAPS]" if info["overlaps"] else "[OK no overlap]" print(f" {verdict} {label:12} -> title={info['title']!r:40} " f"overlap_px={info['overlap_px']:+.1f}") else: print(f" [?] {label:12} -> could not determine (insufficient blocks)") print("\n" + "=" * 60) print("Visual comparison rendered to:") print(f" {old_png}") print(f" {new_png}") print(f" {orig_png}") print("=" * 60) return 0 if __name__ == "__main__": sys.exit(main())