#!/usr/bin/env python3 """Verify the FRESH build-dir .pyd (the gateway copy is locked by the running gateway) without touching the gateway. Importing pdfengine from the build lib FIRST caches it in sys.modules, so when _reflow_repro imports pdfengine it reuses this fresh one. Runs the reflow regression gate + a Raw Text (StreamEditor) smoke for the new TJ-positioning path. Run: gateway/.venv/Scripts/python.exe tests/edits/_verify_build.py """ from __future__ import annotations import sys from pathlib import Path BUILD_LIB = r"C:\Users\furqa\pdfeng-build\win-local-pdfium\lib" sys.path.insert(0, BUILD_LIB) import pdfengine # noqa: E402 -- FRESH build, cached in sys.modules before the harness imports it ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(Path(__file__).resolve().parent)) import _reflow_repro as H # noqa: E402 -- reuses the cached fresh pdfengine print("pdfengine loaded from:", pdfengine.__file__) assert BUILD_LIB.lower() in pdfengine.__file__.lower(), "NOT the fresh build .pyd!" PDF = ROOT / "Mo-Faishal-Qureshi.pdf" def section(t): H.report.write(f"\n########## {t} ##########\n") # ---- 1. Reflow regression: multi-edit survival + no-op overlay diff (left/justify) ---- section("REFLOW stress (multi-edit survival)") H.stress() section("REFLOW no-op overlay (regression: left + justify paragraphs must stay ~baseline)") H.overlay(0, "Java Backend Developer") # flowing left/justify body H.overlay(0, "Java Backend Developer", use_lines=True) # ---- 2. Center alignment: reflow a centered heading with align=center; assert no crash + survives ---- section("CENTER align smoke") try: doc = pdfengine.PdfDocument.load_from_memory(PDF.read_bytes(), "") m = doc.get_page(0).extract_document_model() i, p = H.find_para(m, "Software Engineer") if p is None: H.log(" 'Software Engineer' heading not found (skip center smoke)") else: op = H.build_reflow_op(p, 0) op["data"]["align"] = "center" # widen the column to the page content box so centering is visible op["data"]["columnLeft"] = 40.0 op["data"]["columnRight"] = m.width - 40.0 doc.apply_edits(__import__("json").dumps({"version": "1.0", "operations": [op]})) m2 = pdfengine.PdfDocument.load_from_memory(doc.save_full(), "").get_page(0).extract_document_model() _, pc = H.find_para(m2, "Software") txt = H.para_text(pc) if pc else "" H.log(f" center reflow OK; text survived = {('Software' in txt and 'Engineer' in txt)} -> {txt[:60]!r}") except Exception as exc: H.log(f" CENTER smoke FAILED: {exc}") # ---- 3. Raw Text StreamEditor: extract + same-length replace (P1a TJ preservation) ---- section("RAW TEXT StreamEditor smoke (P1a)") try: import tempfile, os tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf"); tmp.write(PDF.read_bytes()); tmp.close() ed = pdfengine.StreamEditor(tmp.name) objs = ed.extract_text_objects(0) H.log(f" extract_text_objects(0) -> {len(objs)} objects") # pick the first object with >=3 ASCII chars; do a SAME-LENGTH edit (exercises TJ redistribution) target = None for idx, o in enumerate(objs): t = o["text"].decode("latin-1") if isinstance(o["text"], bytes) else o["text"] if len(t) >= 3 and t.strip() and all(32 <= ord(c) < 127 for c in t): target = (idx, t); break if target is None: H.log(" no suitable ASCII object found (skip)") else: idx, t = target # same-length swap: reverse-safe -> replace each alnum char's case-insensitive 'a'->'e' won't change len new = t[:-1] + ("X" if t[-1] != "X" else "Y") # same length, last char changed out = tmp.name + ".out.pdf" ok = ed.replace_text_object(0, idx, new.encode("latin-1"), out) H.log(f" replace obj#{idx} {t!r} -> {new!r} : success={ok}") if ok and os.path.exists(out): ed2 = pdfengine.StreamEditor(out) objs2 = ed2.extract_text_objects(0) t2 = objs2[idx]["text"]; t2 = t2.decode("latin-1") if isinstance(t2, bytes) else t2 H.log(f" re-extract obj#{idx} = {t2!r} (expected {new!r}, match={t2 == new})") os.remove(out) os.remove(tmp.name) except Exception as exc: H.log(f" RAW TEXT smoke FAILED: {exc}") OUT = Path(__file__).resolve().parent / "_verify_build.out.txt" OUT.write_text(H.report.getvalue(), encoding="utf-8") print(f"wrote {OUT}")