71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
"""QR generation engine. No credit deduction — generation is free and unlimited."""
|
|
import io
|
|
import secrets
|
|
import zipfile
|
|
|
|
import segno
|
|
|
|
from app.core.config import settings
|
|
from app.models.documents import QRStyle
|
|
|
|
_EC_MAP = {"L": "l", "M": "m", "Q": "q", "H": "h"}
|
|
|
|
|
|
def new_code(prefix: str = "vp") -> str:
|
|
return f"{prefix}{secrets.token_urlsafe(8).replace('-', '').replace('_', '')[:10]}".lower()
|
|
|
|
|
|
def verify_url(code: str) -> str:
|
|
return f"{settings.frontend_url}/v/{code}"
|
|
|
|
|
|
def _make(code: str, style: QRStyle) -> segno.QRCode:
|
|
ec = _EC_MAP.get(style.error_correction.upper(), "m")
|
|
if style.logo_url:
|
|
ec = "h" # force high error correction when a logo is overlaid
|
|
return segno.make(verify_url(code), error=ec)
|
|
|
|
|
|
def render_png(code: str, style: QRStyle, scale: int = 10) -> bytes:
|
|
buf = io.BytesIO()
|
|
_make(code, style).save(buf, kind="png", scale=scale,
|
|
dark=style.fg_color, light=style.bg_color,
|
|
border=style.quiet_zone)
|
|
return buf.getvalue()
|
|
|
|
|
|
def render_svg(code: str, style: QRStyle, scale: int = 10) -> bytes:
|
|
buf = io.BytesIO()
|
|
_make(code, style).save(buf, kind="svg", scale=scale,
|
|
dark=style.fg_color, light=style.bg_color,
|
|
border=style.quiet_zone)
|
|
return buf.getvalue()
|
|
|
|
|
|
def render_pdf(code: str, style: QRStyle, scale: int = 10) -> bytes:
|
|
buf = io.BytesIO()
|
|
_make(code, style).save(buf, kind="pdf", scale=scale,
|
|
dark=style.fg_color, light=style.bg_color,
|
|
border=style.quiet_zone)
|
|
return buf.getvalue()
|
|
|
|
|
|
def render(code: str, style: QRStyle, fmt: str = "png") -> tuple[bytes, str]:
|
|
fmt = fmt.lower()
|
|
if fmt == "svg":
|
|
return render_svg(code, style), "image/svg+xml"
|
|
if fmt == "pdf":
|
|
return render_pdf(code, style), "application/pdf"
|
|
return render_png(code, style), "image/png"
|
|
|
|
|
|
def zip_codes(codes: list[str], style: QRStyle, fmt: str = "png") -> bytes:
|
|
"""Bundle per-pack QRs (Option 2) into a single downloadable ZIP."""
|
|
buf = io.BytesIO()
|
|
ext = {"svg": "svg", "pdf": "pdf"}.get(fmt.lower(), "png")
|
|
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
for i, code in enumerate(codes, start=1):
|
|
data, _ = render(code, style, fmt)
|
|
zf.writestr(f"qr_{i:05d}_{code}.{ext}", data)
|
|
return buf.getvalue()
|