@sythos/js_barcode_universal 1.1.0 → 1.3.1

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.
Files changed (68) hide show
  1. package/LICENSE +24 -18
  2. package/NOTICE.md +27 -22
  3. package/README.md +596 -460
  4. package/bundle/sythos-barcode.esm.js +4606 -228
  5. package/bundle/sythos-barcode.js +4586 -228
  6. package/examples/create.html +1011 -731
  7. package/licenses/README.md +23 -18
  8. package/licenses/aztec-code.license +9 -7
  9. package/licenses/codabar.license +16 -13
  10. package/licenses/code-11.license +11 -9
  11. package/licenses/code-128.license +8 -6
  12. package/licenses/code-39.license +8 -6
  13. package/licenses/code-93.license +13 -11
  14. package/licenses/data-matrix.license +11 -9
  15. package/licenses/ean-13.license +7 -5
  16. package/licenses/ean-8.license +7 -5
  17. package/licenses/frameqr.license +84 -0
  18. package/licenses/gs1-128.license +7 -5
  19. package/licenses/isbn.license +8 -6
  20. package/licenses/itf-14.license +7 -5
  21. package/licenses/itf.license +7 -5
  22. package/licenses/micro-qr.license +79 -0
  23. package/licenses/micropdf417.license +81 -0
  24. package/licenses/msi-plessey.license +12 -10
  25. package/licenses/pdf417.license +78 -0
  26. package/licenses/pharmacode.license +13 -11
  27. package/licenses/qr-code.license +7 -5
  28. package/licenses/rmqr.license +79 -0
  29. package/licenses/upc-a.license +9 -7
  30. package/licenses/upc-e.license +7 -5
  31. package/package.json +104 -88
  32. package/src/core/reed-solomon.js +326 -312
  33. package/src/datamatrix/decoder.js +262 -262
  34. package/src/datamatrix/detector.js +225 -225
  35. package/src/datamatrix/encoder.js +191 -191
  36. package/src/datamatrix/index.js +42 -42
  37. package/src/datamatrix/tables.js +123 -123
  38. package/src/frameqr/decoder.js +239 -0
  39. package/src/frameqr/detector.js +192 -0
  40. package/src/frameqr/encoder.js +156 -0
  41. package/src/frameqr/index.js +42 -0
  42. package/src/frameqr/tables.js +270 -0
  43. package/src/index.js +166 -3
  44. package/src/micropdf417/compaction.js +116 -0
  45. package/src/micropdf417/decoder.js +183 -0
  46. package/src/micropdf417/detector.js +149 -0
  47. package/src/micropdf417/encoder.js +209 -0
  48. package/src/micropdf417/error-correction.js +55 -0
  49. package/src/micropdf417/index.js +49 -0
  50. package/src/micropdf417/tables.js +184 -0
  51. package/src/microqr/decoder.js +245 -0
  52. package/src/microqr/detector.js +355 -0
  53. package/src/microqr/encoder.js +269 -0
  54. package/src/microqr/index.js +36 -0
  55. package/src/microqr/tables.js +316 -0
  56. package/src/oned/index.js +59 -59
  57. package/src/pdf417/compaction.js +298 -0
  58. package/src/pdf417/decoder.js +75 -0
  59. package/src/pdf417/detector.js +468 -0
  60. package/src/pdf417/encoder.js +91 -0
  61. package/src/pdf417/error-correction.js +47 -0
  62. package/src/pdf417/index.js +6 -0
  63. package/src/pdf417/tables.js +317 -0
  64. package/src/rmqr/decoder.js +101 -0
  65. package/src/rmqr/detector.js +90 -0
  66. package/src/rmqr/encoder.js +172 -0
  67. package/src/rmqr/index.js +37 -0
  68. package/src/rmqr/tables.js +154 -0
