@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,316 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Sythos Barcode Suite
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Sythos
|
|
7
|
+
* SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
|
|
8
|
+
* SPDX-License-Identifier: MIT
|
|
9
|
+
*
|
|
10
|
+
* Original work. No code from any other barcode implementation.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Han Xin Code structural tables and geometry.
|
|
14
|
+
*
|
|
15
|
+
* This module deliberately starts with the compact, alignment-free part of
|
|
16
|
+
* ISO/IEC 20830: versions 1 through 3. Keeping the function pattern mask in
|
|
17
|
+
* one place makes the encoder, decoder and detector agree about every payload
|
|
18
|
+
* cell and prevents accidental data placement over structural information.
|
|
19
|
+
*
|
|
20
|
+
* @module hanxin/tables
|
|
21
|
+
*/
|
|
22
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
23
|
+
import { GF16, GaloisField } from '../core/galois-field.js';
|
|
24
|
+
import { rsDecode, rsEncode } from '../core/reed-solomon.js';
|
|
25
|
+
import { FormatError } from '../core/errors.js';
|
|
26
|
+
export const HANXIN_MIN_VERSION = 1;
|
|
27
|
+
export const HANXIN_MAX_VERSION = 3;
|
|
28
|
+
export const HANXIN_VERSIONS = [1, 2, 3];
|
|
29
|
+
export const HANXIN_ECC_LEVELS = ['L1', 'L2', 'L3', 'L4'];
|
|
30
|
+
/** Han Xin's data field is GF(2^8) with x^8+x^6+x^5+x+1. */
|
|
31
|
+
export const GF256_HANXIN = new GaloisField({
|
|
32
|
+
size: 256,
|
|
33
|
+
primitive: 0x163,
|
|
34
|
+
name: 'GF(256)/HanXin',
|
|
35
|
+
});
|
|
36
|
+
/** Total codewords, including error correction, for versions 1 through 3. */
|
|
37
|
+
export const HANXIN_TOTAL_CODEWORDS = [25, 37, 50];
|
|
38
|
+
/** Data modules, including the five zero remainder modules in each compact version. */
|
|
39
|
+
export const HANXIN_DATA_MODULES = [205, 301, 405];
|
|
40
|
+
/** Unused tail modules after the complete codeword stream. */
|
|
41
|
+
export const HANXIN_REMAINDER_BITS = [5, 5, 5];
|
|
42
|
+
/**
|
|
43
|
+
* One Reed--Solomon batch is `(blockCount, dataCodewords, eccCodewords)`.
|
|
44
|
+
* The compact versions use one block at every error-correction level.
|
|
45
|
+
*/
|
|
46
|
+
const EC_BATCHES = [
|
|
47
|
+
[[1, 21, 4], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
48
|
+
[[1, 17, 8], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
49
|
+
[[1, 13, 12], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
50
|
+
[[1, 9, 16], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
51
|
+
[[1, 31, 6], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
52
|
+
[[1, 25, 12], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
53
|
+
[[1, 19, 18], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
54
|
+
[[1, 15, 22], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
55
|
+
[[1, 42, 8], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
56
|
+
[[1, 34, 16], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
57
|
+
[[1, 26, 24], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
58
|
+
[[1, 20, 30], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
|
59
|
+
];
|
|
60
|
+
/** @returns {number} Side length in modules. */
|
|
61
|
+
export function hanXinSize(version) {
|
|
62
|
+
if (!Number.isInteger(version) || version < HANXIN_MIN_VERSION || version > HANXIN_MAX_VERSION) {
|
|
63
|
+
throw new FormatError(`Han Xin: supported versions are 1-3, got ${version}`);
|
|
64
|
+
}
|
|
65
|
+
return 21 + version * 2;
|
|
66
|
+
}
|
|
67
|
+
/** @returns {HanXinVersion} */
|
|
68
|
+
export function normalizeHanXinVersion(value) {
|
|
69
|
+
const version = typeof value === 'string' ? Number(value.replace(/^V/i, '')) : Number(value);
|
|
70
|
+
if (!Number.isInteger(version) || version < HANXIN_MIN_VERSION || version > HANXIN_MAX_VERSION) {
|
|
71
|
+
throw new FormatError(`Han Xin: supported versions are 1-3, got ${String(value)}`);
|
|
72
|
+
}
|
|
73
|
+
return version;
|
|
74
|
+
}
|
|
75
|
+
/** @returns {HanXinEccLevel} */
|
|
76
|
+
export function normalizeHanXinEcc(value) {
|
|
77
|
+
if (value == null)
|
|
78
|
+
return 'L1';
|
|
79
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 4) {
|
|
80
|
+
return HANXIN_ECC_LEVELS[value - 1];
|
|
81
|
+
}
|
|
82
|
+
const text = String(value).toUpperCase();
|
|
83
|
+
if (HANXIN_ECC_LEVELS.includes(text))
|
|
84
|
+
return text;
|
|
85
|
+
throw new FormatError(`Han Xin: error correction must be L1, L2, L3 or L4, got ${String(value)}`);
|
|
86
|
+
}
|
|
87
|
+
/** @returns {number} Index of an error-correction level. */
|
|
88
|
+
export function hanXinEccIndex(level) {
|
|
89
|
+
return HANXIN_ECC_LEVELS.indexOf(level);
|
|
90
|
+
}
|
|
91
|
+
/** @returns {{blockCount:number,dataCodewords:number,eccCodewords:number}} */
|
|
92
|
+
export function hanXinEcLayout(version, level) {
|
|
93
|
+
const entry = EC_BATCHES[(version - 1) * 4 + hanXinEccIndex(level)][0];
|
|
94
|
+
return { blockCount: entry[0], dataCodewords: entry[1], eccCodewords: entry[2] };
|
|
95
|
+
}
|
|
96
|
+
/** @returns {number} Data codewords for a version and EC level. */
|
|
97
|
+
export function hanXinDataCodewords(version, level) {
|
|
98
|
+
return hanXinEcLayout(version, level).dataCodewords;
|
|
99
|
+
}
|
|
100
|
+
/** Four orientation-specific 7x7 finder patterns, packed MSB first. */
|
|
101
|
+
export const HANXIN_FINDER_TOP_LEFT = [0x7f, 0x40, 0x5f, 0x50, 0x57, 0x57, 0x57];
|
|
102
|
+
export const HANXIN_FINDER_SIDE = [0x7f, 0x01, 0x7d, 0x05, 0x75, 0x75, 0x75];
|
|
103
|
+
export const HANXIN_FINDER_BOTTOM_RIGHT = [0x75, 0x75, 0x75, 0x05, 0x7d, 0x01, 0x7f];
|
|
104
|
+
function index(size, x, y) {
|
|
105
|
+
return y * size + x;
|
|
106
|
+
}
|
|
107
|
+
function reserve(mask, matrix, x, y, dark) {
|
|
108
|
+
if (x < 0 || y < 0 || x >= matrix.width || y >= matrix.height)
|
|
109
|
+
return;
|
|
110
|
+
mask[index(matrix.width, x, y)] = 1;
|
|
111
|
+
matrix.setValue(x, y, dark);
|
|
112
|
+
}
|
|
113
|
+
function placeFinder(mask, matrix, offsetX, offsetY, rows) {
|
|
114
|
+
for (let y = 0; y < 7; y++)
|
|
115
|
+
for (let x = 0; x < 7; x++) {
|
|
116
|
+
reserve(mask, matrix, offsetX + x, offsetY + y, (rows[y] & (0x40 >> x)) !== 0);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Return the function modules and their fixed darkness for a version. */
|
|
120
|
+
export function createHanXinFunctionGrid(version) {
|
|
121
|
+
const size = hanXinSize(version);
|
|
122
|
+
const matrix = new BitMatrix(size, size);
|
|
123
|
+
const reserved = new Uint8Array(size * size);
|
|
124
|
+
placeFinder(reserved, matrix, 0, 0, HANXIN_FINDER_TOP_LEFT);
|
|
125
|
+
placeFinder(reserved, matrix, size - 7, 0, HANXIN_FINDER_SIDE);
|
|
126
|
+
placeFinder(reserved, matrix, 0, size - 7, HANXIN_FINDER_SIDE);
|
|
127
|
+
placeFinder(reserved, matrix, size - 7, size - 7, HANXIN_FINDER_BOTTOM_RIGHT);
|
|
128
|
+
// The one-module light separators belong to the function region.
|
|
129
|
+
for (let i = 0; i < 8; i++) {
|
|
130
|
+
reserve(reserved, matrix, i, 7, false);
|
|
131
|
+
reserve(reserved, matrix, 7, i, false);
|
|
132
|
+
reserve(reserved, matrix, size - i - 1, 7, false);
|
|
133
|
+
reserve(reserved, matrix, 7, size - i - 1, false);
|
|
134
|
+
reserve(reserved, matrix, size - 8, i, false);
|
|
135
|
+
reserve(reserved, matrix, i, size - 8, false);
|
|
136
|
+
reserve(reserved, matrix, size - 8, size - i - 1, false);
|
|
137
|
+
reserve(reserved, matrix, size - i - 1, size - 8, false);
|
|
138
|
+
}
|
|
139
|
+
// Two redundant copies of the 34-bit structural information are carried
|
|
140
|
+
// around the finder patterns. The four 9-module strips below reserve all
|
|
141
|
+
// positions; shared corners make the wire order 9+8+9+8 modules per copy.
|
|
142
|
+
for (let i = 0; i < 9; i++) {
|
|
143
|
+
reserve(reserved, matrix, i, 8, false);
|
|
144
|
+
reserve(reserved, matrix, 8, i, false);
|
|
145
|
+
reserve(reserved, matrix, size - i - 1, 8, false);
|
|
146
|
+
reserve(reserved, matrix, 8, size - i - 1, false);
|
|
147
|
+
reserve(reserved, matrix, size - 9, i, false);
|
|
148
|
+
reserve(reserved, matrix, i, size - 9, false);
|
|
149
|
+
reserve(reserved, matrix, size - 9, size - i - 1, false);
|
|
150
|
+
reserve(reserved, matrix, size - i - 1, size - 9, false);
|
|
151
|
+
}
|
|
152
|
+
return { matrix, reserved };
|
|
153
|
+
}
|
|
154
|
+
/** Return payload positions in the normative row-major order. */
|
|
155
|
+
export function hanXinDataCoordinates(version) {
|
|
156
|
+
const { reserved } = createHanXinFunctionGrid(version);
|
|
157
|
+
const size = hanXinSize(version);
|
|
158
|
+
const result = [];
|
|
159
|
+
for (let y = 0; y < size; y++)
|
|
160
|
+
for (let x = 0; x < size; x++) {
|
|
161
|
+
if (reserved[index(size, x, y)] === 0)
|
|
162
|
+
result.push([x, y]);
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
/** Whether a data module is inverted by one of Han Xin's four masks. */
|
|
167
|
+
export function hanXinMaskFlip(mask, x, y) {
|
|
168
|
+
if (!Number.isInteger(mask) || mask < 0 || mask > 3)
|
|
169
|
+
return false;
|
|
170
|
+
const i = y + 1;
|
|
171
|
+
const j = x + 1;
|
|
172
|
+
// The first public mask is the constant-zero (no inversion) mask. The
|
|
173
|
+
// remaining three masks are the parity expressions from the format
|
|
174
|
+
// definition, represented here with zero-based API values 1-3.
|
|
175
|
+
if (mask === 0)
|
|
176
|
+
return false;
|
|
177
|
+
if (mask === 1)
|
|
178
|
+
return ((i + j) & 1) === 0;
|
|
179
|
+
if (mask === 2)
|
|
180
|
+
return ((((i + j) % 3) + (j % 3)) & 1) === 0;
|
|
181
|
+
if (j === 0 || i === 0)
|
|
182
|
+
return false;
|
|
183
|
+
return (((i % j) + (j % i) + (i % 3) + (j % 3)) & 1) === 0;
|
|
184
|
+
}
|
|
185
|
+
/** Build the 34 structural bits protected by the GF(16) short RS block. */
|
|
186
|
+
export function hanXinFunctionInfoBits(version, level, mask) {
|
|
187
|
+
if (!Number.isInteger(mask) || mask < 0 || mask > 3)
|
|
188
|
+
throw new FormatError('Han Xin: mask must be an integer from 0 to 3');
|
|
189
|
+
const value = ((version + 20) << 4) | (hanXinEccIndex(level) << 2) | mask;
|
|
190
|
+
const data = [(value >>> 8) & 0x0f, (value >>> 4) & 0x0f, value & 0x0f];
|
|
191
|
+
const ecc = rsEncode(data, 4, GF16, 1);
|
|
192
|
+
const bits = [];
|
|
193
|
+
for (const symbol of data.concat(ecc))
|
|
194
|
+
for (let bit = 3; bit >= 0; bit--)
|
|
195
|
+
bits.push(((symbol >>> bit) & 1) !== 0);
|
|
196
|
+
// The six non-codeword bits are part of the fixed Han Xin function
|
|
197
|
+
// information pattern, not arbitrary padding.
|
|
198
|
+
for (const bit of [false, true, false, true, false, true])
|
|
199
|
+
bits.push(bit);
|
|
200
|
+
if (bits.length !== 34)
|
|
201
|
+
throw new FormatError('Han Xin: invalid function information length');
|
|
202
|
+
return bits;
|
|
203
|
+
}
|
|
204
|
+
/** Place both redundant structural-information copies in the fixed strips. */
|
|
205
|
+
export function placeHanXinFunctionInfo(matrix, version, level, mask) {
|
|
206
|
+
const size = hanXinSize(version);
|
|
207
|
+
const bits = hanXinFunctionInfoBits(version, level, mask);
|
|
208
|
+
// The four strips contain 9 + 8 + 9 + 8 modules. The corner cells where
|
|
209
|
+
// two strips meet are shared; they must not consume the preceding bit a
|
|
210
|
+
// second time.
|
|
211
|
+
for (let i = 0; i < 9; i++) {
|
|
212
|
+
matrix.setValue(i, 8, bits[i]);
|
|
213
|
+
matrix.setValue(size - 1 - i, size - 9, bits[i]);
|
|
214
|
+
}
|
|
215
|
+
for (let i = 0; i < 8; i++) {
|
|
216
|
+
matrix.setValue(8, 7 - i, bits[9 + i]);
|
|
217
|
+
matrix.setValue(size - 9, size - 8 + i, bits[9 + i]);
|
|
218
|
+
}
|
|
219
|
+
for (let i = 0; i < 9; i++) {
|
|
220
|
+
matrix.setValue(size - 9, i, bits[i + 17]);
|
|
221
|
+
matrix.setValue(8, size - 1 - i, bits[i + 17]);
|
|
222
|
+
}
|
|
223
|
+
for (let i = 0; i < 8; i++) {
|
|
224
|
+
matrix.setValue(size - 8 + i, 8, bits[26 + i]);
|
|
225
|
+
matrix.setValue(7 - i, size - 9, bits[26 + i]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function readInfoCopy(matrix, version) {
|
|
229
|
+
const size = hanXinSize(version);
|
|
230
|
+
const bits = [];
|
|
231
|
+
for (let i = 0; i < 9; i++)
|
|
232
|
+
bits.push(matrix.get(i, 8));
|
|
233
|
+
for (let i = 0; i < 8; i++)
|
|
234
|
+
bits.push(matrix.get(8, 7 - i));
|
|
235
|
+
for (let i = 0; i < 9; i++)
|
|
236
|
+
bits.push(matrix.get(size - 9, i));
|
|
237
|
+
for (let i = 0; i < 8; i++)
|
|
238
|
+
bits.push(matrix.get(size - 8 + i, 8));
|
|
239
|
+
return bits;
|
|
240
|
+
}
|
|
241
|
+
/** Decode a structural information copy, correcting up to two nibble errors. */
|
|
242
|
+
export function decodeHanXinFunctionInfo(matrix, version) {
|
|
243
|
+
const copies = [readInfoCopy(matrix, version)];
|
|
244
|
+
const size = hanXinSize(version);
|
|
245
|
+
const second = new BitMatrix(size, size);
|
|
246
|
+
// The second copy is read directly in its wire order. Keeping this helper
|
|
247
|
+
// local avoids exposing an orientation-specific representation publicly.
|
|
248
|
+
for (let i = 0; i < 9; i++) {
|
|
249
|
+
second.setValue(i, 8, matrix.get(size - 1 - i, size - 9));
|
|
250
|
+
}
|
|
251
|
+
for (let i = 0; i < 8; i++) {
|
|
252
|
+
second.setValue(8, 7 - i, matrix.get(size - 9, size - 8 + i));
|
|
253
|
+
}
|
|
254
|
+
for (let i = 0; i < 9; i++) {
|
|
255
|
+
second.setValue(size - 9, i, matrix.get(8, size - 1 - i));
|
|
256
|
+
}
|
|
257
|
+
for (let i = 0; i < 8; i++) {
|
|
258
|
+
second.setValue(size - 8 + i, 8, matrix.get(7 - i, size - 9));
|
|
259
|
+
}
|
|
260
|
+
copies.push(readInfoCopy(second, version));
|
|
261
|
+
let best = null;
|
|
262
|
+
for (const bits of copies) {
|
|
263
|
+
const fixedTail = [false, true, false, true, false, true];
|
|
264
|
+
if (bits.length !== 34 || fixedTail.some((bit, index) => bits[28 + index] !== bit))
|
|
265
|
+
continue;
|
|
266
|
+
const symbols = [];
|
|
267
|
+
for (let i = 0; i < 7; i++) {
|
|
268
|
+
let symbol = 0;
|
|
269
|
+
for (let bit = 0; bit < 4; bit++)
|
|
270
|
+
symbol = (symbol << 1) | (bits[i * 4 + bit] ? 1 : 0);
|
|
271
|
+
symbols.push(symbol);
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const corrections = rsDecode(symbols, 4, GF16, 1);
|
|
275
|
+
if (!best || corrections < best.corrections)
|
|
276
|
+
best = { value: symbols, corrections };
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Try the redundant copy before rejecting the symbol.
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!best)
|
|
283
|
+
throw new FormatError('Han Xin: structural information is unreadable');
|
|
284
|
+
const value = (best.value[0] << 8) | (best.value[1] << 4) | best.value[2];
|
|
285
|
+
const encodedVersion = (value >>> 4) - 20;
|
|
286
|
+
const levelIndex = (value >>> 2) & 3;
|
|
287
|
+
const mask = value & 3;
|
|
288
|
+
if (encodedVersion !== version)
|
|
289
|
+
throw new FormatError('Han Xin: structural version disagrees with matrix dimensions');
|
|
290
|
+
return {
|
|
291
|
+
version,
|
|
292
|
+
level: HANXIN_ECC_LEVELS[levelIndex],
|
|
293
|
+
mask,
|
|
294
|
+
corrections: best.corrections,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** Verify all fixed modules and count the payload cells. */
|
|
298
|
+
export function validateHanXinTables() {
|
|
299
|
+
const errors = [];
|
|
300
|
+
for (const version of HANXIN_VERSIONS) {
|
|
301
|
+
const size = hanXinSize(version);
|
|
302
|
+
const cells = hanXinDataCoordinates(version).length;
|
|
303
|
+
if (cells !== HANXIN_DATA_MODULES[version - 1]) {
|
|
304
|
+
errors.push(`Version ${version}: ${cells} data modules, expected ${HANXIN_DATA_MODULES[version - 1]}`);
|
|
305
|
+
}
|
|
306
|
+
for (const level of HANXIN_ECC_LEVELS) {
|
|
307
|
+
const layout = hanXinEcLayout(version, level);
|
|
308
|
+
if (layout.dataCodewords + layout.eccCodewords !== HANXIN_TOTAL_CODEWORDS[version - 1]) {
|
|
309
|
+
errors.push(`Version ${version} ${level}: invalid RS block total`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (size !== 21 + version * 2)
|
|
313
|
+
errors.push(`Version ${version}: invalid dimension`);
|
|
314
|
+
}
|
|
315
|
+
return errors;
|
|
316
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
const TRACKER_PROFILE = { top: 0.25, bottom: 0.75 };
|
|
31
|
+
const ASCENDER_PROFILE = { top: 0, bottom: 0.75 };
|
|
32
|
+
const DESCENDER_PROFILE = { top: 0.25, bottom: 1 };
|
|
33
|
+
const FULL_PROFILE = { top: 0, bottom: 1 };
|
|
34
|
+
// Keep validation and output allocations bounded when a caller passes data
|
|
35
|
+
// originating in a file or camera pipeline.
|
|
36
|
+
const MAX_PROFILE_LENGTH = 1000000;
|
|
37
|
+
function validateStateCount(stateCount) {
|
|
38
|
+
if (stateCount !== 2 && stateCount !== 4) {
|
|
39
|
+
throw new RangeError(`Height state count must be 2 or 4, got ${String(stateCount)}`);
|
|
40
|
+
}
|
|
41
|
+
return stateCount;
|
|
42
|
+
}
|
|
43
|
+
function validateProfileLength(value, label) {
|
|
44
|
+
if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
45
|
+
throw new TypeError(`${label} must be an array-like object`);
|
|
46
|
+
}
|
|
47
|
+
const length = value.length;
|
|
48
|
+
if (typeof length !== 'number'
|
|
49
|
+
|| !Number.isSafeInteger(length) || length < 0 || length > MAX_PROFILE_LENGTH) {
|
|
50
|
+
throw new RangeError(`${label} length must be a safe integer between 0 and ${MAX_PROFILE_LENGTH}`);
|
|
51
|
+
}
|
|
52
|
+
return length;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate one state and return its narrowed value.
|
|
56
|
+
*
|
|
57
|
+
* When `stateCount` is supplied, states outside that alphabet are rejected;
|
|
58
|
+
* leaving it out validates the complete four-state range.
|
|
59
|
+
*
|
|
60
|
+
* @param {number} state
|
|
61
|
+
* @param {2|4} [stateCount]
|
|
62
|
+
* @returns {HeightState}
|
|
63
|
+
*/
|
|
64
|
+
export function validateHeightState(state, stateCount) {
|
|
65
|
+
if (stateCount !== undefined)
|
|
66
|
+
validateStateCount(stateCount);
|
|
67
|
+
if (!Number.isSafeInteger(state)) {
|
|
68
|
+
throw new RangeError(`Height state must be a finite safe integer, got ${String(state)}`);
|
|
69
|
+
}
|
|
70
|
+
const maximum = stateCount === 2 ? 1 : 3;
|
|
71
|
+
if (state < 0 || state > maximum) {
|
|
72
|
+
throw new RangeError(`Height state must be between 0 and ${maximum} for ${stateCount ?? 4} states, got ${state}`);
|
|
73
|
+
}
|
|
74
|
+
return state;
|
|
75
|
+
}
|
|
76
|
+
function copyProfile(profile) {
|
|
77
|
+
return { top: profile.top, bottom: profile.bottom };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Return the normalized top and bottom edges for one state.
|
|
81
|
+
*
|
|
82
|
+
* State numbering is intentionally generic: 0 is tracker, 1 is ascender,
|
|
83
|
+
* 2 is descender and 3 is full for the four-state alphabet. The two-state
|
|
84
|
+
* alphabet exposes tracker (0) and full (1), so it is a strict subset of the
|
|
85
|
+
* four-state geometry and does not prescribe a postal format's semantics.
|
|
86
|
+
*
|
|
87
|
+
* @param {number} state
|
|
88
|
+
* @param {2|4} stateCount
|
|
89
|
+
* @returns {HeightCodedBar}
|
|
90
|
+
*/
|
|
91
|
+
export function barHeightProfile(state, stateCount) {
|
|
92
|
+
const count = validateStateCount(stateCount);
|
|
93
|
+
const checked = validateHeightState(state, count);
|
|
94
|
+
if (count === 2) {
|
|
95
|
+
return copyProfile(checked === 0 ? TRACKER_PROFILE : FULL_PROFILE);
|
|
96
|
+
}
|
|
97
|
+
switch (checked) {
|
|
98
|
+
case 0: return copyProfile(TRACKER_PROFILE);
|
|
99
|
+
case 1: return copyProfile(ASCENDER_PROFILE);
|
|
100
|
+
case 2: return copyProfile(DESCENDER_PROFILE);
|
|
101
|
+
default: return copyProfile(FULL_PROFILE);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Convert a state sequence to normalized top/bottom bar profiles.
|
|
106
|
+
*
|
|
107
|
+
* The returned array and every profile in it are newly allocated, so callers
|
|
108
|
+
* can safely adjust rendering coordinates without changing shared constants.
|
|
109
|
+
*
|
|
110
|
+
* @param {ArrayLike<number>} states
|
|
111
|
+
* @param {2|4} stateCount
|
|
112
|
+
* @returns {HeightCodedBar[]}
|
|
113
|
+
*/
|
|
114
|
+
export function encodeHeightProfile(states, stateCount) {
|
|
115
|
+
const count = validateStateCount(stateCount);
|
|
116
|
+
const length = validateProfileLength(states, 'Height states');
|
|
117
|
+
const profile = new Array(length);
|
|
118
|
+
for (let index = 0; index < length; index++) {
|
|
119
|
+
profile[index] = barHeightProfile(states[index], count);
|
|
120
|
+
}
|
|
121
|
+
return profile;
|
|
122
|
+
}
|
|
123
|
+
function decodeBar(profile, stateCount, index) {
|
|
124
|
+
if (profile === null || typeof profile !== 'object' || Array.isArray(profile)) {
|
|
125
|
+
throw new TypeError(`Height profile at index ${index} must be an object`);
|
|
126
|
+
}
|
|
127
|
+
const { top, bottom } = profile;
|
|
128
|
+
if (typeof top !== 'number' || typeof bottom !== 'number'
|
|
129
|
+
|| !Number.isFinite(top) || !Number.isFinite(bottom)) {
|
|
130
|
+
throw new RangeError(`Height profile at index ${index} must contain finite edges`);
|
|
131
|
+
}
|
|
132
|
+
if (top < 0 || top > 1 || bottom < 0 || bottom > 1 || top >= bottom) {
|
|
133
|
+
throw new RangeError(`Height profile at index ${index} must satisfy 0 <= top < bottom <= 1`);
|
|
134
|
+
}
|
|
135
|
+
const maximum = stateCount === 2 ? 1 : 3;
|
|
136
|
+
for (let state = 0; state <= maximum; state++) {
|
|
137
|
+
const expected = barHeightProfile(state, stateCount);
|
|
138
|
+
if (top === expected.top && bottom === expected.bottom) {
|
|
139
|
+
return state;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
throw new RangeError(`Height profile at index ${index} does not match a canonical ${stateCount}-state profile`);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Convert normalized top/bottom bar profiles back to their states.
|
|
146
|
+
*
|
|
147
|
+
* Decoding is deliberately exact: a profile must match one of the canonical
|
|
148
|
+
* normalized pairs for the selected alphabet. Raster or measurement code can
|
|
149
|
+
* quantize its observations before calling this helper; accepting arbitrary
|
|
150
|
+
* in-range pairs here would turn malformed bars into valid states.
|
|
151
|
+
*
|
|
152
|
+
* @param {ArrayLike<HeightCodedBar>} profile
|
|
153
|
+
* @param {2|4} stateCount
|
|
154
|
+
* @returns {HeightState[]}
|
|
155
|
+
*/
|
|
156
|
+
export function decodeHeightProfile(profile, stateCount) {
|
|
157
|
+
const count = validateStateCount(stateCount);
|
|
158
|
+
const length = validateProfileLength(profile, 'Height profile');
|
|
159
|
+
const states = new Array(length);
|
|
160
|
+
for (let index = 0; index < length; index++) {
|
|
161
|
+
states[index] = decodeBar(profile[index], count, index);
|
|
162
|
+
}
|
|
163
|
+
return states;
|
|
164
|
+
}
|