Files
pdf/tests/edits/_verify_build.py
T

96 lines
4.4 KiB
Python

#!/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 "<NOT FOUND>"
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}")