Skip to content

Small toSVG() Helper for QR Code generator (Javascript) #243

Description

@suchislife801

A built-in toSVG for the QR Code generator

Library: QR Code generator (JavaScript)

Add a small SVG output helper — toSVG — directly to the qrcodegen namespace, so a QrCode can be rendered in one line in the browser without copying boilerplate out of the demo files. It merges horizontally-adjacent dark modules into a single rectangle, which produces a meaningfully smaller SVG path.

One file, ready to drop in: qradapter.js

Motivation

The library encodes beautifully, but the SVG rendering lives only in the worked-example / demo files, so every project re-copies an SVG helper.
Promoting it into the library namespace makes the common case a one-liner, and it's a natural place to apply one small optimization.

Run-length merging. The current demo helper emits one path command per dark module (M{x},{y}h1v1h-1z). Merging each horizontal run of dark modules into a single rectangle keeps the output pixel-identical while roughly halving the path:

Payload QR size Dark modules Merged runs Reduction
https://nayuki.io 25×25 312 165 ~48% fewer path ops
300-char string 69×69 2436 1237 ~50% fewer path ops

That is roughly half the path-data bytes, for identical output.

Code - qradapter.js

/*
 * QR Code generator - SVG output adapter (JavaScript)
 *
 * A small companion to Nayuki's QR Code generator library that adds a
 * convenience SVG renderer directly to the "qrcodegen" namespace object. Load
 * this file after qrcodegen.js; it extends the same global "qrcodegen", so you
 * can simply call qrcodegen.toSVG(...) in the browser.
 *
 * Horizontally-adjacent dark modules are merged into a single rectangle, which
 * shrinks the SVG path data. The output is identical to drawing every module,
 * only smaller.
 *
 * Example:
 *   var qr = qrcodegen.QrCode.encodeText("Hello, world!", qrcodegen.QrCode.Ecc.MEDIUM);
 *   document.querySelector("div").innerHTML = qrcodegen.toSVG(qr, 4);
 *
 * Offered as a feature-request contribution, under the same MIT License as the
 * library it extends. https://www.nayuki.io/page/qr-code-generator-library
 */

"use strict";
var qrcodegen;
(function (qrcodegen) {

    // Returns a string of SVG code for an image depicting the given QR Code, with the
    // given number of border modules on all sides. The result always uses Unix newlines
    // ("\n"), regardless of platform. Horizontally-adjacent dark modules are merged into
    // one rectangle, so the path stays compact even at large versions.
    function toSVG(qr, border, lightColor, darkColor) {
        if (lightColor === undefined) lightColor = "#FFFFFF";
        if (darkColor === undefined) darkColor = "#000000";
        if (border < 0)
            throw new RangeError("Border must be non-negative");
        var dim = qr.size + border * 2;
        var parts = [];
        for (var y = 0; y < qr.size; y++) {
            for (var x = 0; x < qr.size;) {
                if (!qr.getModule(x, y)) {
                    x++;
                    continue;
                }
                var run = 1;  // Length of the dark run starting at (x, y)
                while (x + run < qr.size && qr.getModule(x + run, y))
                    run++;
                parts.push("M" + (x + border) + "," + (y + border) + "h" + run + "v1h-" + run + "z");
                x += run;
            }
        }
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" +
            "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 " + dim + " " + dim + "\" stroke=\"none\">\n" +
            "\t<rect width=\"100%\" height=\"100%\" fill=\"" + lightColor + "\"/>\n" +
            "\t<path d=\"" + parts.join(" ") + "\" fill=\"" + darkColor + "\"/>\n" +
            "</svg>\n";
    }
    qrcodegen.toSVG = toSVG;

})(qrcodegen || (qrcodegen = {}));

API

The function merges into the existing qrcodegen namespace:

// Returns SVG code for the QR Code, with `border` light modules on every side.
qrcodegen.toSVG(qr, border, lightColor = "#FFFFFF", darkColor = "#000000") -> string

Usage

<script src="qrcodegen.js"></script>
<script src="qradapter.js"></script>   <!-- extends the same global qrcodegen -->
<script>
  var qr = qrcodegen.QrCode.encodeText("Hello, world!", qrcodegen.QrCode.Ecc.MEDIUM);
  document.querySelector("div").innerHTML = qrcodegen.toSVG(qr, 4);
</script>

Correctness

The helper was checked by reconstructing the module grid from its output and comparing it, cell for cell, against QrCode.getModule — the SVG path parsed back to dark cells — across QR sizes 21×21 to 69×69 and numeric, alphanumeric and byte/UTF-8 payloads. Every case matched exactly.

Notes

  • Style follows the library: same namespace, per-function comment, and the <?xml … ?> + DOCTYPE SVG preamble the existing helper uses.
  • Offered under the same MIT License as the library. The header credits the library but leaves authorship of this addition open — set the copyright line however you prefer for contributions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions