File size: 2,029 Bytes
a9f4e61 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | """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())
|