quadqr-js 1.0.2 → 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/docs/API.md CHANGED
@@ -110,12 +110,32 @@ Renders to an HTML canvas.
110
110
 
111
111
  ```js
112
112
  renderToCanvas(code, canvas, {
113
- moduleSize: 12,
113
+ imageSize: 720,
114
114
  quietZone: 4,
115
- style: "classic"
115
+ style: "classic",
116
+ logo: {
117
+ source: loadedImage,
118
+ size: 0.12,
119
+ clearBackground: true,
120
+ padding: 0.65,
121
+ radius: 0.8
122
+ }
116
123
  });
117
124
  ```
118
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
+
119
139
  Supported styles:
120
140
 
121
141
  - `classic`
@@ -135,6 +155,23 @@ Returns runtime-neutral RGBA pixels:
135
155
  }
136
156
  ```
137
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
+
138
175
  ## Image and camera scanning
139
176
 
140
177
  ### `scanImageData(imageData, options?)`
@@ -225,7 +262,22 @@ Writes a PNG file.
225
262
 
226
263
  ```js
227
264
  await savePNG(code, "quadqr.png", {
228
- moduleSize: 12,
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,
229
281
  quietZone: 4
230
282
  });
231
283
  ```
@@ -315,6 +367,10 @@ Common exported constants include:
315
367
  - `CELL`
316
368
  - `DEFAULT_PALETTE`
317
369
  - `RENDER_STYLES`
370
+ - `RENDER_MODES`
371
+ - `COMPRESSION_MODES`
372
+ - `SIGNATURE_ALGORITHMS`
373
+ - `STRESS_PROFILES`
318
374
  - `ECC_LEVELS`
319
375
 
320
376
  ## Benchmark entry
@@ -324,6 +380,166 @@ Benchmark helpers are available from `quadqr-js/benchmark`:
324
380
  ```js
325
381
  import {
326
382
  buildCapacityComparison,
327
- benchmarkCodec
383
+ benchmarkCodec,
384
+ calculateCapacityPlan
328
385
  } from "quadqr-js/benchmark";
329
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.
@@ -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
- moduleSize: 12,
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.1/dist/quadqr.min.js"></script>
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
- moduleSize: 12,
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.1/dist/quadqr.min.js"></script>
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.1/dist/browser.js";
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.1` in CDN URLs so an existing site does not silently change when a newer package version becomes available.
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,150 @@
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
- Optional encoding controls:
12
-
13
- ```bash
14
- npx quadqr-js encode "Hello" --ecc M --version auto --module-size 12 --quiet-zone 4 -o hello.png
15
- ```
16
-
17
- ## Decode an image
18
-
19
- ```bash
20
- npx quadqr-js decode hello.png
21
- ```
22
-
23
- For an unencrypted text payload, the decoded text is printed to stdout.
24
-
25
- ## Password-protected payloads
26
-
27
- Encode:
28
-
29
- ```bash
30
- npx quadqr-js encode "Private data" --password "my-password" -o secure.png
31
- ```
32
-
33
- Decode:
34
-
35
- ```bash
36
- npx quadqr-js decode secure.png --password "my-password"
37
- ```
38
-
39
- If an encrypted symbol is decoded without a credential, the CLI reports that decryption is required instead of exposing plaintext.
40
-
41
- ## Raw 256-bit key mode
42
-
43
- Generate a random 256-bit key:
44
-
45
- ```bash
46
- npx quadqr-js keygen
47
- ```
48
-
49
- The output is a 64-character hexadecimal key. Store it securely and do not place it inside the same QuadQR symbol.
50
-
51
- Encode using the key:
52
-
53
- ```bash
54
- npx quadqr-js encode "Application secret" --key <64-hex-key> -o secure-key.png
55
- ```
56
-
57
- Decode using the key:
58
-
59
- ```bash
60
- npx quadqr-js decode secure-key.png --key <64-hex-key>
61
- ```
62
-
63
- ## Options
64
-
65
- | Option | Purpose |
66
- | --- | --- |
67
- | `-o, --output <file>` | Output PNG path. Default: `quadqr.png` |
68
- | `--ecc <L|M|Q|H>` | QuadQR ECC profile. Default: `M` |
69
- | `--version <auto|1..40>` | Symbol version. Default: `auto` |
70
- | `--password <text>` | Password-mode encryption/decryption |
71
- | `--key <hex>` | Raw 256-bit key encryption/decryption |
72
- | `--module-size <px>` | PNG pixels per module. Default: `12` |
73
- | `--quiet-zone <modules>` | Quiet-zone size. Default: `4` |
74
- | `-h, --help` | Show CLI help |
75
-
76
- Password mode and raw-key mode are mutually exclusive for a single operation.
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
+ | `--sign-key <file>` | Sign using a key bundle generated by `signkeygen` |
138
+ | `--key-id <id>` | Override the signing key ID stored in the symbol |
139
+ | `--embed-public-key` | Explicit compatibility mode that embeds the public key |
140
+ | `--verify-key <file>` | Verify a signed symbol with a trusted Ed25519 key bundle |
141
+ | `--password <text>` | Password-mode encryption/decryption |
142
+ | `--key <hex>` | Raw 256-bit key encryption/decryption |
143
+ | `--print` | Use the print-safe render profile |
144
+ | `--image-size <px>` | Exact square output size in pixels. Default: `720` |
145
+ | `--module-size <px>` | Legacy pixels-per-module sizing. Used when `--image-size` is omitted |
146
+ | `--quiet-zone <modules>` | Quiet-zone size in modules. Default: `4` |
147
+ | `--debug` | Emit scanner diagnostics to stderr when decoding |
148
+ | `-h, --help` | Show CLI help |
149
+
150
+ Password mode and raw-key mode are mutually exclusive for a single operation.