@sythos/js_barcode_universal 1.5.13 → 1.5.15

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 (137) hide show
  1. package/LICENSE +22 -4
  2. package/NOTICE.md +10 -1
  3. package/README.md +416 -22
  4. package/bundle/sythos-barcode.esm.js +22176 -11991
  5. package/bundle/sythos-barcode.js +22059 -11991
  6. package/licenses/README.md +12 -1
  7. package/licenses/codablockf.license +51 -0
  8. package/licenses/code16k.license +52 -0
  9. package/licenses/code25.license +58 -0
  10. package/licenses/code32.license +51 -0
  11. package/licenses/dotcode.license +66 -0
  12. package/licenses/gs1-composite.license +106 -0
  13. package/licenses/gs1-databar.license +21 -14
  14. package/licenses/hanxin.license +95 -0
  15. package/licenses/maxicode.license +62 -0
  16. package/licenses/postal.license +61 -0
  17. package/licenses/pzn.license +53 -0
  18. package/licenses/telepen.license +73 -0
  19. package/llms.txt +62 -6
  20. package/package.json +47 -3
  21. package/src/index.d.ts +111 -6
  22. package/src/index.js +362 -13
  23. package/src/js/codablockf/decoder.js +198 -0
  24. package/src/js/codablockf/encoder.js +97 -0
  25. package/src/js/codablockf/index.js +3 -0
  26. package/src/js/code16k/decoder.js +303 -0
  27. package/src/js/code16k/detector.js +187 -0
  28. package/src/js/code16k/encoder.js +153 -0
  29. package/src/js/code16k/index.js +14 -0
  30. package/src/js/code16k/tables.js +152 -0
  31. package/src/js/composite/index.js +570 -0
  32. package/src/js/core/detection-contract.js +185 -0
  33. package/src/js/core/symbol-layout.js +155 -0
  34. package/src/js/databar/expanded.js +968 -0
  35. package/src/js/databar/index.js +4 -0
  36. package/src/js/databar/layout.js +192 -0
  37. package/src/js/databar/limited.js +533 -0
  38. package/src/js/databar/stacked-omnidirectional.js +548 -0
  39. package/src/js/databar/stacked.js +528 -0
  40. package/src/js/dotcode/decoder.js +548 -0
  41. package/src/js/dotcode/detector.js +251 -0
  42. package/src/js/dotcode/encoder.js +453 -0
  43. package/src/js/dotcode/index.js +34 -0
  44. package/src/js/dotcode/tables.js +168 -0
  45. package/src/js/hanxin/decoder.js +299 -0
  46. package/src/js/hanxin/detector.js +122 -0
  47. package/src/js/hanxin/encoder.js +259 -0
  48. package/src/js/hanxin/index.js +16 -0
  49. package/src/js/hanxin/tables.js +316 -0
  50. package/src/js/image/height-coded.js +164 -0
  51. package/src/js/maxicode/decoder.js +275 -0
  52. package/src/js/maxicode/detector.js +118 -0
  53. package/src/js/maxicode/encoder.js +438 -0
  54. package/src/js/maxicode/index.js +34 -0
  55. package/src/js/maxicode/tables.js +132 -0
  56. package/src/js/oned/code25.js +148 -0
  57. package/src/js/oned/index.js +21 -3
  58. package/src/js/oned/postal.js +889 -0
  59. package/src/js/oned/reader.js +272 -7
  60. package/src/js/oned/telepen.js +361 -0
  61. package/src/js/oned/writers.js +186 -12
  62. package/src/js/stacked128/common.js +208 -0
  63. package/src/ts/codablockf/decoder.ts +212 -0
  64. package/src/ts/codablockf/encoder.ts +128 -0
  65. package/src/ts/codablockf/index.d.ts +31 -0
  66. package/src/ts/codablockf/index.ts +6 -0
  67. package/src/ts/code16k/decoder.d.ts +25 -0
  68. package/src/ts/code16k/decoder.ts +313 -0
  69. package/src/ts/code16k/detector.d.ts +21 -0
  70. package/src/ts/code16k/detector.ts +203 -0
  71. package/src/ts/code16k/encoder.d.ts +26 -0
  72. package/src/ts/code16k/encoder.ts +198 -0
  73. package/src/ts/code16k/index.d.ts +10 -0
  74. package/src/ts/code16k/index.ts +47 -0
  75. package/src/ts/code16k/tables.d.ts +54 -0
  76. package/src/ts/code16k/tables.ts +195 -0
  77. package/src/ts/composite/index.d.ts +74 -0
  78. package/src/ts/composite/index.ts +547 -0
  79. package/src/ts/core/detection-contract.d.ts +119 -0
  80. package/src/ts/core/detection-contract.ts +267 -0
  81. package/src/ts/core/symbol-layout.d.ts +108 -0
  82. package/src/ts/core/symbol-layout.ts +236 -0
  83. package/src/ts/databar/expanded.d.ts +40 -0
  84. package/src/ts/databar/expanded.ts +948 -0
  85. package/src/ts/databar/index.d.ts +48 -0
  86. package/src/ts/databar/index.ts +48 -0
  87. package/src/ts/databar/layout.d.ts +122 -0
  88. package/src/ts/databar/layout.ts +275 -0
  89. package/src/ts/databar/limited.d.ts +85 -0
  90. package/src/ts/databar/limited.ts +553 -0
  91. package/src/ts/databar/stacked-omnidirectional.d.ts +128 -0
  92. package/src/ts/databar/stacked-omnidirectional.ts +559 -0
  93. package/src/ts/databar/stacked.d.ts +96 -0
  94. package/src/ts/databar/stacked.ts +549 -0
  95. package/src/ts/dotcode/decoder.d.ts +58 -0
  96. package/src/ts/dotcode/decoder.ts +467 -0
  97. package/src/ts/dotcode/detector.d.ts +63 -0
  98. package/src/ts/dotcode/detector.ts +263 -0
  99. package/src/ts/dotcode/encoder.d.ts +69 -0
  100. package/src/ts/dotcode/encoder.ts +427 -0
  101. package/src/ts/dotcode/index.d.ts +37 -0
  102. package/src/ts/dotcode/index.ts +72 -0
  103. package/src/ts/dotcode/tables.d.ts +74 -0
  104. package/src/ts/dotcode/tables.ts +174 -0
  105. package/src/ts/hanxin/decoder.d.ts +48 -0
  106. package/src/ts/hanxin/decoder.ts +314 -0
  107. package/src/ts/hanxin/detector.d.ts +45 -0
  108. package/src/ts/hanxin/detector.ts +124 -0
  109. package/src/ts/hanxin/encoder.d.ts +41 -0
  110. package/src/ts/hanxin/encoder.ts +290 -0
  111. package/src/ts/hanxin/index.d.ts +16 -0
  112. package/src/ts/hanxin/index.ts +18 -0
  113. package/src/ts/hanxin/tables.d.ts +80 -0
  114. package/src/ts/hanxin/tables.ts +348 -0
  115. package/src/ts/image/height-coded.d.ts +101 -0
  116. package/src/ts/image/height-coded.ts +227 -0
  117. package/src/ts/index.d.ts +46 -2
  118. package/src/ts/index.ts +380 -13
  119. package/src/ts/maxicode/decoder.ts +265 -0
  120. package/src/ts/maxicode/detector.ts +109 -0
  121. package/src/ts/maxicode/encoder.ts +414 -0
  122. package/src/ts/maxicode/index.d.ts +96 -0
  123. package/src/ts/maxicode/index.ts +44 -0
  124. package/src/ts/maxicode/tables.ts +136 -0
  125. package/src/ts/oned/code25.d.ts +42 -0
  126. package/src/ts/oned/code25.ts +170 -0
  127. package/src/ts/oned/index.d.ts +7 -2
  128. package/src/ts/oned/index.ts +45 -3
  129. package/src/ts/oned/postal.d.ts +44 -0
  130. package/src/ts/oned/postal.ts +819 -0
  131. package/src/ts/oned/reader.d.ts +35 -0
  132. package/src/ts/oned/reader.ts +261 -7
  133. package/src/ts/oned/telepen.d.ts +65 -0
  134. package/src/ts/oned/telepen.ts +368 -0
  135. package/src/ts/oned/writers.d.ts +32 -0
  136. package/src/ts/oned/writers.ts +184 -13
  137. package/src/ts/stacked128/common.ts +225 -0
