""" B3.8 visual verification on a REAL multi-column layout. Generates a 2-column journal-style PDF (3 paragraphs in each column, each paragraph = 3 lines), then translates it with B3.8 enabled. Without B3.8, the next_block_y of a left-column block would point to the right-column block at the same y (the closest in y-order), which is wrong — the smart-fit would never let left-column text expand down into the left-column space below it. """ import sys sys.path.insert(0, '.') import fitz from pathlib import Path from unittest.mock import MagicMock REPO_ROOT = Path('.').resolve() OUT_DIR = REPO_ROOT / 'scripts' / '_renders_b3_8' OUT_DIR.mkdir(parents=True, exist_ok=True) def build_2col_journal_pdf(path: Path) -> None: """Build a 2-column journal-style PDF.""" doc = fitz.open() page = doc.new_page(width=612, height=792) # Centered title title_rect = fitz.Rect(72, 60, 540, 100) page.insert_textbox(title_rect, "Article Title", fontsize=20) # Author under title page.insert_text((250, 120), "By John Doe", fontsize=10) # Left column: 3 paragraphs left_x0 = 72 left_x1 = 300 paras_left = [ "This is the first paragraph of the left column. It has multiple lines that span across the column width.", "Second paragraph of the left column. This text continues to fill the left side of the page.", "Third and final paragraph on the left side. The text wraps to fill the available space.", ] y = 170 for p in paras_left: rect = fitz.Rect(left_x0, y, left_x1, y + 80) page.insert_textbox(rect, p, fontsize=10) y += 100 # Right column: 3 paragraphs (same y positions as left, so the # "wrong" next-block mapping would matter here) right_x0 = 312 right_x1 = 540 paras_right = [ "Right column, first paragraph. This should stay in the right column and not be affected by left.", "Right column, second paragraph. Independent of the left column's translation length.", "Right column final paragraph. Verifies column-aware layout.", ] y = 170 for p in paras_right: rect = fitz.Rect(right_x0, y, right_x1, y + 80) page.insert_textbox(rect, p, fontsize=10) y += 100 doc.save(str(path)) doc.close() def translate_with_mock(input_path: Path, output_path: Path) -> None: """Translate using a mock that returns the same text (identity).""" from translators.pdf_translator import PDFTranslator translator = PDFTranslator() def _flex(*args, **kwargs): if args and hasattr(args[0], '__iter__') and not isinstance(args[0], str): first = args[0][0] if len(args[0]) else None if first is not None and hasattr(first, 'text'): class _R: def __init__(self, t): self.translated_text = t return [_R(r.text) for r in args[0]] return list(args[0]) if args else [] mock = MagicMock() mock.__class__.__name__ = "MagicMock" mock.translate_batch = MagicMock(side_effect=_flex) translator.set_provider(mock) translator.translate_file(input_path, output_path, target_language='fr', source_language='en') def inspect_columns(pdf_path: Path) -> dict: """Count blocks per column and check no cross-column overlap.""" doc = fitz.open(str(pdf_path)) page = doc[0] blocks = [b for b in page.get_text("dict").get("blocks", []) if b.get("type") == 0] left_blocks = [b for b in blocks if b["bbox"][0] < 200] right_blocks = [b for b in blocks if b["bbox"][0] >= 200] doc.close() return { "total": len(blocks), "left": len(left_blocks), "right": len(right_blocks), } def render(pdf_path: Path, out_png: Path) -> None: doc = fitz.open(str(pdf_path)) pix = doc[0].get_pixmap(matrix=fitz.Matrix(1.5, 1.5)) pix.save(str(out_png)) doc.close() def main(): print("B3.8 multi-column verification") print("=" * 60) in_pdf = OUT_DIR / 'journal_in.pdf' out_pdf = OUT_DIR / 'journal_out.pdf' in_png = OUT_DIR / 'journal_in.png' out_png = OUT_DIR / 'journal_out.png' print(f"[1] Building 2-column journal PDF -> {in_pdf.name}") build_2col_journal_pdf(in_pdf) print(f"[2] Translating with B3.8 enabled -> {out_pdf.name}") translate_with_mock(in_pdf, out_pdf) print(f"[3] Inspecting input column distribution...") in_info = inspect_columns(in_pdf) print(f" input : total={in_info['total']} left={in_info['left']} right={in_info['right']}") print(f"[4] Inspecting output column distribution...") out_info = inspect_columns(out_pdf) print(f" output : total={out_info['total']} left={out_info['left']} right={out_info['right']}") print(f"[5] Rendering both to PNG...") render(in_pdf, in_png) render(out_pdf, out_png) print(f" input : {in_png}") print(f" output : {out_png}") print("=" * 60) return 0 if __name__ == "__main__": sys.exit(main())