@sythos/js_barcode_universal 1.5.8 → 1.5.10
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 +61 -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,298 @@
|
|
|
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
|
+
/** PDF417 high-level text, byte and numeric compaction. @module pdf417/compaction */
|
|
32
|
+
|
|
33
|
+
import { EncodeError, FormatError } from '../core/errors.js';
|
|
34
|
+
|
|
35
|
+
const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
|
|
36
|
+
const LOWER = 'abcdefghijklmnopqrstuvwxyz ';
|
|
37
|
+
const MIXED = '0123456789&\r\t,:#-.$/+%*=^';
|
|
38
|
+
const PUNCT = ';<>@[\\]_`~!\r\t,:\n-.$/"|*()?{}\'';
|
|
39
|
+
|
|
40
|
+
function packBase30(values) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (let i = 0; i < values.length; i += 2) out.push(values[i] * 30 + (i + 1 < values.length ? values[i + 1] : 29));
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Compact a value using the PDF417 Text Compaction alphabet. */
|
|
47
|
+
export function compactPdf417Text(value) {
|
|
48
|
+
if (typeof value !== 'string') throw new EncodeError('PDF417 text: value must be a string');
|
|
49
|
+
const values = [];
|
|
50
|
+
let submode = 'alpha';
|
|
51
|
+
for (const character of value) {
|
|
52
|
+
const inAlpha = ALPHA.indexOf(character), inLower = LOWER.indexOf(character);
|
|
53
|
+
const inMixed = MIXED.indexOf(character), inPunct = PUNCT.indexOf(character);
|
|
54
|
+
if (submode === 'alpha') {
|
|
55
|
+
if (inAlpha >= 0) values.push(inAlpha);
|
|
56
|
+
else if (inLower >= 0) { values.push(27, inLower); submode = 'lower'; }
|
|
57
|
+
else if (inMixed >= 0 || character === ' ') { values.push(28, character === ' ' ? 26 : inMixed); submode = 'mixed'; }
|
|
58
|
+
else if (inPunct >= 0) values.push(29, inPunct);
|
|
59
|
+
else throw new EncodeError(`PDF417 text: unsupported character ${JSON.stringify(character)}`);
|
|
60
|
+
} else if (submode === 'lower') {
|
|
61
|
+
if (inLower >= 0) values.push(inLower);
|
|
62
|
+
else if (inAlpha >= 0) values.push(27, inAlpha);
|
|
63
|
+
else if (inMixed >= 0 || character === ' ') { values.push(28, character === ' ' ? 26 : inMixed); submode = 'mixed'; }
|
|
64
|
+
else if (inPunct >= 0) values.push(29, inPunct);
|
|
65
|
+
else throw new EncodeError(`PDF417 text: unsupported character ${JSON.stringify(character)}`);
|
|
66
|
+
} else {
|
|
67
|
+
if (inMixed >= 0) values.push(inMixed);
|
|
68
|
+
else if (character === ' ') values.push(26);
|
|
69
|
+
else if (inAlpha >= 0) { values.push(28); submode = 'alpha'; values.push(inAlpha); }
|
|
70
|
+
else if (inLower >= 0) { values.push(27); submode = 'lower'; values.push(inLower); }
|
|
71
|
+
else if (inPunct >= 0) values.push(29, inPunct);
|
|
72
|
+
else throw new EncodeError(`PDF417 text: unsupported character ${JSON.stringify(character)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return packBase30(values);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function asBytes(value) {
|
|
79
|
+
if (value instanceof Uint8Array) return value;
|
|
80
|
+
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
81
|
+
if (typeof value === 'string') return new TextEncoder().encode(value);
|
|
82
|
+
throw new EncodeError('PDF417 byte: value must be text or a byte array');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Compact bytes using latch 924 for exact six-byte blocks and 901 otherwise. */
|
|
86
|
+
export function compactPdf417Bytes(value) {
|
|
87
|
+
const bytes = asBytes(value);
|
|
88
|
+
const utf8 = typeof value === 'string' && /[^\x00-\x7f]/.test(value);
|
|
89
|
+
const out = utf8 ? [927, 26] : [];
|
|
90
|
+
out.push(bytes.length > 0 && bytes.length % 6 === 0 ? 924 : 901);
|
|
91
|
+
let at = 0;
|
|
92
|
+
while (at + 6 <= bytes.length) {
|
|
93
|
+
let number = 0n;
|
|
94
|
+
for (let i = 0; i < 6; i++) number = (number << 8n) | BigInt(bytes[at++]);
|
|
95
|
+
const group = new Array(5);
|
|
96
|
+
for (let i = 4; i >= 0; i--) { group[i] = Number(number % 900n); number /= 900n; }
|
|
97
|
+
out.push(...group);
|
|
98
|
+
}
|
|
99
|
+
while (at < bytes.length) out.push(bytes[at++]);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Compact decimal digits using latch 902 and groups of at most 44 digits. */
|
|
104
|
+
export function compactPdf417Numeric(value) {
|
|
105
|
+
if (typeof value !== 'string' || !/^\d+$/.test(value)) throw new EncodeError('PDF417 numeric: value must contain decimal digits only');
|
|
106
|
+
const out = [902];
|
|
107
|
+
for (let at = 0; at < value.length; at += 44) {
|
|
108
|
+
let number = BigInt(`1${value.slice(at, at + 44)}`);
|
|
109
|
+
const group = [];
|
|
110
|
+
do { group.unshift(Number(number % 900n)); number /= 900n; } while (number > 0n);
|
|
111
|
+
out.push(...group);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Compact a single value, selecting text, numeric or byte mode. */
|
|
117
|
+
export function compactPdf417(value, options = {}) {
|
|
118
|
+
const mode = options.compaction ?? 'auto';
|
|
119
|
+
if (mode === 'text') return compactPdf417Text(value);
|
|
120
|
+
if (mode === 'byte') return compactPdf417Bytes(value);
|
|
121
|
+
if (mode === 'numeric') return compactPdf417Numeric(value);
|
|
122
|
+
if (mode !== 'auto') throw new EncodeError(`PDF417: unsupported compaction mode ${JSON.stringify(mode)}`);
|
|
123
|
+
if (typeof value === 'string' && /^\d{13,}$/.test(value)) return compactPdf417Numeric(value);
|
|
124
|
+
if (typeof value === 'string') {
|
|
125
|
+
try { return compactPdf417Text(value); } catch (error) { if (!(error instanceof EncodeError)) throw error; }
|
|
126
|
+
}
|
|
127
|
+
return compactPdf417Bytes(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertCodeword(codeword) {
|
|
131
|
+
if (!Number.isInteger(codeword) || codeword < 0 || codeword > 928) throw new FormatError('PDF417: codeword is outside 0..928');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function decodeUtf8(bytes, eci) {
|
|
135
|
+
if (eci === 3) return Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
|
|
136
|
+
if (eci !== 26) throw new FormatError(`PDF417 ECI: unsupported assignment number ${eci}`);
|
|
137
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(new Uint8Array(bytes)); }
|
|
138
|
+
catch { throw new FormatError('PDF417 byte: invalid UTF-8 sequence'); }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function decodeByteSegment(codewords, at, eci, sixOnly = false) {
|
|
142
|
+
const values = [];
|
|
143
|
+
while (at < codewords.length && codewords[at] < 900) values.push(codewords[at++]);
|
|
144
|
+
if (sixOnly && values.length % 5) throw new FormatError('PDF417 byte: 924 segment must contain complete six-byte groups');
|
|
145
|
+
const bytes = [];
|
|
146
|
+
// In 901 mode an encoder can use five terminal literal codewords. The
|
|
147
|
+
// unambiguous groups are therefore the ones followed by another codeword;
|
|
148
|
+
// 924 is available whenever a segment consists exclusively of six-byte groups.
|
|
149
|
+
const groupCount = sixOnly ? values.length / 5 : Math.max(0, Math.floor((values.length - 1) / 5));
|
|
150
|
+
for (let groupAt = 0; groupAt < groupCount * 5; groupAt += 5) {
|
|
151
|
+
let number = 0n;
|
|
152
|
+
for (let i = 0; i < 5; i++) number = number * 900n + BigInt(values[groupAt + i]);
|
|
153
|
+
const group = new Uint8Array(6);
|
|
154
|
+
for (let i = 5; i >= 0; i--) { group[i] = Number(number & 255n); number >>= 8n; }
|
|
155
|
+
if (number !== 0n) throw new FormatError('PDF417 byte: base-900 group exceeds six bytes');
|
|
156
|
+
bytes.push(...group);
|
|
157
|
+
}
|
|
158
|
+
for (let i = groupCount * 5; i < values.length; i++) {
|
|
159
|
+
if (values[i] > 255) throw new FormatError('PDF417 byte: literal tail is outside 0..255');
|
|
160
|
+
bytes.push(values[i]);
|
|
161
|
+
}
|
|
162
|
+
return { at, text: decodeUtf8(bytes, eci), bytes: Uint8Array.from(bytes) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function decodeTextSegment(codewords, at, eci) {
|
|
166
|
+
let mode = 'alpha';
|
|
167
|
+
let output = '';
|
|
168
|
+
let shift = null;
|
|
169
|
+
let shiftedBytes = [];
|
|
170
|
+
const bytes = [];
|
|
171
|
+
const flushShiftedBytes = () => {
|
|
172
|
+
if (shiftedBytes.length) {
|
|
173
|
+
output += decodeUtf8(shiftedBytes, eci);
|
|
174
|
+
bytes.push(...shiftedBytes);
|
|
175
|
+
shiftedBytes = [];
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const emit = (alphabet, value) => {
|
|
179
|
+
if (value < 0 || value >= alphabet.length) throw new FormatError('PDF417 text: invalid submode value');
|
|
180
|
+
output += alphabet[value];
|
|
181
|
+
};
|
|
182
|
+
const process = (value) => {
|
|
183
|
+
if (shift) { emit(shift === 'alpha' ? ALPHA : PUNCT, value); shift = null; return; }
|
|
184
|
+
if (mode === 'alpha') {
|
|
185
|
+
if (value < 26) emit(ALPHA, value);
|
|
186
|
+
else if (value === 26) output += ' ';
|
|
187
|
+
else if (value === 27) mode = 'lower';
|
|
188
|
+
else if (value === 28) mode = 'mixed';
|
|
189
|
+
else if (value === 29) shift = 'punct';
|
|
190
|
+
} else if (mode === 'lower') {
|
|
191
|
+
if (value < 26) emit(LOWER, value);
|
|
192
|
+
else if (value === 26) output += ' ';
|
|
193
|
+
else if (value === 27) shift = 'alpha';
|
|
194
|
+
else if (value === 28) mode = 'mixed';
|
|
195
|
+
else if (value === 29) shift = 'punct';
|
|
196
|
+
} else if (mode === 'mixed') {
|
|
197
|
+
if (value < 25) emit(MIXED, value);
|
|
198
|
+
else if (value === 25) mode = 'punct';
|
|
199
|
+
else if (value === 26) output += ' ';
|
|
200
|
+
else if (value === 27) mode = 'lower';
|
|
201
|
+
else if (value === 28) mode = 'alpha';
|
|
202
|
+
else if (value === 29) shift = 'punct';
|
|
203
|
+
} else {
|
|
204
|
+
if (value < 29) emit(PUNCT, value);
|
|
205
|
+
else if (value === 29) mode = 'alpha';
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
while (at < codewords.length) {
|
|
209
|
+
const codeword = codewords[at];
|
|
210
|
+
if (codeword >= 900 && codeword !== 913) break;
|
|
211
|
+
at++;
|
|
212
|
+
if (codeword === 913) {
|
|
213
|
+
if (at >= codewords.length || codewords[at] > 255) throw new FormatError('PDF417 text: invalid byte shift');
|
|
214
|
+
shiftedBytes.push(codewords[at++]);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
flushShiftedBytes();
|
|
218
|
+
process(Math.floor(codeword / 30));
|
|
219
|
+
process(codeword % 30);
|
|
220
|
+
}
|
|
221
|
+
flushShiftedBytes();
|
|
222
|
+
return { at, text: output, bytes: Uint8Array.from(bytes) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function decodeNumericSegment(codewords, at) {
|
|
226
|
+
let output = '';
|
|
227
|
+
while (at < codewords.length && codewords[at] < 900) {
|
|
228
|
+
const end = Math.min(at + 15, codewords.length);
|
|
229
|
+
let number = 0n;
|
|
230
|
+
for (; at < end && codewords[at] < 900; at++) number = number * 900n + BigInt(codewords[at]);
|
|
231
|
+
const decimal = number.toString();
|
|
232
|
+
if (!decimal.startsWith('1')) throw new FormatError('PDF417 numeric: missing leading sentinel');
|
|
233
|
+
output += decimal.slice(1);
|
|
234
|
+
}
|
|
235
|
+
return { at, text: output, bytes: new Uint8Array(0) };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Decode PDF417 compaction while preserving raw Byte Compaction and byte-shift
|
|
240
|
+
* payloads. Text and Numeric Compaction do not manufacture bytes: their text
|
|
241
|
+
* is available on each segment, while `bytes` contains only octets carried by
|
|
242
|
+
* modes that encode octets explicitly.
|
|
243
|
+
*/
|
|
244
|
+
export function decodePdf417CompactionDetailed(codewords) {
|
|
245
|
+
if (!Array.isArray(codewords) && !ArrayBuffer.isView(codewords)) throw new FormatError('PDF417: codewords must be an array');
|
|
246
|
+
for (const codeword of codewords) assertCodeword(codeword);
|
|
247
|
+
let at = 0;
|
|
248
|
+
// ISO/IEC 8859-1 is the PDF417 default; UTF-8 is selected explicitly with ECI 26.
|
|
249
|
+
let eci = 3;
|
|
250
|
+
let output = '';
|
|
251
|
+
const bytes = [];
|
|
252
|
+
const segments = [];
|
|
253
|
+
while (at < codewords.length) {
|
|
254
|
+
const codeword = codewords[at];
|
|
255
|
+
if (codeword < 900 || codeword === 900 || codeword === 913) {
|
|
256
|
+
const start = at;
|
|
257
|
+
const latch = codeword === 900 ? codeword : null;
|
|
258
|
+
if (latch !== null) at++;
|
|
259
|
+
const segment = decodeTextSegment(codewords, at, eci);
|
|
260
|
+
at = segment.at;
|
|
261
|
+
output += segment.text;
|
|
262
|
+
bytes.push(...segment.bytes);
|
|
263
|
+
if (segment.text.length || segment.bytes.length) segments.push({ mode: 'text', text: segment.text, bytes: segment.bytes, eci, latch, codewordStart: start, codewordEnd: at });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const start = at;
|
|
267
|
+
at++;
|
|
268
|
+
if (codeword === 901) {
|
|
269
|
+
const segment = decodeByteSegment(codewords, at, eci);
|
|
270
|
+
at = segment.at;
|
|
271
|
+
output += segment.text;
|
|
272
|
+
bytes.push(...segment.bytes);
|
|
273
|
+
segments.push({ mode: 'byte', text: segment.text, bytes: segment.bytes, eci, latch: codeword, codewordStart: start, codewordEnd: at });
|
|
274
|
+
} else if (codeword === 924) {
|
|
275
|
+
const segment = decodeByteSegment(codewords, at, eci, true);
|
|
276
|
+
at = segment.at;
|
|
277
|
+
output += segment.text;
|
|
278
|
+
bytes.push(...segment.bytes);
|
|
279
|
+
segments.push({ mode: 'byte', text: segment.text, bytes: segment.bytes, eci, latch: codeword, codewordStart: start, codewordEnd: at });
|
|
280
|
+
} else if (codeword === 902) {
|
|
281
|
+
const segment = decodeNumericSegment(codewords, at);
|
|
282
|
+
at = segment.at;
|
|
283
|
+
output += segment.text;
|
|
284
|
+
segments.push({ mode: 'numeric', text: segment.text, bytes: segment.bytes, eci, latch: codeword, codewordStart: start, codewordEnd: at });
|
|
285
|
+
} else if (codeword === 927) {
|
|
286
|
+
if (at >= codewords.length || codewords[at] > 899) throw new FormatError('PDF417 ECI: missing assignment number');
|
|
287
|
+
eci = codewords[at++];
|
|
288
|
+
} else {
|
|
289
|
+
throw new FormatError(`PDF417: unsupported compaction codeword ${codeword}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { text: output, bytes: Uint8Array.from(bytes), segments };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Decode PDF417 Text, Byte, Numeric and UTF-8 ECI compaction segments in source order. */
|
|
296
|
+
export function decodePdf417Compaction(codewords) {
|
|
297
|
+
return decodePdf417CompactionDetailed(codewords).text;
|
|
298
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import { FormatError } from '../core/errors.js';
|
|
32
|
+
import { decodePdf417CompactionDetailed } from './compaction.js';
|
|
33
|
+
import { pdf417CorrectErrors, pdf417EccLength } from './error-correction.js';
|
|
34
|
+
import { pdf417CodewordForPattern } from './tables.js';
|
|
35
|
+
|
|
36
|
+
const START = '11111111010101000';
|
|
37
|
+
const STOP = '111111101000101001';
|
|
38
|
+
function bits(matrix, y, x, width) { let value = 0; for (let i = 0; i < width; i++) value = (value << 1) | (matrix.get(x + i, y) ? 1 : 0); return value; }
|
|
39
|
+
function indicators(row, rows, cols, level) { const group = Math.floor(row / 3), y = Math.floor((rows - 1) / 3), z = level * 3 + (rows - 1) % 3, v = cols - 1; return row % 3 === 0 ? [30 * group + y, 30 * group + v] : row % 3 === 1 ? [30 * group + z, 30 * group + y] : [30 * group + v, 30 * group + z]; }
|
|
40
|
+
|
|
41
|
+
export function decodePDF417(matrix, options = {}) {
|
|
42
|
+
if (!matrix?.width || !matrix?.height || (matrix.width - 69) % 17) throw new FormatError('PDF417: invalid matrix dimensions');
|
|
43
|
+
const cols = (matrix.width - 69) / 17, rowHeight = options.rowHeight ?? matrix.pdf417?.rowHeight ?? 3;
|
|
44
|
+
if (!Number.isInteger(rowHeight) || matrix.height % rowHeight) throw new FormatError('PDF417: invalid row height');
|
|
45
|
+
const rows = matrix.height / rowHeight;
|
|
46
|
+
if (rows < 3 || rows > 90 || cols < 1 || cols > 30) throw new FormatError('PDF417: dimensions outside the standard range');
|
|
47
|
+
const all = [];
|
|
48
|
+
const erasures = [];
|
|
49
|
+
for (let row = 0; row < rows; row++) {
|
|
50
|
+
const y = row * rowHeight, cluster = (row % 3) * 3;
|
|
51
|
+
if (bits(matrix, y, 0, 17).toString(2).padStart(17, '0') !== START || bits(matrix, y, matrix.width - 18, 18).toString(2).padStart(18, '0') !== STOP) throw new FormatError('PDF417: missing start or stop pattern');
|
|
52
|
+
const read = (x) => { const result = pdf417CodewordForPattern(bits(matrix, y, x, 17)); if (!result || result.cluster !== cluster) throw new FormatError('PDF417: invalid codeword pattern'); return result.codeword; };
|
|
53
|
+
const left = read(17), right = read(34 + cols * 17);
|
|
54
|
+
let matched = false; for (let level = 0; level <= 8; level++) { const expected = indicators(row, rows, cols, level); if (left === expected[0] && right === expected[1]) { matched = true; break; } }
|
|
55
|
+
if (!matched) throw new FormatError('PDF417: row indicator mismatch');
|
|
56
|
+
for (let col = 0; col < cols; col++) {
|
|
57
|
+
try { all.push(read(34 + col * 17)); }
|
|
58
|
+
catch {
|
|
59
|
+
erasures.push(all.length);
|
|
60
|
+
all.push(0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let level = -1; for (let candidate = 0; candidate <= 8; candidate++) if (all.length > pdf417EccLength(candidate)) { level = candidate; break; }
|
|
65
|
+
// The row indicators determine the level uniquely across the symbol.
|
|
66
|
+
for (let candidate = 0; candidate <= 8; candidate++) {
|
|
67
|
+
let ok = true; for (let row = 0; row < rows; row++) { const y = row * rowHeight, cluster = (row % 3) * 3, expected = indicators(row, rows, cols, candidate); const left = pdf417CodewordForPattern(bits(matrix, y, 17, 17)); const right = pdf417CodewordForPattern(bits(matrix, y, 34 + cols * 17, 17)); if (!left || !right || left.cluster !== cluster || right.cluster !== cluster || left.codeword !== expected[0] || right.codeword !== expected[1]) { ok = false; break; } } if (ok) { level = candidate; break; }
|
|
68
|
+
}
|
|
69
|
+
if (level < 0) throw new FormatError('PDF417: could not determine error correction level');
|
|
70
|
+
const corrected = all.slice(); const corrections = pdf417CorrectErrors(corrected, level, erasures);
|
|
71
|
+
const length = corrected[0]; if (length < 1 || length > corrected.length - pdf417EccLength(level)) throw new FormatError('PDF417: invalid symbol length descriptor');
|
|
72
|
+
const payload = corrected.slice(1, length);
|
|
73
|
+
const decoded = decodePdf417CompactionDetailed(payload);
|
|
74
|
+
return { ...decoded, codewords: corrected, rows, columns: cols, eccLevel: level, corrections };
|
|
75
|
+
}
|