@@ -0,0 +1,453 @@
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
+ /** Original dependency-free DotCode encoder. @module dotcode/encoder */
31
+ import { BitMatrix } from '../core/bit-matrix.js';
32
+ import { EncodeError } from '../core/errors.js';
33
+ import { rsEncode } from '../core/reed-solomon.js';
34
+ import { DOTCODE_CODEWORD_COUNT, DOTCODE_FIELD_SIZE, DOTCODE_MAX_DIMENSION, DOTCODE_MASK_STEPS, DOTCODE_MIN_DIMENSION, GF113_DOTCODE, dotCodeActivePositions, dotCodeCodewordCapacity, dotCodeCornerOrder, dotCodeDataCapacity, dotCodeIsCorner, dotCodeIsDataPosition, dotCodePattern, } from './tables.js';
35
+ function bytesFor(value, encoding) {
36
+ if (value instanceof Uint8Array)
37
+ return new Uint8Array(value);
38
+ if (Array.isArray(value)) {
39
+ if (value.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
40
+ throw new EncodeError('DotCode: byte input must contain integers from 0 to 255');
41
+ }
42
+ return Uint8Array.from(value);
43
+ }
44
+ if (typeof value !== 'string' || value.length === 0) {
45
+ throw new EncodeError('DotCode: value must be a non-empty string or byte array');
46
+ }
47
+ if (encoding === 'utf8')
48
+ return new TextEncoder().encode(value);
49
+ const bytes = new Uint8Array(value.length);
50
+ for (let index = 0; index < value.length; index++) {
51
+ const code = value.charCodeAt(index);
52
+ if (code > 255)
53
+ throw new EncodeError(`DotCode: character U+${code.toString(16).padStart(4, '0')} does not fit ${encoding}`);
54
+ bytes[index] = code;
55
+ }
56
+ return bytes;
57
+ }
58
+ function digit(byte) { return byte >= 48 && byte <= 57; }
59
+ function hasDigitPair(bytes, index) {
60
+ return index + 1 < bytes.length && digit(bytes[index]) && digit(bytes[index + 1]);
61
+ }
62
+ function appendUpperShift(out, byte) {
63
+ if (byte < 128 || byte > 255)
64
+ throw new EncodeError('DotCode: upper-shift byte must be in 128..255');
65
+ if (byte < 160)
66
+ out.push(110, byte - 64);
67
+ else
68
+ out.push(111, byte - 160);
69
+ }
70
+ function appendCodeSetA(out, byte) {
71
+ if (byte > 95)
72
+ throw new EncodeError('DotCode: byte is not directly encodable in Code Set A');
73
+ out.push(byte < 32 ? byte + 64 : byte - 32);
74
+ }
75
+ function appendCodeSetB(out, byte) {
76
+ if (byte >= 32 && byte <= 127)
77
+ out.push(byte - 32);
78
+ else if (byte === 9)
79
+ out.push(97);
80
+ else if (byte === 28)
81
+ out.push(98);
82
+ else if (byte === 29)
83
+ out.push(99);
84
+ else if (byte === 30)
85
+ out.push(100);
86
+ else if (byte === 13)
87
+ out.push(96);
88
+ else
89
+ throw new EncodeError('DotCode: byte is not directly encodable in Code Set B');
90
+ }
91
+ /** Convert five base-259 bytes into six base-103 codewords (or a short tail). */
92
+ function appendBinaryGroup(out, bytes, start, count) {
93
+ let value = 0;
94
+ for (let index = 0; index < count; index++)
95
+ value = value * 259 + bytes[start + index];
96
+ const words = new Array(count + 1).fill(0);
97
+ for (let index = words.length - 1; index >= 0; index--) {
98
+ words[index] = value % 103;
99
+ value = Math.floor(value / 103);
100
+ }
101
+ if (value !== 0)
102
+ throw new EncodeError('DotCode: binary radix conversion overflow');
103
+ out.push(...words);
104
+ }
105
+ /**
106
+ * Encode the supported Code Set A/B/C subset and the complete binary-latch
107
+ * byte path. The result is deliberately deterministic and never depends on a
108
+ * third-party encoder.
109
+ */
110
+ function encodeDataCodewords(bytes, gs1, forceBinary) {
111
+ if (!bytes.length)
112
+ throw new EncodeError('DotCode: payload must not be empty');
113
+ const words = [];
114
+ let mode = 'C';
115
+ let position = 0;
116
+ let usedBinary = forceBinary;
117
+ // A leading numeric pair would be interpreted as GS1 by readers. FNC1 is
118
+ // the standard disambiguator for an ordinary numeric message.
119
+ if (!gs1 && hasDigitPair(bytes, 0))
120
+ words.push(107);
121
+ if (forceBinary) {
122
+ words.push(112);
123
+ usedBinary = true;
124
+ for (; position < bytes.length; position += 5)
125
+ appendBinaryGroup(words, bytes, position, Math.min(5, bytes.length - position));
126
+ return { words, encoding: 'binary' };
127
+ }
128
+ while (position < bytes.length) {
129
+ const byte = bytes[position];
130
+ if (mode === 'X') {
131
+ for (; position < bytes.length; position += 5)
132
+ appendBinaryGroup(words, bytes, position, Math.min(5, bytes.length - position));
133
+ break;
134
+ }
135
+ if (gs1 && byte === 29) {
136
+ words.push(107);
137
+ position++;
138
+ continue;
139
+ }
140
+ if (mode === 'C') {
141
+ if (hasDigitPair(bytes, position)) {
142
+ words.push((bytes[position] - 48) * 10 + bytes[position + 1] - 48);
143
+ position += 2;
144
+ }
145
+ else if (byte >= 128) {
146
+ if (bytes.length - position >= 5) {
147
+ words.push(112);
148
+ mode = 'X';
149
+ usedBinary = true;
150
+ }
151
+ else {
152
+ appendUpperShift(words, byte);
153
+ position++;
154
+ }
155
+ }
156
+ else if (byte < 32) {
157
+ words.push(101);
158
+ mode = 'A';
159
+ }
160
+ else {
161
+ words.push(106);
162
+ mode = 'B';
163
+ }
164
+ continue;
165
+ }
166
+ if (mode === 'A') {
167
+ if (hasDigitPair(bytes, position)) {
168
+ words.push(106);
169
+ mode = 'C';
170
+ continue;
171
+ }
172
+ if (byte >= 128) {
173
+ if (bytes.length - position >= 5) {
174
+ words.push(112);
175
+ mode = 'X';
176
+ usedBinary = true;
177
+ }
178
+ else {
179
+ appendUpperShift(words, byte);
180
+ position++;
181
+ }
182
+ }
183
+ else if (byte <= 95) {
184
+ appendCodeSetA(words, byte);
185
+ position++;
186
+ }
187
+ else {
188
+ words.push(102);
189
+ mode = 'B';
190
+ }
191
+ continue;
192
+ }
193
+ // Code Set B.
194
+ if (hasDigitPair(bytes, position)) {
195
+ words.push(106);
196
+ mode = 'C';
197
+ continue;
198
+ }
199
+ if (byte >= 128) {
200
+ if (bytes.length - position >= 5) {
201
+ words.push(112);
202
+ mode = 'X';
203
+ usedBinary = true;
204
+ }
205
+ else {
206
+ appendUpperShift(words, byte);
207
+ position++;
208
+ }
209
+ }
210
+ else if ((byte >= 32 && byte <= 127) || byte === 9 || byte === 28 || byte === 29 || byte === 30) {
211
+ appendCodeSetB(words, byte);
212
+ position++;
213
+ }
214
+ else {
215
+ words.push(102);
216
+ mode = 'A';
217
+ }
218
+ }
219
+ // Binary mode only occurs as the final payload segment above, but RS/data
220
+ // padding follows it. Terminate explicitly so the padding is not bytes.
221
+ if (usedBinary && mode === 'X')
222
+ words.push(109);
223
+ return { words, encoding: usedBinary ? 'binary' : 'latin1' };
224
+ }
225
+ function codewordEccLength(dataLength) { return 3 + Math.floor(dataLength / 2); }
226
+ function maxDataLength(codewordCapacity) {
227
+ return dotCodeDataCapacity(codewordCapacity);
228
+ }
229
+ function validateDimension(name, value) {
230
+ if (value === undefined)
231
+ return undefined;
232
+ if (!Number.isInteger(value) || value < DOTCODE_MIN_DIMENSION || value > DOTCODE_MAX_DIMENSION) {
233
+ throw new EncodeError(`DotCode: ${name} must be an integer in ${DOTCODE_MIN_DIMENSION}..${DOTCODE_MAX_DIMENSION}`);
234
+ }
235
+ return value;
236
+ }
237
+ function chooseDimensions(dataLength, options) {
238
+ const requestedWidth = validateDimension('width', options.width ?? options.columns);
239
+ const requestedHeight = validateDimension('height', options.height ?? options.rows);
240
+ if (options.aspectRatio !== undefined && (!Number.isFinite(options.aspectRatio) || options.aspectRatio <= 0)) {
241
+ throw new EncodeError('DotCode: aspectRatio must be a finite positive number');
242
+ }
243
+ if (requestedWidth !== undefined && requestedHeight !== undefined && ((requestedWidth + requestedHeight) & 1) === 0) {
244
+ throw new EncodeError('DotCode: width plus height must be odd');
245
+ }
246
+ const fits = (width, height) => {
247
+ if (((width + height) & 1) === 0)
248
+ return null;
249
+ const slots = dotCodeCodewordCapacity(width, height);
250
+ const capacity = maxDataLength(slots);
251
+ if (capacity < dataLength)
252
+ return null;
253
+ return { width, height, dataLength: capacity };
254
+ };
255
+ if (requestedWidth !== undefined || requestedHeight !== undefined) {
256
+ if (requestedWidth !== undefined && requestedHeight !== undefined) {
257
+ const result = fits(requestedWidth, requestedHeight);
258
+ if (!result)
259
+ throw new EncodeError('DotCode: payload does not fit the requested dimensions');
260
+ return result;
261
+ }
262
+ const fixed = requestedWidth ?? requestedHeight;
263
+ let best = null;
264
+ for (let varying = DOTCODE_MIN_DIMENSION; varying <= DOTCODE_MAX_DIMENSION; varying++) {
265
+ const width = requestedWidth === undefined ? varying : fixed;
266
+ const height = requestedWidth === undefined ? fixed : varying;
267
+ const candidate = fits(width, height);
268
+ if (candidate) {
269
+ best = candidate;
270
+ break;
271
+ }
272
+ }
273
+ if (!best)
274
+ throw new EncodeError('DotCode: payload does not fit the requested fixed dimension');
275
+ return best;
276
+ }
277
+ const aspect = options.aspectRatio ?? 1.5;
278
+ let best = null;
279
+ for (let height = DOTCODE_MIN_DIMENSION; height <= DOTCODE_MAX_DIMENSION; height++) {
280
+ for (let width = DOTCODE_MIN_DIMENSION; width <= DOTCODE_MAX_DIMENSION; width++) {
281
+ const candidate = fits(width, height);
282
+ if (!candidate)
283
+ continue;
284
+ const area = width * height;
285
+ const score = area * 100 + Math.abs(width / height - aspect) * 10 + (candidate.dataLength - dataLength);
286
+ if (!best || score < best.score)
287
+ best = { ...candidate, score };
288
+ }
289
+ }
290
+ if (!best)
291
+ throw new EncodeError('DotCode: payload exceeds the safe 200 by 200 implementation limit');
292
+ return best;
293
+ }
294
+ function rsProtected(data, mask) {
295
+ const step = DOTCODE_MASK_STEPS[mask];
296
+ const protectedData = [mask, ...data.map((value, index) => (value + step * index) % DOTCODE_FIELD_SIZE)];
297
+ const eccLength = codewordEccLength(data.length);
298
+ const total = protectedData.length + eccLength;
299
+ // DotCode interleaves Reed-Solomon blocks by residue class. A block is
300
+ // selected with `start + index * blockCount`, and its parity follows that
301
+ // block's data in the same residue class. Keeping the mask at wire index
302
+ // zero is important: it participates in the first RS block just like the
303
+ // public symbology procedure specifies.
304
+ const blockCount = Math.ceil(total / (DOTCODE_FIELD_SIZE - 1));
305
+ const wire = new Array(total).fill(0);
306
+ for (let block = 0; block < blockCount; block++) {
307
+ const blockData = Math.ceil((protectedData.length - block) / blockCount);
308
+ const blockTotal = Math.ceil((total - block) / blockCount);
309
+ const blockEcc = blockTotal - blockData;
310
+ const values = [];
311
+ for (let index = 0; index < blockData; index++)
312
+ values.push(protectedData[block + index * blockCount]);
313
+ const parity = rsEncode(values, blockEcc, GF113_DOTCODE, 1);
314
+ for (let index = 0; index < blockData; index++)
315
+ wire[block + index * blockCount] = values[index];
316
+ for (let index = 0; index < blockEcc; index++)
317
+ wire[block + blockData * blockCount + index * blockCount] = parity[index];
318
+ }
319
+ return wire;
320
+ }
321
+ function streamFor(width, height, codewords) {
322
+ const mask = codewords[0];
323
+ const bits = [(mask & 2) !== 0, (mask & 1) !== 0];
324
+ for (let index = 1; index < codewords.length; index++) {
325
+ const pattern = dotCodePattern(codewords[index]);
326
+ for (let bit = 8; bit >= 0; bit--)
327
+ bits.push(((pattern >>> bit) & 1) !== 0);
328
+ }
329
+ const active = dotCodeActivePositions(width, height);
330
+ if (bits.length > active)
331
+ throw new EncodeError('DotCode: codeword stream exceeds matrix capacity');
332
+ while (bits.length < active)
333
+ bits.push(true);
334
+ return bits;
335
+ }
336
+ function fold(width, height, stream) {
337
+ const matrix = new BitMatrix(width, height);
338
+ let position = 0;
339
+ if (height & 1) {
340
+ for (let row = 0; row < height; row++)
341
+ for (let column = 0; column < width; column++) {
342
+ if (!dotCodeIsDataPosition(column, row))
343
+ continue;
344
+ if (dotCodeIsCorner(column, row, width, height))
345
+ continue;
346
+ const targetY = height - row - 1;
347
+ matrix.setValue(column, targetY, stream[position++]);
348
+ }
349
+ }
350
+ else {
351
+ for (let column = 0; column < width; column++)
352
+ for (let row = 0; row < height; row++) {
353
+ if (!dotCodeIsDataPosition(column, row))
354
+ continue;
355
+ if (dotCodeIsCorner(column, row, width, height))
356
+ continue;
357
+ matrix.setValue(column, row, stream[position++]);
358
+ }
359
+ }
360
+ for (const [column, row] of dotCodeCornerOrder(width, height))
361
+ matrix.setValue(column, row, stream[position++]);
362
+ if (position !== stream.length)
363
+ throw new EncodeError('DotCode: fold did not consume the complete dot stream');
364
+ return matrix;
365
+ }
366
+ function maskScore(matrix) {
367
+ let score = 0;
368
+ for (let row = 0; row < matrix.height; row++) {
369
+ let count = 0;
370
+ for (let column = row & 1; column < matrix.width; column += 2)
371
+ if (matrix.get(column, row))
372
+ count++;
373
+ if (count)
374
+ score += 2 + Math.min(count, 10);
375
+ }
376
+ for (let column = 0; column < matrix.width; column++) {
377
+ let count = 0;
378
+ for (let row = column & 1; row < matrix.height; row += 2)
379
+ if (matrix.get(column, row))
380
+ count++;
381
+ if (count)
382
+ score += 2 + Math.min(count, 10);
383
+ }
384
+ for (let row = 0; row < matrix.height; row++)
385
+ for (let column = row & 1; column < matrix.width; column += 2) {
386
+ if (!matrix.get(column, row) &&
387
+ !matrix.get(column - 1, row - 1) && !matrix.get(column + 1, row - 1) &&
388
+ !matrix.get(column - 1, row + 1) && !matrix.get(column + 1, row + 1))
389
+ score -= 1;
390
+ }
391
+ return score;
392
+ }
393
+ function validateMask(value) {
394
+ if (value === undefined)
395
+ return undefined;
396
+ if (!Number.isInteger(value) || value < 0 || value > 3) {
397
+ throw new EncodeError('DotCode: mask must be an integer in 0..3');
398
+ }
399
+ return value;
400
+ }
401
+ function matrixForData(data, options, encoding, gs1) {
402
+ if (!data.length)
403
+ throw new EncodeError('DotCode: payload must not be empty');
404
+ if (data.some((value) => !Number.isInteger(value) || value < 0 || value >= DOTCODE_CODEWORD_COUNT)) {
405
+ throw new EncodeError(`DotCode: data codewords must be integers in 0..${DOTCODE_CODEWORD_COUNT - 1}`);
406
+ }
407
+ const dimensions = chooseDimensions(data.length, options);
408
+ while (data.length < dimensions.dataLength)
409
+ data.push(106);
410
+ const eccLength = codewordEccLength(data.length);
411
+ const selected = validateMask(options.mask);
412
+ let bestMask = selected ?? 0;
413
+ let bestMatrix = null;
414
+ if (selected === undefined) {
415
+ let bestScore = Number.NEGATIVE_INFINITY;
416
+ for (let mask = 0; mask < 4; mask++) {
417
+ const matrix = fold(dimensions.width, dimensions.height, streamFor(dimensions.width, dimensions.height, rsProtected(data, mask)));
418
+ const score = maskScore(matrix);
419
+ if (score >= bestScore) {
420
+ bestScore = score;
421
+ bestMask = mask;
422
+ bestMatrix = matrix;
423
+ }
424
+ }
425
+ }
426
+ const matrix = (bestMatrix ?? fold(dimensions.width, dimensions.height, streamFor(dimensions.width, dimensions.height, rsProtected(data, bestMask))));
427
+ matrix.dotcode = {
428
+ format: 'dotcode', width: dimensions.width, height: dimensions.height,
429
+ mask: bestMask, dataCodewords: data.length, errorCodewords: eccLength,
430
+ modulePositions: dotCodeActivePositions(dimensions.width, dimensions.height),
431
+ gs1, encoding,
432
+ };
433
+ return matrix;
434
+ }
435
+ /** Encode a string or byte payload into a DotCode module matrix. */
436
+ export function encodeDotCode(value, options = {}) {
437
+ const requestedEncoding = options.encoding ?? (value instanceof Uint8Array || Array.isArray(value) ? 'binary' : 'utf8');
438
+ const bytes = bytesFor(value, requestedEncoding);
439
+ if (!bytes.length)
440
+ throw new EncodeError('DotCode: payload must not be empty');
441
+ const encoded = encodeDataCodewords(bytes, options.gs1 === true, requestedEncoding === 'binary');
442
+ return matrixForData(encoded.words, options, encoded.encoding, options.gs1 === true);
443
+ }
444
+ /** Encode an explicit set of unmasked data codewords for conformance fixtures. */
445
+ export function encodeDotCodeCodewords(codewords, options = {}) {
446
+ if (!Array.isArray(codewords) && !(codewords instanceof Uint8Array))
447
+ throw new EncodeError('DotCode: codewords must be an array');
448
+ const data = Array.from(codewords);
449
+ if (!data.length || data.some((value) => !Number.isInteger(value) || value < 0 || value >= DOTCODE_CODEWORD_COUNT)) {
450
+ throw new EncodeError(`DotCode: codewords must be integers in 0..${DOTCODE_CODEWORD_COUNT - 1}`);
451
+ }
452
+ return matrixForData(data, options, 'binary', options.gs1 === true);
453
+ }
@@ -0,0 +1,34 @@
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
+ /** Public DotCode module surface. @module dotcode */
31
+ export { encodeDotCode, encodeDotCodeCodewords } from './encoder.js';
32
+ export { decodeDotCode } from './decoder.js';
33
+ export { detectDotCode, detectAndDecodeDotCode } from './detector.js';
34
+ export { DOTCODE_CODEWORD_COUNT, DOTCODE_FIELD_SIZE, DOTCODE_MASK_STEPS, DOTCODE_MAX_DIMENSION, DOTCODE_MIN_DIMENSION, DOTCODE_PATTERNS, dotCodeActivePositions, dotCodeCodeword, dotCodeCodewordCapacity, dotCodeCornerOrder, dotCodeDataCapacity, dotCodeIsCorner, dotCodeIsDataPosition, dotCodePattern, validateDotCodeTables, } from './tables.js';
@@ -0,0 +1,168 @@
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
+ * DotCode structural constants and the public 5-of-9 symbol assignment.
32
+ *
33
+ * The pattern assignment is a normative symbology table. It is recorded as
34
+ * compact hexadecimal values so the runtime does not need a third-party table
35
+ * or a generated dependency. Bit 8 is the first dot in the nine-dot pattern.
36
+ * Every entry contains exactly five dark dots.
37
+ *
38
+ * @module dotcode/tables
39
+ */
40
+ import { GaloisField } from '../core/galois-field.js';
41
+ export const DOTCODE_MIN_DIMENSION = 5;
42
+ export const DOTCODE_MAX_DIMENSION = 200;
43
+ export const DOTCODE_FIELD_SIZE = 113;
44
+ export const DOTCODE_CODEWORD_COUNT = 113;
45
+ export const DOTCODE_MIN_ECC = 3;
46
+ export const DOTCODE_MASK_STEPS = Object.freeze([0, 3, 7, 17]);
47
+ /** GF(113), with the primitive root required by the DotCode RS procedure. */
48
+ export const GF113_DOTCODE = new GaloisField({
49
+ size: DOTCODE_FIELD_SIZE,
50
+ prime: true,
51
+ generator: 3,
52
+ name: 'GF(113)/DotCode',
53
+ });
54
+ /**
55
+ * DotCode Annex C's 113 legal 5-of-9 patterns.
56
+ *
57
+ * This is format data, not executable code copied from an implementation.
58
+ * Keeping it in the authoritative TypeScript source makes the JS runtime and
59
+ * its declarations reproducible without a package-time generator.
60
+ */
61
+ export const DOTCODE_PATTERNS = Object.freeze([
62
+ 0x155, 0x0ab, 0x0ad, 0x0b5, 0x0d5, 0x156, 0x15a, 0x16a, 0x1aa, 0x0ae,
63
+ 0x0b6, 0x0ba, 0x0d6, 0x0da, 0x0ea, 0x12b, 0x12d, 0x135, 0x14b, 0x14d,
64
+ 0x153, 0x159, 0x165, 0x169, 0x195, 0x1a5, 0x1a9, 0x057, 0x05b, 0x05d,
65
+ 0x06b, 0x06d, 0x075, 0x097, 0x09b, 0x09d, 0x0a7, 0x0b3, 0x0b9, 0x0cb,
66
+ 0x0cd, 0x0d3, 0x0d9, 0x0e5, 0x0e9, 0x12e, 0x136, 0x13a, 0x14e, 0x15c,
67
+ 0x166, 0x16c, 0x172, 0x174, 0x196, 0x19a, 0x1a6, 0x1ac, 0x1b2, 0x1b4,
68
+ 0x1ca, 0x1d2, 0x1d4, 0x05e, 0x06e, 0x076, 0x07a, 0x09e, 0x0bc, 0x0ce,
69
+ 0x0dc, 0x0e6, 0x0ec, 0x0f2, 0x0f4, 0x117, 0x11b, 0x11d, 0x127, 0x133,
70
+ 0x139, 0x147, 0x163, 0x171, 0x18b, 0x18d, 0x193, 0x199, 0x1a3, 0x1b1,
71
+ 0x1c5, 0x1c9, 0x1d1, 0x02f, 0x037, 0x03b, 0x03d, 0x04f, 0x067, 0x073,
72
+ 0x079, 0x08f, 0x0c7, 0x0e3, 0x0f1, 0x11e, 0x13c, 0x178, 0x18e, 0x19c,
73
+ 0x1b8, 0x1c6, 0x1cc,
74
+ ]);
75
+ const PATTERN_TO_CODEWORD = new Map(DOTCODE_PATTERNS.map((pattern, value) => [pattern, value]));
76
+ /** Return the nine-dot pattern assigned to a codeword. */
77
+ export function dotCodePattern(codeword) {
78
+ if (!Number.isInteger(codeword) || codeword < 0 || codeword >= DOTCODE_CODEWORD_COUNT) {
79
+ throw new RangeError(`DotCode: codeword must be an integer in 0..${DOTCODE_CODEWORD_COUNT - 1}`);
80
+ }
81
+ return DOTCODE_PATTERNS[codeword];
82
+ }
83
+ /** Return a codeword for a nine-dot pattern, or -1 when it is not assigned. */
84
+ export function dotCodeCodeword(pattern) {
85
+ return PATTERN_TO_CODEWORD.get(pattern) ?? -1;
86
+ }
87
+ /** Number of active alternating positions in a W by H symbol. */
88
+ export function dotCodeActivePositions(width, height) {
89
+ return Math.floor((width * height) / 2);
90
+ }
91
+ /** Number of complete nine-bit codewords available after the mask bits. */
92
+ export function dotCodeCodewordCapacity(width, height) {
93
+ return Math.floor((dotCodeActivePositions(width, height) - 2) / 9);
94
+ }
95
+ /** DotCode uses alternating positions; the six corner positions carry tail bits. */
96
+ export function dotCodeIsDataPosition(column, row) {
97
+ return ((column + row) & 1) === 0;
98
+ }
99
+ /** Return true for one of the six corner positions used by the folded stream. */
100
+ export function dotCodeIsCorner(column, row, width, height) {
101
+ if (column === 0 && row === 0)
102
+ return true;
103
+ if (height & 1) {
104
+ if ((column === width - 2 && row === 0) || (column === width - 1 && row === 1))
105
+ return true;
106
+ if (column === 0 && row === height - 1)
107
+ return true;
108
+ }
109
+ else {
110
+ if (column === width - 1 && row === 0)
111
+ return true;
112
+ if ((column === 0 && row === height - 2) || (column === 1 && row === height - 1))
113
+ return true;
114
+ }
115
+ return (column === width - 2 && row === height - 1) ||
116
+ (column === width - 1 && row === height - 2);
117
+ }
118
+ /** Return the six corner coordinates in wire order for a canonical matrix. */
119
+ export function dotCodeCornerOrder(width, height) {
120
+ if (height & 1) {
121
+ return [
122
+ [width - 2, 0],
123
+ [width - 2, height - 1],
124
+ [width - 1, 1],
125
+ [width - 1, height - 2],
126
+ [0, 0],
127
+ [0, height - 1],
128
+ ];
129
+ }
130
+ return [
131
+ [width - 1, height - 2],
132
+ [0, height - 2],
133
+ [width - 2, height - 1],
134
+ [1, height - 1],
135
+ [width - 1, 0],
136
+ [0, 0],
137
+ ];
138
+ }
139
+ /** Largest data-codeword count that fits the supplied nine-bit capacity. */
140
+ export function dotCodeDataCapacity(codewordCapacity) {
141
+ if (!Number.isInteger(codewordCapacity) || codewordCapacity < DOTCODE_MIN_ECC + 1)
142
+ return 0;
143
+ let best = 0;
144
+ for (let data = 1; data <= codewordCapacity; data++) {
145
+ const ecc = DOTCODE_MIN_ECC + Math.floor(data / 2);
146
+ if (data + ecc <= codewordCapacity)
147
+ best = data;
148
+ }
149
+ return best;
150
+ }
151
+ /** Check public structural constants and pattern invariants. */
152
+ export function validateDotCodeTables() {
153
+ const errors = [];
154
+ if (DOTCODE_PATTERNS.length !== DOTCODE_CODEWORD_COUNT) {
155
+ errors.push(`pattern count ${DOTCODE_PATTERNS.length} is not ${DOTCODE_CODEWORD_COUNT}`);
156
+ }
157
+ const seen = new Set();
158
+ for (let value = 0; value < DOTCODE_PATTERNS.length; value++) {
159
+ const pattern = DOTCODE_PATTERNS[value];
160
+ if (seen.has(pattern))
161
+ errors.push(`pattern 0x${pattern.toString(16)} is duplicated`);
162
+ seen.add(pattern);
163
+ if (pattern < 0 || pattern > 0x1ff || pattern.toString(2).split('1').length - 1 !== 5) {
164
+ errors.push(`pattern ${value} is not a 5-of-9 value`);
165
+ }
166
+ }
167
+ return errors;
168
+ }