"""The one web entry point, shared by server.py (Heroku, local) and the in-browser Hugging Face Space (Pyodide).""" import base64 import io import threading from urllib.parse import parse_qs from PIL import features from quill import INKS, STYLES, Glyphs, animate, is_cjk, render G = None LOCK = threading.Lock() def render_query(query): """URL query string -> (HTTP status, dict with an image data URL in `src`, or an `error`).""" global G q = {k: v[0] for k, v in parse_qs(query).items()} text = q.get("text", "").replace("\\n", "\n").strip() style, ink, anim = q.get("style", "italic"), q.get("ink") or None, bool(q.get("animate")) if style not in STYLES or (ink and ink not in INKS) or not text: return 400, dict(error="need text, a known style and ink") limit = (24 if anim else 36) if is_cjk(text) else (120 if anim else 300) # keeps a small dyno in budget if len(text) > limit: return 400, dict(error=f"please keep it under {limit} characters") try: size = min(max(int(q.get("size", 72)), 32), 140) except ValueError: size = 72 if is_cjk(text) != (style == "shodo"): # same switch quill.compose makes style = "shodo" if is_cjk(text) else "brush" with LOCK: # one render at a time bounds memory G = G or Glyphs() args = (text, style, ink, size, 1800, bool(q.get("flourish")), bool(q.get("initial")), q.get("seal", "")[:4], G) if anim: fmt = "WEBP" if features.check("webp") else "GIF" # some Pillow builds lack WebP data = animate(*args, fmt=fmt) return 200, dict(style=style, src=f"data:image/{fmt.lower()};base64," + base64.b64encode(data).decode()) buf = io.BytesIO() render(*args).save(buf, "PNG") return 200, dict(style=style, src="data:image/png;base64," + base64.b64encode(buf.getvalue()).decode())