Docs
/

@leumas/qr

The QR engine: payload builders, a style model, and matrix → SVG / PNG / PDF rendering. Pure and browser-safe except for ./raster, which is the only subpath that touches sharp.

@leumas/qr

The QR engine behind qr.leumas.tech: what text goes in a code, what it looks like, whether it will actually scan, and how it comes out as SVG, PDF or PNG.

Absorbs and replaces the payload builders that used to live inside shared/engines/adapters/domain/qr/index.js (themselves ported from leumas-middleware/lib/a.qr). That adapter now imports this package rather than keeping its own copy, so a fix to WIFI escaping lands everywhere at once.

The split, and why it matters

Everything on the barrel is pure and browser-safe. ./raster is the only subpath that touches sharp, and it is separate for the same reason @leumas/artwork splits ./dpi from ./prepare: a libvips binding of tens of megabytes must not sit on the boot path of a page that wanted a mailto: string.

The practical payoff is that the generator page renders a live, fully styled preview with no server call, and the download rasterises the same SVG string. Preview and export cannot drift, because they are one function.

stepsubpathneeds
buildPayload({type, …}) — what text goes in./payloadnothing
resolveStyle({…}) — what it looks like, and what's wrong with it./stylenothing
encodeMatrix(text, {…}) — the module grid./matrixqrcode
planCode({matrix, style}) — format-neutral shapes./plannothing
renderSvg(plan) · renderPdf(plan)./svg · ./pdfnothing
rasterize(svg, {…}) — PNG / JPEG / WebP./rastersharp — server only

generateSvg({payload, style}) runs the first five for the common case; generatePlan(…) stops one step earlier so a caller exporting three formats encodes once.

Payload types

url · text · wifi · email · tel · sms · geo · vcard · event · whatsapp · crypto · dynamic

dynamic is the commercial one. It builds <base>/api/qr/r/<code> — the short link whose destination stays editable after the code is printed. It is a builder rather than a string assembled at three call sites because the redirect route, the generator and the printed artwork must agree on that URL for ever; a printed code cannot be reissued when they drift.

There is deliberately no pay builder. A payment link is a plain URL whose shape differs per provider and changes without notice, and getting one wrong sends somebody's money nowhere. Build the URL you have verified and pass it to url. whatsapp and crypto are here because both have a published, stable URI scheme rather than a marketing URL that happens to work today.

[warning] crypto amounts are not one unit. BIP-21 (bitcoin: and friends) takes whole coins; EIP-681 (ethereum:) takes wei. This package passes each through unchanged and converts nothing — a form must label its own field.

Styling

Module shapes square · dot · rounded · diamond. Eye frames square · rounded · circle · leaf. Pupils square · dot · rounded. Plus flat or gradient foreground, per-eye colours, quiet zone, error-correction level, a centred logo, and a caption bar.

Four module shapes, not six, and none of them joins to its neighbours. The connected "classy" look every styling library ships needs each module to know which neighbours are filled, and gets subtly wrong results at the finder boundaries and the timing rows. Four that are exactly right beat six with two quietly deformed.

The readability check is the point

resolveStyle always returns a usable style plus a list of warnings — it never throws. Almost every bad style is still drawable: a pale code on white renders beautifully and no phone reads it; a logo over a third of the modules produces a lovely image that fails at the till. Throwing would block a preview the user is entitled to see; staying silent would ship the failure.

  • low_contrasterror below 3:1, warning below 4.5:1.
  • inverted — warning. Light-on-dark scans on modern phones and fails on older and industrial readers.
  • logo_too_largeerror above 25% width, where even level H starts to fail.
  • quiet_zone_small — warning below the 4 modules ISO/IEC 18004 requires.
  • ec_raised — informational: a logo silently raises error correction to H, because that repair has

no downside and forgetting it has a large one. It happens before encoding; raising it afterwards would change nothing.

scannable is false when any warning is an error. A save lane must read it.

Formats

  • SVG — the vector deliverable, and what a printer wants.
  • PDF — a real vector PDF, written here with no library: because ./plan.js emits only

rectangles-with-radii and polygons, every shape converts to Bézier operators with no loss. Gradients become PDF axial/radial shadings. A logo is omitted and reported as logoOmitted — embedding an arbitrary image means owning a PNG decoder or putting JPEG ringing on the hard edges a scanner samples. Export PNG or SVG for a logo'd code.

  • PNG / JPEG / WebP./raster, via sharp rendering the SVG. rasterizeForPrint({mm, dpi})

sizes by physical width, because "2000px" says nothing about whether a module survives the press.

[warning] rasterize's density is the grid sharp rasterises the SVG at before scaling to width. Left low, a large width produces a big fuzzy image rather than a big sharp one — that is the actual cause of soft QR exports.

Tests

node --test test/ — 24 checks. The central one rasterises at an odd number of pixels per module, samples the centre of every module, and asserts the result is the source matrix, for every module shape and at all three finders. There is no QR decoder in this workspace, and adding one would test somebody else's library; what this package can get wrong is the drawing — a transposed matrix, an eye one module out, a shape that misses its own cell — and every one of those produces a plausible image that throws nothing.

What it exports

74 exports: 19 components, 0 hooks, 55 helpers. Import from @leumas/qr.

DEFAULT_DYNAMIC_BASEDEFAULT_DYNAMIC_BASEDYNAMIC_PATHDYNAMIC_PATHERROR_LEVELSERROR_LEVELSEYE_BALL_SHAPESEYE_BALL_SHAPESEYE_FRAME_SHAPESEYE_FRAME_SHAPESFRAME_STYLESFRAME_STYLESMODULE_SHAPESMODULE_SHAPESPRINT_DPISPEC_QUIET_ZONESPEC_QUIET_ZONESTYLE_PRESETSSTYLE_PRESETSassemblePdfassemblePdfbuildPayloadbuildPayloadcontrastWarningscontrastWarningsencodeMatrixencodeMatrixeyeBallShapeeyeBallShapeeyeFrameShapeeyeFrameShapefinderOriginsfinderOriginsgeneratePlangenerateSvgicalStampicalStampisDarkerisDarkerisFinderCellisFinderCelllistStylePresetslistStylePresetsmoduleShapemoduleShapepaintRefpaintRefpayloadBuilderspayloadBuilderspayloadTypespayloadTypespdfColorpdfColorpixelsForMmplanCodeplanCoderasterizerasterizeForPrintrenderPdfrenderPdfrenderSvgrenderSvgresolveStyleresolveStyleshapeOpsshapeOpsshapePathshapePathsvgToDataUrisvgToDataUritextWidthEmtextWidthEmtoLatin1toLatin1
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt