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/FORMAT.md +11 -0
- package/README.md +165 -11
- 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 +1435 -74
- package/dist/quadqr.js +1435 -76
- package/dist/quadqr.min.js +1429 -76
- package/docs/API.md +220 -4
- package/docs/BROWSER_CDN.md +51 -6
- package/docs/CLI.md +150 -76
- package/docs/GETTING_STARTED.md +80 -7
- package/docs/NODE.md +137 -123
- package/docs/README.md +59 -41
- package/docs/WASM.md +2 -2
- package/package.json +8 -3
- package/types/benchmark.d.ts +4 -3
- package/types/index.d.ts +115 -1
- package/types/node.d.ts +2 -0
package/FORMAT.md
CHANGED
|
@@ -388,3 +388,14 @@ Format v5 is intentionally incompatible with standard QR scanners and with the p
|
|
|
388
388
|
## Rendering profiles are not part of the wire format
|
|
389
389
|
|
|
390
390
|
The canonical QuadQR matrix is independent of presentation style. Renderers may offer styles such as `classic`, `depth`, `soft`, or `inset`, provided structural finder/timing/alignment/calibration references remain sufficiently faithful for decoding. Style selection is not encoded in the payload/header and does not change matrix cell values.
|
|
391
|
+
|
|
392
|
+
## Compression and signature flags
|
|
393
|
+
|
|
394
|
+
Format v5 keeps the physical matrix and ECC framing unchanged while reserving two protected-header flags for optional internal payload metadata:
|
|
395
|
+
|
|
396
|
+
```text
|
|
397
|
+
bit 4 = internal payload-extension metadata present
|
|
398
|
+
bit 5 = signed-payload hint
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
These bits are not a public payload-type mode. Applications continue to encode normal UTF-8 text or arbitrary bytes. Compression and Ed25519 signing use a compact internal extension envelope only when metadata is required. The envelope, print profile, diagnostics, and scanability test model are specified in [`SPECIFICATION.md`](./SPECIFICATION.md). None of these features changes RGBW cell mapping or Spectrum ECC.
|
package/README.md
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
·
|
|
22
22
|
<a href="docs/README.md">Markdown Docs</a>
|
|
23
23
|
·
|
|
24
|
-
<a href="FORMAT.md">Format
|
|
24
|
+
<a href="FORMAT.md">Matrix Format</a>
|
|
25
|
+
·
|
|
26
|
+
<a href="SPECIFICATION.md">Technical Specification</a>
|
|
25
27
|
</p>
|
|
26
28
|
|
|
27
29
|
**QuadQR** is an experimental open-source 2D matrix code that uses four visible data states instead of the two states used by a traditional black-and-white QR module.
|
|
@@ -179,6 +181,56 @@ The scanner never expects the secret to be embedded in the QuadQR itself. Raw-ke
|
|
|
179
181
|
|
|
180
182
|
---
|
|
181
183
|
|
|
184
|
+
## Compression, signatures, and robustness tooling
|
|
185
|
+
|
|
186
|
+
QuadQR keeps the application payload simple: **text stays text and bytes stay bytes**. There is no public payload-type registry to maintain. Compression and signing metadata are added only as internal implementation details when those features are enabled.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import {
|
|
190
|
+
encodeText,
|
|
191
|
+
encodeSignedText,
|
|
192
|
+
generateSigningKeyPair,
|
|
193
|
+
decodeMatrix,
|
|
194
|
+
verifyDecodedSignature
|
|
195
|
+
} from "quadqr-js";
|
|
196
|
+
|
|
197
|
+
// Auto compression is zero-overhead when it does not help.
|
|
198
|
+
const compressed = encodeText("repeated repeated repeated", {
|
|
199
|
+
compression: "auto",
|
|
200
|
+
ecc: "M"
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const keys = await generateSigningKeyPair();
|
|
204
|
+
const signed = await encodeSignedText("verified offline", {
|
|
205
|
+
compression: "auto",
|
|
206
|
+
privateKey: keys.privateKey,
|
|
207
|
+
keyId: keys.keyId
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const decoded = decodeMatrix(signed.matrix);
|
|
211
|
+
const verified = await verifyDecodedSignature(decoded, {
|
|
212
|
+
publicKey: keys.publicKey
|
|
213
|
+
});
|
|
214
|
+
console.log(verified.signatureVerified); // true
|
|
215
|
+
console.log(verified.signatureTrusted); // true
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Compression modes are `none`, `auto`, and `lz`. `auto` keeps the original payload untouched when compression would not save space. Ed25519 signing stores the signature plus an optional compact `keyId`; the public verification key stays outside the QuadQR by default. Applications do not need to choose or maintain content types.
|
|
219
|
+
|
|
220
|
+
Signing can also be composed with Secure Payload. QuadQR compresses if requested, signs the normal payload with the private key, then encrypts the protected bytes with AES-256-GCM. A verifier supplies the trusted public key separately, or resolves it from `keyId`.
|
|
221
|
+
|
|
222
|
+
The renderer supports an explicit `mode: "print"`. Print mode enforces a minimum 4-module quiet zone, uses darker print-safe RGB defaults, and prefers Classic solid modules. `getPrintGuidance()` converts a chosen physical size into module millimeters/pixels so print layouts can be checked before production testing.
|
|
223
|
+
|
|
224
|
+
Centered logos support `size: "auto"`, which estimates a conservative ECC-aware ratio from code utilization and rendering choices. `findMaxSafeLogoSize()` can additionally probe ImageData output and empirically search for the largest size that still decodes.
|
|
225
|
+
|
|
226
|
+
Scanner results include normalized diagnostics such as `confidence`, `geometryConfidence`, `calibrationConfidence`, `structureConfidence`, `eccUtilization`, and `correctedErrors`. Set `debug: true` or call `debugScanImageData()` to inspect finder/geometry candidates, the sampled matrix, color-confidence data, ECC stages, and the stage that failed.
|
|
227
|
+
|
|
228
|
+
For regression and demo testing, `runImageStressTest()` / `assessScanability()` apply deterministic blur, brightness, exposure, shadow, contrast, perspective, JPEG-like artifacts, and downscaling. The browser demo exposes the same tools as an interactive stress-test lab and shows an overall scanability rating.
|
|
229
|
+
|
|
230
|
+
The interoperability details are documented in [`SPECIFICATION.md`](./SPECIFICATION.md).
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
182
234
|
## Current benchmark
|
|
183
235
|
|
|
184
236
|
Run:
|
|
@@ -508,7 +560,7 @@ The `inset` style preserves the exact encoded R/G/B/W color around the center of
|
|
|
508
560
|
|
|
509
561
|
```js
|
|
510
562
|
renderToCanvas(code, canvas, {
|
|
511
|
-
|
|
563
|
+
imageSize: 720,
|
|
512
564
|
quietZone: 4,
|
|
513
565
|
style: "inset" // classic | depth | soft | inset
|
|
514
566
|
});
|
|
@@ -516,13 +568,58 @@ renderToCanvas(code, canvas, {
|
|
|
516
568
|
|
|
517
569
|
The styling is deterministic. Generating the same matrix with the same style produces the same visual tile treatment rather than changing randomly on every render.
|
|
518
570
|
|
|
571
|
+
`imageSize` sets the exact square output size in pixels. When neither `imageSize` nor `moduleSize` is supplied, QuadQR renders at **720 × 720 px** by default. `moduleSize` remains available as the lower-level legacy sizing control; when `imageSize` is supplied, the exact image size takes precedence.
|
|
572
|
+
|
|
573
|
+
### Logo overlays, quiet zones, and SVG export
|
|
574
|
+
|
|
575
|
+
The renderer can place a centered logo over the symbol. Transparent pixels in the logo stay transparent, so the QuadQR modules remain visible through those areas. Enable `clearBackground` when you want a clean padded white area behind the logo instead.
|
|
576
|
+
|
|
577
|
+
```js
|
|
578
|
+
const logoImage = new Image();
|
|
579
|
+
logoImage.src = "/brand-mark.png";
|
|
580
|
+
await logoImage.decode();
|
|
581
|
+
|
|
582
|
+
renderToCanvas(code, canvas, {
|
|
583
|
+
imageSize: 720,
|
|
584
|
+
quietZone: 6,
|
|
585
|
+
style: "classic",
|
|
586
|
+
logo: {
|
|
587
|
+
source: logoImage,
|
|
588
|
+
size: 0.12,
|
|
589
|
+
clearBackground: true,
|
|
590
|
+
padding: 0.65,
|
|
591
|
+
radius: 0.8
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
`quietZone` is measured in modules and can be set to `0` or increased for print/camera use. Four modules remains the recommended default.
|
|
597
|
+
|
|
598
|
+
SVG uses the same matrix, palette, styles, quiet-zone size, and logo geometry:
|
|
599
|
+
|
|
600
|
+
```js
|
|
601
|
+
import { renderToSVG } from "quadqr-js";
|
|
602
|
+
|
|
603
|
+
const svg = renderToSVG(code, {
|
|
604
|
+
imageSize: 720,
|
|
605
|
+
quietZone: 4,
|
|
606
|
+
logo: {
|
|
607
|
+
source: "data:image/png;base64,...",
|
|
608
|
+
size: 0.12,
|
|
609
|
+
clearBackground: true
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Logo overlays intentionally consume some ECC margin because they cover encoded cells. Keep logos conservative, especially with `L`/`M` ECC. The browser demo verifies the final rendered image before enabling downloads.
|
|
615
|
+
|
|
519
616
|
---
|
|
520
617
|
|
|
521
618
|
## Demo
|
|
522
619
|
|
|
523
620
|
**Live demo:** https://akanshsirohi.github.io/QuadQR/demo/
|
|
524
621
|
|
|
525
|
-
The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded.
|
|
622
|
+
The browser demo runs directly on GitHub Pages and is split into separate views so the interface does not become overloaded. The generator keeps only payload, version, and ECC visible by default; optional capabilities are grouped into independent advanced accordions.
|
|
526
623
|
|
|
527
624
|
### Generator & Image Scanner
|
|
528
625
|
|
|
@@ -531,9 +628,13 @@ Use this tab to:
|
|
|
531
628
|
- enter text/data;
|
|
532
629
|
- generate a QuadQR code;
|
|
533
630
|
- select ECC;
|
|
631
|
+
- open Output & Rendering controls for image size, quiet zone, style, and print mode;
|
|
632
|
+
- open the Center Logo accordion for branding;
|
|
633
|
+
- enable internal automatic compression without choosing a payload type;
|
|
634
|
+
- optionally add an Ed25519 signature;
|
|
534
635
|
- optionally encrypt using a password or raw 256-bit key;
|
|
535
636
|
- inspect version and capacity;
|
|
536
|
-
- download
|
|
637
|
+
- download PNG or SVG output;
|
|
537
638
|
- scan an uploaded image.
|
|
538
639
|
|
|
539
640
|
### Camera Scanner
|
|
@@ -552,7 +653,43 @@ The benchmark tab provides an easier visual view of:
|
|
|
552
653
|
- comparison with standard QR;
|
|
553
654
|
- byte gain;
|
|
554
655
|
- capacity ratio;
|
|
555
|
-
- codec timing
|
|
656
|
+
- codec timing;
|
|
657
|
+
- capacity planning;
|
|
658
|
+
- interactive stress testing.
|
|
659
|
+
|
|
660
|
+
Benchmark tools are also separated into accordions so the page stays compact until a tool is needed.
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## New API highlights
|
|
665
|
+
|
|
666
|
+
```js
|
|
667
|
+
import {
|
|
668
|
+
encodeText,
|
|
669
|
+
encodeUint8Array,
|
|
670
|
+
decodeUint8Array,
|
|
671
|
+
renderToImageData,
|
|
672
|
+
assessScanability,
|
|
673
|
+
getPrintGuidance
|
|
674
|
+
} from "quadqr-js";
|
|
675
|
+
|
|
676
|
+
const binary = encodeUint8Array(new Uint8Array([1, 2, 3, 4]), { ecc: "M" });
|
|
677
|
+
const bytes = decodeUint8Array(binary.matrix);
|
|
678
|
+
|
|
679
|
+
const code = encodeText("Hello hello hello hello", {
|
|
680
|
+
compression: "auto",
|
|
681
|
+
ecc: "Q"
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const printImage = renderToImageData(code, {
|
|
685
|
+
imageSize: 1200,
|
|
686
|
+
mode: "print",
|
|
687
|
+
logo: { source: logoImageData, size: "auto", clearBackground: true }
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
console.log(getPrintGuidance(code, { physicalSizeMm: 45, dpi: 300 }));
|
|
691
|
+
console.log(assessScanability(code, { imageSize: 480 }));
|
|
692
|
+
```
|
|
556
693
|
|
|
557
694
|
---
|
|
558
695
|
|
|
@@ -583,14 +720,15 @@ const result = decodeMatrix(code.matrix);
|
|
|
583
720
|
console.log(result.text);
|
|
584
721
|
```
|
|
585
722
|
|
|
586
|
-
Node PNG usage:
|
|
723
|
+
Node PNG/SVG usage:
|
|
587
724
|
|
|
588
725
|
```js
|
|
589
726
|
import { encodeText } from "quadqr-js";
|
|
590
|
-
import { savePNG, scanFile } from "quadqr-js/node";
|
|
727
|
+
import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
|
|
591
728
|
|
|
592
729
|
const code = encodeText("Generated on Node.js");
|
|
593
|
-
await savePNG(code, "quadqr.png", {
|
|
730
|
+
await savePNG(code, "quadqr.png", { imageSize: 720, quietZone: 4 });
|
|
731
|
+
await saveSVG(code, "quadqr.svg", { imageSize: 720, quietZone: 4 });
|
|
594
732
|
|
|
595
733
|
const result = await scanFile("quadqr.png");
|
|
596
734
|
console.log(result.text);
|
|
@@ -601,7 +739,7 @@ console.log(result.text);
|
|
|
601
739
|
The `quadqr-js` package can be loaded directly from npm-backed CDNs:
|
|
602
740
|
|
|
603
741
|
```html
|
|
604
|
-
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.0
|
|
742
|
+
<script src="https://cdn.jsdelivr.net/npm/quadqr-js@1.1.0/dist/quadqr.min.js"></script>
|
|
605
743
|
<script>
|
|
606
744
|
const code = QuadQR.encodeText("Hello from a script tag");
|
|
607
745
|
</script>
|
|
@@ -618,6 +756,16 @@ npx quadqr-js encode "Hello QuadQR" -o hello.png
|
|
|
618
756
|
npx quadqr-js decode hello.png
|
|
619
757
|
```
|
|
620
758
|
|
|
759
|
+
Compressed, print-safe, and signed output are also available:
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
npx quadqr-js encode "repeat repeat repeat" --compression auto -o compressed.svg
|
|
763
|
+
npx quadqr-js encode "Print me" --print -o print.svg
|
|
764
|
+
npx quadqr-js signkeygen -o signing-key.json
|
|
765
|
+
npx quadqr-js encode "Verified ticket" --sign-key signing-key.json -o signed.png
|
|
766
|
+
npx quadqr-js decode signed.png --verify-key signing-key.json
|
|
767
|
+
```
|
|
768
|
+
|
|
621
769
|
Password-protected payloads use the same commands:
|
|
622
770
|
|
|
623
771
|
```bash
|
|
@@ -625,7 +773,7 @@ npx quadqr-js encode "Private data" --password "my-password" -o secure.png
|
|
|
625
773
|
npx quadqr-js decode secure.png --password "my-password"
|
|
626
774
|
```
|
|
627
775
|
|
|
628
|
-
See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including raw 256-bit key mode.
|
|
776
|
+
See [`docs/CLI.md`](docs/CLI.md) for all CLI options, including compression, raw 256-bit key mode, signing, print mode, and scanner diagnostics.
|
|
629
777
|
|
|
630
778
|
### Run from source
|
|
631
779
|
|
|
@@ -862,7 +1010,7 @@ The result reports fields such as `spectralInterleaving`, `confidenceAssisted`,
|
|
|
862
1010
|
|
|
863
1011
|
### `renderToCanvas(codeOrMatrix, canvas, options?)`
|
|
864
1012
|
|
|
865
|
-
Renders a QuadQR symbol into a browser canvas. `options.style` supports `classic`, `depth`, `soft`, and `inset`.
|
|
1013
|
+
Renders a QuadQR symbol into a browser canvas. `imageSize` sets the exact square pixel output and defaults to 720 when neither sizing option is supplied. `moduleSize` remains available for legacy pixels-per-module sizing. `options.style` supports `classic`, `depth`, `soft`, and `inset`. `quietZone` controls the border in modules. `logo` accepts a loaded image/canvas source or `{ source, size, clearBackground, padding, radius, backgroundColor }`.
|
|
866
1014
|
|
|
867
1015
|
### `renderToImageData(codeOrMatrix, options?)`
|
|
868
1016
|
|
|
@@ -878,6 +1026,12 @@ Returns an ImageData-like object and supports the same rendering styles as `rend
|
|
|
878
1026
|
|
|
879
1027
|
This is also useful for tests and non-DOM workflows.
|
|
880
1028
|
|
|
1029
|
+
When a logo is used with `renderToImageData()`, its source must be an ImageData-like `{ width, height, data }` object so the renderer can composite it without DOM APIs.
|
|
1030
|
+
|
|
1031
|
+
### `renderToSVG(codeOrMatrix, options?)`
|
|
1032
|
+
|
|
1033
|
+
Returns a standalone SVG string using the same exact `imageSize`, render styles, and quiet-zone controls. SVG logo sources can be a URL/data URL string or an object with a `src` string. The SVG remains vector-sharp regardless of how large the preview is displayed.
|
|
1034
|
+
|
|
881
1035
|
### `scanImageData(imageData, options?)`
|
|
882
1036
|
|
|
883
1037
|
Runs the complete perspective-aware and color-aware image scanner. The scanner first tries the normal detected geometry with the observed RGB palette, preserving the fast path for clean images. Only after that fails does it progressively fall back to per-channel white balancing, spatial black/white normalization, tighter centre sampling, a cheap module-grid Auto Tone / Auto Contrast / Auto Color-style recovery, a rectified QR-region pixel enhancement pass, and finally bounded sub-module geometry micro-refinement. If locator detection itself is weakened by a flat/yellow frame, a full-image enhancement retry is also available. RGBW confidence values are carried into Reed-Solomon so ambiguous cells can be treated as erasures when ordinary hard-decision ECC is insufficient.
|
package/SPECIFICATION.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# QuadQR Technical Specification
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
QuadQR is an experimental four-state RGBW matrix symbology. It is not ISO/IEC QR Code and is not intended to be decoded by standard QR readers.
|
|
6
|
+
|
|
7
|
+
The physical matrix format remains **QuadQR Format v5**. Normal application data is always treated simply as UTF-8 text or arbitrary bytes. Compression, signatures, encryption, rendering, and diagnostics are optional features layered around that stable matrix codec.
|
|
8
|
+
|
|
9
|
+
For exact matrix geometry and Reed-Solomon framing, see [`FORMAT.md`](./FORMAT.md).
|
|
10
|
+
|
|
11
|
+
## 1. Layer model
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Application bytes / UTF-8 text
|
|
15
|
+
│
|
|
16
|
+
├─ optional compression metadata (internal)
|
|
17
|
+
├─ optional Ed25519 signature metadata (internal)
|
|
18
|
+
├─ optional Secure Payload v1 (AES-256-GCM)
|
|
19
|
+
│
|
|
20
|
+
└─ QuadQR Format v5
|
|
21
|
+
├─ protected header
|
|
22
|
+
├─ CRC-32
|
|
23
|
+
├─ GF(256) Reed-Solomon Spectrum ECC
|
|
24
|
+
├─ spectral-spatial interleaving
|
|
25
|
+
└─ RGBW matrix
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
There is deliberately **no public payload-type registry**. Applications do not select URL, JSON, contact, Wi-Fi, or other semantic types. They encode text or bytes and interpret that data themselves.
|
|
29
|
+
|
|
30
|
+
## 2. Physical cell alphabet
|
|
31
|
+
|
|
32
|
+
Data modules use exactly four states:
|
|
33
|
+
|
|
34
|
+
| State | Bits | Internal value |
|
|
35
|
+
|---|---|---:|
|
|
36
|
+
| Red | `00` | 0 |
|
|
37
|
+
| Green | `01` | 1 |
|
|
38
|
+
| Blue | `10` | 2 |
|
|
39
|
+
| White | `11` | 3 |
|
|
40
|
+
|
|
41
|
+
Structural black is separate from the data alphabet. One encoded byte maps to exactly four RGBW data cells.
|
|
42
|
+
|
|
43
|
+
## 3. Matrix sizing
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
size = 21 + 4 × (version - 1)
|
|
47
|
+
version = 1..40
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Three 7×7 finder patterns are always present. Version 1 has the legacy QuadQR 5×5 bottom-right alignment marker. Versions 2–40 use the distributed alignment schedule defined in `FORMAT.md`.
|
|
51
|
+
|
|
52
|
+
## 4. Format v5 header flags
|
|
53
|
+
|
|
54
|
+
The protected Format v5 header uses:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
bit 0 UTF-8 text flag
|
|
58
|
+
bits 1..2 ECC profile id
|
|
59
|
+
bit 3 Secure Payload v1 envelope
|
|
60
|
+
bit 4 internal payload-extension metadata present
|
|
61
|
+
bit 5 signed-payload hint
|
|
62
|
+
bits 6..7 reserved
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Bit 4 is an implementation/interoperability hint used only when compression or signing requires metadata. It is not a user-selectable payload mode.
|
|
66
|
+
|
|
67
|
+
## 5. Spectrum ECC
|
|
68
|
+
|
|
69
|
+
The field remains:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
GF(2^8) = GF(256)
|
|
73
|
+
primitive polynomial = 0x11d
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Version 2+ parity profiles:
|
|
77
|
+
|
|
78
|
+
| Profile | Parity bytes/block | Correctable unknown byte errors/block |
|
|
79
|
+
|---|---:|---:|
|
|
80
|
+
| L | 12 | 6 |
|
|
81
|
+
| M | 24 | 12 |
|
|
82
|
+
| Q | 36 | 18 |
|
|
83
|
+
| H | 48 | 24 |
|
|
84
|
+
|
|
85
|
+
The decoder retains per-cell color confidence. Low-confidence bytes can be promoted to known erasures, allowing Reed-Solomon recovery to use the existing parity budget more efficiently.
|
|
86
|
+
|
|
87
|
+
## 6. Internal payload extension envelope
|
|
88
|
+
|
|
89
|
+
Compression and signing require a small amount of metadata. QuadQR stores that metadata in an **internal extension envelope** only when needed. Applications should normally use `encodeText()`, `encodeBytes()`, `encodeSignedText()`, or `encodeSignedBytes()` and never construct this envelope themselves.
|
|
90
|
+
|
|
91
|
+
Current envelope magic:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
QPX1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Fixed header size: **16 bytes**.
|
|
98
|
+
|
|
99
|
+
| Offset | Size | Field |
|
|
100
|
+
|---:|---:|---|
|
|
101
|
+
| 0 | 4 | ASCII magic `QPX1` |
|
|
102
|
+
| 4 | 1 | Extension version (`2`; decoder also accepts legacy `1`) |
|
|
103
|
+
| 5 | 1 | Flags |
|
|
104
|
+
| 6 | 1 | Compression ID |
|
|
105
|
+
| 7 | 1 | Signature algorithm ID |
|
|
106
|
+
| 8 | 4 | Original application payload length, big-endian |
|
|
107
|
+
| 12 | 1 | Signing key-ID length (v2); legacy signer-label length in v1 |
|
|
108
|
+
| 13 | 1 | Optional embedded public-key length |
|
|
109
|
+
| 14 | 1 | Signature length |
|
|
110
|
+
| 15 | 1 | Reserved (`0`) |
|
|
111
|
+
|
|
112
|
+
Variable bytes follow as:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
keyId || optionalPublicKey || signature || storedPayload
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Envelope flags currently use:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
bit 0 signed
|
|
122
|
+
bit 1 compressed
|
|
123
|
+
bit 2 public key embedded
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The envelope contains no semantic content type. The Format v5 text flag still determines whether the recovered application payload should be decoded as UTF-8 text.
|
|
127
|
+
|
|
128
|
+
## 7. Compression
|
|
129
|
+
|
|
130
|
+
Compression IDs:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
0 = none
|
|
134
|
+
1 = QuadQR portable LZ
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The portable LZ stream is an LZSS-style format with groups of up to eight tokens. Each group begins with one flag byte. A flag bit of `0` means a one-byte literal. A flag bit of `1` means a two-byte back-reference:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
12-bit offset: 1..4095 bytes
|
|
141
|
+
4-bit length: stored value + 3, therefore 3..18 bytes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Public compression modes are:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
none
|
|
148
|
+
auto
|
|
149
|
+
lz
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`compression: "auto"` uses compression only when it meaningfully reduces payload size. When it does not help and the payload is not signed, QuadQR stores the original payload directly with **no extension-envelope overhead**.
|
|
153
|
+
|
|
154
|
+
Compression occurs before signing, encryption, and Format v5 ECC.
|
|
155
|
+
|
|
156
|
+
## 8. Signed QuadQR
|
|
157
|
+
|
|
158
|
+
Signature algorithm ID `1` is **Ed25519**.
|
|
159
|
+
|
|
160
|
+
Signed payloads in extension v2 store:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
optional compact key ID
|
|
164
|
+
64-byte Ed25519 signature
|
|
165
|
+
optional 32-byte raw Ed25519 public key only when explicitly requested
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The normal production profile does **not** embed the public key. The signature covers:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
16-byte extension header
|
|
172
|
+
|| key ID
|
|
173
|
+
|| optional embedded public key
|
|
174
|
+
|| stored payload bytes
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The signature field itself is excluded from the signed message.
|
|
178
|
+
|
|
179
|
+
The private Ed25519 key is used only by the issuer to create signatures and must never be embedded in a QuadQR. Verification uses a trusted public key supplied externally by the application, server, certificate, or trusted-key registry. The optional key ID is only an identifier that helps the verifier select the correct trusted public key.
|
|
180
|
+
|
|
181
|
+
For compatibility or self-contained integrity checks, implementations may set `embedPublicKey: true`. A signature verified only against a key embedded in the same QuadQR proves integrity and key possession, but does not establish a trusted signer identity. Such verification should be reported separately from verification against an external trust anchor.
|
|
182
|
+
|
|
183
|
+
Legacy extension v1 symbols used a signer label and embedded public key. Decoders may continue to read and verify those symbols for backward compatibility.
|
|
184
|
+
|
|
185
|
+
## 9. Secure + signed composition
|
|
186
|
+
|
|
187
|
+
When compression, signing, and encryption are combined, QuadQR uses:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
application payload
|
|
191
|
+
→ optional compression
|
|
192
|
+
→ optional Ed25519 signature metadata
|
|
193
|
+
→ AES-256-GCM Secure Payload v1
|
|
194
|
+
→ Format v5 ECC/matrix
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
After scanning:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
Format v5 decode
|
|
201
|
+
→ AES-GCM authentication/decryption
|
|
202
|
+
→ internal compression/signature metadata processing
|
|
203
|
+
→ application payload
|
|
204
|
+
→ optional Ed25519 signature verification
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
This keeps signature metadata confidential when encryption is enabled while preserving offline verification after decryption.
|
|
208
|
+
|
|
209
|
+
## 10. Text and binary APIs
|
|
210
|
+
|
|
211
|
+
Text and bytes are first-class without semantic payload types:
|
|
212
|
+
|
|
213
|
+
- `encodeText()` / `decodeMatrix().text` for UTF-8 text;
|
|
214
|
+
- `encodeBytes()` for arbitrary bytes;
|
|
215
|
+
- `encodeUint8Array()` / `decodeUint8Array()` as explicit byte-oriented convenience APIs.
|
|
216
|
+
|
|
217
|
+
Compression works on both text and byte payloads through the same `compression` option.
|
|
218
|
+
|
|
219
|
+
## 11. Rendering profiles
|
|
220
|
+
|
|
221
|
+
Rendering does not modify the encoded matrix.
|
|
222
|
+
|
|
223
|
+
### Screen mode
|
|
224
|
+
|
|
225
|
+
Uses the normal RGBW palette and permits:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
classic
|
|
229
|
+
soft
|
|
230
|
+
depth
|
|
231
|
+
inset
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Print mode
|
|
235
|
+
|
|
236
|
+
`mode: "print"` applies conservative defaults:
|
|
237
|
+
|
|
238
|
+
- minimum quiet zone of 4 modules unless explicitly overridden;
|
|
239
|
+
- print-safe darker RGB primaries;
|
|
240
|
+
- Classic solid-module rendering by default;
|
|
241
|
+
- physical-size guidance through `getPrintGuidance()`.
|
|
242
|
+
|
|
243
|
+
Recommended general-purpose starting module size is **0.40 mm/module**. Real printer, paper, ink/toner, lamination, lighting, and camera validation remains necessary for production deployments.
|
|
244
|
+
|
|
245
|
+
## 12. Logo safety
|
|
246
|
+
|
|
247
|
+
Logos are rendering overlays and never modify Format v5 data structures.
|
|
248
|
+
|
|
249
|
+
`size: "auto"` estimates a conservative logo ratio from:
|
|
250
|
+
|
|
251
|
+
- ECC profile;
|
|
252
|
+
- encoded utilization;
|
|
253
|
+
- version 1 compact-profile penalty;
|
|
254
|
+
- clear-background usage;
|
|
255
|
+
- screen vs print mode.
|
|
256
|
+
|
|
257
|
+
`findMaxSafeLogoSize()` can empirically search the largest decodable logo size when an ImageData-like logo is available. Generated symbols should still be verified after final rendering.
|
|
258
|
+
|
|
259
|
+
## 13. Scanner diagnostics
|
|
260
|
+
|
|
261
|
+
Successful image scans expose normalized diagnostic fields including:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
confidence
|
|
265
|
+
geometryConfidence
|
|
266
|
+
calibrationConfidence
|
|
267
|
+
structureConfidence
|
|
268
|
+
eccUtilization
|
|
269
|
+
correctedErrors
|
|
270
|
+
erasureSymbols
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
With `debug: true`, the scanner can also report stage state, geometry candidates, finder/vision passes, latest sampled matrix, confidence matrix, color-normalization method, and the failed stage when decoding does not complete.
|
|
274
|
+
|
|
275
|
+
Debug output is diagnostic evidence, not a cryptographic assurance score.
|
|
276
|
+
|
|
277
|
+
## 14. Scanability score and torture testing
|
|
278
|
+
|
|
279
|
+
`runImageStressTest()` and `assessScanability()` use deterministic synthetic distortions to estimate robustness against:
|
|
280
|
+
|
|
281
|
+
- blur;
|
|
282
|
+
- low brightness;
|
|
283
|
+
- high exposure;
|
|
284
|
+
- uneven shadow;
|
|
285
|
+
- contrast loss;
|
|
286
|
+
- perspective distortion;
|
|
287
|
+
- JPEG-like quantization/block artifacts;
|
|
288
|
+
- downscaling.
|
|
289
|
+
|
|
290
|
+
Current rating bands:
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
90–100 Excellent
|
|
294
|
+
75–89 Good
|
|
295
|
+
50–74 Risky
|
|
296
|
+
0–49 Likely unscannable
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
These scores are regression/testing aids. They do not replace validation with real phone cameras, printers, displays, paper stocks, lighting conditions, and physical damage.
|
|
300
|
+
|
|
301
|
+
## 15. Capacity planning
|
|
302
|
+
|
|
303
|
+
Capacity is determined from the actual Format v5 layout, protected header, CRC, and Spectrum ECC plan. Compression can reduce stored payload bytes, while signatures and encryption add metadata bytes before the matrix codec.
|
|
304
|
+
|
|
305
|
+
The benchmark helper can report:
|
|
306
|
+
|
|
307
|
+
- minimum QuadQR version;
|
|
308
|
+
- encoded bytes;
|
|
309
|
+
- remaining capacity;
|
|
310
|
+
- utilization;
|
|
311
|
+
- approximate same-letter standard QR byte-mode version.
|
|
312
|
+
|
|
313
|
+
When only a payload byte count is known, `compression: "auto"` cannot predict the gain because compressibility depends on the actual bytes.
|
|
314
|
+
|
|
315
|
+
Standard QR comparisons use the same **nominal ECC letter only**. QuadQR and ISO QR recovery strengths are not equivalent and should not be presented as such.
|
|
316
|
+
|
|
317
|
+
## 16. Compatibility principles
|
|
318
|
+
|
|
319
|
+
Implementations should follow these rules:
|
|
320
|
+
|
|
321
|
+
1. Keep RGBW mapping exactly `R=00, G=01, B=10, W=11`.
|
|
322
|
+
2. Keep GF(256) Spectrum ECC and its errors+erasures behavior.
|
|
323
|
+
3. Preserve Format v5 decoding for normal and Secure Payload symbols.
|
|
324
|
+
4. Keep application semantics outside the QuadQR codec. Do not require a growing content-type registry.
|
|
325
|
+
5. Treat compression/signature metadata as internal transport metadata, not a separate user payload mode.
|
|
326
|
+
6. Treat `keyId` only as an identifier. Signer trust comes from an external trusted public-key binding.
|
|
327
|
+
7. Keep rendering effects out of structural finder/timing/alignment/calibration modules.
|
|
328
|
+
8. Validate public format changes with both matrix and rendered-image scanner tests.
|
|
329
|
+
|
|
330
|
+
## 17. Reference implementation
|
|
331
|
+
|
|
332
|
+
The JavaScript implementation in this repository is the current reference implementation. Public entry points are documented under `docs/` and exercised by `tests/self-test.js` and `tests/package-test.js`.
|