@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.
- package/LICENSE +22 -4
- package/NOTICE.md +10 -1
- package/README.md +416 -22
- package/bundle/sythos-barcode.esm.js +22176 -11991
- package/bundle/sythos-barcode.js +22059 -11991
- package/licenses/README.md +12 -1
- package/licenses/codablockf.license +51 -0
- package/licenses/code16k.license +52 -0
- package/licenses/code25.license +58 -0
- package/licenses/code32.license +51 -0
- package/licenses/dotcode.license +66 -0
- package/licenses/gs1-composite.license +106 -0
- package/licenses/gs1-databar.license +21 -14
- package/licenses/hanxin.license +95 -0
- package/licenses/maxicode.license +62 -0
- package/licenses/postal.license +61 -0
- package/licenses/pzn.license +53 -0
- package/licenses/telepen.license +73 -0
- package/llms.txt +62 -6
- package/package.json +47 -3
- package/src/index.d.ts +111 -6
- package/src/index.js +362 -13
- package/src/js/codablockf/decoder.js +198 -0
- package/src/js/codablockf/encoder.js +97 -0
- package/src/js/codablockf/index.js +3 -0
- package/src/js/code16k/decoder.js +303 -0
- package/src/js/code16k/detector.js +187 -0
- package/src/js/code16k/encoder.js +153 -0
- package/src/js/code16k/index.js +14 -0
- package/src/js/code16k/tables.js +152 -0
- package/src/js/composite/index.js +570 -0
- package/src/js/core/detection-contract.js +185 -0
- package/src/js/core/symbol-layout.js +155 -0
- package/src/js/databar/expanded.js +968 -0
- package/src/js/databar/index.js +4 -0
- package/src/js/databar/layout.js +192 -0
- package/src/js/databar/limited.js +533 -0
- package/src/js/databar/stacked-omnidirectional.js +548 -0
- package/src/js/databar/stacked.js +528 -0
- package/src/js/dotcode/decoder.js +548 -0
- package/src/js/dotcode/detector.js +251 -0
- package/src/js/dotcode/encoder.js +453 -0
- package/src/js/dotcode/index.js +34 -0
- package/src/js/dotcode/tables.js +168 -0
- package/src/js/hanxin/decoder.js +299 -0
- package/src/js/hanxin/detector.js +122 -0
- package/src/js/hanxin/encoder.js +259 -0
- package/src/js/hanxin/index.js +16 -0
- package/src/js/hanxin/tables.js +316 -0
- package/src/js/image/height-coded.js +164 -0
- package/src/js/maxicode/decoder.js +275 -0
- package/src/js/maxicode/detector.js +118 -0
- package/src/js/maxicode/encoder.js +438 -0
- package/src/js/maxicode/index.js +34 -0
- package/src/js/maxicode/tables.js +132 -0
- package/src/js/oned/code25.js +148 -0
- package/src/js/oned/index.js +21 -3
- package/src/js/oned/postal.js +889 -0
- package/src/js/oned/reader.js +272 -7
- package/src/js/oned/telepen.js +361 -0
- package/src/js/oned/writers.js +186 -12
- package/src/js/stacked128/common.js +208 -0
- package/src/ts/codablockf/decoder.ts +212 -0
- package/src/ts/codablockf/encoder.ts +128 -0
- package/src/ts/codablockf/index.d.ts +31 -0
- package/src/ts/codablockf/index.ts +6 -0
- package/src/ts/code16k/decoder.d.ts +25 -0
- package/src/ts/code16k/decoder.ts +313 -0
- package/src/ts/code16k/detector.d.ts +21 -0
- package/src/ts/code16k/detector.ts +203 -0
- package/src/ts/code16k/encoder.d.ts +26 -0
- package/src/ts/code16k/encoder.ts +198 -0
- package/src/ts/code16k/index.d.ts +10 -0
- package/src/ts/code16k/index.ts +47 -0
- package/src/ts/code16k/tables.d.ts +54 -0
- package/src/ts/code16k/tables.ts +195 -0
- package/src/ts/composite/index.d.ts +74 -0
- package/src/ts/composite/index.ts +547 -0
- package/src/ts/core/detection-contract.d.ts +119 -0
- package/src/ts/core/detection-contract.ts +267 -0
- package/src/ts/core/symbol-layout.d.ts +108 -0
- package/src/ts/core/symbol-layout.ts +236 -0
- package/src/ts/databar/expanded.d.ts +40 -0
- package/src/ts/databar/expanded.ts +948 -0
- package/src/ts/databar/index.d.ts +48 -0
- package/src/ts/databar/index.ts +48 -0
- package/src/ts/databar/layout.d.ts +122 -0
- package/src/ts/databar/layout.ts +275 -0
- package/src/ts/databar/limited.d.ts +85 -0
- package/src/ts/databar/limited.ts +553 -0
- package/src/ts/databar/stacked-omnidirectional.d.ts +128 -0
- package/src/ts/databar/stacked-omnidirectional.ts +559 -0
- package/src/ts/databar/stacked.d.ts +96 -0
- package/src/ts/databar/stacked.ts +549 -0
- package/src/ts/dotcode/decoder.d.ts +58 -0
- package/src/ts/dotcode/decoder.ts +467 -0
- package/src/ts/dotcode/detector.d.ts +63 -0
- package/src/ts/dotcode/detector.ts +263 -0
- package/src/ts/dotcode/encoder.d.ts +69 -0
- package/src/ts/dotcode/encoder.ts +427 -0
- package/src/ts/dotcode/index.d.ts +37 -0
- package/src/ts/dotcode/index.ts +72 -0
- package/src/ts/dotcode/tables.d.ts +74 -0
- package/src/ts/dotcode/tables.ts +174 -0
- package/src/ts/hanxin/decoder.d.ts +48 -0
- package/src/ts/hanxin/decoder.ts +314 -0
- package/src/ts/hanxin/detector.d.ts +45 -0
- package/src/ts/hanxin/detector.ts +124 -0
- package/src/ts/hanxin/encoder.d.ts +41 -0
- package/src/ts/hanxin/encoder.ts +290 -0
- package/src/ts/hanxin/index.d.ts +16 -0
- package/src/ts/hanxin/index.ts +18 -0
- package/src/ts/hanxin/tables.d.ts +80 -0
- package/src/ts/hanxin/tables.ts +348 -0
- package/src/ts/image/height-coded.d.ts +101 -0
- package/src/ts/image/height-coded.ts +227 -0
- package/src/ts/index.d.ts +46 -2
- package/src/ts/index.ts +380 -13
- package/src/ts/maxicode/decoder.ts +265 -0
- package/src/ts/maxicode/detector.ts +109 -0
- package/src/ts/maxicode/encoder.ts +414 -0
- package/src/ts/maxicode/index.d.ts +96 -0
- package/src/ts/maxicode/index.ts +44 -0
- package/src/ts/maxicode/tables.ts +136 -0
- package/src/ts/oned/code25.d.ts +42 -0
- package/src/ts/oned/code25.ts +170 -0
- package/src/ts/oned/index.d.ts +7 -2
- package/src/ts/oned/index.ts +45 -3
- package/src/ts/oned/postal.d.ts +44 -0
- package/src/ts/oned/postal.ts +819 -0
- package/src/ts/oned/reader.d.ts +35 -0
- package/src/ts/oned/reader.ts +261 -7
- package/src/ts/oned/telepen.d.ts +65 -0
- package/src/ts/oned/telepen.ts +368 -0
- package/src/ts/oned/writers.d.ts +32 -0
- package/src/ts/oned/writers.ts +184 -13
- package/src/ts/stacked128/common.ts +225 -0
|
@@ -0,0 +1,438 @@
|
|
|
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
|
+
/** MaxiCode encoder for ISO/IEC 16023 modes 2 through 5. @module maxicode/encoder */
|
|
31
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
32
|
+
import { EncodeError } from '../core/errors.js';
|
|
33
|
+
import { GF64 } from '../core/galois-field.js';
|
|
34
|
+
import { rsEncode } from '../core/reed-solomon.js';
|
|
35
|
+
import { MAXICODE_CODEWORDS, MAXICODE_DATA_MODULES, MAXICODE_GRID, MAXICODE_HEIGHT, MAXICODE_ORIENTATION_DARK, MAXICODE_WIDTH, maxicodeFinderValue, } from './tables.js';
|
|
36
|
+
/** @typedef {'A'|'B'|'C'|'D'|'E'} MaxiCodeSet */
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} MaxiCodePrimary
|
|
39
|
+
* @property {string} postalCode Mode 2 numeric postal code, or Mode 3 text.
|
|
40
|
+
* @property {number} countryCode ISO numeric country code 0..999.
|
|
41
|
+
* @property {number} serviceClass Three-digit service class 0..999.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {object} MaxiCodeEncodeOptions
|
|
45
|
+
* @property {2|3|4|5} [mode] MaxiCode mode. Default 4.
|
|
46
|
+
* @property {MaxiCodePrimary} [primary] Required for modes 2 and 3.
|
|
47
|
+
* @property {'latin1'} [charset] Accepted for API clarity; ISO-8859-1 only.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* A matrix returned by the encoder carries non-enumerable-free metadata used
|
|
51
|
+
* by the focused reader tests and by applications that want the mode.
|
|
52
|
+
* @typedef {import('../core/bit-matrix.js').BitMatrix & {maxicode?: object}} MaxiCodeMatrix
|
|
53
|
+
*/
|
|
54
|
+
/** @param {number} cp @returns {number|null} Code Set A symbol value. */
|
|
55
|
+
export function codeSetAValue(cp) {
|
|
56
|
+
if (cp === 28 || cp === 29 || cp === 30)
|
|
57
|
+
return cp;
|
|
58
|
+
if (cp === 32)
|
|
59
|
+
return 32;
|
|
60
|
+
if (cp >= 34 && cp <= 58)
|
|
61
|
+
return cp;
|
|
62
|
+
if (cp >= 48 && cp <= 57)
|
|
63
|
+
return cp;
|
|
64
|
+
if (cp >= 65 && cp <= 90)
|
|
65
|
+
return cp - 64;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/** @param {number} cp @returns {number|null} Code Set B symbol value. */
|
|
69
|
+
export function codeSetBValue(cp) {
|
|
70
|
+
if (cp === 96)
|
|
71
|
+
return 0;
|
|
72
|
+
if (cp >= 97 && cp <= 122)
|
|
73
|
+
return cp - 96;
|
|
74
|
+
if (cp === 28 || cp === 29 || cp === 30)
|
|
75
|
+
return cp;
|
|
76
|
+
const values = {
|
|
77
|
+
32: 47, 33: 53, 44: 48, 46: 49, 47: 50, 58: 51,
|
|
78
|
+
59: 37, 60: 38, 61: 39, 62: 40, 63: 41, 64: 52,
|
|
79
|
+
91: 42, 92: 43, 93: 44, 94: 45, 95: 46,
|
|
80
|
+
123: 32, 124: 54, 125: 34, 126: 35, 127: 36,
|
|
81
|
+
};
|
|
82
|
+
return Object.prototype.hasOwnProperty.call(values, cp) ? values[cp] : null;
|
|
83
|
+
}
|
|
84
|
+
/** @param {number} value @returns {number|null} Code Set A value to ASCII. */
|
|
85
|
+
export function codeSetACharacter(value) {
|
|
86
|
+
if (value >= 1 && value <= 26)
|
|
87
|
+
return value + 64;
|
|
88
|
+
if (value === 28 || value === 29 || value === 30 || value === 32)
|
|
89
|
+
return value;
|
|
90
|
+
if (value >= 34 && value <= 58)
|
|
91
|
+
return value;
|
|
92
|
+
if (value >= 48 && value <= 57)
|
|
93
|
+
return value;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** @param {number} value @returns {number|null} Code Set B value to ASCII. */
|
|
97
|
+
export function codeSetBCharacter(value) {
|
|
98
|
+
if (value === 0)
|
|
99
|
+
return 96;
|
|
100
|
+
if (value >= 1 && value <= 26)
|
|
101
|
+
return value + 96;
|
|
102
|
+
const chars = new Map([
|
|
103
|
+
[32, 123], [34, 125], [35, 126], [36, 127], [37, 59], [38, 60],
|
|
104
|
+
[39, 61], [40, 62], [41, 63], [42, 91], [43, 92], [44, 93],
|
|
105
|
+
[45, 94], [46, 95], [47, 32], [48, 44], [49, 46], [50, 47],
|
|
106
|
+
[51, 58], [52, 64], [53, 33], [54, 124],
|
|
107
|
+
]);
|
|
108
|
+
return chars.get(value) ?? null;
|
|
109
|
+
}
|
|
110
|
+
/** @param {number} cp @returns {number|null} Code Set C symbol value. */
|
|
111
|
+
export function codeSetCValue(cp) {
|
|
112
|
+
if (cp >= 192 && cp <= 218)
|
|
113
|
+
return cp - 192;
|
|
114
|
+
if (cp >= 219 && cp <= 223)
|
|
115
|
+
return cp - 187;
|
|
116
|
+
const values = {
|
|
117
|
+
128: 48, 129: 49, 130: 50, 131: 51, 132: 52, 133: 53, 134: 54, 135: 55, 136: 56, 137: 57,
|
|
118
|
+
170: 37, 172: 38, 177: 39, 178: 40, 179: 41, 181: 42, 185: 43, 186: 44, 188: 45, 189: 46, 190: 47,
|
|
119
|
+
};
|
|
120
|
+
return Object.prototype.hasOwnProperty.call(values, cp) ? values[cp] : null;
|
|
121
|
+
}
|
|
122
|
+
/** @param {number} cp @returns {number|null} Code Set D symbol value. */
|
|
123
|
+
export function codeSetDValue(cp) {
|
|
124
|
+
if (cp >= 224 && cp <= 250)
|
|
125
|
+
return cp - 224;
|
|
126
|
+
if (cp >= 251 && cp <= 255)
|
|
127
|
+
return cp - 219;
|
|
128
|
+
const values = {
|
|
129
|
+
138: 47, 139: 48, 140: 49, 141: 50, 142: 51, 143: 52, 144: 53, 145: 54, 146: 55, 147: 56, 148: 57,
|
|
130
|
+
161: 37, 168: 38, 171: 39, 175: 40, 176: 41, 180: 42, 183: 43, 184: 44, 187: 45, 191: 46,
|
|
131
|
+
};
|
|
132
|
+
return Object.prototype.hasOwnProperty.call(values, cp) ? values[cp] : null;
|
|
133
|
+
}
|
|
134
|
+
/** @param {number} cp @returns {number|null} Code Set E symbol value. */
|
|
135
|
+
export function codeSetEValue(cp) {
|
|
136
|
+
if (cp >= 0 && cp <= 26)
|
|
137
|
+
return cp;
|
|
138
|
+
if (cp === 27)
|
|
139
|
+
return 30;
|
|
140
|
+
if (cp >= 28 && cp <= 30)
|
|
141
|
+
return cp + 4;
|
|
142
|
+
if (cp === 31)
|
|
143
|
+
return 35;
|
|
144
|
+
if (cp === 32)
|
|
145
|
+
return 59;
|
|
146
|
+
if (cp >= 149 && cp <= 158)
|
|
147
|
+
return cp - 101;
|
|
148
|
+
const values = {
|
|
149
|
+
159: 36, 160: 37, 162: 38, 163: 39, 164: 40, 165: 41, 166: 42, 167: 43,
|
|
150
|
+
169: 44, 173: 45, 174: 46, 182: 47,
|
|
151
|
+
};
|
|
152
|
+
return Object.prototype.hasOwnProperty.call(values, cp) ? values[cp] : null;
|
|
153
|
+
}
|
|
154
|
+
/** @param {number} value @returns {number|null} Code Set C value to ISO-8859-1. */
|
|
155
|
+
export function codeSetCCharacter(value) {
|
|
156
|
+
if (value >= 0 && value <= 26)
|
|
157
|
+
return value + 192;
|
|
158
|
+
if (value >= 32 && value <= 36)
|
|
159
|
+
return value + 187;
|
|
160
|
+
const chars = new Map([
|
|
161
|
+
[37, 170], [38, 172], [39, 177], [40, 178], [41, 179], [42, 181], [43, 185], [44, 186], [45, 188], [46, 189], [47, 190],
|
|
162
|
+
[48, 128], [49, 129], [50, 130], [51, 131], [52, 132], [53, 133], [54, 134], [55, 135], [56, 136], [57, 137],
|
|
163
|
+
]);
|
|
164
|
+
return chars.get(value) ?? null;
|
|
165
|
+
}
|
|
166
|
+
/** @param {number} value @returns {number|null} Code Set D value to ISO-8859-1. */
|
|
167
|
+
export function codeSetDCharacter(value) {
|
|
168
|
+
if (value >= 0 && value <= 26)
|
|
169
|
+
return value + 224;
|
|
170
|
+
if (value >= 32 && value <= 36)
|
|
171
|
+
return value + 219;
|
|
172
|
+
const chars = new Map([
|
|
173
|
+
[37, 161], [38, 168], [39, 171], [40, 175], [41, 176], [42, 180], [43, 183], [44, 184], [45, 187], [46, 191],
|
|
174
|
+
[47, 138], [48, 139], [49, 140], [50, 141], [51, 142], [52, 143], [53, 144], [54, 145], [55, 146], [56, 147], [57, 148],
|
|
175
|
+
]);
|
|
176
|
+
return chars.get(value) ?? null;
|
|
177
|
+
}
|
|
178
|
+
/** @param {number} value @returns {number|null} Code Set E value to ISO-8859-1. */
|
|
179
|
+
export function codeSetECharacter(value) {
|
|
180
|
+
if (value >= 0 && value <= 26)
|
|
181
|
+
return value;
|
|
182
|
+
if (value === 30)
|
|
183
|
+
return 27;
|
|
184
|
+
if (value >= 32 && value <= 34)
|
|
185
|
+
return value - 4;
|
|
186
|
+
if (value === 35)
|
|
187
|
+
return 31;
|
|
188
|
+
if (value === 36)
|
|
189
|
+
return 159;
|
|
190
|
+
if (value === 37)
|
|
191
|
+
return 160;
|
|
192
|
+
if (value >= 38 && value <= 43)
|
|
193
|
+
return value + 124;
|
|
194
|
+
if (value === 44)
|
|
195
|
+
return 169;
|
|
196
|
+
if (value === 45)
|
|
197
|
+
return 173;
|
|
198
|
+
if (value === 46)
|
|
199
|
+
return 174;
|
|
200
|
+
if (value === 47)
|
|
201
|
+
return 182;
|
|
202
|
+
if (value >= 48 && value <= 57)
|
|
203
|
+
return value + 101;
|
|
204
|
+
if (value === 59)
|
|
205
|
+
return 32;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/** @param {number} cp @returns {{set:'C'|'D'|'E', value:number}|null} */
|
|
209
|
+
function extendedCodeSetValue(cp) {
|
|
210
|
+
const c = codeSetCValue(cp);
|
|
211
|
+
if (c !== null)
|
|
212
|
+
return { set: 'C', value: c };
|
|
213
|
+
const d = codeSetDValue(cp);
|
|
214
|
+
if (d !== null)
|
|
215
|
+
return { set: 'D', value: d };
|
|
216
|
+
const e = codeSetEValue(cp);
|
|
217
|
+
return e === null ? null : { set: 'E', value: e };
|
|
218
|
+
}
|
|
219
|
+
/** @param {string|Uint8Array|number[]} value @returns {number[]} Latin-1 bytes. */
|
|
220
|
+
function inputBytes(value) {
|
|
221
|
+
if (value instanceof Uint8Array)
|
|
222
|
+
return Array.from(value);
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
if (value.some((v) => !Number.isInteger(v) || v < 0 || v > 255)) {
|
|
225
|
+
throw new EncodeError('MaxiCode: numeric byte input must contain values from 0 to 255');
|
|
226
|
+
}
|
|
227
|
+
return value.slice();
|
|
228
|
+
}
|
|
229
|
+
if (typeof value !== 'string')
|
|
230
|
+
throw new EncodeError('MaxiCode: value must be text or bytes');
|
|
231
|
+
const bytes = [];
|
|
232
|
+
for (const character of value) {
|
|
233
|
+
const cp = character.codePointAt(0);
|
|
234
|
+
if (cp === undefined || cp > 255) {
|
|
235
|
+
throw new EncodeError('MaxiCode: text must be representable in ISO-8859-1');
|
|
236
|
+
}
|
|
237
|
+
bytes.push(cp);
|
|
238
|
+
}
|
|
239
|
+
return bytes;
|
|
240
|
+
}
|
|
241
|
+
/** Convert source bytes to MaxiCode codewords using explicit A/B latches. */
|
|
242
|
+
export function encodeMaxiCodeText(value) {
|
|
243
|
+
const bytes = inputBytes(value);
|
|
244
|
+
const out = [];
|
|
245
|
+
/** @type {MaxiCodeSet} */
|
|
246
|
+
let current = 'A';
|
|
247
|
+
for (const cp of bytes) {
|
|
248
|
+
const a = codeSetAValue(cp);
|
|
249
|
+
const b = codeSetBValue(cp);
|
|
250
|
+
if (current === 'A') {
|
|
251
|
+
if (a !== null) {
|
|
252
|
+
out.push(a);
|
|
253
|
+
}
|
|
254
|
+
else if (b !== null) {
|
|
255
|
+
// Latch B is deterministic and keeps the stream easy to audit. A
|
|
256
|
+
// following A character emits Latch A, so no implicit state leaks.
|
|
257
|
+
out.push(63, b);
|
|
258
|
+
current = 'B';
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const extended = extendedCodeSetValue(cp);
|
|
262
|
+
if (extended === null)
|
|
263
|
+
throw new EncodeError(`MaxiCode: character U+${cp.toString(16).padStart(4, '0')} is not encodable`);
|
|
264
|
+
out.push(extended.set === 'C' ? 60 : extended.set === 'D' ? 61 : 62, extended.value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else if (b !== null) {
|
|
268
|
+
out.push(b);
|
|
269
|
+
}
|
|
270
|
+
else if (a !== null) {
|
|
271
|
+
out.push(63, a);
|
|
272
|
+
current = 'A';
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const extended = extendedCodeSetValue(cp);
|
|
276
|
+
if (extended === null)
|
|
277
|
+
throw new EncodeError(`MaxiCode: character U+${cp.toString(16).padStart(4, '0')} is not encodable`);
|
|
278
|
+
out.push(extended.set === 'C' ? 60 : extended.set === 'D' ? 61 : 62, extended.value);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
/** @param {string} postal @param {number} country @param {number} service */
|
|
284
|
+
function mode2Primary(postal, country, service) {
|
|
285
|
+
if (!/^\d{1,9}$/.test(postal))
|
|
286
|
+
throw new EncodeError('MaxiCode Mode 2: postalCode must contain 1 to 9 digits');
|
|
287
|
+
const numericPostal = Number(postal);
|
|
288
|
+
return [
|
|
289
|
+
((numericPostal & 0x03) << 4) | 2,
|
|
290
|
+
(numericPostal & 0xfc) >> 2,
|
|
291
|
+
(numericPostal & 0x3f00) >> 8,
|
|
292
|
+
(numericPostal & 0xfc000) >> 14,
|
|
293
|
+
(numericPostal & 0x3f00000) >> 20,
|
|
294
|
+
((numericPostal & 0x3c000000) >> 26) | ((postal.length & 0x3) << 4),
|
|
295
|
+
((postal.length & 0x3c) >> 2) | ((country & 0x3) << 4),
|
|
296
|
+
(country & 0xfc) >> 2,
|
|
297
|
+
((country & 0x300) >> 8) | ((service & 0xf) << 2),
|
|
298
|
+
(service & 0x3f0) >> 4,
|
|
299
|
+
].map((word) => word & 0x3f);
|
|
300
|
+
}
|
|
301
|
+
/** @param {string} postal @param {number} country @param {number} service */
|
|
302
|
+
function mode3Primary(postal, country, service) {
|
|
303
|
+
if (typeof postal !== 'string' || postal.length < 1 || postal.length > 6) {
|
|
304
|
+
throw new EncodeError('MaxiCode Mode 3: postalCode must contain 1 to 6 Code Set A characters');
|
|
305
|
+
}
|
|
306
|
+
const values = postal.toUpperCase().padEnd(6, ' ').split('').map((c) => codeSetAValue(c.charCodeAt(0)));
|
|
307
|
+
if (values.some((value) => value === null))
|
|
308
|
+
throw new EncodeError('MaxiCode Mode 3: postalCode contains an unsupported character');
|
|
309
|
+
const v = /** @type {number[]} */ (values);
|
|
310
|
+
return [
|
|
311
|
+
((v[5] & 0x03) << 4) | 3,
|
|
312
|
+
((v[4] & 0x03) << 4) | ((v[5] & 0x3c) >> 2),
|
|
313
|
+
((v[3] & 0x03) << 4) | ((v[4] & 0x3c) >> 2),
|
|
314
|
+
((v[2] & 0x03) << 4) | ((v[3] & 0x3c) >> 2),
|
|
315
|
+
((v[1] & 0x03) << 4) | ((v[2] & 0x3c) >> 2),
|
|
316
|
+
((v[0] & 0x03) << 4) | ((v[1] & 0x3c) >> 2),
|
|
317
|
+
((v[0] & 0x3c) >> 2) | ((country & 0x3) << 4),
|
|
318
|
+
(country & 0xfc) >> 2,
|
|
319
|
+
((country & 0x300) >> 8) | ((service & 0xf) << 2),
|
|
320
|
+
(service & 0x3f0) >> 4,
|
|
321
|
+
].map((word) => word & 0x3f);
|
|
322
|
+
}
|
|
323
|
+
/** @param {MaxiCodeEncodeOptions} options @returns {2|3|4|5} */
|
|
324
|
+
function normalizeMode(options) {
|
|
325
|
+
const mode = options.mode ?? 4;
|
|
326
|
+
if (!Number.isInteger(mode) || mode < 2 || mode > 5) {
|
|
327
|
+
throw new EncodeError('MaxiCode: mode must be 2, 3, 4 or 5');
|
|
328
|
+
}
|
|
329
|
+
return /** @type {2|3|4|5} */ (mode);
|
|
330
|
+
}
|
|
331
|
+
/** @param {MaxiCodeEncodeOptions} options @param {2|3|4|5} mode @param {number[]} [payload] */
|
|
332
|
+
function primaryCodewords(options, mode, payload = []) {
|
|
333
|
+
if (mode === 4 || mode === 5) {
|
|
334
|
+
const primary = [mode, ...payload.slice(0, 9)];
|
|
335
|
+
while (primary.length < 10)
|
|
336
|
+
primary.push(33);
|
|
337
|
+
return primary;
|
|
338
|
+
}
|
|
339
|
+
const primary = options.primary;
|
|
340
|
+
if (!primary || typeof primary.postalCode !== 'string') {
|
|
341
|
+
throw new EncodeError(`MaxiCode Mode ${mode}: primary { postalCode, countryCode, serviceClass } is required`);
|
|
342
|
+
}
|
|
343
|
+
const country = primary.countryCode;
|
|
344
|
+
const service = primary.serviceClass;
|
|
345
|
+
if (!Number.isInteger(country) || country < 0 || country > 999 ||
|
|
346
|
+
!Number.isInteger(service) || service < 0 || service > 999) {
|
|
347
|
+
throw new EncodeError('MaxiCode: countryCode and serviceClass must be integers from 0 to 999');
|
|
348
|
+
}
|
|
349
|
+
return mode === 2
|
|
350
|
+
? mode2Primary(primary.postalCode, country, service)
|
|
351
|
+
: mode3Primary(primary.postalCode, country, service);
|
|
352
|
+
}
|
|
353
|
+
/** @param {number[]} data @param {number} eccLength @returns {number[]} */
|
|
354
|
+
function secondaryErrorCorrection(data, eccLength) {
|
|
355
|
+
const halfData = data.length / 2;
|
|
356
|
+
const even = [];
|
|
357
|
+
const odd = [];
|
|
358
|
+
for (let i = 0; i < data.length; i++)
|
|
359
|
+
(i % 2 === 0 ? even : odd).push(data[i]);
|
|
360
|
+
if (even.length !== halfData || odd.length !== halfData)
|
|
361
|
+
throw new EncodeError('MaxiCode: secondary data must have an even length');
|
|
362
|
+
const evenParity = rsEncode(even, eccLength / 2, GF64, 1);
|
|
363
|
+
const oddParity = rsEncode(odd, eccLength / 2, GF64, 1);
|
|
364
|
+
const parity = [];
|
|
365
|
+
for (let i = 0; i < evenParity.length; i++) {
|
|
366
|
+
parity.push(evenParity[i], oddParity[i]);
|
|
367
|
+
}
|
|
368
|
+
return parity;
|
|
369
|
+
}
|
|
370
|
+
/** @param {number[]} codewords @returns {BitMatrix} */
|
|
371
|
+
export function placeMaxiCodeCodewords(codewords) {
|
|
372
|
+
if (!Array.isArray(codewords) || codewords.length !== MAXICODE_CODEWORDS ||
|
|
373
|
+
codewords.some((word) => !Number.isInteger(word) || word < 0 || word > 63)) {
|
|
374
|
+
throw new EncodeError('MaxiCode: internal codeword stream must contain 144 six-bit values');
|
|
375
|
+
}
|
|
376
|
+
const matrix = new BitMatrix(MAXICODE_WIDTH, MAXICODE_HEIGHT);
|
|
377
|
+
for (let y = 0; y < MAXICODE_HEIGHT; y++) {
|
|
378
|
+
for (let x = 0; x < MAXICODE_WIDTH; x++) {
|
|
379
|
+
const sequence = MAXICODE_GRID[y * MAXICODE_WIDTH + x];
|
|
380
|
+
if (sequence === 0)
|
|
381
|
+
continue;
|
|
382
|
+
const wire = sequence + 5;
|
|
383
|
+
const codeword = Math.floor(wire / 6) - 1;
|
|
384
|
+
const bit = 5 - (wire % 6);
|
|
385
|
+
matrix.setValue(x, y, ((codewords[codeword] >>> bit) & 1) !== 0);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Finder: the standard's hexagonal bull's-eye is sampled as concentric
|
|
389
|
+
// rings by the square-module interchange representation used by this SDK.
|
|
390
|
+
for (let y = 0; y < MAXICODE_HEIGHT; y++)
|
|
391
|
+
for (let x = 0; x < MAXICODE_WIDTH; x++) {
|
|
392
|
+
const value = maxicodeFinderValue(x, y);
|
|
393
|
+
if (value !== null)
|
|
394
|
+
matrix.setValue(x, y, value);
|
|
395
|
+
}
|
|
396
|
+
for (const [x, y] of MAXICODE_ORIENTATION_DARK)
|
|
397
|
+
matrix.set(x, y);
|
|
398
|
+
return matrix;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Encode a MaxiCode symbol. Modes 2 and 3 carry a structured primary message;
|
|
402
|
+
* modes 4 and 5 encode an unstructured secondary text message.
|
|
403
|
+
*
|
|
404
|
+
* @param {string|Uint8Array|number[]} value
|
|
405
|
+
* @param {MaxiCodeEncodeOptions} [options]
|
|
406
|
+
* @returns {MaxiCodeMatrix}
|
|
407
|
+
*/
|
|
408
|
+
export function encodeMaxiCode(value, options = {}) {
|
|
409
|
+
const mode = normalizeMode(options);
|
|
410
|
+
const data = encodeMaxiCodeText(value);
|
|
411
|
+
const secondaryLength = mode === 5 ? 68 : 84;
|
|
412
|
+
const secondaryEccLength = mode === 5 ? 56 : 40;
|
|
413
|
+
const primaryDataLength = mode === 4 || mode === 5 ? 9 : 0;
|
|
414
|
+
if (data.length > primaryDataLength + secondaryLength) {
|
|
415
|
+
throw new EncodeError(`MaxiCode Mode ${mode}: payload needs ${data.length} codewords, maximum is ` +
|
|
416
|
+
`${primaryDataLength + secondaryLength}`);
|
|
417
|
+
}
|
|
418
|
+
const primary = primaryCodewords(options, mode, data);
|
|
419
|
+
const secondary = data.slice(primaryDataLength);
|
|
420
|
+
while (secondary.length < secondaryLength)
|
|
421
|
+
secondary.push(33);
|
|
422
|
+
const primaryParity = rsEncode(primary, 10, GF64, 1);
|
|
423
|
+
const codewords = primary.concat(primaryParity, secondary, secondaryErrorCorrection(secondary, secondaryEccLength));
|
|
424
|
+
if (codewords.length !== MAXICODE_CODEWORDS)
|
|
425
|
+
throw new EncodeError('MaxiCode: internal codeword length mismatch');
|
|
426
|
+
const matrix = /** @type {MaxiCodeMatrix} */ (placeMaxiCodeCodewords(codewords));
|
|
427
|
+
matrix.maxicode = {
|
|
428
|
+
mode,
|
|
429
|
+
codewords: codewords.slice(),
|
|
430
|
+
dataCodewords: secondary.slice(),
|
|
431
|
+
dataModules: MAXICODE_DATA_MODULES,
|
|
432
|
+
moduleShape: 'hexagonal',
|
|
433
|
+
width: MAXICODE_WIDTH,
|
|
434
|
+
height: MAXICODE_HEIGHT,
|
|
435
|
+
};
|
|
436
|
+
return matrix;
|
|
437
|
+
}
|
|
438
|
+
export { mode2Primary, mode3Primary, primaryCodewords };
|
|
@@ -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
|
+
/** MaxiCode public entry points. @module maxicode */
|
|
31
|
+
export { encodeMaxiCode, encodeMaxiCodeText, placeMaxiCodeCodewords } from './encoder.js';
|
|
32
|
+
export { decodeMaxiCode, decodeMaxiCodeText, maxicodeStructureMatches, readMaxiCodeCodewords } from './decoder.js';
|
|
33
|
+
export { detectMaxiCode, detectAndDecodeMaxiCode } from './detector.js';
|
|
34
|
+
export { MAXICODE_CODEWORDS, MAXICODE_DATA_MODULES, MAXICODE_FIELD_SIZE, MAXICODE_GRID, MAXICODE_HEIGHT, MAXICODE_WIDTH, validateMaxiCodeTables, } from './tables.js';
|
|
@@ -0,0 +1,132 @@
|
|
|
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 structural constants and the module sequence from ISO/IEC 16023
|
|
32
|
+
* Figure 5. The sequence is transcribed from the published standard figure,
|
|
33
|
+
* not imported from another implementation. Zero entries are the alternating
|
|
34
|
+
* row gaps, finder reservation and orientation markers; values 1..864 identify
|
|
35
|
+
* the six-bit data modules in wire order.
|
|
36
|
+
*
|
|
37
|
+
* @module maxicode/tables
|
|
38
|
+
*/
|
|
39
|
+
export const MAXICODE_WIDTH = 30;
|
|
40
|
+
export const MAXICODE_HEIGHT = 33;
|
|
41
|
+
export const MAXICODE_CODEWORDS = 144;
|
|
42
|
+
export const MAXICODE_DATA_MODULES = 864;
|
|
43
|
+
export const MAXICODE_FIELD_SIZE = 64;
|
|
44
|
+
/** ISO 16023 Figure 5, laid out as 33 rows of 30 logical positions. */
|
|
45
|
+
const MAXICODE_GRID_ROWS = [
|
|
46
|
+
[122, 121, 128, 127, 134, 133, 140, 139, 146, 145, 152, 151, 158, 157, 164, 163, 170, 169, 176, 175, 182, 181, 188, 187, 194, 193, 200, 199, 0, 0],
|
|
47
|
+
[124, 123, 130, 129, 136, 135, 142, 141, 148, 147, 154, 153, 160, 159, 166, 165, 172, 171, 178, 177, 184, 183, 190, 189, 196, 195, 202, 201, 817, 0],
|
|
48
|
+
[126, 125, 132, 131, 138, 137, 144, 143, 150, 149, 156, 155, 162, 161, 168, 167, 174, 173, 180, 179, 186, 185, 192, 191, 198, 197, 204, 203, 819, 818],
|
|
49
|
+
[284, 283, 278, 277, 272, 271, 266, 265, 260, 259, 254, 253, 248, 247, 242, 241, 236, 235, 230, 229, 224, 223, 218, 217, 212, 211, 206, 205, 820, 0],
|
|
50
|
+
[286, 285, 280, 279, 274, 273, 268, 267, 262, 261, 256, 255, 250, 249, 244, 243, 238, 237, 232, 231, 226, 225, 220, 219, 214, 213, 208, 207, 822, 821],
|
|
51
|
+
[288, 287, 282, 281, 276, 275, 270, 269, 264, 263, 258, 257, 252, 251, 246, 245, 240, 239, 234, 233, 228, 227, 222, 221, 216, 215, 210, 209, 823, 0],
|
|
52
|
+
[290, 289, 296, 295, 302, 301, 308, 307, 314, 313, 320, 319, 326, 325, 332, 331, 338, 337, 344, 343, 350, 349, 356, 355, 362, 361, 368, 367, 825, 824],
|
|
53
|
+
[292, 291, 298, 297, 304, 303, 310, 309, 316, 315, 322, 321, 328, 327, 334, 333, 340, 339, 346, 345, 352, 351, 358, 357, 364, 363, 370, 369, 826, 0],
|
|
54
|
+
[294, 293, 300, 299, 306, 305, 312, 311, 318, 317, 324, 323, 330, 329, 336, 335, 342, 341, 348, 347, 354, 353, 360, 359, 366, 365, 372, 371, 828, 827],
|
|
55
|
+
[410, 409, 404, 403, 398, 397, 392, 391, 80, 79, 0, 0, 14, 13, 38, 37, 3, 0, 45, 44, 110, 109, 386, 385, 380, 379, 374, 373, 829, 0],
|
|
56
|
+
[412, 411, 406, 405, 400, 399, 394, 393, 82, 81, 41, 0, 16, 15, 40, 39, 4, 0, 0, 46, 112, 111, 388, 387, 382, 381, 376, 375, 831, 830],
|
|
57
|
+
[414, 413, 408, 407, 402, 401, 396, 395, 84, 83, 42, 0, 0, 0, 0, 0, 6, 5, 48, 47, 114, 113, 390, 389, 384, 383, 378, 377, 832, 0],
|
|
58
|
+
[416, 415, 422, 421, 428, 427, 104, 103, 56, 55, 17, 0, 0, 0, 0, 0, 0, 0, 21, 20, 86, 85, 434, 433, 440, 439, 446, 445, 834, 833],
|
|
59
|
+
[418, 417, 424, 423, 430, 429, 106, 105, 58, 57, 0, 0, 0, 0, 0, 0, 0, 0, 23, 22, 88, 87, 436, 435, 442, 441, 448, 447, 835, 0],
|
|
60
|
+
[420, 419, 426, 425, 432, 431, 108, 107, 60, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 90, 89, 438, 437, 444, 443, 450, 449, 837, 836],
|
|
61
|
+
[482, 481, 476, 475, 470, 469, 49, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 54, 53, 464, 463, 458, 457, 452, 451, 838, 0],
|
|
62
|
+
[484, 483, 478, 477, 472, 471, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 466, 465, 460, 459, 454, 453, 840, 839],
|
|
63
|
+
[486, 485, 480, 479, 474, 473, 52, 51, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 43, 468, 467, 462, 461, 456, 455, 841, 0],
|
|
64
|
+
[488, 487, 494, 493, 500, 499, 98, 97, 62, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 92, 91, 506, 505, 512, 511, 518, 517, 843, 842],
|
|
65
|
+
[490, 489, 496, 495, 502, 501, 100, 99, 64, 63, 0, 0, 0, 0, 0, 0, 0, 0, 29, 28, 94, 93, 508, 507, 514, 513, 520, 519, 844, 0],
|
|
66
|
+
[492, 491, 498, 497, 504, 503, 102, 101, 66, 65, 18, 0, 0, 0, 0, 0, 0, 0, 19, 30, 96, 95, 510, 509, 516, 515, 522, 521, 846, 845],
|
|
67
|
+
[560, 559, 554, 553, 548, 547, 542, 541, 74, 73, 33, 0, 0, 0, 0, 0, 0, 11, 68, 67, 116, 115, 536, 535, 530, 529, 524, 523, 847, 0],
|
|
68
|
+
[562, 561, 556, 555, 550, 549, 544, 543, 76, 75, 0, 0, 8, 7, 36, 35, 12, 0, 70, 69, 118, 117, 538, 537, 532, 531, 526, 525, 849, 848],
|
|
69
|
+
[564, 563, 558, 557, 552, 551, 546, 545, 78, 77, 0, 34, 10, 9, 26, 25, 0, 0, 72, 71, 120, 119, 540, 539, 534, 533, 528, 527, 850, 0],
|
|
70
|
+
[566, 565, 572, 571, 578, 577, 584, 583, 590, 589, 596, 595, 602, 601, 608, 607, 614, 613, 620, 619, 626, 625, 632, 631, 638, 637, 644, 643, 852, 851],
|
|
71
|
+
[568, 567, 574, 573, 580, 579, 586, 585, 592, 591, 598, 597, 604, 603, 610, 609, 616, 615, 622, 621, 628, 627, 634, 633, 640, 639, 646, 645, 853, 0],
|
|
72
|
+
[570, 569, 576, 575, 582, 581, 588, 587, 594, 593, 600, 599, 606, 605, 612, 611, 618, 617, 624, 623, 630, 629, 636, 635, 642, 641, 648, 647, 855, 854],
|
|
73
|
+
[728, 727, 722, 721, 716, 715, 710, 709, 704, 703, 698, 697, 692, 691, 686, 685, 680, 679, 674, 673, 668, 667, 662, 661, 656, 655, 650, 649, 856, 0],
|
|
74
|
+
[730, 729, 724, 723, 718, 717, 712, 711, 706, 705, 700, 699, 694, 693, 688, 687, 682, 681, 676, 675, 670, 669, 664, 663, 658, 657, 652, 651, 858, 857],
|
|
75
|
+
[732, 731, 726, 725, 720, 719, 714, 713, 708, 707, 702, 701, 696, 695, 690, 689, 684, 683, 678, 677, 672, 671, 666, 665, 660, 659, 654, 653, 859, 0],
|
|
76
|
+
[734, 733, 740, 739, 746, 745, 752, 751, 758, 757, 764, 763, 770, 769, 776, 775, 782, 781, 788, 787, 794, 793, 800, 799, 806, 805, 812, 811, 861, 860],
|
|
77
|
+
[736, 735, 742, 741, 748, 747, 754, 753, 760, 759, 766, 765, 772, 771, 778, 777, 784, 783, 790, 789, 796, 795, 802, 801, 808, 807, 814, 813, 862, 0],
|
|
78
|
+
[738, 737, 744, 743, 750, 749, 756, 755, 762, 761, 768, 767, 774, 773, 780, 779, 786, 785, 792, 791, 798, 797, 804, 803, 810, 809, 816, 815, 864, 863],
|
|
79
|
+
];
|
|
80
|
+
/** Flattened standard module sequence. */
|
|
81
|
+
export const MAXICODE_GRID = MAXICODE_GRID_ROWS.flat();
|
|
82
|
+
/** Fixed orientation modules, expressed as [x, y] logical positions. */
|
|
83
|
+
export const MAXICODE_ORIENTATION_DARK = Object.freeze([
|
|
84
|
+
[28, 0], [29, 0],
|
|
85
|
+
[10, 9], [11, 9], [11, 10],
|
|
86
|
+
[7, 15], [8, 16],
|
|
87
|
+
[20, 16], [20, 17],
|
|
88
|
+
[10, 22], [10, 23],
|
|
89
|
+
[17, 22], [17, 23],
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* Return the fixed colour of a structural module, or null for a data module.
|
|
93
|
+
*
|
|
94
|
+
* MaxiCode's finder is drawn on a staggered hexagonal lattice. The logical
|
|
95
|
+
* interchange grid keeps one cell per module, so the standard ring radii are
|
|
96
|
+
* evaluated at the centre of each staggered cell. Using the structural zero
|
|
97
|
+
* entries from Figure 5 here is important: treating the entire circular area
|
|
98
|
+
* as finder cells would overwrite payload modules at the edge of the rings.
|
|
99
|
+
*/
|
|
100
|
+
export function maxicodeFinderValue(x, y) {
|
|
101
|
+
if (MAXICODE_GRID[y * MAXICODE_WIDTH + x] !== 0)
|
|
102
|
+
return null;
|
|
103
|
+
// Coordinates are scaled only to preserve the 30:33 staggered lattice
|
|
104
|
+
// proportions. The constants are the normalized centres and radii of the
|
|
105
|
+
// three standard annuli; a scale factor cancels out of the classification.
|
|
106
|
+
const cellX = (y & 1) ? x + 34.5 / 35 : x + 16.5 / 35;
|
|
107
|
+
const cellY = y + 20 / 30;
|
|
108
|
+
const centreX = 507 / 35;
|
|
109
|
+
const centreY = 506 / 30;
|
|
110
|
+
const distance = Math.hypot((cellX - centreX) * 35, (cellY - centreY) * 30);
|
|
111
|
+
return (distance >= 20 && distance < 46) ||
|
|
112
|
+
(distance >= 73 && distance < 100) ||
|
|
113
|
+
(distance >= 126 && distance < 153);
|
|
114
|
+
}
|
|
115
|
+
/** Verify that the transcribed standard sequence is complete and one-to-one. */
|
|
116
|
+
export function validateMaxiCodeTables() {
|
|
117
|
+
const errors = [];
|
|
118
|
+
if (MAXICODE_GRID.length !== MAXICODE_WIDTH * MAXICODE_HEIGHT) {
|
|
119
|
+
errors.push(`grid length ${MAXICODE_GRID.length} is not ${MAXICODE_WIDTH * MAXICODE_HEIGHT}`);
|
|
120
|
+
}
|
|
121
|
+
const values = MAXICODE_GRID.filter((value) => value > 0);
|
|
122
|
+
if (values.length !== MAXICODE_DATA_MODULES)
|
|
123
|
+
errors.push(`grid has ${values.length} data modules`);
|
|
124
|
+
const seen = new Set(values);
|
|
125
|
+
if (seen.size !== values.length)
|
|
126
|
+
errors.push('grid contains duplicate module sequence numbers');
|
|
127
|
+
for (let value = 1; value <= MAXICODE_DATA_MODULES; value++) {
|
|
128
|
+
if (!seen.has(value))
|
|
129
|
+
errors.push(`grid is missing module sequence ${value}`);
|
|
130
|
+
}
|
|
131
|
+
return errors;
|
|
132
|
+
}
|