@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,187 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2026 Sythos
6
+ * SPDX-License-Identifier: MIT
7
+ *
8
+ * Original work. No code from any other barcode implementation.
9
+ */
10
+ /** Conservative integer-module Code 16K detector. @module code16k/detector */
11
+ import { BitMatrix } from '../core/bit-matrix.js';
12
+ import { code128PatternModules, renderStackedRows, sampleModules, scanStackedRows, } from '../stacked128/common.js';
13
+ import { code16kStartModules, code16kStopModules } from './tables.js';
14
+ import { decodeCode16K, decodeCode16KRow } from './decoder.js';
15
+ function rowModules(row, values) {
16
+ return code16kStartModules(row) + '1' + values.map((value) => code128PatternModules(value)).join('') +
17
+ code16kStopModules(row);
18
+ }
19
+ function rotateClockwise(source) {
20
+ const rotated = new BitMatrix(source.height, source.width);
21
+ for (let y = 0; y < source.height; y++) {
22
+ for (let x = 0; x < source.width; x++) {
23
+ if (source.get(x, y))
24
+ rotated.set(source.height - 1 - y, x);
25
+ }
26
+ }
27
+ return rotated;
28
+ }
29
+ function cornersFor(x, y, width, height) {
30
+ return [
31
+ { x, y }, { x: x + width, y },
32
+ { x: x + width, y: y + height }, { x, y: y + height },
33
+ ];
34
+ }
35
+ function rowHasDarkPixels(image, y, x, width) {
36
+ if (y < 0 || y >= image.height)
37
+ return false;
38
+ for (let offset = 0; offset < width; offset++)
39
+ if (!image.get(x + offset, y))
40
+ return false;
41
+ return true;
42
+ }
43
+ function contiguousSpan(values) {
44
+ if (!values.length)
45
+ return 0;
46
+ const sorted = [...new Set(values)].sort((a, b) => a - b);
47
+ let best = 1;
48
+ let current = 1;
49
+ for (let index = 1; index < sorted.length; index++) {
50
+ if (sorted[index] === sorted[index - 1] + 1)
51
+ current++;
52
+ else {
53
+ best = Math.max(best, current);
54
+ current = 1;
55
+ }
56
+ }
57
+ return Math.max(best, current);
58
+ }
59
+ function parseSample(row, start, moduleSize) {
60
+ const modules = sampleModules(row, start, moduleSize, 70);
61
+ if (!modules)
62
+ return null;
63
+ const parsed = decodeCode16KRow(modules);
64
+ return parsed ? { ...parsed, modules } : null;
65
+ }
66
+ function detectOriented(image, options) {
67
+ const requestedScale = options.moduleSize;
68
+ if (requestedScale !== undefined &&
69
+ (!Number.isInteger(requestedScale) || requestedScale < 1 || requestedScale > 32))
70
+ return null;
71
+ const maxModules = 70 * (requestedScale ?? 32);
72
+ const found = scanStackedRows(image, 70, parseSample, maxModules)
73
+ .filter((candidate) => requestedScale === undefined || candidate.moduleSize === requestedScale);
74
+ const groups = new Map();
75
+ for (const candidate of found) {
76
+ const key = `${candidate.x}:${candidate.moduleSize}`;
77
+ let group = groups.get(key);
78
+ if (!group) {
79
+ group = { x: candidate.x, moduleSize: candidate.moduleSize, rows: new Map() };
80
+ groups.set(key, group);
81
+ }
82
+ let values = group.rows.get(candidate.parsed.row);
83
+ if (!values) {
84
+ values = [];
85
+ group.rows.set(candidate.parsed.row, values);
86
+ }
87
+ values.push({ y: candidate.y, parsed: candidate.parsed });
88
+ }
89
+ for (const group of groups.values()) {
90
+ const firstCandidates = group.rows.get(0);
91
+ if (!firstCandidates?.length)
92
+ continue;
93
+ for (const first of firstCandidates) {
94
+ const encodedRows = Math.floor(first.parsed.values[0] / 7) + 2;
95
+ if (encodedRows < 2 || encodedRows > 16 || group.rows.size !== encodedRows)
96
+ continue;
97
+ const chosen = [];
98
+ let complete = true;
99
+ for (let row = 0; row < encodedRows; row++) {
100
+ const entries = group.rows.get(row);
101
+ if (!entries?.length) {
102
+ complete = false;
103
+ break;
104
+ }
105
+ const sorted = entries.slice().sort((a, b) => a.y - b.y);
106
+ chosen.push({ y: sorted[0].y, parsed: sorted[0].parsed });
107
+ }
108
+ if (!complete)
109
+ continue;
110
+ const scale = group.moduleSize;
111
+ const spans = chosen.map((entry) => {
112
+ const entries = group.rows.get(entry.parsed.row) ?? [];
113
+ return contiguousSpan(entries.map((candidate) => candidate.y));
114
+ });
115
+ if (!spans.every((span) => span === spans[0]) || spans[0] < scale || spans[0] % scale !== 0)
116
+ continue;
117
+ const rowHeight = spans[0] / scale;
118
+ const firstY = chosen[0].y;
119
+ const steps = chosen.slice(1).map((entry, index) => entry.y - chosen[index].y);
120
+ if (!steps.length || !steps.every((step) => step === steps[0]) || steps[0] % scale !== 0)
121
+ continue;
122
+ const separatorHeight = steps[0] / scale - rowHeight;
123
+ if (separatorHeight < 1 || separatorHeight > 16)
124
+ continue;
125
+ if (options.rowHeight !== undefined && rowHeight !== options.rowHeight)
126
+ continue;
127
+ if (options.separatorHeight !== undefined && separatorHeight !== options.separatorHeight)
128
+ continue;
129
+ const modules = chosen.map((entry) => rowModules(entry.parsed.row, entry.parsed.values));
130
+ let matrix;
131
+ try {
132
+ matrix = renderStackedRows(modules, rowHeight, separatorHeight);
133
+ const result = decodeCode16K(matrix, { rowHeight, separatorHeight, rows: encodedRows });
134
+ const topSeparator = rowHasDarkPixels(image, firstY - scale, group.x, 70 * scale) ? scale : 0;
135
+ const last = chosen[chosen.length - 1];
136
+ const bottomY = last.y + spans[spans.length - 1];
137
+ const bottomSeparator = rowHasDarkPixels(image, bottomY, group.x, 70 * scale) ? scale : 0;
138
+ const boundsY = firstY - topSeparator;
139
+ const boundsHeight = (bottomY + bottomSeparator) - boundsY;
140
+ return {
141
+ ...result,
142
+ matrix,
143
+ corners: cornersFor(group.x, boundsY, 70 * scale, boundsHeight),
144
+ rotation: 0,
145
+ moduleSize: scale,
146
+ };
147
+ }
148
+ catch {
149
+ // A candidate is accepted only after all rows and checks decode.
150
+ }
151
+ }
152
+ }
153
+ return null;
154
+ }
155
+ /**
156
+ * Detect and decode one Code 16K symbol from a binarized image.
157
+ *
158
+ * The detector is intentionally conservative and enumerates integer module
159
+ * scales and the four orthogonal orientations. Non-integer perspective and
160
+ * photographic threshold recovery remain the responsibility of the generic
161
+ * image pipeline.
162
+ */
163
+ export function detectCode16K(binaryImage, options = {}) {
164
+ if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function')
165
+ return null;
166
+ let oriented = binaryImage;
167
+ let toOriginal = (point) => ({ x: point.x, y: point.y });
168
+ for (let rotation = 0; rotation < 4; rotation++) {
169
+ const found = detectOriented(oriented, options);
170
+ if (found) {
171
+ return {
172
+ ...found,
173
+ corners: found.corners.map(toOriginal),
174
+ rotation: rotation * 90,
175
+ };
176
+ }
177
+ const previous = oriented;
178
+ const previousToOriginal = toOriginal;
179
+ oriented = rotateClockwise(previous);
180
+ toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
181
+ }
182
+ return null;
183
+ }
184
+ /** Alias matching the detector naming used by the other format modules. */
185
+ export function detectAndDecodeCode16K(binaryImage, options = {}) {
186
+ return detectCode16K(binaryImage, options);
187
+ }
@@ -0,0 +1,153 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2026 Sythos
6
+ * SPDX-License-Identifier: MIT
7
+ *
8
+ * Original work. No code from any other barcode implementation.
9
+ */
10
+ import { EncodeError } from '../core/errors.js';
11
+ import { code128DataCodewords, } from '../oned/writers.js';
12
+ import { renderStackedRows } from '../stacked128/common.js';
13
+ import { CODE16K_CODEWORD_WIDTH, CODE16K_PAD, CODE16K_SYMBOLS_PER_ROW, code16kModeInfo, code16kStartModules, code16kStopModules, validateCode16KOptions, } from './tables.js';
14
+ import { code128PatternModules } from '../stacked128/common.js';
15
+ function asciiText(value) {
16
+ if (typeof value === 'string') {
17
+ if (value.length > 4096)
18
+ throw new EncodeError('Code 16K: value is too long for the 16-row symbol limit');
19
+ for (const character of value) {
20
+ if (character.codePointAt(0) > 127) {
21
+ throw new EncodeError('Code 16K: value must contain seven-bit ASCII characters');
22
+ }
23
+ }
24
+ return value;
25
+ }
26
+ const bytes = value instanceof Uint8Array ? Array.from(value) :
27
+ Array.isArray(value) ? value : null;
28
+ if (!bytes)
29
+ throw new EncodeError('Code 16K: value must be text or seven-bit bytes');
30
+ if (bytes.length > 4096)
31
+ throw new EncodeError('Code 16K: value is too long for the 16-row symbol limit');
32
+ if (bytes.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 127)) {
33
+ throw new EncodeError('Code 16K: byte input must contain values from 0 to 127');
34
+ }
35
+ return bytes.map((byte) => String.fromCharCode(byte)).join('');
36
+ }
37
+ function checksumOne(values) {
38
+ let sum = 0;
39
+ for (let index = 0; index < values.length; index++)
40
+ sum += (index + 2) * values[index];
41
+ return sum % 107;
42
+ }
43
+ function checksumTwo(valuesThroughC1) {
44
+ let sum = 0;
45
+ for (let index = 0; index < valuesThroughC1.length; index++) {
46
+ sum += (index + 1) * valuesThroughC1[index];
47
+ }
48
+ return sum % 107;
49
+ }
50
+ function rowModules(row, values) {
51
+ if (values.length !== CODE16K_SYMBOLS_PER_ROW) {
52
+ throw new EncodeError('Code 16K: every row must contain exactly five symbol characters');
53
+ }
54
+ let modules = code16kStartModules(row) + '1';
55
+ for (const value of values) {
56
+ if (!Number.isInteger(value) || value < 0 || value > 106) {
57
+ throw new EncodeError(`Code 16K: invalid Code 128 symbol value ${value}`);
58
+ }
59
+ const symbol = code128PatternModules(value);
60
+ if (symbol.length !== CODE16K_CODEWORD_WIDTH) {
61
+ throw new EncodeError('Code 16K: Code 128 symbol width invariant failed');
62
+ }
63
+ modules += symbol;
64
+ }
65
+ modules += code16kStopModules(row);
66
+ if (modules.length !== 70)
67
+ throw new EncodeError('Code 16K: internal row width mismatch');
68
+ return modules;
69
+ }
70
+ /**
71
+ * Encode a Code 16K symbol.
72
+ *
73
+ * The implementation accepts seven-bit ASCII and the standard Code 128 A/B/C
74
+ * data sets. It chooses the smallest legal row count unless `rows` is given.
75
+ * The first symbol character carries the row count and mode; C1 and C2 are
76
+ * calculated over every preceding symbol character exactly as specified.
77
+ *
78
+ * @param {string|Uint8Array|number[]} value
79
+ * @param {Code16KEncodeOptions} [options]
80
+ * @returns {Code16KMatrix}
81
+ */
82
+ export function encodeCode16K(value, options = {}) {
83
+ const text = asciiText(value);
84
+ if (text.length === 0)
85
+ throw new EncodeError('Code 16K: value must not be empty');
86
+ const validated = validateCode16KOptions(options);
87
+ const info = code16kModeInfo(options.mode, options.gs1 === true);
88
+ const tokenized = code128DataCodewords(text, {
89
+ gs1: info.gs1,
90
+ startSet: info.startSet,
91
+ });
92
+ const data = tokenized.values.slice();
93
+ const minimumRows = Math.max(2, Math.ceil((data.length + 3) / CODE16K_SYMBOLS_PER_ROW));
94
+ const rows = validated.rows ?? minimumRows;
95
+ if (rows < 2 || rows > 16) {
96
+ throw new EncodeError('Code 16K: payload needs between 2 and 16 rows');
97
+ }
98
+ const capacity = rows * CODE16K_SYMBOLS_PER_ROW - 3;
99
+ if (data.length > capacity) {
100
+ throw new EncodeError(`Code 16K: payload needs ${data.length} data symbols but ${capacity} fit in ${rows} rows`);
101
+ }
102
+ const first = 7 * (rows - 2) + info.mode;
103
+ if (first > 104)
104
+ throw new EncodeError('Code 16K: row count and mode do not fit the S symbol');
105
+ while (data.length < capacity)
106
+ data.push(CODE16K_PAD);
107
+ const beforeChecks = [first, ...data];
108
+ const c1 = checksumOne(beforeChecks);
109
+ const c2 = checksumTwo([...beforeChecks, c1]);
110
+ const codewords = [...beforeChecks, c1, c2];
111
+ if (codewords.length !== rows * CODE16K_SYMBOLS_PER_ROW) {
112
+ throw new EncodeError('Code 16K: internal codeword packing mismatch');
113
+ }
114
+ const rowsModules = [];
115
+ for (let row = 0; row < rows; row++) {
116
+ rowsModules.push(rowModules(row, codewords.slice(row * CODE16K_SYMBOLS_PER_ROW, (row + 1) * CODE16K_SYMBOLS_PER_ROW)));
117
+ }
118
+ const matrix = renderStackedRows(rowsModules, validated.rowHeight, validated.separatorHeight);
119
+ matrix.code16k = {
120
+ format: 'code16k',
121
+ rows,
122
+ columns: CODE16K_SYMBOLS_PER_ROW,
123
+ rowHeight: validated.rowHeight,
124
+ separatorHeight: validated.separatorHeight,
125
+ mode: info.mode,
126
+ codewords,
127
+ checksum: true,
128
+ };
129
+ return matrix;
130
+ }
131
+ /** Return the dimensions selected for a Code 16K payload without rendering it. */
132
+ export function code16kDimensions(value, options = {}) {
133
+ const text = asciiText(value);
134
+ if (text.length === 0)
135
+ throw new EncodeError('Code 16K: value must not be empty');
136
+ const validated = validateCode16KOptions(options);
137
+ const info = code16kModeInfo(options.mode, options.gs1 === true);
138
+ const tokenized = code128DataCodewords(text, { gs1: info.gs1, startSet: info.startSet });
139
+ const dataLength = tokenized.values.length;
140
+ const rows = validated.rows ?? Math.max(2, Math.ceil((dataLength + 3) / CODE16K_SYMBOLS_PER_ROW));
141
+ const capacity = rows * CODE16K_SYMBOLS_PER_ROW - 3;
142
+ if (rows > 16 || dataLength > capacity) {
143
+ throw new EncodeError('Code 16K: payload does not fit the requested row count');
144
+ }
145
+ return {
146
+ rows,
147
+ columns: CODE16K_SYMBOLS_PER_ROW,
148
+ width: 70,
149
+ height: 2 + rows * validated.rowHeight + (rows - 1) * validated.separatorHeight,
150
+ rowHeight: validated.rowHeight,
151
+ separatorHeight: validated.separatorHeight,
152
+ };
153
+ }
@@ -0,0 +1,14 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2026 Sythos
6
+ * SPDX-License-Identifier: MIT
7
+ *
8
+ * Original work. No code from any other barcode implementation.
9
+ */
10
+ /** Code 16K public module exports. @module code16k */
11
+ export { CODE16K_MIN_ROWS, CODE16K_MAX_ROWS, CODE16K_SYMBOLS_PER_ROW, CODE16K_MODULE_WIDTH, CODE16K_START_WIDTH, CODE16K_GUARD_WIDTH, CODE16K_CODEWORD_WIDTH, CODE16K_STOP_WIDTH, CODE16K_DEFAULT_ROW_HEIGHT, CODE16K_DEFAULT_SEPARATOR_HEIGHT, CODE16K_PAD, CODE16K_ROW_PAIRS, CODE16K_START_WIDTHS, code16kModeNumber, code16kModeInfo, code16kStartModules, code16kStopModules, code16kWidth, code16kGeometry, validateCode16KOptions, validateCode16KTables, } from './tables.js';
12
+ export { encodeCode16K, code16kDimensions } from './encoder.js';
13
+ export { decodeCode16K, decodeCode16KRow } from './decoder.js';
14
+ export { detectCode16K, detectAndDecodeCode16K } from './detector.js';
@@ -0,0 +1,152 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2026 Sythos
6
+ * SPDX-License-Identifier: MIT
7
+ *
8
+ * Original work. No code from any other barcode implementation.
9
+ */
10
+ /**
11
+ * Structural facts and option validation for Code 16K.
12
+ *
13
+ * A Code 16K row is 70 modules wide: a seven-module start pattern, a one
14
+ * module guard bar, five eleven-module Code 128 symbols and a seven-module
15
+ * stop pattern. The row start and stop pair identifies its one-based row
16
+ * number. This module contains only the public table facts; packing and
17
+ * decoding live in encoder.ts and decoder.ts.
18
+ *
19
+ * @module code16k/tables
20
+ */
21
+ import { EncodeError } from '../core/errors.js';
22
+ import { alternatingWidths } from '../stacked128/common.js';
23
+ export const CODE16K_MIN_ROWS = 2;
24
+ export const CODE16K_MAX_ROWS = 16;
25
+ export const CODE16K_SYMBOLS_PER_ROW = 5;
26
+ export const CODE16K_MODULE_WIDTH = 70;
27
+ export const CODE16K_START_WIDTH = 7;
28
+ export const CODE16K_GUARD_WIDTH = 1;
29
+ export const CODE16K_CODEWORD_WIDTH = 11;
30
+ export const CODE16K_STOP_WIDTH = 7;
31
+ export const CODE16K_DEFAULT_ROW_HEIGHT = 8;
32
+ export const CODE16K_DEFAULT_SEPARATOR_HEIGHT = 1;
33
+ export const CODE16K_PAD = 103;
34
+ /** Start/stop values used to identify rows, in top-to-bottom order. */
35
+ export const CODE16K_ROW_PAIRS = Object.freeze([
36
+ Object.freeze([0, 0]), Object.freeze([1, 1]),
37
+ Object.freeze([2, 2]), Object.freeze([3, 3]),
38
+ Object.freeze([4, 4]), Object.freeze([5, 5]),
39
+ Object.freeze([6, 6]), Object.freeze([7, 7]),
40
+ Object.freeze([0, 4]), Object.freeze([1, 5]),
41
+ Object.freeze([2, 6]), Object.freeze([3, 7]),
42
+ Object.freeze([4, 0]), Object.freeze([5, 1]),
43
+ Object.freeze([6, 2]), Object.freeze([7, 3]),
44
+ ]);
45
+ /** Widths for the eight edge-decodable start patterns. */
46
+ export const CODE16K_START_WIDTHS = Object.freeze([
47
+ Object.freeze([3, 2, 1, 1]), Object.freeze([2, 2, 2, 1]),
48
+ Object.freeze([2, 1, 2, 2]), Object.freeze([1, 4, 1, 1]),
49
+ Object.freeze([1, 1, 3, 2]), Object.freeze([1, 2, 3, 1]),
50
+ Object.freeze([1, 1, 1, 4]), Object.freeze([3, 1, 1, 2]),
51
+ ]);
52
+ /** Return the canonical mode number used by the first symbol character. */
53
+ export function code16kModeNumber(mode, gs1 = false) {
54
+ if (mode === undefined)
55
+ return gs1 ? 3 : 1;
56
+ if (typeof mode === 'string') {
57
+ if (mode === 'A')
58
+ return 0;
59
+ if (mode === 'B')
60
+ return gs1 ? 3 : 1;
61
+ return gs1 ? 4 : 2;
62
+ }
63
+ if (!Number.isInteger(mode) || mode < 0 || mode > 6) {
64
+ throw new EncodeError('Code 16K: mode must be A, B, C or an integer in 0..6');
65
+ }
66
+ return mode;
67
+ }
68
+ /** Resolve the initial Code 128 set and whether the first data value is FNC1. */
69
+ export function code16kModeInfo(mode, gs1 = false) {
70
+ const resolved = code16kModeNumber(mode, gs1);
71
+ if (resolved === 5 || resolved === 6) {
72
+ throw new EncodeError('Code 16K: modes 5 and 6 require a dedicated shift encoder');
73
+ }
74
+ const startSet = resolved === 0 ? 'A' :
75
+ resolved === 1 || resolved === 3 ? 'B' : 'C';
76
+ return {
77
+ mode: resolved,
78
+ startSet,
79
+ gs1: resolved === 3 || resolved === 4,
80
+ };
81
+ }
82
+ /** Return the seven-module start pattern for a zero-based row index. */
83
+ export function code16kStartModules(row) {
84
+ if (!Number.isInteger(row) || row < 0 || row >= CODE16K_MAX_ROWS) {
85
+ throw new RangeError('Code 16K: row index must be an integer in 0..15');
86
+ }
87
+ return alternatingWidths(CODE16K_START_WIDTHS[CODE16K_ROW_PAIRS[row][0]], true);
88
+ }
89
+ /** Return the seven-module stop pattern for a zero-based row index. */
90
+ export function code16kStopModules(row) {
91
+ if (!Number.isInteger(row) || row < 0 || row >= CODE16K_MAX_ROWS) {
92
+ throw new RangeError('Code 16K: row index must be an integer in 0..15');
93
+ }
94
+ return alternatingWidths(CODE16K_START_WIDTHS[CODE16K_ROW_PAIRS[row][1]], false);
95
+ }
96
+ /** Return a complete row's bare module width. */
97
+ export function code16kWidth() {
98
+ return CODE16K_MODULE_WIDTH;
99
+ }
100
+ /** Validate a matrix geometry and return its row count and separator height. */
101
+ export function code16kGeometry(width, height, rows, rowHeight, separatorHeight, outerSeparators = true) {
102
+ if (width !== CODE16K_MODULE_WIDTH) {
103
+ throw new RangeError(`Code 16K: width must be ${CODE16K_MODULE_WIDTH} modules`);
104
+ }
105
+ if (!Number.isInteger(rows) || rows < CODE16K_MIN_ROWS || rows > CODE16K_MAX_ROWS) {
106
+ throw new RangeError('Code 16K: rows must be an integer in 2..16');
107
+ }
108
+ if (!Number.isInteger(rowHeight) || rowHeight < 1 || rowHeight > 128) {
109
+ throw new RangeError('Code 16K: rowHeight must be an integer in 1..128');
110
+ }
111
+ if (!Number.isInteger(separatorHeight) || separatorHeight < 1 || separatorHeight > 16) {
112
+ throw new RangeError('Code 16K: separatorHeight must be an integer in 1..16');
113
+ }
114
+ const outer = outerSeparators ? 2 : 0;
115
+ const expected = outer + rows * rowHeight + (rows - 1) * separatorHeight;
116
+ if (height !== expected) {
117
+ throw new RangeError(`Code 16K: height ${height} does not match the row geometry`);
118
+ }
119
+ return { width, height, rows, rowHeight, separatorHeight };
120
+ }
121
+ /** Validate public encoder options before any large matrix allocation. */
122
+ export function validateCode16KOptions(options = {}) {
123
+ const rowHeight = options.rowHeight ?? CODE16K_DEFAULT_ROW_HEIGHT;
124
+ const separatorHeight = options.separatorHeight ?? CODE16K_DEFAULT_SEPARATOR_HEIGHT;
125
+ if (!Number.isInteger(rowHeight) || rowHeight < 1 || rowHeight > 128) {
126
+ throw new EncodeError('Code 16K: rowHeight must be an integer in 1..128');
127
+ }
128
+ if (!Number.isInteger(separatorHeight) || separatorHeight < 1 || separatorHeight > 16) {
129
+ throw new EncodeError('Code 16K: separatorHeight must be an integer in 1..16');
130
+ }
131
+ if (options.rows !== undefined &&
132
+ (!Number.isInteger(options.rows) || options.rows < CODE16K_MIN_ROWS || options.rows > CODE16K_MAX_ROWS)) {
133
+ throw new EncodeError('Code 16K: rows must be an integer in 2..16');
134
+ }
135
+ const info = code16kModeInfo(options.mode, options.gs1 === true);
136
+ return { rows: options.rows, rowHeight, separatorHeight, ...info };
137
+ }
138
+ /** Check table invariants used by tests and CI. */
139
+ export function validateCode16KTables() {
140
+ const problems = [];
141
+ if (CODE16K_ROW_PAIRS.length !== CODE16K_MAX_ROWS)
142
+ problems.push('row pair count mismatch');
143
+ if (CODE16K_START_WIDTHS.length !== 8)
144
+ problems.push('start pattern count mismatch');
145
+ for (let row = 0; row < CODE16K_MAX_ROWS; row++) {
146
+ if (code16kStartModules(row).length !== CODE16K_START_WIDTH)
147
+ problems.push(`row ${row + 1} start width mismatch`);
148
+ if (code16kStopModules(row).length !== CODE16K_STOP_WIDTH)
149
+ problems.push(`row ${row + 1} stop width mismatch`);
150
+ }
151
+ return problems;
152
+ }