{"_id":"@12-apps/qr","_rev":"3-4da3cccc7ca7d0f77c367d988210951c","name":"@12-apps/qr","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@12-apps/qr","version":"1.0.0","license":"MIT","_id":"@12-apps/qr@1.0.0","maintainers":[{"name":"tigredonorte","email":"tigredonorte3@gmail.com"}],"homepage":"https://github.com/12-apps/shared-packages#readme","bugs":{"url":"https://github.com/12-apps/shared-packages/issues"},"dist":{"shasum":"02f084402c53077aaea9b7df7e4fa0b02db3c8d9","tarball":"https://registry.npmjs.org/@12-apps/qr/-/qr-1.0.0.tgz","fileCount":7,"integrity":"sha512-vrx+jS94iUrmUlb8UCxfcPO+dtByuXyDXYD/+ADCVuxIGF6psaDKMuTfeT0QZ4ALQrZaP7Q3C788jIfX91XmKg==","signatures":[{"sig":"MEUCIGVHYGg3N2JP6980Q6rg0bJ6+/PS/F2D/5oWRh82j+GvAiEAlA4mxLzqQ6WX+MwpMasD4r5h5xCX8Bc9ufIsaPAvUkk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":38456},"type":"module","engines":{"node":">=22.0.0"},"exports":{"./pdf":"./src/pdf/index.ts","./package.json":"./package.json"},"gitHead":"22aaabb0f6802507b2cff99ab3e0694417fed489","scripts":{"lint":"eslint src --max-warnings 0","test":"node ../../scripts/vitest-with-teardown.mjs run","clean":"rm -rf node_modules coverage dist","typecheck":"tsc --noEmit","test:watch":"vitest watch","check-types":"tsc --noEmit"},"_npmUser":{"name":"tigredonorte","email":"tigredonorte3@gmail.com"},"repository":{"url":"git+https://github.com/12-apps/shared-packages.git","type":"git","directory":"packages/qr"},"_npmVersion":"12.0.2","description":"QR codes in both directions: print-ready sticker artwork a gráfica can run — trim/bleed boxes, crop marks, K-only black, error-correction H (./pdf) — and reading one back through a device camera, native BarcodeDetector where it exists and a lazily-importe","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"qrcode-generator":"^2.0.4"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","vitest":"^3.2.4","typescript":"^5.9.2","@12-apps/eslint-config":"^1.22.0","@12-apps/typescript-config":"^1.21.0","eslint-plugin-test-flakiness":"^1.4.0"},"_npmOperationalInternal":{"tmp":"tmp/qr_1.0.0_1788117448676_0.4261441856326056","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@12-apps/qr","version":"1.1.0","license":"MIT","_id":"@12-apps/qr@1.1.0","maintainers":[{"name":"tigredonorte","email":"tigredonorte3@gmail.com"}],"homepage":"https://github.com/12-apps/shared-packages#readme","bugs":{"url":"https://github.com/12-apps/shared-packages/issues"},"dist":{"shasum":"74a6388c78422c6f15784e0caa3d7f0c97d7bc80","tarball":"https://registry.npmjs.org/@12-apps/qr/-/qr-1.1.0.tgz","fileCount":11,"integrity":"sha512-YqVSPvqVD8lk9QTmGuRVOO6qpZ9G6JIn9KkKSpeRlxHT8LJII4NeSmY4pQP4sew0Ipmo7gj81n6hhYZQLPgDIw==","signatures":[{"sig":"MEUCIAqv88rVS0w1F4zDuZ9JlnczbTEjb6fqOZPXRwFpW/3sAiEA38xZrajQ+55I999QpK+KerSwUWqEokA4vmTEUPvdOZc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@12-apps%2fqr@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":60680},"type":"module","engines":{"node":">=22.0.0"},"exports":{"./pdf":"./src/pdf/index.ts","./scan":"./src/scan/index.ts","./package.json":"./package.json"},"gitHead":"833bf60c5ae8bc050195dc15154c19a58b7917ea","scripts":{"lint":"eslint src --max-warnings 0","test":"node ../../scripts/vitest-with-teardown.mjs run","clean":"rm -rf node_modules coverage dist","typecheck":"tsc --noEmit","test:watch":"vitest watch","check-types":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f3f8a87b-2955-4401-8eca-78507477553d"}},"repository":{"url":"git+https://github.com/12-apps/shared-packages.git","type":"git","directory":"packages/qr"},"_npmVersion":"12.0.2","description":"QR codes in both directions: print-ready sticker artwork a gráfica can run — trim/bleed boxes, crop marks, K-only black, error-correction H (./pdf) — and reading one back through a device camera, native BarcodeDetector where it exists and a lazily-importe","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"jsqr":"^1.4.0","qrcode-generator":"^2.0.4"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^26.1.0","react":"^19.2.0","eslint":"^9.39.1","vitest":"^3.2.4","react-dom":"^19.2.0","typescript":"^5.9.2","@types/react":"^19.2.0","@12-apps/eslint-config":"^1.22.0","@testing-library/react":"^16.1.0","@12-apps/typescript-config":"^1.21.0","eslint-plugin-test-flakiness":"^1.4.0"},"peerDependencies":{"react":">=19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/qr_1.1.0_1788230476971_0.6505258494434287","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@12-apps/qr","version":"1.2.0","type":"module","sideEffects":false,"description":"QR codes in both directions: print-ready sticker artwork a gráfica can run — trim/bleed boxes, crop marks, K-only black, error-correction H (./pdf) — and reading one back through a device camera, native BarcodeDetector where it exists and a lazily-importe","exports":{"./pdf":"./src/pdf/index.ts","./scan":"./src/scan/index.ts","./package.json":"./package.json"},"scripts":{"clean":"rm -rf node_modules coverage dist","test":"node ../../scripts/vitest-with-teardown.mjs run","test:watch":"vitest watch","lint":"eslint src --max-warnings 0","check-types":"tsc --noEmit","typecheck":"tsc --noEmit"},"dependencies":{"jsqr":"^1.4.0","qrcode-generator":"^2.0.4"},"devDependencies":{"@12-apps/eslint-config":"^1.22.0","@12-apps/typescript-config":"^1.21.0","@testing-library/react":"^16.1.0","@types/react":"^19.2.0","eslint":"^9.39.1","eslint-plugin-test-flakiness":"^1.4.0","jsdom":"^26.1.0","react":"^19.2.0","react-dom":"^19.2.0","typescript":"^5.9.2","vitest":"^3.2.4"},"engines":{"node":">=22.0.0"},"license":"MIT","publishConfig":{"registry":"https://registry.npmjs.org","access":"public"},"repository":{"type":"git","url":"git+https://github.com/12-apps/shared-packages.git","directory":"packages/qr"},"peerDependencies":{"react":">=19.0.0"},"gitHead":"7eaaa8ccf6e7d201eb9d10af9b1a89f06045b63f","_id":"@12-apps/qr@1.2.0","bugs":{"url":"https://github.com/12-apps/shared-packages/issues"},"homepage":"https://github.com/12-apps/shared-packages#readme","_nodeVersion":"24.20.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-IUZZbhlZ9vmQk4MwCrZNnOnoaCXh/PhWbhzgsxnWr5+yHB1+qODNvw0dl3zAKYlwn5SVwPus+TB0VSCvVhE0iQ==","shasum":"bafafa58a2fd9c016f1fbf04166b07283c0d2e0b","tarball":"https://registry.npmjs.org/@12-apps/qr/-/qr-1.2.0.tgz","fileCount":12,"unpackedSize":67468,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@12-apps%2fqr@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCgadIkM4YaegUSM+8SCzxXG43Y3H7JjOZTjiCRsjNm8AIgPQeVOlNNzOcd7X7TPwzC+KsOFOwBE9/w7HKsERdT9yM="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f3f8a87b-2955-4401-8eca-78507477553d"}},"directories":{},"maintainers":[{"name":"tigredonorte","email":"tigredonorte3@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/qr_1.2.0_1788538315620_0.11650163031657934"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-30T19:17:28.509Z","modified":"2026-09-04T16:11:56.125Z","1.0.0":"2026-08-30T19:17:28.813Z","1.1.0":"2026-09-01T02:41:17.109Z","1.2.0":"2026-09-04T16:11:55.751Z"},"bugs":{"url":"https://github.com/12-apps/shared-packages/issues"},"license":"MIT","homepage":"https://github.com/12-apps/shared-packages#readme","repository":{"type":"git","url":"git+https://github.com/12-apps/shared-packages.git","directory":"packages/qr"},"description":"QR codes in both directions: print-ready sticker artwork a gráfica can run — trim/bleed boxes, crop marks, K-only black, error-correction H (./pdf) — and reading one back through a device camera, native BarcodeDetector where it exists and a lazily-importe","maintainers":[{"name":"tigredonorte","email":"tigredonorte3@gmail.com"}],"readme":"# @12-apps/qr\n\nQR codes in both directions.\n\n```bash\npnpm add @12-apps/qr\n```\n\n| subpath | what it does |\n| --- | --- |\n| `@12-apps/qr/pdf` | a URL → print-ready sticker artwork a gráfica can run |\n| `@12-apps/qr/scan` | a device camera → the text a QR carries |\n\n## Why this exists\n\nBoth halves of \"QR\" are things every host eventually needs and nobody should\nwrite twice, because both fail in ways you cannot see on a screen.\n\n**Printing.** The browser will happily draw a code and `window.print()` will\nprint the page around it — chrome, sidebar and all, at whatever size the paper\nhappens to be. What a print shop actually reads is a short list, and every item\non it is invisible in a preview: the declared page boxes, whether the marks sit\noutside the trim, how many plates the black is built from, and whether the code\nkept its quiet zone. Get any one wrong and you find out when the box of\nlaminated stickers arrives.\n\n**Reading.** A camera has a lifecycle and a component has a render, and mixing\nthe two is how a scanner ends up leaving the lens on — a failure nothing crashes\non and no suite notices, whose only symptom is the indicator light on somebody's\nphone staying lit after they closed the sheet. Plus two decoders, because there\nis no one way that works: `BarcodeDetector` is the platform's own and is what\nChrome and Android have; WebKit has never shipped it, so on iOS Safari the\n\"fallback\" is not a fallback, it is the whole feature.\n\n## Printing a sticker\n\n```ts\nimport { buildQrStickerPdf, pdfDateOf, STICKER_SIZES } from \"@12-apps/qr/pdf\";\n\nconst bytes = buildQrStickerPdf({\n  stickers: [\n    {\n      label: \"Cerveja Pilsen 350ml\",\n      url: \"https://loja.example.com/market/p/abc/def\",\n      hint: \"Aponte a câmera e pague pelo celular\",\n      priceLine: \"R$ 8,90\",     // optional — see below\n    },\n  ],\n  size: STICKER_SIZES.small,     // 50 × 70 mm\n  layout: \"individual\",          // or \"sheet\" — A4 with cut guides\n  brandName: \"Bar do Zé\",\n  title: \"QR do mercado\",\n  date: pdfDateOf(new Date()),\n  creator: \"Your app\",\n});\n```\n\n`bytes` is a `Uint8Array` — hand it to a download, an upload, or a mailer. The\nbuilder is **pure**: it reads no clock (hence `date`), no locale and no DOM, so\nit runs identically in a browser, in a job, and in a test.\n\n### It prints a URL, not a table\n\nEvery word on the sticker arrives as data. There is deliberately **no copy in\nthis package** — `STICKER_SIZES` is dimensions and stable ids, and what a size\nis *called* is yours, in your languages. A package that shipped one language's\nnames would either force it on every caller or need a locale resolver in order\nto print a rectangle.\n\n### `priceLine` is a string, and optional\n\nA shelf label without a price is not a shelf label; a table's plaquinha with one\nis wrong. So it is per sticker, optional, and already **formatted** — currency\nand its placement are locale facts this package has no business deciding.\n\nThe part it cannot help you with: a price change means a reprint. Say so at your\ncall site.\n\n## Reading a code\n\n```tsx\nimport { useQrCamera } from \"@12-apps/qr/scan\";\n\nfunction Scanner({ onCode }: { onCode: (text: string) => void }) {\n  const { videoRef, fault, live } = useQrCamera(true, onCode);\n  if (fault) return <p>{yourCopy[fault]}</p>;\n  return <video ref={videoRef} autoPlay playsInline muted />;\n}\n```\n\n`fault` is one of `unsupported | denied | missing | busy | failed` — five values,\nnot five sentences, because each one is a different thing to *do* about it and\nwhat to SAY is yours, in your languages.\n\n### The pixels are not in this package\n\nA viewfinder is styled chrome. It belongs to whichever design system you already\nship, and a package bringing its own would either fight that system or drag it in\nas a dependency. You get a hook and a `<video>` ref; the surface around it is\nyours.\n\n### A scanned code is untrusted input\n\nThe hook hands you the decoded string and stops there, deliberately. It is a\nsticker — anybody can print one and put it anywhere.\n\n**Never navigate to what comes back.** Reduce it to values you already trust (an\nid you then look up), and route with those. A `javascript:` code, a foreign\norigin, a path escape and a phishing link should all end at the same honest\nrefusal. That parser is yours because only you know what a legitimate code says.\n\n### It keeps reading after a hit\n\nA decoded code is not necessarily a *usable* one — a Wi-Fi QR on the same table\ndecodes perfectly — so the loop does not stop on the first success. Ignore\nrepeats of a code you have already refused.\n\n## The decisions baked in\n\nThese are the ones that only fail on paper, so they are not configurable:\n\n| | why |\n| --- | --- |\n| `TrimBox` + `BleedBox` **declared** | that is how a RIP knows where the blade goes; artwork that only *looks* trimmed gets imposed by eye |\n| **3mm bleed**, artwork running into it | a blade landing a hair off leaves no white lip |\n| **crop marks outside the bleed** | a mark over the artwork is a mark on every sticker in the run |\n| **K-only black** | rich black is four plates, and any misregistration fringes every edge of a code whose whole job is to be read by a camera in bad light |\n| **error correction H** | it gets scratched, splashed and half-covered — H tolerates ~30% loss, the on-screen default 15% |\n| **4-module quiet zone**, drawn | the most common way a printed code stops scanning, and invisible on screen because the browser gives you one free |\n\n`layout` is the one real fork: `individual` is one page per sticker with trim,\nbleed and marks — what a print shop imposes and die-cuts. `sheet` is A4 with\nthin cut guides, for the office printer at 11pm when one label went missing.\n\n### How one sticker is composed\n\nBrand, code, name, price, hint, address — top to bottom, centred, with the code\ntaking whatever the type does not. Two rules decide the rest, and both exist\nbecause a sticker is ordered in three sizes and reviewed in one:\n\n- **The gaps are fractions of the sticker's own height, not millimetres.** The\n  gutter a 50 mm shelf label wants is not the one a 150 mm tent card wants, and\n  printing the first on the second is a composition sized for a sticker nobody\n  ordered.\n- **Leftover height is split evenly above and below, never pooled at the foot.**\n  The code is capped by the sticker's WIDTH on every preset but the smallest, so\n  its height budget is routinely unspent — and the address used to be pinned to\n  the foot, which turned that slack into one empty band. On the tent preset it\n  was 61 pt, more than a fifth of the face.\n\nSo every preset leaves the same margin at the head and the foot, and the last\nline clears the blade by the same padding as the sides.\n\n## Sizes\n\n| id | trimmed size |\n| --- | --- |\n| `small` | 50 × 70 mm |\n| `medium` | 70 × 100 mm |\n| `tent` | 100 × 150 mm |\n\nAny `{ widthMm, heightMm }` is legal; these are the common ones. Millimetres\nbecause that is the unit the conversation with a gráfica happens in.\n\n## A note on the PDF writer\n\n`@12-apps/qr/pdf` also exports the small writer underneath the sticker (`mm`,\n`rect`, `line`, `text`, `buildPdf`, `FILL_BLACK`). It is **not** a general PDF\nlibrary and will not become one: it writes filled rectangles, stroked lines and\nbase-14 text, which is all a sticker sheet needs. Anything past that is a reason\nto reach for a real library, not to grow this one.\n\nIt is published so a host that wants a different *layout* can compose these\noperators instead of forking the sticker — the parts worth sharing are the ones\nnobody can check on screen, and those stay in the builder however the page is\narranged.\n\n## Seeing the artwork\n\nYou cannot review a print job in a diff, and the failures this package guards\nagainst are all invisible on screen. To look at real output:\n\n```ts\n// scratch.ts — run it with vitest, which already resolves this package's TS\nimport { writeFileSync } from \"node:fs\";\nimport { buildQrStickerPdf, pdfDateOf, STICKER_SIZES } from \"@12-apps/qr/pdf\";\n\nwriteFileSync(\"/tmp/sample.pdf\", buildQrStickerPdf({\n  stickers: [{ label: \"Cerveja Pilsen 350ml\", url: \"https://…\", hint: \"Aponte a câmera\", priceLine: \"R$ 8,90\" }],\n  size: STICKER_SIZES.small, layout: \"individual\", brandName: \"Bar do Zé\",\n  title: \"QR do mercado\", date: pdfDateOf(new Date()), creator: \"scratch\",\n}));\n```\n\nOpen it in any reader and check the four things a test cannot judge for you: the\ncode scans with a real phone **at the distance it will be read from**, the trim\nmarks fall where you expect, the type is legible at size, and the label is not\nrunning off the sticker.\n\nThat last check matters most at the small preset with a long URL — a denser code\nmeans finer modules, and the smallest sticker is where a payload first stops\nscanning.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}