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())