@sythos/js_barcode_universal 1.5.13 → 1.5.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/LICENSE +22 -4
  2. package/NOTICE.md +10 -1
  3. package/README.md +416 -22
  4. package/bundle/sythos-barcode.esm.js +22176 -11991
  5. package/bundle/sythos-barcode.js +22059 -11991
  6. package/licenses/README.md +12 -1
  7. package/licenses/codablockf.license +51 -0
  8. package/licenses/code16k.license +52 -0
  9. package/licenses/code25.license +58 -0
  10. package/licenses/code32.license +51 -0
  11. package/licenses/dotcode.license +66 -0
  12. package/licenses/gs1-composite.license +106 -0
  13. package/licenses/gs1-databar.license +21 -14
  14. package/licenses/hanxin.license +95 -0
  15. package/licenses/maxicode.license +62 -0
  16. package/licenses/postal.license +61 -0
  17. package/licenses/pzn.license +53 -0
  18. package/licenses/telepen.license +73 -0
  19. package/llms.txt +62 -6
  20. package/package.json +47 -3
  21. package/src/index.d.ts +111 -6
  22. package/src/index.js +362 -13
  23. package/src/js/codablockf/decoder.js +198 -0
  24. package/src/js/codablockf/encoder.js +97 -0
  25. package/src/js/codablockf/index.js +3 -0
  26. package/src/js/code16k/decoder.js +303 -0
  27. package/src/js/code16k/detector.js +187 -0
  28. package/src/js/code16k/encoder.js +153 -0
  29. package/src/js/code16k/index.js +14 -0
  30. package/src/js/code16k/tables.js +152 -0
  31. package/src/js/composite/index.js +570 -0
  32. package/src/js/core/detection-contract.js +185 -0
  33. package/src/js/core/symbol-layout.js +155 -0
  34. package/src/js/databar/expanded.js +968 -0
  35. package/src/js/databar/index.js +4 -0
  36. package/src/js/databar/layout.js +192 -0
  37. package/src/js/databar/limited.js +533 -0
  38. package/src/js/databar/stacked-omnidirectional.js +548 -0
  39. package/src/js/databar/stacked.js +528 -0
  40. package/src/js/dotcode/decoder.js +548 -0
  41. package/src/js/dotcode/detector.js +251 -0
  42. package/src/js/dotcode/encoder.js +453 -0
  43. package/src/js/dotcode/index.js +34 -0
  44. package/src/js/dotcode/tables.js +168 -0
  45. package/src/js/hanxin/decoder.js +299 -0
  46. package/src/js/hanxin/detector.js +122 -0
  47. package/src/js/hanxin/encoder.js +259 -0
  48. package/src/js/hanxin/index.js +16 -0
  49. package/src/js/hanxin/tables.js +316 -0
  50. package/src/js/image/height-coded.js +164 -0
  51. package/src/js/maxicode/decoder.js +275 -0
  52. package/src/js/maxicode/detector.js +118 -0
  53. package/src/js/maxicode/encoder.js +438 -0
  54. package/src/js/maxicode/index.js +34 -0
  55. package/src/js/maxicode/tables.js +132 -0
  56. package/src/js/oned/code25.js +148 -0
  57. package/src/js/oned/index.js +21 -3
  58. package/src/js/oned/postal.js +889 -0
  59. package/src/js/oned/reader.js +272 -7
  60. package/src/js/oned/telepen.js +361 -0
  61. package/src/js/oned/writers.js +186 -12
  62. package/src/js/stacked128/common.js +208 -0
  63. package/src/ts/codablockf/decoder.ts +212 -0
  64. package/src/ts/codablockf/encoder.ts +128 -0
  65. package/src/ts/codablockf/index.d.ts +31 -0
  66. package/src/ts/codablockf/index.ts +6 -0
  67. package/src/ts/code16k/decoder.d.ts +25 -0
  68. package/src/ts/code16k/decoder.ts +313 -0
  69. package/src/ts/code16k/detector.d.ts +21 -0
  70. package/src/ts/code16k/detector.ts +203 -0
  71. package/src/ts/code16k/encoder.d.ts +26 -0
  72. package/src/ts/code16k/encoder.ts +198 -0
  73. package/src/ts/code16k/index.d.ts +10 -0
  74. package/src/ts/code16k/index.ts +47 -0
  75. package/src/ts/code16k/tables.d.ts +54 -0
  76. package/src/ts/code16k/tables.ts +195 -0
  77. package/src/ts/composite/index.d.ts +74 -0
  78. package/src/ts/composite/index.ts +547 -0
  79. package/src/ts/core/detection-contract.d.ts +119 -0
  80. package/src/ts/core/detection-contract.ts +267 -0
  81. package/src/ts/core/symbol-layout.d.ts +108 -0
  82. package/src/ts/core/symbol-layout.ts +236 -0
  83. package/src/ts/databar/expanded.d.ts +40 -0
  84. package/src/ts/databar/expanded.ts +948 -0
  85. package/src/ts/databar/index.d.ts +48 -0
  86. package/src/ts/databar/index.ts +48 -0
  87. package/src/ts/databar/layout.d.ts +122 -0
  88. package/src/ts/databar/layout.ts +275 -0
  89. package/src/ts/databar/limited.d.ts +85 -0
  90. package/src/ts/databar/limited.ts +553 -0
  91. package/src/ts/databar/stacked-omnidirectional.d.ts +128 -0
  92. package/src/ts/databar/stacked-omnidirectional.ts +559 -0
  93. package/src/ts/databar/stacked.d.ts +96 -0
  94. package/src/ts/databar/stacked.ts +549 -0
  95. package/src/ts/dotcode/decoder.d.ts +58 -0
  96. package/src/ts/dotcode/decoder.ts +467 -0
  97. package/src/ts/dotcode/detector.d.ts +63 -0
  98. package/src/ts/dotcode/detector.ts +263 -0
  99. package/src/ts/dotcode/encoder.d.ts +69 -0
  100. package/src/ts/dotcode/encoder.ts +427 -0
  101. package/src/ts/dotcode/index.d.ts +37 -0
  102. package/src/ts/dotcode/index.ts +72 -0
  103. package/src/ts/dotcode/tables.d.ts +74 -0
  104. package/src/ts/dotcode/tables.ts +174 -0
  105. package/src/ts/hanxin/decoder.d.ts +48 -0
  106. package/src/ts/hanxin/decoder.ts +314 -0
  107. package/src/ts/hanxin/detector.d.ts +45 -0
  108. package/src/ts/hanxin/detector.ts +124 -0
  109. package/src/ts/hanxin/encoder.d.ts +41 -0
  110. package/src/ts/hanxin/encoder.ts +290 -0
  111. package/src/ts/hanxin/index.d.ts +16 -0
  112. package/src/ts/hanxin/index.ts +18 -0
  113. package/src/ts/hanxin/tables.d.ts +80 -0
  114. package/src/ts/hanxin/tables.ts +348 -0
  115. package/src/ts/image/height-coded.d.ts +101 -0
  116. package/src/ts/image/height-coded.ts +227 -0
  117. package/src/ts/index.d.ts +46 -2
  118. package/src/ts/index.ts +380 -13
  119. package/src/ts/maxicode/decoder.ts +265 -0
  120. package/src/ts/maxicode/detector.ts +109 -0
  121. package/src/ts/maxicode/encoder.ts +414 -0
  122. package/src/ts/maxicode/index.d.ts +96 -0
  123. package/src/ts/maxicode/index.ts +44 -0
  124. package/src/ts/maxicode/tables.ts +136 -0
  125. package/src/ts/oned/code25.d.ts +42 -0
  126. package/src/ts/oned/code25.ts +170 -0
  127. package/src/ts/oned/index.d.ts +7 -2
  128. package/src/ts/oned/index.ts +45 -3
  129. package/src/ts/oned/postal.d.ts +44 -0
  130. package/src/ts/oned/postal.ts +819 -0
  131. package/src/ts/oned/reader.d.ts +35 -0
  132. package/src/ts/oned/reader.ts +261 -7
  133. package/src/ts/oned/telepen.d.ts +65 -0
  134. package/src/ts/oned/telepen.ts +368 -0
  135. package/src/ts/oned/writers.d.ts +32 -0
  136. package/src/ts/oned/writers.ts +184 -13
  137. package/src/ts/stacked128/common.ts +225 -0
