initial commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only link annotation evidence through the production sandboxed worker.
|
||||
|
||||
Poppler is an independent comparison, not an all-features correctness oracle.
|
||||
Run after building pdf-worker-evidence. Requires Pillow and Poppler utils.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import time
|
||||
from PIL import Image, ImageChops, ImageStat
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
def sha(path):
|
||||
with path.open('rb') as source:
|
||||
return hashlib.file_digest(source, 'sha256').hexdigest()
|
||||
|
||||
def stats(a, b):
|
||||
assert a.size == b.size
|
||||
diff = ImageChops.difference(a, b)
|
||||
pixels = list(diff.getdata())
|
||||
return {'size': list(a.size), 'pixels': len(pixels), 'equal_pixels': sum(max(p) == 0 for p in pixels),
|
||||
'pixels_max_delta_gt_16': sum(max(p) > 16 for p in pixels),
|
||||
'mean_absolute_channel_error': sum(ImageStat.Stat(diff).mean) / 3}
|
||||
|
||||
def region(rect, rotation, margin=6):
|
||||
points = []
|
||||
for x in [rect[0], rect[2]]:
|
||||
for y in [rect[1], rect[3]]:
|
||||
x0, y0 = (x - 20) * 2, (450 - y) * 2
|
||||
points.append([(x0, y0), (840 - y0, x0), (1020 - x0, 840 - y0), (y0, 1020 - x0)][rotation // 90])
|
||||
return (min(p[0] for p in points) - margin, min(p[1] for p in points) - margin,
|
||||
max(p[0] for p in points) + margin, max(p[1] for p in points) + margin)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build-dir', type=Path, default=ROOT / 'build')
|
||||
parser.add_argument('--output', type=Path, default=ROOT / 'tests/results/link-borders')
|
||||
args = parser.parse_args()
|
||||
out = args.output.resolve(); out.mkdir(parents=True, exist_ok=True)
|
||||
build = args.build_dir.resolve(); helper = build / 'pdf-worker-evidence'
|
||||
fixture = ROOT / 'tests/fixtures/pdf/link-borders/styles.pdf'
|
||||
before = sha(fixture)
|
||||
start = time.monotonic()
|
||||
subprocess.run([str(helper), str(fixture), str(out / 'worker')], check=True, timeout=60)
|
||||
elapsed = time.monotonic() - start
|
||||
reference = subprocess.run(['/usr/bin/pdftoppm', '-cropbox', '-r', '144', '-png', str(fixture), str(out / 'poppler')], capture_output=True, check=True, timeout=60)
|
||||
(out / 'poppler-stderr.txt').write_bytes(reference.stderr)
|
||||
manifest = json.loads((fixture.parent / 'manifest.json').read_text())['fixtures'][0]
|
||||
common = {'default', 'solid-rgb', 'dashed-border', 'dashed-bs-odd', 'dashed-default', 'underline',
|
||||
'bs-overrides-border', 'zero-width', 'transparent', 'hidden', 'no-view', 'print-flag-visible'}
|
||||
records = []
|
||||
for index, rotation in enumerate([0, 90, 180, 270], 1):
|
||||
a = Image.open(out / f'worker/page-{index}.png').convert('RGB')
|
||||
b = Image.open(out / f'poppler-{index}.png').convert('RGB')
|
||||
diff = ImageChops.difference(a, b); diff.save(out / f'difference-{index}.png')
|
||||
comparisons = []
|
||||
for case in manifest['annotations']:
|
||||
bounds = region(case['rect'], rotation)
|
||||
row = {'name': case['name'], 'reference_supported': case['name'] in common, **stats(a.crop(bounds), b.crop(bounds))}
|
||||
if case['name'] in common:
|
||||
# At 144dpi the grid-aligned borders should match except small
|
||||
# independent rasterizer antialias/color-rounding differences.
|
||||
assert row['mean_absolute_channel_error'] < 3, row
|
||||
comparisons.append(row)
|
||||
records.append({'page': index, 'rotation': rotation, 'whole_page': stats(a, b), 'annotations': comparisons})
|
||||
navigation = ROOT / 'tests/fixtures/pdf/navigation.pdf'
|
||||
navigation_before = sha(navigation)
|
||||
subprocess.run([str(helper), str(navigation), str(out / 'navigation-worker')], check=True, timeout=60)
|
||||
subprocess.run(['/usr/bin/pdftoppm', '-cropbox', '-f', '1', '-singlefile', '-r', '144', '-png', str(navigation), str(out / 'navigation-poppler')], capture_output=True, check=True, timeout=60)
|
||||
a = Image.open(out / 'navigation-worker/page-1.png').convert('RGB')
|
||||
b = Image.open(out / 'navigation-poppler.png').convert('RGB')
|
||||
# The exact region where the original default link border was absent.
|
||||
navigation_comparison = stats(a.crop((18, 368, 282, 400)), b.crop((18, 368, 282, 400)))
|
||||
assert navigation_comparison['mean_absolute_channel_error'] < 3, navigation_comparison
|
||||
assert before == sha(fixture) and navigation_before == sha(navigation)
|
||||
result = {'transport': 'production WorkerProcess, sandboxed PDFWorker and FontBroker; no direct PDFium exporter',
|
||||
'worker_sha256': sha(build / 'docview-pdf-worker'), 'exporter_sha256': sha(helper),
|
||||
'qpdf_version': subprocess.check_output(['qpdf', '--version'], text=True).splitlines()[0],
|
||||
'poppler_version': subprocess.run(['/usr/bin/pdftoppm', '-v'], capture_output=True, text=True).stderr.splitlines()[0],
|
||||
'fixture_sha256': before, 'source_unchanged': True, 'four_page_export_seconds': elapsed,
|
||||
'reference_status': 'independent Poppler comparison; manually inspect images; not a golden-image acceptance gate',
|
||||
'reference_limitations': manifest['reference_limitations'],
|
||||
'known_limitations': ['NoZoom/NoRotate geometry for generated links, saved normal APs, and native Text icons is covered separately by tests/results/pdf-annotation-flags.',
|
||||
'Other annotation types without a supplied AP retain PDFium generation behavior; this corpus does not certify their NoZoom handling.',
|
||||
'Text annotation icon appearance is viewer-specific and is not a link border defect.',
|
||||
'This closes tested standard link border cases, not the entire G-RENDER gate.'],
|
||||
'pages': records, 'navigation_fixture_sha256': navigation_before, 'navigation_default_link_region': navigation_comparison}
|
||||
(out / 'comparison.json').write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(json.dumps({'source_unchanged': True, 'four_page_export_seconds': elapsed,
|
||||
'standard_case_comparisons': len(common) * 4, 'navigation_default_link_region': navigation_comparison}, indent=2))
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
Reference in New Issue
Block a user