"""Generate original, fictional test documents for paddleocr.dev benchmarks. All names, numbers and addresses are invented.""" import io, random from pathlib import Path from PIL import Image, ImageDraw, ImageFont, ImageFilter OUT = Path(__file__).parent / "samples" OUT.mkdir(exist_ok=True) random.seed(7) INTER = "/usr/share/fonts/opentype/inter/Inter-Regular.otf" INTER_B = "/usr/share/fonts/opentype/inter/Inter-Bold.otf" SERIF = "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf" SERIF_B = "/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf" MONO = "/usr/share/fonts/truetype/jetbrains-mono/JetBrainsMono-Regular.ttf" ZH = "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc" JA = "/usr/share/fonts/opentype/ipafont-gothic/ipag.ttf" def F(path, size): return ImageFont.truetype(path, size) def paper(w, h, tint=(252, 250, 245)): img = Image.new("RGB", (w, h), tint) px = img.load() for _ in range(w * h // 18): x, y = random.randrange(w), random.randrange(h) r, g, b = px[x, y] d = random.randint(-7, 3) px[x, y] = (r + d, g + d, b + d) return img def receipt(): w, h = 620, 1040 img = paper(w, h) d = ImageDraw.Draw(img) y = 50 def c(text, font, fill=(25, 25, 25)): nonlocal y tw = d.textlength(text, font=font) d.text(((w - tw) / 2, y), text, font=font, fill=fill) y += font.size + 14 def row(left, right, font): nonlocal y d.text((50, y), left, font=font, fill=(30, 30, 30)) tw = d.textlength(right, font=font) d.text((w - 50 - tw, y), right, font=font, fill=(30, 30, 30)) y += font.size + 14 c("HARBOR STREET COFFEE", F(MONO, 30)) c("214 Harbor Street, Portland ME", F(MONO, 20)) c("Tel (207) 555-0148", F(MONO, 20)) y += 10 d.line((40, y, w - 40, y), fill=(60, 60, 60), width=2); y += 22 row("Order #4471", "2026-10-21 08:42", F(MONO, 21)) row("Cashier: Dana", "Table 6", F(MONO, 21)) d.line((40, y, w - 40, y), fill=(120, 120, 120), width=1); y += 22 for item, price in [("2 x Flat White", "9.50"), ("1 x Oat Latte", "5.25"), ("1 x Blueberry Muffin", "3.75"), ("1 x Avocado Toast", "11.00"), ("1 x Sparkling Water", "2.50")]: row(item, price, F(MONO, 23)) d.line((40, y, w - 40, y), fill=(120, 120, 120), width=1); y += 22 row("Subtotal", "32.00", F(MONO, 23)) row("Sales tax 5.5%", "1.76", F(MONO, 23)) row("TOTAL", "$33.76", F(MONO, 30)) y += 6 row("VISA **** 4012", "33.76", F(MONO, 21)) y += 26 c("Thank you! Come again.", F(MONO, 22)) c("Free Wi-Fi: HarborGuest", F(MONO, 19)) img = img.filter(ImageFilter.GaussianBlur(0.5)) img.save(OUT / "receipt.png") return img def photo_skew(base): # Simulate a phone photo: rotate, darken edges, add blur and noise, place on a table. bg = Image.new("RGB", (900, 1200), (92, 74, 58)) bd = ImageDraw.Draw(bg) for i in range(0, 1200, 6): shade = 86 + int(10 * random.random()) bd.line((0, i, 900, i + random.randint(-3, 3)), fill=(shade, shade - 16, shade - 32)) r = base.convert("RGBA").rotate(7.5, expand=True, resample=Image.BICUBIC) bg.paste(r, (110, 50), r) bg = bg.filter(ImageFilter.GaussianBlur(1.1)) px = bg.load() for _ in range(140000): x, y = random.randrange(900), random.randrange(1200) p = px[x, y] n = random.randint(-14, 14) px[x, y] = tuple(max(0, min(255, v + n)) for v in p) # uneven lighting shade = Image.new("L", bg.size, 0) sd = ImageDraw.Draw(shade) for i in range(60): sd.ellipse((-200 + i * 4, -300 + i * 4, 1100 - i * 4, 1500 - i * 4), fill=int(i * 1.6)) bg = Image.composite(bg, Image.new("RGB", bg.size, (20, 18, 16)), shade.point(lambda v: 150 + v // 1)) bg.save(OUT / "receipt-photo.jpg", quality=82) def invoice(): w, h = 1240, 1754 img = Image.new("RGB", (w, h), (255, 255, 255)) d = ImageDraw.Draw(img) d.rectangle((0, 0, w, 12), fill=(35, 87, 217)) d.text((90, 90), "Northwind Lighting Co.", font=F(INTER_B, 44), fill=(20, 24, 33)) d.text((90, 150), "88 Foundry Lane, Leeds LS1 4AB, United Kingdom", font=F(INTER, 22), fill=(80, 86, 99)) d.text((90, 182), "billing@northwind-lighting.example · VAT GB 123 4567 89", font=F(INTER, 22), fill=(80, 86, 99)) d.text((860, 90), "INVOICE", font=F(INTER_B, 52), fill=(35, 87, 217)) for i, (k, v) in enumerate([("Invoice no.", "INV-2026-0917"), ("Issue date", "14 Oct 2026"), ("Due date", "13 Nov 2026")]): d.text((860, 170 + i * 34, ), k, font=F(INTER, 22), fill=(110, 116, 129)) d.text((1020, 170 + i * 34), v, font=F(INTER_B, 22), fill=(20, 24, 33)) d.text((90, 300), "BILL TO", font=F(INTER_B, 20), fill=(110, 116, 129)) for i, line in enumerate(["Ashgrove Architects Ltd", "Unit 5, Canal Wharf", "Manchester M1 2EQ"]): d.text((90, 334 + i * 32), line, font=F(INTER, 24), fill=(20, 24, 33)) d.text((640, 300), "PROJECT", font=F(INTER_B, 20), fill=(110, 116, 129)) for i, line in enumerate(["Library refit, level 2", "PO number: AA-77310"]): d.text((640, 334 + i * 32), line, font=F(INTER, 24), fill=(20, 24, 33)) # table x0, x1 = 90, 1150 cols = [90, 600, 760, 940, 1150] heads = ["Description", "Qty", "Unit price", "Amount"] rows = [ ["LED panel 600x600, 4000K", "24", "£46.00", "£1,104.00"], ["Linear pendant, 1.2 m", "10", "£128.50", "£1,285.00"], ["Emergency exit sign", "6", "£39.90", "£239.40"], ["DALI dimming driver", "12", "£22.75", "£273.00"], ["Installation labour (hours)", "16", "£55.00", "£880.00"], ] y = 470 rh = 62 d.rectangle((x0, y, x1, y + rh), fill=(241, 243, 248)) for i, htxt in enumerate(heads): font = F(INTER_B, 23) if i == 0: d.text((cols[i] + 16, y + 18), htxt, font=font, fill=(20, 24, 33)) else: tw = d.textlength(htxt, font=font) d.text((cols[i + 1] - 16 - tw, y + 18), htxt, font=font, fill=(20, 24, 33)) for r_i, r in enumerate(rows): yy = y + rh * (r_i + 1) for i, cell in enumerate(r): font = F(INTER, 23) if i == 0: d.text((cols[i] + 16, yy + 18), cell, font=font, fill=(40, 44, 53)) else: tw = d.textlength(cell, font=font) d.text((cols[i + 1] - 16 - tw, yy + 18), cell, font=font, fill=(40, 44, 53)) yend = y + rh * (len(rows) + 1) for i in range(len(rows) + 2): d.line((x0, y + i * rh, x1, y + i * rh), fill=(200, 205, 214), width=2) for cx in cols: d.line((cx, y, cx, yend), fill=(200, 205, 214), width=2) # totals ty = yend + 40 for i, (k, v) in enumerate([("Subtotal", "£3,781.40"), ("VAT 20%", "£756.28"), ("Total due", "£4,537.68")]): bold = i == 2 font = F(INTER_B if bold else INTER, 28 if bold else 24) d.text((760, ty + i * 46), k, font=font, fill=(20, 24, 33)) tw = d.textlength(v, font=font) d.text((1150 - 16 - tw, ty + i * 46), v, font=font, fill=(20, 24, 33)) d.text((90, ty + 200), "Payment terms", font=F(INTER_B, 24), fill=(20, 24, 33)) terms = ["Please pay within 30 days by bank transfer to Northwind Lighting Co.", "Sort code 12-34-56, account 87654321. Quote the invoice number as reference.", "Late payments may incur interest at 8% above the Bank of England base rate."] for i, t in enumerate(terms): d.text((90, ty + 240 + i * 34), t, font=F(INTER, 22), fill=(80, 86, 99)) d.text((90, h - 110), "Thank you for your business.", font=F(INTER, 22), fill=(110, 116, 129)) img.save(OUT / "invoice.png") def bilingual_zh(): w, h = 1100, 760 img = paper(w, h, (250, 250, 247)) d = ImageDraw.Draw(img) d.rectangle((0, 0, w, 120), fill=(196, 38, 38)) d.text((60, 22), "城东社区图书馆", font=F(ZH, 54), fill=(255, 255, 255)) d.text((620, 40), "Chengdong Community Library", font=F(INTER_B, 30), fill=(255, 240, 240)) lines = [ ("开放时间:周一至周五 9:00–21:00", ZH, 38), ("Opening hours: Mon–Fri 9:00–21:00", INTER, 30), ("周末及法定节假日 10:00–17:00", ZH, 38), ("Weekends and public holidays 10:00–17:00", INTER, 30), ("请保持安静,馆内禁止饮食。", ZH, 38), ("Please keep quiet. No food or drink inside.", INTER, 30), ("借书证办理请到一楼服务台。", ZH, 38), ("Library cards are issued at the ground-floor desk.", INTER, 30), ] y = 170 for t, f, s in lines: d.text((60, y), t, font=F(f, s), fill=(30, 30, 30)) y += s + (30 if f == INTER else 14) img.save(OUT / "notice-zh-en.png") def japanese(): w, h = 1000, 700 img = paper(w, h, (252, 251, 246)) d = ImageDraw.Draw(img) d.text((60, 50), "臨時休業のお知らせ", font=F(JA, 58), fill=(20, 20, 20)) d.line((60, 130, 940, 130), fill=(40, 40, 40), width=3) lines = [ "いつもご利用いただき誠にありがとうございます。", "設備点検のため、下記の日程で臨時休業いたします。", "休業日:2026年11月18日(水)", "営業再開:2026年11月19日(木)午前10時", "お客様にはご迷惑をおかけしますが、", "何卒ご理解のほどよろしくお願い申し上げます。", "みなと町ベーカリー 店主", ] y = 170 for i, t in enumerate(lines): d.text((60 if i < 6 else 520, y), t, font=F(JA, 34), fill=(30, 30, 30)) y += 64 img.save(OUT / "notice-ja.png") def paper_page(): import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt fig = plt.figure(figsize=(3.6, 0.7), dpi=200) fig.text(0.02, 0.35, r"$\mathcal{L}=-\frac{1}{N}\sum_{i=1}^{N}\log p(y_i \mid x_i)$", fontsize=15) buf = io.BytesIO() fig.savefig(buf, format="png", transparent=False, facecolor="white") plt.close(fig) formula = Image.open(io.BytesIO(buf.getvalue())).convert("RGB") w, h = 1240, 1600 img = Image.new("RGB", (w, h), (255, 255, 255)) d = ImageDraw.Draw(img) d.text((110, 100), "3. Training Objective", font=F(SERIF_B, 40), fill=(10, 10, 10)) body = [ "We train the recognizer with a standard sequence objective. Given N text-line", "images x_i and their transcriptions y_i, the model minimises the average", "negative log-likelihood of the target sequence:", ] y = 180 for t in body: d.text((110, y), t, font=F(SERIF, 28), fill=(20, 20, 20)); y += 42 y += 20 img.paste(formula, ((w - formula.width) // 2, y)) d.text((1060, y + formula.height // 2 - 16), "(1)", font=F(SERIF, 28), fill=(20, 20, 20)) y += formula.height + 30 body2 = [ "Table 2 reports the effect of the training schedule on a held-out set of", "2,000 receipt photographs. Longer schedules improve accuracy but cost time.", ] for t in body2: d.text((110, y), t, font=F(SERIF, 28), fill=(20, 20, 20)); y += 42 y += 30 d.text((110, y), "Table 2. Schedule ablation on the receipt set.", font=F(SERIF_B, 26), fill=(10, 10, 10)); y += 50 cols = [110, 470, 760, 1130] rows = [["Schedule", "Line accuracy", "Train hours"], ["Short (20 epochs)", "91.4%", "6"], ["Medium (50 epochs)", "93.1%", "15"], ["Long (100 epochs)", "93.6%", "29"]] rh = 54 d.line((110, y, 1130, y), fill=(0, 0, 0), width=3) for i, r in enumerate(rows): for j, cell in enumerate(r): d.text((cols[j] + 10, y + 12 + i * rh), cell, font=F(SERIF_B if i == 0 else SERIF, 27), fill=(15, 15, 15)) if i == 0: d.line((110, y + rh, 1130, y + rh), fill=(0, 0, 0), width=2) d.line((110, y + rh * len(rows), 1130, y + rh * len(rows)), fill=(0, 0, 0), width=3) y += rh * len(rows) + 60 body3 = [ "4. Discussion", ] d.text((110, y), body3[0], font=F(SERIF_B, 40), fill=(10, 10, 10)); y += 70 for t in ["The medium schedule offers the best trade-off: it reaches 93.1% line", "accuracy in roughly half the training time of the long schedule."]: d.text((110, y), t, font=F(SERIF, 28), fill=(20, 20, 20)); y += 42 d.text((600, h - 90), "4", font=F(SERIF, 26), fill=(60, 60, 60)) img.save(OUT / "paper-page.png") if __name__ == "__main__": r = receipt() photo_skew(r) invoice() bilingual_zh() japanese() paper_page() for p in sorted(OUT.iterdir()): im = Image.open(p) print(p.name, im.size, p.stat().st_size)