import { createMemo } from "@solidrt/core" import type { LayoutProps } from "@solidrt/core" import qrcode from "qrcode-generator" export interface QrCodeProps { // The string to encode (URL, pairing ticket, text, ...). data: string // Pixels per QR module (the smallest square). The grid is // moduleCount * moduleSize on a side, plus the margin around it. moduleSize?: number // Quiet-zone padding in pixels around the grid. Scanners need a light border, // so keep this non-zero. margin?: number // Dark/light module colors. Defaults are black on white for reliable scanning // regardless of theme; override only if you know the contrast still holds. color?: string background?: string // Error-correction level: higher tolerates more damage but packs denser and // caps the data length sooner. level?: "L" | "M" | "Q" | "H" // Corner radius of the background panel. radius?: number layout?: LayoutProps } const MODULE_SIZE = 6 const MARGIN = 16 const RADIUS = 8 // Render a QR for `data` as primitives: merge horizontal runs of dark modules // per row into a single d-rect, placed at explicit coordinates on a light // quiet-zone panel. Everything inside the panel is detached, so a data change // repaints without touching layout; the panel view itself has a fixed size // (module count * module size + margins) that only changes when the data // crosses a QR version boundary. export function QrCode(props: QrCodeProps) { let grid = createMemo(() => { let qr = qrcode(0, props.level ?? "M") qr.addData(props.data) qr.make() let n = qr.getModuleCount() let runs: { x: number; y: number; len: number }[] = [] for (let y = 0; y < n; y++) { let x = 0 while (x < n) { if (!qr.isDark(y, x)) { x++ continue } let len = 1 while (x + len < n && qr.isDark(y, x + len)) len++ runs.push({ x, y, len }) x += len } } return { n, runs } }) let size = () => props.moduleSize ?? MODULE_SIZE let margin = () => props.margin ?? MARGIN let side = () => grid().n * size() + 2 * margin() return ( {grid().runs.map((run) => ( ))} ) }