"""Compare recorded real live-preview frames with native exported PDFs. Requires pdfplumber, pypdfium2 and Pillow (available in Codex's bundled runtime). Run the cv-readiness browser corpus first; this separate gate fails honestly on material differences and never modifies the application or its PDF output. """ import argparse import json import re from pathlib import Path import pdfplumber import pypdfium2 from PIL import Image, ImageDraw MM_PER_POINT = 25.4 / 72 POINTS_PER_PIXEL = 72 / 96 POSITION_TOLERANCE_MM = 2 PAGE_TOLERANCE_MM = 0.5 FONT_TOLERANCE_PT = 0.5 COLOR_TOLERANCE = 1 / 255 def measure(root: Path, fixture: dict) -> dict: directory = root / fixture["fixture"] settings, preview = fixture["settings"], fixture["measured"] width_mm, height_mm = (215.9, 279.4) if settings.get("pageSize") == "letter" else (210, 297) failures, pdf_tokens, pages, fonts = [], [], [], set() with pdfplumber.open(directory / "export.pdf") as pdf: if len(pdf.pages) != fixture["previewPages"]: failures.append(f"page count: preview {fixture['previewPages']}, PDF {len(pdf.pages)}") for number, page in enumerate(pdf.pages): dimensions = [page.width * MM_PER_POINT, page.height * MM_PER_POINT] if any(abs(actual - expected) > PAGE_TOLERANCE_MM for actual, expected in zip(dimensions, [width_mm, height_mm])): failures.append(f"page {number + 1}: unexpected physical dimensions {dimensions}") text = page.extract_text() or "" if not text.strip(): failures.append(f"page {number + 1}: blank page") if "HIDDEN-MARKER" in text: failures.append(f"page {number + 1}: hidden section leaked") for word in page.extract_words(extra_attrs=["fontname", "size", "non_stroking_color"]): fonts.add(word["fontname"]) token = re.fullmatch(r"[SEBPR]\d{2}", word["text"]) if token: pdf_tokens.append({"token": token[0], "page": number + 1, "x": word["x0"], "y": word["top"], "fontSize": word["size"], "font": word["fontname"], "color": word["non_stroking_color"]}) margin_mm = float(preview["effectiveMargin"].strip().removesuffix("mm")) outside = [c for c in page.chars if c["text"].strip() and (c["top"] * MM_PER_POINT < margin_mm - 1 or c["bottom"] * MM_PER_POINT > height_mm - margin_mm + 1)] if outside: failures.append(f"page {number + 1}: {len(outside)} glyphs outside vertical safe area") pages.append({"page": number + 1, "dimensionsMm": dimensions, "text": text, "glyphBoundsPt": [min((c["x0"] for c in page.chars), default=None), min((c["top"] for c in page.chars), default=None), max((c["x1"] for c in page.chars), default=None), max((c["bottom"] for c in page.chars), default=None)]}) remaining = list(pdf_tokens) comparisons = [] height_px = height_mm * 96 / 25.4 for token in preview["tokens"]: match = next((candidate for candidate in remaining if candidate["token"] == token["token"]), None) if match is None: failures.append(f"missing PDF marker {token['token']}") continue remaining.remove(match) preview_page = int(token["y"] // height_px) + 1 dx = abs(token["x"] * POINTS_PER_PIXEL - match["x"]) * MM_PER_POINT dy = abs((token["y"] % height_px) * POINTS_PER_PIXEL - match["y"]) * MM_PER_POINT font_delta = abs(float(token["fontSize"].removesuffix("px")) * POINTS_PER_PIXEL - match["fontSize"]) preview_color = [int(value) / 255 for value in re.findall(r"\d+", token["color"])[:3]] pdf_color = match["color"] color_delta = max(abs(a - b) for a, b in zip(preview_color, pdf_color)) if isinstance(pdf_color, (list, tuple)) and len(pdf_color) == 3 else None comparisons.append({"token": token["token"], "previewPage": preview_page, "pdfPage": match["page"], "deltaXmm": dx, "deltaYmm": dy, "fontDeltaPt": font_delta, "colorDelta": color_delta}) if preview_page != match["page"]: failures.append(f"{token['token']}: preview page {preview_page}, PDF page {match['page']}") elif max(dx, dy) > POSITION_TOLERANCE_MM: failures.append(f"{token['token']}: position delta x={dx:.2f}mm y={dy:.2f}mm") if font_delta > FONT_TOLERANCE_PT: failures.append(f"{token['token']}: font-size delta {font_delta:.2f}pt") if color_delta is not None and color_delta > COLOR_TOLERANCE: failures.append(f"{token['token']}: RGB component delta {color_delta:.4f}") if remaining: failures.append(f"extra PDF markers: {[word['token'] for word in remaining]}") # Visual evidence uses actual exported bytes, rendered independently of Chromium. document = pypdfium2.PdfDocument(str(directory / "export.pdf")) for index in range(len(document)): pdf_page = document[index] bitmap = pdf_page.render(scale=96 / 72) image = bitmap.to_pil() image.save(directory / f"pdf-{index + 1}.png") preview_path = directory / f"preview-{index + 1}.png" if preview_path.exists(): preview_image = Image.open(preview_path).convert("RGB") side_by_side = Image.new("RGB", (preview_image.width + image.width, max(preview_image.height, image.height) + 28), "#ffffff") side_by_side.paste(preview_image, (0, 28)) side_by_side.paste(image, (preview_image.width, 28)) draw = ImageDraw.Draw(side_by_side) draw.text((8, 6), "Live preview", fill="black") draw.text((preview_image.width + 8, 6), "Native exported PDF", fill="black") side_by_side.save(directory / f"comparison-{index + 1}.png") bitmap.close() pdf_page.close() document.close() return {"fixture": fixture["fixture"], "pass": not failures, "failures": failures, "previewPages": fixture["previewPages"], "pdfPages": len(pages), "fontNames": sorted(fonts), "comparisons": comparisons, "pages": pages} def main(): parser = argparse.ArgumentParser() parser.add_argument("directory", type=Path) args = parser.parse_args() inputs = json.loads((args.directory / "measurements.json").read_text(encoding="utf-8")) results = [measure(args.directory, fixture) for fixture in inputs["fixtures"]] report = {"tolerances": {"pageDimensionMm": PAGE_TOLERANCE_MM, "positionMm": POSITION_TOLERANCE_MM, "fontPt": FONT_TOLERANCE_PT, "rgbComponent": COLOR_TOLERANCE, "pageCountAndMembership": "exact"}, "browserVersion": inputs["browserVersion"], "passed": sum(result["pass"] for result in results), "total": len(results), "fixtures": results} (args.directory / "fidelity-results.json").write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") for result in results: print(f"{'PASS' if result['pass'] else 'FAIL'} {result['fixture']}: preview={result['previewPages']} PDF={result['pdfPages']}; {len(result['failures'])} differences") raise SystemExit(0 if report["passed"] == report["total"] else 1) if __name__ == "__main__": main()