peachQR.ts

2.5 KB · raw

1/**2 * peachQR.ts — render a Peach binary payload as a printable QR code.3 *4 * The spec (§4) calls for a proprietary dense matrix format with custom5 * anchor patterns and Reed-Solomon ECC tuned for high-capacity payloads.6 * That decoder is non-trivial (perspective correction + ECC + camera7 * frame analysis) and is genuinely out of scope for v2.5.0. For now the8 * print-backup path uses a standard QR code via the bundled `qrcode`9 * package with byte-mode segments and low ECC for maximum capacity.10 *11 * Standard QR (v40, ECC level L) caps at 2,953 binary bytes — that12 * covers the common case (vaults up to roughly 100 entries). Larger13 * vaults are advised to use PeachScript instead. The full proprietary14 * matrix code (20 KB capacity, custom Reed-Solomon, four-anchor15 * orientation) is a follow-up; the call sites below already separate16 * "encode" from "render", so swapping in the spec-compliant codec later17 * does not require touching the UI layer.18 */1920import QRCode from "qrcode";2122/** Approximate binary capacity for standard QR Version 40, ECC level L. */23export const QR_MAX_BYTES = 2953;2425export interface PeachQROptions {26  /** Pixel width of one matrix module. Higher = larger PNG. Default 6. */27  scale?: number;28  /** White margin in modules around the code. Default 4. */29  margin?: number;30}3132/**33 * Render a binary payload to a black-and-white QR canvas. Throws34 * `QR_PAYLOAD_TOO_LARGE` if the payload exceeds standard QR capacity —35 * callers should fall back to PeachScript in that case.36 */37export async function renderPeachQR(38  payload: Uint8Array,39  opts: PeachQROptions = {}40): Promise<HTMLCanvasElement> {41  if (payload.length > QR_MAX_BYTES) {42    throw new Error("QR_PAYLOAD_TOO_LARGE");43  }4445  const canvas = document.createElement("canvas");46  await QRCode.toCanvas(47    canvas,48    [{ data: payload, mode: "byte" }],49    {50      errorCorrectionLevel: "L",51      margin: opts.margin ?? 4,52      scale: opts.scale ?? 6,53      color: { dark: "#000000", light: "#ffffff" },54    }55  );56  return canvas;57}5859/** Convenience: PNG data URL ready for download. */60export async function renderPeachQRDataURL(61  payload: Uint8Array,62  opts: PeachQROptions = {}63): Promise<string> {64  if (payload.length > QR_MAX_BYTES) {65    throw new Error("QR_PAYLOAD_TOO_LARGE");66  }67  return QRCode.toDataURL([{ data: payload, mode: "byte" }], {68    errorCorrectionLevel: "L",69    margin: opts.margin ?? 4,70    scale: opts.scale ?? 6,71    color: { dark: "#000000", light: "#ffffff" },72  });73}