qr-kit 2.1.0 โ 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -323
- package/package.json +1 -1
- package/qr/qr-core.js +333 -178
- package/renderers/svg.js +23 -40
package/README.md
CHANGED
|
@@ -1,323 +1,323 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# ๐ฏ qr-kit
|
|
4
|
-
|
|
5
|
-
**Complete QR code toolkit. Zero dependencies. 5.4 kB core.**
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/qr-kit)
|
|
8
|
-
[](https://bundlephobia.com/package/qr-kit)
|
|
9
|
-
[](tests/)
|
|
10
|
-
[](LICENSE)
|
|
11
|
-
|
|
12
|
-
**[๐ Live Playground](playground/index.html)** ยท **[๐ Docs](#installation)** ยท **[๐จ Examples](#quick-start)**
|
|
13
|
-
|
|
14
|
-
<img src="docs/hero-demo.gif" width="600" alt="QR code with logo overlay demo" />
|
|
15
|
-
|
|
16
|
-
*Generate QR codes with logos, export to PDF, optimize URLs โ all in the browser, zero dependencies.*
|
|
17
|
-
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## โจ Why this library?
|
|
23
|
-
|
|
24
|
-
Every QR library on npm either:
|
|
25
|
-
- ๐ฆ Pulls in 10+ dependencies
|
|
26
|
-
- ๐ Bundles 150+ kB of minified code
|
|
27
|
-
- ๐จ Requires a build step (TypeScript, Babel, Webpack)
|
|
28
|
-
- ๐ Works only in Node.js OR only in browser
|
|
29
|
-
|
|
30
|
-
**This library:**
|
|
31
|
-
- โ
Zero dependencies โ literally `"dependencies": {}`
|
|
32
|
-
- โ
5.4 kB gzipped core โ smaller than most images on your page
|
|
33
|
-
- โ
Ships as ES modules โ use directly, no build step
|
|
34
|
-
- โ
Works everywhere โ Node, Deno, Cloudflare Workers, browser, Web Worker
|
|
35
|
-
- โ
Logo overlay with ECC budget enforcement
|
|
36
|
-
- โ
PDF export, poster generation, link optimization
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## ๐ Quick start
|
|
41
|
-
|
|
42
|
-
### Install
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npm install qr-kit
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Basic QR in React (3 lines)
|
|
49
|
-
|
|
50
|
-
```jsx
|
|
51
|
-
import QRCodeGenerator from 'qr-kit';
|
|
52
|
-
|
|
53
|
-
export default () => <QRCodeGenerator value="https://example.com" />;
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Output:** Crisp SVG, single `<path>` element (not 500 `<rect>`), accessible, scales infinitely.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## ๐จ Logo overlay โ the killer feature
|
|
61
|
-
|
|
62
|
-
```jsx
|
|
63
|
-
import { makeQr } from 'qr-kit';
|
|
64
|
-
import { buildQrWithLogoSvgAsync } from 'qr-kit/utils/logo';
|
|
65
|
-
|
|
66
|
-
const model = makeQr('https://example.com', { eccLevel: 'M' });
|
|
67
|
-
const svg = await buildQrWithLogoSvgAsync(model, '/logo.svg', {
|
|
68
|
-
size: 400,
|
|
69
|
-
maxCoverage: 0.11, // 11% of QR area โ safe for ECC M
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
document.body.innerHTML = svg; // self-contained SVG string
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**How it works:**
|
|
76
|
-
- QR rendered in 3 layers: data โ logo โ finder patterns (always on top)
|
|
77
|
-
- ECC M budget: 11% coverage leaves 4% safety margin
|
|
78
|
-
- Zero-DOM implementation โ works in Node.js, Cloudflare Workers, anywhere
|
|
79
|
-
|
|
80
|
-
<div align="center">
|
|
81
|
-
<img src="docs/logo-overlay-example.png" width="300" alt="QR with company logo" />
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## ๐ฆ Tiny bundle, huge features
|
|
87
|
-
|
|
88
|
-
| Feature | Size (gzip) | Description |
|
|
89
|
-
|---------|-------------|-------------|
|
|
90
|
-
| **qr-core** | 4.7 kB | Pure QR engine โ works everywhere |
|
|
91
|
-
| **React component** | +1.1 kB | SVG component with `forwardRef` |
|
|
92
|
-
| **Logo overlay** | +3.0 kB | Embed logos with ECC enforcement |
|
|
93
|
-
| **PDF export** | +4.4 kB | Generate PDFs in browser |
|
|
94
|
-
| **Link optimizer** | +2.1 kB | Fit URLs in QR byte budget |
|
|
95
|
-
| **Web Worker** | +0.8 kB | Off-thread for v10-12 |
|
|
96
|
-
|
|
97
|
-
**Total library:** 26.7 kB gzip
|
|
98
|
-
**Core only:** 5.4 kB gzip (qr-core + layout + url utilities)
|
|
99
|
-
|
|
100
|
-
Tree-shake what you don't need. Import only what you use.
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## ๐ฅ More examples
|
|
105
|
-
|
|
106
|
-
### Export to PNG/JPEG/PDF
|
|
107
|
-
|
|
108
|
-
```js
|
|
109
|
-
import { downloadQrPng } from 'qr-kit/utils/raster';
|
|
110
|
-
import { downloadQrJpeg } from 'qr-kit/utils/jpegQr';
|
|
111
|
-
import { downloadQrPdf } from 'qr-kit/utils/pdf';
|
|
112
|
-
|
|
113
|
-
// PNG at 3ร scale
|
|
114
|
-
await downloadQrPng(svgRef.current, { scale: 3 });
|
|
115
|
-
|
|
116
|
-
// JPEG (direct from canvas, no SVG round-trip)
|
|
117
|
-
await downloadQrJpeg({ value: 'https://example.com', size: 300 });
|
|
118
|
-
|
|
119
|
-
// PDF with title and metadata
|
|
120
|
-
await downloadQrPdf({
|
|
121
|
-
svgEl: svgRef.current,
|
|
122
|
-
title: 'Event QR Code',
|
|
123
|
-
org: 'Your Company',
|
|
124
|
-
url: 'https://example.com',
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Optimize URLs for QR (Link Builder)
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
import { buildQrLink } from 'qr-kit/utils/link';
|
|
132
|
-
|
|
133
|
-
const { qrUrl, fullUrl, trimmed } = buildQrLink({
|
|
134
|
-
baseUrl: 'https://example.com/app',
|
|
135
|
-
payload: { userId: 'abc123', campaign: 'summer2024promo' },
|
|
136
|
-
budget: 120, // target bytes
|
|
137
|
-
strategy: 'trim', // shorten campaign if needed
|
|
138
|
-
trimKey: 'campaign',
|
|
139
|
-
removeProtocol: true, // saves 8 bytes
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// qrUrl: "example.com/app?d={...shortened...}"
|
|
143
|
-
// fullUrl: "https://example.com/app?d={...full payload...}"
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**Use case:** Generate short QR codes, redirect to full URLs on server.
|
|
147
|
-
|
|
148
|
-
### Composite QR onto background image (Poster)
|
|
149
|
-
|
|
150
|
-
```js
|
|
151
|
-
import { downloadQrComposite } from 'qr-kit/utils/poster';
|
|
152
|
-
|
|
153
|
-
await downloadQrComposite({
|
|
154
|
-
svgEl: qrRef.current,
|
|
155
|
-
templateSrc: '/event-poster.jpg',
|
|
156
|
-
qr: { x: 50, y: 400, size: 200 }, // position in px
|
|
157
|
-
scale: 2, // 2ร for print
|
|
158
|
-
fileName: 'poster.jpg',
|
|
159
|
-
});
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
<div align="center">
|
|
163
|
-
<img src="docs/poster-example.png" width="400" alt="QR on event poster" />
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
### Branded QR (custom colors for finder patterns)
|
|
167
|
-
|
|
168
|
-
```js
|
|
169
|
-
import { makeQrSvgString } from 'qr-kit/renderers/svg';
|
|
170
|
-
|
|
171
|
-
const svg = makeQrSvgString(model, {
|
|
172
|
-
fg: '#000', // data modules
|
|
173
|
-
fnColor: '#f97316', // finder patterns (3 corners)
|
|
174
|
-
bg: '#fff',
|
|
175
|
-
});
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Web Worker (no jank on v10-12)
|
|
179
|
-
|
|
180
|
-
```jsx
|
|
181
|
-
import { useQrWorker } from 'qr-kit';
|
|
182
|
-
|
|
183
|
-
function MyQr({ url }) {
|
|
184
|
-
const { model, error, pending } = useQrWorker(url, { maxVersion: 10 });
|
|
185
|
-
|
|
186
|
-
if (pending) return <Spinner />;
|
|
187
|
-
return <canvas ref={useQrCanvas(model)} />;
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
**When to use:**
|
|
192
|
-
- `useQrCode` (sync) โ fast, zero overhead, v1-6
|
|
193
|
-
- `useQrWorker` (async) โ prevents jank on v7-12 or real-time input
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## ๐ฎ Interactive playground
|
|
198
|
-
|
|
199
|
-
Open [`playground/index.html`](playground/index.html) in your browser โ no build, no server.
|
|
200
|
-
|
|
201
|
-
**Features:**
|
|
202
|
-
- 4 render modes: Basic, Rounded, Branded, Logo
|
|
203
|
-
- Link Builder with real-time byte counting
|
|
204
|
-
- PDF Export with metadata
|
|
205
|
-
- All export formats (SVG, PNG, JPEG)
|
|
206
|
-
- Live code examples
|
|
207
|
-
|
|
208
|
-
**Works offline.** Single HTML file, 53 kB.
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## ๐๏ธ Architecture
|
|
213
|
-
|
|
214
|
-
Three layers, zero coupling:
|
|
215
|
-
|
|
216
|
-
**Layer 1 โ Pure computation** (Node, Deno, Workers, browser)
|
|
217
|
-
```js
|
|
218
|
-
import { makeQr } from 'qr-kit/qr/qr-core';
|
|
219
|
-
// โ { modules: Uint8Array, functionMask: Uint8Array, version, size, eccLevel }
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
**Layer 2 โ Rendering adapters** (return data, no side effects)
|
|
223
|
-
```js
|
|
224
|
-
import { makeQrSvgString } from 'qr-kit/renderers/svg';
|
|
225
|
-
import { renderQrToCanvas } from 'qr-kit/renderers/canvas';
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**Layer 3 โ Browser actions** (downloads, side effects)
|
|
229
|
-
```js
|
|
230
|
-
import { downloadQrPng } from 'qr-kit/utils/raster';
|
|
231
|
-
// Internally calls Layer 2 โ triggers download
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
**Design principle:** Functions return data, not perform actions.
|
|
235
|
-
Every `download*` function has a `build*Bytes` / `build*Blob` primitive.
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## ๐ Bundle size comparison
|
|
240
|
-
|
|
241
|
-
| Library | Size (gzip) | Dependencies | Logo overlay |
|
|
242
|
-
|---------|-------------|--------------|--------------|
|
|
243
|
-
| **qr-kit** | **5.4 kB** | **0** | โ
|
|
|
244
|
-
| qrcode | 29.8 kB | 4 | โ |
|
|
245
|
-
| qr-code-generator | 7.2 kB | 0 | โ |
|
|
246
|
-
| node-qrcode | 52.1 kB | 6 | โ |
|
|
247
|
-
|
|
248
|
-
*Core bundle size. Full library with all utilities: 26.7 kB gzip.*
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## ๐งช Testing
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
npm test # Run 109 tests
|
|
256
|
-
npm run size # Bundle size report
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
**Test coverage:**
|
|
260
|
-
- โ
20 unit tests (qr-core, layout, svg, url, link, logo)
|
|
261
|
-
- โ
23 logo overlay tests (ECC budget, constraints, rendering)
|
|
262
|
-
- โ
8 property-based tests (500-10K random inputs)
|
|
263
|
-
- โ
8 golden file tests (regression against known outputs)
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## ๐ API Reference
|
|
268
|
-
|
|
269
|
-
Full API docs: [`types/index.d.ts`](types/index.d.ts)
|
|
270
|
-
|
|
271
|
-
**Core:**
|
|
272
|
-
- `makeQr(text, opts)` โ Generate QR model
|
|
273
|
-
- `getModule(model, x, y)` โ Read module at position
|
|
274
|
-
- `isFunctionModule(model, x, y)` โ Check if module is functional
|
|
275
|
-
|
|
276
|
-
**Renderers:**
|
|
277
|
-
- `makeQrPath(model, opts)` โ SVG `<path>` d-attribute
|
|
278
|
-
- `makeQrPathSplit(model, opts)` โ Separate data/function paths
|
|
279
|
-
- `makeQrSvgString(model, opts)` โ Complete SVG markup
|
|
280
|
-
- `renderQrToCanvas(model, canvas, opts)` โ Draw on canvas
|
|
281
|
-
|
|
282
|
-
**Logo overlay:**
|
|
283
|
-
- `makeQrWithLogoSvg(model, logoDataUrl, opts)` โ SVG with logo
|
|
284
|
-
- `getLogoConstraints(model, size, margin)` โ Max safe logo size
|
|
285
|
-
- `buildQrWithLogoSvgAsync(model, logoSrc, opts)` โ Browser helper
|
|
286
|
-
|
|
287
|
-
**Exports:**
|
|
288
|
-
- `downloadQrPng(svgEl, opts)` โ PNG export
|
|
289
|
-
- `downloadQrJpeg(opts)` โ JPEG export (canvas-based)
|
|
290
|
-
- `downloadQrPdf(opts)` โ PDF with QR + metadata
|
|
291
|
-
- `downloadQrComposite(opts)` โ QR on background image
|
|
292
|
-
|
|
293
|
-
**Utilities:**
|
|
294
|
-
- `buildQrLink(opts)` โ Optimize URL for QR byte budget
|
|
295
|
-
- `sanitizeUrlForQR(url, opts)` โ Strip tracking params
|
|
296
|
-
- `utf8ByteLen(str)` โ Count UTF-8 bytes
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## ๐ค Contributing
|
|
301
|
-
|
|
302
|
-
See [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
303
|
-
|
|
304
|
-
**Philosophy:**
|
|
305
|
-
- Zero dependencies, always
|
|
306
|
-
- No build step โ ship source as ES modules
|
|
307
|
-
- Browser-only features are opt-in (deep imports)
|
|
308
|
-
- Every function is testable in Node.js
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## ๐ License
|
|
313
|
-
|
|
314
|
-
MIT ยฉ [
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
## ๐ Show your support
|
|
319
|
-
|
|
320
|
-
If this library saved you time, give it a โญ on [GitHub](https://github.com/
|
|
321
|
-
|
|
322
|
-
**Built with โค๏ธ to prove that npm packages don't need dependencies to be powerful.**
|
|
323
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ฏ qr-kit
|
|
4
|
+
|
|
5
|
+
**Complete QR code toolkit. Zero dependencies. 5.4 kB core.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/qr-kit)
|
|
8
|
+
[](https://bundlephobia.com/package/qr-kit)
|
|
9
|
+
[](tests/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
**[๐ Live Playground](playground/index.html)** ยท **[๐ Docs](#installation)** ยท **[๐จ Examples](#quick-start)**
|
|
13
|
+
|
|
14
|
+
<img src="docs/hero-demo.gif" width="600" alt="QR code with logo overlay demo" />
|
|
15
|
+
|
|
16
|
+
*Generate QR codes with logos, export to PDF, optimize URLs โ all in the browser, zero dependencies.*
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## โจ Why this library?
|
|
23
|
+
|
|
24
|
+
Every QR library on npm either:
|
|
25
|
+
- ๐ฆ Pulls in 10+ dependencies
|
|
26
|
+
- ๐ Bundles 150+ kB of minified code
|
|
27
|
+
- ๐จ Requires a build step (TypeScript, Babel, Webpack)
|
|
28
|
+
- ๐ Works only in Node.js OR only in browser
|
|
29
|
+
|
|
30
|
+
**This library:**
|
|
31
|
+
- โ
Zero dependencies โ literally `"dependencies": {}`
|
|
32
|
+
- โ
5.4 kB gzipped core โ smaller than most images on your page
|
|
33
|
+
- โ
Ships as ES modules โ use directly, no build step
|
|
34
|
+
- โ
Works everywhere โ Node, Deno, Cloudflare Workers, browser, Web Worker
|
|
35
|
+
- โ
Logo overlay with ECC budget enforcement
|
|
36
|
+
- โ
PDF export, poster generation, link optimization
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ๐ Quick start
|
|
41
|
+
|
|
42
|
+
### Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install qr-kit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Basic QR in React (3 lines)
|
|
49
|
+
|
|
50
|
+
```jsx
|
|
51
|
+
import QRCodeGenerator from 'qr-kit';
|
|
52
|
+
|
|
53
|
+
export default () => <QRCodeGenerator value="https://example.com" />;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Output:** Crisp SVG, single `<path>` element (not 500 `<rect>`), accessible, scales infinitely.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ๐จ Logo overlay โ the killer feature
|
|
61
|
+
|
|
62
|
+
```jsx
|
|
63
|
+
import { makeQr } from 'qr-kit';
|
|
64
|
+
import { buildQrWithLogoSvgAsync } from 'qr-kit/utils/logo';
|
|
65
|
+
|
|
66
|
+
const model = makeQr('https://example.com', { eccLevel: 'M' });
|
|
67
|
+
const svg = await buildQrWithLogoSvgAsync(model, '/logo.svg', {
|
|
68
|
+
size: 400,
|
|
69
|
+
maxCoverage: 0.11, // 11% of QR area โ safe for ECC M
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
document.body.innerHTML = svg; // self-contained SVG string
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**How it works:**
|
|
76
|
+
- QR rendered in 3 layers: data โ logo โ finder patterns (always on top)
|
|
77
|
+
- ECC M budget: 11% coverage leaves 4% safety margin
|
|
78
|
+
- Zero-DOM implementation โ works in Node.js, Cloudflare Workers, anywhere
|
|
79
|
+
|
|
80
|
+
<div align="center">
|
|
81
|
+
<img src="docs/logo-overlay-example.png" width="300" alt="QR with company logo" />
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## ๐ฆ Tiny bundle, huge features
|
|
87
|
+
|
|
88
|
+
| Feature | Size (gzip) | Description |
|
|
89
|
+
|---------|-------------|-------------|
|
|
90
|
+
| **qr-core** | 4.7 kB | Pure QR engine โ works everywhere |
|
|
91
|
+
| **React component** | +1.1 kB | SVG component with `forwardRef` |
|
|
92
|
+
| **Logo overlay** | +3.0 kB | Embed logos with ECC enforcement |
|
|
93
|
+
| **PDF export** | +4.4 kB | Generate PDFs in browser |
|
|
94
|
+
| **Link optimizer** | +2.1 kB | Fit URLs in QR byte budget |
|
|
95
|
+
| **Web Worker** | +0.8 kB | Off-thread for v10-12 |
|
|
96
|
+
|
|
97
|
+
**Total library:** 26.7 kB gzip
|
|
98
|
+
**Core only:** 5.4 kB gzip (qr-core + layout + url utilities)
|
|
99
|
+
|
|
100
|
+
Tree-shake what you don't need. Import only what you use.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## ๐ฅ More examples
|
|
105
|
+
|
|
106
|
+
### Export to PNG/JPEG/PDF
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
import { downloadQrPng } from 'qr-kit/utils/raster';
|
|
110
|
+
import { downloadQrJpeg } from 'qr-kit/utils/jpegQr';
|
|
111
|
+
import { downloadQrPdf } from 'qr-kit/utils/pdf';
|
|
112
|
+
|
|
113
|
+
// PNG at 3ร scale
|
|
114
|
+
await downloadQrPng(svgRef.current, { scale: 3 });
|
|
115
|
+
|
|
116
|
+
// JPEG (direct from canvas, no SVG round-trip)
|
|
117
|
+
await downloadQrJpeg({ value: 'https://example.com', size: 300 });
|
|
118
|
+
|
|
119
|
+
// PDF with title and metadata
|
|
120
|
+
await downloadQrPdf({
|
|
121
|
+
svgEl: svgRef.current,
|
|
122
|
+
title: 'Event QR Code',
|
|
123
|
+
org: 'Your Company',
|
|
124
|
+
url: 'https://example.com',
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Optimize URLs for QR (Link Builder)
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
import { buildQrLink } from 'qr-kit/utils/link';
|
|
132
|
+
|
|
133
|
+
const { qrUrl, fullUrl, trimmed } = buildQrLink({
|
|
134
|
+
baseUrl: 'https://example.com/app',
|
|
135
|
+
payload: { userId: 'abc123', campaign: 'summer2024promo' },
|
|
136
|
+
budget: 120, // target bytes
|
|
137
|
+
strategy: 'trim', // shorten campaign if needed
|
|
138
|
+
trimKey: 'campaign',
|
|
139
|
+
removeProtocol: true, // saves 8 bytes
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// qrUrl: "example.com/app?d={...shortened...}"
|
|
143
|
+
// fullUrl: "https://example.com/app?d={...full payload...}"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Use case:** Generate short QR codes, redirect to full URLs on server.
|
|
147
|
+
|
|
148
|
+
### Composite QR onto background image (Poster)
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import { downloadQrComposite } from 'qr-kit/utils/poster';
|
|
152
|
+
|
|
153
|
+
await downloadQrComposite({
|
|
154
|
+
svgEl: qrRef.current,
|
|
155
|
+
templateSrc: '/event-poster.jpg',
|
|
156
|
+
qr: { x: 50, y: 400, size: 200 }, // position in px
|
|
157
|
+
scale: 2, // 2ร for print
|
|
158
|
+
fileName: 'poster.jpg',
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
<div align="center">
|
|
163
|
+
<img src="docs/poster-example.png" width="400" alt="QR on event poster" />
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
### Branded QR (custom colors for finder patterns)
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
import { makeQrSvgString } from 'qr-kit/renderers/svg';
|
|
170
|
+
|
|
171
|
+
const svg = makeQrSvgString(model, {
|
|
172
|
+
fg: '#000', // data modules
|
|
173
|
+
fnColor: '#f97316', // finder patterns (3 corners)
|
|
174
|
+
bg: '#fff',
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Web Worker (no jank on v10-12)
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
import { useQrWorker } from 'qr-kit';
|
|
182
|
+
|
|
183
|
+
function MyQr({ url }) {
|
|
184
|
+
const { model, error, pending } = useQrWorker(url, { maxVersion: 10 });
|
|
185
|
+
|
|
186
|
+
if (pending) return <Spinner />;
|
|
187
|
+
return <canvas ref={useQrCanvas(model)} />;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**When to use:**
|
|
192
|
+
- `useQrCode` (sync) โ fast, zero overhead, v1-6
|
|
193
|
+
- `useQrWorker` (async) โ prevents jank on v7-12 or real-time input
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## ๐ฎ Interactive playground
|
|
198
|
+
|
|
199
|
+
Open [`playground/index.html`](playground/index.html) in your browser โ no build, no server.
|
|
200
|
+
|
|
201
|
+
**Features:**
|
|
202
|
+
- 4 render modes: Basic, Rounded, Branded, Logo
|
|
203
|
+
- Link Builder with real-time byte counting
|
|
204
|
+
- PDF Export with metadata
|
|
205
|
+
- All export formats (SVG, PNG, JPEG)
|
|
206
|
+
- Live code examples
|
|
207
|
+
|
|
208
|
+
**Works offline.** Single HTML file, 53 kB.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## ๐๏ธ Architecture
|
|
213
|
+
|
|
214
|
+
Three layers, zero coupling:
|
|
215
|
+
|
|
216
|
+
**Layer 1 โ Pure computation** (Node, Deno, Workers, browser)
|
|
217
|
+
```js
|
|
218
|
+
import { makeQr } from 'qr-kit/qr/qr-core';
|
|
219
|
+
// โ { modules: Uint8Array, functionMask: Uint8Array, version, size, eccLevel }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Layer 2 โ Rendering adapters** (return data, no side effects)
|
|
223
|
+
```js
|
|
224
|
+
import { makeQrSvgString } from 'qr-kit/renderers/svg';
|
|
225
|
+
import { renderQrToCanvas } from 'qr-kit/renderers/canvas';
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Layer 3 โ Browser actions** (downloads, side effects)
|
|
229
|
+
```js
|
|
230
|
+
import { downloadQrPng } from 'qr-kit/utils/raster';
|
|
231
|
+
// Internally calls Layer 2 โ triggers download
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Design principle:** Functions return data, not perform actions.
|
|
235
|
+
Every `download*` function has a `build*Bytes` / `build*Blob` primitive.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## ๐ Bundle size comparison
|
|
240
|
+
|
|
241
|
+
| Library | Size (gzip) | Dependencies | Logo overlay |
|
|
242
|
+
|---------|-------------|--------------|--------------|
|
|
243
|
+
| **qr-kit** | **5.4 kB** | **0** | โ
|
|
|
244
|
+
| qrcode | 29.8 kB | 4 | โ |
|
|
245
|
+
| qr-code-generator | 7.2 kB | 0 | โ |
|
|
246
|
+
| node-qrcode | 52.1 kB | 6 | โ |
|
|
247
|
+
|
|
248
|
+
*Core bundle size. Full library with all utilities: 26.7 kB gzip.*
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## ๐งช Testing
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm test # Run 109 tests
|
|
256
|
+
npm run size # Bundle size report
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Test coverage:**
|
|
260
|
+
- โ
20 unit tests (qr-core, layout, svg, url, link, logo)
|
|
261
|
+
- โ
23 logo overlay tests (ECC budget, constraints, rendering)
|
|
262
|
+
- โ
8 property-based tests (500-10K random inputs)
|
|
263
|
+
- โ
8 golden file tests (regression against known outputs)
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## ๐ API Reference
|
|
268
|
+
|
|
269
|
+
Full API docs: [`types/index.d.ts`](types/index.d.ts)
|
|
270
|
+
|
|
271
|
+
**Core:**
|
|
272
|
+
- `makeQr(text, opts)` โ Generate QR model
|
|
273
|
+
- `getModule(model, x, y)` โ Read module at position
|
|
274
|
+
- `isFunctionModule(model, x, y)` โ Check if module is functional
|
|
275
|
+
|
|
276
|
+
**Renderers:**
|
|
277
|
+
- `makeQrPath(model, opts)` โ SVG `<path>` d-attribute
|
|
278
|
+
- `makeQrPathSplit(model, opts)` โ Separate data/function paths
|
|
279
|
+
- `makeQrSvgString(model, opts)` โ Complete SVG markup
|
|
280
|
+
- `renderQrToCanvas(model, canvas, opts)` โ Draw on canvas
|
|
281
|
+
|
|
282
|
+
**Logo overlay:**
|
|
283
|
+
- `makeQrWithLogoSvg(model, logoDataUrl, opts)` โ SVG with logo
|
|
284
|
+
- `getLogoConstraints(model, size, margin)` โ Max safe logo size
|
|
285
|
+
- `buildQrWithLogoSvgAsync(model, logoSrc, opts)` โ Browser helper
|
|
286
|
+
|
|
287
|
+
**Exports:**
|
|
288
|
+
- `downloadQrPng(svgEl, opts)` โ PNG export
|
|
289
|
+
- `downloadQrJpeg(opts)` โ JPEG export (canvas-based)
|
|
290
|
+
- `downloadQrPdf(opts)` โ PDF with QR + metadata
|
|
291
|
+
- `downloadQrComposite(opts)` โ QR on background image
|
|
292
|
+
|
|
293
|
+
**Utilities:**
|
|
294
|
+
- `buildQrLink(opts)` โ Optimize URL for QR byte budget
|
|
295
|
+
- `sanitizeUrlForQR(url, opts)` โ Strip tracking params
|
|
296
|
+
- `utf8ByteLen(str)` โ Count UTF-8 bytes
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## ๐ค Contributing
|
|
301
|
+
|
|
302
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
303
|
+
|
|
304
|
+
**Philosophy:**
|
|
305
|
+
- Zero dependencies, always
|
|
306
|
+
- No build step โ ship source as ES modules
|
|
307
|
+
- Browser-only features are opt-in (deep imports)
|
|
308
|
+
- Every function is testable in Node.js
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## ๐ License
|
|
313
|
+
|
|
314
|
+
MIT ยฉ [Yaroslav3991](https://github.com/Yaroslav3991)
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## ๐ Show your support
|
|
319
|
+
|
|
320
|
+
If this library saved you time, give it a โญ on [GitHub](https://github.com/Yaroslav3991/qr-kit)!
|
|
321
|
+
|
|
322
|
+
**Built with โค๏ธ to prove that npm packages don't need dependencies to be powerful.**
|
|
323
|
+
|