from __future__ import annotations import sys import urllib.request from pathlib import Path from arabic_reshaper import reshape from bidi.algorithm import get_display from PIL import Image, ImageDraw, ImageFont ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from ocr_studio.config import ARABIC_FONT_PATH, EXAMPLES_DIR, FONTS_DIR, LATIN_FONT_PATH FONT_SOURCES = { LATIN_FONT_PATH: ( "https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/hinted/ttf/NotoSans/NotoSans-Regular.ttf", "https://github.com/notofonts/latin-greek-cyrillic/raw/main/fonts/NotoSans/hinted/ttf/NotoSans-Regular.ttf", ), ARABIC_FONT_PATH: ( "https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/hinted/ttf/NotoNaskhArabic/NotoNaskhArabic-Regular.ttf", "https://github.com/notofonts/notonaskharabic/raw/main/fonts/NotoNaskhArabic/hinted/ttf/NotoNaskhArabic-Regular.ttf", ), } def _download(path: Path, urls: tuple[str, ...]) -> None: path.parent.mkdir(parents=True, exist_ok=True) last_error: Exception | None = None for url in urls: try: with urllib.request.urlopen(url, timeout=60) as response: data = response.read() if len(data) < 10_000: continue path.write_bytes(data) return except Exception as exc: # noqa: BLE001 last_error = exc if last_error: raise last_error def _font(path: Path, size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: if path.exists(): return ImageFont.truetype(str(path), size=size) return ImageFont.load_default() def _shape(text: str) -> str: if any("\u0600" <= char <= "\u06ff" for char in text): return get_display(reshape(text)) return text def _render(path: Path, lines: list[list[tuple[str, Path, int]]], align: str = "left") -> None: width, height = 1280, 760 image = Image.new("RGB", (width, height), "#f7f3ea") draw = ImageDraw.Draw(image) draw.rectangle((36, 36, width - 36, height - 36), outline="#1f4f4a", width=3) y = 88 for segments in lines: x = 80 if align == "left" else width - 80 if align == "right": for text, font_path, size in reversed(segments): font = _font(font_path, size) shaped = _shape(text) bbox = draw.textbbox((0, 0), shaped, font=font) text_w = bbox[2] - bbox[0] x -= text_w draw.text((x, y), shaped, fill="#1c1915", font=font) x -= 12 else: for text, font_path, size in segments: font = _font(font_path, size) shaped = _shape(text) draw.text((x, y), shaped, fill="#1c1915", font=font) bbox = draw.textbbox((0, 0), shaped, font=font) x += bbox[2] - bbox[0] + 10 y += max(size for _, _, size in segments) + 28 path.parent.mkdir(parents=True, exist_ok=True) image.save(path, format="PNG") def main() -> None: FONTS_DIR.mkdir(parents=True, exist_ok=True) EXAMPLES_DIR.mkdir(parents=True, exist_ok=True) for destination, urls in FONT_SOURCES.items(): if destination.exists(): continue try: _download(destination, urls) except Exception as exc: # noqa: BLE001 print(f"Font download skipped for {destination.name}: {exc}") _render( EXAMPLES_DIR / "english-print.png", [ [("INVOICE 1048", LATIN_FONT_PATH, 42)], [("Customer: Jane Walton", LATIN_FONT_PATH, 32)], [("Amount due: 128.40 USD", LATIN_FONT_PATH, 32)], [("Due date: 24 August 2026", LATIN_FONT_PATH, 32)], ], ) _render( EXAMPLES_DIR / "persian-print.png", [ [("فاکتور ۱۰۴۸", ARABIC_FONT_PATH, 44)], [("خریدار: سارا محمدی", ARABIC_FONT_PATH, 34)], [("مبلغ قابل پرداخت: ۱۲۸۴۰۰۰ تومان", ARABIC_FONT_PATH, 34)], [("تاریخ سررسید: ۳ شهریور ۱۴۰۵", ARABIC_FONT_PATH, 34)], ], align="right", ) _render( EXAMPLES_DIR / "mixed-print.png", [ [("BOARDING PASS / ", LATIN_FONT_PATH, 36), ("کارت پرواز", ARABIC_FONT_PATH, 36)], [("Passenger / ", LATIN_FONT_PATH, 30), ("مسافر: ", ARABIC_FONT_PATH, 30), ("Ali Rezaei", LATIN_FONT_PATH, 30)], [("Flight / ", LATIN_FONT_PATH, 30), ("پرواز: ", ARABIC_FONT_PATH, 30), ("W5 1084", LATIN_FONT_PATH, 30)], [("Seat / ", LATIN_FONT_PATH, 30), ("صندلی: ", ARABIC_FONT_PATH, 30), ("14A", LATIN_FONT_PATH, 30)], ], ) if __name__ == "__main__": main()