// Package qrcode wraps github.com/skip2/go-qrcode to emit small, crisp SVGs // instead of PNGs. SVGs scale perfectly at any size (laptop screen, phone, // printed conference badge) without re-encoding, and they compress well over // gzip. package qrcode import ( "bytes" "fmt" "io" qr "github.com/skip2/go-qrcode" ) // SVGOptions controls the rendered SVG. type SVGOptions struct { // ModulePx is the side length of each QR "module" (cell) in SVG user // units. The output viewBox is sized to (size * ModulePx). 8 is a good // default for ~256-300 px renders. ModulePx int // Margin is the white quiet zone around the code, measured in modules. // QR spec mandates 4-module minimum for reliable scanning; some scanners // tolerate less but 4 is the safe default. Margin int // Foreground / Background are CSS color strings. Default: "#11111b" / // "#cdd6f4" to match the app's terminal palette. Foreground string Background string // Level is the error-correction level (Low/Medium/High/Highest). Higher // = more robust to scratches/glare but larger code. Medium is the // industry default; we use High so the QR survives being shown on a // half-occluded phone screen at a noisy conference. Level qr.RecoveryLevel } // DefaultOptions returns reasonable defaults for displaying on a profile. func DefaultOptions() SVGOptions { return SVGOptions{ ModulePx: 8, Margin: 4, Foreground: "#11111b", Background: "#ffffff", Level: qr.High, } } // EncodeSVG generates an SVG string for the given content (e.g. a URL) using // the provided options. Returns ("", err) on encode failure. // // The output is a self-contained SVG document with explicit viewBox and // width/height in modules so it can be sized via CSS without distortion. func EncodeSVG(content string, opts SVGOptions) ([]byte, error) { if opts.ModulePx <= 0 { opts.ModulePx = 8 } if opts.Margin < 0 { opts.Margin = 4 } if opts.Foreground == "" { opts.Foreground = "#11111b" } if opts.Background == "" { opts.Background = "#cdd6f4" } q, err := qr.New(content, opts.Level) if err != nil { return nil, fmt.Errorf("qrcode: encode: %w", err) } // Disable the library's built-in border — we render our own quiet zone // using the opts.Margin parameter so the SVG dimensions are predictable. q.DisableBorder = true bm := q.Bitmap() size := len(bm) // matrix is square totalModules := size + opts.Margin*2 totalPx := totalModules * opts.ModulePx var buf bytes.Buffer writeSVG(&buf, bm, size, opts, totalModules, totalPx) return buf.Bytes(), nil } // writeSVG emits the SVG document. Split out for testability. func writeSVG(w io.Writer, bm [][]bool, size int, opts SVGOptions, totalModules, totalPx int) { fmt.Fprintf(w, ``, totalModules, totalModules, totalPx, totalPx) // Background fills the whole viewBox (modules + quiet zone). fmt.Fprintf(w, ``, totalModules, totalModules, opts.Background) // Foreground modules. We emit a single built from "M x y h1 v1 h-1 z" // rectangles — fewer DOM nodes than one per module, and renders // identically. var path bytes.Buffer for y := 0; y < size; y++ { for x := 0; x < size; x++ { if bm[y][x] { fmt.Fprintf(&path, "M%d %dh1v1h-1z", x+opts.Margin, y+opts.Margin) } } } fmt.Fprintf(w, ``, path.String(), opts.Foreground) }