@sythos/js_barcode_universal 1.0.0 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +16 -17
  2. package/NOTICE.md +24 -22
  3. package/README.md +137 -60
  4. package/bundle/sythos-barcode.esm.js +3380 -216
  5. package/bundle/sythos-barcode.js +3368 -216
  6. package/examples/create.html +2 -0
  7. package/examples/read.html +341 -341
  8. package/licenses/README.md +58 -29
  9. package/licenses/aztec-code.license +74 -0
  10. package/licenses/codabar.license +9 -9
  11. package/licenses/code-11.license +9 -9
  12. package/licenses/code-128.license +6 -6
  13. package/licenses/code-39.license +6 -6
  14. package/licenses/code-93.license +7 -7
  15. package/licenses/data-matrix.license +11 -11
  16. package/licenses/ean-13.license +6 -6
  17. package/licenses/ean-8.license +6 -6
  18. package/licenses/gs1-128.license +6 -6
  19. package/licenses/isbn.license +7 -7
  20. package/licenses/itf-14.license +6 -6
  21. package/licenses/itf.license +6 -6
  22. package/licenses/micropdf417.license +96 -0
  23. package/licenses/msi-plessey.license +7 -7
  24. package/licenses/pdf417.license +37 -0
  25. package/licenses/pharmacode.license +7 -7
  26. package/licenses/qr-code.license +6 -6
  27. package/licenses/upc-a.license +7 -7
  28. package/licenses/upc-e.license +6 -6
  29. package/package.json +13 -3
  30. package/src/aztec/decoder.js +317 -0
  31. package/src/aztec/detector.js +224 -0
  32. package/src/aztec/encoder.js +257 -0
  33. package/src/aztec/high-level.js +211 -0
  34. package/src/aztec/index.js +45 -0
  35. package/src/aztec/tables.js +210 -0
  36. package/src/core/galois-field.js +3 -0
  37. package/src/core/reed-solomon.js +64 -50
  38. package/src/datamatrix/decoder.js +262 -262
  39. package/src/datamatrix/detector.js +225 -225
  40. package/src/datamatrix/encoder.js +191 -191
  41. package/src/datamatrix/index.js +42 -42
  42. package/src/datamatrix/tables.js +123 -123
  43. package/src/index.js +113 -3
  44. package/src/micropdf417/compaction.js +116 -0
  45. package/src/micropdf417/decoder.js +183 -0
  46. package/src/micropdf417/detector.js +149 -0
  47. package/src/micropdf417/encoder.js +209 -0
  48. package/src/micropdf417/error-correction.js +55 -0
  49. package/src/micropdf417/index.js +49 -0
  50. package/src/micropdf417/tables.js +184 -0
  51. package/src/pdf417/compaction.js +298 -0
  52. package/src/pdf417/decoder.js +75 -0
  53. package/src/pdf417/detector.js +468 -0
  54. package/src/pdf417/encoder.js +91 -0
  55. package/src/pdf417/error-correction.js +47 -0
  56. package/src/pdf417/index.js +6 -0
  57. package/src/pdf417/tables.js +317 -0
