Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
68 lines
2.8 KiB
Python
Executable File
68 lines
2.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Crop the item table out of each probe PDF and stack the crops under a Pillow/libraqm reference line.
|
|
|
|
Usage: compare.py <pdf-dir> <png-dir> <font.ttf>
|
|
|
|
The reference is the same probe text drawn by Pillow's RAQM layout engine (libraqm -> HarfBuzz) with the same
|
|
Noto Sans Devanagari file: an independent shaper, there to show what "correct" looks like.
|
|
"""
|
|
import re, subprocess, sys
|
|
from pathlib import Path
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
pdfdir, pngdir, fontfile = Path(sys.argv[1]), Path(sys.argv[2]), sys.argv[3]
|
|
PROBE = "क्षत्रिय ज्ञान श्री द्वार कर्म कि प्रिय"
|
|
DPI = 200
|
|
ENGINES = [
|
|
("openpdf", "OpenPDF 3.0.5, defaults"),
|
|
("itext", "iText Core 9.8.0, defaults"),
|
|
("flyingsaucer", "Flying Saucer 10.7.0, defaults"),
|
|
("openpdf-fop", "OpenPDF + Apache FOP 2.11 on the classpath"),
|
|
("openpdf-jdk-actualtext", "OpenPDF GlyphLayoutManager (JDK shaper)"),
|
|
("flyingsaucer-shaped", "Flying Saucer + OpenPDF LayoutProcessor"),
|
|
]
|
|
|
|
def box(pdf, word):
|
|
out = subprocess.run(["pdftotext", "-bbox", str(pdf), "-"], capture_output=True, text=True).stdout
|
|
hits = re.findall(r'<word xMin="([\d.]+)" yMin="([\d.]+)" xMax="([\d.]+)" yMax="([\d.]+)">%s[^<]*</word>' % re.escape(word), out)
|
|
return [tuple(map(float, h)) for h in hits]
|
|
|
|
font_big = ImageFont.truetype(fontfile, 40, layout_engine=ImageFont.Layout.RAQM)
|
|
font_lbl = ImageFont.truetype(fontfile, 22, layout_engine=ImageFont.Layout.RAQM)
|
|
rows = []
|
|
|
|
ref = Image.new("RGB", (1500, 120), "white")
|
|
d = ImageDraw.Draw(ref)
|
|
d.text((10, 6), "reference: Pillow + libraqm (HarfBuzz), same font", font=font_lbl, fill="#555")
|
|
d.text((10, 44), PROBE, font=font_big, fill="black")
|
|
rows.append(ref)
|
|
|
|
for engine, label in ENGINES:
|
|
pdf = pdfdir / f"probe-{engine}.pdf"
|
|
if not pdf.exists():
|
|
print("skip", engine, "(no probe pdf)")
|
|
continue
|
|
item = box(pdf, "Item")[0]
|
|
total = box(pdf, "Total")[-1]
|
|
y0 = int((item[1] - 8) * DPI / 72)
|
|
y1 = int((total[3] + 62) * DPI / 72)
|
|
subprocess.run(["pdftoppm", "-r", str(DPI), "-png", "-singlefile", "-x", str(int(36 * DPI / 72)), "-y", str(y0),
|
|
"-W", "1500", "-H", str(y1 - y0), str(pdf), str(pngdir / f"crop-{engine}")], check=True)
|
|
img = Image.open(pngdir / f"crop-{engine}.png").convert("RGB")
|
|
band = Image.new("RGB", (1500, 34), "#f4f5f7")
|
|
ImageDraw.Draw(band).text((10, 4), label, font=font_lbl, fill="#222")
|
|
rows.append(band)
|
|
rows.append(img)
|
|
(pngdir / f"crop-{engine}.png").unlink()
|
|
|
|
W = 1500
|
|
H = sum(r.height for r in rows)
|
|
out = Image.new("RGB", (W, H), "white")
|
|
y = 0
|
|
for r in rows:
|
|
out.paste(r, (0, y))
|
|
y += r.height
|
|
out = out.convert("P", palette=Image.ADAPTIVE, colors=32)
|
|
out.save(pngdir / "shaping-comparison.png", optimize=True)
|
|
print("wrote", pngdir / "shaping-comparison.png", out.size)
|