quadqr-js 1.0.2 → 1.3.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 +40 -3
- package/README.md +199 -20
- package/SPECIFICATION.md +337 -0
- package/bin/quadqr.js +105 -14
- package/dist/esm/benchmark.js +113 -8
- package/dist/esm/node.js +13 -1
- package/dist/esm/quadqr.js +2312 -299
- package/dist/esm/vision.js +452 -14
- package/dist/quadqr.js +2746 -299
- package/dist/quadqr.min.js +2736 -297
- package/docs/API.md +230 -8
- package/docs/BROWSER_CDN.md +51 -6
- package/docs/CLI.md +156 -76
- package/docs/GETTING_STARTED.md +80 -7
- package/docs/HIGH_DENSITY_MODE.md +100 -0
- package/docs/NODE.md +137 -123
- package/docs/README.md +60 -41
- package/docs/TRIANGLE16.md +108 -0
- package/docs/WASM.md +2 -2
- package/package.json +10 -3
- package/types/benchmark.d.ts +4 -3
- package/types/index.d.ts +144 -2
- package/types/node.d.ts +2 -0
package/docs/API.md
CHANGED
|
@@ -11,12 +11,15 @@ import { encodeText } from "quadqr-js";
|
|
|
11
11
|
|
|
12
12
|
const code = encodeText("Hello", {
|
|
13
13
|
ecc: "M",
|
|
14
|
-
version: 5
|
|
14
|
+
version: 5,
|
|
15
|
+
highDensity: true // optional experimental 4-bit cells
|
|
15
16
|
});
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
If `version` is omitted, QuadQR selects the smallest version that fits.
|
|
19
20
|
|
|
21
|
+
`highDensity` is a boolean. It defaults to `false`. Set `highDensity: true` to enable the experimental Triangle16 layout with two RGBW triangles, 16 states, and 4 raw bits per body cell. The protected header remains solid-color in High Density Mode. Image and camera scanners auto-detect it.
|
|
22
|
+
|
|
20
23
|
### `encodeBytes(bytes, options?)`
|
|
21
24
|
|
|
22
25
|
Encodes arbitrary bytes.
|
|
@@ -110,12 +113,32 @@ Renders to an HTML canvas.
|
|
|
110
113
|
|
|
111
114
|
```js
|
|
112
115
|
renderToCanvas(code, canvas, {
|
|
113
|
-
|
|
116
|
+
imageSize: 720,
|
|
114
117
|
quietZone: 4,
|
|
115
|
-
style: "classic"
|
|
118
|
+
style: "classic",
|
|
119
|
+
logo: {
|
|
120
|
+
source: loadedImage,
|
|
121
|
+
size: 0.12,
|
|
122
|
+
clearBackground: true,
|
|
123
|
+
padding: 0.65,
|
|
124
|
+
radius: 0.8
|
|
125
|
+
}
|
|
116
126
|
});
|
|
117
127
|
```
|
|
118
128
|
|
|
129
|
+
`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.
|
|
130
|
+
|
|
131
|
+
`quietZone` is measured in modules. The default is `4`.
|
|
132
|
+
|
|
133
|
+
Logo options:
|
|
134
|
+
|
|
135
|
+
- `source`: loaded CanvasImageSource for canvas rendering, ImageData-like RGBA data for `renderToImageData()`, or URL/data URL for SVG.
|
|
136
|
+
- `size`: fraction of the matrix width/height, clamped to `0.05..0.30`. Default `0.18`.
|
|
137
|
+
- `clearBackground`: clears modules behind the logo using a solid background before drawing the logo.
|
|
138
|
+
- `padding`: clear-background padding in modules. Default `0.65`.
|
|
139
|
+
- `radius`: clear-background corner radius in modules. Default `0.8`.
|
|
140
|
+
- `backgroundColor`: background color when clearing. Defaults to palette white.
|
|
141
|
+
|
|
119
142
|
Supported styles:
|
|
120
143
|
|
|
121
144
|
- `classic`
|
|
@@ -135,11 +158,28 @@ Returns runtime-neutral RGBA pixels:
|
|
|
135
158
|
}
|
|
136
159
|
```
|
|
137
160
|
|
|
161
|
+
### `renderToSVG(codeOrMatrix, options?)`
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const svg = renderToSVG(code, {
|
|
167
|
+
imageSize: 720,
|
|
168
|
+
quietZone: 6,
|
|
169
|
+
style: "soft",
|
|
170
|
+
logo: {
|
|
171
|
+
source: "data:image/png;base64,...",
|
|
172
|
+
size: 0.12,
|
|
173
|
+
clearBackground: true
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
138
178
|
## Image and camera scanning
|
|
139
179
|
|
|
140
180
|
### `scanImageData(imageData, options?)`
|
|
141
181
|
|
|
142
|
-
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.
|
|
182
|
+
Scans an ImageData-like RGBA object. The scanner automatically attempts both normal RGBW center sampling and Triangle16 dual-region sampling when geometry is found. When normal projective geometry is locatable but too coarse for half-cell Triangle16 regions, one bounded precise-alignment recovery pass refines the alignment center at sub-module resolution. 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. Dense versions also use their distributed alignment markers to refine a noisy four-point projective solution when the initial alignment-grid score is plausible but imperfect. If exactly two strong finder patterns survive a steep angle, a bounded perspective-tolerant third-finder recovery pass is attempted before heavier color recovery.
|
|
143
183
|
|
|
144
184
|
```js
|
|
145
185
|
const result = scanImageData({
|
|
@@ -149,7 +189,7 @@ const result = scanImageData({
|
|
|
149
189
|
});
|
|
150
190
|
```
|
|
151
191
|
|
|
152
|
-
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
|
|
192
|
+
Useful scanner options include `sampleRadius`, `robustSampleRadius`, `adaptiveSampling`, `spatialColorNormalization`, `autoEnhanceRecovery`, `rectifiedAutoEnhanceRecovery`, `rectifiedRecoveryModuleSize`, `autoEnhanceBlackClip`, `autoEnhanceWhiteClip`, `autoEnhanceSaturation`, `geometryRefinement`, `alignmentRefinement`, `alignmentRefinePatternThreshold`, `refinementOffset`, `structureTolerance`, and `maxErasureConfidence`. Auto enhancement and geometry refinement are enabled by default, but bounded recovery work only runs when the normal scan path or initial geometry needs it.
|
|
153
193
|
|
|
154
194
|
### Browser `scanFile(file, options?)`
|
|
155
195
|
|
|
@@ -169,7 +209,7 @@ const result = scanVideoFrame(video);
|
|
|
169
209
|
|
|
170
210
|
### `startCameraScanner(video, options?)`
|
|
171
211
|
|
|
172
|
-
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
|
|
212
|
+
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 a miss still contains at least two strong finder patterns, the scanner can retry the visible ROI at up to 1600 px before heavier recovery, which preserves more pixels per module for dense versions. If that does not decode, the **same captured frame** is retried through code-centric Auto Color recovery. 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.
|
|
173
213
|
|
|
174
214
|
```js
|
|
175
215
|
const scanner = await startCameraScanner(video, {
|
|
@@ -183,6 +223,9 @@ const scanner = await startCameraScanner(video, {
|
|
|
183
223
|
cameraAutoColorAnalysisInset: 0.10,
|
|
184
224
|
cameraAutoEnhanceEvery: 2,
|
|
185
225
|
cameraFinderRecoveryEvery: 2,
|
|
226
|
+
cameraHighResolutionRecovery: true,
|
|
227
|
+
cameraHighResolutionMaxDimension: 1600,
|
|
228
|
+
cameraHighResolutionEvery: 2,
|
|
186
229
|
onResult(result) {
|
|
187
230
|
console.log(result);
|
|
188
231
|
},
|
|
@@ -225,7 +268,22 @@ Writes a PNG file.
|
|
|
225
268
|
|
|
226
269
|
```js
|
|
227
270
|
await savePNG(code, "quadqr.png", {
|
|
228
|
-
|
|
271
|
+
imageSize: 720,
|
|
272
|
+
quietZone: 4
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `toSVG(codeOrMatrix, options?)`
|
|
277
|
+
|
|
278
|
+
Returns the standalone SVG string from the Node entry point.
|
|
279
|
+
|
|
280
|
+
### `saveSVG(codeOrMatrix, filename, options?)`
|
|
281
|
+
|
|
282
|
+
Writes SVG directly to disk.
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
await saveSVG(code, "quadqr.svg", {
|
|
286
|
+
imageSize: 720,
|
|
229
287
|
quietZone: 4
|
|
230
288
|
});
|
|
231
289
|
```
|
|
@@ -315,6 +373,10 @@ Common exported constants include:
|
|
|
315
373
|
- `CELL`
|
|
316
374
|
- `DEFAULT_PALETTE`
|
|
317
375
|
- `RENDER_STYLES`
|
|
376
|
+
- `RENDER_MODES`
|
|
377
|
+
- `COMPRESSION_MODES`
|
|
378
|
+
- `SIGNATURE_ALGORITHMS`
|
|
379
|
+
- `STRESS_PROFILES`
|
|
318
380
|
- `ECC_LEVELS`
|
|
319
381
|
|
|
320
382
|
## Benchmark entry
|
|
@@ -324,6 +386,166 @@ Benchmark helpers are available from `quadqr-js/benchmark`:
|
|
|
324
386
|
```js
|
|
325
387
|
import {
|
|
326
388
|
buildCapacityComparison,
|
|
327
|
-
benchmarkCodec
|
|
389
|
+
benchmarkCodec,
|
|
390
|
+
calculateCapacityPlan
|
|
328
391
|
} from "quadqr-js/benchmark";
|
|
329
392
|
```
|
|
393
|
+
|
|
394
|
+
### `calculateCapacityPlan(options)`
|
|
395
|
+
|
|
396
|
+
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.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Compression
|
|
401
|
+
|
|
402
|
+
### `encodeText(text, options?)` / `encodeBytes(input, options?)`
|
|
403
|
+
|
|
404
|
+
Both normal encoding APIs accept an optional compression mode:
|
|
405
|
+
|
|
406
|
+
```js
|
|
407
|
+
const code = QuadQR.encodeText("hello hello hello", {
|
|
408
|
+
compression: "auto",
|
|
409
|
+
ecc: "M"
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`compression` may be `none`, `auto`, or `lz`.
|
|
414
|
+
|
|
415
|
+
- `none` stores the payload directly.
|
|
416
|
+
- `auto` compresses only when the result is meaningfully smaller. If compression does not help, no internal envelope is added.
|
|
417
|
+
- `lz` always stores the payload through QuadQR's portable LZ compressor.
|
|
418
|
+
|
|
419
|
+
There is no public content-type registry. Text remains text and byte arrays remain byte arrays.
|
|
420
|
+
|
|
421
|
+
### `compressPayload(input)` / `decompressPayload(input, expectedLength?)`
|
|
422
|
+
|
|
423
|
+
Portable synchronous LZSS-style compression helpers. They do not require Node zlib or browser `CompressionStream`.
|
|
424
|
+
|
|
425
|
+
## Binary convenience APIs
|
|
426
|
+
|
|
427
|
+
### `encodeUint8Array(input, options?)`
|
|
428
|
+
|
|
429
|
+
Explicit byte-oriented alias of `encodeBytes()`. It supports the same optional `compression` setting.
|
|
430
|
+
|
|
431
|
+
### `decodeUint8Array(matrix, options?)`
|
|
432
|
+
|
|
433
|
+
Decodes a matrix and returns only the application payload bytes.
|
|
434
|
+
|
|
435
|
+
## Signed QuadQR
|
|
436
|
+
|
|
437
|
+
### `generateSigningKeyPair()`
|
|
438
|
+
|
|
439
|
+
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`.
|
|
440
|
+
|
|
441
|
+
### `encodeSignedText(text, options)` / `encodeSignedBytes(input, options)`
|
|
442
|
+
|
|
443
|
+
Signs the normal application payload with Ed25519. Any required signature and compression metadata is handled internally.
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
const pair = await QuadQR.generateSigningKeyPair();
|
|
447
|
+
const code = await QuadQR.encodeSignedText("certificate payload", {
|
|
448
|
+
ecc: "Q",
|
|
449
|
+
compression: "auto",
|
|
450
|
+
privateKey: pair.privateKey,
|
|
451
|
+
keyId: pair.keyId
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Only the private key is required for signing. `publicKey` is accepted only for the explicit `embedPublicKey: true` compatibility mode.
|
|
456
|
+
|
|
457
|
+
### `verifyDecodedSignature(result, options?)`
|
|
458
|
+
|
|
459
|
+
Verifies a signed decode result against a trusted external Ed25519 public key.
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
const decoded = QuadQR.decodeMatrix(code.matrix);
|
|
463
|
+
const verified = await QuadQR.verifyDecodedSignature(decoded, {
|
|
464
|
+
publicKey: knownPublicKey
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
For multiple issuers, pass a `trustedKeys` object or `Map` keyed by the embedded `keyId`:
|
|
469
|
+
|
|
470
|
+
```js
|
|
471
|
+
const verified = await QuadQR.verifyDecodedSignature(decoded, {
|
|
472
|
+
trustedKeys: {
|
|
473
|
+
[decoded.signingKeyId]: knownPublicKey
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
`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.
|
|
479
|
+
|
|
480
|
+
### Signing + encryption
|
|
481
|
+
|
|
482
|
+
`encodeSecureText()` and `encodeSecureBytes()` accept an optional `signing` object alongside `security` and `compression`. The internal pipeline is compression → signing → AES-256-GCM → Spectrum ECC.
|
|
483
|
+
|
|
484
|
+
```js
|
|
485
|
+
const code = await QuadQR.encodeSecureText("private signed payload", {
|
|
486
|
+
compression: "auto",
|
|
487
|
+
security: { mode: "password", password: "secret" },
|
|
488
|
+
signing: {
|
|
489
|
+
privateKey: pair.privateKey,
|
|
490
|
+
keyId: pair.keyId
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Print rendering
|
|
496
|
+
|
|
497
|
+
All render APIs accept:
|
|
498
|
+
|
|
499
|
+
```js
|
|
500
|
+
{ mode: "screen" | "print" }
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
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.
|
|
504
|
+
|
|
505
|
+
### `getPrintGuidance(codeOrMatrix, options?)`
|
|
506
|
+
|
|
507
|
+
Returns physical module size, pixels/module at a DPI, recommended minimum physical size, and print recommendations.
|
|
508
|
+
|
|
509
|
+
## Automatic logo safety
|
|
510
|
+
|
|
511
|
+
A logo can use:
|
|
512
|
+
|
|
513
|
+
```js
|
|
514
|
+
logo: {
|
|
515
|
+
source: logo,
|
|
516
|
+
size: "auto",
|
|
517
|
+
clearBackground: true
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
`estimateSafeLogoSize()` exposes the conservative ratio used by auto mode. `findMaxSafeLogoSize()` performs an empirical binary search when the logo source is ImageData-like.
|
|
522
|
+
|
|
523
|
+
## Scanner confidence and debug mode
|
|
524
|
+
|
|
525
|
+
Normal successful scans now include:
|
|
526
|
+
|
|
527
|
+
- `confidence`
|
|
528
|
+
- `geometryConfidence`
|
|
529
|
+
- `calibrationConfidence`
|
|
530
|
+
- `structureConfidence`
|
|
531
|
+
- `eccUtilization`
|
|
532
|
+
- `correctedErrors`
|
|
533
|
+
- `diagnostics`
|
|
534
|
+
|
|
535
|
+
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 }`.
|
|
536
|
+
|
|
537
|
+
## Scanability and stress testing
|
|
538
|
+
|
|
539
|
+
### `applyStressDistortion(imageData, type, severity?)`
|
|
540
|
+
|
|
541
|
+
Deterministically applies a selected synthetic distortion.
|
|
542
|
+
|
|
543
|
+
### `runImageStressTest(imageData, expected?, options?)`
|
|
544
|
+
|
|
545
|
+
Runs the standard torture profiles and returns a 0–100 score plus per-scenario decode results.
|
|
546
|
+
|
|
547
|
+
### `assessScanability(code, renderOptions?, options?)`
|
|
548
|
+
|
|
549
|
+
Renders a code and runs the standard test suite. It returns `Excellent`, `Good`, `Risky`, or `Likely unscannable` together with recommendations.
|
|
550
|
+
|
|
551
|
+
Synthetic scores are regression aids and do not replace physical camera/print testing.
|
package/docs/BROWSER_CDN.md
CHANGED
|
@@ -8,6 +8,7 @@ With Vite, webpack, Rollup, Next.js client code, or another browser bundler:
|
|
|
8
8
|
import {
|
|
9
9
|
encodeText,
|
|
10
10
|
renderToCanvas,
|
|
11
|
+
renderToSVG,
|
|
11
12
|
scanFile,
|
|
12
13
|
startCameraScanner
|
|
13
14
|
} from "quadqr-js/browser";
|
|
@@ -21,14 +22,58 @@ The main `quadqr-js` entry also works in modern browser bundlers for runtime-neu
|
|
|
21
22
|
const code = encodeText("Hello browser");
|
|
22
23
|
|
|
23
24
|
renderToCanvas(code, document.querySelector("#qr"), {
|
|
24
|
-
|
|
25
|
+
imageSize: 720,
|
|
25
26
|
quietZone: 4,
|
|
26
27
|
style: "classic"
|
|
27
28
|
});
|
|
29
|
+
|
|
30
|
+
const svg = renderToSVG(code, {
|
|
31
|
+
imageSize: 720,
|
|
32
|
+
quietZone: 4
|
|
33
|
+
});
|
|
28
34
|
```
|
|
29
35
|
|
|
36
|
+
`imageSize` controls the exact square output size. If neither `imageSize` nor `moduleSize` is supplied, the renderer defaults to 720 × 720 px.
|
|
37
|
+
|
|
30
38
|
Available styles are `classic`, `depth`, `soft`, and `inset`.
|
|
31
39
|
|
|
40
|
+
## Compressed, signed, and print-safe output
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import {
|
|
44
|
+
assessScanability,
|
|
45
|
+
decodeMatrix,
|
|
46
|
+
encodeText,
|
|
47
|
+
encodeSignedText,
|
|
48
|
+
generateSigningKeyPair,
|
|
49
|
+
renderToCanvas,
|
|
50
|
+
verifyDecodedSignature
|
|
51
|
+
} from "quadqr-js/browser";
|
|
52
|
+
|
|
53
|
+
const compressed = encodeText("repeat repeat repeat repeat", {
|
|
54
|
+
compression: "auto"
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const keys = await generateSigningKeyPair();
|
|
58
|
+
const signed = await encodeSignedText("ticket", {
|
|
59
|
+
compression: "auto",
|
|
60
|
+
privateKey: keys.privateKey,
|
|
61
|
+
keyId: keys.keyId
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const checked = await verifyDecodedSignature(decodeMatrix(signed.matrix), {
|
|
65
|
+
publicKey: keys.publicKey
|
|
66
|
+
});
|
|
67
|
+
console.log(checked.signatureVerified, checked.signatureTrusted);
|
|
68
|
+
|
|
69
|
+
const canvas = document.querySelector("#qr");
|
|
70
|
+
renderToCanvas(compressed, canvas, { mode: "print", imageSize: 720 });
|
|
71
|
+
const report = assessScanability(compressed, { imageSize: 480 });
|
|
72
|
+
console.log(report.score, report.rating);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Compression and signing use internal metadata only when required. There is no public content-type mode to configure. The private key signs, while the public verification key stays outside the QuadQR by default. Use the stored `keyId` to select an application-trusted public key. Scanability testing is deterministic synthetic regression testing and should be supplemented with real devices and print samples.
|
|
76
|
+
|
|
32
77
|
## Scan an uploaded image
|
|
33
78
|
|
|
34
79
|
```js
|
|
@@ -88,12 +133,12 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
88
133
|
|
|
89
134
|
```html
|
|
90
135
|
<canvas id="qr"></canvas>
|
|
91
|
-
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0
|
|
136
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
|
|
92
137
|
<script>
|
|
93
138
|
const code = QuadQR.encodeText("Hello CDN");
|
|
94
139
|
|
|
95
140
|
QuadQR.renderToCanvas(code, document.querySelector("#qr"), {
|
|
96
|
-
|
|
141
|
+
imageSize: 720,
|
|
97
142
|
quietZone: 4
|
|
98
143
|
});
|
|
99
144
|
</script>
|
|
@@ -102,7 +147,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
102
147
|
## unpkg
|
|
103
148
|
|
|
104
149
|
```html
|
|
105
|
-
<script src="https://unpkg.com/quadqr-js@1.0
|
|
150
|
+
<script src="https://unpkg.com/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
|
|
106
151
|
```
|
|
107
152
|
|
|
108
153
|
## Direct CDN ESM
|
|
@@ -112,7 +157,7 @@ The global browser build exposes `window.QuadQR` / `globalThis.QuadQR`.
|
|
|
112
157
|
import {
|
|
113
158
|
encodeText,
|
|
114
159
|
renderToCanvas
|
|
115
|
-
} from "https://cdn.jsdelivr.net/npm/quadqr-js@1.0
|
|
160
|
+
} from "https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/browser.js";
|
|
116
161
|
|
|
117
162
|
const code = encodeText("ES module CDN");
|
|
118
163
|
renderToCanvas(code, document.querySelector("#qr"));
|
|
@@ -139,4 +184,4 @@ WASM is optional. The normal JavaScript implementation remains available if it c
|
|
|
139
184
|
|
|
140
185
|
## Production recommendation
|
|
141
186
|
|
|
142
|
-
Pin a concrete package version such as `@1.0
|
|
187
|
+
Pin a concrete package version such as `@1.1.0` in CDN URLs so an existing site does not silently change when a newer package version becomes available.
|
package/docs/CLI.md
CHANGED
|
@@ -1,76 +1,156 @@
|
|
|
1
|
-
# Command Line Interface
|
|
2
|
-
|
|
3
|
-
The npm package includes the `quadqr` executable and can be used directly through `npx`.
|
|
4
|
-
|
|
5
|
-
## Encode text
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
# Command Line Interface
|
|
2
|
+
|
|
3
|
+
The npm package includes the `quadqr` executable and can be used directly through `npx`.
|
|
4
|
+
|
|
5
|
+
## Encode text
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
9
|
+
npx quadqr-js encode "Hello QuadQR" -o hello.svg
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Optional encoding controls:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx quadqr-js encode "Hello" --ecc M --version auto --image-size 720 --quiet-zone 4 -o hello.png
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use the print-safe rendering profile when the symbol is intended for physical output:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx quadqr-js encode "Print me" --print -o print.svg
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Print mode enforces a minimum four-module quiet zone and uses the print-safe rendering defaults.
|
|
25
|
+
|
|
26
|
+
## Compression
|
|
27
|
+
|
|
28
|
+
Compression works directly with normal text payloads:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx quadqr-js encode "repeat repeat repeat repeat" \
|
|
32
|
+
--compression auto \
|
|
33
|
+
-o compressed.png
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Compression modes are `none`, `auto`, and `lz`. `auto` keeps the original payload untouched when compression does not make it smaller. No separate payload mode is required.
|
|
37
|
+
|
|
38
|
+
## Signed QuadQR
|
|
39
|
+
|
|
40
|
+
Generate an Ed25519 signing-key bundle:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx quadqr-js signkeygen -o quadqr-signing-key.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Keep this file secret because it contains the private signing key. Encode a signed payload with:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
50
|
+
--sign-key quadqr-signing-key.json \
|
|
51
|
+
-o signed.png
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The generated key bundle contains both keys plus a compact `keyId`. Only the private key signs. The public key is **not embedded** in the QuadQR by default and should be distributed separately to trusted scanners or stored in a trusted-key registry.
|
|
55
|
+
|
|
56
|
+
To override the identifier stored in the symbol:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx quadqr-js encode "Offline-verifiable ticket" \
|
|
60
|
+
--sign-key quadqr-signing-key.json \
|
|
61
|
+
--key-id event-main-2026 \
|
|
62
|
+
-o signed.png
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Signing can be combined with password or raw-key encryption:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx quadqr-js encode "Signed and private" \
|
|
69
|
+
--sign-key quadqr-signing-key.json \
|
|
70
|
+
--password "my-password" \
|
|
71
|
+
-o signed-secure.png
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Decode an image
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx quadqr-js decode hello.png
|
|
78
|
+
npx quadqr-js decode signed.png --verify-key quadqr-signing-key.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For an unencrypted text payload, the decoded text is printed to stdout. To verify a signed symbol against a trusted public key, pass the signing bundle with `--verify-key`.
|
|
82
|
+
|
|
83
|
+
Add scanner diagnostics without changing the normal stdout payload:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx quadqr-js decode hello.png --debug
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Diagnostics are written to stderr and include confidence, geometry/color confidence, ECC utilization, signing state, and the scanner diagnostics object when available.
|
|
90
|
+
|
|
91
|
+
## Password-protected payloads
|
|
92
|
+
|
|
93
|
+
Encode:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx quadqr-js encode "Private data" --password "my-password" -o secure.png
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Decode:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx quadqr-js decode secure.png --password "my-password"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If an encrypted symbol is decoded without a credential, the CLI reports that decryption is required instead of exposing plaintext.
|
|
106
|
+
|
|
107
|
+
## Raw 256-bit key mode
|
|
108
|
+
|
|
109
|
+
Generate a random 256-bit encryption key:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx quadqr-js keygen
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The output is a 64-character hexadecimal key. Store it securely and do not place it inside the same QuadQR symbol.
|
|
116
|
+
|
|
117
|
+
Encode using the key:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx quadqr-js encode "Application secret" --key <64-hex-key> -o secure-key.png
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Decode using the key:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx quadqr-js decode secure-key.png --key <64-hex-key>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Options
|
|
130
|
+
|
|
131
|
+
| Option | Purpose |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `-o, --output <file>` | Output PNG/SVG path, or signing-key JSON path for `signkeygen` |
|
|
134
|
+
| `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
|
|
135
|
+
| `--version <auto|1..40>` | Symbol version. Default: `auto` |
|
|
136
|
+
| `--compression <mode>` | `none`, `auto`, or `lz`. Default: `auto` |
|
|
137
|
+
| `--high-density` | Enable experimental Triangle16 High Density Mode |
|
|
138
|
+
| `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
|
|
139
|
+
| `--key-id <id>` | Override the signing key ID stored in the symbol |
|
|
140
|
+
| `--embed-public-key` | Explicit compatibility mode that embeds the public key |
|
|
141
|
+
| `--verify-key <file>` | Verify a signed symbol with a trusted Ed25519 key bundle |
|
|
142
|
+
| `--password <text>` | Password-mode encryption/decryption |
|
|
143
|
+
| `--key <hex>` | Raw 256-bit key encryption/decryption |
|
|
144
|
+
| `--print` | Use the print-safe render profile |
|
|
145
|
+
| `--image-size <px>` | Exact square output size in pixels. Default: `720` |
|
|
146
|
+
| `--module-size <px>` | Legacy pixels-per-module sizing. Used when `--image-size` is omitted |
|
|
147
|
+
| `--quiet-zone <modules>` | Quiet-zone size in modules. Default: `4` |
|
|
148
|
+
| `--debug` | Emit scanner diagnostics to stderr when decoding |
|
|
149
|
+
| `-h, --help` | Show CLI help |
|
|
150
|
+
|
|
151
|
+
Password mode and raw-key mode are mutually exclusive for a single operation.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## High Density Mode (Experimental)
|
|
155
|
+
|
|
156
|
+
Use `--high-density` to enable the experimental Triangle16 layout with 16 states and 4 raw bits per body cell. Decode is automatic; no matching decode flag is required.
|