Add pdf-invoice module: one invoice in OpenPDF 3.0.5, iText Core 9.8.0 and Flying Saucer 10.7.0, Devanagari shaping checked by rendering, streamed vs buffered download, subset sizes and timings
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
This commit is contained in:
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user