@sythos/js_barcode_universal 1.1.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -18
- package/NOTICE.md +27 -22
- package/README.md +596 -460
- package/bundle/sythos-barcode.esm.js +4606 -228
- package/bundle/sythos-barcode.js +4586 -228
- package/examples/create.html +1011 -731
- package/licenses/README.md +23 -18
- package/licenses/aztec-code.license +9 -7
- package/licenses/codabar.license +16 -13
- package/licenses/code-11.license +11 -9
- package/licenses/code-128.license +8 -6
- package/licenses/code-39.license +8 -6
- package/licenses/code-93.license +13 -11
- package/licenses/data-matrix.license +11 -9
- package/licenses/ean-13.license +7 -5
- package/licenses/ean-8.license +7 -5
- package/licenses/frameqr.license +84 -0
- package/licenses/gs1-128.license +7 -5
- package/licenses/isbn.license +8 -6
- package/licenses/itf-14.license +7 -5
- package/licenses/itf.license +7 -5
- package/licenses/micro-qr.license +79 -0
- package/licenses/micropdf417.license +81 -0
- package/licenses/msi-plessey.license +12 -10
- package/licenses/pdf417.license +78 -0
- package/licenses/pharmacode.license +13 -11
- package/licenses/qr-code.license +7 -5
- package/licenses/rmqr.license +79 -0
- package/licenses/upc-a.license +9 -7
- package/licenses/upc-e.license +7 -5
- package/package.json +104 -88
- package/src/core/reed-solomon.js +326 -312
- package/src/datamatrix/decoder.js +262 -262
- package/src/datamatrix/detector.js +225 -225
- package/src/datamatrix/encoder.js +191 -191
- package/src/datamatrix/index.js +42 -42
- package/src/datamatrix/tables.js +123 -123
- package/src/frameqr/decoder.js +239 -0
- package/src/frameqr/detector.js +192 -0
- package/src/frameqr/encoder.js +156 -0
- package/src/frameqr/index.js +42 -0
- package/src/frameqr/tables.js +270 -0
- package/src/index.js +166 -3
- package/src/micropdf417/compaction.js +116 -0
- package/src/micropdf417/decoder.js +183 -0
- package/src/micropdf417/detector.js +149 -0
- package/src/micropdf417/encoder.js +209 -0
- package/src/micropdf417/error-correction.js +55 -0
- package/src/micropdf417/index.js +49 -0
- package/src/micropdf417/tables.js +184 -0
- package/src/microqr/decoder.js +245 -0
- package/src/microqr/detector.js +355 -0
- package/src/microqr/encoder.js +269 -0
- package/src/microqr/index.js +36 -0
- package/src/microqr/tables.js +316 -0
- package/src/oned/index.js +59 -59
- package/src/pdf417/compaction.js +298 -0
- package/src/pdf417/decoder.js +75 -0
- package/src/pdf417/detector.js +468 -0
- package/src/pdf417/encoder.js +91 -0
- package/src/pdf417/error-correction.js +47 -0
- package/src/pdf417/index.js +6 -0
- package/src/pdf417/tables.js +317 -0
- package/src/rmqr/decoder.js +101 -0
- package/src/rmqr/detector.js +90 -0
- package/src/rmqr/encoder.js +172 -0
- package/src/rmqr/index.js +37 -0
- package/src/rmqr/tables.js +154 -0
package/src/index.js
CHANGED
|
@@ -54,6 +54,11 @@ import { decodeOneD } from './oned/reader.js';
|
|
|
54
54
|
import * as datamatrix from './datamatrix/index.js';
|
|
55
55
|
import * as qr from './qr/index.js';
|
|
56
56
|
import * as aztec from './aztec/index.js';
|
|
57
|
+
import * as pdf417 from './pdf417/index.js';
|
|
58
|
+
import * as micropdf417 from './micropdf417/index.js';
|
|
59
|
+
import * as microqr from './microqr/index.js';
|
|
60
|
+
import * as rmqr from './rmqr/index.js';
|
|
61
|
+
import * as frameqr from './frameqr/index.js';
|
|
57
62
|
|
|
58
63
|
export { BitMatrix };
|
|
59
64
|
export {
|
|
@@ -72,6 +77,15 @@ export {
|
|
|
72
77
|
encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix,
|
|
73
78
|
} from './datamatrix/index.js';
|
|
74
79
|
export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './aztec/index.js';
|
|
80
|
+
export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './pdf417/index.js';
|
|
81
|
+
export {
|
|
82
|
+
encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417,
|
|
83
|
+
} from './micropdf417/index.js';
|
|
84
|
+
export { encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR } from './microqr/index.js';
|
|
85
|
+
export { encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR } from './rmqr/index.js';
|
|
86
|
+
export {
|
|
87
|
+
encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR,
|
|
88
|
+
} from './frameqr/index.js';
|
|
75
89
|
|
|
76
90
|
/**
|
|
77
91
|
* @typedef {object} FormatInfo
|
|
@@ -99,6 +113,20 @@ const dataMatrixCanEncode = typeof datamatrix.encodeDataMatrix === 'function';
|
|
|
99
113
|
const dataMatrixCanDecode = typeof datamatrix.detectAndDecodeDataMatrix === 'function';
|
|
100
114
|
const aztecCanEncode = typeof aztec.encodeAztec === 'function';
|
|
101
115
|
const aztecCanDecode = typeof aztec.detectAndDecodeAztec === 'function';
|
|
116
|
+
const pdf417CanEncode = typeof pdf417.encodePDF417 === 'function';
|
|
117
|
+
// The matrix decoder is complete, but automatic image localization is still
|
|
118
|
+
// limited to clean module-aligned symbols or an application-supplied
|
|
119
|
+
// quadrilateral. Keep the generic scanner capability opt-in until a
|
|
120
|
+
// perspective/noise corpus is passed.
|
|
121
|
+
const pdf417CanDecode = typeof pdf417.detectAndDecodePDF417 === 'function';
|
|
122
|
+
const microPdf417CanEncode = typeof micropdf417.encodeMicroPDF417 === 'function';
|
|
123
|
+
const microPdf417CanDecode = typeof micropdf417.detectAndDecodeMicroPDF417 === 'function';
|
|
124
|
+
const microQrCanEncode = typeof microqr.encodeMicroQR === 'function';
|
|
125
|
+
const microQrCanDecode = typeof microqr.detectAndDecodeMicroQR === 'function';
|
|
126
|
+
const rmqrCanEncode = typeof rmqr.encodeRMQR === 'function';
|
|
127
|
+
const rmqrCanDecode = typeof rmqr.detectAndDecodeRMQR === 'function';
|
|
128
|
+
const frameQrCanEncode = typeof frameqr.encodeFrameQR === 'function';
|
|
129
|
+
const frameQrCanDecode = typeof frameqr.detectAndDecodeFrameQR === 'function';
|
|
102
130
|
|
|
103
131
|
/**
|
|
104
132
|
* Every format this build supports.
|
|
@@ -140,6 +168,41 @@ export function listFormats() {
|
|
|
140
168
|
canRead: aztecCanDecode,
|
|
141
169
|
kind: /** @type {'2D'} */ ('2D'),
|
|
142
170
|
});
|
|
171
|
+
formats.push({
|
|
172
|
+
id: 'pdf417',
|
|
173
|
+
label: 'PDF417',
|
|
174
|
+
canWrite: pdf417CanEncode,
|
|
175
|
+
canRead: pdf417CanDecode,
|
|
176
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
177
|
+
});
|
|
178
|
+
formats.push({
|
|
179
|
+
id: 'micropdf417',
|
|
180
|
+
label: 'MicroPDF417',
|
|
181
|
+
canWrite: microPdf417CanEncode,
|
|
182
|
+
canRead: microPdf417CanDecode,
|
|
183
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
184
|
+
});
|
|
185
|
+
formats.push({
|
|
186
|
+
id: 'microqr',
|
|
187
|
+
label: 'Micro QR Code',
|
|
188
|
+
canWrite: microQrCanEncode,
|
|
189
|
+
canRead: microQrCanDecode,
|
|
190
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
191
|
+
});
|
|
192
|
+
formats.push({
|
|
193
|
+
id: 'rmqr',
|
|
194
|
+
label: 'rMQR Code',
|
|
195
|
+
canWrite: rmqrCanEncode,
|
|
196
|
+
canRead: rmqrCanDecode,
|
|
197
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
198
|
+
});
|
|
199
|
+
formats.push({
|
|
200
|
+
id: 'frameqr',
|
|
201
|
+
label: 'Sythos Canvas QR profile',
|
|
202
|
+
canWrite: frameQrCanEncode,
|
|
203
|
+
canRead: frameQrCanDecode,
|
|
204
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
205
|
+
});
|
|
143
206
|
|
|
144
207
|
return formats;
|
|
145
208
|
}
|
|
@@ -163,6 +226,21 @@ export function listFormats() {
|
|
|
163
226
|
* @param {number} [options.layers] Aztec layer count; automatic if omitted.
|
|
164
227
|
* @param {boolean} [options.compact] Force an Aztec Compact or Full symbol.
|
|
165
228
|
* @param {number} [options.eccPercent] Requested Aztec error-correction percentage.
|
|
229
|
+
* @param {number} [options.eccLevel] PDF417 error-correction level, 0-8.
|
|
230
|
+
* @param {number} [options.columns] PDF417 columns, 1-30.
|
|
231
|
+
* @param {number} [options.rows] PDF417 rows, 3-90.
|
|
232
|
+
* @param {number} [options.rowHeight] PDF417 row height in modules.
|
|
233
|
+
* @param {'auto'|'text'|'byte'|'numeric'} [options.compaction] PDF417 compaction mode.
|
|
234
|
+
* @param {number} [options.eci] MicroPDF417 byte-compaction ECI assignment (3 or 26).
|
|
235
|
+
* @param {number} [options.aspectRatio] Preferred MicroPDF417 symbol aspect ratio.
|
|
236
|
+
* @param {object} [options.canvas] Sythos Canvas QR artwork reservation.
|
|
237
|
+
* @param {'square'|'circle'|'diamond'} [options.canvas.shape] Canvas shape.
|
|
238
|
+
* @param {number} [options.canvas.size] Odd canvas size in QR modules.
|
|
239
|
+
* @param {number} [options.canvas.width] Canvas width in QR modules.
|
|
240
|
+
* @param {number} [options.canvas.height] Canvas height in QR modules.
|
|
241
|
+
* @param {number} [options.canvas.centerX] Canvas centre X in QR modules.
|
|
242
|
+
* @param {number} [options.canvas.centerY] Canvas centre Y in QR modules.
|
|
243
|
+
* @param {0|90|180|270} [options.canvas.angle] Canvas quarter-turn.
|
|
166
244
|
* @returns {BitMatrix}
|
|
167
245
|
*/
|
|
168
246
|
export function encode(text, options = {}) {
|
|
@@ -178,10 +256,25 @@ export function encode(text, options = {}) {
|
|
|
178
256
|
if (format === 'aztec' || format === 'aztec-code') {
|
|
179
257
|
return aztec.encodeAztec(value, options);
|
|
180
258
|
}
|
|
259
|
+
if (format === 'pdf417' || format === 'pdf-417') {
|
|
260
|
+
return pdf417.encodePDF417(value, options);
|
|
261
|
+
}
|
|
262
|
+
if (format === 'micropdf417' || format === 'micro-pdf417' || format === 'micro-pdf-417') {
|
|
263
|
+
return micropdf417.encodeMicroPDF417(value, options);
|
|
264
|
+
}
|
|
265
|
+
if (format === 'microqr' || format === 'micro-qr') {
|
|
266
|
+
return microqr.encodeMicroQR(value, options);
|
|
267
|
+
}
|
|
268
|
+
if (format === 'rmqr' || format === 'r-mqr' || format === 'rectangular-micro-qr') {
|
|
269
|
+
return rmqr.encodeRMQR(value, options);
|
|
270
|
+
}
|
|
271
|
+
if (format === 'frameqr' || format === 'frame-qr' || format === 'canvas-qr') {
|
|
272
|
+
return frameqr.encodeFrameQR(value, options);
|
|
273
|
+
}
|
|
181
274
|
|
|
182
275
|
const entry = ONED_FORMATS[format];
|
|
183
276
|
if (!entry) {
|
|
184
|
-
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec'].join(', ');
|
|
277
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'pdf417', 'micropdf417', 'microqr', 'rmqr', 'frameqr'].join(', ');
|
|
185
278
|
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
186
279
|
}
|
|
187
280
|
return entry.encode(value, options);
|
|
@@ -191,12 +284,22 @@ export function encode(text, options = {}) {
|
|
|
191
284
|
* @typedef {object} DecodeResult
|
|
192
285
|
* @property {string} text
|
|
193
286
|
* @property {string} format
|
|
194
|
-
* @property {Uint8Array} [bytes] Raw payload, before text decoding.
|
|
287
|
+
* @property {Uint8Array} [bytes] Raw octets exposed by byte-oriented payload modes, before text decoding.
|
|
288
|
+
* @property {{mode: 'text'|'byte'|'numeric', text: string, bytes: Uint8Array, eci: number, latch: number|null, codewordStart: number, codewordEnd: number}[]} [segments] PDF417 compaction segments in source order.
|
|
195
289
|
* @property {number} [version] QR version.
|
|
196
290
|
* @property {string} [ecc] QR error-correction level.
|
|
197
291
|
* @property {number} [layers] Aztec layer count.
|
|
198
292
|
* @property {boolean} [compact] Whether an Aztec symbol is Compact.
|
|
199
293
|
* @property {number} [corrections] Reed–Solomon corrections applied by an Aztec decode.
|
|
294
|
+
* @property {number} [rows] PDF417 row count.
|
|
295
|
+
* @property {number} [columns] PDF417 column count.
|
|
296
|
+
* @property {number} [eccLevel] PDF417 error-correction level.
|
|
297
|
+
* @property {number} [rowHeight] PDF417 row height in modules.
|
|
298
|
+
* @property {number} [variant] MicroPDF417 predefined variant number.
|
|
299
|
+
* @property {number} [eccCodewords] MicroPDF417 fixed error-correction codewords.
|
|
300
|
+
* @property {string} [profile] Sythos Canvas QR profile identifier.
|
|
301
|
+
* @property {boolean} [certified] Whether the profile is certified by its originator.
|
|
302
|
+
* @property {object} [canvas] Canvas reservation metadata for the Sythos profile.
|
|
200
303
|
*/
|
|
201
304
|
|
|
202
305
|
/**
|
|
@@ -211,6 +314,8 @@ export function encode(text, options = {}) {
|
|
|
211
314
|
* @param {string[]} [options.formats] Restrict to these format ids.
|
|
212
315
|
* @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
|
|
213
316
|
* @param {'global'|'hybrid'|'auto'} [options.binarizer]
|
|
317
|
+
* @param {object} [options.frameqr] Sythos Canvas QR detector options when
|
|
318
|
+
* the profile marker is not preserved through image rendering.
|
|
214
319
|
* @returns {DecodeResult[]}
|
|
215
320
|
*/
|
|
216
321
|
export function decode(image, options = {}) {
|
|
@@ -219,6 +324,11 @@ export function decode(image, options = {}) {
|
|
|
219
324
|
const wantQR = !want || want.has('qr') || want.has('qrcode');
|
|
220
325
|
const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
|
|
221
326
|
const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
|
|
327
|
+
const wantPDF417 = !want || want.has('pdf417') || want.has('pdf-417');
|
|
328
|
+
const wantMicroPDF417 = !want || want.has('micropdf417') || want.has('micro-pdf417') || want.has('micro-pdf-417');
|
|
329
|
+
const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
|
|
330
|
+
const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
|
|
331
|
+
const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
|
|
222
332
|
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
223
333
|
|
|
224
334
|
const source = LuminanceSource.fromImageData(image);
|
|
@@ -271,6 +381,59 @@ export function decode(image, options = {}) {
|
|
|
271
381
|
}
|
|
272
382
|
}
|
|
273
383
|
|
|
384
|
+
if (wantPDF417 && pdf417CanDecode) {
|
|
385
|
+
try {
|
|
386
|
+
const found = pdf417.detectAndDecodePDF417(bits);
|
|
387
|
+
if (found) results.push({ ...found, format: 'pdf417' });
|
|
388
|
+
} catch {
|
|
389
|
+
/* no PDF417 in this pass */
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (wantMicroPDF417 && microPdf417CanDecode) {
|
|
394
|
+
// MicroPDF417 detection measures runs across the whole raster. Hybrid
|
|
395
|
+
// thresholding can alter uniform modules near local-window boundaries,
|
|
396
|
+
// so retry the global threshold in auto mode as for the other 2D codes.
|
|
397
|
+
const microPdf417Bits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
|
|
398
|
+
for (const candidateBits of microPdf417Bits) {
|
|
399
|
+
try {
|
|
400
|
+
const found = micropdf417.detectAndDecodeMicroPDF417(candidateBits);
|
|
401
|
+
if (found) { results.push({ ...found, format: 'micropdf417' }); break; }
|
|
402
|
+
} catch {
|
|
403
|
+
/* no MicroPDF417 with this threshold */
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (wantMicroQR && microQrCanDecode) {
|
|
409
|
+
try {
|
|
410
|
+
for (const found of microqr.detectAndDecodeMicroQR(bits)) {
|
|
411
|
+
results.push({ ...found, format: 'microqr' });
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
/* no Micro QR in this pass */
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (wantRMQR && rmqrCanDecode) {
|
|
419
|
+
try {
|
|
420
|
+
const found = rmqr.detectAndDecodeRMQR(bits);
|
|
421
|
+
if (found) results.push({ ...found, format: 'rmqr' });
|
|
422
|
+
} catch {
|
|
423
|
+
/* no rMQR in this pass */
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (wantFrameQR && frameQrCanDecode) {
|
|
428
|
+
try {
|
|
429
|
+
for (const found of frameqr.detectAndDecodeFrameQR(bits, options.frameqr ?? {})) {
|
|
430
|
+
results.push({ ...found, format: 'frameqr' });
|
|
431
|
+
}
|
|
432
|
+
} catch {
|
|
433
|
+
/* no Sythos Canvas QR profile in this pass */
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
274
437
|
if (wantOneD) {
|
|
275
438
|
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
276
439
|
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
@@ -305,4 +468,4 @@ export function decodeStrict(image, options) {
|
|
|
305
468
|
}
|
|
306
469
|
|
|
307
470
|
/** Library version, matching package.json. */
|
|
308
|
-
export const VERSION = '1.1
|
|
471
|
+
export const VERSION = '1.3.1';
|
|
@@ -0,0 +1,116 @@
|
|
|
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 high-level compaction adapter. @module micropdf417/compaction */
|
|
32
|
+
|
|
33
|
+
import { EncodeError } from '../core/errors.js';
|
|
34
|
+
import {
|
|
35
|
+
compactPdf417Bytes,
|
|
36
|
+
compactPdf417Numeric,
|
|
37
|
+
compactPdf417Text,
|
|
38
|
+
} from '../pdf417/compaction.js';
|
|
39
|
+
|
|
40
|
+
function byteLength(value) {
|
|
41
|
+
if (value instanceof Uint8Array) return value.byteLength;
|
|
42
|
+
if (ArrayBuffer.isView(value)) return value.byteLength;
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function assertNotEmpty(value) {
|
|
47
|
+
if ((typeof value === 'string' && value.length === 0) || byteLength(value) === 0) {
|
|
48
|
+
throw new EncodeError('MicroPDF417: value must not be empty');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function latin1Bytes(value) {
|
|
53
|
+
if (typeof value !== 'string') return value;
|
|
54
|
+
const bytes = [];
|
|
55
|
+
for (const character of value) {
|
|
56
|
+
const codePoint = character.codePointAt(0);
|
|
57
|
+
if (codePoint > 255) {
|
|
58
|
+
throw new EncodeError('MicroPDF417 ECI 3: string contains a character outside ISO-8859-1');
|
|
59
|
+
}
|
|
60
|
+
bytes.push(codePoint);
|
|
61
|
+
}
|
|
62
|
+
return Uint8Array.from(bytes);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function compactByte(value, eci) {
|
|
66
|
+
if (eci === undefined) return compactPdf417Bytes(value);
|
|
67
|
+
if (eci === 3) return compactPdf417Bytes(latin1Bytes(value));
|
|
68
|
+
if (eci === 26) {
|
|
69
|
+
if (typeof value !== 'string') {
|
|
70
|
+
throw new EncodeError('MicroPDF417 ECI 26: value must be a string so UTF-8 validity is known');
|
|
71
|
+
}
|
|
72
|
+
const encoded = compactPdf417Bytes(value);
|
|
73
|
+
return encoded[0] === 927 && encoded[1] === 26 ? encoded : [927, 26, ...encoded];
|
|
74
|
+
}
|
|
75
|
+
throw new EncodeError('MicroPDF417: supported ECI assignment numbers are 3 and 26');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Compact one MicroPDF417 value.
|
|
80
|
+
*
|
|
81
|
+
* Unlike PDF417, MicroPDF417 starts in Byte Compaction. Text therefore needs
|
|
82
|
+
* an explicit 900 latch. Byte compaction always emits 901 or 924 so its start
|
|
83
|
+
* state is unambiguous, including when an ECI designator precedes it.
|
|
84
|
+
*/
|
|
85
|
+
export function compactMicroPDF417(value, options = {}) {
|
|
86
|
+
assertNotEmpty(value);
|
|
87
|
+
const mode = options.compaction ?? 'auto';
|
|
88
|
+
const eci = options.eci;
|
|
89
|
+
if (eci !== undefined && eci !== 3 && eci !== 26) {
|
|
90
|
+
throw new EncodeError('MicroPDF417: supported ECI assignment numbers are 3 and 26');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (mode === 'text') {
|
|
94
|
+
if (eci !== undefined) throw new EncodeError('MicroPDF417: explicit ECI is supported only with byte compaction');
|
|
95
|
+
return [900, ...compactPdf417Text(value)];
|
|
96
|
+
}
|
|
97
|
+
if (mode === 'numeric') {
|
|
98
|
+
if (eci !== undefined) throw new EncodeError('MicroPDF417: explicit ECI is supported only with byte compaction');
|
|
99
|
+
return compactPdf417Numeric(value);
|
|
100
|
+
}
|
|
101
|
+
if (mode === 'byte') return compactByte(value, eci);
|
|
102
|
+
if (mode !== 'auto') {
|
|
103
|
+
throw new EncodeError(`MicroPDF417: unsupported compaction mode ${JSON.stringify(mode)}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (eci !== undefined) return compactByte(value, eci);
|
|
107
|
+
if (typeof value === 'string' && /^\d{13,}$/.test(value)) return compactPdf417Numeric(value);
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
try {
|
|
110
|
+
return [900, ...compactPdf417Text(value)];
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!(error instanceof EncodeError)) throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return compactPdf417Bytes(value);
|
|
116
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
* Direct-module MicroPDF417 decoder.
|
|
33
|
+
*
|
|
34
|
+
* This module reads an already sampled, axis-aligned `BitMatrix`. Image finding,
|
|
35
|
+
* perspective correction, and recognition from photographic pixels are deliberately
|
|
36
|
+
* outside this first decoder boundary. The RAP sequence is authoritative for format
|
|
37
|
+
* detection; matrix metadata is used only as an optional row-height hint.
|
|
38
|
+
*
|
|
39
|
+
* @module micropdf417/decoder
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { FormatError } from '../core/errors.js';
|
|
43
|
+
import { decodePdf417CompactionDetailed } from '../pdf417/compaction.js';
|
|
44
|
+
import { pdf417CodewordForPattern } from '../pdf417/tables.js';
|
|
45
|
+
import { microPdf417CorrectErrors } from './error-correction.js';
|
|
46
|
+
import {
|
|
47
|
+
MICROPDF417_VARIANTS,
|
|
48
|
+
microPdf417RapSequence,
|
|
49
|
+
microPdf417RowAddress,
|
|
50
|
+
microPdf417VariantByNumber,
|
|
51
|
+
} from './tables.js';
|
|
52
|
+
|
|
53
|
+
function symbolWidth(columns) {
|
|
54
|
+
return 21 + columns * 17 + (columns > 2 ? 10 : 0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function bits(matrix, y, x, width) {
|
|
58
|
+
let value = 0;
|
|
59
|
+
for (let i = 0; i < width; i++) value = (value << 1) | (matrix.get(x + i, y) ? 1 : 0);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function widthsToBits(widths) {
|
|
64
|
+
let dark = true;
|
|
65
|
+
let out = '';
|
|
66
|
+
for (const digit of widths) {
|
|
67
|
+
out += (dark ? '1' : '0').repeat(digit.charCodeAt(0) - 48);
|
|
68
|
+
dark = !dark;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasExpectedBits(matrix, y, x, sequence) {
|
|
74
|
+
const expected = widthsToBits(sequence);
|
|
75
|
+
for (let i = 0; i < expected.length; i++) {
|
|
76
|
+
if ((matrix.get(x + i, y) ? '1' : '0') !== expected[i]) return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function centralRapAfter(entry, column) {
|
|
82
|
+
return (entry.columns === 3 && column === 0) ||
|
|
83
|
+
(entry.columns === 4 && column === 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Return true when all address patterns for this format candidate agree. */
|
|
87
|
+
function hasValidRowAddresses(matrix, entry, rowHeight) {
|
|
88
|
+
for (let row = 0; row < entry.rows; row++) {
|
|
89
|
+
const y = row * rowHeight;
|
|
90
|
+
const address = microPdf417RowAddress(entry, row);
|
|
91
|
+
let x = 0;
|
|
92
|
+
if (!hasExpectedBits(matrix, y, x, microPdf417RapSequence(address.left, 'side'))) return false;
|
|
93
|
+
x += 10;
|
|
94
|
+
for (let column = 0; column < entry.columns; column++) {
|
|
95
|
+
x += 17;
|
|
96
|
+
if (centralRapAfter(entry, column)) {
|
|
97
|
+
if (address.center === null ||
|
|
98
|
+
!hasExpectedBits(matrix, y, x, microPdf417RapSequence(address.center, 'center'))) return false;
|
|
99
|
+
x += 10;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!hasExpectedBits(matrix, y, x, microPdf417RapSequence(address.right, 'side'))) return false;
|
|
103
|
+
x += 10;
|
|
104
|
+
if (!matrix.get(x, y) || x + 1 !== matrix.width) return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function candidateFormats(matrix, options) {
|
|
110
|
+
const metadataHeight = matrix.micropdf417?.rowHeight;
|
|
111
|
+
const requestedHeight = options.rowHeight ?? metadataHeight;
|
|
112
|
+
if (requestedHeight !== undefined && (!Number.isInteger(requestedHeight) || requestedHeight < 1)) {
|
|
113
|
+
throw new FormatError('MicroPDF417: rowHeight must be a positive integer');
|
|
114
|
+
}
|
|
115
|
+
const requestedVariant = options.variant === undefined ? null : microPdf417VariantByNumber(options.variant);
|
|
116
|
+
const pool = requestedVariant ? [requestedVariant] : MICROPDF417_VARIANTS;
|
|
117
|
+
const candidates = [];
|
|
118
|
+
for (const entry of pool) {
|
|
119
|
+
if (matrix.width !== symbolWidth(entry.columns)) continue;
|
|
120
|
+
if (matrix.height % entry.rows) continue;
|
|
121
|
+
const rowHeight = matrix.height / entry.rows;
|
|
122
|
+
if (requestedHeight !== undefined && rowHeight !== requestedHeight) continue;
|
|
123
|
+
if (hasValidRowAddresses(matrix, entry, rowHeight)) candidates.push({ entry, rowHeight });
|
|
124
|
+
}
|
|
125
|
+
return candidates;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveFormat(matrix, options) {
|
|
129
|
+
if (!matrix?.width || !matrix?.height || typeof matrix.get !== 'function') {
|
|
130
|
+
throw new FormatError('MicroPDF417: matrix with width, height and get() is required');
|
|
131
|
+
}
|
|
132
|
+
const candidates = candidateFormats(matrix, options);
|
|
133
|
+
if (!candidates.length) throw new FormatError('MicroPDF417: no variant matches matrix geometry and row-address patterns');
|
|
134
|
+
if (candidates.length > 1) throw new FormatError('MicroPDF417: row-address patterns do not identify a unique variant');
|
|
135
|
+
return candidates[0];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readCodewords(matrix, entry, rowHeight) {
|
|
139
|
+
const codewords = [];
|
|
140
|
+
const erasures = [];
|
|
141
|
+
for (let row = 0; row < entry.rows; row++) {
|
|
142
|
+
const y = row * rowHeight;
|
|
143
|
+
const address = microPdf417RowAddress(entry, row);
|
|
144
|
+
let x = 10;
|
|
145
|
+
for (let column = 0; column < entry.columns; column++) {
|
|
146
|
+
const decoded = pdf417CodewordForPattern(bits(matrix, y, x, 17));
|
|
147
|
+
if (!decoded || decoded.cluster !== address.cluster) {
|
|
148
|
+
erasures.push(codewords.length);
|
|
149
|
+
codewords.push(0);
|
|
150
|
+
} else {
|
|
151
|
+
codewords.push(decoded.codeword);
|
|
152
|
+
}
|
|
153
|
+
x += 17;
|
|
154
|
+
if (centralRapAfter(entry, column)) x += 10;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { codewords, erasures };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Decode a sampled MicroPDF417 matrix.
|
|
162
|
+
*
|
|
163
|
+
* The complete fixed data region is compacted after correction. Encoder padding
|
|
164
|
+
* is PDF417 Text latch 900, which contributes no characters at the end of the
|
|
165
|
+
* payload. This avoids relying on non-symbol metadata for payload length.
|
|
166
|
+
*/
|
|
167
|
+
export function decodeMicroPDF417(matrix, options = {}) {
|
|
168
|
+
const { entry, rowHeight } = resolveFormat(matrix, options);
|
|
169
|
+
const { codewords, erasures } = readCodewords(matrix, entry, rowHeight);
|
|
170
|
+
const corrections = microPdf417CorrectErrors(codewords, entry, erasures);
|
|
171
|
+
const data = codewords.slice(0, entry.dataCodewords);
|
|
172
|
+
const decoded = decodePdf417CompactionDetailed(data);
|
|
173
|
+
return {
|
|
174
|
+
...decoded,
|
|
175
|
+
codewords,
|
|
176
|
+
rows: entry.rows,
|
|
177
|
+
columns: entry.columns,
|
|
178
|
+
variant: entry.id,
|
|
179
|
+
eccCodewords: entry.eccCodewords,
|
|
180
|
+
rowHeight,
|
|
181
|
+
corrections,
|
|
182
|
+
};
|
|
183
|
+
}
|