@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
@@ -0,0 +1,149 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+
31
+ /**
32
+ * Axis-aligned MicroPDF417 raster detection.
33
+ *
34
+ * A MicroPDF417 symbol has a fixed module width for each column count and a
35
+ * dark leading and trailing module in every row. Consequently the bounding
36
+ * rectangle of dark pixels identifies the complete symbol even when a light
37
+ * quiet zone surrounds it. Its width determines the integer raster scale;
38
+ * the existing direct-module decoder then verifies every row-address pattern
39
+ * and selects the exact variant. This is intentionally narrower than the
40
+ * PDF417 photo detector: it handles clean binarized rasters only, not skew or
41
+ * projective camera images.
42
+ *
43
+ * @module micropdf417/detector
44
+ */
45
+
46
+ import { BitMatrix } from '../core/bit-matrix.js';
47
+ import { decodeMicroPDF417 } from './decoder.js';
48
+
49
+ /** @typedef {{x:number, y:number}} Point */
50
+
51
+ // Each row has two 10-module side RAPs, a final separator, and 17 modules for
52
+ // each data column. Three and four columns also contain one central RAP.
53
+ function symbolWidth(columns) { return 21 + columns * 17 + (columns > 2 ? 10 : 0); }
54
+ const WIDTHS = [1, 2, 3, 4].map(symbolWidth);
55
+
56
+ function rotateClockwise(source) {
57
+ const rotated = new BitMatrix(source.height, source.width);
58
+ for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) {
59
+ if (source.get(x, y)) rotated.set(source.height - 1 - y, x);
60
+ }
61
+ return rotated;
62
+ }
63
+
64
+ function integerScale(width, modules) {
65
+ if (width % modules) return 0;
66
+ const scale = width / modules;
67
+ return Number.isInteger(scale) && scale > 0 ? scale : 0;
68
+ }
69
+
70
+ /** Collapse exact integer scale blocks using a majority vote. */
71
+ function sampleRaster(image, bounds, modulesWide, scale) {
72
+ const modulesHigh = bounds.height / scale;
73
+ if (!Number.isInteger(modulesHigh) || modulesHigh < 1) return null;
74
+ const matrix = new BitMatrix(modulesWide, modulesHigh);
75
+ for (let y = 0; y < modulesHigh; y++) for (let x = 0; x < modulesWide; x++) {
76
+ let dark = 0;
77
+ for (let py = 0; py < scale; py++) for (let px = 0; px < scale; px++) {
78
+ if (image.get(bounds.x + x * scale + px, bounds.y + y * scale + py)) dark++;
79
+ }
80
+ if (dark * 2 >= scale * scale) matrix.set(x, y);
81
+ }
82
+ return matrix;
83
+ }
84
+
85
+ function rectangle(bounds) {
86
+ return [
87
+ { x: bounds.x, y: bounds.y },
88
+ { x: bounds.x + bounds.width, y: bounds.y },
89
+ { x: bounds.x + bounds.width, y: bounds.y + bounds.height },
90
+ { x: bounds.x, y: bounds.y + bounds.height },
91
+ ];
92
+ }
93
+
94
+ function detectAxisAligned(image, options) {
95
+ const bounds = image.getBounds();
96
+ if (!bounds) return null;
97
+ for (const width of WIDTHS) {
98
+ const scale = integerScale(bounds.width, width);
99
+ if (!scale || bounds.height % scale) continue;
100
+ const matrix = sampleRaster(image, bounds, width, scale);
101
+ if (!matrix) continue;
102
+ try {
103
+ const decoded = decodeMicroPDF417(matrix, options);
104
+ return { matrix, corners: rectangle(bounds), moduleSize: scale, ...decoded };
105
+ } catch { /* The RAP sequence is not a MicroPDF417 symbol of this width. */ }
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * Detect and decode one clean, binarized MicroPDF417 raster.
112
+ *
113
+ * Integer upscaling and quiet zones are accepted. The image is retried at all
114
+ * quarter-turns, but arbitrary angles and perspective require caller-side
115
+ * rectification before this function is used. `rotation` reports the clockwise
116
+ * orientation of the supplied input relative to a normally oriented symbol;
117
+ * it is not the inverse correction applied internally while searching.
118
+ *
119
+ * @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
120
+ * @param {object} [options] Passed to {@link decodeMicroPDF417}.
121
+ * @returns {(ReturnType<typeof decodeMicroPDF417> & {matrix: BitMatrix, corners: Point[], moduleSize: number, rotation: number}) | null}
122
+ */
123
+ export function detectMicroPDF417(binaryImage, options = {}) {
124
+ if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function') return null;
125
+ let oriented = binaryImage;
126
+ let toOriginal = (point) => ({ x: point.x, y: point.y });
127
+ for (let turns = 0; turns < 4; turns++) {
128
+ const found = detectAxisAligned(oriented, options);
129
+ // Search rotates clockwise to normalize the input. Public rotation has the
130
+ // opposite meaning: it describes how the input itself was rotated.
131
+ if (found) return {
132
+ ...found,
133
+ rotation: (360 - turns * 90) % 360,
134
+ corners: found.corners.map(toOriginal),
135
+ };
136
+ const previous = oriented;
137
+ const previousToOriginal = toOriginal;
138
+ oriented = rotateClockwise(previous);
139
+ // Boundary coordinates (rather than just pixel centres) are transformed
140
+ // here, so callers can draw the returned rectangle directly on the input.
141
+ toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
142
+ }
143
+ return null;
144
+ }
145
+
146
+ /** Alias kept symmetric with the other 2D readers. */
147
+ export function detectAndDecodeMicroPDF417(binaryImage, options = {}) {
148
+ return detectMicroPDF417(binaryImage, options);
149
+ }
@@ -0,0 +1,209 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+
31
+ /** MicroPDF417 encoder. @module micropdf417/encoder */
32
+
33
+ import { BitMatrix } from '../core/bit-matrix.js';
34
+ import { EncodeError } from '../core/errors.js';
35
+ import { pdf417PatternForCodeword } from '../pdf417/tables.js';
36
+ import { compactMicroPDF417 } from './compaction.js';
37
+ import { microPdf417ErrorCorrection } from './error-correction.js';
38
+ import {
39
+ MICROPDF417_VARIANTS,
40
+ microPdf417RapSequence,
41
+ microPdf417RowAddress,
42
+ microPdf417VariantByNumber,
43
+ microPdf417VariantForCapacity,
44
+ } from './tables.js';
45
+
46
+ function appendWidths(matrix, y, x, sequence, height) {
47
+ let dark = true;
48
+ for (const digit of sequence) {
49
+ const width = digit.charCodeAt(0) - 48;
50
+ if (!Number.isInteger(width) || width < 1 || width > 6) {
51
+ throw new EncodeError('MicroPDF417: invalid module-width sequence');
52
+ }
53
+ if (dark) matrix.setRegion(x, y, width, height);
54
+ x += width;
55
+ dark = !dark;
56
+ }
57
+ return x;
58
+ }
59
+
60
+ function codewordSequence(codeword, cluster) {
61
+ return pdf417PatternForCodeword(codeword, cluster)
62
+ .toString(2)
63
+ .padStart(17, '0')
64
+ .replace(/0+|1+/g, (run) => String(run.length));
65
+ }
66
+
67
+ function symbolWidth(columns) {
68
+ return 21 + columns * 17 + (columns > 2 ? 10 : 0);
69
+ }
70
+
71
+ function validateOptions(options) {
72
+ const rowHeight = options.rowHeight ?? 2;
73
+ if (!Number.isInteger(rowHeight) || rowHeight < 2) {
74
+ throw new EncodeError('MicroPDF417: rowHeight must be an integer of at least 2');
75
+ }
76
+ if (options.columns !== undefined &&
77
+ (!Number.isInteger(options.columns) || options.columns < 1 || options.columns > 4)) {
78
+ throw new EncodeError('MicroPDF417: columns must be an integer in 1..4');
79
+ }
80
+ if (options.variant !== undefined &&
81
+ (!Number.isInteger(options.variant) || options.variant < 1 || options.variant > 34)) {
82
+ throw new EncodeError('MicroPDF417: variant must be an integer in 1..34');
83
+ }
84
+ if (options.aspectRatio !== undefined &&
85
+ (!Number.isFinite(options.aspectRatio) || options.aspectRatio <= 0)) {
86
+ throw new EncodeError('MicroPDF417: aspectRatio must be positive');
87
+ }
88
+ if (options.rows !== undefined) {
89
+ throw new EncodeError('MicroPDF417: rows are fixed by the selected variant');
90
+ }
91
+ if (options.eccLevel !== undefined) {
92
+ throw new EncodeError('MicroPDF417: error correction is fixed by the selected variant');
93
+ }
94
+ for (const feature of [
95
+ 'structuredAppend', 'macro', 'macroPdf417', 'macroControlBlock',
96
+ 'readerInit', 'gs1', 'hibc', 'linkage',
97
+ ]) {
98
+ if (options[feature] !== undefined) {
99
+ throw new EncodeError(`MicroPDF417: ${feature} is not implemented`);
100
+ }
101
+ }
102
+ return rowHeight;
103
+ }
104
+
105
+ function chooseVariant(codewordCount, rowHeight, options) {
106
+ if (options.variant !== undefined) {
107
+ const variant = microPdf417VariantByNumber(options.variant);
108
+ if (!variant) throw new EncodeError(`MicroPDF417: unknown variant ${options.variant}`);
109
+ if (options.columns !== undefined && variant.columns !== options.columns) {
110
+ throw new EncodeError(`MicroPDF417: variant ${variant.id} has ${variant.columns} columns`);
111
+ }
112
+ if (codewordCount > variant.dataCodewords) {
113
+ throw new EncodeError(
114
+ `MicroPDF417: payload requires ${codewordCount} data codewords, variant ${variant.id} holds ${variant.dataCodewords}`
115
+ );
116
+ }
117
+ return variant;
118
+ }
119
+
120
+ if (options.columns === undefined && options.aspectRatio === undefined) {
121
+ try {
122
+ return microPdf417VariantForCapacity(codewordCount);
123
+ } catch (error) {
124
+ if (!(error instanceof RangeError)) throw error;
125
+ throw new EncodeError(`MicroPDF417: payload requires ${codewordCount} data codewords and exceeds every variant`);
126
+ }
127
+ }
128
+
129
+ const candidates = MICROPDF417_VARIANTS.filter((variant) =>
130
+ variant.dataCodewords >= codewordCount &&
131
+ (options.columns === undefined || variant.columns === options.columns)
132
+ );
133
+ if (!candidates.length) {
134
+ const columnText = options.columns === undefined ? '' : ` with ${options.columns} columns`;
135
+ throw new EncodeError(`MicroPDF417: payload does not fit any supported variant${columnText}`);
136
+ }
137
+ if (options.aspectRatio === undefined) {
138
+ return candidates.reduce((best, variant) =>
139
+ variant.dataCodewords < best.dataCodewords ||
140
+ (variant.dataCodewords === best.dataCodewords && variant.totalCodewords < best.totalCodewords)
141
+ ? variant : best
142
+ );
143
+ }
144
+
145
+ const target = options.aspectRatio;
146
+ return candidates.reduce((best, variant) => {
147
+ const ratio = symbolWidth(variant.columns) / (variant.rows * rowHeight);
148
+ const score = Math.abs(Math.log(ratio / target)) +
149
+ (variant.dataCodewords - codewordCount) / 10000;
150
+ return !best || score < best.score ? { variant, score } : best;
151
+ }, null).variant;
152
+ }
153
+
154
+ /** Encode a value as one of the 34 fixed MicroPDF417 variants. */
155
+ export function encodeMicroPDF417(value, options = {}) {
156
+ const rowHeight = validateOptions(options);
157
+ const payload = compactMicroPDF417(value, options);
158
+ const variant = chooseVariant(payload.length, rowHeight, options);
159
+ const data = payload.slice();
160
+ while (data.length < variant.dataCodewords) data.push(900);
161
+ const ecc = microPdf417ErrorCorrection(data, variant);
162
+ if (ecc.length !== variant.eccCodewords) {
163
+ throw new EncodeError('MicroPDF417: error-correction length does not match the selected variant');
164
+ }
165
+ const codewords = data.concat(ecc);
166
+ if (codewords.length !== variant.totalCodewords || codewords.length !== variant.rows * variant.columns) {
167
+ throw new EncodeError('MicroPDF417: selected variant has inconsistent codeword dimensions');
168
+ }
169
+
170
+ const matrix = new BitMatrix(symbolWidth(variant.columns), variant.rows * rowHeight);
171
+ for (let row = 0; row < variant.rows; row++) {
172
+ const y = row * rowHeight;
173
+ const address = microPdf417RowAddress(variant, row);
174
+ let x = appendWidths(matrix, y, 0, microPdf417RapSequence(address.left, 'side'), rowHeight);
175
+ for (let column = 0; column < variant.columns; column++) {
176
+ x = appendWidths(
177
+ matrix,
178
+ y,
179
+ x,
180
+ codewordSequence(codewords[row * variant.columns + column], address.cluster),
181
+ rowHeight
182
+ );
183
+ const hasCentralRap = (variant.columns === 3 && column === 0) ||
184
+ (variant.columns === 4 && column === 1);
185
+ if (hasCentralRap) {
186
+ if (address.center === null) {
187
+ throw new EncodeError('MicroPDF417: selected variant is missing its centre row address');
188
+ }
189
+ x = appendWidths(matrix, y, x, microPdf417RapSequence(address.center, 'center'), rowHeight);
190
+ }
191
+ }
192
+ x = appendWidths(matrix, y, x, microPdf417RapSequence(address.right, 'side'), rowHeight);
193
+ matrix.setRegion(x, y, 1, rowHeight);
194
+ x++;
195
+ if (x !== matrix.width) throw new EncodeError('MicroPDF417: row width does not match the selected variant');
196
+ }
197
+
198
+ matrix.micropdf417 = {
199
+ variant: variant.id,
200
+ rows: variant.rows,
201
+ columns: variant.columns,
202
+ eccCodewords: variant.eccCodewords,
203
+ rowHeight,
204
+ payloadCodewords: payload.length,
205
+ dataCodewords: data,
206
+ codewords,
207
+ };
208
+ return matrix;
209
+ }
@@ -0,0 +1,55 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+
31
+ /** MicroPDF417 error correction over the existing GF(929) core. @module micropdf417/error-correction */
32
+
33
+ import { EncodeError } from '../core/errors.js';
34
+ import { GF929 } from '../core/galois-field.js';
35
+ import { generatorPoly, rsDecode, rsEncode } from '../core/reed-solomon.js';
36
+
37
+ function eccLength(entry) {
38
+ if (!entry || !Number.isInteger(entry.eccCodewords)) throw new EncodeError('MicroPDF417: a variant with an ECC length is required');
39
+ if (entry.eccCodewords < 1 || entry.eccCodewords >= GF929.size) throw new EncodeError('MicroPDF417: ECC length is outside GF(929) bounds');
40
+ return entry.eccCodewords;
41
+ }
42
+
43
+ /** Return the fixed number of parity codewords for a MicroPDF417 variant. */
44
+ export function microPdf417EccLength(entry) { return eccLength(entry); }
45
+
46
+ /** Build the MicroPDF417 generator polynomial for a variant's fixed ECC length. */
47
+ export function microPdf417Generator(entry) { return generatorPoly(eccLength(entry), GF929, 1); }
48
+
49
+ /** Compute systematic MicroPDF417 parity codewords. `data` must already include padding. */
50
+ export function microPdf417ErrorCorrection(data, entry) { return rsEncode(data, eccLength(entry), GF929, 1); }
51
+
52
+ /** Correct a complete MicroPDF417 codeword stream, optionally marking erasures. */
53
+ export function microPdf417CorrectErrors(codewords, entry, erasures = []) {
54
+ return rsDecode(codewords, eccLength(entry), GF929, 1, erasures);
55
+ }
@@ -0,0 +1,49 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+
31
+ export {
32
+ MICROPDF417_VARIANTS,
33
+ microPdf417NextRap,
34
+ microPdf417VariantByNumber,
35
+ microPdf417VariantForCapacity,
36
+ microPdf417RapSequence,
37
+ microPdf417RowAddress,
38
+ validateMicroPdf417Tables,
39
+ } from './tables.js';
40
+ export {
41
+ microPdf417EccLength,
42
+ microPdf417Generator,
43
+ microPdf417ErrorCorrection,
44
+ microPdf417CorrectErrors,
45
+ } from './error-correction.js';
46
+ export { compactMicroPDF417 } from './compaction.js';
47
+ export { encodeMicroPDF417 } from './encoder.js';
48
+ export { decodeMicroPDF417 } from './decoder.js';
49
+ export { detectMicroPDF417, detectAndDecodeMicroPDF417 } from './detector.js';
@@ -0,0 +1,184 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+
31
+ /**
32
+ * MicroPDF417 format facts and Row Address Pattern (RAP) helpers.
33
+ *
34
+ * The tables are represented as compact, immutable data and are guarded by
35
+ * {@link validateMicroPdf417Tables}. They are deliberately separate from the
36
+ * PDF417 symbol-character table: MicroPDF417 has a fixed family of symbols and
37
+ * its own row-address system.
38
+ *
39
+ * Values are derived from publicly available symbology documentation and
40
+ * independently checked against black-box reference output. This module makes
41
+ * no certification or conformance claim.
42
+ *
43
+ * @module micropdf417/tables
44
+ */
45
+
46
+ const variant = (id, columns, rows, eccCodewords, rapStart, rapRotation) => Object.freeze({
47
+ id,
48
+ columns,
49
+ rows,
50
+ totalCodewords: columns * rows,
51
+ dataCodewords: columns * rows - eccCodewords,
52
+ eccCodewords,
53
+ rapStart,
54
+ rapRotation,
55
+ });
56
+
57
+ /** All 34 predefined MicroPDF417 symbol variants, in format-table order. */
58
+ export const MICROPDF417_VARIANTS = Object.freeze([
59
+ variant(1, 1, 11, 7, 1, 8), variant(2, 1, 14, 7, 8, 0),
60
+ variant(3, 1, 17, 7, 36, 0), variant(4, 1, 20, 8, 19, 0),
61
+ variant(5, 1, 24, 8, 9, 8), variant(6, 1, 28, 8, 25, 8),
62
+ variant(7, 2, 8, 8, 1, 0), variant(8, 2, 11, 9, 1, 8),
63
+ variant(9, 2, 14, 9, 8, 0), variant(10, 2, 17, 10, 36, 0),
64
+ variant(11, 2, 20, 11, 19, 0), variant(12, 2, 23, 13, 9, 8),
65
+ variant(13, 2, 26, 15, 27, 8),
66
+ variant(14, 3, 6, 12, 1, 0), variant(15, 3, 8, 14, 7, 0),
67
+ variant(16, 3, 10, 16, 15, 0), variant(17, 3, 12, 18, 25, 0),
68
+ variant(18, 3, 15, 21, 37, 0), variant(19, 3, 20, 26, 1, 16),
69
+ variant(20, 3, 26, 32, 1, 8), variant(21, 3, 32, 38, 21, 8),
70
+ variant(22, 3, 38, 44, 15, 16), variant(23, 3, 44, 50, 1, 24),
71
+ variant(24, 4, 4, 8, 47, 24), variant(25, 4, 6, 12, 1, 0),
72
+ variant(26, 4, 8, 14, 7, 0), variant(27, 4, 10, 16, 15, 0),
73
+ variant(28, 4, 12, 18, 25, 0), variant(29, 4, 15, 21, 37, 0),
74
+ variant(30, 4, 20, 26, 1, 16), variant(31, 4, 26, 32, 1, 8),
75
+ variant(32, 4, 32, 38, 21, 8), variant(33, 4, 38, 44, 15, 16),
76
+ variant(34, 4, 44, 50, 1, 24),
77
+ ]);
78
+
79
+ const byId = new Map(MICROPDF417_VARIANTS.map((entry) => [entry.id, entry]));
80
+
81
+ // Six run widths, ordered bar-space-bar-space-bar-space. A RAP is ten
82
+ // modules wide; the right RAP has one additional one-module stop bar when
83
+ // rendered. Keeping runs rather than bitmap literals makes each invariant
84
+ // inspectable and avoids a rendering-specific representation here.
85
+ const SIDE_RAP_RUNS = Object.freeze([
86
+ '221311', '311311', '312211', '222211', '213211', '214111', '223111', '313111',
87
+ '322111', '412111', '421111', '331111', '241111', '232111', '231211', '321211',
88
+ '411211', '411121', '411112', '321112', '312112', '311212', '311221', '311131',
89
+ '311122', '311113', '221113', '221122', '221131', '221221', '222121', '312121',
90
+ '321121', '231121', '231112', '222112', '213112', '212212', '212221', '212131',
91
+ '212122', '212113', '211213', '211123', '211132', '211141', '211231', '211222',
92
+ '211312', '211321', '211411', '212311',
93
+ ]);
94
+
95
+ const CENTER_RAP_RUNS = Object.freeze([
96
+ '112231', '121231', '122131', '131131', '131221', '132121', '141121', '141211',
97
+ '142111', '133111', '132211', '131311', '122311', '123211', '124111', '115111',
98
+ '114211', '114121', '123121', '123112', '122212', '122221', '121321', '121411',
99
+ '112411', '113311', '113221', '113212', '113122', '122122', '131122', '131113',
100
+ '122113', '113113', '112213', '112222', '112312', '112321', '111421', '111331',
101
+ '111322', '111232', '111223', '111133', '111124', '111214', '112114', '121114',
102
+ '121123', '121132', '112132', '112141',
103
+ ]);
104
+
105
+ /** @param {number} value @param {number} offset @returns {number} */
106
+ export function microPdf417NextRap(value, offset = 1) {
107
+ if (!Number.isInteger(value) || value < 1 || value > 52) throw new RangeError('MicroPDF417: RAP number must be in 1..52');
108
+ if (!Number.isInteger(offset)) throw new RangeError('MicroPDF417: RAP offset must be an integer');
109
+ return ((value - 1 + offset) % 52 + 52) % 52 + 1;
110
+ }
111
+
112
+ /** @param {number} id @returns {Readonly<typeof MICROPDF417_VARIANTS[number]>} */
113
+ export function microPdf417VariantByNumber(id) {
114
+ const entry = byId.get(id);
115
+ if (!entry) throw new RangeError('MicroPDF417: variant must be an integer in 1..34');
116
+ return entry;
117
+ }
118
+
119
+ /**
120
+ * Return the smallest data-region candidate that fits `codewords`.
121
+ * Ties are resolved by width, then height, so selection is deterministic.
122
+ */
123
+ export function microPdf417VariantForCapacity(codewords) {
124
+ if (!Number.isInteger(codewords) || codewords < 1) throw new RangeError('MicroPDF417: codeword capacity must be a positive integer');
125
+ const candidates = MICROPDF417_VARIANTS.filter((entry) => entry.dataCodewords >= codewords);
126
+ if (!candidates.length) throw new RangeError('MicroPDF417: payload exceeds the largest symbol data region');
127
+ return candidates.slice().sort((a, b) => a.totalCodewords - b.totalCodewords || a.columns - b.columns || a.rows - b.rows)[0];
128
+ }
129
+
130
+ /** Return the six bar/space run widths for a numbered side or center RAP. */
131
+ export function microPdf417RapSequence(number, kind = 'side') {
132
+ if (!Number.isInteger(number) || number < 1 || number > 52) throw new RangeError('MicroPDF417: RAP number must be in 1..52');
133
+ if (kind === 'side') return SIDE_RAP_RUNS[number - 1];
134
+ if (kind === 'center') return CENTER_RAP_RUNS[number - 1];
135
+ throw new RangeError('MicroPDF417: RAP kind must be side or center');
136
+ }
137
+
138
+ /**
139
+ * Resolve all row-address data for a zero-based row within a variant.
140
+ * @returns {{left: number, center: number|null, right: number, cluster: 0|3|6}}
141
+ */
142
+ export function microPdf417RowAddress(entry, row) {
143
+ if (!entry || !Number.isInteger(entry.columns) || !Number.isInteger(entry.rows)) throw new TypeError('MicroPDF417: a variant entry is required');
144
+ if (!Number.isInteger(row) || row < 0 || row >= entry.rows) throw new RangeError(`MicroPDF417: row must be in 0..${entry.rows - 1}`);
145
+ const left = microPdf417NextRap(entry.rapStart, row);
146
+ const cluster = /** @type {0|3|6} */ (((left - 1) % 3) * 3);
147
+ if (entry.columns < 3) return { left, center: null, right: microPdf417NextRap(left, entry.rapRotation), cluster };
148
+ const center = microPdf417NextRap(left, entry.rapRotation);
149
+ return { left, center, right: microPdf417NextRap(center, entry.rapRotation), cluster };
150
+ }
151
+
152
+ const validRuns = (runs) => runs.length === 6 && /^[1-9]{6}$/.test(runs) && [...runs].reduce((sum, digit) => sum + Number(digit), 0) === 10;
153
+ const oneEdgeShift = (from, to) => [...from].reduce((sum, digit, index) => sum + Math.abs(Number(digit) - Number(to[index])), 0) === 2;
154
+
155
+ /** Return any table-invariant failures; an empty result means the table is coherent. */
156
+ export function validateMicroPdf417Tables() {
157
+ const issues = [];
158
+ if (MICROPDF417_VARIANTS.length !== 34) issues.push('expected 34 variants');
159
+ const ids = new Set();
160
+ const formats = new Set();
161
+ for (const entry of MICROPDF417_VARIANTS) {
162
+ if (ids.has(entry.id)) issues.push(`duplicate variant ${entry.id}`); ids.add(entry.id);
163
+ const format = `${entry.columns}x${entry.rows}`;
164
+ if (formats.has(format)) issues.push(`duplicate format ${format}`); formats.add(format);
165
+ if (entry.totalCodewords !== entry.columns * entry.rows) issues.push(`${format}: total codeword geometry mismatch`);
166
+ if (entry.dataCodewords + entry.eccCodewords !== entry.totalCodewords) issues.push(`${format}: data/ECC capacity mismatch`);
167
+ if (entry.eccCodewords < 7 || entry.eccCodewords > 50) issues.push(`${format}: invalid ECC length`);
168
+ if (entry.rapStart < 1 || entry.rapStart > 52 || entry.rapRotation < 0 || entry.rapRotation > 51) issues.push(`${format}: invalid RAP assignment`);
169
+ for (let row = 0; row < entry.rows; row++) {
170
+ const address = microPdf417RowAddress(entry, row);
171
+ if (address.cluster !== ((address.left - 1) % 3) * 3) issues.push(`${format}: cluster mismatch at row ${row}`);
172
+ if ((entry.columns < 3) !== (address.center === null)) issues.push(`${format}: center RAP layout mismatch`);
173
+ }
174
+ }
175
+ for (const [kind, runs] of [['side', SIDE_RAP_RUNS], ['center', CENTER_RAP_RUNS]]) {
176
+ if (runs.length !== 52) issues.push(`${kind}: expected 52 RAPs`);
177
+ if (new Set(runs).size !== runs.length) issues.push(`${kind}: duplicate RAP`);
178
+ for (let i = 0; i < runs.length; i++) {
179
+ if (!validRuns(runs[i])) issues.push(`${kind}: invalid RAP ${i + 1}`);
180
+ if (runs.length && !oneEdgeShift(runs[i], runs[(i + 1) % runs.length])) issues.push(`${kind}: RAP ${i + 1} is not adjacent to its successor`);
181
+ }
182
+ }
183
+ return issues;
184
+ }