quadqr-js 1.0.1 → 1.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/FORMAT.md +11 -0
- package/README.md +1222 -1057
- package/SPECIFICATION.md +332 -0
- package/bin/quadqr.js +102 -13
- package/dist/esm/benchmark.js +80 -0
- package/dist/esm/node.js +13 -1
- package/dist/esm/quadqr.js +2714 -182
- package/dist/esm/vision.js +597 -52
- package/dist/quadqr.js +3315 -244
- package/dist/quadqr.min.js +3322 -241
- package/docs/API.md +545 -309
- package/docs/BROWSER_CDN.md +187 -137
- package/docs/CLI.md +150 -76
- package/docs/GETTING_STARTED.md +247 -173
- package/docs/NODE.md +137 -123
- package/docs/README.md +59 -41
- package/docs/WASM.md +67 -67
- package/package.json +100 -95
- package/types/benchmark.d.ts +4 -3
- package/types/index.d.ts +238 -2
- package/types/node.d.ts +2 -0
package/docs/API.md
CHANGED
|
@@ -1,309 +1,545 @@
|
|
|
1
|
-
# API Reference
|
|
2
|
-
|
|
3
|
-
## Core encoding
|
|
4
|
-
|
|
5
|
-
### `encodeText(text, options?)`
|
|
6
|
-
|
|
7
|
-
Synchronously encodes UTF-8 text.
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
import { encodeText } from "quadqr-js";
|
|
11
|
-
|
|
12
|
-
const code = encodeText("Hello", {
|
|
13
|
-
ecc: "M",
|
|
14
|
-
version: 5
|
|
15
|
-
});
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
If `version` is omitted, QuadQR selects the smallest version that fits.
|
|
19
|
-
|
|
20
|
-
### `encodeBytes(bytes, options?)`
|
|
21
|
-
|
|
22
|
-
Encodes arbitrary bytes.
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
const code = encodeBytes(
|
|
26
|
-
new Uint8Array([1, 2, 3, 4]),
|
|
27
|
-
{ ecc: "Q" }
|
|
28
|
-
);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Secure encoding
|
|
32
|
-
|
|
33
|
-
### `encodeSecureText(text, options)`
|
|
34
|
-
|
|
35
|
-
Encrypts text using Secure Payload v1 and encodes the encrypted envelope.
|
|
36
|
-
|
|
37
|
-
Password mode:
|
|
38
|
-
|
|
39
|
-
```js
|
|
40
|
-
const code = await encodeSecureText("secret", {
|
|
41
|
-
security: {
|
|
42
|
-
mode: "password",
|
|
43
|
-
password: "example-password"
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Raw-key mode:
|
|
49
|
-
|
|
50
|
-
```js
|
|
51
|
-
const code = await encodeSecureText("secret", {
|
|
52
|
-
security: {
|
|
53
|
-
mode: "raw-key",
|
|
54
|
-
key
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### `encodeSecureBytes(bytes, options)`
|
|
60
|
-
|
|
61
|
-
Byte-oriented equivalent of `encodeSecureText()`.
|
|
62
|
-
|
|
63
|
-
### `decryptDecoded(result, credentials)`
|
|
64
|
-
|
|
65
|
-
Unlocks a secure result returned by matrix, image, camera, or Node.js scanning.
|
|
66
|
-
|
|
67
|
-
```js
|
|
68
|
-
const unlocked = await decryptDecoded(locked, {
|
|
69
|
-
password: "example-password"
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
For raw-key mode:
|
|
74
|
-
|
|
75
|
-
```js
|
|
76
|
-
const unlocked = await decryptDecoded(locked, { key });
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Matrix decoding
|
|
80
|
-
|
|
81
|
-
### `decodeMatrix(matrix, options?)`
|
|
82
|
-
|
|
83
|
-
Decodes an already sampled QuadQR matrix.
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
const result = decodeMatrix(code.matrix);
|
|
87
|
-
console.log(result.text);
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
A secure result remains locked until it is decrypted:
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
{
|
|
94
|
-
secure: true,
|
|
95
|
-
requiresDecryption: true,
|
|
96
|
-
text: null,
|
|
97
|
-
payload: Uint8Array(...),
|
|
98
|
-
security: {
|
|
99
|
-
mode: "password",
|
|
100
|
-
algorithm: "AES-256-GCM"
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Rendering
|
|
106
|
-
|
|
107
|
-
### `renderToCanvas(codeOrMatrix, canvas, options?)`
|
|
108
|
-
|
|
109
|
-
Renders to an HTML canvas.
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
renderToCanvas(code, canvas, {
|
|
113
|
-
|
|
114
|
-
quietZone: 4,
|
|
115
|
-
style: "classic"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Core encoding
|
|
4
|
+
|
|
5
|
+
### `encodeText(text, options?)`
|
|
6
|
+
|
|
7
|
+
Synchronously encodes UTF-8 text.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { encodeText } from "quadqr-js";
|
|
11
|
+
|
|
12
|
+
const code = encodeText("Hello", {
|
|
13
|
+
ecc: "M",
|
|
14
|
+
version: 5
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If `version` is omitted, QuadQR selects the smallest version that fits.
|
|
19
|
+
|
|
20
|
+
### `encodeBytes(bytes, options?)`
|
|
21
|
+
|
|
22
|
+
Encodes arbitrary bytes.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
const code = encodeBytes(
|
|
26
|
+
new Uint8Array([1, 2, 3, 4]),
|
|
27
|
+
{ ecc: "Q" }
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Secure encoding
|
|
32
|
+
|
|
33
|
+
### `encodeSecureText(text, options)`
|
|
34
|
+
|
|
35
|
+
Encrypts text using Secure Payload v1 and encodes the encrypted envelope.
|
|
36
|
+
|
|
37
|
+
Password mode:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const code = await encodeSecureText("secret", {
|
|
41
|
+
security: {
|
|
42
|
+
mode: "password",
|
|
43
|
+
password: "example-password"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Raw-key mode:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
const code = await encodeSecureText("secret", {
|
|
52
|
+
security: {
|
|
53
|
+
mode: "raw-key",
|
|
54
|
+
key
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `encodeSecureBytes(bytes, options)`
|
|
60
|
+
|
|
61
|
+
Byte-oriented equivalent of `encodeSecureText()`.
|
|
62
|
+
|
|
63
|
+
### `decryptDecoded(result, credentials)`
|
|
64
|
+
|
|
65
|
+
Unlocks a secure result returned by matrix, image, camera, or Node.js scanning.
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
const unlocked = await decryptDecoded(locked, {
|
|
69
|
+
password: "example-password"
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For raw-key mode:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const unlocked = await decryptDecoded(locked, { key });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Matrix decoding
|
|
80
|
+
|
|
81
|
+
### `decodeMatrix(matrix, options?)`
|
|
82
|
+
|
|
83
|
+
Decodes an already sampled QuadQR matrix.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
const result = decodeMatrix(code.matrix);
|
|
87
|
+
console.log(result.text);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A secure result remains locked until it is decrypted:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
{
|
|
94
|
+
secure: true,
|
|
95
|
+
requiresDecryption: true,
|
|
96
|
+
text: null,
|
|
97
|
+
payload: Uint8Array(...),
|
|
98
|
+
security: {
|
|
99
|
+
mode: "password",
|
|
100
|
+
algorithm: "AES-256-GCM"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Rendering
|
|
106
|
+
|
|
107
|
+
### `renderToCanvas(codeOrMatrix, canvas, options?)`
|
|
108
|
+
|
|
109
|
+
Renders to an HTML canvas.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
renderToCanvas(code, canvas, {
|
|
113
|
+
imageSize: 720,
|
|
114
|
+
quietZone: 4,
|
|
115
|
+
style: "classic",
|
|
116
|
+
logo: {
|
|
117
|
+
source: loadedImage,
|
|
118
|
+
size: 0.12,
|
|
119
|
+
clearBackground: true,
|
|
120
|
+
padding: 0.65,
|
|
121
|
+
radius: 0.8
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`imageSize` sets the exact square output width/height in pixels. The default is `720` when neither `imageSize` nor `moduleSize` is provided. `moduleSize` remains supported as a lower-level pixels-per-module sizing option and is used when `imageSize` is omitted.
|
|
127
|
+
|
|
128
|
+
`quietZone` is measured in modules. The default is `4`.
|
|
129
|
+
|
|
130
|
+
Logo options:
|
|
131
|
+
|
|
132
|
+
- `source`: loaded CanvasImageSource for canvas rendering, ImageData-like RGBA data for `renderToImageData()`, or URL/data URL for SVG.
|
|
133
|
+
- `size`: fraction of the matrix width/height, clamped to `0.05..0.30`. Default `0.18`.
|
|
134
|
+
- `clearBackground`: clears modules behind the logo using a solid background before drawing the logo.
|
|
135
|
+
- `padding`: clear-background padding in modules. Default `0.65`.
|
|
136
|
+
- `radius`: clear-background corner radius in modules. Default `0.8`.
|
|
137
|
+
- `backgroundColor`: background color when clearing. Defaults to palette white.
|
|
138
|
+
|
|
139
|
+
Supported styles:
|
|
140
|
+
|
|
141
|
+
- `classic`
|
|
142
|
+
- `depth`
|
|
143
|
+
- `soft`
|
|
144
|
+
- `inset`
|
|
145
|
+
|
|
146
|
+
### `renderToImageData(codeOrMatrix, options?)`
|
|
147
|
+
|
|
148
|
+
Returns runtime-neutral RGBA pixels:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
{
|
|
152
|
+
width,
|
|
153
|
+
height,
|
|
154
|
+
data: Uint8ClampedArray
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `renderToSVG(codeOrMatrix, options?)`
|
|
159
|
+
|
|
160
|
+
Returns a standalone SVG string. It supports the same exact `imageSize`, palette, render style, quiet-zone, and logo geometry options as the browser canvas renderer. Because the preview/export is vector, it stays sharp when displayed above or below its nominal pixel size.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
const svg = renderToSVG(code, {
|
|
164
|
+
imageSize: 720,
|
|
165
|
+
quietZone: 6,
|
|
166
|
+
style: "soft",
|
|
167
|
+
logo: {
|
|
168
|
+
source: "data:image/png;base64,...",
|
|
169
|
+
size: 0.12,
|
|
170
|
+
clearBackground: true
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Image and camera scanning
|
|
176
|
+
|
|
177
|
+
### `scanImageData(imageData, options?)`
|
|
178
|
+
|
|
179
|
+
Scans an ImageData-like RGBA object. Clean frames use the normal observed-RGB path first. Difficult frames then get bounded fallback attempts using per-channel white balancing, spatial black/white normalization, tighter centre sampling, module-grid Auto Tone / Auto Contrast / Auto Color recovery, a rectified QR-region pixel enhancement pass, and sub-module geometry micro-refinement before the scan is rejected.
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const result = scanImageData({
|
|
183
|
+
width,
|
|
184
|
+
height,
|
|
185
|
+
data
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `autoEnhanceBlackClip`, `autoEnhanceWhiteClip`, `autoEnhanceSaturation`, `geometryRefinement`, `refinementOffset`, `structureTolerance`, and `maxErasureConfidence`. Auto enhancement and geometry refinement are enabled by default, but only run after the normal scan path fails.
|
|
190
|
+
|
|
191
|
+
### Browser `scanFile(file, options?)`
|
|
192
|
+
|
|
193
|
+
Available from `quadqr-js/browser` and the browser-compatible main entry. Accepts a browser `File` or `Blob`.
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
const result = await scanFile(file);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `scanVideoFrame(video, options?)`
|
|
200
|
+
|
|
201
|
+
Scans the current frame from an HTML video element. With the default `videoCropMode: "visible"`, an `object-fit: cover` preview is mapped back to the matching source crop before scanning, so hidden sensor pixels do not shrink the code or skew enhancement statistics.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
const result = scanVideoFrame(video);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `startCameraScanner(video, options?)`
|
|
208
|
+
|
|
209
|
+
Starts live camera scanning. On browsers that expose camera controls, QuadQR requests continuous autofocus, exposure, and white balance. It scans the CSS-visible preview region by default. Finder detection uses a QuadQR-specific RGB value channel (`max(R,G,B)`) on the fast pass so saturated blue/red/green data cells are not mistaken for structural black. If that fast pass fails, the **same captured frame** is retried through code-centric Auto Color recovery before finder detection. The default recovery sequence crops 8%, 16%, and 22% from the camera-frame edges, then falls back to the full frame. This prevents dark room pixels, browser UI, or monitor bezels outside the guide from controlling Auto Color and Otsu thresholds. Each crop uses the Photoshop-style per-channel shadow/highlight correction with a neutral mid-high highlight target (190 by default). Finder-only recovery also tries multiple center-weighted Auto Color histogram windows before raw threshold bracketing. The normal fast path is untouched; these extra passes run only after a miss. If geometry is found but color decoding fails, the captured ROI is retried with the stronger color/geometry recovery, and consecutive failed frames can still be combined with confidence-weighted module voting.
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
const scanner = await startCameraScanner(video, {
|
|
213
|
+
scanInterval: 120,
|
|
214
|
+
multiFrame: true,
|
|
215
|
+
multiFrameWindow: 4,
|
|
216
|
+
cameraAutoColorEvery: 1,
|
|
217
|
+
cameraAutoColorCropInsets: [0.08, 0.16, 0.22, 0],
|
|
218
|
+
cameraAutoColorHighlightPercentile: 0.95,
|
|
219
|
+
cameraAutoColorOutputHighlight: 190,
|
|
220
|
+
cameraAutoColorAnalysisInset: 0.10,
|
|
221
|
+
cameraAutoEnhanceEvery: 2,
|
|
222
|
+
cameraFinderRecoveryEvery: 2,
|
|
223
|
+
onResult(result) {
|
|
224
|
+
console.log(result);
|
|
225
|
+
},
|
|
226
|
+
onScanMiss() {
|
|
227
|
+
// Keep searching.
|
|
228
|
+
},
|
|
229
|
+
onDiagnostic(event) {
|
|
230
|
+
// Finder positions are in the scanner frame coordinate space.
|
|
231
|
+
// Use scanWidth/scanHeight to map them onto a video overlay.
|
|
232
|
+
if (event.type === "frame") {
|
|
233
|
+
console.log(event.method, event.finderCount, event.geometry?.version, event.elapsedMs);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
scanner.stop();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`onDiagnostic` is opt-in. It reports finder candidates, the locator method (`rgb-value-otsu`, threshold-bracket recovery, or luminance fallback), geometry data, and recovery state. Diagnostics reuse the scanner's existing passes and do not add a separate finder scan.
|
|
242
|
+
|
|
243
|
+
### `rectifyDetectedCode(imageData, options?)`
|
|
244
|
+
|
|
245
|
+
Returns the perspective-corrected detected symbol image when detection succeeds.
|
|
246
|
+
|
|
247
|
+
### `rotateMatrix(matrix, quarterTurns?)`
|
|
248
|
+
|
|
249
|
+
Rotates a matrix by 90-degree quarter turns.
|
|
250
|
+
|
|
251
|
+
## Node.js helpers
|
|
252
|
+
|
|
253
|
+
Import from `quadqr-js/node`.
|
|
254
|
+
|
|
255
|
+
### `toPNG(codeOrMatrix, options?)`
|
|
256
|
+
|
|
257
|
+
Returns a Node.js `Buffer` containing a PNG.
|
|
258
|
+
|
|
259
|
+
### `savePNG(codeOrMatrix, filename, options?)`
|
|
260
|
+
|
|
261
|
+
Writes a PNG file.
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
await savePNG(code, "quadqr.png", {
|
|
265
|
+
imageSize: 720,
|
|
266
|
+
quietZone: 4
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `toSVG(codeOrMatrix, options?)`
|
|
271
|
+
|
|
272
|
+
Returns the standalone SVG string from the Node entry point.
|
|
273
|
+
|
|
274
|
+
### `saveSVG(codeOrMatrix, filename, options?)`
|
|
275
|
+
|
|
276
|
+
Writes SVG directly to disk.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
await saveSVG(code, "quadqr.svg", {
|
|
280
|
+
imageSize: 720,
|
|
281
|
+
quietZone: 4
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### `scanBuffer(buffer, options?)`
|
|
286
|
+
|
|
287
|
+
Scans an image buffer. PNG is supported without dependencies. Other formats can use the host application's optional `sharp` installation.
|
|
288
|
+
|
|
289
|
+
### Node `scanFile(filename, options?)`
|
|
290
|
+
|
|
291
|
+
Reads and scans an image file.
|
|
292
|
+
|
|
293
|
+
### `decodePNG(buffer)`
|
|
294
|
+
|
|
295
|
+
Decodes supported PNG data into RGBA pixels.
|
|
296
|
+
|
|
297
|
+
### `encodePNG(imageData, options?)`
|
|
298
|
+
|
|
299
|
+
Encodes RGBA pixels into a PNG `Buffer`.
|
|
300
|
+
|
|
301
|
+
## Security utilities
|
|
302
|
+
|
|
303
|
+
### `generateRaw256Key()`
|
|
304
|
+
|
|
305
|
+
Generates a cryptographically random 32-byte key.
|
|
306
|
+
|
|
307
|
+
### `normalizeRaw256Key(key)`
|
|
308
|
+
|
|
309
|
+
Normalizes a 32-byte key supplied as bytes, ArrayBuffer, or 64-character hexadecimal text.
|
|
310
|
+
|
|
311
|
+
### `bytesToHex(bytes)`
|
|
312
|
+
|
|
313
|
+
Converts bytes to lowercase hexadecimal text.
|
|
314
|
+
|
|
315
|
+
Security constants include:
|
|
316
|
+
|
|
317
|
+
- `SECURITY_MODES`
|
|
318
|
+
- `SECURITY_ALGORITHMS`
|
|
319
|
+
- `SECURE_PAYLOAD_VERSION`
|
|
320
|
+
- `DEFAULT_PBKDF2_ITERATIONS`
|
|
321
|
+
|
|
322
|
+
## WASM
|
|
323
|
+
|
|
324
|
+
### `initWasm(options?)`
|
|
325
|
+
|
|
326
|
+
Loads and activates the optional bundled WASM accelerator.
|
|
327
|
+
|
|
328
|
+
```js
|
|
329
|
+
import { initWasm } from "quadqr-js";
|
|
330
|
+
await initWasm();
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
An explicit URL or byte buffer can also be supplied.
|
|
334
|
+
|
|
335
|
+
### `getWasmState()`
|
|
336
|
+
|
|
337
|
+
Returns the active WASM state or `null`.
|
|
338
|
+
|
|
339
|
+
### `disableWasm()`
|
|
340
|
+
|
|
341
|
+
Returns the codec to the JavaScript implementation.
|
|
342
|
+
|
|
343
|
+
## Version and capacity
|
|
344
|
+
|
|
345
|
+
### `getVersionInfo(version, options?)`
|
|
346
|
+
|
|
347
|
+
Returns geometry and payload information for a specific QuadQR version/ECC profile.
|
|
348
|
+
|
|
349
|
+
## CRC utilities
|
|
350
|
+
|
|
351
|
+
### `crc32(bytes)`
|
|
352
|
+
|
|
353
|
+
Calculates QuadQR's CRC-32 value for a byte array.
|
|
354
|
+
|
|
355
|
+
### `installCrc32Accelerator(accelerator?)`
|
|
356
|
+
|
|
357
|
+
Installs or removes a synchronous CRC-32 accelerator. Most applications should use `initWasm()` rather than calling this directly.
|
|
358
|
+
|
|
359
|
+
## Constants
|
|
360
|
+
|
|
361
|
+
Common exported constants include:
|
|
362
|
+
|
|
363
|
+
- `FORMAT_VERSION`
|
|
364
|
+
- `MIN_VERSION`
|
|
365
|
+
- `MAX_VERSION`
|
|
366
|
+
- `DEFAULT_ECC_LEVEL`
|
|
367
|
+
- `CELL`
|
|
368
|
+
- `DEFAULT_PALETTE`
|
|
369
|
+
- `RENDER_STYLES`
|
|
370
|
+
- `RENDER_MODES`
|
|
371
|
+
- `COMPRESSION_MODES`
|
|
372
|
+
- `SIGNATURE_ALGORITHMS`
|
|
373
|
+
- `STRESS_PROFILES`
|
|
374
|
+
- `ECC_LEVELS`
|
|
375
|
+
|
|
376
|
+
## Benchmark entry
|
|
377
|
+
|
|
378
|
+
Benchmark helpers are available from `quadqr-js/benchmark`:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
import {
|
|
382
|
+
buildCapacityComparison,
|
|
383
|
+
benchmarkCodec,
|
|
384
|
+
calculateCapacityPlan
|
|
385
|
+
} from "quadqr-js/benchmark";
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### `calculateCapacityPlan(options)`
|
|
389
|
+
|
|
390
|
+
Estimates the smallest QuadQR version for a byte count or concrete payload and reports remaining capacity plus the standard QR byte-mode version at the same nominal ECC letter. When only a byte count is known, compression gain is intentionally not guessed.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Compression
|
|
395
|
+
|
|
396
|
+
### `encodeText(text, options?)` / `encodeBytes(input, options?)`
|
|
397
|
+
|
|
398
|
+
Both normal encoding APIs accept an optional compression mode:
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
const code = QuadQR.encodeText("hello hello hello", {
|
|
402
|
+
compression: "auto",
|
|
403
|
+
ecc: "M"
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`compression` may be `none`, `auto`, or `lz`.
|
|
408
|
+
|
|
409
|
+
- `none` stores the payload directly.
|
|
410
|
+
- `auto` compresses only when the result is meaningfully smaller. If compression does not help, no internal envelope is added.
|
|
411
|
+
- `lz` always stores the payload through QuadQR's portable LZ compressor.
|
|
412
|
+
|
|
413
|
+
There is no public content-type registry. Text remains text and byte arrays remain byte arrays.
|
|
414
|
+
|
|
415
|
+
### `compressPayload(input)` / `decompressPayload(input, expectedLength?)`
|
|
416
|
+
|
|
417
|
+
Portable synchronous LZSS-style compression helpers. They do not require Node zlib or browser `CompressionStream`.
|
|
418
|
+
|
|
419
|
+
## Binary convenience APIs
|
|
420
|
+
|
|
421
|
+
### `encodeUint8Array(input, options?)`
|
|
422
|
+
|
|
423
|
+
Explicit byte-oriented alias of `encodeBytes()`. It supports the same optional `compression` setting.
|
|
424
|
+
|
|
425
|
+
### `decodeUint8Array(matrix, options?)`
|
|
426
|
+
|
|
427
|
+
Decodes a matrix and returns only the application payload bytes.
|
|
428
|
+
|
|
429
|
+
## Signed QuadQR
|
|
430
|
+
|
|
431
|
+
### `generateSigningKeyPair()`
|
|
432
|
+
|
|
433
|
+
Generates an extractable Ed25519 key pair using Web Crypto and returns the CryptoKeys, raw public-key bytes, PKCS#8 private-key bytes, and a compact SHA-256-derived `keyId`.
|
|
434
|
+
|
|
435
|
+
### `encodeSignedText(text, options)` / `encodeSignedBytes(input, options)`
|
|
436
|
+
|
|
437
|
+
Signs the normal application payload with Ed25519. Any required signature and compression metadata is handled internally.
|
|
438
|
+
|
|
439
|
+
```js
|
|
440
|
+
const pair = await QuadQR.generateSigningKeyPair();
|
|
441
|
+
const code = await QuadQR.encodeSignedText("certificate payload", {
|
|
442
|
+
ecc: "Q",
|
|
443
|
+
compression: "auto",
|
|
444
|
+
privateKey: pair.privateKey,
|
|
445
|
+
keyId: pair.keyId
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Only the private key is required for signing. `publicKey` is accepted only for the explicit `embedPublicKey: true` compatibility mode.
|
|
450
|
+
|
|
451
|
+
### `verifyDecodedSignature(result, options?)`
|
|
452
|
+
|
|
453
|
+
Verifies a signed decode result against a trusted external Ed25519 public key.
|
|
454
|
+
|
|
455
|
+
```js
|
|
456
|
+
const decoded = QuadQR.decodeMatrix(code.matrix);
|
|
457
|
+
const verified = await QuadQR.verifyDecodedSignature(decoded, {
|
|
458
|
+
publicKey: knownPublicKey
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
For multiple issuers, pass a `trustedKeys` object or `Map` keyed by the embedded `keyId`:
|
|
463
|
+
|
|
464
|
+
```js
|
|
465
|
+
const verified = await QuadQR.verifyDecodedSignature(decoded, {
|
|
466
|
+
trustedKeys: {
|
|
467
|
+
[decoded.signingKeyId]: knownPublicKey
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`signatureVerified: true` means the signature is mathematically valid. `signatureTrusted: true` means it was checked using an external trusted key. `allowEmbeddedKey: true` may be used for compatibility/self-contained integrity checks, but such a result is not considered a trusted signer.
|
|
473
|
+
|
|
474
|
+
### Signing + encryption
|
|
475
|
+
|
|
476
|
+
`encodeSecureText()` and `encodeSecureBytes()` accept an optional `signing` object alongside `security` and `compression`. The internal pipeline is compression → signing → AES-256-GCM → Spectrum ECC.
|
|
477
|
+
|
|
478
|
+
```js
|
|
479
|
+
const code = await QuadQR.encodeSecureText("private signed payload", {
|
|
480
|
+
compression: "auto",
|
|
481
|
+
security: { mode: "password", password: "secret" },
|
|
482
|
+
signing: {
|
|
483
|
+
privateKey: pair.privateKey,
|
|
484
|
+
keyId: pair.keyId
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Print rendering
|
|
490
|
+
|
|
491
|
+
All render APIs accept:
|
|
492
|
+
|
|
493
|
+
```js
|
|
494
|
+
{ mode: "screen" | "print" }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Print mode uses a darker print-safe palette, forces Classic modules by default, and enforces a minimum 4-module quiet zone unless `allowUnsafePrintQuietZone: true` is explicitly set.
|
|
498
|
+
|
|
499
|
+
### `getPrintGuidance(codeOrMatrix, options?)`
|
|
500
|
+
|
|
501
|
+
Returns physical module size, pixels/module at a DPI, recommended minimum physical size, and print recommendations.
|
|
502
|
+
|
|
503
|
+
## Automatic logo safety
|
|
504
|
+
|
|
505
|
+
A logo can use:
|
|
506
|
+
|
|
507
|
+
```js
|
|
508
|
+
logo: {
|
|
509
|
+
source: logo,
|
|
510
|
+
size: "auto",
|
|
511
|
+
clearBackground: true
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`estimateSafeLogoSize()` exposes the conservative ratio used by auto mode. `findMaxSafeLogoSize()` performs an empirical binary search when the logo source is ImageData-like.
|
|
516
|
+
|
|
517
|
+
## Scanner confidence and debug mode
|
|
518
|
+
|
|
519
|
+
Normal successful scans now include:
|
|
520
|
+
|
|
521
|
+
- `confidence`
|
|
522
|
+
- `geometryConfidence`
|
|
523
|
+
- `calibrationConfidence`
|
|
524
|
+
- `structureConfidence`
|
|
525
|
+
- `eccUtilization`
|
|
526
|
+
- `correctedErrors`
|
|
527
|
+
- `diagnostics`
|
|
528
|
+
|
|
529
|
+
Use `scanImageData(image, { debug: true })` for detailed geometry/sampling information. `debugScanImageData()` is a non-throwing helper that returns `{ ok, result, debug }` or `{ ok: false, error, debug }`.
|
|
530
|
+
|
|
531
|
+
## Scanability and stress testing
|
|
532
|
+
|
|
533
|
+
### `applyStressDistortion(imageData, type, severity?)`
|
|
534
|
+
|
|
535
|
+
Deterministically applies a selected synthetic distortion.
|
|
536
|
+
|
|
537
|
+
### `runImageStressTest(imageData, expected?, options?)`
|
|
538
|
+
|
|
539
|
+
Runs the standard torture profiles and returns a 0–100 score plus per-scenario decode results.
|
|
540
|
+
|
|
541
|
+
### `assessScanability(code, renderOptions?, options?)`
|
|
542
|
+
|
|
543
|
+
Renders a code and runs the standard test suite. It returns `Excellent`, `Good`, `Risky`, or `Likely unscannable` together with recommendations.
|
|
544
|
+
|
|
545
|
+
Synthetic scores are regression aids and do not replace physical camera/print testing.
|