import sys
W = 920
PAD = 40
LABEL_W = 74 VALUE_ROOM = 128 BAR_H = 14
ROW_PITCH = 26
PANEL_TITLE_H = 30
PANEL_GAP = 26
BAR_X = PAD + LABEL_W
PLOT_W = W - BAR_X - PAD - VALUE_ROOM
PANELS = [
dict(title="Page count", sub="12,732 pages · 200 MB", cap=22.0, rows=[
("pdq", 6.1, "6.1 ms", None),
("qpdf", 14.5, "14.5 ms", "2.4×"),
("Poppler", 20.5, "20.5 ms", "3.4×"),
("MuPDF", 1288, "1.29 s", "211×"),
]),
dict(title="Split into single pages", sub="12,732 pages · 200 MB", cap=4940.0, rows=[
("pdq", 1050, "1.05 s", None),
("qpdf", 4940, "4.94 s", "4.7×"),
("Poppler", None, "× timeout — 6 of 12,732 files after 120 s", None),
]),
dict(title="Split into single pages", sub="2,642 pages · 26 MB", cap=2000.0, rows=[
("pdq", 280, "280 ms", None),
("qpdf", None, "× timeout — 1,295 of 2,642 files after 120 s", None),
("Poppler", None, "× timeout — 113 of 2,642 files after 120 s", None),
]),
dict(title="Extract pages 5000–5100", sub="from 12,732 pages", cap=356.0, rows=[
("pdq", 37.3, "37 ms", None),
("MuPDF", 60.0, "60 ms", "1.6×"),
("qpdf", 355.4, "355 ms", "9.5×"),
]),
dict(title="Full rewrite", sub="2,642 pages", cap=136.3, rows=[
("pdq", 86.6, "87 ms", None),
("MuPDF", 115.9, "116 ms", "1.3×"),
("qpdf", 136.3, "136 ms", "1.6×"),
]),
dict(title="Full rewrite", sub="12,732 pages · pdq peak heap 43 MB", cap=746.8, rows=[
("MuPDF", 506.7, "507 ms", None),
("pdq", 618.7, "619 ms", "1.2×"),
("qpdf", 746.8, "747 ms", "1.5×"),
]),
dict(title="Merge", sub="12,732 + 2,642 pages", cap=9450.0, rows=[
("pdq", 830, "0.83 s", None),
("qpdf", 1421, "1.42 s", "1.7×"),
("MuPDF", 9446, "9.45 s", "11.4×"),
("Poppler", 24820, "24.8 s", "30×"),
]),
]
HEADER_H = 96
FOOTER_H = 126
FOOT_NOTE = ("Constrained server (4 GB · 2 vCPU container): pdq sustains "
"2,120 mixed ops/min · qpdf 532 · zero OOM kills · "
"scripts/throughput_bench.py")
def panel_height(p):
return PANEL_TITLE_H + len(p["rows"]) * ROW_PITCH
H = PAD + HEADER_H + sum(panel_height(p) for p in PANELS) \
+ PANEL_GAP * (len(PANELS) - 1) + FOOTER_H
def bar_path(x, y, w, h, r=4):
w = max(w, 3.0)
r = min(r, w / 2, h / 2)
return (f"M{x:.1f},{y:.1f} h{w - r:.1f} a{r},{r} 0 0 1 {r},{r} "
f"v{h - 2 * r:.1f} a{r},{r} 0 0 1 -{r},{r} h-{w - r:.1f} Z")
out = []
out.append(
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="pdq benchmark results against qpdf, MuPDF and Poppler">')
out.append(f"""<title>pdq benchmarks — real-world PDFs</title>
<style>
text {{ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }}
.surface {{ fill: #fcfcfb; }}
.border {{ stroke: rgba(11,11,11,0.10); }}
.ink1 {{ fill: #0b0b0b; }}
.ink2 {{ fill: #52514e; }}
.muted {{ fill: #898781; }}
.accent {{ fill: #2a78d6; }}
.gray {{ fill: #c3c2b7; }}
.inbar {{ fill: #52514e; }}
.hatchline {{ stroke: #b5b3ab; }}
.hatchbg {{ fill: #f0efec; }}
.crit {{ fill: #d03b3b; }}
.break {{ stroke: #fcfcfb; }}
.rule {{ stroke: #e1e0d9; }}
@media (prefers-color-scheme: dark) {{
.surface {{ fill: #1a1a19; }}
.border {{ stroke: rgba(255,255,255,0.10); }}
.ink1 {{ fill: #ffffff; }}
.ink2 {{ fill: #c3c2b7; }}
.muted {{ fill: #898781; }}
.accent {{ fill: #3987e5; }}
.gray {{ fill: #52514e; }}
.inbar {{ fill: #c3c2b7; }}
.hatchline {{ stroke: #52514e; }}
.hatchbg {{ fill: #242423; }}
.crit {{ fill: #e66767; }}
.break {{ stroke: #1a1a19; }}
.rule {{ stroke: #2c2c2a; }}
}}
</style>
<defs>
<pattern id="hatch" patternUnits="userSpaceOnUse" width="7" height="7" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="7" stroke-width="1.6" class="hatchline"/>
</pattern>
</defs>""")
out.append(f'<rect class="surface border" x="0.5" y="0.5" width="{W-1}" height="{H-1}" rx="14" stroke-width="1"/>')
y = PAD + 6
out.append(f'<text class="ink1" x="{PAD}" y="{y + 14}" font-size="19" font-weight="650">pdq benchmarks</text>')
out.append(f'<text class="ink2" x="{PAD}" y="{y + 40}" font-size="13">'
f'Real-world court PDFs · pdq <tspan class="muted">vs</tspan> qpdf, MuPDF (mutool) and Poppler · lower is better</text>')
sw = 9
out.append(f'<rect class="accent" x="{W - PAD - 52}" y="{y + 6}" width="{sw}" height="{sw}" rx="2"/>')
out.append(f'<text class="ink2" x="{W - PAD - 38}" y="{y + 14}" font-size="12">pdq</text>')
y = PAD + HEADER_H
for i, p in enumerate(PANELS):
out.append(f'<text class="ink1" x="{PAD}" y="{y + 12}" font-size="13.5" font-weight="600">{p["title"]}'
f'<tspan class="muted" font-weight="400" dx="10">{p["sub"]}</tspan></text>')
by = y + PANEL_TITLE_H
for tool, ms, label, mult in p["rows"]:
cy = by + BAR_H / 2
out.append(f'<text class="ink2" x="{BAR_X - 12}" y="{cy + 4}" font-size="12" text-anchor="end">{tool}</text>')
if ms is None:
bw = PLOT_W + VALUE_ROOM - 12
out.append(f'<rect class="hatchbg" x="{BAR_X}" y="{by}" width="{bw:.1f}" height="{BAR_H}" rx="4"/>')
out.append(f'<rect fill="url(#hatch)" x="{BAR_X}" y="{by}" width="{bw:.1f}" height="{BAR_H}" rx="4"/>')
out.append(f'<text class="ink2" x="{BAR_X + 10}" y="{cy + 4}" font-size="11.5">{label}</text>')
else:
frac = ms / p["cap"]
if frac <= 1.001:
bw = frac * PLOT_W
cls = "accent" if tool == "pdq" else "gray"
out.append(f'<path class="{cls}" d="{bar_path(BAR_X, by, bw, BAR_H)}"/>')
weight = "650" if mult is None else "400"
cls_v = "ink1" if mult is None else "ink2"
tip = f'<text class="{cls_v}" x="{BAR_X + max(bw,3) + 10}" y="{cy + 4}" font-size="12" font-weight="{weight}">{label}'
if mult:
tip += f'<tspan class="muted" font-weight="400" dx="5">·</tspan><tspan class="muted" font-weight="400" dx="5">{mult}</tspan>'
tip += '</text>'
out.append(tip)
else:
bw = PLOT_W + VALUE_ROOM - 12
out.append(f'<path class="gray" d="{bar_path(BAR_X, by, bw, BAR_H)}"/>')
bx = BAR_X + bw - 46
for dx in (0, 7):
out.append(f'<line class="break" x1="{bx+dx}" y1="{by-2}" x2="{bx+dx-6}" y2="{by+BAR_H+2}" stroke-width="3.5"/>')
tip = f'<text class="inbar" x="{BAR_X + bw - 58}" y="{cy + 4}" font-size="11.5" text-anchor="end" font-weight="500">{label}'
if mult:
tip += f'<tspan font-weight="400" dx="5">·</tspan><tspan font-weight="400" dx="5">{mult}</tspan>'
tip += '</text>'
out.append(tip)
by += ROW_PITCH
y = by + (PANEL_GAP if i < len(PANELS) - 1 else 0)
fy = H - FOOTER_H + 10
out.append(f'<line class="rule" x1="{PAD}" y1="{fy}" x2="{W - PAD}" y2="{fy}" stroke-width="1"/>')
FOOT_COLS = [
("Method", ["hyperfine mean · warmup 1, 5 runs", "count & rewrites: 10 runs · 120 s timeout"]),
("Validation", ["every output checked with qpdf --check", "bars scaled per scenario · axis breaks marked"]),
("Environment", ["Apple M4 Max · qpdf 12.3.2 · MuPDF 1.28.0", "Poppler 26.02 · macOS · 2026-07-04"]),
]
col_w = (W - 2 * PAD) / len(FOOT_COLS)
for ci, (label, lines) in enumerate(FOOT_COLS):
cx = PAD + ci * col_w
out.append(f'<text class="muted" x="{cx:.0f}" y="{fy + 24}" font-size="9.5" font-weight="600" '
f'letter-spacing="0.9" style="text-transform:uppercase">{label.upper()}</text>')
for li, line in enumerate(lines):
out.append(f'<text class="ink2" x="{cx:.0f}" y="{fy + 44 + li * 17}" font-size="11">{line}</text>')
out.append(f'<text class="muted" x="{PAD}" y="{fy + 88}" font-size="11">{FOOT_NOTE}</text>')
out.append('</svg>')
svg = "\n".join(out)
path = sys.argv[1] if len(sys.argv) > 1 else "benchmark.svg"
with open(path, "w") as f:
f.write(svg)
print(f"{path}: {len(svg)} bytes, {W}x{H}")