@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,265 @@
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
+ /** MaxiCode decoder for ISO/IEC 16023 modes 2 through 5. @module maxicode/decoder */
32
+
33
+ import { BitMatrix } from '../core/bit-matrix.js';
34
+ import { FormatError } from '../core/errors.js';
35
+ import { GF64 } from '../core/galois-field.js';
36
+ import { rsDecode } from '../core/reed-solomon.js';
37
+ import {
38
+ MAXICODE_CODEWORDS,
39
+ MAXICODE_GRID,
40
+ MAXICODE_HEIGHT,
41
+ MAXICODE_ORIENTATION_DARK,
42
+ MAXICODE_WIDTH,
43
+ maxicodeFinderValue,
44
+ } from './tables.js';
45
+ import {
46
+ codeSetACharacter,
47
+ codeSetBCharacter,
48
+ codeSetCCharacter,
49
+ codeSetDCharacter,
50
+ codeSetECharacter,
51
+ } from './encoder.js';
52
+
53
+ /** @param {BitMatrix} matrix @returns {BitMatrix} */
54
+ function rotate180(matrix) {
55
+ const out = new BitMatrix(matrix.width, matrix.height);
56
+ for (let y = 0; y < matrix.height; y++) for (let x = 0; x < matrix.width; x++) {
57
+ if (matrix.get(x, y)) out.set(matrix.width - 1 - x, matrix.height - 1 - y);
58
+ }
59
+ return out;
60
+ }
61
+
62
+ /** @param {BitMatrix} matrix @returns {BitMatrix} */
63
+ function invert(matrix) {
64
+ const out = matrix.clone();
65
+ for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
66
+ return out;
67
+ }
68
+
69
+ /** Check the finder and orientation markers without trusting payload bits. */
70
+ export function maxicodeStructureMatches(matrix) {
71
+ if (matrix.width !== MAXICODE_WIDTH || matrix.height !== MAXICODE_HEIGHT) return false;
72
+ for (let y = 0; y < MAXICODE_HEIGHT; y++) for (let x = 0; x < MAXICODE_WIDTH; x++) {
73
+ const finder = maxicodeFinderValue(x, y);
74
+ // A few orientation modules intentionally overlap the outer finder ring.
75
+ // They are fixed dark modules in the standard and therefore take
76
+ // precedence over the ring predicate at those coordinates.
77
+ const orientationDark = MAXICODE_ORIENTATION_DARK.some(([ox, oy]) => ox === x && oy === y);
78
+ if (finder !== null && !orientationDark && matrix.get(x, y) !== finder) return false;
79
+ }
80
+ for (const [x, y] of MAXICODE_ORIENTATION_DARK) if (!matrix.get(x, y)) return false;
81
+ // The two fixed upper-right cells are deliberately dark in every symbol.
82
+ return matrix.get(28, 0) && matrix.get(29, 0);
83
+ }
84
+
85
+ /** @param {BitMatrix} matrix @returns {number[]} */
86
+ export function readMaxiCodeCodewords(matrix) {
87
+ if (matrix.width !== MAXICODE_WIDTH || matrix.height !== MAXICODE_HEIGHT) {
88
+ throw new FormatError(`MaxiCode: expected a ${MAXICODE_WIDTH}x${MAXICODE_HEIGHT} module matrix`);
89
+ }
90
+ const codewords = new Array(MAXICODE_CODEWORDS).fill(0);
91
+ for (let y = 0; y < MAXICODE_HEIGHT; y++) for (let x = 0; x < MAXICODE_WIDTH; x++) {
92
+ const sequence = MAXICODE_GRID[y * MAXICODE_WIDTH + x];
93
+ if (sequence === 0) continue;
94
+ const wire = sequence + 5;
95
+ const codeword = Math.floor(wire / 6) - 1;
96
+ const bit = 5 - (wire % 6);
97
+ if (matrix.get(x, y)) codewords[codeword] |= 1 << bit;
98
+ }
99
+ return codewords;
100
+ }
101
+
102
+ /** @param {number[]} codewords @param {number} length @returns {{data:number[],corrections:number}} */
103
+ function correctSecondary(codewords, length) {
104
+ const eccLength = codewords.length - length;
105
+ const data = codewords.slice(0, length);
106
+ const even = [];
107
+ const odd = [];
108
+ for (let i = 0; i < length; i++) (i % 2 === 0 ? even : odd).push(data[i]);
109
+ const evenParity = [];
110
+ const oddParity = [];
111
+ for (let i = 0; i < eccLength / 2; i++) {
112
+ evenParity.push(codewords[length + i * 2]);
113
+ oddParity.push(codewords[length + i * 2 + 1]);
114
+ }
115
+ const evenReceived = even.concat(evenParity);
116
+ const oddReceived = odd.concat(oddParity);
117
+ const corrections = rsDecode(evenReceived, eccLength / 2, GF64, 1) +
118
+ rsDecode(oddReceived, eccLength / 2, GF64, 1);
119
+ for (let i = 0; i < length; i++) data[i] = i % 2 === 0 ? evenReceived[i / 2] : oddReceived[(i - 1) / 2];
120
+ return { data, corrections };
121
+ }
122
+
123
+ /** @param {number[]} words @returns {{text:string,bytes:Uint8Array}} */
124
+ export function decodeMaxiCodeText(words) {
125
+ /** @type {string[]} */
126
+ const chars = [];
127
+ let set = 'A';
128
+ let i = 0;
129
+ let padding = false;
130
+ while (i < words.length) {
131
+ const value = words[i++];
132
+ if (!Number.isInteger(value) || value < 0 || value > 63) throw new FormatError('MaxiCode: codeword is outside the six-bit range');
133
+ if (value === 33) {
134
+ padding = true;
135
+ for (let j = i; j < words.length; j++) if (words[j] !== 33) throw new FormatError('MaxiCode: data appears after a pad codeword');
136
+ break;
137
+ }
138
+ if (padding) throw new FormatError('MaxiCode: non-padding data follows a pad codeword');
139
+ if (value === 63) {
140
+ set = set === 'A' ? 'B' : 'A';
141
+ continue;
142
+ }
143
+ if (value === 59) {
144
+ if (i >= words.length) throw new FormatError('MaxiCode: truncated shift sequence');
145
+ const shifted = words[i++];
146
+ const character = set === 'A' ? codeSetBCharacter(shifted) : codeSetACharacter(shifted);
147
+ if (character === null) throw new FormatError('MaxiCode: shift targets an invalid codeword');
148
+ chars.push(String.fromCharCode(character));
149
+ continue;
150
+ }
151
+ if (value >= 60 && value <= 62) {
152
+ if (i >= words.length) throw new FormatError('MaxiCode: truncated shift sequence');
153
+ const shifted = words[i++];
154
+ const character = value === 60 ? codeSetCCharacter(shifted) :
155
+ value === 61 ? codeSetDCharacter(shifted) : codeSetECharacter(shifted);
156
+ if (character === null) throw new FormatError('MaxiCode: shift targets an invalid codeword');
157
+ chars.push(String.fromCharCode(character));
158
+ continue;
159
+ }
160
+ if (value === 31) {
161
+ if (i + 5 > words.length) throw new FormatError('MaxiCode: truncated numeric shift');
162
+ let compact = 0;
163
+ for (let j = 0; j < 5; j++) compact = compact * 64 + words[i++];
164
+ if (compact > 999999999) throw new FormatError('MaxiCode: numeric shift value exceeds nine digits');
165
+ chars.push(String(compact).padStart(9, '0'));
166
+ continue;
167
+ }
168
+ if (value === 27) throw new FormatError('MaxiCode: ECI sequences are not enabled in this build');
169
+ const character = set === 'A' ? codeSetACharacter(value) : codeSetBCharacter(value);
170
+ if (character === null) throw new FormatError(`MaxiCode: codeword ${value} is not assigned in Code Set ${set}`);
171
+ chars.push(String.fromCharCode(character));
172
+ }
173
+ const text = chars.join('');
174
+ return { text, bytes: Uint8Array.from(chars.map((character) => character.charCodeAt(0))) };
175
+ }
176
+
177
+ /** @param {number[]} primary @param {2|3} mode */
178
+ function decodePrimary(primary, mode) {
179
+ const countryCode = ((primary[6] >>> 4) & 0x03) | (primary[7] << 2) | ((primary[8] & 0x03) << 8);
180
+ const serviceClass = ((primary[8] >>> 2) & 0x0f) | (primary[9] << 4);
181
+ if (mode === 2) {
182
+ const length = ((primary[5] >>> 4) & 0x03) | ((primary[6] & 0x0f) << 2);
183
+ let postal = ((primary[0] >>> 4) & 0x03) |
184
+ (primary[1] << 2) |
185
+ (primary[2] << 8) |
186
+ (primary[3] << 14) |
187
+ (primary[4] << 20) |
188
+ ((primary[5] & 0x0f) << 26);
189
+ postal = String(postal).padStart(length, '0');
190
+ return { postalCode: postal, countryCode, serviceClass };
191
+ }
192
+ const values = [
193
+ ((primary[5] >>> 4) & 0x03) | ((primary[6] & 0x0f) << 2),
194
+ ((primary[4] >>> 4) & 0x03) | ((primary[5] & 0x0f) << 2),
195
+ ((primary[3] >>> 4) & 0x03) | ((primary[4] & 0x0f) << 2),
196
+ ((primary[2] >>> 4) & 0x03) | ((primary[3] & 0x0f) << 2),
197
+ ((primary[1] >>> 4) & 0x03) | ((primary[2] & 0x0f) << 2),
198
+ ((primary[0] >>> 4) & 0x03) | ((primary[1] & 0x0f) << 2),
199
+ ];
200
+ const postal = values.map((value) => {
201
+ const cp = codeSetACharacter(value);
202
+ if (cp === null) throw new FormatError('MaxiCode Mode 3: primary postal code contains an invalid value');
203
+ return String.fromCharCode(cp);
204
+ }).join('').trimEnd();
205
+ return { postalCode: postal, countryCode, serviceClass };
206
+ }
207
+
208
+ /**
209
+ * Decode an upright MaxiCode matrix. The reader accepts the canonical matrix
210
+ * and its 180-degree turn; arbitrary perspective belongs to the detector.
211
+ *
212
+ * @param {BitMatrix} matrix
213
+ * @param {{inverted?: boolean|'auto',rotation?:0|180|'auto'}} [options]
214
+ * @returns {object}
215
+ */
216
+ export function decodeMaxiCode(matrix, options = {}) {
217
+ if (!matrix || matrix.width !== MAXICODE_WIDTH || matrix.height !== MAXICODE_HEIGHT) {
218
+ throw new FormatError(`MaxiCode: expected a ${MAXICODE_WIDTH}x${MAXICODE_HEIGHT} module matrix`);
219
+ }
220
+ const rotations = options.rotation === 180 ? [180] : options.rotation === 0 ? [0] : [0, 180];
221
+ const invertedModes = options.inverted === true ? [true] : options.inverted === false ? [false] : [false, true];
222
+ let lastError = null;
223
+
224
+ for (const rotation of rotations) {
225
+ const oriented = rotation === 180 ? rotate180(matrix) : matrix;
226
+ for (const inverted of invertedModes) {
227
+ const candidate = inverted ? invert(oriented) : oriented;
228
+ if (!maxicodeStructureMatches(candidate)) continue;
229
+ try {
230
+ const codewords = readMaxiCodeCodewords(candidate);
231
+ const primaryReceived = codewords.slice(0, 20);
232
+ const primaryCorrections = rsDecode(primaryReceived, 10, GF64, 1);
233
+ const primary = primaryReceived.slice(0, 10);
234
+ const mode = primary[0] & 0x0f;
235
+ if (mode < 2 || mode > 5) throw new FormatError(`MaxiCode: unsupported mode ${mode}`);
236
+ const secondaryLength = mode === 5 ? 68 : 84;
237
+ const secondaryWords = codewords.slice(20, 20 + secondaryLength + (mode === 5 ? 56 : 40));
238
+ const secondary = correctSecondary(secondaryWords, secondaryLength);
239
+ // Modes 4 and 5 place the first nine secondary codewords in the
240
+ // primary message (after the mode indicator). Padding in that
241
+ // prefix must be removed before joining the corrected secondary part.
242
+ const primaryPayload = (mode === 4 || mode === 5) ? primary.slice(1) : [];
243
+ while (primaryPayload.length && primaryPayload[primaryPayload.length - 1] === 33) primaryPayload.pop();
244
+ const payload = decodeMaxiCodeText(primaryPayload.concat(secondary.data));
245
+ const result = {
246
+ format: 'maxicode',
247
+ text: payload.text,
248
+ bytes: payload.bytes,
249
+ mode,
250
+ corrections: primaryCorrections + secondary.corrections,
251
+ rows: MAXICODE_HEIGHT,
252
+ columns: MAXICODE_WIDTH,
253
+ inverted,
254
+ rotation,
255
+ };
256
+ if (mode === 2 || mode === 3) result.primary = decodePrimary(primary, mode);
257
+ return result;
258
+ } catch (error) {
259
+ lastError = error;
260
+ }
261
+ }
262
+ }
263
+ if (lastError) throw new FormatError(`MaxiCode: Reed-Solomon or payload validation failed: ${lastError.message}`);
264
+ throw new FormatError('MaxiCode: finder or orientation structure is invalid');
265
+ }
@@ -0,0 +1,109 @@
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
+ /** MaxiCode detector for fixed 30 by 33 module images. @module maxicode/detector */
32
+
33
+ import { BitMatrix } from '../core/bit-matrix.js';
34
+ import { NotFoundError } from '../core/errors.js';
35
+ import { decodeMaxiCode } from './decoder.js';
36
+ import { MAXICODE_HEIGHT, MAXICODE_WIDTH } from './tables.js';
37
+
38
+ /** @param {BitMatrix} source @param {{x:number,y:number,width:number,height:number}} bounds */
39
+ function sampleBounds(source, bounds) {
40
+ const matrix = new BitMatrix(MAXICODE_WIDTH, MAXICODE_HEIGHT);
41
+ for (let y = 0; y < MAXICODE_HEIGHT; y++) for (let x = 0; x < MAXICODE_WIDTH; x++) {
42
+ const px = Math.min(source.width - 1, Math.max(0,
43
+ Math.floor(bounds.x + ((x + 0.5) * bounds.width) / MAXICODE_WIDTH)));
44
+ const py = Math.min(source.height - 1, Math.max(0,
45
+ Math.floor(bounds.y + ((y + 0.5) * bounds.height) / MAXICODE_HEIGHT)));
46
+ if (source.get(px, py)) matrix.set(x, y);
47
+ }
48
+ return matrix;
49
+ }
50
+
51
+ /** @param {BitMatrix} source @returns {BitMatrix} */
52
+ function inverted(source) {
53
+ const out = source.clone();
54
+ for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
55
+ return out;
56
+ }
57
+
58
+ /**
59
+ * Detect a MaxiCode in a binarized image. The detector intentionally accepts a
60
+ * single prominent symbol and an integer or near-integer scale; callers that
61
+ * have a perspective quadrilateral can sample it first and call the matrix
62
+ * decoder directly.
63
+ *
64
+ * @param {BitMatrix} binaryImage
65
+ * @returns {{corners:Array<{x:number,y:number}>,dimension:{width:number,height:number},moduleSize:number,matrix:BitMatrix,result:object}|null}
66
+ */
67
+ export function detectMaxiCode(binaryImage) {
68
+ if (!binaryImage || !binaryImage.width || !binaryImage.height) {
69
+ throw new NotFoundError('detectMaxiCode: no image supplied');
70
+ }
71
+ const candidates = [];
72
+ for (const source of [binaryImage, inverted(binaryImage)]) {
73
+ const bounds = source.width === MAXICODE_WIDTH && source.height === MAXICODE_HEIGHT
74
+ ? { x: 0, y: 0, width: source.width, height: source.height }
75
+ : source.getBounds();
76
+ if (!bounds || bounds.width < MAXICODE_WIDTH || bounds.height < MAXICODE_HEIGHT) continue;
77
+ const ratio = bounds.width / bounds.height;
78
+ if (Math.abs(ratio - MAXICODE_WIDTH / MAXICODE_HEIGHT) > 0.2) continue;
79
+ // A valid symbol may legitimately fill the complete input frame (for
80
+ // example an integer-scaled 30x33 matrix). Payload validation below
81
+ // rejects a uniform background, so do not discard full-frame candidates
82
+ // before sampling them.
83
+ const matrix = sampleBounds(source, bounds);
84
+ let result;
85
+ try { result = decodeMaxiCode(matrix); } catch { continue; }
86
+ candidates.push({
87
+ corners: [
88
+ { x: bounds.x, y: bounds.y },
89
+ { x: bounds.x + bounds.width, y: bounds.y },
90
+ { x: bounds.x + bounds.width, y: bounds.y + bounds.height },
91
+ { x: bounds.x, y: bounds.y + bounds.height },
92
+ ],
93
+ dimension: { width: MAXICODE_WIDTH, height: MAXICODE_HEIGHT },
94
+ moduleSize: (bounds.width / MAXICODE_WIDTH + bounds.height / MAXICODE_HEIGHT) / 2,
95
+ matrix,
96
+ result,
97
+ });
98
+ }
99
+ candidates.sort((a, b) => b.moduleSize - a.moduleSize);
100
+ return candidates[0] ?? null;
101
+ }
102
+
103
+ /** Detect and decode a MaxiCode or return null when no verified symbol exists. */
104
+ export function detectAndDecodeMaxiCode(binaryImage) {
105
+ let detection;
106
+ try { detection = detectMaxiCode(binaryImage); } catch { return null; }
107
+ if (!detection) return null;
108
+ return { ...detection.result, corners: detection.corners, moduleSize: detection.moduleSize };
109
+ }