@@ -0,0 +1,267 @@
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-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
+ * SPDX-License-Identifier: MIT
28
+ *
29
+ * Original work. No code from any other barcode implementation.
30
+ */
31
+
32
+ /**
33
+ * Shared, format-neutral detector contracts.
34
+ *
35
+ * The module deliberately has no dependency on a particular matrix
36
+ * implementation. A detector can supply its own matrix type through the
37
+ * `TMatrix` generic while keeping the common geometry and candidate metadata.
38
+ *
39
+ * @module core/detection-contract
40
+ */
41
+
42
+ /** A point in the source image, expressed in image coordinates. */
43
+ export type Point = {
44
+ x: number;
45
+ y: number;
46
+ };
47
+
48
+ /** The canonical in-plane orientations supported by shared detectors. */
49
+ export type Rotation = 0 | 45 | 90 | 135 | 180 | 225 | 270 | 315;
50
+
51
+ /**
52
+ * Geometry recovered by a detector.
53
+ *
54
+ * `corners` are ordered top-left, top-right, bottom-right, bottom-left in the
55
+ * source image. `matrix` is the detector's rectified or otherwise decoder-ready
56
+ * representation, and is intentionally generic to keep this contract
57
+ * dependency-free.
58
+ */
59
+ export interface DetectionGeometry<TMatrix = unknown> {
60
+ corners: Point[];
61
+ moduleSize: number;
62
+ rotation: Rotation;
63
+ matrix: TMatrix;
64
+ confidence?: number;
65
+ }
66
+
67
+ /**
68
+ * Evidence collected while validating a candidate.
69
+ *
70
+ * Fields are optional because a 1D or 2D detector may not be able to provide
71
+ * every signal. When present, `rows` is a non-negative row count and
72
+ * `consistency` is a normalized value from zero to one.
73
+ */
74
+ export interface ValidationQuality {
75
+ quietZone?: boolean;
76
+ checksum?: boolean | null;
77
+ rows?: number | null;
78
+ consistency?: number | null;
79
+ [key: string]: unknown;
80
+ }
81
+
82
+ /** Optional decoded result and ranking metadata for a detection. */
83
+ export interface DetectionCandidateOptions<TResult = unknown> {
84
+ result?: TResult;
85
+ quality?: ValidationQuality;
86
+ score?: number;
87
+ }
88
+
89
+ /** A validated detector geometry with optional decode and quality metadata. */
90
+ export type DetectionCandidate<TResult = unknown, TMatrix = unknown> =
91
+ DetectionGeometry<TMatrix> & DetectionCandidateOptions<TResult>;
92
+
93
+ function hasOwn(value: object, property: string): boolean {
94
+ return Object.prototype.hasOwnProperty.call(value, property);
95
+ }
96
+
97
+ function isRecord(value: unknown): value is Record<string, unknown> {
98
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
99
+ }
100
+
101
+ function isFiniteNumber(value: unknown): value is number {
102
+ return typeof value === 'number' && Number.isFinite(value);
103
+ }
104
+
105
+ function cross(a: Point, b: Point, c: Point): number {
106
+ return (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
107
+ }
108
+
109
+ /**
110
+ * Normalize a detector rotation to one of the supported 45-degree turns.
111
+ *
112
+ * This function is intentionally strict: arbitrary angles are not silently
113
+ * snapped to a nearby orientation. The accepted domain is the integer range
114
+ * `0..359`, and only exact multiples of 45 degrees are canonical.
115
+ *
116
+ * @throws {TypeError} If `rotation` is not a number.
117
+ * @throws {RangeError} If `rotation` is outside the canonical domain.
118
+ */
119
+ export function normalizeRotation(rotation: number): Rotation {
120
+ if (typeof rotation !== 'number') {
121
+ throw new TypeError('Rotation must be a number');
122
+ }
123
+ if (!Number.isFinite(rotation) || !Number.isInteger(rotation)
124
+ || rotation < 0 || rotation >= 360 || rotation % 45 !== 0) {
125
+ throw new RangeError('Rotation must be an integer multiple of 45 degrees in 0..359');
126
+ }
127
+ return rotation as Rotation;
128
+ }
129
+
130
+ /**
131
+ * Return whether four finite points form an ordered, non-degenerate quad.
132
+ *
133
+ * The required order is top-left, top-right, bottom-right, bottom-left. Both
134
+ * clockwise and counter-clockwise winding are accepted; repeated points,
135
+ * zero-length edges, collinear turns, concave quads, and self-intersections
136
+ * are rejected.
137
+ */
138
+ export function isValidCorners(corners: unknown): corners is [Point, Point, Point, Point] {
139
+ if (!Array.isArray(corners) || corners.length !== 4) return false;
140
+
141
+ const points: Point[] = [];
142
+ for (const value of corners) {
143
+ if (!isRecord(value) || !isFiniteNumber(value.x) || !isFiniteNumber(value.y)) {
144
+ return false;
145
+ }
146
+ points.push({ x: value.x, y: value.y });
147
+ }
148
+
149
+ for (let first = 0; first < points.length; first++) {
150
+ for (let second = first + 1; second < points.length; second++) {
151
+ if (points[first].x === points[second].x && points[first].y === points[second].y) {
152
+ return false;
153
+ }
154
+ }
155
+ }
156
+
157
+ let winding = 0;
158
+ for (let index = 0; index < points.length; index++) {
159
+ const turn = cross(points[index], points[(index + 1) % points.length], points[(index + 2) % points.length]);
160
+ if (!Number.isFinite(turn) || turn === 0) return false;
161
+ const sign = Math.sign(turn);
162
+ if (winding === 0) winding = sign;
163
+ else if (sign !== winding) return false;
164
+ }
165
+
166
+ return true;
167
+ }
168
+
169
+ function isValidQuality(value: unknown): value is ValidationQuality {
170
+ if (!isRecord(value)) return false;
171
+
172
+ if (hasOwn(value, 'quietZone') && typeof value.quietZone !== 'boolean') return false;
173
+ if (hasOwn(value, 'checksum')
174
+ && value.checksum !== null && typeof value.checksum !== 'boolean') return false;
175
+
176
+ if (hasOwn(value, 'rows') && value.rows !== null
177
+ && (!isFiniteNumber(value.rows) || !Number.isInteger(value.rows) || value.rows < 0)) {
178
+ return false;
179
+ }
180
+
181
+ if (hasOwn(value, 'consistency') && value.consistency !== null
182
+ && (!isFiniteNumber(value.consistency) || value.consistency < 0 || value.consistency > 1)) {
183
+ return false;
184
+ }
185
+
186
+ return true;
187
+ }
188
+
189
+ function isValidConfidence(value: unknown): value is number | undefined {
190
+ return value === undefined
191
+ || (isFiniteNumber(value) && value >= 0 && value <= 1);
192
+ }
193
+
194
+ function isValidScore(value: unknown): value is number | undefined {
195
+ return value === undefined || isFiniteNumber(value);
196
+ }
197
+
198
+ /**
199
+ * Create a validated detector candidate.
200
+ *
201
+ * All optional decoded-result and ranking metadata must be supplied in the
202
+ * options object: `createDetectionCandidate(geometry, { result, quality, score })`.
203
+ * Keeping the decoded result under the explicit `result` key avoids confusing
204
+ * a result object with candidate metadata. The returned value owns a fresh
205
+ * corner array and canonical rotation, and the input objects are never
206
+ * mutated.
207
+ *
208
+ * @throws {TypeError} If the geometry, matrix, or options have an invalid type.
209
+ * @throws {RangeError} If a numeric geometry or metadata value is invalid.
210
+ */
211
+ export function createDetectionCandidate<TResult = unknown, TMatrix = unknown>(
212
+ geometry: DetectionGeometry<TMatrix>,
213
+ options?: DetectionCandidateOptions<TResult>,
214
+ ): DetectionCandidate<TResult, TMatrix> {
215
+ if (!isRecord(geometry)) {
216
+ throw new TypeError('Detection geometry must be an object');
217
+ }
218
+
219
+ const geometryValue = geometry as unknown as Record<string, unknown>;
220
+ const corners = geometryValue.corners;
221
+ if (!isValidCorners(corners)) {
222
+ throw new RangeError('Detection geometry must contain four non-degenerate corners');
223
+ }
224
+
225
+ const moduleSize = geometryValue.moduleSize;
226
+ if (!isFiniteNumber(moduleSize) || moduleSize <= 0) {
227
+ throw new RangeError('Detection geometry moduleSize must be a positive finite number');
228
+ }
229
+
230
+ const rotation = normalizeRotation(geometryValue.rotation as number);
231
+
232
+ if (!hasOwn(geometryValue, 'matrix') || geometryValue.matrix === null
233
+ || geometryValue.matrix === undefined) {
234
+ throw new TypeError('Detection geometry must contain a matrix');
235
+ }
236
+
237
+ if (!isValidConfidence(geometryValue.confidence)) {
238
+ throw new RangeError('Detection geometry confidence must be a finite number in 0..1');
239
+ }
240
+
241
+ const candidateOptions = options === undefined ? {} : options;
242
+ if (!isRecord(candidateOptions)) {
243
+ throw new TypeError('Detection candidate options must be an object');
244
+ }
245
+ const validatedOptions = candidateOptions as DetectionCandidateOptions<TResult>;
246
+
247
+ const quality = validatedOptions.quality;
248
+ if (quality !== undefined && !isValidQuality(quality)) {
249
+ throw new TypeError('Detection candidate quality must be an object');
250
+ }
251
+ if (!isValidScore(validatedOptions.score)) {
252
+ throw new RangeError('Detection candidate score must be a finite number');
253
+ }
254
+
255
+ const candidate = {
256
+ ...geometry,
257
+ corners: corners.map((point) => ({ x: point.x, y: point.y })),
258
+ moduleSize,
259
+ rotation,
260
+ } as DetectionCandidate<TResult, TMatrix>;
261
+
262
+ if (hasOwn(validatedOptions, 'result') && validatedOptions.result !== undefined) candidate.result = validatedOptions.result as TResult;
263
+ if (hasOwn(validatedOptions, 'quality') && quality !== undefined) candidate.quality = quality;
264
+ if (hasOwn(validatedOptions, 'score') && validatedOptions.score !== undefined) candidate.score = validatedOptions.score;
265
+
266
+ return candidate;
267
+ }
@@ -0,0 +1,108 @@
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-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
+ * SPDX-License-Identifier: MIT
28
+ *
29
+ * Original work. No code from any other barcode implementation.
30
+ */
31
+
32
+ /**
33
+ * Shared geometry metadata for matrix, dot, hexagonal, and stacked symbols.
34
+ *
35
+ * A layout is deliberately only a descriptor. It does not allocate a module
36
+ * matrix and does not prescribe how a format stores or renders its data.
37
+ *
38
+ * @module core/symbol-layout
39
+ */
40
+
41
+ /** The shape of one logical module. */
42
+ export type ModuleShape = 'square' | 'dot';
43
+
44
+ /** The lattice used to arrange logical modules. */
45
+ export type Lattice = 'square' | 'hex';
46
+
47
+ /** Explicit aliases for callers that prefer the longer type names. */
48
+ export type SymbolModuleShape = ModuleShape;
49
+ export type SymbolLattice = Lattice;
50
+
51
+ /**
52
+ * The immutable description of a symbol's module geometry.
53
+ *
54
+ * `width` and `height` describe one symbol panel in logical modules. Optional
55
+ * `rows` and `columns` describe a repeated panel arrangement, which lets a
56
+ * future stacked format share this descriptor without changing the matrix
57
+ * formats. They default to one panel when calculating a module count.
58
+ */
59
+ export interface SymbolLayoutDescriptor {
60
+ readonly moduleShape: ModuleShape;
61
+ readonly lattice: Lattice;
62
+ readonly width: number;
63
+ readonly height: number;
64
+ readonly quietZone: number;
65
+ readonly rows?: number;
66
+ readonly columns?: number;
67
+ }
68
+
69
+ /** The canonical layout type returned by {@link createSymbolLayout}. */
70
+ export type SymbolLayout = SymbolLayoutDescriptor;
71
+
72
+ /** Input accepted by {@link createSymbolLayout}. */
73
+ export type SymbolLayoutOptions = SymbolLayoutDescriptor;
74
+
75
+ /** Alias for code that uses input-oriented naming. */
76
+ export type SymbolLayoutInput = SymbolLayoutOptions;
77
+
78
+ /**
79
+ * Return whether an unknown value is a valid symbol layout descriptor.
80
+ *
81
+ * Validation is side-effect free and performs no matrix allocation. A valid
82
+ * descriptor may be mutable when supplied by a caller; use
83
+ * {@link createSymbolLayout} when an immutable value is required.
84
+ */
85
+ export declare function validateSymbolLayout(value: unknown): value is SymbolLayout;
86
+
87
+ /**
88
+ * Create a validated, shallowly immutable symbol layout descriptor.
89
+ *
90
+ * Only the documented fields are copied. Unknown input properties are ignored,
91
+ * so a future caller can pass a richer format-specific object without leaking
92
+ * mutable implementation state into the shared descriptor.
93
+ *
94
+ * @throws {TypeError} If `options` is not an object.
95
+ * @throws {RangeError} If any field is invalid or exceeds the safe budget.
96
+ */
97
+ export declare function createSymbolLayout(options: SymbolLayoutOptions): SymbolLayout;
98
+
99
+ /**
100
+ * Count logical modules in a complete layout without allocating a matrix.
101
+ *
102
+ * Optional rows and columns multiply the panel dimensions. Quiet-zone modules
103
+ * are intentionally excluded: this helper reports symbol modules, while the
104
+ * validation budget also protects the future rendered footprint.
105
+ *
106
+ * @throws {TypeError} If `layout` is not a valid descriptor.
107
+ */
108
+ export declare function layoutModuleCount(layout: SymbolLayout): number;
@@ -0,0 +1,236 @@
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
+ /**
32
+ * Shared geometry metadata for matrix, dot, hexagonal, and stacked symbols.
33
+ *
34
+ * A layout is deliberately only a descriptor. It does not allocate a module
35
+ * matrix and does not prescribe how a format stores or renders its data.
36
+ *
37
+ * @module core/symbol-layout
38
+ */
39
+
40
+ /** The shape of one logical module. */
41
+ export type ModuleShape = 'square' | 'dot';
42
+
43
+ /** The lattice used to arrange logical modules. */
44
+ export type Lattice = 'square' | 'hex';
45
+
46
+ /** Explicit aliases for callers that prefer the longer type names. */
47
+ export type SymbolModuleShape = ModuleShape;
48
+ export type SymbolLattice = Lattice;
49
+
50
+ /**
51
+ * The immutable description of a symbol's module geometry.
52
+ *
53
+ * `width` and `height` describe one symbol panel in logical modules. Optional
54
+ * `rows` and `columns` describe a repeated panel arrangement, which lets a
55
+ * future stacked format share this descriptor without changing the matrix
56
+ * formats. They default to one panel when calculating a module count.
57
+ */
58
+ export interface SymbolLayoutDescriptor {
59
+ readonly moduleShape: ModuleShape;
60
+ readonly lattice: Lattice;
61
+ readonly width: number;
62
+ readonly height: number;
63
+ readonly quietZone: number;
64
+ readonly rows?: number;
65
+ readonly columns?: number;
66
+ }
67
+
68
+ /** The canonical layout type returned by {@link createSymbolLayout}. */
69
+ export type SymbolLayout = SymbolLayoutDescriptor;
70
+
71
+ /** Input accepted by {@link createSymbolLayout}. */
72
+ export type SymbolLayoutOptions = SymbolLayoutDescriptor;
73
+
74
+ /** Alias for code that uses input-oriented naming. */
75
+ export type SymbolLayoutInput = SymbolLayoutOptions;
76
+
77
+ /*
78
+ * These limits are intentionally conservative. They keep this foundation safe
79
+ * for future matrix allocators while allowing the largest practical barcode
80
+ * geometries. The implementation never allocates based on a descriptor.
81
+ */
82
+ const MAX_LAYOUT_DIMENSION = 32_768;
83
+ const MAX_LAYOUT_MODULES = 16_777_216;
84
+
85
+ const hasOwn = (value: object, property: string): boolean =>
86
+ Object.prototype.hasOwnProperty.call(value, property);
87
+
88
+ function isBoundedPositiveInteger(value: unknown, maximum: number): value is number {
89
+ return typeof value === 'number'
90
+ && Number.isFinite(value)
91
+ && Number.isInteger(value)
92
+ && value > 0
93
+ && value <= maximum;
94
+ }
95
+
96
+ function isRecord(value: unknown): value is Record<string, unknown> {
97
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
98
+ }
99
+
100
+ /**
101
+ * Check that the descriptor's full panel footprint stays within the allocation
102
+ * budget. The quiet zone is included in the footprint because a later renderer
103
+ * may materialize it alongside the active modules.
104
+ */
105
+ function isWithinModuleBudget(
106
+ width: number,
107
+ height: number,
108
+ quietZone: number,
109
+ rows: number,
110
+ columns: number,
111
+ ): boolean {
112
+ const footprintWidth = width + quietZone * 2;
113
+ const footprintHeight = height + quietZone * 2;
114
+
115
+ if (footprintWidth > MAX_LAYOUT_DIMENSION || footprintHeight > MAX_LAYOUT_DIMENSION) {
116
+ return false;
117
+ }
118
+
119
+ let total = footprintWidth;
120
+ if (total > MAX_LAYOUT_MODULES / footprintHeight) return false;
121
+ total *= footprintHeight;
122
+ if (total > MAX_LAYOUT_MODULES / rows) return false;
123
+ total *= rows;
124
+ if (total > MAX_LAYOUT_MODULES / columns) return false;
125
+ return total * columns <= MAX_LAYOUT_MODULES;
126
+ }
127
+
128
+ /**
129
+ * Return whether an unknown value is a valid symbol layout descriptor.
130
+ *
131
+ * Validation is side-effect free and performs no matrix allocation. A valid
132
+ * descriptor may be mutable when supplied by a caller; use
133
+ * {@link createSymbolLayout} when an immutable value is required.
134
+ */
135
+ export function validateSymbolLayout(value: unknown): value is SymbolLayout {
136
+ if (!isRecord(value)) return false;
137
+
138
+ if (!hasOwn(value, 'moduleShape') || (value.moduleShape !== 'square' && value.moduleShape !== 'dot')) {
139
+ return false;
140
+ }
141
+ if (!hasOwn(value, 'lattice') || (value.lattice !== 'square' && value.lattice !== 'hex')) {
142
+ return false;
143
+ }
144
+ if (!hasOwn(value, 'width') || !isBoundedPositiveInteger(value.width, MAX_LAYOUT_DIMENSION)) {
145
+ return false;
146
+ }
147
+ if (!hasOwn(value, 'height') || !isBoundedPositiveInteger(value.height, MAX_LAYOUT_DIMENSION)) {
148
+ return false;
149
+ }
150
+ if (!hasOwn(value, 'quietZone') || !isBoundedPositiveInteger(value.quietZone, MAX_LAYOUT_DIMENSION)) {
151
+ return false;
152
+ }
153
+
154
+ const rows = value.rows;
155
+ if (rows !== undefined
156
+ && (!hasOwn(value, 'rows') || !isBoundedPositiveInteger(rows, MAX_LAYOUT_DIMENSION))) {
157
+ return false;
158
+ }
159
+
160
+ const columns = value.columns;
161
+ if (columns !== undefined
162
+ && (!hasOwn(value, 'columns') || !isBoundedPositiveInteger(columns, MAX_LAYOUT_DIMENSION))) {
163
+ return false;
164
+ }
165
+
166
+ const rowCount = rows === undefined ? 1 : rows as number;
167
+ const columnCount = columns === undefined ? 1 : columns as number;
168
+
169
+ return isWithinModuleBudget(
170
+ value.width,
171
+ value.height,
172
+ value.quietZone,
173
+ rowCount,
174
+ columnCount,
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Create a validated, shallowly immutable symbol layout descriptor.
180
+ *
181
+ * Only the documented fields are copied. Unknown input properties are ignored,
182
+ * so a future caller can pass a richer format-specific object without leaking
183
+ * mutable implementation state into the shared descriptor.
184
+ *
185
+ * @throws {TypeError} If `options` is not an object.
186
+ * @throws {RangeError} If any field is invalid or exceeds the safe budget.
187
+ */
188
+ export function createSymbolLayout(options: SymbolLayoutOptions): SymbolLayout {
189
+ if (!isRecord(options)) {
190
+ throw new TypeError('Symbol layout options must be an object');
191
+ }
192
+
193
+ const descriptor: {
194
+ moduleShape: ModuleShape;
195
+ lattice: Lattice;
196
+ width: number;
197
+ height: number;
198
+ quietZone: number;
199
+ rows?: number;
200
+ columns?: number;
201
+ } = {
202
+ moduleShape: options.moduleShape as ModuleShape,
203
+ lattice: options.lattice as Lattice,
204
+ width: options.width as number,
205
+ height: options.height as number,
206
+ quietZone: options.quietZone as number,
207
+ };
208
+
209
+ if (options.rows !== undefined) descriptor.rows = options.rows as number;
210
+ if (options.columns !== undefined) descriptor.columns = options.columns as number;
211
+
212
+ if (!validateSymbolLayout(descriptor)) {
213
+ throw new RangeError('Invalid symbol layout descriptor');
214
+ }
215
+
216
+ return Object.freeze(descriptor);
217
+ }
218
+
219
+ /**
220
+ * Count logical modules in a complete layout without allocating a matrix.
221
+ *
222
+ * Optional rows and columns multiply the panel dimensions. Quiet-zone modules
223
+ * are intentionally excluded: this helper reports symbol modules, while the
224
+ * validation budget also protects the future rendered footprint.
225
+ *
226
+ * @throws {TypeError} If `layout` is not a valid descriptor.
227
+ */
228
+ export function layoutModuleCount(layout: SymbolLayout): number {
229
+ if (!validateSymbolLayout(layout)) {
230
+ throw new TypeError('Cannot count modules for an invalid symbol layout');
231
+ }
232
+
233
+ const rows = layout.rows === undefined ? 1 : layout.rows;
234
+ const columns = layout.columns === undefined ? 1 : layout.columns;
235
+ return layout.width * layout.height * rows * columns;
236
+ }
@@ -0,0 +1,40 @@
1
+ import { BitMatrix } from '../core/bit-matrix.js';
2
+
3
+ export interface DataBarExpandedOptions {
4
+ linkage?: boolean;
5
+ moduleScale?: number;
6
+ scale?: number;
7
+ /** Physical bar height in raster modules; minimum is 34 times the scale. */
8
+ height?: number;
9
+ }
10
+
11
+ export interface DataBarExpandedResult {
12
+ format: 'databar-expanded';
13
+ variant: 'expanded';
14
+ text: string;
15
+ raw: string;
16
+ gs1: true;
17
+ linkage: boolean;
18
+ checksum: number;
19
+ checksumValid: true;
20
+ dataCharacters: number;
21
+ pairs: number;
22
+ moduleScale: number;
23
+ height: number;
24
+ symbologyIdentifier: ']e0';
25
+ elements: Array<{ ai: string; value: string; fixed: boolean }>;
26
+ }
27
+
28
+ /** Encode a parenthesized GS1 element string as linear GS1 DataBar Expanded. */
29
+ export function encodeDataBarExpanded(value: string | Array<{ ai: string; value: string }>, options?: DataBarExpandedOptions): BitMatrix;
30
+ /** Decode a clean, upright or integer-scaled GS1 DataBar Expanded matrix. */
31
+ export function decodeDataBarExpanded(matrix: BitMatrix): DataBarExpandedResult;
32
+ /** Detect one complete, axis-aligned or quarter-turned Expanded symbol, or return null. */
33
+ export function detectDataBarExpanded(binaryImage: BitMatrix, options?: object): DataBarExpandedResult | null;
34
+ export const detectAndDecodeDataBarExpanded: typeof detectDataBarExpanded;
35
+ /** Decode a complete unscaled Expanded scanline, or return null. */
36
+ export function decodeDataBarExpandedScanline(row: ArrayLike<boolean | number>): DataBarExpandedResult | null;
37
+ export const encodeGS1DataBarExpanded: typeof encodeDataBarExpanded;
38
+ export const decodeGS1DataBarExpanded: typeof decodeDataBarExpanded;
39
+ export const detectGS1DataBarExpanded: typeof detectDataBarExpanded;
40
+ export const detectAndDecodeGS1DataBarExpanded: typeof detectDataBarExpanded;