{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/qr",
  "slug": "packages/qr",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/qr",
  "name": "@leumas/qr",
  "eyebrow": null,
  "chip": null,
  "summary": "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.",
  "keywords": [
    "qr",
    "qr code",
    "qrcode",
    "vcard",
    "wifi qr",
    "dynamic qr",
    "qr styling",
    "svg",
    "pdf"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "\n# @leumas/qr\n\nThe QR engine behind `qr.leumas.tech`: what text goes in a code, what it looks like, whether it will\nactually scan, and how it comes out as SVG, PDF or PNG.\n\nAbsorbs and replaces the payload builders that used to live inside\n`shared/engines/adapters/domain/qr/index.js` (themselves ported from `leumas-middleware/lib/a.qr`).\nThat adapter now imports this package rather than keeping its own copy, so a fix to WIFI escaping\nlands everywhere at once.\n\n## The split, and why it matters\n\nEverything on the barrel is **pure and browser-safe**. `./raster` is the only subpath that touches\n`sharp`, and it is separate for the same reason `@leumas/artwork` splits `./dpi` from `./prepare`: a\nlibvips binding of tens of megabytes must not sit on the boot path of a page that wanted a `mailto:`\nstring.\n\nThe practical payoff is that the generator page renders a **live, fully styled preview with no server\ncall**, and the download rasterises the *same* SVG string. Preview and export cannot drift, because\nthey are one function.\n\n| step | subpath | needs |\n|---|---|---|\n| `buildPayload({type, …})` — what text goes in | `./payload` | nothing |\n| `resolveStyle({…})` — what it looks like, and what's wrong with it | `./style` | nothing |\n| `encodeMatrix(text, {…})` — the module grid | `./matrix` | `qrcode` |\n| `planCode({matrix, style})` — format-neutral shapes | `./plan` | nothing |\n| `renderSvg(plan)` · `renderPdf(plan)` | `./svg` · `./pdf` | nothing |\n| `rasterize(svg, {…})` — PNG / JPEG / WebP | `./raster` | **sharp** — server only |\n\n`generateSvg({payload, style})` runs the first five for the common case; `generatePlan(…)` stops one\nstep earlier so a caller exporting three formats encodes once.\n\n## Payload types\n\n`url` · `text` · `wifi` · `email` · `tel` · `sms` · `geo` · `vcard` · `event` · `whatsapp` ·\n`crypto` · `dynamic`\n\n**`dynamic` is the commercial one.** It builds `<base>/api/qr/r/<code>` — the short link whose\ndestination stays editable after the code is printed. It is a builder rather than a string assembled\nat three call sites because the redirect route, the generator and the printed artwork must agree on\nthat URL *for ever*; a printed code cannot be reissued when they drift.\n\n**There is deliberately no `pay` builder.** A payment link is a plain URL whose shape differs per\nprovider and changes without notice, and getting one wrong sends somebody's money nowhere. Build the\nURL you have verified and pass it to `url`. `whatsapp` and `crypto` are here because both have a\npublished, stable URI scheme rather than a marketing URL that happens to work today.\n\n[warning] **`crypto` amounts are not one unit.** BIP-21 (`bitcoin:` and friends) takes whole coins; EIP-681\n(`ethereum:`) takes **wei**. This package passes each through unchanged and converts nothing — a form\nmust label its own field.\n\n## Styling\n\nModule shapes `square` · `dot` · `rounded` · `diamond`. Eye frames `square` · `rounded` · `circle` ·\n`leaf`. Pupils `square` · `dot` · `rounded`. Plus flat or gradient foreground, per-eye colours, quiet\nzone, error-correction level, a centred logo, and a caption bar.\n\nFour module shapes, not six, and none of them joins to its neighbours. The connected \"classy\" look\nevery styling library ships needs each module to know which neighbours are filled, and gets subtly\nwrong results at the finder boundaries and the timing rows. Four that are exactly right beat six with\ntwo quietly deformed.\n\n## The readability check is the point\n\n`resolveStyle` **always returns a usable style plus a list of warnings** — it never throws. Almost\nevery bad style is still drawable: a pale code on white renders beautifully and no phone reads it; a\nlogo over a third of the modules produces a lovely image that fails at the till. Throwing would block\na preview the user is entitled to see; staying silent would ship the failure.\n\n- `low_contrast` — **error** below 3:1, warning below 4.5:1.\n- `inverted` — warning. Light-on-dark scans on modern phones and fails on older and industrial readers.\n- `logo_too_large` — **error** above 25% width, where even level H starts to fail.\n- `quiet_zone_small` — warning below the 4 modules ISO/IEC 18004 requires.\n- `ec_raised` — informational: a logo silently raises error correction to `H`, because that repair has\n  no downside and forgetting it has a large one. It happens *before* encoding; raising it afterwards\n  would change nothing.\n\n`scannable` is false when any warning is an error. **A save lane must read it.**\n\n## Formats\n\n- **SVG** — the vector deliverable, and what a printer wants.\n- **PDF** — a real vector PDF, written here with no library: because `./plan.js` emits only\n  rectangles-with-radii and polygons, every shape converts to Bézier operators with no loss.\n  Gradients become PDF axial/radial shadings. **A logo is omitted** and reported as `logoOmitted` —\n  embedding an arbitrary image means owning a PNG decoder or putting JPEG ringing on the hard edges a\n  scanner samples. Export PNG or SVG for a logo'd code.\n- **PNG / JPEG / WebP** — `./raster`, via sharp rendering the SVG. `rasterizeForPrint({mm, dpi})`\n  sizes by physical width, because \"2000px\" says nothing about whether a module survives the press.\n\n[warning] `rasterize`'s `density` is the grid sharp rasterises the SVG at *before* scaling to `width`. Left\nlow, a large `width` produces a big **fuzzy** image rather than a big sharp one — that is the actual\ncause of soft QR exports.\n\n## Tests\n\n`node --test test/` — 24 checks. The central one rasterises at an odd number of pixels per module,\nsamples the centre of every module, and asserts the result **is** the source matrix, for every module\nshape and at all three finders. There is no QR decoder in this workspace, and adding one would test\nsomebody else's library; what *this* package can get wrong is the drawing — a transposed matrix, an\neye one module out, a shape that misses its own cell — and every one of those produces a plausible\nimage that throws nothing.\n",
  "source": {
    "path": "shared/packages/qr/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 6181,
    "hash": "61a7df98d426aa7c79d4015789c27a3f633372cb"
  },
  "urls": {
    "html": "/p/packages/qr",
    "json": "/docs/packages/qr.json",
    "md": "/docs/packages/qr.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/config-registry"
    ],
    "usedBy": [
      "pkg:@leumas/adapter-qr"
    ],
    "product": [],
    "howTo": [],
    "skills": []
  },
  "exports": {
    "total": 74,
    "component": 19,
    "hook": 0,
    "helper": 55,
    "names": [
      {
        "n": "DEFAULT_DYNAMIC_BASE",
        "k": "component"
      },
      {
        "n": "DEFAULT_DYNAMIC_BASE",
        "k": "component"
      },
      {
        "n": "DYNAMIC_PATH",
        "k": "component"
      },
      {
        "n": "DYNAMIC_PATH",
        "k": "component"
      },
      {
        "n": "ERROR_LEVELS",
        "k": "component"
      },
      {
        "n": "ERROR_LEVELS",
        "k": "component"
      },
      {
        "n": "EYE_BALL_SHAPES",
        "k": "component"
      },
      {
        "n": "EYE_BALL_SHAPES",
        "k": "component"
      },
      {
        "n": "EYE_FRAME_SHAPES",
        "k": "component"
      },
      {
        "n": "EYE_FRAME_SHAPES",
        "k": "component"
      },
      {
        "n": "FRAME_STYLES",
        "k": "component"
      },
      {
        "n": "FRAME_STYLES",
        "k": "component"
      },
      {
        "n": "MODULE_SHAPES",
        "k": "component"
      },
      {
        "n": "MODULE_SHAPES",
        "k": "component"
      },
      {
        "n": "PRINT_DPI",
        "k": "component"
      },
      {
        "n": "SPEC_QUIET_ZONE",
        "k": "component"
      },
      {
        "n": "SPEC_QUIET_ZONE",
        "k": "component"
      },
      {
        "n": "STYLE_PRESETS",
        "k": "component"
      },
      {
        "n": "STYLE_PRESETS",
        "k": "component"
      },
      {
        "n": "assemblePdf",
        "k": "helper"
      },
      {
        "n": "assemblePdf",
        "k": "helper"
      },
      {
        "n": "buildPayload",
        "k": "helper"
      },
      {
        "n": "buildPayload",
        "k": "helper"
      },
      {
        "n": "contrastWarnings",
        "k": "helper"
      },
      {
        "n": "contrastWarnings",
        "k": "helper"
      },
      {
        "n": "encodeMatrix",
        "k": "helper"
      },
      {
        "n": "encodeMatrix",
        "k": "helper"
      },
      {
        "n": "eyeBallShape",
        "k": "helper"
      },
      {
        "n": "eyeBallShape",
        "k": "helper"
      },
      {
        "n": "eyeFrameShape",
        "k": "helper"
      },
      {
        "n": "eyeFrameShape",
        "k": "helper"
      },
      {
        "n": "finderOrigins",
        "k": "helper"
      },
      {
        "n": "finderOrigins",
        "k": "helper"
      },
      {
        "n": "generatePlan",
        "k": "helper"
      },
      {
        "n": "generateSvg",
        "k": "helper"
      },
      {
        "n": "icalStamp",
        "k": "helper"
      },
      {
        "n": "icalStamp",
        "k": "helper"
      },
      {
        "n": "isDarker",
        "k": "helper"
      },
      {
        "n": "isDarker",
        "k": "helper"
      },
      {
        "n": "isFinderCell",
        "k": "helper"
      },
      {
        "n": "isFinderCell",
        "k": "helper"
      },
      {
        "n": "listStylePresets",
        "k": "helper"
      },
      {
        "n": "listStylePresets",
        "k": "helper"
      },
      {
        "n": "moduleShape",
        "k": "helper"
      },
      {
        "n": "moduleShape",
        "k": "helper"
      },
      {
        "n": "paintRef",
        "k": "helper"
      },
      {
        "n": "paintRef",
        "k": "helper"
      },
      {
        "n": "payloadBuilders",
        "k": "helper"
      },
      {
        "n": "payloadBuilders",
        "k": "helper"
      },
      {
        "n": "payloadTypes",
        "k": "helper"
      },
      {
        "n": "payloadTypes",
        "k": "helper"
      },
      {
        "n": "pdfColor",
        "k": "helper"
      },
      {
        "n": "pdfColor",
        "k": "helper"
      },
      {
        "n": "pixelsForMm",
        "k": "helper"
      },
      {
        "n": "planCode",
        "k": "helper"
      },
      {
        "n": "planCode",
        "k": "helper"
      },
      {
        "n": "rasterize",
        "k": "helper"
      },
      {
        "n": "rasterizeForPrint",
        "k": "helper"
      },
      {
        "n": "renderPdf",
        "k": "helper"
      },
      {
        "n": "renderPdf",
        "k": "helper"
      },
      {
        "n": "renderSvg",
        "k": "helper"
      },
      {
        "n": "renderSvg",
        "k": "helper"
      },
      {
        "n": "resolveStyle",
        "k": "helper"
      },
      {
        "n": "resolveStyle",
        "k": "helper"
      },
      {
        "n": "shapeOps",
        "k": "helper"
      },
      {
        "n": "shapeOps",
        "k": "helper"
      },
      {
        "n": "shapePath",
        "k": "helper"
      },
      {
        "n": "shapePath",
        "k": "helper"
      },
      {
        "n": "svgToDataUri",
        "k": "helper"
      },
      {
        "n": "svgToDataUri",
        "k": "helper"
      },
      {
        "n": "textWidthEm",
        "k": "helper"
      },
      {
        "n": "textWidthEm",
        "k": "helper"
      },
      {
        "n": "toLatin1",
        "k": "helper"
      },
      {
        "n": "toLatin1",
        "k": "helper"
      }
    ]
  }
}
