@unisim/qr
v0.1.0
Published
The QR design model, shaped-plate geometry and renderer shared by the Universal Apps — one definition of the format a saved code travels in.
Maintainers
Readme
@unisim/qr
The QR design model, shaped-plate geometry and renderer shared by the Universal Apps. One definition of the format a saved code travels in.
npm install @unisim/qr qr-code-stylingimport { DEFAULT_DESIGN, PRESETS, renderQrCanvas, qrContrastIssue } from '@unisim/qr'
const design = { ...DEFAULT_DESIGN, ...PRESETS[5].patch, data: 'https://unisim.co.uk' }
const problem = qrContrastIssue(design) // null, or why a decoder would struggle
const canvas = await renderQrCanvas(design, 1024)
document.body.append(canvas)Why this package exists
Universal PDF carried a hand-taken copy of Universal QR's design model and renderer. A copy is faithful only on the day it is taken.
On 2026-08-24 Universal QR added starPlacement and starColor, moving its
Star preset to the star standing behind the code. The copy did not get them
until 2026-08-28. In between, a star designed in one app rendered as a
different picture in the other — a small black-and-white code on a white plate
instead of an orange star with the code in front.
Nothing failed. An incoming design is merged over the reader's own defaults
({ ...DEFAULT_DESIGN, ...incoming }), so a field the reader has never heard
of is dropped in silence. There is no error to notice; the user simply gets a
different picture from the one they saved. That is the failure mode this
package removes, and scripts/selftest.mjs asserts the two structural
properties that keep it removed:
- every field declared on
QrDesignhas a value inDEFAULT_DESIGN, so a merge can never leave a hole; - every preset lands in the same design however you arrived at it.
The format
QrDesign is the whole serialisable description of a code: its data, colours,
module and corner shapes, plate silhouette, decoration, logo and quiet zone.
It is what Universal QR writes to its saved-design store and to the .json
sidecar beside an account save, and what Universal PDF reads from both.
QrConfig and DEFAULT_CONFIG are aliases — Universal QR's spelling for the
same interface and constant. Prefer QrDesign / DEFAULT_DESIGN in new code.
Rendering
| Export | Use it when |
|---|---|
| renderQrCanvas(design, size) | you want one path for every design, square included |
| composeShapedCanvas(design, size) | your app renders square codes its own way (throws on frameShape: 'square') |
| composeShapedSvg(design, size) | you need a vector export — the code nests as a real <svg>, not a raster |
Both canvas entries run the same layering underneath. The choice is policy, not format: Universal QR keeps its plain path for square codes untouched, Universal PDF sends everything through the composite. What they must never disagree about — the geometry, the draw order, the colours — is one piece of code either way.
qr-code-styling is a peer dependency and is imported at call time, not at
module load, so importing this package does not pull the renderer into your
main bundle.
The rules the geometry keeps
⚠️ The code is never clipped to a shape. A QR is only readable if every module and its quiet zone are present, so a silhouette is only ever the plate the code sits on: the code is rendered smaller and centred in the largest square that fits. Anything that trims the silhouette out of the modules produces a picture of a QR code rather than a QR code.
The single exception keeps the same rule from the other side.
starPlacement: 'behind' makes the star a backdrop rather than a plate: it is
drawn under a code that covers its notches, so the code is whole and the star
is what gets overlapped. It is also the more scannable of the two by a
distance — the code goes from 37% of the image to 72%.
⚠️ Contrast is not decoration. qrContrastIssue distinguishes an inverted
code (light modules on a dark ground — strict readers reject these outright)
from one whose polarity is right but whose ratio is too thin (MIN_QR_CONTRAST
is 3:1). The thin case is the dangerous one: brand orange on white decodes at
256 px and fails at 512 px, so it passes a desk test and fails on a poster.
A star standing behind the code is checked as a second background, because it
sits under most of the modules.
Tests
npm test # builds, then runs the pure checksThe canvas and SVG composites need a DOM, so they are covered where a browser
is: Universal PDF's npm run test:qr-star renders a Universal-QR-authored
design through a real browser and measures the pixels.
