@sythos/js_barcode_universal 1.5.8 → 1.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -4
- package/NOTICE.md +2 -2
- package/README.md +17 -13
- package/bundle/sythos-barcode.esm.js +11658 -11244
- package/bundle/sythos-barcode.js +11658 -11244
- package/package.json +10 -3
- package/src/index.d.ts +1 -1
- package/src/index.js +508 -516
- package/src/js/aztec/decoder.js +208 -199
- package/src/js/aztec/detector.js +153 -126
- package/src/js/aztec/encoder.js +172 -153
- package/src/js/aztec/high-level.js +106 -101
- package/src/js/aztec/index.js +1 -12
- package/src/js/aztec/tables.js +106 -120
- package/src/js/aztecrune/decoder.js +76 -85
- package/src/js/aztecrune/detector.js +107 -93
- package/src/js/aztecrune/encoder.js +50 -68
- package/src/js/aztecrune/index.js +1 -16
- package/src/js/aztecrune/tables.js +59 -54
- package/src/js/compactpdf417/decoder.js +69 -74
- package/src/js/compactpdf417/detector.js +74 -75
- package/src/js/compactpdf417/encoder.js +68 -81
- package/src/js/compactpdf417/index.js +1 -13
- package/src/js/compactpdf417/tables.js +71 -80
- package/src/js/core/bit-buffer.js +106 -122
- package/src/js/core/bit-matrix.js +196 -194
- package/src/js/core/errors.js +13 -15
- package/src/js/core/galois-field.js +119 -133
- package/src/js/core/index.js +2 -19
- package/src/js/core/reed-solomon.js +195 -204
- package/src/js/databar/codec.js +93 -108
- package/src/js/databar/decoder.js +149 -140
- package/src/js/databar/encoder.js +47 -55
- package/src/js/databar/gs1.js +122 -123
- package/src/js/databar/index.js +4 -36
- package/src/js/databar/patterns.js +81 -85
- package/src/js/databar/tables.js +51 -52
- package/src/js/datamatrix/decoder.js +216 -182
- package/src/js/datamatrix/detector.js +161 -132
- package/src/js/datamatrix/encoder.js +192 -133
- package/src/js/datamatrix/index.js +1 -9
- package/src/js/datamatrix/tables.js +68 -69
- package/src/js/frameqr/decoder.js +102 -125
- package/src/js/frameqr/detector.js +101 -94
- package/src/js/frameqr/encoder.js +67 -80
- package/src/js/frameqr/index.js +1 -10
- package/src/js/frameqr/tables.js +145 -163
- package/src/js/image/binarizer.js +146 -162
- package/src/js/image/grid-sampler.js +63 -79
- package/src/js/image/index.js +0 -2
- package/src/js/image/luminance.js +129 -140
- package/src/js/image/perspective.js +112 -149
- package/src/js/micropdf417/compaction.js +64 -65
- package/src/js/micropdf417/decoder.js +106 -109
- package/src/js/micropdf417/detector.js +73 -70
- package/src/js/micropdf417/encoder.js +129 -154
- package/src/js/micropdf417/error-correction.js +6 -11
- package/src/js/micropdf417/index.js +2 -16
- package/src/js/micropdf417/tables.js +112 -99
- package/src/js/microqr/decoder.js +177 -182
- package/src/js/microqr/detector.js +240 -256
- package/src/js/microqr/encoder.js +239 -194
- package/src/js/microqr/index.js +0 -2
- package/src/js/microqr/tables.js +208 -200
- package/src/js/oned/addons.js +167 -191
- package/src/js/oned/index.js +27 -58
- package/src/js/oned/patterns.js +194 -213
- package/src/js/oned/reader.js +1003 -984
- package/src/js/oned/writers.js +411 -466
- package/src/js/pdf417/compaction.js +315 -222
- package/src/js/pdf417/decoder.js +71 -36
- package/src/js/pdf417/detector.js +377 -361
- package/src/js/pdf417/encoder.js +57 -46
- package/src/js/pdf417/error-correction.js +5 -8
- package/src/js/pdf417/tables.js +248 -250
- package/src/js/qr/decoder.js +311 -347
- package/src/js/qr/detector.js +378 -395
- package/src/js/qr/encoder.js +514 -599
- package/src/js/qr/index.js +0 -2
- package/src/js/qr/tables.js +375 -422
- package/src/js/render/image-data.js +57 -62
- package/src/js/render/index.js +21 -23
- package/src/js/render/options.js +82 -90
- package/src/js/render/png.js +144 -169
- package/src/js/render/svg.js +41 -44
- package/src/js/render/webgl.js +103 -109
- package/src/js/render/webgpu.js +205 -217
- package/src/js/rmqr/decoder.js +161 -53
- package/src/js/rmqr/detector.js +73 -41
- package/src/js/rmqr/encoder.js +253 -104
- package/src/js/rmqr/index.js +1 -5
- package/src/js/rmqr/tables.js +108 -85
- package/src/ts/aztec/decoder.ts +317 -0
- package/src/ts/aztec/detector.ts +224 -0
- package/src/ts/aztec/encoder.ts +257 -0
- package/src/ts/aztec/high-level.ts +211 -0
- package/src/ts/aztec/index.ts +45 -0
- package/src/ts/aztec/tables.ts +210 -0
- package/src/ts/aztecrune/decoder.ts +155 -0
- package/src/ts/aztecrune/detector.ts +166 -0
- package/src/ts/aztecrune/encoder.ts +121 -0
- package/src/ts/aztecrune/index.ts +49 -0
- package/src/ts/aztecrune/tables.ts +137 -0
- package/src/ts/compactpdf417/decoder.ts +128 -0
- package/src/ts/compactpdf417/detector.ts +139 -0
- package/src/ts/compactpdf417/encoder.ts +140 -0
- package/src/ts/compactpdf417/index.ts +17 -0
- package/src/ts/compactpdf417/tables.ts +173 -0
- package/src/ts/core/bit-buffer.ts +174 -0
- package/src/ts/core/bit-matrix.ts +241 -0
- package/src/ts/core/errors.ts +61 -0
- package/src/ts/core/galois-field.ts +207 -0
- package/src/ts/core/index.ts +56 -0
- package/src/ts/core/reed-solomon.ts +327 -0
- package/src/ts/databar/codec.ts +181 -0
- package/src/ts/databar/decoder.ts +215 -0
- package/src/ts/databar/encoder.ts +96 -0
- package/src/ts/databar/gs1.ts +177 -0
- package/src/ts/databar/index.ts +68 -0
- package/src/ts/databar/patterns.ts +143 -0
- package/src/ts/databar/tables.ts +124 -0
- package/src/ts/datamatrix/decoder.ts +262 -0
- package/src/ts/datamatrix/detector.ts +225 -0
- package/src/ts/datamatrix/encoder.ts +191 -0
- package/src/ts/datamatrix/index.ts +42 -0
- package/src/ts/datamatrix/tables.ts +123 -0
- package/src/ts/frameqr/decoder.ts +239 -0
- package/src/ts/frameqr/detector.ts +192 -0
- package/src/ts/frameqr/encoder.ts +156 -0
- package/src/ts/frameqr/index.ts +42 -0
- package/src/ts/frameqr/tables.ts +270 -0
- package/src/ts/image/binarizer.ts +270 -0
- package/src/ts/image/grid-sampler.ts +164 -0
- package/src/ts/image/index.ts +40 -0
- package/src/ts/image/luminance.ts +196 -0
- package/src/ts/image/perspective.ts +195 -0
- package/src/ts/index.d.ts +1 -1
- package/src/ts/index.ts +790 -0
- package/src/ts/micropdf417/compaction.ts +116 -0
- package/src/ts/micropdf417/decoder.ts +183 -0
- package/src/ts/micropdf417/detector.ts +149 -0
- package/src/ts/micropdf417/encoder.ts +209 -0
- package/src/ts/micropdf417/error-correction.ts +55 -0
- package/src/ts/micropdf417/index.ts +49 -0
- package/src/ts/micropdf417/tables.ts +184 -0
- package/src/ts/microqr/decoder.ts +245 -0
- package/src/ts/microqr/detector.ts +355 -0
- package/src/ts/microqr/encoder.ts +269 -0
- package/src/ts/microqr/index.ts +36 -0
- package/src/ts/microqr/tables.ts +316 -0
- package/src/ts/oned/addons.ts +420 -0
- package/src/ts/oned/index.ts +106 -0
- package/src/ts/oned/patterns.ts +384 -0
- package/src/ts/oned/reader.ts +1364 -0
- package/src/ts/oned/writers.ts +746 -0
- package/src/ts/pdf417/compaction.ts +298 -0
- package/src/ts/pdf417/decoder.ts +75 -0
- package/src/ts/pdf417/detector.ts +468 -0
- package/src/ts/pdf417/encoder.ts +91 -0
- package/src/ts/pdf417/error-correction.ts +47 -0
- package/src/ts/pdf417/index.ts +6 -0
- package/src/ts/pdf417/tables.ts +317 -0
- package/src/ts/qr/decoder.ts +575 -0
- package/src/ts/qr/detector.ts +630 -0
- package/src/ts/qr/encoder.ts +958 -0
- package/src/ts/qr/index.ts +44 -0
- package/src/ts/qr/tables.ts +737 -0
- package/src/ts/render/image-data.ts +125 -0
- package/src/ts/render/index.ts +130 -0
- package/src/ts/render/options.ts +160 -0
- package/src/ts/render/png.ts +295 -0
- package/src/ts/render/svg.ts +120 -0
- package/src/ts/render/webgl.ts +206 -0
- package/src/ts/render/webgpu.ts +369 -0
- package/src/ts/rmqr/decoder.ts +101 -0
- package/src/ts/rmqr/detector.ts +90 -0
- package/src/ts/rmqr/encoder.ts +172 -0
- package/src/ts/rmqr/index.ts +37 -0
- package/src/ts/rmqr/tables.ts +154 -0
package/src/js/rmqr/tables.js
CHANGED
|
@@ -27,128 +27,151 @@
|
|
|
27
27
|
*
|
|
28
28
|
* Original work. No code from any other barcode implementation.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
30
|
import { BitMatrix } from '../core/bit-matrix.js';
|
|
32
|
-
|
|
33
31
|
/** The 32 rMQR dimensions in ISO/IEC 23941 order (width, height). */
|
|
34
32
|
export const RMQR_SIZES = Object.freeze([
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
[43, 7], [59, 7], [77, 7], [99, 7], [139, 7],
|
|
34
|
+
[43, 9], [59, 9], [77, 9], [99, 9], [139, 9],
|
|
35
|
+
[27, 11], [43, 11], [59, 11], [77, 11], [99, 11], [139, 11],
|
|
36
|
+
[27, 13], [43, 13], [59, 13], [77, 13], [99, 13], [139, 13],
|
|
37
|
+
[43, 15], [59, 15], [77, 15], [99, 15], [139, 15],
|
|
38
|
+
[43, 17], [59, 17], [77, 17], [99, 17], [139, 17],
|
|
41
39
|
]);
|
|
42
|
-
|
|
43
40
|
const REMAINDER_BITS = Object.freeze([0, 3, 5, 6, 1, 2, 3, 1, 4, 5, 2, 1, 0, 2, 7, 6, 4, 1, 6, 4, 3, 0, 1, 4, 6, 7, 2, 1, 2, 0, 3, 4]);
|
|
44
41
|
const TOTAL_CODEWORDS = Object.freeze([13, 21, 32, 44, 68, 21, 33, 49, 66, 99, 15, 31, 47, 67, 89, 132, 21, 41, 60, 85, 113, 166, 51, 74, 103, 136, 199, 61, 88, 122, 160, 232]);
|
|
45
|
-
|
|
46
42
|
// Each block is [number of blocks, total codewords per block, data codewords per block].
|
|
47
43
|
const M_BLOCKS = [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
[[1, 13, 6]], [[1, 21, 12]], [[1, 32, 20]], [[1, 44, 28]], [[1, 68, 44]],
|
|
45
|
+
[[1, 21, 12]], [[1, 33, 21]], [[1, 49, 31]], [[1, 66, 42]], [[1, 49, 31], [1, 50, 32]],
|
|
46
|
+
[[1, 15, 7]], [[1, 31, 19]], [[1, 47, 31]], [[1, 67, 43]], [[1, 44, 28], [1, 45, 29]], [[2, 66, 42]],
|
|
47
|
+
[[1, 21, 14]], [[1, 41, 27]], [[1, 60, 38]], [[1, 42, 26], [1, 43, 27]], [[1, 56, 36], [1, 57, 37]], [[2, 55, 35], [1, 56, 36]],
|
|
48
|
+
[[1, 51, 33]], [[1, 74, 48]], [[1, 51, 33], [1, 52, 34]], [[2, 68, 44]], [[2, 66, 42], [1, 67, 43]],
|
|
49
|
+
[[1, 60, 39]], [[2, 44, 28]], [[2, 61, 39]], [[2, 53, 33], [1, 54, 34]], [[4, 58, 38]],
|
|
54
50
|
];
|
|
55
51
|
const H_BLOCKS = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
[[1, 13, 3]], [[1, 21, 7]], [[1, 32, 10]], [[1, 44, 14]], [[2, 34, 12]],
|
|
53
|
+
[[1, 21, 7]], [[1, 33, 11]], [[1, 24, 8], [1, 25, 9]], [[2, 33, 11]], [[3, 33, 11]],
|
|
54
|
+
[[1, 15, 5]], [[1, 31, 11]], [[1, 23, 7], [1, 24, 8]], [[1, 33, 11], [1, 34, 12]], [[1, 44, 14], [1, 45, 15]], [[3, 44, 14]],
|
|
55
|
+
[[1, 21, 7]], [[1, 41, 13]], [[2, 30, 10]], [[1, 42, 14], [1, 43, 15]], [[1, 37, 11], [2, 38, 12]], [[2, 41, 13], [2, 42, 14]],
|
|
56
|
+
[[1, 25, 7], [1, 26, 8]], [[2, 37, 13]], [[2, 34, 10], [1, 35, 11]], [[4, 34, 12]], [[1, 39, 13], [4, 40, 14]],
|
|
57
|
+
[[1, 30, 10], [1, 31, 11]], [[2, 44, 14]], [[1, 40, 12], [2, 41, 13]], [[4, 40, 14]], [[2, 38, 12], [4, 39, 13]],
|
|
62
58
|
];
|
|
63
|
-
|
|
64
59
|
const COUNT_BITS = Object.freeze({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
numeric: [4, 5, 6, 7, 7, 5, 6, 7, 7, 8, 4, 6, 7, 7, 8, 8, 5, 6, 7, 7, 8, 8, 7, 7, 8, 8, 9, 7, 8, 8, 8, 9],
|
|
61
|
+
alphanumeric: [3, 5, 5, 6, 6, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 5, 6, 6, 7, 7, 8, 6, 7, 7, 7, 8, 6, 7, 7, 8, 8],
|
|
62
|
+
byte: [3, 4, 5, 5, 6, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 4, 6, 6, 7, 7, 7, 6, 6, 7, 7, 7, 6, 6, 7, 7, 8],
|
|
63
|
+
kanji: [2, 3, 4, 5, 5, 3, 4, 5, 5, 6, 2, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 5, 5, 6, 6, 7, 5, 6, 6, 6, 7],
|
|
69
64
|
});
|
|
70
|
-
|
|
71
65
|
const ALIGNMENT_BY_WIDTH = Object.freeze({ 27: [], 43: [21], 59: [19, 39], 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111] });
|
|
72
|
-
|
|
73
66
|
/** @param {number} version */
|
|
74
67
|
export function versionInfo(version) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
if (!Number.isInteger(version) || version < 1 || version > 32)
|
|
69
|
+
throw new RangeError(`rMQR: version must be 1-32, got ${version}`);
|
|
70
|
+
const [width, height] = RMQR_SIZES[version - 1];
|
|
71
|
+
const blockTable = (ecc) => ecc === 'M' ? M_BLOCKS[version - 1] : H_BLOCKS[version - 1];
|
|
72
|
+
const blockLayout = (ecc) => {
|
|
73
|
+
if (ecc !== 'M' && ecc !== 'H')
|
|
74
|
+
throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
75
|
+
const blocks = [];
|
|
76
|
+
for (const [count, total, data] of blockTable(ecc))
|
|
77
|
+
for (let i = 0; i < count; i++)
|
|
78
|
+
blocks.push({ total, data, ecc: total - data });
|
|
79
|
+
return { blocks, totalCodewords: TOTAL_CODEWORDS[version - 1], totalDataCodewords: blocks.reduce((n, b) => n + b.data, 0), eccCodewords: blocks.reduce((n, b) => n + b.ecc, 0) };
|
|
80
|
+
};
|
|
81
|
+
return Object.freeze({ version, width, height, name: `R${height}x${width}`, indicator: version - 1, remainderBits: REMAINDER_BITS[version - 1], totalCodewords: TOTAL_CODEWORDS[version - 1], countBits(mode) { return COUNT_BITS[mode]?.[version - 1] ?? 0; }, blockLayout });
|
|
85
82
|
}
|
|
86
|
-
|
|
87
83
|
/** @param {number} width @param {number} height */
|
|
88
84
|
export function versionForSize(width, height) {
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const i = RMQR_SIZES.findIndex(([w, h]) => w === width && h === height);
|
|
86
|
+
return i < 0 ? null : versionInfo(i + 1);
|
|
91
87
|
}
|
|
92
|
-
|
|
93
88
|
/** @param {number} version */
|
|
94
89
|
export function alignmentCoordinates(version) { return ALIGNMENT_BY_WIDTH[versionInfo(version).width] || []; }
|
|
95
|
-
|
|
96
90
|
function bchRemainder(value) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
const generator = 0x1f25;
|
|
92
|
+
let v = value << 12;
|
|
93
|
+
while (v !== 0 && v.toString(2).length >= 13)
|
|
94
|
+
v ^= generator << (v.toString(2).length - 13);
|
|
95
|
+
return v;
|
|
101
96
|
}
|
|
102
|
-
|
|
103
97
|
/** Unmasked 18-bit format sequence: 5-bit version indicator plus ECC bit. */
|
|
104
98
|
export function formatBits(version, ecc) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
const v = versionInfo(version);
|
|
100
|
+
if (ecc !== 'M' && ecc !== 'H')
|
|
101
|
+
throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
102
|
+
const data = v.indicator | (ecc === 'H' ? 1 << 5 : 0);
|
|
103
|
+
return (data << 12) | bchRemainder(data);
|
|
109
104
|
}
|
|
110
|
-
|
|
111
105
|
export const FORMAT_MASK_FINDER = 0b011111101010110010;
|
|
112
106
|
export const FORMAT_MASK_SUB = 0b100000101001111011;
|
|
113
|
-
|
|
114
107
|
/** rMQR has one fixed mask: floor(y/2)+floor(x/3) even. */
|
|
115
108
|
export function maskBit(x, y) { return (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0; }
|
|
116
|
-
|
|
117
109
|
/** Function modules; set bits are non-data cells. */
|
|
118
110
|
export function functionModules(version) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
111
|
+
const v = versionInfo(version);
|
|
112
|
+
const m = new BitMatrix(v.width, v.height);
|
|
113
|
+
const { width: w, height: h } = v;
|
|
114
|
+
m.setRegion(0, 0, w, 1);
|
|
115
|
+
m.setRegion(0, h - 1, w, 1);
|
|
116
|
+
m.setRegion(0, 1, 1, h - 2);
|
|
117
|
+
m.setRegion(w - 1, 1, 1, h - 2);
|
|
118
|
+
for (const cx of alignmentCoordinates(version)) {
|
|
119
|
+
m.setRegion(cx - 1, 1, 3, 2);
|
|
120
|
+
m.setRegion(cx - 1, h - 3, 3, 2);
|
|
121
|
+
m.setRegion(cx, 3, 1, h - 6);
|
|
122
|
+
}
|
|
123
|
+
m.setRegion(1, 1, 7, h === 7 ? 5 : 7);
|
|
124
|
+
m.setRegion(8, 1, 3, 5);
|
|
125
|
+
m.setRegion(11, 1, 1, 3);
|
|
126
|
+
m.setRegion(w - 5, h - 5, 4, 4);
|
|
127
|
+
m.setRegion(w - 8, h - 6, 3, 5);
|
|
128
|
+
m.setRegion(w - 5, h - 6, 3, 1);
|
|
129
|
+
m.set(w - 2, 1);
|
|
130
|
+
if (h > 9)
|
|
131
|
+
m.set(1, h - 2);
|
|
132
|
+
return m;
|
|
126
133
|
}
|
|
127
|
-
|
|
128
134
|
/** Data coordinates in the standard right-to-left two-column traversal. */
|
|
129
135
|
export function dataModuleOrder(version) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
const v = versionInfo(version);
|
|
137
|
+
const fn = functionModules(version);
|
|
138
|
+
const out = [];
|
|
139
|
+
let cx = v.width - 2, cy = v.height - 6, dy = -1;
|
|
140
|
+
// The data path starts beside the lower-right format area, then snakes
|
|
141
|
+
// through each pair of columns between the one-module outer border.
|
|
142
|
+
while (cx > 0) {
|
|
143
|
+
for (const xx of [cx, cx - 1])
|
|
144
|
+
if (!fn.get(xx, cy))
|
|
145
|
+
out.push([xx, cy]);
|
|
146
|
+
if (dy < 0 && cy === 1) {
|
|
147
|
+
cx -= 2;
|
|
148
|
+
dy = 1;
|
|
149
|
+
}
|
|
150
|
+
else if (dy > 0 && cy === v.height - 2) {
|
|
151
|
+
cx -= 2;
|
|
152
|
+
dy = -1;
|
|
153
|
+
}
|
|
154
|
+
else
|
|
155
|
+
cy += dy;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
141
158
|
}
|
|
142
|
-
|
|
143
159
|
export function dataBitCapacity(version, ecc) { const b = versionInfo(version).blockLayout(ecc); return b.totalDataCodewords * 8; }
|
|
144
|
-
|
|
145
160
|
export function validateTables() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
const problems = [];
|
|
162
|
+
if (RMQR_SIZES.length !== 32)
|
|
163
|
+
problems.push(`expected 32 sizes, got ${RMQR_SIZES.length}`);
|
|
164
|
+
for (let i = 1; i <= 32; i++) {
|
|
165
|
+
const v = versionInfo(i);
|
|
166
|
+
const order = dataModuleOrder(i);
|
|
167
|
+
const expected = v.totalCodewords * 8 + v.remainderBits;
|
|
168
|
+
if (order.length !== expected)
|
|
169
|
+
problems.push(`${v.name}: data modules ${order.length}, expected ${expected}`);
|
|
170
|
+
for (const ecc of ['M', 'H']) {
|
|
171
|
+
const b = v.blockLayout(ecc);
|
|
172
|
+
if (b.totalCodewords !== v.totalCodewords)
|
|
173
|
+
problems.push(`${v.name}-${ecc}: block total mismatch`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return problems;
|
|
154
177
|
}
|
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
* Decoder for a sampled Aztec symbol.
|
|
33
|
+
*
|
|
34
|
+
* This module deliberately accepts only a square, module-aligned BitMatrix.
|
|
35
|
+
* Locating a bull's-eye in a photograph and perspective sampling are detector
|
|
36
|
+
* concerns. Keeping the two stages apart makes all bit order and ECC rules
|
|
37
|
+
* testable without image-processing noise.
|
|
38
|
+
*
|
|
39
|
+
* Contract with tables.js:
|
|
40
|
+
* - aztecSymbolForLayers(compact, layers) returns the nominal symbol data;
|
|
41
|
+
* - aztecWordSizeForLayers(layers) returns 6, 8, 10 or 12;
|
|
42
|
+
* - aztecFieldForLayers(layers) returns the matching binary Galois field;
|
|
43
|
+
* - aztecMatrixSize(compact, layers) returns the rendered square size.
|
|
44
|
+
*
|
|
45
|
+
* @module aztec/decoder
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { FormatError } from '../core/errors.js';
|
|
49
|
+
import { rsDecode } from '../core/reed-solomon.js';
|
|
50
|
+
import {
|
|
51
|
+
aztecLayer as aztecSymbolForLayers,
|
|
52
|
+
wordSizeForLayers as aztecWordSizeForLayers,
|
|
53
|
+
fieldForLayers as aztecFieldForLayers,
|
|
54
|
+
fieldForWordSize,
|
|
55
|
+
aztecSymbolSize as aztecMatrixSize,
|
|
56
|
+
} from './tables.js';
|
|
57
|
+
|
|
58
|
+
const UPPER = ['CTRL_PS', ' ', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'CTRL_LL', 'CTRL_ML', 'CTRL_DL', 'CTRL_BS'];
|
|
59
|
+
const LOWER = ['CTRL_PS', ' ', ...'abcdefghijklmnopqrstuvwxyz', 'CTRL_US', 'CTRL_ML', 'CTRL_DL', 'CTRL_BS'];
|
|
60
|
+
const MIXED = [
|
|
61
|
+
'CTRL_PS', ' ', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\b', '\t', '\n', '\x0b', '\f', '\r', '\x1b',
|
|
62
|
+
'\x1c', '\x1d', '\x1e', '\x1f', '@', '\\', '^', '_', '`', '|', '~', '\x7f', 'CTRL_LL', 'CTRL_UL', 'CTRL_PL', 'CTRL_BS',
|
|
63
|
+
];
|
|
64
|
+
const PUNCT = ['FLG(n)', '\r', '\r\n', '. ', ', ', ': ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', ']', '{', '}', 'CTRL_UL'];
|
|
65
|
+
const DIGIT = ['CTRL_PS', ' ', ...'0123456789', ',', '.', 'CTRL_UL'];
|
|
66
|
+
const TABLES = { UPPER, LOWER, MIXED, PUNCT, DIGIT };
|
|
67
|
+
|
|
68
|
+
/** @param {boolean[]} bits @param {number} offset @param {number} count */
|
|
69
|
+
function readBits(bits, offset, count) {
|
|
70
|
+
if (offset + count > bits.length) throw new FormatError('Aztec: truncated high-level stream');
|
|
71
|
+
let value = 0;
|
|
72
|
+
for (let i = 0; i < count; i++) value = (value << 1) | (bits[offset + i] ? 1 : 0);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @param {number} value @param {number} count @param {boolean[]} out */
|
|
77
|
+
function appendBits(value, count, out) {
|
|
78
|
+
for (let i = count - 1; i >= 0; i--) out.push(((value >>> i) & 1) !== 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Decode an Aztec high-level bit stream to its exact byte payload.
|
|
83
|
+
*
|
|
84
|
+
* Text tables contribute their ISO-8859-1 byte values; Binary Shift appends
|
|
85
|
+
* raw bytes. ECI markers are consumed but intentionally not emitted: callers
|
|
86
|
+
* receive the transported byte payload and may select their own charset.
|
|
87
|
+
*
|
|
88
|
+
* @param {boolean[]} bits
|
|
89
|
+
* @returns {Uint8Array}
|
|
90
|
+
*/
|
|
91
|
+
export function decodeHighLevelBits(bits) {
|
|
92
|
+
const output = [];
|
|
93
|
+
let latch = 'UPPER';
|
|
94
|
+
let shift = 'UPPER';
|
|
95
|
+
let offset = 0;
|
|
96
|
+
|
|
97
|
+
while (offset < bits.length) {
|
|
98
|
+
if (shift === 'BINARY') {
|
|
99
|
+
if (offset + 5 > bits.length) break; // legal trailing pad
|
|
100
|
+
let length = readBits(bits, offset, 5);
|
|
101
|
+
offset += 5;
|
|
102
|
+
if (length === 0) {
|
|
103
|
+
if (offset + 11 > bits.length) throw new FormatError('Aztec: truncated Binary Shift length');
|
|
104
|
+
length = readBits(bits, offset, 11) + 31;
|
|
105
|
+
offset += 11;
|
|
106
|
+
}
|
|
107
|
+
if (offset + length * 8 > bits.length) throw new FormatError('Aztec: truncated Binary Shift data');
|
|
108
|
+
for (let i = 0; i < length; i++) {
|
|
109
|
+
output.push(readBits(bits, offset, 8));
|
|
110
|
+
offset += 8;
|
|
111
|
+
}
|
|
112
|
+
shift = latch;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const size = shift === 'DIGIT' ? 4 : 5;
|
|
117
|
+
if (offset + size > bits.length) break; // trailing pad after unstuffing
|
|
118
|
+
const code = readBits(bits, offset, size);
|
|
119
|
+
offset += size;
|
|
120
|
+
const table = TABLES[shift];
|
|
121
|
+
const token = table[code];
|
|
122
|
+
if (token === undefined) throw new FormatError(`Aztec: invalid ${shift} code ${code}`);
|
|
123
|
+
|
|
124
|
+
if (token === 'FLG(n)') {
|
|
125
|
+
if (offset + 3 > bits.length) throw new FormatError('Aztec: truncated FLG(n)');
|
|
126
|
+
const count = readBits(bits, offset, 3);
|
|
127
|
+
offset += 3;
|
|
128
|
+
if (count === 0) output.push(0x1d); // FNC1 / GS
|
|
129
|
+
else if (count <= 6) {
|
|
130
|
+
// ECI assignment number, encoded as count decimal digits. It changes
|
|
131
|
+
// interpretation, not the wire bytes, so consume it without output.
|
|
132
|
+
for (let i = 0; i < count; i++) {
|
|
133
|
+
if (offset + 4 > bits.length) throw new FormatError('Aztec: truncated ECI');
|
|
134
|
+
const digit = readBits(bits, offset, 4);
|
|
135
|
+
offset += 4;
|
|
136
|
+
if (digit < 2 || digit > 11) throw new FormatError('Aztec: invalid ECI digit');
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
throw new FormatError(`Aztec: unsupported FLG(${count})`);
|
|
140
|
+
}
|
|
141
|
+
shift = latch;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (token.startsWith('CTRL_')) {
|
|
146
|
+
const targetCode = token.slice(5, -1);
|
|
147
|
+
const latchMode = token.endsWith('L');
|
|
148
|
+
const target = ({ P: 'PUNCT', L: 'LOWER', M: 'MIXED', D: 'DIGIT', U: 'UPPER', B: 'BINARY' })[targetCode];
|
|
149
|
+
if (!target) throw new FormatError(`Aztec: invalid control ${token}`);
|
|
150
|
+
shift = target;
|
|
151
|
+
if (latchMode) latch = shift;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < token.length; i++) output.push(token.charCodeAt(i));
|
|
156
|
+
shift = latch;
|
|
157
|
+
}
|
|
158
|
+
return Uint8Array.from(output);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @param {boolean} compact @param {number} layers */
|
|
162
|
+
function alignmentMap(compact, layers) {
|
|
163
|
+
const baseSize = (compact ? 11 : 14) + layers * 4;
|
|
164
|
+
if (compact) return Array.from({ length: baseSize }, (_, i) => i);
|
|
165
|
+
const size = aztecMatrixSize(layers, false);
|
|
166
|
+
const map = new Array(baseSize);
|
|
167
|
+
const baseCenter = baseSize >> 1;
|
|
168
|
+
const center = size >> 1;
|
|
169
|
+
for (let i = 0; i < baseCenter; i++) {
|
|
170
|
+
const offset = i + Math.floor(i / 15);
|
|
171
|
+
map[baseCenter - i - 1] = center - offset - 1;
|
|
172
|
+
map[baseCenter + i] = center + offset + 1;
|
|
173
|
+
}
|
|
174
|
+
return map;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Read the four sides of the parameter message. The order mirrors the
|
|
179
|
+
* clockwise write order and is independent of the data spiral.
|
|
180
|
+
*
|
|
181
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
182
|
+
* @param {boolean} compact
|
|
183
|
+
* @returns {boolean[]}
|
|
184
|
+
*/
|
|
185
|
+
function readModeBits(matrix, compact) {
|
|
186
|
+
const center = matrix.width >> 1;
|
|
187
|
+
const side = compact ? 7 : 10;
|
|
188
|
+
const offset = compact ? 5 : 7;
|
|
189
|
+
// Full symbols skip the reference grid line through the bull's-eye. This
|
|
190
|
+
// exact sequence is also used by drawModeMessage() in encoder.js.
|
|
191
|
+
const positions = Array.from(
|
|
192
|
+
{ length: side },
|
|
193
|
+
(_, i) => compact ? center - 3 + i : center - 5 + i + Math.floor(i / 5),
|
|
194
|
+
);
|
|
195
|
+
const bits = [];
|
|
196
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(positions[i], center - offset));
|
|
197
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(center + offset, positions[i]));
|
|
198
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(positions[side - 1 - i], center + offset));
|
|
199
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(center - offset, positions[side - 1 - i]));
|
|
200
|
+
return bits;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @param {boolean[]} bits @param {boolean} compact */
|
|
204
|
+
function decodeModeMessage(bits, compact) {
|
|
205
|
+
const total = compact ? 7 : 10;
|
|
206
|
+
const dataWords = compact ? 2 : 4;
|
|
207
|
+
const words = new Array(total);
|
|
208
|
+
for (let i = 0; i < total; i++) words[i] = readBits(bits, i * 4, 4);
|
|
209
|
+
const corrections = rsDecode(words, total - dataWords, fieldForWordSize(4), 1);
|
|
210
|
+
let data = 0;
|
|
211
|
+
for (let i = 0; i < dataWords; i++) data = (data << 4) | words[i];
|
|
212
|
+
const layers = compact ? (data >>> 6) + 1 : (data >>> 11) + 1;
|
|
213
|
+
const dataCodewords = compact ? (data & 0x3f) + 1 : (data & 0x7ff) + 1;
|
|
214
|
+
return { layers, dataCodewords, corrections };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @param {boolean} compact @param {number} layers */
|
|
218
|
+
function totalBitsInLayers(compact, layers) {
|
|
219
|
+
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract raw, stuffed codeword bits in logical ring order.
|
|
224
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
225
|
+
* @param {boolean} compact @param {number} layers
|
|
226
|
+
* @returns {boolean[]}
|
|
227
|
+
*/
|
|
228
|
+
function extractBits(matrix, compact, layers) {
|
|
229
|
+
const baseSize = (compact ? 11 : 14) + layers * 4;
|
|
230
|
+
const map = alignmentMap(compact, layers);
|
|
231
|
+
const raw = new Array(totalBitsInLayers(compact, layers));
|
|
232
|
+
let offset = 0;
|
|
233
|
+
for (let layer = 0; layer < layers; layer++) {
|
|
234
|
+
const rowSize = (layers - layer) * 4 + (compact ? 9 : 12);
|
|
235
|
+
for (let j = 0; j < rowSize; j++) {
|
|
236
|
+
const col = j * 2;
|
|
237
|
+
for (let k = 0; k < 2; k++) {
|
|
238
|
+
raw[offset + col + k] = matrix.get(map[layer * 2 + k], map[layer * 2 + j]);
|
|
239
|
+
raw[offset + rowSize * 2 + col + k] = matrix.get(map[layer * 2 + j], map[baseSize - 1 - layer * 2 - k]);
|
|
240
|
+
raw[offset + rowSize * 4 + col + k] = matrix.get(map[baseSize - 1 - layer * 2 - k], map[baseSize - 1 - layer * 2 - j]);
|
|
241
|
+
raw[offset + rowSize * 6 + col + k] = matrix.get(map[baseSize - 1 - layer * 2 - j], map[layer * 2 + k]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
offset += rowSize * 8;
|
|
245
|
+
}
|
|
246
|
+
return raw;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** @param {boolean[]} raw @param {number} layers @param {number} dataCodewords */
|
|
250
|
+
function correctAndUnstuff(raw, layers, dataCodewords) {
|
|
251
|
+
const wordSize = aztecWordSizeForLayers(layers);
|
|
252
|
+
const totalWords = Math.floor(raw.length / wordSize);
|
|
253
|
+
if (dataCodewords <= 0 || dataCodewords > totalWords) throw new FormatError('Aztec: invalid data word count');
|
|
254
|
+
const start = raw.length % wordSize;
|
|
255
|
+
const words = new Array(totalWords);
|
|
256
|
+
for (let i = 0; i < totalWords; i++) words[i] = readBits(raw, start + i * wordSize, wordSize);
|
|
257
|
+
const corrections = rsDecode(words, totalWords - dataCodewords, aztecFieldForLayers(layers), 1);
|
|
258
|
+
const mask = (1 << wordSize) - 1;
|
|
259
|
+
const corrected = [];
|
|
260
|
+
for (let i = 0; i < dataCodewords; i++) {
|
|
261
|
+
const word = words[i];
|
|
262
|
+
if (word === 0 || word === mask) throw new FormatError('Aztec: invalid stuffed codeword');
|
|
263
|
+
if (word === 1 || word === mask - 1) {
|
|
264
|
+
for (let j = 0; j < wordSize - 1; j++) corrected.push(word === mask - 1);
|
|
265
|
+
} else {
|
|
266
|
+
appendBits(word, wordSize, corrected);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { bits: corrected, corrections };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @param {Uint8Array} bytes */
|
|
273
|
+
function bytesToText(bytes) {
|
|
274
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
|
|
275
|
+
catch { return new TextDecoder('latin1').decode(bytes); }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Decode a square Aztec symbol with one bit per module and no quiet zone.
|
|
280
|
+
* The matrix must already be oriented with the mode message at the top.
|
|
281
|
+
*
|
|
282
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
283
|
+
* @returns {{text: string, bytes: Uint8Array, compact: boolean, layers: number, corrections: number, eccPercent: number}}
|
|
284
|
+
*/
|
|
285
|
+
export function decodeAztec(matrix) {
|
|
286
|
+
if (!matrix || matrix.width !== matrix.height) throw new FormatError('Aztec: expected a square BitMatrix');
|
|
287
|
+
let compact;
|
|
288
|
+
let mode;
|
|
289
|
+
// Compact and full dimensions are disjoint; trying both also makes malformed
|
|
290
|
+
// candidate handling deterministic for the future image detector.
|
|
291
|
+
for (const candidate of [true, false]) {
|
|
292
|
+
try {
|
|
293
|
+
const value = decodeModeMessage(readModeBits(matrix, candidate), candidate);
|
|
294
|
+
if (value.layers < 1 || value.layers > (candidate ? 4 : 32)) continue;
|
|
295
|
+
if (aztecMatrixSize(value.layers, candidate) !== matrix.width) continue;
|
|
296
|
+
compact = candidate;
|
|
297
|
+
mode = value;
|
|
298
|
+
break;
|
|
299
|
+
} catch { /* Try the other family. */ }
|
|
300
|
+
}
|
|
301
|
+
if (compact === undefined || !mode) throw new FormatError('Aztec: invalid mode message or dimensions');
|
|
302
|
+
// Ensure the declared layer data agrees with the table module, so a future
|
|
303
|
+
// tables refactor cannot silently make decoder capacity calculations stale.
|
|
304
|
+
aztecSymbolForLayers(mode.layers, compact);
|
|
305
|
+
const raw = extractBits(matrix, compact, mode.layers);
|
|
306
|
+
const payload = correctAndUnstuff(raw, mode.layers, mode.dataCodewords);
|
|
307
|
+
const bytes = decodeHighLevelBits(payload.bits);
|
|
308
|
+
const totalWords = Math.floor(raw.length / aztecWordSizeForLayers(mode.layers));
|
|
309
|
+
return {
|
|
310
|
+
text: bytesToText(bytes),
|
|
311
|
+
bytes,
|
|
312
|
+
compact,
|
|
313
|
+
layers: mode.layers,
|
|
314
|
+
corrections: mode.corrections + payload.corrections,
|
|
315
|
+
eccPercent: Math.round(((totalWords - mode.dataCodewords) * 100) / totalWords),
|
|
316
|
+
};
|
|
317
|
+
}
|