"""The Emergency Kit oracle: read the PDF back the way a person would. Two questions, and the second is the one that matters: 1. Does the text extract? A document whose code cannot be selected or searched is one somebody has to retype by hand from a photograph. 2. **Does the QR inside the rendered page actually scan?** Not the matrix the encoder produced — the page as a reader draws it. Between those two sit the coordinate flip, the module size, the quiet zone and whether the squares land on the same grid. Every one of those can be wrong in a document whose matrix is perfect. PyMuPDF renders, ZXing decodes, pypdf extracts. None of them is ours. Reads {pdf_base64, expect_code, expect_text[]} on stdin, writes a verdict. Needs: pip install --user pymupdf zxing-cpp pypdf pillow numpy """ import base64 import io import json import sys import numpy as np import pymupdf import zxingcpp from PIL import Image from pypdf import PdfReader def check(case): raw = base64.b64decode(case["pdf_base64"]) out = {"name": case.get("name", "")} # 1. Structure and text, through a second reader. reader = PdfReader(io.BytesIO(raw)) out["pages"] = len(reader.pages) text = "\n".join(page.extract_text() or "" for page in reader.pages) out["missing_text"] = [s for s in case.get("expect_text", []) if s not in text] out["code_in_text"] = case["expect_code"] in text.replace("\n", "") # 2. Render at print resolution and scan it, exactly as a phone would. doc = pymupdf.open(stream=raw, filetype="pdf") found = [] for page in doc: pix = page.get_pixmap(dpi=case.get("dpi", 200)) img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples).convert("L") for r in zxingcpp.read_barcodes(np.array(img)): found.append(r.text) out["scanned"] = found out["qr_scans"] = case["expect_code"] in found # Exactly one QR per document: a second one would mean the two secrets had # ended up on one sheet, which is the whole thing these documents are two # of in order to prevent. out["one_qr"] = len(found) == 1 out["ok"] = bool( out["qr_scans"] and out["one_qr"] and out["code_in_text"] and not out["missing_text"] ) return out def main(): cases = json.load(sys.stdin) print(json.dumps([check(c) for c in cases])) if __name__ == "__main__": main()