// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package adminui import ( "fmt" "html/template" "strings" qrcode "github.com/skip2/go-qrcode" ) // qrSVG renders text as an inline SVG QR code, or empty if it will not encode. // // Inline SVG rather than a PNG data: URI because the page's CSP is `default-src 'none'` and means // it. A data: image would need img-src opened up; markup needs nothing, and the QR is generated // here from a boolean matrix, so nothing a user supplied reaches the output. // // Drawn as one path rather than a rect per module: a link of this length encodes to roughly 60x60 // modules, and two thousand elements is a lot of DOM for a picture of a square. func qrSVG(text string) template.HTML { if text == "" { return "" } // Medium recovery: a phone camera reading a screen has no dirt or creases to survive, and // lower recovery keeps the module count down, which keeps it scannable on a small display. q, err := qrcode.New(text, qrcode.Medium) if err != nil { return "" // too long to encode; the link text below it still works } bitmap := q.Bitmap() n := len(bitmap) if n == 0 { return "" } var path strings.Builder for y, row := range bitmap { for x, dark := range row { if dark { fmt.Fprintf(&path, "M%d %dh1v1h-1z", x, y) } } } // A quiet zone is part of the spec, not decoration: without it a scanner cannot find the // symbol's edges against whatever is next to it on the page. var out strings.Builder fmt.Fprintf(&out, ``+ ``+ ``, n, n, n, n, path.String()) return template.HTML(out.String()) }