@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,124 @@
|
|
|
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
|
+
/**
|
|
14
|
+
* Integer-scale Han Xin detector.
|
|
15
|
+
*
|
|
16
|
+
* Detection deliberately accepts one prominent, axis-aligned symbol from a
|
|
17
|
+
* binarized image. It does not guess a perspective quadrilateral or return a
|
|
18
|
+
* low-confidence payload: the strict module decoder remains the final gate.
|
|
19
|
+
*
|
|
20
|
+
* @module hanxin/detector
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
24
|
+
import { NotFoundError } from '../core/errors.js';
|
|
25
|
+
import { decodeHanXin, HanXinDecodeResult } from './decoder.js';
|
|
26
|
+
import { HanXinVersion, hanXinSize } from './tables.js';
|
|
27
|
+
|
|
28
|
+
export interface HanXinDetection {
|
|
29
|
+
corners: Array<{ x: number; y: number }>;
|
|
30
|
+
dimension: { width: number; height: number };
|
|
31
|
+
moduleSize: number;
|
|
32
|
+
matrix: BitMatrix;
|
|
33
|
+
result: HanXinDecodeResult;
|
|
34
|
+
}
|
|
35
|
+
interface Bounds { x: number; y: number; width: number; height: number; }
|
|
36
|
+
|
|
37
|
+
function boundsForPolarity(image: BitMatrix, inverted: boolean): Bounds | null {
|
|
38
|
+
let left = image.width;
|
|
39
|
+
let top = image.height;
|
|
40
|
+
let right = -1;
|
|
41
|
+
let bottom = -1;
|
|
42
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
43
|
+
const dark = image.get(x, y) !== inverted;
|
|
44
|
+
if (!dark) continue;
|
|
45
|
+
if (x < left) left = x;
|
|
46
|
+
if (x > right) right = x;
|
|
47
|
+
if (y < top) top = y;
|
|
48
|
+
if (y > bottom) bottom = y;
|
|
49
|
+
}
|
|
50
|
+
return right < left || bottom < top ? null : {
|
|
51
|
+
x: left,
|
|
52
|
+
y: top,
|
|
53
|
+
width: right - left + 1,
|
|
54
|
+
height: bottom - top + 1,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sampleBounds(source: BitMatrix, bounds: Bounds, version: HanXinVersion, inverted: boolean): BitMatrix {
|
|
59
|
+
const size = hanXinSize(version);
|
|
60
|
+
const matrix = new BitMatrix(size, size);
|
|
61
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
62
|
+
const px = Math.min(source.width - 1, Math.max(0,
|
|
63
|
+
Math.floor(bounds.x + ((x + 0.5) * bounds.width) / size)));
|
|
64
|
+
const py = Math.min(source.height - 1, Math.max(0,
|
|
65
|
+
Math.floor(bounds.y + ((y + 0.5) * bounds.height) / size)));
|
|
66
|
+
if (source.get(px, py) !== inverted) matrix.set(x, y);
|
|
67
|
+
}
|
|
68
|
+
return matrix;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Detect one strict, integer-scale Han Xin symbol in a binarized image. */
|
|
72
|
+
export function detectHanXin(binaryImage: BitMatrix): HanXinDetection | null {
|
|
73
|
+
if (!binaryImage || !Number.isInteger(binaryImage.width) || !Number.isInteger(binaryImage.height) ||
|
|
74
|
+
binaryImage.width < 1 || binaryImage.height < 1) {
|
|
75
|
+
throw new NotFoundError('detectHanXin: no image supplied');
|
|
76
|
+
}
|
|
77
|
+
const candidates: HanXinDetection[] = [];
|
|
78
|
+
for (const inverted of [false, true]) {
|
|
79
|
+
const bounds = boundsForPolarity(binaryImage, inverted);
|
|
80
|
+
if (!bounds) continue;
|
|
81
|
+
for (const version of [1, 2, 3] as HanXinVersion[]) {
|
|
82
|
+
const size = hanXinSize(version);
|
|
83
|
+
if (bounds.width !== bounds.height || bounds.width % size !== 0) continue;
|
|
84
|
+
const moduleSize = bounds.width / size;
|
|
85
|
+
if (!Number.isInteger(moduleSize) || moduleSize < 1) continue;
|
|
86
|
+
const matrix = sampleBounds(binaryImage, bounds, version, inverted);
|
|
87
|
+
let result: HanXinDecodeResult;
|
|
88
|
+
try {
|
|
89
|
+
result = decodeHanXin(matrix, { rotation: 'auto', inverted: false });
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
candidates.push({
|
|
94
|
+
corners: [
|
|
95
|
+
{ x: bounds.x, y: bounds.y },
|
|
96
|
+
{ x: bounds.x + bounds.width, y: bounds.y },
|
|
97
|
+
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
|
98
|
+
{ x: bounds.x, y: bounds.y + bounds.height },
|
|
99
|
+
],
|
|
100
|
+
dimension: { width: size, height: size },
|
|
101
|
+
moduleSize,
|
|
102
|
+
matrix,
|
|
103
|
+
result: { ...result, inverted: result.inverted || inverted },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
candidates.sort((left, right) => right.moduleSize - left.moduleSize);
|
|
108
|
+
return candidates[0] ?? null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Detect and decode one verified Han Xin symbol, or return null. */
|
|
112
|
+
export function detectAndDecodeHanXin(binaryImage: BitMatrix): HanXinDecodeResult & {
|
|
113
|
+
corners: Array<{ x: number; y: number }>;
|
|
114
|
+
moduleSize: number;
|
|
115
|
+
} | null {
|
|
116
|
+
let detection: HanXinDetection | null;
|
|
117
|
+
try { detection = detectHanXin(binaryImage); } catch { return null; }
|
|
118
|
+
if (!detection) return null;
|
|
119
|
+
return {
|
|
120
|
+
...detection.result,
|
|
121
|
+
corners: detection.corners,
|
|
122
|
+
moduleSize: detection.moduleSize,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
* Dependency-free Han Xin Code encoder for the alignment-free versions 1-3.
|
|
14
|
+
*
|
|
15
|
+
* The implementation follows the public ISO/IEC 20830 structure: four corner
|
|
16
|
+
* position patterns, protected structural information, GF(256) Reed--Solomon
|
|
17
|
+
* blocks, 13-column picket-fence ordering and one of the four data masks.
|
|
18
|
+
* Numeric, Text and Byte modes are intentionally explicit so callers never
|
|
19
|
+
* get a silent lossy conversion.
|
|
20
|
+
*
|
|
21
|
+
* @module hanxin/encoder
|
|
22
|
+
*/
|
|
23
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
24
|
+
import { HanXinEccLevel, HanXinVersion } from './tables.js';
|
|
25
|
+
export type HanXinEncodeMode = 'auto' | 'numeric' | 'text' | 'byte';
|
|
26
|
+
export interface HanXinEncodeOptions {
|
|
27
|
+
/** Force one of the three implemented dimensions. */
|
|
28
|
+
version?: HanXinVersion | `V${HanXinVersion}` | number;
|
|
29
|
+
/** L1 is the least redundant level; L4 is the strongest. */
|
|
30
|
+
ecc?: HanXinEccLevel | 1 | 2 | 3 | 4;
|
|
31
|
+
/** Select the payload interpretation. Defaults to auto. */
|
|
32
|
+
mode?: HanXinEncodeMode;
|
|
33
|
+
/** Force a mask 0-3. By default the lowest local penalty is chosen. */
|
|
34
|
+
mask?: 0 | 1 | 2 | 3 | number;
|
|
35
|
+
}
|
|
36
|
+
/** Encode a string or byte array as a Han Xin Code module matrix. */
|
|
37
|
+
export declare function encodeHanXin(value: string | Uint8Array | number[], options?: HanXinEncodeOptions): BitMatrix;
|
|
38
|
+
/** Encode explicitly in byte mode; useful when the input contains arbitrary data. */
|
|
39
|
+
export declare function encodeHanXinBytes(bytes: Uint8Array | number[], options?: Omit<HanXinEncodeOptions, 'mode'>): BitMatrix;
|
|
40
|
+
/** Expose the implemented geometric side length for callers building rasters. */
|
|
41
|
+
export declare function hanXinDimension(version: HanXinVersion | number): number;
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
/**
|
|
14
|
+
* Dependency-free Han Xin Code encoder for the alignment-free versions 1-3.
|
|
15
|
+
*
|
|
16
|
+
* The implementation follows the public ISO/IEC 20830 structure: four corner
|
|
17
|
+
* position patterns, protected structural information, GF(256) Reed--Solomon
|
|
18
|
+
* blocks, 13-column picket-fence ordering and one of the four data masks.
|
|
19
|
+
* Numeric, Text and Byte modes are intentionally explicit so callers never
|
|
20
|
+
* get a silent lossy conversion.
|
|
21
|
+
*
|
|
22
|
+
* @module hanxin/encoder
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
26
|
+
import { BitWriter } from '../core/bit-buffer.js';
|
|
27
|
+
import { EncodeError } from '../core/errors.js';
|
|
28
|
+
import { rsEncode } from '../core/reed-solomon.js';
|
|
29
|
+
import {
|
|
30
|
+
GF256_HANXIN,
|
|
31
|
+
HANXIN_VERSIONS,
|
|
32
|
+
HanXinEccLevel,
|
|
33
|
+
HanXinVersion,
|
|
34
|
+
createHanXinFunctionGrid,
|
|
35
|
+
hanXinDataCoordinates,
|
|
36
|
+
hanXinDataCodewords,
|
|
37
|
+
hanXinEcLayout,
|
|
38
|
+
hanXinMaskFlip,
|
|
39
|
+
hanXinSize,
|
|
40
|
+
normalizeHanXinEcc,
|
|
41
|
+
normalizeHanXinVersion,
|
|
42
|
+
placeHanXinFunctionInfo,
|
|
43
|
+
} from './tables.js';
|
|
44
|
+
|
|
45
|
+
export type HanXinEncodeMode = 'auto' | 'numeric' | 'text' | 'byte';
|
|
46
|
+
|
|
47
|
+
export interface HanXinEncodeOptions {
|
|
48
|
+
/** Force one of the three implemented dimensions. */
|
|
49
|
+
version?: HanXinVersion | `V${HanXinVersion}` | number;
|
|
50
|
+
/** L1 is the least redundant level; L4 is the strongest. */
|
|
51
|
+
ecc?: HanXinEccLevel | 1 | 2 | 3 | 4;
|
|
52
|
+
/** Select the payload interpretation. Defaults to auto. */
|
|
53
|
+
mode?: HanXinEncodeMode;
|
|
54
|
+
/** Force a mask 0-3. By default the lowest local penalty is chosen. */
|
|
55
|
+
mask?: 0 | 1 | 2 | 3 | number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function inputBytes(value: string | Uint8Array | number[]): { bytes: Uint8Array; text: string | null } {
|
|
59
|
+
if (value instanceof Uint8Array) {
|
|
60
|
+
if (value.length === 0) throw new EncodeError('Han Xin: payload must not be empty');
|
|
61
|
+
return { bytes: new Uint8Array(value), text: null };
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
if (value.length === 0 || value.length > 8191 || value.some((item) => !Number.isInteger(item) || item < 0 || item > 255)) {
|
|
65
|
+
throw new EncodeError('Han Xin: byte arrays must contain at most 8191 values from 0 to 255');
|
|
66
|
+
}
|
|
67
|
+
return { bytes: Uint8Array.from(value), text: null };
|
|
68
|
+
}
|
|
69
|
+
if (typeof value !== 'string') throw new EncodeError('Han Xin: value must be text or bytes');
|
|
70
|
+
if (!value.length) throw new EncodeError('Han Xin: payload must not be empty');
|
|
71
|
+
return { bytes: new TextEncoder().encode(value), text: value };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function text1Value(codePoint: number): number | null {
|
|
75
|
+
if (codePoint >= 0x30 && codePoint <= 0x39) return codePoint - 0x30;
|
|
76
|
+
if (codePoint >= 0x41 && codePoint <= 0x5a) return codePoint - 0x41 + 10;
|
|
77
|
+
if (codePoint >= 0x61 && codePoint <= 0x7a) return codePoint - 0x61 + 36;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function text2Value(codePoint: number): number | null {
|
|
82
|
+
if (codePoint >= 0 && codePoint <= 0x1b) return codePoint;
|
|
83
|
+
if (codePoint >= 0x20 && codePoint <= 0x2f) return codePoint - 0x20 + 28;
|
|
84
|
+
if (codePoint >= 0x3a && codePoint <= 0x40) return codePoint - 0x3a + 44;
|
|
85
|
+
if (codePoint >= 0x5b && codePoint <= 0x60) return codePoint - 0x5b + 51;
|
|
86
|
+
if (codePoint >= 0x7b && codePoint <= 0x7f) return codePoint - 0x7b + 57;
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function textValue(codePoint: number, mode: 1 | 2): number | null {
|
|
91
|
+
return mode === 1 ? text1Value(codePoint) : text2Value(codePoint);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function selectMode(text: string | null, requested: HanXinEncodeMode | undefined): HanXinEncodeMode {
|
|
95
|
+
const mode = requested == null ? 'auto' : String(requested).toLowerCase() as HanXinEncodeMode;
|
|
96
|
+
if (!['auto', 'numeric', 'text', 'byte'].includes(mode)) {
|
|
97
|
+
throw new EncodeError(`Han Xin: unsupported payload mode ${String(requested)}`);
|
|
98
|
+
}
|
|
99
|
+
if (text == null) {
|
|
100
|
+
if (mode === 'numeric' || mode === 'text') throw new EncodeError('Han Xin: numeric and text modes require a string input');
|
|
101
|
+
return 'byte';
|
|
102
|
+
}
|
|
103
|
+
const points = Array.from(text);
|
|
104
|
+
const numeric = /^\d+$/.test(text);
|
|
105
|
+
const textCompatible = points.every((point) => {
|
|
106
|
+
const cp = point.codePointAt(0) as number;
|
|
107
|
+
return cp <= 0x7f && (text1Value(cp) !== null || text2Value(cp) !== null);
|
|
108
|
+
});
|
|
109
|
+
if (mode === 'numeric' && !numeric) throw new EncodeError('Han Xin: numeric mode accepts digits only');
|
|
110
|
+
if (mode === 'text' && !textCompatible) throw new EncodeError('Han Xin: text mode accepts supported ASCII characters only');
|
|
111
|
+
if (mode === 'auto') return numeric ? 'numeric' : textCompatible ? 'text' : 'byte';
|
|
112
|
+
return mode;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function encodeNumeric(text: string, writer: BitWriter): void {
|
|
116
|
+
let finalCount = 0;
|
|
117
|
+
for (let offset = 0; offset < text.length; offset += 3) {
|
|
118
|
+
finalCount = Math.min(3, text.length - offset);
|
|
119
|
+
writer.put(Number(text.slice(offset, offset + finalCount)), 10);
|
|
120
|
+
}
|
|
121
|
+
writer.put(1020 + finalCount, 10);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function encodeText(text: string, writer: BitWriter): void {
|
|
125
|
+
let submode: 1 | 2 = 1;
|
|
126
|
+
for (const point of Array.from(text)) {
|
|
127
|
+
const cp = point.codePointAt(0) as number;
|
|
128
|
+
let value = textValue(cp, submode);
|
|
129
|
+
if (value === null) {
|
|
130
|
+
submode = submode === 1 ? 2 : 1;
|
|
131
|
+
writer.put(62, 6);
|
|
132
|
+
value = textValue(cp, submode);
|
|
133
|
+
}
|
|
134
|
+
if (value === null) throw new EncodeError(`Han Xin: character U+${cp.toString(16).padStart(4, '0')} is not encodable in text mode`);
|
|
135
|
+
writer.put(value, 6);
|
|
136
|
+
}
|
|
137
|
+
writer.put(63, 6);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function encodeByte(bytes: Uint8Array, writer: BitWriter): void {
|
|
141
|
+
if (bytes.length > 8191) throw new EncodeError('Han Xin: byte mode supports at most 8191 bytes');
|
|
142
|
+
writer.put(3, 4);
|
|
143
|
+
writer.put(bytes.length, 13);
|
|
144
|
+
writer.putBytes(bytes);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function payloadBits(value: string | Uint8Array | number[], mode: HanXinEncodeMode): BitWriter {
|
|
148
|
+
const source = inputBytes(value);
|
|
149
|
+
const writer = new BitWriter();
|
|
150
|
+
if (mode === 'numeric') {
|
|
151
|
+
writer.put(1, 4);
|
|
152
|
+
encodeNumeric(source.text as string, writer);
|
|
153
|
+
} else if (mode === 'text') {
|
|
154
|
+
writer.put(2, 4);
|
|
155
|
+
encodeText(source.text as string, writer);
|
|
156
|
+
} else {
|
|
157
|
+
encodeByte(source.bytes, writer);
|
|
158
|
+
}
|
|
159
|
+
return writer;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function picketFence(codewords: number[]): number[] {
|
|
163
|
+
const output: number[] = [];
|
|
164
|
+
for (let column = 0; column < 13; column++) {
|
|
165
|
+
for (let i = column; i < codewords.length; i += 13) output.push(codewords[i]);
|
|
166
|
+
}
|
|
167
|
+
return output;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function placePayload(matrix: BitMatrix, version: HanXinVersion, codewords: number[], mask: number): void {
|
|
171
|
+
const coordinates = hanXinDataCoordinates(version);
|
|
172
|
+
const bits = picketFence(codewords);
|
|
173
|
+
const totalBits = bits.length * 8;
|
|
174
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
175
|
+
const [x, y] = coordinates[i];
|
|
176
|
+
// Compact versions have five remainder modules after the final complete
|
|
177
|
+
// codeword. They carry zero before masking and are checked by the reader.
|
|
178
|
+
let dark = i < totalBits && ((bits[i >>> 3] >>> (7 - (i & 7))) & 1) !== 0;
|
|
179
|
+
if (hanXinMaskFlip(mask, x, y)) dark = !dark;
|
|
180
|
+
matrix.setValue(x, y, dark);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function maskPenalty(matrix: BitMatrix, version: HanXinVersion, level: HanXinEccLevel, mask: number): number {
|
|
185
|
+
const candidate = matrix.clone();
|
|
186
|
+
for (const [x, y] of hanXinDataCoordinates(version)) {
|
|
187
|
+
if (hanXinMaskFlip(mask, x, y)) candidate.flip(x, y);
|
|
188
|
+
}
|
|
189
|
+
placeHanXinFunctionInfo(candidate, version, level, mask);
|
|
190
|
+
let penalty = 0;
|
|
191
|
+
const runPenalty = (values: boolean[]): void => {
|
|
192
|
+
let current = values[0];
|
|
193
|
+
let run = 1;
|
|
194
|
+
for (let i = 1; i <= values.length; i++) {
|
|
195
|
+
if (i < values.length && values[i] === current) run++;
|
|
196
|
+
else {
|
|
197
|
+
if (run >= 3) penalty += run * 4;
|
|
198
|
+
current = values[i];
|
|
199
|
+
run = 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
for (let y = 0; y < candidate.height; y++) {
|
|
204
|
+
const row: boolean[] = [];
|
|
205
|
+
for (let x = 0; x < candidate.width; x++) row.push(candidate.get(x, y));
|
|
206
|
+
runPenalty(row);
|
|
207
|
+
}
|
|
208
|
+
for (let x = 0; x < candidate.width; x++) {
|
|
209
|
+
const column: boolean[] = [];
|
|
210
|
+
for (let y = 0; y < candidate.height; y++) column.push(candidate.get(x, y));
|
|
211
|
+
runPenalty(column);
|
|
212
|
+
}
|
|
213
|
+
return penalty;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function chooseMask(base: BitMatrix, version: HanXinVersion, level: HanXinEccLevel, requested: number | undefined): number {
|
|
217
|
+
if (requested !== undefined) {
|
|
218
|
+
if (!Number.isInteger(requested) || requested < 0 || requested > 3) throw new EncodeError('Han Xin: mask must be an integer from 0 to 3');
|
|
219
|
+
return requested;
|
|
220
|
+
}
|
|
221
|
+
let best = 0;
|
|
222
|
+
let score = Number.POSITIVE_INFINITY;
|
|
223
|
+
for (let mask = 0; mask < 4; mask++) {
|
|
224
|
+
const candidate = base.clone();
|
|
225
|
+
// The score is intentionally simple and stable. Every candidate is still
|
|
226
|
+
// validated by the strict reader, so no mask choice can affect integrity.
|
|
227
|
+
const value = maskPenalty(candidate, version, level, mask);
|
|
228
|
+
if (value < score) { score = value; best = mask; }
|
|
229
|
+
}
|
|
230
|
+
return best;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function encodeVersion(
|
|
234
|
+
writer: BitWriter,
|
|
235
|
+
version: HanXinVersion,
|
|
236
|
+
level: HanXinEccLevel,
|
|
237
|
+
requestedMask: number | undefined,
|
|
238
|
+
): BitMatrix {
|
|
239
|
+
const dataCodewords = hanXinDataCodewords(version, level);
|
|
240
|
+
const layout = hanXinEcLayout(version, level);
|
|
241
|
+
const capacity = dataCodewords * 8;
|
|
242
|
+
if (writer.length > capacity) throw new EncodeError(`Han Xin: payload needs ${writer.length} bits but ${capacity} are available`);
|
|
243
|
+
const data = new Uint8Array(dataCodewords);
|
|
244
|
+
const encoded = writer.toBytes();
|
|
245
|
+
data.set(encoded.subarray(0, data.length));
|
|
246
|
+
const parity = rsEncode(Array.from(data), layout.eccCodewords, GF256_HANXIN, 1);
|
|
247
|
+
const full = Array.from(data).concat(parity);
|
|
248
|
+
const { matrix } = createHanXinFunctionGrid(version);
|
|
249
|
+
placePayload(matrix, version, full, 0);
|
|
250
|
+
const mask = chooseMask(matrix, version, level, requestedMask);
|
|
251
|
+
placePayload(matrix, version, full, mask);
|
|
252
|
+
placeHanXinFunctionInfo(matrix, version, level, mask);
|
|
253
|
+
return matrix;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Encode a string or byte array as a Han Xin Code module matrix. */
|
|
257
|
+
export function encodeHanXin(
|
|
258
|
+
value: string | Uint8Array | number[],
|
|
259
|
+
options: HanXinEncodeOptions = {},
|
|
260
|
+
): BitMatrix {
|
|
261
|
+
const source = inputBytes(value);
|
|
262
|
+
const mode = selectMode(source.text, options.mode);
|
|
263
|
+
const writer = payloadBits(value, mode);
|
|
264
|
+
const level = normalizeHanXinEcc(options.ecc);
|
|
265
|
+
const wantedVersion = options.version == null ? null : normalizeHanXinVersion(options.version);
|
|
266
|
+
const requestedMask = options.mask == null ? undefined : Number(options.mask);
|
|
267
|
+
if (requestedMask !== undefined &&
|
|
268
|
+
(!Number.isInteger(requestedMask) || requestedMask < 0 || requestedMask > 3)) {
|
|
269
|
+
throw new EncodeError('Han Xin: mask must be an integer from 0 to 3');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const version of HANXIN_VERSIONS) {
|
|
273
|
+
if (wantedVersion !== null && version !== wantedVersion) continue;
|
|
274
|
+
try { return encodeVersion(writer, version, level, requestedMask); } catch (error) {
|
|
275
|
+
if (!(error instanceof EncodeError)) throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const target = wantedVersion == null ? 'versions 1-3' : `version ${wantedVersion}`;
|
|
279
|
+
throw new EncodeError(`Han Xin: payload does not fit ${target} at error correction ${level}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Encode explicitly in byte mode; useful when the input contains arbitrary data. */
|
|
283
|
+
export function encodeHanXinBytes(bytes: Uint8Array | number[], options: Omit<HanXinEncodeOptions, 'mode'> = {}): BitMatrix {
|
|
284
|
+
return encodeHanXin(bytes, { ...options, mode: 'byte' });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Expose the implemented geometric side length for callers building rasters. */
|
|
288
|
+
export function hanXinDimension(version: HanXinVersion | number): number {
|
|
289
|
+
return hanXinSize(Number(version));
|
|
290
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
/** Public Han Xin Code API. @module hanxin */
|
|
13
|
+
export * from './tables.js';
|
|
14
|
+
export * from './encoder.js';
|
|
15
|
+
export * from './decoder.js';
|
|
16
|
+
export * from './detector.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
/** Public Han Xin Code API. @module hanxin */
|
|
14
|
+
|
|
15
|
+
export * from './tables.js';
|
|
16
|
+
export * from './encoder.js';
|
|
17
|
+
export * from './decoder.js';
|
|
18
|
+
export * from './detector.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { GaloisField } from '../core/galois-field.js';
|
|
24
|
+
export declare const HANXIN_MIN_VERSION = 1;
|
|
25
|
+
export declare const HANXIN_MAX_VERSION = 3;
|
|
26
|
+
export declare const HANXIN_VERSIONS: readonly [1, 2, 3];
|
|
27
|
+
export type HanXinVersion = typeof HANXIN_VERSIONS[number];
|
|
28
|
+
export declare const HANXIN_ECC_LEVELS: readonly ['L1', 'L2', 'L3', 'L4'];
|
|
29
|
+
export type HanXinEccLevel = typeof HANXIN_ECC_LEVELS[number];
|
|
30
|
+
/** Han Xin's data field is GF(2^8) with x^8+x^6+x^5+x+1. */
|
|
31
|
+
export declare const GF256_HANXIN: GaloisField;
|
|
32
|
+
/** Total codewords, including error correction, for versions 1 through 3. */
|
|
33
|
+
export declare const HANXIN_TOTAL_CODEWORDS: readonly [25, 37, 50];
|
|
34
|
+
/** Data modules, including the five zero remainder modules in each compact version. */
|
|
35
|
+
export declare const HANXIN_DATA_MODULES: readonly [205, 301, 405];
|
|
36
|
+
/** Unused tail modules after the complete codeword stream. */
|
|
37
|
+
export declare const HANXIN_REMAINDER_BITS: readonly [5, 5, 5];
|
|
38
|
+
/** @returns {number} Side length in modules. */
|
|
39
|
+
export declare function hanXinSize(version: number): number;
|
|
40
|
+
/** @returns {HanXinVersion} */
|
|
41
|
+
export declare function normalizeHanXinVersion(value: unknown): HanXinVersion;
|
|
42
|
+
/** @returns {HanXinEccLevel} */
|
|
43
|
+
export declare function normalizeHanXinEcc(value: unknown): HanXinEccLevel;
|
|
44
|
+
/** @returns {number} Index of an error-correction level. */
|
|
45
|
+
export declare function hanXinEccIndex(level: HanXinEccLevel): number;
|
|
46
|
+
/** @returns {{blockCount:number,dataCodewords:number,eccCodewords:number}} */
|
|
47
|
+
export declare function hanXinEcLayout(version: HanXinVersion, level: HanXinEccLevel): {
|
|
48
|
+
blockCount: number;
|
|
49
|
+
dataCodewords: number;
|
|
50
|
+
eccCodewords: number;
|
|
51
|
+
};
|
|
52
|
+
/** @returns {number} Data codewords for a version and EC level. */
|
|
53
|
+
export declare function hanXinDataCodewords(version: HanXinVersion, level: HanXinEccLevel): number;
|
|
54
|
+
/** Four orientation-specific 7x7 finder patterns, packed MSB first. */
|
|
55
|
+
export declare const HANXIN_FINDER_TOP_LEFT: readonly [127, 64, 95, 80, 87, 87, 87];
|
|
56
|
+
export declare const HANXIN_FINDER_SIDE: readonly [127, 1, 125, 5, 117, 117, 117];
|
|
57
|
+
export declare const HANXIN_FINDER_BOTTOM_RIGHT: readonly [117, 117, 117, 5, 125, 1, 127];
|
|
58
|
+
export type HanXinCoordinate = readonly [number, number];
|
|
59
|
+
/** Return the function modules and their fixed darkness for a version. */
|
|
60
|
+
export declare function createHanXinFunctionGrid(version: HanXinVersion): {
|
|
61
|
+
matrix: BitMatrix;
|
|
62
|
+
reserved: Uint8Array;
|
|
63
|
+
};
|
|
64
|
+
/** Return payload positions in the normative row-major order. */
|
|
65
|
+
export declare function hanXinDataCoordinates(version: HanXinVersion): HanXinCoordinate[];
|
|
66
|
+
/** Whether a data module is inverted by one of Han Xin's four masks. */
|
|
67
|
+
export declare function hanXinMaskFlip(mask: number, x: number, y: number): boolean;
|
|
68
|
+
/** Build the 34 structural bits protected by the GF(16) short RS block. */
|
|
69
|
+
export declare function hanXinFunctionInfoBits(version: HanXinVersion, level: HanXinEccLevel, mask: number): boolean[];
|
|
70
|
+
/** Place both redundant structural-information copies in the fixed strips. */
|
|
71
|
+
export declare function placeHanXinFunctionInfo(matrix: BitMatrix, version: HanXinVersion, level: HanXinEccLevel, mask: number): void;
|
|
72
|
+
/** Decode a structural information copy, correcting up to two nibble errors. */
|
|
73
|
+
export declare function decodeHanXinFunctionInfo(matrix: BitMatrix, version: HanXinVersion): {
|
|
74
|
+
version: HanXinVersion;
|
|
75
|
+
level: HanXinEccLevel;
|
|
76
|
+
mask: number;
|
|
77
|
+
corrections: number;
|
|
78
|
+
};
|
|
79
|
+
/** Verify all fixed modules and count the payload cells. */
|
|
80
|
+
export declare function validateHanXinTables(): string[];
|