package/README.md CHANGED
@@ -1,246 +1,267 @@
1
- # Sythos Barcode Suite
2
-
3
- Read and write barcodes in JavaScript.
4
-
5
- [![npm](https://img.shields.io/npm/v/@sythos/js_barcode_universal.svg)](https://www.npmjs.com/package/@sythos/js_barcode_universal)
6
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
- [![Runtime dependencies: 0](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen.svg)](package.json)
8
-
9
- 100% original code, zero runtime dependencies, MIT. It runs unmodified in Node, in browsers
10
- (including Safari on iOS) and in web workers. The core requires no canvas, no filesystem and no
11
- DOM — images go in and come out as plain `{ data, width, height }` RGBA objects, which is exactly
12
- what an `ImageData` is.
13
-
14
- **The code is complete and entirely human-readable.** The full source ships. There is no
15
- WebAssembly, no native addon, no compiled artefact, no binary blob and no minified file anywhere
16
- in this repository every tracked file is text you can open and read.
17
-
18
- That includes the prebuilt bundles. They are *generated*, concatenated and wrapped from `src/` by
19
- the project's own bundler, but nothing is stripped in the process:
20
- [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) runs to roughly 7,900 lines, about a third
21
- of them comments, averaging a little over 30 characters a line. Open it anywhere and you are
22
- reading the same annotated code as the source, in the same order a convenience, not a black box.
23
-
24
- Don't take that on trust either; it takes one command:
25
-
26
- ```sh
27
- awk '{ n += length($0) } END { print "lines:", NR, " avg length:", int(n/NR) }' bundle/sythos-barcode.js
28
- ```
29
-
30
- A minified bundle gives you a handful of lines averaging thousands of characters. This one does
31
- not, and that is the whole point.
32
-
33
- This is deliberate. A barcode library decides what a scanner believes a label says, so it belongs
34
- in the category of code you can audit rather than have to trust. Every constant table, every
35
- check digit and every error-correction step is here in full, with the reasoning next to it.
36
-
37
- ```js
38
- import { encode, decode, toSVG, toImageData } from './src/index.js';
39
-
40
- const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
41
- const svg = toSVG(matrix, { scale: 8 });
42
-
43
- const found = decode(toImageData(matrix, { scale: 4 }), { formats: ['qr'] });
44
- console.log(found[0].text); // 'https://example.com'
45
- ```
46
-
47
- ---
48
-
49
- ## Quick start
50
-
51
- There are four ways in, and none of them needs a build step.
52
-
53
- ### 1. npm
54
-
55
- ```sh
56
- npm install @sythos/js_barcode_universal
57
- ```
58
-
59
- `yarn add @sythos/js_barcode_universal` and `pnpm add @sythos/js_barcode_universal` do the same thing. Nothing is installed
60
- alongside it — there are no runtime dependencies, no postinstall script and no native build. The
61
- package is plain ESM (`"type": "module"`) and asks for Node 18 or newer.
62
-
63
- ```js
64
- import { encode, decode, toSVG, toImageData } from '@sythos/js_barcode_universal';
65
-
66
- const code = encode('SYT-2026-0042', { format: 'code128' });
67
-
68
- const svg = toSVG(code, { scale: 2, margin: 10, barHeight: 60 });
69
- // '<svg xmlns="http://www.w3.org/2000/svg" width="374" height="100" …'
70
-
71
- const found = decode(toImageData(code, { scale: 4, margin: 10 }), { formats: ['code128'] });
72
- console.log(found[0].text); // 'SYT-2026-0042'
73
- ```
74
-
75
- **Subpath exports** hand you one layer instead of the whole surface, which is what lets a
76
- tree-shaking bundler drop everything you did not ask for. Importing only the QR writer and only
77
- the SVG renderer never pulls in the 1D formats, the PNG encoder or the read pipeline:
78
-
79
- ```js
80
- import { encodeQR } from '@sythos/js_barcode_universal/qr';
81
- import { toSVG } from '@sythos/js_barcode_universal/render/svg';
82
-
83
- const svg = toSVG(encodeQR('https://example.com', { ecc: 'M' }), { scale: 8 });
84
- // a 25×25 module symbol — 264×264 px at scale 8 with the default 4-module quiet zone
85
- ```
86
-
87
- | Subpath | What it exports |
88
- |---|---|
89
- | `@sythos/js_barcode_universal` | The whole surface: `encode`, `decode`, every renderer, every error type |
90
- | `@sythos/js_barcode_universal/core` | `BitMatrix`, `GaloisField`, Reed–Solomon, the error classes |
91
- | `@sythos/js_barcode_universal/image` | `LuminanceSource`, the binarizers, grid sampling, `PerspectiveTransform` |
92
- | `@sythos/js_barcode_universal/oned` | The per-format 1D writers (`encodeEAN13`, `encodeCode128`, …) and `decodeOneD` |
93
- | `@sythos/js_barcode_universal/qr` | `encodeQR`, `decodeQR`, `detectQR`, `detectAndDecodeQR` |
94
- | `@sythos/js_barcode_universal/datamatrix` | `encodeDataMatrix`, `decodeDataMatrix`, `detectDataMatrix`, `detectAndDecodeDataMatrix` |
95
- | `@sythos/js_barcode_universal/render` | Every renderer plus `isWebGL2Available` / `isWebGPUAvailable` |
96
- | `@sythos/js_barcode_universal/render/svg` | `toSVG`, `toSVGDataURI` |
97
- | `@sythos/js_barcode_universal/render/png` | `toPNG`, `toPNGDataURI` |
98
- | `@sythos/js_barcode_universal/render/image-data` | `toImageData`, `toCanvas` |
99
- | `@sythos/js_barcode_universal/bundle` | The prebuilt ESM bundle, as one file |
100
- | `@sythos/js_barcode_universal/bundle/iife` | The prebuilt IIFE bundle, for a `<script>` tag |
101
-
102
- The `unpkg` and `jsdelivr` fields point at the IIFE bundle, so a CDN needs no install at all:
103
-
104
- ```html
105
- <script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
106
- <script src="https://unpkg.com/@sythos/js_barcode_universal@1.1.0"></script>
107
- <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.1.0"></script>
108
- ```
109
-
110
- Pin the version for anything you ship; the unpinned form resolves to `latest` and will move under
111
- you.
112
-
113
- Both CDNs serve the same file the repository ships in
114
- [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) byte for byte, since that is exactly what
115
- npm publishes.
116
-
117
- ### 2. A `<script>` tag
118
-
119
- [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) is a self-contained IIFE that exposes a
120
- single global, `SythosBarcode`. It works straight from `file://` — open an HTML file off your
121
- disk and it runs.
122
-
123
- ```html
124
- <script src="bundle/sythos-barcode.js"></script>
125
- <script>
126
- var encode = SythosBarcode.encode;
127
- var toSVGDataURI = SythosBarcode.toSVGDataURI;
128
-
129
- var img = new Image();
130
- img.src = toSVGDataURI(encode('https://example.com', { format: 'qr' }), { scale: 8 });
131
- document.body.appendChild(img);
132
- </script>
133
- ```
134
-
135
- ### 3. ESM bundle
136
-
137
- [`bundle/sythos-barcode.esm.js`](bundle/sythos-barcode.esm.js) is the same code as a single ES
138
- module, for `<script type="module">`, a bundler, or Node.
139
-
140
- ```js
141
- import { encode, toSVG, toPNG } from './bundle/sythos-barcode.esm.js';
142
-
143
- const ean = encode('5901234123457', { format: 'ean13' });
144
-
145
- const svg = toSVG(ean, { scale: 3, margin: 10, barHeight: 80 });
146
- // '<svg xmlns="http://www.w3.org/2000/svg" width="345" height="141" …'
147
-
148
- toPNG(ean, { scale: 3, barHeight: 80 }).then((bytes) => {
149
- // Uint8Array a 1-bit palette PNG
150
- });
151
- ```
152
-
153
- ### 4. The source directly
154
-
155
- [`src/index.js`](src/index.js) is plain ESM with JSDoc types and no build step of its own. Import
156
- it and let your bundler tree-shake; the package is marked side-effect free.
157
-
158
- ```js
159
- import { encode, decode, toImageData, listFormats } from './src/index.js';
160
-
161
- const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
162
- const image = toImageData(matrix, { scale: 4, margin: 4 });
163
-
164
- const found = decode(image, { formats: ['qr'] });
165
- // [ { text: 'https://example.com', format: 'qr', version: 2, ecc: 'M', … } ]
166
- ```
167
-
168
- Decoding takes anything `ImageData`-shaped, so a canvas, an `OffscreenCanvas`,
169
- `createImageBitmap`, or an image library's raw buffer all satisfy it without an adapter:
170
-
171
- ```js
172
- const ctx = canvas.getContext('2d');
173
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
174
-
175
- for (const hit of decode(ctx.getImageData(0, 0, canvas.width, canvas.height))) {
176
- console.log(hit.format, hit.text);
177
- }
178
- ```
179
-
180
- `decode` returns an array, empty when nothing is found. A frame with no barcode is an ordinary
181
- outcome for a camera loop, not an error, so the common case needs no `try`/`catch`. Use
182
- `decodeStrict` when absence really is a failure.
183
-
184
- ---
185
-
186
- ## Supported formats
187
-
188
- Generated from `listFormats()`, which reports writing and reading as separate capabilities.
189
- Writing a symbology is a table lookup; reading one needs a detector that finds it in a
190
- photograph. The two lists legitimately differ, and saying so here is better than failing at call
191
- time.
192
-
193
- | Format | `id` | Kind | Write | Read |
194
- |---|---|:---:|:---:|:---:|
195
- | EAN-13 | `ean13` | 1D | ✅ | ✅ |
196
- | EAN-8 | `ean8` | 1D | ✅ | ✅ |
197
- | UPC-A | `upca` | 1D | ✅ | ✅ |
198
- | UPC-E | `upce` | 1D | ✅ | ✅ |
199
- | ISBN (Bookland) | `isbn` | 1D | ✅ | ✅ [^1] |
200
- | Code 128 | `code128` | 1D | | ✅ |
201
- | GS1-128 | `gs1128` | 1D | | [^1] |
202
- | Code 39 | `code39` | 1D | | |
203
- | Code 93 | `code93` | 1D | ✅ | ✅ |
204
- | ITF (Interleaved 2 of 5) | `itf` | 1D | ✅ | ✅ |
205
- | ITF-14 | `itf14` | 1D | | [^1] |
206
- | Codabar | `codabar` | 1D | ✅ | ✅ |
207
- | Code 11 | `code11` | 1D | ✅ | |
208
- | MSI Plessey | `msi` | 1D | ✅ | |
209
- | Pharmacode | `pharmacode` | 1D | ✅ | |
1
+ # Sythos Barcode Suite
2
+
3
+ Read and write barcodes in JavaScript.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@sythos/js_barcode_universal.svg)](https://www.npmjs.com/package/@sythos/js_barcode_universal)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+ [![Runtime dependencies: 0](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen.svg)](package.json)
8
+ [![ESM](https://img.shields.io/badge/ESM-supported-3178C6.svg?logo=javascript&logoColor=white)](https://nodejs.org/api/esm.html)
9
+ [![Node](https://img.shields.io/node/v/%40sythos%2Fjs_barcode_universal.svg)](https://www.npmjs.com/package/@sythos/js_barcode_universal)
10
+
11
+ [![npm downloads](https://img.shields.io/npm/dm/%40sythos%2Fjs_barcode_universal.svg?label=npm%20downloads)](https://www.npmjs.com/package/@sythos/js_barcode_universal)
12
+ [![GitHub last commit](https://img.shields.io/github/last-commit/Sythos/JS_Barcode_Universal.svg)](https://github.com/Sythos/JS_Barcode_Universal/commits/main/)
13
+ [![GitHub issues](https://img.shields.io/github/issues/Sythos/JS_Barcode_Universal.svg)](https://github.com/Sythos/JS_Barcode_Universal/issues)
14
+
15
+ Original Sythos implementation, zero runtime dependencies, MIT. It runs unmodified in Node, in browsers
16
+ (including Safari on iOS) and in web workers. The core requires no canvas, no filesystem and no
17
+ DOM — images go in and come out as plain `{ data, width, height }` RGBA objects, which is exactly
18
+ what an `ImageData` is.
19
+
20
+ **The code is complete and entirely human-readable.** The full source ships. There is no
21
+ WebAssembly, no native addon, no compiled artefact, no binary blob and no minified file anywhere
22
+ in this repository every tracked file is text you can open and read.
23
+
24
+ That includes the prebuilt bundles. They are *generated*, concatenated and wrapped from `src/` by
25
+ the project's own bundler, but nothing is stripped in the process:
26
+ [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) runs to roughly 7,900 lines, about a third
27
+ of them comments, averaging a little over 30 characters a line. Open it anywhere and you are
28
+ reading the same annotated code as the source, in the same order — a convenience, not a black box.
29
+
30
+ Don't take that on trust either; it takes one command:
31
+
32
+ ```sh
33
+ awk '{ n += length($0) } END { print "lines:", NR, " avg length:", int(n/NR) }' bundle/sythos-barcode.js
34
+ ```
35
+
36
+ A minified bundle gives you a handful of lines averaging thousands of characters. This one does
37
+ not, and that is the whole point.
38
+
39
+ This is deliberate. A barcode library decides what a scanner believes a label says, so it belongs
40
+ in the category of code you can audit rather than have to trust. Every constant table, every
41
+ check digit and every error-correction step is here in full, with the reasoning next to it.
42
+
43
+ ```js
44
+ import { encode, decode, toSVG, toImageData } from './src/index.js';
45
+
46
+ const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
47
+ const svg = toSVG(matrix, { scale: 8 });
48
+
49
+ const found = decode(toImageData(matrix, { scale: 4 }), { formats: ['qr'] });
50
+ console.log(found[0].text); // 'https://example.com'
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ There are four ways in, and none of them needs a build step.
58
+
59
+ ### 1. npm
60
+
61
+ ```sh
62
+ npm install @sythos/js_barcode_universal
63
+ ```
64
+
65
+ `yarn add @sythos/js_barcode_universal` and `pnpm add @sythos/js_barcode_universal` do the same thing. Nothing is installed
66
+ alongside it there are no runtime dependencies, no postinstall script and no native build. The
67
+ package is plain ESM (`"type": "module"`) and asks for Node 18 or newer.
68
+
69
+ ```js
70
+ import { encode, decode, toSVG, toImageData } from '@sythos/js_barcode_universal';
71
+
72
+ const code = encode('SYT-2026-0042', { format: 'code128' });
73
+
74
+ const svg = toSVG(code, { scale: 2, margin: 10, barHeight: 60 });
75
+ // '<svg xmlns="http://www.w3.org/2000/svg" width="374" height="100" …'
76
+
77
+ const found = decode(toImageData(code, { scale: 4, margin: 10 }), { formats: ['code128'] });
78
+ console.log(found[0].text); // 'SYT-2026-0042'
79
+ ```
80
+
81
+ **Subpath exports** hand you one layer instead of the whole surface, which is what lets a
82
+ tree-shaking bundler drop everything you did not ask for. Importing only the QR writer and only
83
+ the SVG renderer never pulls in the 1D formats, the PNG encoder or the read pipeline:
84
+
85
+ ```js
86
+ import { encodeQR } from '@sythos/js_barcode_universal/qr';
87
+ import { toSVG } from '@sythos/js_barcode_universal/render/svg';
88
+
89
+ const svg = toSVG(encodeQR('https://example.com', { ecc: 'M' }), { scale: 8 });
90
+ // a 25×25 module symbol 264×264 px at scale 8 with the default 4-module quiet zone
91
+ ```
92
+
93
+ | Subpath | What it exports |
94
+ |---|---|
95
+ | `@sythos/js_barcode_universal` | The whole surface: `encode`, `decode`, every renderer, every error type |
96
+ | `@sythos/js_barcode_universal/core` | `BitMatrix`, `GaloisField`, Reed–Solomon, the error classes |
97
+ | `@sythos/js_barcode_universal/image` | `LuminanceSource`, the binarizers, grid sampling, `PerspectiveTransform` |
98
+ | `@sythos/js_barcode_universal/oned` | The per-format 1D writers (`encodeEAN13`, `encodeCode128`, …) and `decodeOneD` |
99
+ | `@sythos/js_barcode_universal/qr` | `encodeQR`, `decodeQR`, `detectQR`, `detectAndDecodeQR` |
100
+ | `@sythos/js_barcode_universal/datamatrix` | `encodeDataMatrix`, `decodeDataMatrix`, `detectDataMatrix`, `detectAndDecodeDataMatrix` |
101
+ | `@sythos/js_barcode_universal/aztec` | `encodeAztec`, `decodeAztec`, `detectAztec`, `detectAndDecodeAztec` |
102
+ | `@sythos/js_barcode_universal/pdf417` | `encodePDF417`, `decodePDF417`, `detectPDF417`, `detectAndDecodePDF417` |
103
+ | `@sythos/js_barcode_universal/micropdf417` | `encodeMicroPDF417`, `decodeMicroPDF417`, `detectMicroPDF417`, `detectAndDecodeMicroPDF417` |
104
+ | `@sythos/js_barcode_universal/microqr` | `encodeMicroQR`, `decodeMicroQR`, `detectMicroQR`, `detectAndDecodeMicroQR` |
105
+ | `@sythos/js_barcode_universal/rmqr` | `encodeRMQR`, `decodeRMQR`, `detectRMQR`, `detectAndDecodeRMQR` |
106
+ | `@sythos/js_barcode_universal/frameqr` | `encodeFrameQR`, `decodeFrameQR`, `detectFrameQR`, `detectAndDecodeFrameQR` |
107
+ | `@sythos/js_barcode_universal/render` | Every renderer plus `isWebGL2Available` / `isWebGPUAvailable` |
108
+ | `@sythos/js_barcode_universal/render/svg` | `toSVG`, `toSVGDataURI` |
109
+ | `@sythos/js_barcode_universal/render/png` | `toPNG`, `toPNGDataURI` |
110
+ | `@sythos/js_barcode_universal/render/image-data` | `toImageData`, `toCanvas` |
111
+ | `@sythos/js_barcode_universal/bundle` | The prebuilt ESM bundle, as one file |
112
+ | `@sythos/js_barcode_universal/bundle/iife` | The prebuilt IIFE bundle, for a `<script>` tag |
113
+
114
+ The `unpkg` and `jsdelivr` fields point at the IIFE bundle, so a CDN needs no install at all:
115
+
116
+ ```html
117
+ <script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
118
+ <script src="https://unpkg.com/@sythos/js_barcode_universal@1.3.1"></script>
119
+ <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.3.1"></script>
120
+ ```
121
+
122
+ Pin the version for anything you ship; the unpinned form resolves to `latest` and will move under
123
+ you.
124
+
125
+ Both CDNs serve the same file the repository ships in
126
+ [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) byte for byte, since that is exactly what
127
+ npm publishes.
128
+
129
+ ### 2. A `<script>` tag
130
+
131
+ [`bundle/sythos-barcode.js`](bundle/sythos-barcode.js) is a self-contained IIFE that exposes a
132
+ single global, `SythosBarcode`. It works straight from `file://` — open an HTML file off your
133
+ disk and it runs.
134
+
135
+ ```html
136
+ <script src="bundle/sythos-barcode.js"></script>
137
+ <script>
138
+ var encode = SythosBarcode.encode;
139
+ var toSVGDataURI = SythosBarcode.toSVGDataURI;
140
+
141
+ var img = new Image();
142
+ img.src = toSVGDataURI(encode('https://example.com', { format: 'qr' }), { scale: 8 });
143
+ document.body.appendChild(img);
144
+ </script>
145
+ ```
146
+
147
+ ### 3. ESM bundle
148
+
149
+ [`bundle/sythos-barcode.esm.js`](bundle/sythos-barcode.esm.js) is the same code as a single ES
150
+ module, for `<script type="module">`, a bundler, or Node.
151
+
152
+ ```js
153
+ import { encode, toSVG, toPNG } from './bundle/sythos-barcode.esm.js';
154
+
155
+ const ean = encode('5901234123457', { format: 'ean13' });
156
+
157
+ const svg = toSVG(ean, { scale: 3, margin: 10, barHeight: 80 });
158
+ // '<svg xmlns="http://www.w3.org/2000/svg" width="345" height="141" …'
159
+
160
+ toPNG(ean, { scale: 3, barHeight: 80 }).then((bytes) => {
161
+ // Uint8Array a 1-bit palette PNG
162
+ });
163
+ ```
164
+
165
+ ### 4. The source directly
166
+
167
+ [`src/index.js`](src/index.js) is plain ESM with JSDoc types and no build step of its own. Import
168
+ it and let your bundler tree-shake; the package is marked side-effect free.
169
+
170
+ ```js
171
+ import { encode, decode, toImageData, listFormats } from './src/index.js';
172
+
173
+ const matrix = encode('https://example.com', { format: 'qr', ecc: 'M' });
174
+ const image = toImageData(matrix, { scale: 4, margin: 4 });
175
+
176
+ const found = decode(image, { formats: ['qr'] });
177
+ // [ { text: 'https://example.com', format: 'qr', version: 2, ecc: 'M', … } ]
178
+ ```
179
+
180
+ Decoding takes anything `ImageData`-shaped, so a canvas, an `OffscreenCanvas`,
181
+ `createImageBitmap`, or an image library's raw buffer all satisfy it without an adapter:
182
+
183
+ ```js
184
+ const ctx = canvas.getContext('2d');
185
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
186
+
187
+ for (const hit of decode(ctx.getImageData(0, 0, canvas.width, canvas.height))) {
188
+ console.log(hit.format, hit.text);
189
+ }
190
+ ```
191
+
192
+ `decode` returns an array, empty when nothing is found. A frame with no barcode is an ordinary
193
+ outcome for a camera loop, not an error, so the common case needs no `try`/`catch`. Use
194
+ `decodeStrict` when absence really is a failure.
195
+
196
+ ---
197
+
198
+ ## Supported formats
199
+
200
+ Generated from `listFormats()`, which reports writing and reading as separate capabilities.
201
+ Writing a symbology is a table lookup; reading one needs a detector that finds it in a
202
+ photograph. The two lists legitimately differ, and saying so here is better than failing at call
203
+ time.
204
+
205
+ | Format | `id` | Kind | Write | Read |
206
+ |---|---|:---:|:---:|:---:|
207
+ | EAN-13 | `ean13` | 1D | ✅ | |
208
+ | EAN-8 | `ean8` | 1D | ✅ | |
209
+ | UPC-A | `upca` | 1D | ✅ | |
210
+ | UPC-E | `upce` | 1D | ✅ | ✅ |
211
+ | ISBN (Bookland) | `isbn` | 1D | ✅ | ✅ [^1] |
212
+ | Code 128 | `code128` | 1D | ✅ | ✅ |
213
+ | GS1-128 | `gs1128` | 1D | ✅ | ✅ [^1] |
214
+ | Code 39 | `code39` | 1D | ✅ | ✅ |
215
+ | Code 93 | `code93` | 1D | ✅ | ✅ |
216
+ | ITF (Interleaved 2 of 5) | `itf` | 1D | ✅ | ✅ |
217
+ | ITF-14 | `itf14` | 1D | ✅ | ✅ [^1] |
218
+ | Codabar | `codabar` | 1D | ✅ | ✅ |
219
+ | Code 11 | `code11` | 1D | ✅ | — |
220
+ | MSI Plessey | `msi` | 1D | ✅ | — |
221
+ | Pharmacode | `pharmacode` | 1D | ✅ | — |
210
222
  | QR Code | `qr` | 2D | ✅ | ✅ |
211
223
  | Data Matrix ECC 200 | `datamatrix` | 2D | ✅ | ✅ |
212
224
  | Aztec Code | `aztec` | 2D | ✅ | ✅ |
225
+ | PDF417 | `pdf417` | 2D | ✅ | ✅ |
226
+ | MicroPDF417 | `micropdf417` | 2D | ✅ | ✅ |
227
+ | Micro QR Code | `microqr` | 2D | ✅ | ✅ |
228
+ | rMQR Code | `rmqr` | 2D | ✅ | ✅ |
229
+ | Sythos Canvas QR profile (non-certified; not DENSO FrameQR) | `frameqr` | 2D | ✅ | ✅ |
230
+
231
+ Twenty-three formats, all writable, twenty readable. **Code 11, MSI Plessey and Pharmacode remain
232
+ write-only in the generic image pipeline.** PDF417 exposes direct matrix decoding, automatic
233
+ camera localization and an assisted quadrilateral sampler through its subpath. Its detector is
234
+ validated on degraded synthetic photographs and real Pixel 10/Chrome and iPhone 17/Safari camera
235
+ tests; external black-box vectors from ZXing 3.5.3 and bwip-js also pass in both directions.
236
+ Text and Numeric vectors are covered bidirectionally; binary byte-for-byte interop remains
237
+ explicitly unclaimed until a dedicated external byte corpus is added.
238
+
239
+ [^1]: `gs1128`, `itf14` and `isbn` are sub-variants that share a decoder with their base format, so
240
+ they decode under that base id: a GS1-128 comes back as `code128`, an ITF-14 as `itf`, and an ISBN
241
+ as `ean13`. The payload is intact either way — a GS1-128 *is* a Code 128 with a leading FNC1, an
242
+ ITF-14 *is* an ITF fixed at fourteen digits, and an ISBN barcode *is* an EAN-13 with a 978/979
243
+ prefix. Match on `result.format === 'code128'` rather than `'gs1128'`, or a
244
+ condition on the sub-variant id will silently never fire.
213
245
 
214
- Eighteen formats, all writable, fifteen readable. **Code 11, MSI Plessey and Pharmacode are
215
- write-only** — they encode correctly, but there is no reader for them, and `decode` will never
216
- return one.
217
-
218
- [^1]: `gs1128`, `itf14` and `isbn` are sub-variants that share a decoder with their base format, so
219
- they decode under that base id: a GS1-128 comes back as `code128`, an ITF-14 as `itf`, and an ISBN
220
- as `ean13`. The payload is intact either way — a GS1-128 *is* a Code 128 with a leading FNC1, an
221
- ITF-14 *is* an ITF fixed at fourteen digits, and an ISBN barcode *is* an EAN-13 with a 978/979
222
- prefix. Match on `result.format === 'code128'` rather than `'gs1128'`, or a
223
- condition on the sub-variant id will silently never fire.
224
-
225
- ### Data Matrix ECC 200
226
-
227
- `datamatrix` writes and reads the 30 classic ECC 200 square and rectangular symbol sizes. The
228
- encoder supports ASCII compaction (including numeric pairs), Base256 binary payloads, automatic
229
- or forced square/rectangular shape, Reed–Solomon error correction and GS1 FNC1 in the first
230
- position. DMRE is not included.
231
-
232
- ```js
233
- import { encodeDataMatrix, decodeDataMatrix } from '@sythos/js_barcode_universal/datamatrix';
234
-
235
- const symbol = encodeDataMatrix('0101234567890128', { gs1: true, shape: 'square' });
236
- const result = decodeDataMatrix(symbol);
237
- console.log(result.text, result.gs1); // 0101234567890128 true
238
- ```
239
-
240
- Binary content is accepted as a `Uint8Array` with `encoding: 'base256'`. The current high-level
241
- decoder handles ASCII and Base256 codewords; C40, Text, X12 and EDIFACT input symbols are not yet
242
- decoded. The current detector accepts axis-aligned square or rectangular symbols; the normal
243
- `decode(image, { formats: ['datamatrix'] })` pipeline also retries quarter-turn rotations.
246
+ ### Data Matrix ECC 200
247
+
248
+ `datamatrix` writes and reads the 30 classic ECC 200 square and rectangular symbol sizes. The
249
+ encoder supports ASCII compaction (including numeric pairs), Base256 binary payloads, automatic
250
+ or forced square/rectangular shape, Reed–Solomon error correction and GS1 FNC1 in the first
251
+ position. DMRE is not included.
252
+
253
+ ```js
254
+ import { encodeDataMatrix, decodeDataMatrix } from '@sythos/js_barcode_universal/datamatrix';
255
+
256
+ const symbol = encodeDataMatrix('0101234567890128', { gs1: true, shape: 'square' });
257
+ const result = decodeDataMatrix(symbol);
258
+ console.log(result.text, result.gs1); // 0101234567890128 true
259
+ ```
260
+
261
+ Binary content is accepted as a `Uint8Array` with `encoding: 'base256'`. The current high-level
262
+ decoder handles ASCII and Base256 codewords; C40, Text, X12 and EDIFACT input symbols are not yet
263
+ decoded. The current detector accepts axis-aligned square or rectangular symbols; the normal
264
+ `decode(image, { formats: ['datamatrix'] })` pipeline also retries quarter-turn rotations.
244
265
  Arbitrary-angle and perspective-skewed Data Matrix photographs are not yet guaranteed.
245
266
 
246
267
  ### Aztec Code
@@ -254,238 +275,353 @@ ECI is not yet a configurable public option.
254
275
  ```js
255
276
  import { encodeAztec, decodeAztec } from '@sythos/js_barcode_universal/aztec';
256
277
 
257
- const symbol = encodeAztec('Ciao, mondo 👋', { eccPercent: 23 });
278
+ const symbol = encodeAztec('Greetings My Lord Sythos 👋', { eccPercent: 23 });
258
279
  const result = decodeAztec(symbol);
259
- console.log(result.text); // Ciao, mondo 👋
280
+ console.log(result.text); // Greetings My Lord Sythos 👋
260
281
  ```
261
282
 
262
283
  The image detector handles rotation, inverted polarity and quadrilateral sampling around the
263
284
  central bull’s-eye. Severe photographic perspective remains an interoperability and robustness
264
285
  gate rather than a guaranteed capability.
265
286
 
287
+ ### PDF417 (writer, matrix decoder and camera reader)
288
+
289
+ `pdf417` supports PDF417 Text, Byte and Numeric compaction, ECI 3 (ISO-8859-1) and ECI 26
290
+ (UTF-8), ECC levels 0–8, row-height inference and Reed–Solomon erasure correction. The direct
291
+ matrix decoder is available from `@sythos/js_barcode_universal/pdf417`.
292
+
293
+ The image helper handles clean module-aligned raster symbols, integer scale, right-angle
294
+ rotations, automatic perspective estimation, mild blur/noise and an application-supplied
295
+ quadrilateral. Results expose `bytes` and ordered `segments` for byte-preserving payloads. Real
296
+ device validation covers Pixel 10/Chrome and iPhone 17/Safari with printed symbols and continuous
297
+ camera capture. Extreme glare, severe occlusion, curved media and multi-symbol scenes remain
298
+ outside the validated robustness envelope.
299
+
300
+ ```js
301
+ import { encodePDF417, decodePDF417 } from '@sythos/js_barcode_universal/pdf417';
302
+
303
+ const symbol = encodePDF417('AAMVA SAMPLE', { eccLevel: 3 });
304
+ console.log(decodePDF417(symbol).text);
305
+ ```
306
+
307
+ ### MicroPDF417
308
+
309
+ `micropdf417` writes and reads the 34 fixed MicroPDF417 variants. It supports Text, Byte and
310
+ Numeric compaction, plus Byte-compaction ECI 3 (ISO-8859-1) and 26 (UTF-8). `columns`,
311
+ `rowHeight` and `aspectRatio` let callers constrain automatic variant selection.
312
+
313
+ ```js
314
+ import { encodeMicroPDF417, decodeMicroPDF417 } from '@sythos/js_barcode_universal/micropdf417';
315
+
316
+ const symbol = encodeMicroPDF417('MICRO PDF417', { compaction: 'text' });
317
+ console.log(decodeMicroPDF417(symbol).text);
318
+ ```
319
+
320
+ The detector accepts clean, integer-scaled raster symbols and quarter-turn rotations. Arbitrary
321
+ perspective, severe photographic degradation and multi-symbol scenes are not yet claimed as
322
+ robust capabilities.
323
+
324
+ ### Micro QR Code
325
+
326
+ `microqr` implements the M1–M4 family with Numeric, Alphanumeric, ISO-8859-1 Byte and Kanji
327
+ payloads, BCH format protection, the four Micro QR masks and Reed–Solomon correction. M1 is
328
+ detection-only. ECI, FNC1/GS1 and Structured Append are intentionally outside the current API.
329
+ The detector accepts clean scaled rasters, quarter-turns, inverted polarity and mild projective
330
+ sampling, and rejects normal QR Model 2 symbols.
331
+
332
+ ```js
333
+ import { encodeMicroQR, decodeMicroQR } from '@sythos/js_barcode_universal/microqr';
334
+
335
+ const symbol = encodeMicroQR('12345', { version: 'M2', ecc: 'L' });
336
+ console.log(decodeMicroQR(symbol).text);
337
+ ```
338
+
339
+ ### rMQR Code
340
+
341
+ `rmqr` implements all 32 standard rectangular geometries, M/H ECC, Numeric, Alphanumeric, Byte,
342
+ Kanji and ECI payloads. The detector accepts clean integer-scaled rasters, quiet zones and
343
+ quarter-turns; arbitrary photographic perspective and multi-symbol scenes are not claimed.
344
+
345
+ ```js
346
+ import { encodeRMQR, decodeRMQR } from '@sythos/js_barcode_universal/rmqr';
347
+
348
+ const symbol = encodeRMQR('rMQR SAMPLE', { ecc: 'M' });
349
+ console.log(decodeRMQR(symbol).text);
350
+ ```
351
+
352
+ ### Sythos Canvas QR profile
353
+
354
+ `frameqr` is an explicitly scoped, non-certified Sythos profile: it reserves a bounded square,
355
+ circle or diamond artwork canvas inside an ECC-H QR Model 2 symbol. It is **not** a native DENSO
356
+ FrameQR encoder or decoder, and the package makes no DENSO interoperability claim. The profile
357
+ can be read from clean rendered rasters and is exposed through the normal `encode`/`decode` API
358
+ and the `frameqr` subpath.
359
+
360
+ ```js
361
+ import { encodeFrameQR, decodeFrameQR } from '@sythos/js_barcode_universal/frameqr';
362
+
363
+ const symbol = encodeFrameQR('https://www.sythos.net/', {
364
+ canvas: { shape: 'square', size: 5 },
365
+ });
366
+ console.log(decodeFrameQR(symbol).text);
367
+ ```
368
+
369
+ The canonical `examples/create.html` preview loads `https://www.sythos.net/favicon.ico` and
370
+ falls back to `https://www.sythos.net/apple-touch-icon.png`. The image is never copied into the
371
+ repository; if browser CORS prevents safe compositing, the page keeps a preview overlay and
372
+ exports the QR symbol without embedding the remote artwork.
373
+
266
374
  ### Not implemented
267
375
 
268
- **PDF417, GS1 DataBar and MaxiCode are not implemented** — neither writing
269
- nor reading. Data Matrix ECC 200 is implemented for its classic square and rectangular symbols;
270
- DMRE remains outside the current scope. Some scaffolding for the remaining formats exists in the core (the Galois field code already handles
271
- the prime field PDF417 needs), but none of those remaining symbologies is usable today. See [`PLAN.md`](PLAN.md)
272
- for where they sit.
273
-
274
- ---
275
-
276
- ## Live examples
277
-
278
- Two self-contained pages, each loading the IIFE bundle with a plain `<script>` tag. **Both open
279
- directly from disk** — double-click the file, no server and no build. This `examples/` directory is
280
- the single canonical source; the development workspace references these files instead of keeping
281
- a second copy.
282
-
283
- ### [`examples/create.html`](examples/create.html)
284
-
285
- Pick any writable format, type a payload, and watch the symbol redraw as you type; download it as
286
- PNG or SVG. For QR it adds a **content-type builder** that assembles the payload for you across
287
- URL, email, phone, SMS, Wi-Fi network, contact card (both vCard and MeCard), geo location and
288
- calendar event — with correct escaping for each — and shows you the exact string it produced, so
289
- you can see what a Wi-Fi or vCard QR actually contains. ECC level, version, scale, margin and both
290
- colours are exposed.
291
-
292
- ### [`examples/read.html`](examples/read.html)
293
-
294
- Decode from an image: drop one onto the page, or click to choose a file. It then offers a live
295
- camera loop that decodes continuously from the video stream.
296
-
297
- > The file and drag-drop path works anywhere, `file://` included. **The camera needs http(s)**,
298
- > because `getUserMedia` requires a secure context and refuses to run from `file://`. Serve the
299
- > folder over localhost for that half; the page detects the situation and says so rather than
300
- > failing silently.
301
-
302
- ---
303
-
304
- ## API summary
305
-
306
- Two functions carry the whole surface. Everything else is a renderer or a format-specific escape
307
- hatch.
308
-
309
- ### Encoding and decoding
310
-
311
- ```js
312
- encode(text, options?) → BitMatrix
313
- ```
314
-
315
- `options`: `format` (default `'qr'`), `ecc` (`'L'|'M'|'Q'|'H'`), `version` (QR 1–40, auto if
376
+ **GS1 DataBar and MaxiCode are not implemented** — neither writing nor reading. Data Matrix ECC
377
+ 200 is implemented for its classic square and rectangular symbols;
378
+ DMRE remains outside the current scope. See [`PLAN.md`](PLAN.md) for the remaining symbologies.
379
+
380
+ ---
381
+
382
+ ## Live examples
383
+
384
+ Two self-contained pages, each loading the IIFE bundle with a plain `<script>` tag. **Both open
385
+ directly from disk** — double-click the file, no server and no build. This `examples/` directory is
386
+ the single canonical source; the development workspace references these files instead of keeping
387
+ a second copy.
388
+
389
+ ### [`examples/create.html`](examples/create.html)
390
+
391
+ Pick any writable format, type a payload, and watch the symbol redraw as you type; download it as
392
+ PNG or SVG. For QR it adds a **content-type builder** that assembles the payload for you across
393
+ URL, email, phone, SMS, Wi-Fi network, contact card (both vCard and MeCard), geo location and
394
+ calendar event with correct escaping for each and shows you the exact string it produced, so
395
+ you can see what a Wi-Fi or vCard QR actually contains. ECC level, version, scale, margin and both
396
+ colours are exposed.
397
+
398
+ ### [`examples/read.html`](examples/read.html)
399
+
400
+ Decode from an image: drop one onto the page, or click to choose a file. It then offers a live
401
+ camera loop that decodes continuously from the video stream.
402
+
403
+ > The file and drag-drop path works anywhere, `file://` included. **The camera needs http(s)**,
404
+ > because `getUserMedia` requires a secure context and refuses to run from `file://`. Serve the
405
+ > folder over localhost for that half; the page detects the situation and says so rather than
406
+ > failing silently.
407
+
408
+ ---
409
+
410
+ ## API summary
411
+
412
+ Two functions carry the whole surface. Everything else is a renderer or a format-specific escape
413
+ hatch.
414
+
415
+ ### Encoding and decoding
416
+
417
+ ```js
418
+ encode(text, options?) → BitMatrix
419
+ ```
420
+
421
+ `options`: `format` (default `'qr'`), `ecc` (`'L'|'M'|'Q'|'H'`), `version` (QR 1–40, auto if
316
422
  omitted), `checkDigit`, `fullAscii` (Code 39 extended), `gs1` (emit a leading FNC1). Data Matrix
317
423
  ECC 200 accepts `shape: 'any' | 'square' | 'rectangular'` and `encoding: 'ascii' | 'base256'`.
318
424
  Aztec accepts `layers`, `compact` and `eccPercent`; it transports UTF-8 byte payloads through
319
425
  Binary Shift, and it does not expose configurable ECI yet.
320
-
321
- ```js
322
- encode('5901234123457', { format: 'ean13' })
323
- encode('ABC-123', { format: 'code39', fullAscii: true, checkDigit: true })
324
- encode('https://example.com', { format: 'qr', ecc: 'H', version: 7 })
426
+ MicroPDF417 accepts `compaction: 'auto' | 'text' | 'byte' | 'numeric'`, ECI 3 or 26 for Byte
427
+ compaction, and optional `columns`, `rowHeight` and `aspectRatio` constraints.
428
+ Micro QR accepts `version: 'M1' | 'M2' | 'M3' | 'M4'`, its legal ECC level and mask; its
429
+ unsupported ECI, FNC1/GS1 and Structured Append features are rejected explicitly. rMQR accepts
430
+ `ecc: 'M' | 'H'`, optional geometry/version constraints and ECI for byte payloads. The Sythos
431
+ Canvas QR profile accepts `canvas: { shape: 'square' | 'circle' | 'diamond', size, width,
432
+ height, centerX, centerY, angle }`; it is non-certified and separate from DENSO FrameQR.
433
+
434
+ ```js
435
+ encode('5901234123457', { format: 'ean13' })
436
+ encode('ABC-123', { format: 'code39', fullAscii: true, checkDigit: true })
437
+ encode('https://example.com', { format: 'qr', ecc: 'H', version: 7 })
325
438
  encode('0101234567890128', { format: 'datamatrix', gs1: true })
326
- encode('Ciao, mondo 👋', { format: 'aztec', eccPercent: 23 })
327
- ```
328
-
329
- ```js
330
- decode(image, options?) → Result[]
331
- decodeStrict(image, options?) → Result // throws NotFoundError instead of returning []
332
- ```
333
-
334
- `image` is `{ data, width, height }` with RGBA bytes. `options`: `formats` (restrict the search,
335
- and go faster), `tryHarder` (retry inverted, default `true`), `binarizer`
336
- (`'global' | 'hybrid' | 'auto'`). A `Result` carries at least `text` and `format`; QR results also
337
- carry `bytes`, `version` and `ecc`.
338
-
339
- ```js
340
- listFormats() → { id, label, canWrite, canRead, kind }[]
341
- ```
342
-
343
- The table above is this function's output. Read it at runtime rather than hard-coding a format
344
- list that is how the demo pages build their dropdowns.
345
-
346
- ### Renderers
347
-
348
- ```js
349
- toSVG(matrix, options?) → string // one merged <path>, not a rect per module
350
- toSVGDataURI(matrix, options?) string // data: URI for an <img src>
351
- toImageData(matrix, options?) { data, width, height }
352
- toPNG(matrix, options?) → Promise<Uint8Array> // 1-bit palette PNG
353
- toPNGDataURI(matrix, options?) → Promise<string>
354
- toCanvas(matrix, canvas, options?) → boolean // 2D context
355
- renderToCanvasAuto(matrix, canvas, options?) → { backend: 'webgl2' | '2d' | 'none' }
356
- renderToCanvasAutoAsync(matrix, canvas, options?) → Promise<{ backend: 'webgpu' | 'webgl2' | '2d' | 'none' }>
357
- ```
358
-
359
- The two PNG functions are async because they use the platform's deflate — `node:zlib` or
360
- `CompressionStream` and fall back to stored blocks where neither exists.
361
-
362
- `renderToCanvasAuto` is synchronous and therefore cannot reach WebGPU: acquiring an adapter is
363
- asynchronous, and a synchronous function can never wait for one. Use `renderToCanvasAutoAsync`
364
- when you want WebGPU in the chain. Both fall through to the 2D context, which always exists.
365
-
366
- All renderers share the same options:
367
-
368
- | Option | Default | Meaning |
369
- |---|---|---|
370
- | `scale` | `8` | Pixels per module |
371
- | `margin` | `4` | Quiet-zone modules on every side |
372
- | `dark` | `'#000000'` | Colour of set modules |
373
- | `light` | `'#ffffff'` | Colour of clear modules; `'none'` for transparent |
374
- | `barHeight` | auto | 1D only: total bar height in pixels |
375
-
376
- Also exported: `BitMatrix`, the error types (`BarcodeError`, `EncodeError`, `NotFoundError`,
377
- `FormatError`, `ChecksumError`), the per-format writers (`encodeEAN13`, `encodeCode128`, …), the
378
- QR entry points (`encodeQR`, `decodeQR`, `detectQR`, `detectAndDecodeQR`), the image primitives
379
- (`LuminanceSource`, `binarize`, `binarizeGlobal`, `binarizeHybrid`), and the capability probes
380
- `isWebGL2Available` / `isWebGPUAvailable`.
381
-
382
- ---
383
-
384
- ## How it works
385
-
386
- **`BitMatrix` is the interchange type.** Every writer produces one, every reader consumes one,
387
- every renderer draws one. That single currency is what keeps symbologies and output targets
388
- independent of each other — adding a format touches no renderer, and adding a renderer touches no
389
- format. Storage is row-packed into a `Uint32Array`: one allocation, cache-friendly row scans, and
390
- cheap whole-row operations for the 1D readers.
391
-
392
- **A set bit is a dark module.** This matches how every specification describes its symbols;
393
- renderers invert where their medium needs it.
394
-
395
- **`encode` returns no quiet zone.** The margin is a rendering decision, not an encoding one how
396
- much white space a symbol needs depends on where it is going — so the renderers add it and the
397
- matrix stays the pure symbol. For the same reason, **linear symbols come back exactly one module
398
- tall**: height carries no information in a 1D barcode, so encoding one would be inventing data.
399
- The renderer stretches the single row to `barHeight` *before* applying the quiet zone, so the
400
- margin ends up uniform on all four sides.
401
-
402
- **The read pipeline** is a straight line, each stage a separate module:
403
-
404
- ```
405
- RGBA bytes luminance binarize detect sample error-correct decode
406
- ```
407
-
408
- Luminance conversion flattens the image to greyscale. Binarization turns that into a `BitMatrix`,
409
- either globally or with a hybrid local threshold that survives uneven lighting. Detection locates
410
- a symbol and its corners in that bit plane. Detectors that recover four perspective-aware corners
411
- (currently QR) sample the symbol back through a perspective transform. Data Matrix currently uses
412
- an axis-aligned bounding box plus quarter-turn retries. Error correction repairs what the camera
413
- lost. Only then is the payload decoded.
414
-
415
- **Reed–Solomon is generic over the finite field.** The `GaloisField` class is constructed with a
416
- field order and a primitive polynomial rather than hard-coding GF(256), which is what lets one
417
- implementation serve QR, and the prime field GF(929) that PDF417 needs. Prime fields are the
418
- subtle case: in a binary field addition and subtraction are both XOR, so a decoder that inlines
419
- `^` for field addition passes every binary field and fails only the prime one.
420
-
421
- ---
422
-
423
- ## About the GPU path
424
-
425
- The WebGL2 and WebGPU backends accelerate **drawing** a barcode, not **computing** one. That is
426
- worth stating plainly, because "GPU barcode generation" naturally suggests the latter.
427
-
428
- Encoding is sequential integer work: Reed–Solomon polynomial division, mask penalty scoring, bit
429
- placement along a zig-zag path. Each step depends on the one before it, which is precisely the
430
- shape a GPU cannot exploit. A complete QR encode takes well under a millisecond on the CPU — less
431
- time than dispatching a compute shader and reading the result back would cost. Moving it to the
432
- GPU would make it slower.
433
-
434
- So encoding stays on the CPU because that is the correct engineering answer, not because
435
- something is missing. Where the GPU genuinely earns its place is drawing large symbols, or many
436
- symbols per frame, straight into a canvas without a CPU-side pixel buffer and it would earn it
437
- again on the read side, where per-frame greyscale conversion and block statistics over a 4K camera
438
- image are both the real bottleneck and embarrassingly parallel.
439
-
440
- ---
441
-
442
- ## Browser support
443
-
444
- The syntax floor is **iOS Safari 15**. No `Array.prototype.at`, no top-level `await`, no
445
- `Object.groupBy`.
446
-
447
- `OffscreenCanvas`, WebGL2 and WebGPU are feature-detected, never assumed. The 2D canvas path
448
- always exists, so nothing is unreachable on an older device — `renderToCanvasAuto` degrades to it
449
- and reports which backend actually drew.
450
-
451
- ---
452
-
453
- ## Licence
454
-
455
- MIT © 2026 Sythos. Every source file carries the header.
456
-
457
- **The code is 100% original.** No source code and no constant table from any other barcode
458
- implementation is present, under any licence, permissive or otherwise. The symbologies are
459
- implemented from published descriptions of the formats — which are systems and facts, not works of
460
- authorship — and from constant tables generated by this project's own scripts wherever a table is
461
- derivable rather than arbitrary. There is consequently no upstream licence to carry and no
462
- co-author to credit.
463
-
464
- **Trademark is not licence.** QR Code® is a registered trademark of DENSO WAVE; Aztec Code,
465
- MaxiCode and GS1 DataBar are likewise marks of their owners. A trademark does not restrict
466
- implementing a symbology, but it does constrain branding which is why this package is named
467
- descriptively rather than after any mark.
468
-
469
- Data Matrix ECC 200 is governed by ISO/IEC 16022:2024; GS1 DataMatrix additionally uses the GS1
470
- General Specifications and a leading FNC1. Its engineering provenance, patent and trademark
471
- research notes are recorded in [`licenses/data-matrix.license`](licenses/data-matrix.license),
472
- with unresolved claims kept explicitly marked `[TO VERIFY]`.
473
-
474
- [`LICENSE`](LICENSE) carries the full MIT text plus an informational appendix inventorying the
475
- specification copyrights, patent history and trademarks that surround these symbologies. None of
476
- them encumbers this code; the appendix is an engineering inventory, not legal advice.
477
- [`NOTICE.md`](NOTICE.md) records the origin of the code and how its correctness is verified.
478
-
479
- ---
480
-
481
- ## Contributing and roadmap
482
-
483
- [`PLAN.md`](PLAN.md) is the live status document: what is shipped, what is next, and the ground
484
- rules chief among them that no code or constant table from any other barcode implementation
485
- enters this project, which is what keeps the licence clean.
486
-
487
- Issues and pull requests are welcome at
488
- [Sythos/JS_Barcode_Universal](https://github.com/Sythos/JS_Barcode_Universal). A patch that adds a
489
- symbology should implement it from the published description of the format, generate its tables
490
- where they are derivable, and come with a symbol that a scanner this project did not write has
491
- actually read — that last one is the check that matters.
439
+ encode('Greetings My Lord Sythos 👋', { format: 'aztec', eccPercent: 23 })
440
+ encode('MICRO PDF417', { format: 'micropdf417', compaction: 'text' })
441
+ encode('12345', { format: 'microqr', version: 'M2', ecc: 'L' })
442
+ encode('rMQR SAMPLE', { format: 'rmqr', ecc: 'M' })
443
+ encode('https://www.sythos.net/', {
444
+ format: 'frameqr',
445
+ canvas: { shape: 'square', size: 5 },
446
+ })
447
+ ```
448
+
449
+ ```js
450
+ decode(image, options?) Result[]
451
+ decodeStrict(image, options?) → Result // throws NotFoundError instead of returning []
452
+ ```
453
+
454
+ `image` is `{ data, width, height }` with RGBA bytes. `options`: `formats` (restrict the search,
455
+ and go faster), `tryHarder` (retry inverted, default `true`), `binarizer`
456
+ (`'global' | 'hybrid' | 'auto'`). A `Result` carries at least `text` and `format`; QR results also
457
+ carry `bytes`, `version` and `ecc`.
458
+
459
+ ```js
460
+ listFormats() → { id, label, canWrite, canRead, kind }[]
461
+ ```
462
+
463
+ The table above is this function's output. Read it at runtime rather than hard-coding a format
464
+ list that is how the demo pages build their dropdowns.
465
+
466
+ ### Renderers
467
+
468
+ ```js
469
+ toSVG(matrix, options?) → string // one merged <path>, not a rect per module
470
+ toSVGDataURI(matrix, options?) → string // data: URI for an <img src>
471
+ toImageData(matrix, options?) → { data, width, height }
472
+ toPNG(matrix, options?) Promise<Uint8Array> // 1-bit palette PNG
473
+ toPNGDataURI(matrix, options?) Promise<string>
474
+ toCanvas(matrix, canvas, options?) → boolean // 2D context
475
+ renderToCanvasAuto(matrix, canvas, options?) { backend: 'webgl2' | '2d' | 'none' }
476
+ renderToCanvasAutoAsync(matrix, canvas, options?) Promise<{ backend: 'webgpu' | 'webgl2' | '2d' | 'none' }>
477
+ ```
478
+
479
+ The two PNG functions are async because they use the platform's deflate — `node:zlib` or
480
+ `CompressionStream` — and fall back to stored blocks where neither exists.
481
+
482
+ `renderToCanvasAuto` is synchronous and therefore cannot reach WebGPU: acquiring an adapter is
483
+ asynchronous, and a synchronous function can never wait for one. Use `renderToCanvasAutoAsync`
484
+ when you want WebGPU in the chain. Both fall through to the 2D context, which always exists.
485
+
486
+ All renderers share the same options:
487
+
488
+ | Option | Default | Meaning |
489
+ |---|---|---|
490
+ | `scale` | `8` | Pixels per module |
491
+ | `margin` | `4` | Quiet-zone modules on every side |
492
+ | `dark` | `'#000000'` | Colour of set modules |
493
+ | `light` | `'#ffffff'` | Colour of clear modules; `'none'` for transparent |
494
+ | `barHeight` | auto | 1D only: total bar height in pixels |
495
+
496
+ Also exported: `BitMatrix`, the error types (`BarcodeError`, `EncodeError`, `NotFoundError`,
497
+ `FormatError`, `ChecksumError`), the per-format writers (`encodeEAN13`, `encodeCode128`, …), the
498
+ QR entry points (`encodeQR`, `decodeQR`, `detectQR`, `detectAndDecodeQR`), the image primitives
499
+ (`LuminanceSource`, `binarize`, `binarizeGlobal`, `binarizeHybrid`), and the capability probes
500
+ `isWebGL2Available` / `isWebGPUAvailable`.
501
+
502
+ ---
503
+
504
+ ## How it works
505
+
506
+ **`BitMatrix` is the interchange type.** Every writer produces one, every reader consumes one,
507
+ every renderer draws one. That single currency is what keeps symbologies and output targets
508
+ independent of each other adding a format touches no renderer, and adding a renderer touches no
509
+ format. Storage is row-packed into a `Uint32Array`: one allocation, cache-friendly row scans, and
510
+ cheap whole-row operations for the 1D readers.
511
+
512
+ **A set bit is a dark module.** This matches how every specification describes its symbols;
513
+ renderers invert where their medium needs it.
514
+
515
+ **`encode` returns no quiet zone.** The margin is a rendering decision, not an encoding one — how
516
+ much white space a symbol needs depends on where it is going — so the renderers add it and the
517
+ matrix stays the pure symbol. For the same reason, **linear symbols come back exactly one module
518
+ tall**: height carries no information in a 1D barcode, so encoding one would be inventing data.
519
+ The renderer stretches the single row to `barHeight` *before* applying the quiet zone, so the
520
+ margin ends up uniform on all four sides.
521
+
522
+ **The read pipeline** is a straight line, each stage a separate module:
523
+
524
+ ```
525
+ RGBA bytes luminance binarize detect sample error-correct → decode
526
+ ```
527
+
528
+ Luminance conversion flattens the image to greyscale. Binarization turns that into a `BitMatrix`,
529
+ either globally or with a hybrid local threshold that survives uneven lighting. Detection locates
530
+ a symbol and its corners in that bit plane. Detectors that recover four perspective-aware corners
531
+ (currently QR) sample the symbol back through a perspective transform. Data Matrix currently uses
532
+ an axis-aligned bounding box plus quarter-turn retries. Error correction repairs what the camera
533
+ lost. Only then is the payload decoded.
534
+
535
+ **Reed–Solomon is generic over the finite field.** The `GaloisField` class is constructed with a
536
+ field order and a primitive polynomial rather than hard-coding GF(256), which is what lets one
537
+ implementation serve QR, and the prime field GF(929) that PDF417 needs. Prime fields are the
538
+ subtle case: in a binary field addition and subtraction are both XOR, so a decoder that inlines
539
+ `^` for field addition passes every binary field and fails only the prime one.
540
+
541
+ ---
542
+
543
+ ## About the GPU path
544
+
545
+ The WebGL2 and WebGPU backends accelerate **drawing** a barcode, not **computing** one. That is
546
+ worth stating plainly, because "GPU barcode generation" naturally suggests the latter.
547
+
548
+ Encoding is sequential integer work: Reed–Solomon polynomial division, mask penalty scoring, bit
549
+ placement along a zig-zag path. Each step depends on the one before it, which is precisely the
550
+ shape a GPU cannot exploit. A complete QR encode takes well under a millisecond on the CPU — less
551
+ time than dispatching a compute shader and reading the result back would cost. Moving it to the
552
+ GPU would make it slower.
553
+
554
+ So encoding stays on the CPU because that is the correct engineering answer, not because
555
+ something is missing. Where the GPU genuinely earns its place is drawing large symbols, or many
556
+ symbols per frame, straight into a canvas without a CPU-side pixel buffer — and it would earn it
557
+ again on the read side, where per-frame greyscale conversion and block statistics over a 4K camera
558
+ image are both the real bottleneck and embarrassingly parallel.
559
+
560
+ ---
561
+
562
+ ## Browser support
563
+
564
+ The syntax floor is **iOS Safari 15**. No `Array.prototype.at`, no top-level `await`, no
565
+ `Object.groupBy`.
566
+
567
+ `OffscreenCanvas`, WebGL2 and WebGPU are feature-detected, never assumed. The 2D canvas path
568
+ always exists, so nothing is unreachable on an older device — `renderToCanvasAuto` degrades to it
569
+ and reports which backend actually drew.
570
+
571
+ ---
572
+
573
+ ## Licence
574
+
575
+ MIT © 2026 Sythos. Every source file carries the header.
576
+
577
+ **The implementation is original Sythos work.** No third-party barcode source code is copied into
578
+ or shipped by this package. The symbologies are implemented from published descriptions of the
579
+ formats and from original Sythos data structures; MicroPDF417, Micro QR, rMQR and the Sythos
580
+ Canvas QR profile carry provenance and pending legal review in `NOTICE.md`. Independent
581
+ implementations and public technical material may be consulted for engineering review or
582
+ black-box verification; no third-party source code is copied or shipped. The distributed package has no runtime third-party
583
+ dependencies. See [`NOTICE.md`](NOTICE.md) and the per-format files in [`licenses/`](licenses/).
584
+
585
+ Public DENSO FrameQR material was consulted only to document the compatibility boundary. ZXing
586
+ was used only as an independent black-box validation tool; no ZXing source code or tables are
587
+ copied or shipped. The Sythos Canvas QR profile does not claim native DENSO FrameQR
588
+ interoperability.
589
+ licence or co-author attribution.
590
+
591
+ **Trademark is not licence.** QR Code® is a registered trademark of DENSO WAVE; Aztec Code,
592
+ MaxiCode and GS1 DataBar are likewise marks of their owners. A trademark does not restrict
593
+ implementing a symbology, but it does constrain branding — which is why this package is named
594
+ descriptively rather than after any mark.
595
+
596
+ Data Matrix ECC 200 is governed by ISO/IEC 16022:2024; GS1 DataMatrix additionally uses the GS1
597
+ General Specifications and a leading FNC1. Its engineering provenance, patent and trademark
598
+ research notes are recorded in [`licenses/data-matrix.license`](licenses/data-matrix.license),
599
+ with unresolved claims kept explicitly marked using scoped review labels.
600
+
601
+ PDF417 and MicroPDF417 provenance and legal review notes are recorded in
602
+ [`licenses/pdf417.license`](licenses/pdf417.license),
603
+ [`licenses/micropdf417.license`](licenses/micropdf417.license) and the attribution log in
604
+ [`NOTICE.md`](NOTICE.md).
605
+
606
+ Micro QR and rMQR provenance and scoped legal-review notes are recorded in
607
+ [`licenses/micro-qr.license`](licenses/micro-qr.license),
608
+ [`licenses/rmqr.license`](licenses/rmqr.license) and [`NOTICE.md`](NOTICE.md).
609
+
610
+ [`LICENSE`](LICENSE) carries the full MIT text plus an informational appendix inventorying the
611
+ specification copyrights, patent history and trademarks that surround these symbologies. None of
612
+ them is resolved by this file; the appendix is an engineering inventory, not legal advice.
613
+ [`NOTICE.md`](NOTICE.md) records the origin of the code and how its correctness is verified.
614
+
615
+ ---
616
+
617
+ ## Contributing and roadmap
618
+
619
+ [`PLAN.md`](PLAN.md) is the live status document: what is shipped, what is next, and the ground
620
+ rules — chief among them that no third-party source code is copied into or shipped by this
621
+ project, while public or normative values remain provenance-tracked and subject to legal review.
622
+
623
+ Issues and pull requests are welcome at
624
+ [Sythos/JS_Barcode_Universal](https://github.com/Sythos/JS_Barcode_Universal). A patch that adds a
625
+ symbology should implement it from the published description of the format, generate its tables
626
+ where they are derivable, and come with a symbol that a scanner this project did not write has
627
+ actually read — that last one is the check that matters.