@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
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
|
+
* Micro QR decoder for an already sampled M1-M4 module matrix.
|
|
33
|
+
*
|
|
34
|
+
* @module microqr/decoder
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { ChecksumError, FormatError } from '../core/errors.js';
|
|
38
|
+
import { GF256_QR } from '../core/galois-field.js';
|
|
39
|
+
import { rsDecode } from '../core/reed-solomon.js';
|
|
40
|
+
import {
|
|
41
|
+
microQrBlockLayout,
|
|
42
|
+
microQrDataModuleOrder,
|
|
43
|
+
microQrDecodeFormatInfo,
|
|
44
|
+
microQrFormatInfoPositions,
|
|
45
|
+
microQrMaskBit,
|
|
46
|
+
} from './tables.js';
|
|
47
|
+
|
|
48
|
+
const ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
49
|
+
const MODE_NAMES = ['numeric', 'alphanumeric', 'byte', 'kanji'];
|
|
50
|
+
const COUNT_BITS = {
|
|
51
|
+
numeric: [0, 3, 4, 5, 6],
|
|
52
|
+
alphanumeric: [0, 0, 3, 4, 5],
|
|
53
|
+
byte: [0, 0, 0, 4, 5],
|
|
54
|
+
kanji: [0, 0, 0, 3, 4],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
class LimitedBitReader {
|
|
58
|
+
constructor(bytes, limit) {
|
|
59
|
+
this.bytes = bytes;
|
|
60
|
+
this.limit = limit;
|
|
61
|
+
this.offset = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
available() { return this.limit - this.offset; }
|
|
65
|
+
|
|
66
|
+
read(count) {
|
|
67
|
+
if (!Number.isInteger(count) || count < 1 || count > 32 || count > this.available()) {
|
|
68
|
+
throw new FormatError(`Micro QR: needed ${count} bits, ${Math.max(0, this.available())} remain`);
|
|
69
|
+
}
|
|
70
|
+
let value = 0;
|
|
71
|
+
for (let i = 0; i < count; i++, this.offset++) {
|
|
72
|
+
value = (value << 1) | ((this.bytes[this.offset >>> 3] >>> (7 - (this.offset & 7))) & 1);
|
|
73
|
+
}
|
|
74
|
+
return value >>> 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function moduleAt(matrix, x, y, mirrored) {
|
|
79
|
+
return mirrored ? matrix.get(y, x) : matrix.get(x, y);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readFormat(matrix, expectedVersion, mirrored) {
|
|
83
|
+
let bits = 0;
|
|
84
|
+
const positions = microQrFormatInfoPositions(matrix.width);
|
|
85
|
+
for (let i = 0; i < positions.length; i++) {
|
|
86
|
+
const [x, y] = positions[i];
|
|
87
|
+
if (moduleAt(matrix, x, y, mirrored)) bits |= 1 << i;
|
|
88
|
+
}
|
|
89
|
+
const format = microQrDecodeFormatInfo(bits);
|
|
90
|
+
if (!format) throw new FormatError('Micro QR: format information is unreadable');
|
|
91
|
+
if (format.version !== expectedVersion) {
|
|
92
|
+
throw new FormatError(
|
|
93
|
+
`Micro QR: format identifies ${format.version}, but the matrix dimension identifies ${expectedVersion}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return format;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readCodewords(matrix, layout, mask, mirrored) {
|
|
100
|
+
const order = microQrDataModuleOrder(layout.version);
|
|
101
|
+
const data = new Array(layout.dataCodewords).fill(0);
|
|
102
|
+
const ecc = new Array(layout.eccCodewords).fill(0);
|
|
103
|
+
let streamOffset = 0;
|
|
104
|
+
|
|
105
|
+
const readBit = () => {
|
|
106
|
+
const x = order[streamOffset * 2];
|
|
107
|
+
const y = order[streamOffset * 2 + 1];
|
|
108
|
+
if (x === undefined || y === undefined) throw new FormatError('Micro QR: encoding region is truncated');
|
|
109
|
+
streamOffset++;
|
|
110
|
+
return moduleAt(matrix, x, y, mirrored) !== microQrMaskBit(mask, x, y) ? 1 : 0;
|
|
111
|
+
};
|
|
112
|
+
const readInto = (target, index, count, highBit = 7) => {
|
|
113
|
+
for (let bit = highBit; bit > highBit - count; bit--) target[index] |= readBit() << bit;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const fullData = layout.shortDataCodewordBits === 4 ? layout.dataCodewords - 1 : layout.dataCodewords;
|
|
117
|
+
for (let i = 0; i < fullData; i++) readInto(data, i, 8);
|
|
118
|
+
if (layout.shortDataCodewordBits === 4) readInto(data, data.length - 1, 4);
|
|
119
|
+
for (let i = 0; i < ecc.length; i++) readInto(ecc, i, 8);
|
|
120
|
+
|
|
121
|
+
if (streamOffset !== order.length / 2) {
|
|
122
|
+
throw new FormatError(`Micro QR: read ${streamOffset} of ${order.length / 2} encoding modules`);
|
|
123
|
+
}
|
|
124
|
+
return data.concat(ecc);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function correctCodewords(received, layout) {
|
|
128
|
+
const corrections = rsDecode(received, layout.eccCodewords, GF256_QR, 0);
|
|
129
|
+
if (layout.version === 'M1' && corrections !== 0) {
|
|
130
|
+
throw new ChecksumError('Micro QR: M1 provides error detection only');
|
|
131
|
+
}
|
|
132
|
+
return { data: Uint8Array.from(received.slice(0, layout.dataCodewords)), corrections };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function decodeKanjiValue(value) {
|
|
136
|
+
const combined = (Math.floor(value / 0xc0) << 8) | (value % 0xc0);
|
|
137
|
+
const sjis = combined + (combined < 0x1f00 ? 0x8140 : 0xc140);
|
|
138
|
+
const bytes = Uint8Array.of(sjis >>> 8, sjis & 0xff);
|
|
139
|
+
try {
|
|
140
|
+
return new TextDecoder('shift_jis', { fatal: true }).decode(bytes);
|
|
141
|
+
} catch {
|
|
142
|
+
throw new FormatError(`Micro QR: invalid Kanji value ${value}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parsePayload(data, version, dataBits) {
|
|
147
|
+
const reader = new LimitedBitReader(data, dataBits);
|
|
148
|
+
const modeValue = version === 1 ? 0 : reader.read(version - 1);
|
|
149
|
+
if (modeValue > 3 || (version === 2 && modeValue > 1)) {
|
|
150
|
+
throw new FormatError(`Micro QR: mode indicator ${modeValue} is unavailable in M${version}`);
|
|
151
|
+
}
|
|
152
|
+
const mode = MODE_NAMES[modeValue];
|
|
153
|
+
const countWidth = COUNT_BITS[mode][version];
|
|
154
|
+
if (!countWidth) throw new FormatError(`Micro QR: ${mode} mode is unavailable in M${version}`);
|
|
155
|
+
const count = reader.read(countWidth);
|
|
156
|
+
if (count === 0) throw new FormatError('Micro QR: zero-length data segment');
|
|
157
|
+
|
|
158
|
+
let text = '';
|
|
159
|
+
const rawBytes = [];
|
|
160
|
+
if (mode === 'numeric') {
|
|
161
|
+
let remaining = count;
|
|
162
|
+
while (remaining >= 3) {
|
|
163
|
+
const value = reader.read(10);
|
|
164
|
+
if (value >= 1000) throw new FormatError(`Micro QR: invalid numeric triplet ${value}`);
|
|
165
|
+
text += String(value).padStart(3, '0');
|
|
166
|
+
remaining -= 3;
|
|
167
|
+
}
|
|
168
|
+
if (remaining === 2) {
|
|
169
|
+
const value = reader.read(7);
|
|
170
|
+
if (value >= 100) throw new FormatError(`Micro QR: invalid numeric pair ${value}`);
|
|
171
|
+
text += String(value).padStart(2, '0');
|
|
172
|
+
} else if (remaining === 1) {
|
|
173
|
+
const value = reader.read(4);
|
|
174
|
+
if (value >= 10) throw new FormatError(`Micro QR: invalid numeric digit ${value}`);
|
|
175
|
+
text += String(value);
|
|
176
|
+
}
|
|
177
|
+
} else if (mode === 'alphanumeric') {
|
|
178
|
+
let remaining = count;
|
|
179
|
+
while (remaining >= 2) {
|
|
180
|
+
const value = reader.read(11);
|
|
181
|
+
if (value >= 45 * 45) throw new FormatError(`Micro QR: invalid alphanumeric pair ${value}`);
|
|
182
|
+
text += ALPHANUMERIC[Math.floor(value / 45)] + ALPHANUMERIC[value % 45];
|
|
183
|
+
remaining -= 2;
|
|
184
|
+
}
|
|
185
|
+
if (remaining === 1) {
|
|
186
|
+
const value = reader.read(6);
|
|
187
|
+
if (value >= 45) throw new FormatError(`Micro QR: invalid alphanumeric value ${value}`);
|
|
188
|
+
text += ALPHANUMERIC[value];
|
|
189
|
+
}
|
|
190
|
+
} else if (mode === 'byte') {
|
|
191
|
+
for (let i = 0; i < count; i++) {
|
|
192
|
+
const value = reader.read(8);
|
|
193
|
+
rawBytes.push(value);
|
|
194
|
+
text += String.fromCharCode(value);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
for (let i = 0; i < count; i++) text += decodeKanjiValue(reader.read(13));
|
|
198
|
+
}
|
|
199
|
+
return { text, bytes: Uint8Array.from(rawBytes), mode };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function decodeOrientation(matrix, expectedVersion, mirrored) {
|
|
203
|
+
const format = readFormat(matrix, expectedVersion, mirrored);
|
|
204
|
+
const layout = microQrBlockLayout(format.version, format.ecc);
|
|
205
|
+
const received = readCodewords(matrix, layout, format.mask, mirrored);
|
|
206
|
+
const { data, corrections } = correctCodewords(received, layout);
|
|
207
|
+
const payload = parsePayload(data, Number(format.version.slice(1)), layout.dataBits);
|
|
208
|
+
return {
|
|
209
|
+
text: payload.text,
|
|
210
|
+
bytes: payload.bytes,
|
|
211
|
+
mode: payload.mode,
|
|
212
|
+
version: format.version,
|
|
213
|
+
ecc: format.ecc,
|
|
214
|
+
mask: format.mask,
|
|
215
|
+
corrections,
|
|
216
|
+
formatCorrections: format.correctedBits,
|
|
217
|
+
mirrored,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Decode a sampled Micro QR Code symbol without its quiet zone. */
|
|
222
|
+
export function decodeMicroQR(matrix) {
|
|
223
|
+
if (!matrix || !Number.isInteger(matrix.width) || typeof matrix.get !== 'function') {
|
|
224
|
+
throw new FormatError('Micro QR: no matrix supplied');
|
|
225
|
+
}
|
|
226
|
+
if (matrix.height !== matrix.width) {
|
|
227
|
+
throw new FormatError(`Micro QR: symbol must be square, got ${matrix.width}x${matrix.height}`);
|
|
228
|
+
}
|
|
229
|
+
const version = (matrix.width - 9) / 2;
|
|
230
|
+
if (!Number.isInteger(version) || version < 1 || version > 4) {
|
|
231
|
+
throw new FormatError(`Micro QR: ${matrix.width} modules is not a valid M1-M4 symbol size`);
|
|
232
|
+
}
|
|
233
|
+
const expectedVersion = `M${version}`;
|
|
234
|
+
try {
|
|
235
|
+
return decodeOrientation(matrix, expectedVersion, false);
|
|
236
|
+
} catch (primaryError) {
|
|
237
|
+
try {
|
|
238
|
+
return decodeOrientation(matrix, expectedVersion, true);
|
|
239
|
+
} catch {
|
|
240
|
+
throw primaryError;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export { ChecksumError, FormatError };
|