@sythos/js_barcode_universal 0.1.0 → 1.1.0

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.
@@ -0,0 +1,45 @@
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
+ /** Aztec Code entry points. @module aztec */
32
+
33
+ export { encodeAztec } from './encoder.js';
34
+ export { decodeAztec } from './decoder.js';
35
+ export { detectAztec, detectAndDecodeAztec } from './detector.js';
36
+ export {
37
+ AZTEC_COMPACT_LAYERS,
38
+ AZTEC_FULL_LAYERS,
39
+ AZTEC_LAYERS,
40
+ AZTEC_DEFAULT_ECC_PERCENT,
41
+ AZTEC_RS_GENERATOR_BASE,
42
+ aztecLayer,
43
+ aztecSymbolSize,
44
+ validateAztecTables,
45
+ } from './tables.js';
@@ -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