initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Compare the original extended corpus with independent Poppler rendering.
No pixel metric is interpreted as a calibrated compatibility acceptance score.
"""
from pathlib import Path
import argparse
import hashlib
import json
import os
import subprocess
import sys
from PIL import Image, ImageChops, ImageDraw, ImageStat
ROOT = Path(__file__).resolve().parent.parent
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--build-dir", type=Path, default=ROOT / os.environ.get("DOCVIEW_BUILD_DIR", "build"))
parser.add_argument("--output", type=Path, default=ROOT / "tests/results/pdf-extended")
parser.add_argument("--pdfium-library", type=Path, default=ROOT / ".deps/pdfium/lib/libpdfium.so",
help="Selected worker library to identify in the evidence record.")
args = parser.parse_args()
BUILD = args.build_dir.resolve()
CORPUS = ROOT / "tests/fixtures/pdf/extended"
OUT = args.output.resolve()
OUT.mkdir(parents=True, exist_ok=True)
# The runtime wrapper can carry a private Fontconfig setup. Prefer an explicit
# system installation so Standard-14 fallback uses that installation's fonts.
def poppler_binary(name):
return os.environ.get("DOCVIEW_" + name.upper(), str(Path("/usr/bin") / name) if (Path("/usr/bin") / name).is_file() else name)
PDFTOPPM, PDFTOTEXT, PDFFONTS = (poppler_binary(name) for name in ("pdftoppm", "pdftotext", "pdffonts"))
manifest = json.loads((CORPUS / "manifest.json").read_text())
helper = BUILD / "pdf-worker-evidence"
if not helper.is_file():
raise SystemExit("Build the pdf-worker-evidence target before comparison.")
report = {"reference_status": "Independent Poppler comparison of synthetic source; not a human-approved golden image or full compatibility acceptance",
"platform": sys.platform, "transport": "production sandboxed PDF worker and FontBroker", "worker_sha256": hashlib.sha256((BUILD / "docview-pdf-worker").read_bytes()).hexdigest(), "broker_sha256": hashlib.sha256((BUILD / "libdocview_font_broker.a").read_bytes()).hexdigest(), "resolution_dpi": 144, "pdfium_adapter_sha256": hashlib.sha256((BUILD / "libdocview_pdf.a").read_bytes()).hexdigest(),
"pdfium_library_sha256": hashlib.sha256(args.pdfium_library.resolve().read_bytes()).hexdigest(),
"poppler_version": subprocess.run([PDFTOPPM, "-v"], text=True, capture_output=True).stderr.splitlines()[0],
"poppler_binary": PDFTOPPM, "documents": []}
report["fontconfig_matches"] = {family: subprocess.check_output(["fc-match", family], text=True).strip()
for family in ["Helvetica", "Times-Roman", "Courier"]}
overview = []
for specification in manifest["documents"]:
source = CORPUS / specification["file"]
if hashlib.sha256(source.read_bytes()).hexdigest() != specification["sha256"]:
raise SystemExit("Fixture hash differs from source manifest: " + source.name)
target = OUT / source.stem
(target / "pdfium").mkdir(parents=True, exist_ok=True)
(target / "poppler").mkdir(exist_ok=True)
subprocess.run([str(helper), str(source), str(target / "pdfium")], check=True)
subprocess.run([PDFTOPPM, "-cropbox", "-r", "144", "-png", str(source), str(target / "poppler/page")], check=True)
subprocess.run([PDFTOTEXT, "-layout", str(source), str(target / "poppler/text.txt")], check=True)
fonts = subprocess.check_output([PDFFONTS, str(source)], text=True)
(target / "fonts.txt").write_text(fonts)
doc = {"file": specification["file"], "fixture_sha256": hashlib.sha256(source.read_bytes()).hexdigest(), "pages": []}
for index in range(1, specification["page_count"] + 1):
a = Image.open(target / "pdfium" / f"page-{index}.png").convert("RGB")
b = Image.open(target / "poppler" / f"page-{index}.png").convert("RGB")
same_size = a.size == b.size
page = {"page": index, "pdfium_size": list(a.size), "poppler_size": list(b.size), "dimensions_match": same_size}
expected_size = specification["expected"].get("page_sizes_pt")
if expected_size:
page["expected_size_px"] = [v * 2 for v in expected_size[index - 1]]
elif specification["file"] == "page-geometry.pdf":
geometry = specification["expected"]["pages"][index - 1]
page["expected_size_px"] = [v * 2 for v in geometry["display_size_pt"]]
left, bottom, right, top = geometry["crop_box_pt"]
w, h = right - left, top - bottom
checks = []
for marker in geometry["markers_pdf_coordinates"]:
x, y = marker["x"] - left, marker["y"] - bottom
coordinates = {0: (x, h - y), 90: (y, x), 180: (w - x, y), 270: (h - y, w - x)}[geometry["rotation"]]
point = tuple(int(v * 2) for v in coordinates)
actual_a, actual_b = a.getpixel(point), b.getpixel(point)
expected = marker["rgb"]
checks.append({"point_px": point, "expected_rgb": expected, "pdfium_rgb": actual_a, "poppler_rgb": actual_b,
"both_match_source_color": max(abs(actual_a[k] - expected[k]) for k in range(3)) <= 2 and max(abs(actual_b[k] - expected[k]) for k in range(3)) <= 2})
page["source_geometry_marker_checks"] = checks
page["dimensions_match_source"] = list(a.size) == page.get("expected_size_px") and list(b.size) == page.get("expected_size_px")
if specification["file"] == "graphics-compositing.pdf":
probes = []
for probe in specification["expected"]["alpha_probes"]:
x, y = probe["point_pt"]
point = (x * 2, (760 - y) * 2)
actual_a, actual_b, expected = a.getpixel(point), b.getpixel(point), probe["expected_rgb"]
probes.append({"point_px": point, "region": probe["region"], "expected_rgb": expected,
"pdfium_rgb": actual_a, "poppler_rgb": actual_b,
"both_match_source_color": max(abs(actual_a[k] - expected[k]) for k in range(3)) <= 2 and max(abs(actual_b[k] - expected[k]) for k in range(3)) <= 2})
page["source_alpha_checks"] = probes
if same_size:
difference = ImageChops.difference(a, b)
difference.save(target / f"difference-{index}.png")
histogram = ImageChops.lighter(ImageChops.lighter(difference.getchannel("R"), difference.getchannel("G")), difference.getchannel("B")).histogram()
page["equal_pixels"] = histogram[0]
page["pixels_max_delta_gt_16"] = sum(histogram[17:])
page["mean_absolute_channel_error"] = sum(ImageStat.Stat(difference).mean) / 3
else:
difference = Image.new("RGB", a.size, "#ff8080")
tile = Image.new("RGB", (1200, 490), "#eeeeee"); draw = ImageDraw.Draw(tile)
for column, (title, picture) in enumerate([(source.stem + f" p{index}: PDFium", a), ("Poppler independent renderer", b), ("Absolute RGB difference", difference)]):
draw.text((column * 400 + 10, 10), title, fill="black")
preview = picture.copy(); preview.thumbnail((385, 450)); tile.paste(preview, (column * 400 + (400 - preview.width) // 2, 33))
tile.save(target / f"comparison-{index}.png")
overview.append(tile)
doc["pages"].append(page)
if specification["file"] == "japanese-layout.pdf":
doc["japanese_text_extracted_by_poppler"] = "日本語の文字配置と埋込みフォント" in (target / "poppler/text.txt").read_text()
meta = json.loads((target / "pdfium/metadata.json").read_text())
doc["japanese_search_matches_pdfium"] = len(meta.get("japaneseSearch", {}).get("matches", []))
doc["font_listing"] = fonts
report["documents"].append(doc)
sheet = Image.new("RGB", (1200, len(overview) * 490), "white")
for index, tile in enumerate(overview): sheet.paste(tile, (0, index * 490))
sheet.save(OUT / "overview.png")
(OUT / "comparison.json").write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n")
summary = {"documents": len(report["documents"]), "pages": len(overview),
"all_dimensions_match_source": all(page["dimensions_match_source"] for doc in report["documents"] for page in doc["pages"]),
"all_geometry_markers_match_source": all(check["both_match_source_color"] for doc in report["documents"] for page in doc["pages"] for check in page.get("source_geometry_marker_checks", [])),
"all_alpha_probes_match_source": all(check["both_match_source_color"] for doc in report["documents"] for page in doc["pages"] for check in page.get("source_alpha_checks", [])),
"japanese_text_and_search": all(doc.get("japanese_text_extracted_by_poppler", True) and doc.get("japanese_search_matches_pdfium", 1) > 0 for doc in report["documents"]),
"report": str(OUT / "comparison.json")}
print(json.dumps(summary, indent=2))
if not all(summary[key] for key in ["all_dimensions_match_source", "all_geometry_markers_match_source", "all_alpha_probes_match_source", "japanese_text_and_search"]):
raise SystemExit("A deterministic source assertion failed; see comparison.json")