@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,368 @@
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
+ * Telepen Alpha and Telepen Numeric.
33
+ *
34
+ * Telepen does not assign an arbitrary glyph table to each character. It emits
35
+ * the seven-bit ASCII value with an even parity bit, least-significant bit
36
+ * first, and maps the resulting bit stream to narrow/wide bar-space pairs.
37
+ * Keeping that mapping algorithmic makes the implementation auditable and
38
+ * avoids shipping a copied third-party pattern table.
39
+ *
40
+ * @module oned/telepen
41
+ */
42
+
43
+ import { BitMatrix } from '../core/bit-matrix.js';
44
+ import { EncodeError } from '../core/errors.js';
45
+
46
+ export const TELEPEN_START_VALUE = 0x5f;
47
+ export const TELEPEN_STOP_VALUE = 0x7a;
48
+ export const TELEPEN_MAX_LENGTH = 500;
49
+
50
+ /** @param {number} value @returns {string} Run widths for one 16-module glyph. */
51
+ export function telepenPattern(value) {
52
+ if (!Number.isInteger(value) || value < 0 || value > 127) {
53
+ throw new RangeError(`Telepen character value must be in 0..127, got ${value}`);
54
+ }
55
+
56
+ // Telepen carries seven-bit ASCII plus an even parity bit in the eighth
57
+ // position. The stream is transmitted least-significant bit first.
58
+ const bits = new Array(8).fill(0);
59
+ let ones = 0;
60
+ for (let bit = 0; bit < 7; bit++) {
61
+ bits[bit] = (value >>> bit) & 1;
62
+ ones += bits[bit];
63
+ }
64
+ bits[7] = ones & 1;
65
+
66
+ // The four legal bar/space pairs encode the bit stream without changing the
67
+ // fixed 16-module character width:
68
+ // 1 -> narrow bar, narrow space (11)
69
+ // 00 -> wide bar, narrow space (31)
70
+ // 010 -> wide bar, wide space (33)
71
+ // 01/10 edges -> narrow bar, wide space (13)
72
+ let widths = '';
73
+ let index = 0;
74
+ while (index < bits.length) {
75
+ if (bits[index] === 1) {
76
+ widths += '11';
77
+ index++;
78
+ continue;
79
+ }
80
+
81
+ let end = index + 1;
82
+ while (end < bits.length && bits[end] === 1) end++;
83
+
84
+ if (end === index + 1) {
85
+ widths += '31';
86
+ index += 2;
87
+ } else if (end === index + 2) {
88
+ widths += '33';
89
+ index += 3;
90
+ } else if (end < bits.length) {
91
+ // A block 0 1* 0 longer than 010 has 01 and 10 edges. Any
92
+ // one-bits between those edges remain single-bit pairs.
93
+ widths += '13';
94
+ widths += '11'.repeat(Math.max(0, end - index - 3));
95
+ widths += '13';
96
+ index = end + 1;
97
+ } else {
98
+ // A final 0 1* block has no trailing zero. The final light element is
99
+ // implied by the following character or quiet zone.
100
+ widths += '13';
101
+ widths += '11'.repeat(Math.max(0, end - index - 2));
102
+ index = end;
103
+ }
104
+ }
105
+
106
+ return widths;
107
+ }
108
+
109
+ /** @param {string} widths @returns {number} */
110
+ function widthTotal(widths) {
111
+ let total = 0;
112
+ for (const width of widths) total += Number(width);
113
+ return total;
114
+ }
115
+
116
+ /** @param {string} widths @returns {BitMatrix} */
117
+ function widthsToMatrix(widths) {
118
+ const matrix = new BitMatrix(widthTotal(widths), 1);
119
+ let dark = true;
120
+ let x = 0;
121
+ for (const width of widths) {
122
+ const count = Number(width);
123
+ if (dark) matrix.setRegion(x, 0, count, 1);
124
+ x += count;
125
+ dark = !dark;
126
+ }
127
+ return matrix;
128
+ }
129
+
130
+ /** @param {string} value @returns {number} */
131
+ function telepenChecksum(value) {
132
+ let sum = 0;
133
+ for (const character of value) sum = (sum + character.charCodeAt(0)) % 127;
134
+ return (127 - sum) % 127;
135
+ }
136
+
137
+ /** @param {string} value @returns {number[]} */
138
+ function numericGlyphs(value) {
139
+ if (value.length % 2 !== 0) {
140
+ throw new EncodeError('Telepen Numeric: payload must contain an even number of characters');
141
+ }
142
+
143
+ const glyphs = [];
144
+ for (let index = 0; index < value.length; index += 2) {
145
+ const first = value[index];
146
+ const second = value[index + 1];
147
+ if (!/[0-9]/.test(first) || !/[0-9X]/.test(second)) {
148
+ throw new EncodeError(
149
+ 'Telepen Numeric: pairs must contain digits, with X allowed only in the second position',
150
+ );
151
+ }
152
+ const firstDigit = Number(first);
153
+ glyphs.push(second === 'X'
154
+ ? firstDigit + 17
155
+ : firstDigit * 10 + Number(second) + 27);
156
+ }
157
+ return glyphs;
158
+ }
159
+
160
+ /**
161
+ * Encode Telepen Alpha (full seven-bit ASCII) or Telepen Numeric.
162
+ *
163
+ * @param {string} value
164
+ * @param {object} [options]
165
+ * @param {boolean} [options.numeric] Use two-digit numeric compaction.
166
+ * @returns {BitMatrix}
167
+ */
168
+ export function encodeTelepen(value, options = {}) {
169
+ const text = String(value);
170
+ const numeric = options.numeric === true || options.mode === 'numeric' || options.telepenMode === 'numeric';
171
+ if (text.length > TELEPEN_MAX_LENGTH) {
172
+ throw new EncodeError(`Telepen: payload is limited to ${TELEPEN_MAX_LENGTH} characters`);
173
+ }
174
+
175
+ const glyphs = [TELEPEN_START_VALUE];
176
+ if (numeric) {
177
+ const compacted = numericGlyphs(text);
178
+ glyphs.push(...compacted);
179
+ const checksumValue = compacted.reduce((sum, glyph) => (sum + glyph) % 127, 0);
180
+ glyphs.push((127 - checksumValue) % 127);
181
+ } else {
182
+ for (const character of text) {
183
+ const code = character.charCodeAt(0);
184
+ if (code > 127) {
185
+ throw new EncodeError(`Telepen: character U+${code.toString(16).toUpperCase()} is outside ASCII`);
186
+ }
187
+ glyphs.push(code);
188
+ }
189
+ glyphs.push(telepenChecksum(text));
190
+ }
191
+ glyphs.push(TELEPEN_STOP_VALUE);
192
+
193
+ return widthsToMatrix(glyphs.map(telepenPattern).join(''));
194
+ }
195
+
196
+ /** Encode Telepen Numeric explicitly. @param {string} value @returns {BitMatrix} */
197
+ export function encodeTelepenNumeric(value) {
198
+ return encodeTelepen(value, { numeric: true });
199
+ }
200
+
201
+ const TELEPEN_RUNS = Array.from({ length: 128 }, (_, value) =>
202
+ telepenPattern(value).split('').map(Number)
203
+ );
204
+ const START_RUNS = TELEPEN_RUNS[TELEPEN_START_VALUE];
205
+ const STOP_RUNS = TELEPEN_RUNS[TELEPEN_STOP_VALUE];
206
+
207
+ /** @param {Uint8Array} row @param {number} start @param {number} count */
208
+ function measureRuns(row, start, count) {
209
+ if (start < 0 || start >= row.length || row[start] !== 1) return null;
210
+ const counters = new Array(count).fill(0);
211
+ let run = 0;
212
+ let dark = true;
213
+ let index = start;
214
+ while (index < row.length) {
215
+ const pixelDark = row[index] === 1;
216
+ if (pixelDark === dark) {
217
+ counters[run]++;
218
+ index++;
219
+ continue;
220
+ }
221
+ run++;
222
+ if (run === count) break;
223
+ dark = !dark;
224
+ counters[run] = 1;
225
+ index++;
226
+ }
227
+ if (run < count - 1 || counters[count - 1] === 0) return null;
228
+ return { counters, end: index };
229
+ }
230
+
231
+ /** @param {number[]} counters @param {number[]} expected @param {number} [ignoredTail] */
232
+ function runVariance(counters, expected, ignoredTail = 0) {
233
+ const total = counters.reduce((sum, width) => sum + width, 0);
234
+ const expectedTotal = expected.reduce((sum, width) => sum + width, 0);
235
+ const compared = expected.length - ignoredTail;
236
+ const comparedTotal = expected
237
+ .slice(0, compared)
238
+ .reduce((sum, width) => sum + width, 0);
239
+ const measuredCompared = counters
240
+ .slice(0, compared)
241
+ .reduce((sum, width) => sum + width, 0);
242
+ if (total <= 0 || expectedTotal <= 0 || measuredCompared <= 0) return Infinity;
243
+
244
+ // For normal glyphs all sixteen modules participate. Stop's final light
245
+ // run includes the quiet zone, so its first eleven runs establish scale.
246
+ const unit = ignoredTail > 0 ? measuredCompared / comparedTotal : total / expectedTotal;
247
+ const limit = unit * 0.85;
248
+ let variance = 0;
249
+ for (let index = 0; index < compared; index++) {
250
+ const delta = Math.abs(counters[index] - expected[index] * unit);
251
+ if (delta > limit) return Infinity;
252
+ variance += delta;
253
+ }
254
+ return variance / Math.max(1, measuredCompared);
255
+ }
256
+
257
+ /** @param {Uint8Array} row @param {number} start @param {number[]} expected */
258
+ function matchGlyph(row, start, expected) {
259
+ const measured = measureRuns(row, start, expected.length);
260
+ if (!measured) return null;
261
+ const score = runVariance(measured.counters, expected);
262
+ if (!Number.isFinite(score) || measured.end >= row.length) return null;
263
+ return { score, end: measured.end };
264
+ }
265
+
266
+ /** @param {Uint8Array} row @param {number} start */
267
+ function matchStop(row, start) {
268
+ const measured = measureRuns(row, start, STOP_RUNS.length);
269
+ if (!measured) return null;
270
+ const score = runVariance(measured.counters, STOP_RUNS, 1);
271
+ if (!Number.isFinite(score)) return null;
272
+ for (let index = measured.end; index < row.length; index++) {
273
+ if (row[index] === 1) return { score, end: measured.end, terminal: false };
274
+ }
275
+ return { score, end: measured.end, terminal: true };
276
+ }
277
+
278
+ /** @param {Uint8Array} row @param {number} start @param {number[]} expected */
279
+ function findStart(row, start, expected) {
280
+ for (let index = Math.max(0, start); index < row.length; index++) {
281
+ if (row[index] !== 1 || (index > 0 && row[index - 1] === 1)) continue;
282
+ const measured = measureRuns(row, index, expected.length);
283
+ if (!measured) continue;
284
+ const score = runVariance(measured.counters, expected);
285
+ if (Number.isFinite(score) && measured.end < row.length) {
286
+ return { end: measured.end, score };
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+
292
+ /** @param {number[]} glyphs @returns {boolean} */
293
+ function validChecksum(glyphs) {
294
+ if (glyphs.length < 1) return false;
295
+ const checksum = glyphs[glyphs.length - 1];
296
+ const payload = glyphs.slice(0, -1);
297
+ const sum = payload.reduce((total, value) => (total + value) % 127, 0);
298
+ return checksum === (127 - sum) % 127;
299
+ }
300
+
301
+ /** @param {number[]} glyphs @returns {string|null} */
302
+ function decodeNumericGlyphs(glyphs) {
303
+ let text = '';
304
+ for (const glyph of glyphs) {
305
+ if (glyph >= 17 && glyph <= 26) {
306
+ text += `${glyph - 17}X`;
307
+ } else if (glyph >= 27 && glyph <= 126) {
308
+ text += String(glyph - 27).padStart(2, '0');
309
+ } else {
310
+ return null;
311
+ }
312
+ }
313
+ return text;
314
+ }
315
+
316
+ /**
317
+ * Decode a Telepen scanline. The row must be binarized (1 = dark).
318
+ *
319
+ * @param {Uint8Array} row
320
+ * @param {object} [options]
321
+ * @param {boolean} [options.numeric] Decode two-digit numeric glyphs.
322
+ * @returns {{format:'telepen'|'telepennumeric', text:string, mode:'ascii'|'numeric'}|null}
323
+ */
324
+ export function decodeTelepen(row, options = {}) {
325
+ const numeric = options.numeric === true || options.mode === 'numeric' || options.telepenMode === 'numeric';
326
+ const start = findStart(row, 0, START_RUNS);
327
+ if (!start) return null;
328
+
329
+ const glyphs = [];
330
+ let offset = start.end;
331
+ for (let count = 0; count <= TELEPEN_MAX_LENGTH + 1; count++) {
332
+ const stop = matchStop(row, offset);
333
+ if (stop?.terminal) {
334
+ if (!validChecksum(glyphs)) return null;
335
+ glyphs.pop();
336
+ if (numeric) {
337
+ const text = decodeNumericGlyphs(glyphs);
338
+ return text === null ? null : { format: 'telepennumeric', text, mode: 'numeric' };
339
+ }
340
+ return {
341
+ format: 'telepen',
342
+ text: glyphs.map((value) => String.fromCharCode(value)).join(''),
343
+ mode: 'ascii',
344
+ };
345
+ }
346
+
347
+ const candidates = [];
348
+ for (let value = 0; value < TELEPEN_RUNS.length; value++) {
349
+ const match = matchGlyph(row, offset, TELEPEN_RUNS[value]);
350
+ if (match) candidates.push({ value, ...match });
351
+ }
352
+ if (candidates.length === 0) return null;
353
+ candidates.sort((a, b) => a.score - b.score);
354
+ const best = candidates[0];
355
+ const second = candidates[1];
356
+ // A close tie is an ambiguous optical measurement. Returning nothing is
357
+ // safer than guessing a character that merely has a similar run profile.
358
+ if (second && second.score - best.score < 0.035) return null;
359
+ glyphs.push(best.value);
360
+ offset = best.end;
361
+ }
362
+ return null;
363
+ }
364
+
365
+ /** Decode Telepen Numeric explicitly. @param {Uint8Array} row */
366
+ export function decodeTelepenNumeric(row) {
367
+ return decodeTelepen(row, { numeric: true });
368
+ }
@@ -142,6 +142,18 @@ export declare function encodeCode93(value: string): BitMatrix;
142
142
  export declare function encodeCode128(value: string, options?: {
143
143
  gs1?: boolean;
144
144
  }): BitMatrix;
145
+ /**
146
+ * Build the Code 128 data stream used by Code 128 and stacked Code 128
147
+ * symbologies, without a checksum or stop symbol.
148
+ */
149
+ export declare function code128DataCodewords(value: string, options?: {
150
+ gs1?: boolean;
151
+ startSet?: 'A' | 'B' | 'C';
152
+ }): {
153
+ start: number;
154
+ values: number[];
155
+ mode: 'A' | 'B' | 'C';
156
+ };
145
157
  /**
146
158
  * Interleaved 2 of 5.
147
159
  *
@@ -216,3 +228,23 @@ export declare function encodeMSI(value: string, options?: {
216
228
  * @returns {BitMatrix}
217
229
  */
218
230
  export declare function encodePharmacode(value: number | string): BitMatrix;
231
+ /** Italian Code 32 check digit for an eight-digit body. */
232
+ export declare function code32CheckDigit(value: string): number;
233
+ /** Encode an Italian Code 32 pharmaceutical identifier. */
234
+ export declare function encodeCode32(value: string): BitMatrix;
235
+ /** Decode and validate a six-character Code 32 payload. */
236
+ export declare function decodeCode32Payload(text: string): {
237
+ text: string;
238
+ checkDigit: number;
239
+ } | null;
240
+ /** Encode a PZN-7 or PZN-8 pharmaceutical identifier through Code 39. */
241
+ export declare function encodePZN(value: string, options?: {
242
+ pzn8?: boolean;
243
+ variant?: 'pzn7' | 'pzn8';
244
+ }): BitMatrix;
245
+ /** Decode and validate a PZN Code 39 payload. */
246
+ export declare function decodePZNPayload(text: string): {
247
+ text: string;
248
+ variant: 'pzn7' | 'pzn8';
249
+ checkDigit: number;
250
+ } | null;
@@ -46,7 +46,7 @@ import {
46
46
  EAN_START_END, EAN_MIDDLE, UPCE_END,
47
47
  CODE39, CODE39_CHECK_SET, CODE39_EXTENDED,
48
48
  CODE93, CODE93_VALUES, CODE93_START_STOP,
49
- CODE128, CODE128_START_B, CODE128_START_C,
49
+ CODE128, CODE128_START_A, CODE128_START_B, CODE128_START_C,
50
50
  CODE128_STOP, CODE128_FNC1, CODE128_CODE_A, CODE128_CODE_B, CODE128_CODE_C,
51
51
  ITF, CODABAR, CODABAR_START_STOP, CODE11, CODE11_START_STOP,
52
52
  MSI_BIT, MSI_START, MSI_STOP,
@@ -373,6 +373,147 @@ export function encodeCode39(value, options = {}) {
373
373
  return toMatrix(parts.join('0'));
374
374
  }
375
375
 
376
+ /* ------------------------------------------------------------------ *
377
+ * Code 32 / PZN
378
+ * ------------------------------------------------------------------ */
379
+
380
+ /** Code 32's six-character alphabet after its four skipped vowels. */
381
+ const CODE32_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUV';
382
+
383
+ /**
384
+ * Convert a Code 32 base-32 digit to the printed character set. The standard
385
+ * skips A, E, I and O so the human-readable text cannot be mistaken for a
386
+ * vowel-heavy word. The successive threshold shifts are intentionally
387
+ * expressed as rules rather than copied lookup data.
388
+ */
389
+ function code32PrintCharacter(character) {
390
+ let code = character.charCodeAt(0);
391
+ for (const vowel of 'AEIO') {
392
+ if (code >= vowel.charCodeAt(0)) code++;
393
+ }
394
+ return String.fromCharCode(code);
395
+ }
396
+
397
+ const CODE32_PRINT_ALPHABET = [...CODE32_ALPHABET].map(code32PrintCharacter).join('');
398
+ const CODE32_DECODE = new Map([...CODE32_PRINT_ALPHABET].map((ch, index) => [ch, index]));
399
+
400
+ /**
401
+ * Italian Code 32 (Italian Pharmacode) check digit.
402
+ *
403
+ * @param {string} value The eight-digit body, without the check digit.
404
+ * @returns {number}
405
+ */
406
+ export function code32CheckDigit(value) {
407
+ let sum = 0;
408
+ for (let i = 0; i < 8; i++) {
409
+ let digit = Number(value[i]);
410
+ if (i % 2 === 1) {
411
+ digit *= 2;
412
+ if (digit > 9) digit -= 9;
413
+ }
414
+ sum += digit;
415
+ }
416
+ return sum % 10;
417
+ }
418
+
419
+ /**
420
+ * Encode the Italian Code 32 pharmaceutical identifier through Code 39.
421
+ * Accepts the eight-digit body or the same body followed by its check digit.
422
+ *
423
+ * @param {string} value Eight or nine digits.
424
+ * @returns {BitMatrix}
425
+ */
426
+ export function encodeCode32(value) {
427
+ const digits = String(value);
428
+ if (!/^\d{8,9}$/.test(digits)) {
429
+ throw new EncodeError('Code 32: payload must contain 8 or 9 digits');
430
+ }
431
+ const body = digits.slice(0, 8);
432
+ const check = code32CheckDigit(body);
433
+ if (digits.length === 9 && Number(digits[8]) !== check) {
434
+ throw new EncodeError(`Code 32: invalid check digit ${digits[8]}, expected ${check}`);
435
+ }
436
+ const complete = body + String(check);
437
+ let base32 = BigInt(complete).toString(32).toUpperCase().padStart(6, '0');
438
+ base32 = [...base32].map(code32PrintCharacter).join('');
439
+ return encodeCode39(base32);
440
+ }
441
+
442
+ /**
443
+ * Decode the Code 39 payload of an Italian Code 32 symbol.
444
+ *
445
+ * This helper is deliberately kept separate from the image reader so it can
446
+ * validate the numeric/check-digit grammar without introducing a module cycle.
447
+ *
448
+ * @param {string} text Code 39 payload, excluding its asterisks.
449
+ * @returns {{text:string, checkDigit:number}|null}
450
+ */
451
+ export function decodeCode32Payload(text) {
452
+ if (text.length !== 6) return null;
453
+ let value = 0n;
454
+ for (const ch of text) {
455
+ const digit = CODE32_DECODE.get(ch);
456
+ if (digit === undefined) return null;
457
+ value = value * 32n + BigInt(digit);
458
+ }
459
+ const complete = value.toString(10).padStart(9, '0');
460
+ if (complete.length !== 9) return null;
461
+ const body = complete.slice(0, 8);
462
+ const checkDigit = code32CheckDigit(body);
463
+ if (Number(complete[8]) !== checkDigit) return null;
464
+ return { text: body, checkDigit };
465
+ }
466
+
467
+ function pznCheckDigit(body, pzn8) {
468
+ let sum = 0;
469
+ const offset = pzn8 ? 1 : 2;
470
+ for (let i = 0; i < body.length; i++) sum += Number(body[i]) * (i + offset);
471
+ const check = sum % 11;
472
+ return check === 10 ? null : check;
473
+ }
474
+
475
+ /**
476
+ * Encode a Pharmazentralnummer (PZN-7 or PZN-8) through Code 39.
477
+ *
478
+ * @param {string} value Six/seven digits for PZN-7, or seven/eight for PZN-8.
479
+ * @param {object} [options]
480
+ * @param {boolean} [options.pzn8=false] Select the modern eight-digit body.
481
+ * @param {'pzn7'|'pzn8'} [options.variant] Alias for `pzn8`.
482
+ * @returns {BitMatrix}
483
+ */
484
+ export function encodePZN(value, options = {}) {
485
+ const pzn8 = options.pzn8 === true || options.variant === 'pzn8';
486
+ const digits = String(value);
487
+ const bodyLength = pzn8 ? 7 : 6;
488
+ if (!new RegExp(`^\\d{${bodyLength},${bodyLength + 1}}$`).test(digits)) {
489
+ throw new EncodeError(`PZN-${pzn8 ? 8 : 7}: payload must contain ${bodyLength} or ${bodyLength + 1} digits`);
490
+ }
491
+ const body = digits.slice(0, bodyLength);
492
+ const check = pznCheckDigit(body, pzn8);
493
+ if (check === null) throw new EncodeError('PZN: input sequence produces the reserved check value 10');
494
+ if (digits.length === bodyLength + 1 && Number(digits[bodyLength]) !== check) {
495
+ throw new EncodeError(`PZN: invalid check digit ${digits[bodyLength]}, expected ${check}`);
496
+ }
497
+ return encodeCode39(`-${body}${check}`);
498
+ }
499
+
500
+ /**
501
+ * Decode the Code 39 payload of a PZN symbol.
502
+ *
503
+ * @param {string} text Code 39 payload, excluding its asterisks.
504
+ * @returns {{text:string, variant:'pzn7'|'pzn8', checkDigit:number}|null}
505
+ */
506
+ export function decodePZNPayload(text) {
507
+ if (!/^-\d+$/.test(text)) return null;
508
+ const digits = text.slice(1);
509
+ const pzn8 = digits.length === 8;
510
+ if (digits.length !== 7 && !pzn8) return null;
511
+ const body = digits.slice(0, -1);
512
+ const checkDigit = pznCheckDigit(body, pzn8);
513
+ if (checkDigit === null || Number(digits.at(-1)) !== checkDigit) return null;
514
+ return { text: body, variant: pzn8 ? 'pzn8' : 'pzn7', checkDigit };
515
+ }
516
+
376
517
  /* ------------------------------------------------------------------ *
377
518
  * Code 93
378
519
  * ------------------------------------------------------------------ */
@@ -421,20 +562,19 @@ export function encodeCode93(value) {
421
562
  const CODE128_B_OFFSET = 32;
422
563
 
423
564
  /**
424
- * Code 128, with automatic code-set selection.
565
+ * Build the Code 128 data stream without the checksum and stop symbol.
425
566
  *
426
- * The heuristic: switch into set C when enough consecutive digits are present
427
- * to repay the switch symbol four at the start or end of the payload, six in
428
- * the middle, since C packs two digits per symbol. An encoder that never
429
- * switches produces a valid but needlessly wide symbol.
567
+ * Stacked Code 128 symbologies use the same A/B/C data alphabet but provide
568
+ * their own row framing and checks. Keeping the tokeniser here makes those
569
+ * writers use the exact same ASCII and numeric rules as the ordinary Code 128
570
+ * writer. `startSet` is an optional explicit starting set for a stacked row.
430
571
  *
431
572
  * @param {string} value
432
- * @param {object} [options]
433
- * @param {boolean} [options.gs1] Emit a leading FNC1, making this GS1-128.
434
- * @returns {BitMatrix}
573
+ * @param {{gs1?: boolean, startSet?: 'A'|'B'|'C'}} [options]
574
+ * @returns {{start: number, values: number[], mode: 'A'|'B'|'C'}}
435
575
  */
436
- export function encodeCode128(value, options = {}) {
437
- const { gs1 = false } = options;
576
+ export function code128DataCodewords(value, options = {}) {
577
+ const { gs1 = false, startSet = null } = options;
438
578
  for (const ch of value) {
439
579
  if (ch.charCodeAt(0) > 127) {
440
580
  throw new EncodeError(`Code 128: '${ch}' is outside ASCII`);
@@ -451,9 +591,20 @@ export function encodeCode128(value, options = {}) {
451
591
  const codes = [];
452
592
  let mode;
453
593
  let i = 0;
454
-
455
594
  const startRun = digitRun(0);
456
- if (startRun >= 4 && startRun % 2 === 0) {
595
+ if (startSet === 'A') {
596
+ codes.push(CODE128_START_A);
597
+ mode = 'A';
598
+ } else if (startSet === 'C') {
599
+ if (startRun < 2 || startRun % 2 !== 0) {
600
+ throw new EncodeError('Code 128: set C requires an even leading digit run');
601
+ }
602
+ codes.push(CODE128_START_C);
603
+ mode = 'C';
604
+ } else if (startSet === 'B') {
605
+ codes.push(CODE128_START_B);
606
+ mode = 'B';
607
+ } else if (startRun >= 4 && startRun % 2 === 0) {
457
608
  codes.push(CODE128_START_C);
458
609
  mode = 'C';
459
610
  } else {
@@ -509,6 +660,26 @@ export function encodeCode128(value, options = {}) {
509
660
  i++;
510
661
  }
511
662
 
663
+ return { start: codes[0], values: codes.slice(1), mode };
664
+ }
665
+
666
+ /**
667
+ * Code 128, with automatic code-set selection.
668
+ *
669
+ * The heuristic: switch into set C when enough consecutive digits are present
670
+ * to repay the switch symbol — four at the start or end of the payload, six in
671
+ * the middle, since C packs two digits per symbol. An encoder that never
672
+ * switches produces a valid but needlessly wide symbol.
673
+ *
674
+ * @param {string} value
675
+ * @param {object} [options]
676
+ * @param {boolean} [options.gs1] Emit a leading FNC1, making this GS1-128.
677
+ * @returns {BitMatrix}
678
+ */
679
+ export function encodeCode128(value, options = {}) {
680
+ const data = code128DataCodewords(value, options);
681
+ const codes = [data.start, ...data.values];
682
+
512
683
  // Checksum: the start value plus each symbol weighted by its position.
513
684
  let sum = codes[0];
514
685
  for (let k = 1; k < codes.length; k++) sum += codes[k] * k;