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 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
- [![npm version](https://img.shields.io/npm/v/qr-kit?color=success)](https://www.npmjs.com/package/qr-kit)
8
- [![bundle size](https://img.shields.io/bundlephobia/minzip/qr-kit?label=gzip&color=success)](https://bundlephobia.com/package/qr-kit)
9
- [![tests](https://img.shields.io/badge/tests-109%20passing-success)](tests/)
10
- [![license](https://img.shields.io/npm/l/qr-kit)](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 ยฉ [Your Name](https://github.com/your-org)
315
-
316
- ---
317
-
318
- ## ๐ŸŒŸ Show your support
319
-
320
- If this library saved you time, give it a โญ on [GitHub](https://github.com/your-org/qr-code)!
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
+ [![npm version](https://img.shields.io/npm/v/qr-kit?color=success)](https://www.npmjs.com/package/qr-kit)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/qr-kit?label=gzip&color=success)](https://bundlephobia.com/package/qr-kit)
9
+ [![tests](https://img.shields.io/badge/tests-109%20passing-success)](tests/)
10
+ [![license](https://img.shields.io/npm/l/qr-kit)](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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qr-kit",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Complete QR code toolkit. Zero dependencies. Logo overlay, PDF export, link optimizer. 5.4 kB core.",
5
5
  "type": "module",
6
6
  "main": "index.js",