@@ -0,0 +1,210 @@
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
+ * Aztec Code layer geometry and Reed-Solomon parameters.
33
+ *
34
+ * `totalBits` counts the payload ring before its leading pad bits are added;
35
+ * consequently only `usableBits` can be partitioned into codewords. Compact
36
+ * symbols have no reference grid. Full symbols insert alternating reference
37
+ * rows and columns every 16 modules around the centre.
38
+ *
39
+ * The five data fields use generator base 1. GF(256)/DataMatrix is also the
40
+ * Aztec 8-bit field: both use primitive polynomial 0x12d.
41
+ *
42
+ * @module aztec/tables
43
+ */
44
+
45
+ import { GF16, GF64, GF256_AZTEC, GF1024, GF4096 } from '../core/galois-field.js';
46
+
47
+ /** Reed-Solomon generator base defined for Aztec parameter and data fields. */
48
+ export const AZTEC_RS_GENERATOR_BASE = 1;
49
+
50
+ /** Minimum recommended error correction: 23 percent plus three codewords. */
51
+ export const AZTEC_DEFAULT_ECC_PERCENT = 23;
52
+ export const AZTEC_MIN_ECC_WORDS = 3;
53
+
54
+ /** Word size selected solely by the number of layers. */
55
+ export function wordSizeForLayers(layers) {
56
+ if (!Number.isInteger(layers) || layers < 1 || layers > 32) {
57
+ throw new RangeError(`Aztec: layers must be an integer from 1 to 32 (got ${layers})`);
58
+ }
59
+ if (layers <= 2) return 6;
60
+ if (layers <= 8) return 8;
61
+ if (layers <= 22) return 10;
62
+ return 12;
63
+ }
64
+
65
+ /** Return the field used by Aztec codewords of `wordSize` bits. */
66
+ export function fieldForWordSize(wordSize) {
67
+ switch (wordSize) {
68
+ case 4: return GF16; // Mode message only.
69
+ case 6: return GF64;
70
+ case 8: return GF256_AZTEC;
71
+ case 10: return GF1024;
72
+ case 12: return GF4096;
73
+ default: throw new RangeError(`Aztec: unsupported codeword size ${wordSize}`);
74
+ }
75
+ }
76
+
77
+ /** Return the data field selected for a symbol with `layers` layers. */
78
+ export function fieldForLayers(layers) {
79
+ return fieldForWordSize(wordSizeForLayers(layers));
80
+ }
81
+
82
+ /** Matrix side length, including Full-mode reference grid lines. */
83
+ export function aztecSymbolSize(layers, compact = false) {
84
+ if (!Number.isInteger(layers) || layers < 1 || layers > (compact ? 4 : 32)) {
85
+ throw new RangeError(`Aztec: ${compact ? 'Compact' : 'Full'} layers out of range: ${layers}`);
86
+ }
87
+ if (compact) return 11 + 4 * layers;
88
+ const baseMatrixSize = 14 + 4 * layers;
89
+ return baseMatrixSize + 1 + 2 * Math.floor((baseMatrixSize / 2 - 1) / 15);
90
+ }
91
+
92
+ function layer(layers, compact) {
93
+ const wordSize = wordSizeForLayers(layers);
94
+ const totalBits = ((compact ? 88 : 112) + 16 * layers) * layers;
95
+ const usableBits = totalBits - totalBits % wordSize;
96
+ const totalCodewords = usableBits / wordSize;
97
+ const baseMatrixSize = (compact ? 11 : 14) + 4 * layers;
98
+ return Object.freeze({
99
+ compact,
100
+ layers,
101
+ wordSize,
102
+ totalBits,
103
+ usableBits,
104
+ totalCodewords,
105
+ // Compact mode encodes the count in six bits and can therefore hold no
106
+ // more than 64 data codewords even where the ring itself is larger.
107
+ maxDataCodewords: compact ? Math.min(totalCodewords, 64) : totalCodewords,
108
+ baseMatrixSize,
109
+ symbolSize: aztecSymbolSize(layers, compact),
110
+ modeMessageDataWords: compact ? 2 : 4,
111
+ modeMessageWords: compact ? 7 : 10,
112
+ modeMessageBits: compact ? 28 : 40,
113
+ rsGeneratorBase: AZTEC_RS_GENERATOR_BASE,
114
+ });
115
+ }
116
+
117
+ /** Compact Aztec layers 1 through 4, in encoding preference order. */
118
+ export const AZTEC_COMPACT_LAYERS = Object.freeze(
119
+ Array.from({ length: 4 }, (_, i) => layer(i + 1, true)),
120
+ );
121
+
122
+ /** Full Aztec layers 1 through 32, in ascending layer order. */
123
+ export const AZTEC_FULL_LAYERS = Object.freeze(
124
+ Array.from({ length: 32 }, (_, i) => layer(i + 1, false)),
125
+ );
126
+
127
+ /** All allowed symbols. Compact entries precede Full entries for automatic selection. */
128
+ export const AZTEC_LAYERS = Object.freeze([
129
+ ...AZTEC_COMPACT_LAYERS,
130
+ ...AZTEC_FULL_LAYERS,
131
+ ]);
132
+
133
+ /** Return one immutable layer record. */
134
+ export function aztecLayer(layers, compact = false) {
135
+ if (!Number.isInteger(layers) || layers < 1 || layers > (compact ? 4 : 32)) {
136
+ throw new RangeError(`Aztec: ${compact ? 'Compact' : 'Full'} layers out of range: ${layers}`);
137
+ }
138
+ return (compact ? AZTEC_COMPACT_LAYERS : AZTEC_FULL_LAYERS)[layers - 1];
139
+ }
140
+
141
+ /**
142
+ * Calculate the minimum parity count for a data word count.
143
+ *
144
+ * The percentage is rounded up because a fractional codeword cannot be
145
+ * emitted. The mandatory three words protect short payloads, where a bare
146
+ * percentage would otherwise round to zero.
147
+ */
148
+ export function eccCodewordsFor(dataCodewords, eccPercent = AZTEC_DEFAULT_ECC_PERCENT) {
149
+ if (!Number.isInteger(dataCodewords) || dataCodewords < 0) {
150
+ throw new RangeError(`Aztec: data codewords must be a non-negative integer (got ${dataCodewords})`);
151
+ }
152
+ if (!Number.isFinite(eccPercent) || eccPercent < 0 || eccPercent > 100) {
153
+ throw new RangeError(`Aztec: ECC percent must be between 0 and 100 (got ${eccPercent})`);
154
+ }
155
+ return Math.ceil(dataCodewords * eccPercent / 100) + AZTEC_MIN_ECC_WORDS;
156
+ }
157
+
158
+ /**
159
+ * Choose the first symbol which holds an already stuffed payload.
160
+ *
161
+ * `dataBits` must be a multiple of the candidate word size; callers which
162
+ * start from high-level bits must stuff separately per candidate word size.
163
+ */
164
+ export function selectAztecLayer(dataBits, {
165
+ eccPercent = AZTEC_DEFAULT_ECC_PERCENT,
166
+ layers = null,
167
+ compact = null,
168
+ } = {}) {
169
+ if (!Number.isInteger(dataBits) || dataBits < 0) {
170
+ throw new RangeError(`Aztec: data bits must be a non-negative integer (got ${dataBits})`);
171
+ }
172
+ if (compact !== null && typeof compact !== 'boolean') {
173
+ throw new TypeError('Aztec: compact must be true, false or null');
174
+ }
175
+
176
+ let candidates;
177
+ if (layers !== null) {
178
+ if (compact === null) throw new TypeError('Aztec: compact must be specified when layers is specified');
179
+ candidates = [aztecLayer(layers, compact)];
180
+ } else if (compact === null) {
181
+ candidates = AZTEC_LAYERS;
182
+ } else {
183
+ candidates = compact ? AZTEC_COMPACT_LAYERS : AZTEC_FULL_LAYERS;
184
+ }
185
+
186
+ for (const candidate of candidates) {
187
+ if (dataBits % candidate.wordSize !== 0) continue;
188
+ const dataCodewords = dataBits / candidate.wordSize;
189
+ const eccCodewords = eccCodewordsFor(dataCodewords, eccPercent);
190
+ if (dataCodewords <= candidate.maxDataCodewords &&
191
+ dataCodewords + eccCodewords <= candidate.totalCodewords) {
192
+ return Object.freeze({ ...candidate, dataCodewords, eccCodewords });
193
+ }
194
+ }
195
+
196
+ throw new RangeError('Aztec: payload and requested error correction do not fit an available symbol');
197
+ }
198
+
199
+ /** Check static identities so table corruption fails explicitly in tests. */
200
+ export function validateAztecTables() {
201
+ const issues = [];
202
+ for (const entry of AZTEC_LAYERS) {
203
+ if (entry.usableBits % entry.wordSize !== 0) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: unaligned usable bits`);
204
+ if (entry.totalCodewords !== entry.usableBits / entry.wordSize) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: codeword mismatch`);
205
+ if (entry.symbolSize !== aztecSymbolSize(entry.layers, entry.compact)) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: matrix size mismatch`);
206
+ if (entry.rsGeneratorBase !== AZTEC_RS_GENERATOR_BASE) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: generator base mismatch`);
207
+ if (entry.compact && entry.maxDataCodewords > 64) issues.push(`C${entry.layers}: Compact data-word limit exceeded`);
208
+ }
209
+ return issues;
210
+ }
@@ -194,6 +194,9 @@ export const GF256_QR = new GaloisField({ size: 256, primitive: 0x011d, name: 'G
194
194
  /** Data Matrix ECC200. x^8 + x^5 + x^3 + x^2 + 1 */
195
195
  export const GF256_DM = new GaloisField({ size: 256, primitive: 0x012d, name: 'GF(256)/DataMatrix' });
196
196
 
197
+ /** Aztec's eight-bit data field is algebraically identical to Data Matrix's. */
198
+ export const GF256_AZTEC = GF256_DM;
199
+
197
200
  /** PDF417. Prime field; 3 is a primitive root modulo 929. */
198
201
  export const GF929 = new GaloisField({ size: 929, prime: true, generator: 3, name: 'GF(929)' });
199
202
 
@@ -56,7 +56,7 @@ import { ChecksumError } from './errors.js';
56
56
  *
57
57
  * g(x) = product over i of (x - a^(base + i)), i = 0 .. eccLen-1
58
58
  *
59
- * `base` is 0 for QR and Aztec; 1 for Data Matrix and PDF417.
59
+ * `base` is 0 for QR; 1 for Aztec, Data Matrix and PDF417.
60
60
  *
61
61
  * @param {number} eccLen
62
62
  * @param {import('./galois-field.js').GaloisField} field
@@ -153,6 +153,44 @@ function evalPoly(poly, x, field) {
153
153
  return acc;
154
154
  }
155
155
 
156
+ function multiplyAscending(left, right, field, limit) {
157
+ const out = new Array(Math.min(limit, left.length + right.length - 1)).fill(0);
158
+ for (let i = 0; i < left.length; i++) for (let j = 0; j < right.length && i + j < out.length; j++) {
159
+ out[i + j] = field.add(out[i + j], field.mul(left[i], right[j]));
160
+ }
161
+ return out;
162
+ }
163
+
164
+ function berlekampMassey(syndromes, field) {
165
+ const limit = syndromes.length;
166
+ const lambda = new Array(limit + 1).fill(0);
167
+ const previous = new Array(limit + 1).fill(0);
168
+ const temporary = new Array(limit + 1).fill(0);
169
+ lambda[0] = 1;
170
+ previous[0] = 1;
171
+ let errorCount = 0;
172
+ let shift = 1;
173
+ let lastDiscrepancy = 1;
174
+
175
+ for (let step = 0; step < limit; step++) {
176
+ let discrepancy = syndromes[step];
177
+ for (let i = 1; i <= errorCount; i++) discrepancy = field.add(discrepancy, field.mul(lambda[i], syndromes[step - i]));
178
+ if (discrepancy === 0) { shift++; continue; }
179
+ const scale = field.div(discrepancy, lastDiscrepancy);
180
+ for (let i = 0; i <= limit; i++) temporary[i] = lambda[i];
181
+ for (let i = 0; i + shift <= limit; i++) if (previous[i] !== 0) {
182
+ lambda[i + shift] = field.sub(lambda[i + shift], field.mul(scale, previous[i]));
183
+ }
184
+ if (2 * errorCount <= step) {
185
+ errorCount = step + 1 - errorCount;
186
+ for (let i = 0; i <= limit; i++) previous[i] = temporary[i];
187
+ lastDiscrepancy = discrepancy;
188
+ shift = 1;
189
+ } else shift++;
190
+ }
191
+ return { locator: lambda.slice(0, errorCount + 1), errorCount };
192
+ }
193
+
156
194
  /**
157
195
  * Correct errors in a received codeword, in place.
158
196
  *
@@ -160,11 +198,16 @@ function evalPoly(poly, x, field) {
160
198
  * @param {number} eccLen
161
199
  * @param {import('./galois-field.js').GaloisField} field
162
200
  * @param {number} [base]
201
+ * @param {number[]} [erasures] Known damaged indexes, counted from wire order.
163
202
  * @returns {number} Number of symbols corrected.
164
203
  * @throws {ChecksumError} If the damage exceeds the correction capacity.
165
204
  */
166
- export function rsDecode(received, eccLen, field, base = 0) {
205
+ export function rsDecode(received, eccLen, field, base = 0, erasures = []) {
167
206
  const n = received.length;
207
+ if (!Array.isArray(erasures) || new Set(erasures).size !== erasures.length || erasures.some((index) => !Number.isInteger(index) || index < 0 || index >= n)) {
208
+ throw new ChecksumError('Reed-Solomon: erasure positions must be unique codeword indexes');
209
+ }
210
+ if (erasures.length > eccLen) throw new ChecksumError(`Reed-Solomon: ${erasures.length} erasures exceeds correction capacity ${eccLen} (${field.name})`);
168
211
 
169
212
  // --- Syndromes. S[i] = R(a^(base+i)); all zero means an intact codeword.
170
213
  const syn = new Array(eccLen).fill(0);
@@ -176,53 +219,24 @@ export function rsDecode(received, eccLen, field, base = 0) {
176
219
  }
177
220
  if (!damaged) return 0;
178
221
 
179
- // --- Berlekamp-Massey. Degree-ascending here: lambda[k] is the coefficient
180
- // of x^k, which is how the recurrence is naturally stated.
181
- const lambda = new Array(eccLen + 1).fill(0);
182
- const prev = new Array(eccLen + 1).fill(0);
183
- const tmp = new Array(eccLen + 1).fill(0);
184
- lambda[0] = 1;
185
- prev[0] = 1;
186
- let errCount = 0; // current LFSR length
187
- let shift = 1; // steps since `prev` was last updated
188
- let lastDisc = 1; // discrepancy at that update
189
-
190
- for (let step = 0; step < eccLen; step++) {
191
- let disc = syn[step];
192
- for (let i = 1; i <= errCount; i++) {
193
- disc = field.add(disc, field.mul(lambda[i], syn[step - i]));
194
- }
195
-
196
- if (disc === 0) {
197
- shift++;
198
- continue;
199
- }
200
-
201
- const scale = field.div(disc, lastDisc);
202
- tmp.fill(0);
203
- for (let i = 0; i <= eccLen; i++) tmp[i] = lambda[i];
204
-
205
- for (let i = 0; i + shift <= eccLen; i++) {
206
- if (prev[i] === 0) continue;
207
- lambda[i + shift] = field.sub(lambda[i + shift], field.mul(scale, prev[i]));
208
- }
209
-
210
- if (2 * errCount <= step) {
211
- errCount = step + 1 - errCount;
212
- for (let i = 0; i <= eccLen; i++) prev[i] = tmp[i];
213
- lastDisc = disc;
214
- shift = 1;
215
- } else {
216
- shift++;
217
- }
222
+ // Remove the known roots before locating unknown errors. The leading
223
+ // erasureCount terms contain only the known-location transient and are not
224
+ // part of the error-only recurrence.
225
+ let erasureLocator = [1];
226
+ for (const index of erasures) {
227
+ const location = field.exp(n - 1 - index);
228
+ erasureLocator = multiplyAscending(erasureLocator, [1, field.neg(location)], field, eccLen + 1);
218
229
  }
219
-
220
- if (errCount === 0 || errCount > eccLen / 2) {
230
+ const modified = multiplyAscending(syn, erasureLocator, field, eccLen).slice(erasures.length);
231
+ const { locator: errorLocator, errorCount } = berlekampMassey(modified, field);
232
+ if (2 * errorCount + erasures.length > eccLen) {
221
233
  throw new ChecksumError(
222
- `Reed-Solomon: ${errCount} errors exceeds correction capacity ` +
223
- `${Math.floor(eccLen / 2)} (${field.name})`
234
+ `Reed-Solomon: ${errorCount} errors and ${erasures.length} erasures exceed correction capacity ` +
235
+ `${eccLen} (${field.name})`
224
236
  );
225
237
  }
238
+ const lambda = multiplyAscending(erasureLocator, errorLocator, field, eccLen + 1);
239
+ const totalCount = errorCount + erasures.length;
226
240
 
227
241
  // --- Chien search. Position p (counted from the low-order end) is in error
228
242
  // when lambda(a^-p) == 0.
@@ -231,16 +245,16 @@ export function rsDecode(received, eccLen, field, base = 0) {
231
245
  const xInv = field.exp(-p);
232
246
  let acc = 0;
233
247
  let term = 1;
234
- for (let i = 0; i <= errCount; i++) {
248
+ for (let i = 0; i <= totalCount; i++) {
235
249
  acc = field.add(acc, field.mul(lambda[i], term));
236
250
  term = field.mul(term, xInv);
237
251
  }
238
252
  if (acc === 0) positions.push(p);
239
253
  }
240
254
 
241
- if (positions.length !== errCount) {
255
+ if (positions.length !== totalCount) {
242
256
  throw new ChecksumError(
243
- `Reed-Solomon: located ${positions.length} of ${errCount} error positions`
257
+ `Reed-Solomon: located ${positions.length} of ${totalCount} error positions`
244
258
  );
245
259
  }
246
260
 
@@ -249,7 +263,7 @@ export function rsDecode(received, eccLen, field, base = 0) {
249
263
  const omega = new Array(eccLen).fill(0);
250
264
  for (let i = 0; i < eccLen; i++) {
251
265
  let acc = 0;
252
- for (let j = 0; j <= i && j <= errCount; j++) {
266
+ for (let j = 0; j <= i && j <= totalCount; j++) {
253
267
  acc = field.add(acc, field.mul(lambda[j], syn[i - j]));
254
268
  }
255
269
  omega[i] = acc;
@@ -273,7 +287,7 @@ export function rsDecode(received, eccLen, field, base = 0) {
273
287
  // in a prime field every term contributes with an integer multiplier.
274
288
  let den = 0;
275
289
  term = 1;
276
- for (let i = 1; i <= errCount; i++) {
290
+ for (let i = 1; i <= totalCount; i++) {
277
291
  if (field.prime) {
278
292
  // i * lambda[i] * x^(i-1), where `i` is repeated addition.
279
293
  let mult = 0;