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