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/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
- moduleSize: 12,
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 run after the normal scan path fails.
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 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.
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
- moduleSize: 12,
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.
@@ -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,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
- 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
+ | `--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.