@sythos/js_barcode_universal 1.2.5 → 1.4.1

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 (124) hide show
  1. package/AI_USAGE.md +33 -0
  2. package/LICENSE +11 -2
  3. package/NOTICE.md +10 -4
  4. package/README.md +601 -465
  5. package/bundle/sythos-barcode.esm.js +4669 -74
  6. package/bundle/sythos-barcode.js +4575 -72
  7. package/examples/create.html +1010 -730
  8. package/examples/read.html +1 -0
  9. package/licenses/README.md +40 -58
  10. package/licenses/aztec-code.license +15 -13
  11. package/licenses/aztec-rune.license +76 -0
  12. package/licenses/codabar.license +18 -15
  13. package/licenses/code-11.license +11 -9
  14. package/licenses/code-128.license +10 -8
  15. package/licenses/code-39.license +10 -8
  16. package/licenses/code-93.license +16 -14
  17. package/licenses/compact-pdf417.license +76 -0
  18. package/licenses/data-matrix.license +15 -13
  19. package/licenses/ean-13.license +10 -8
  20. package/licenses/ean-2.license +74 -0
  21. package/licenses/ean-5.license +74 -0
  22. package/licenses/ean-8.license +10 -8
  23. package/licenses/frameqr.license +84 -0
  24. package/licenses/gs1-128.license +10 -8
  25. package/licenses/gs1-databar.license +81 -0
  26. package/licenses/isbn.license +10 -8
  27. package/licenses/itf-14.license +10 -8
  28. package/licenses/itf.license +9 -7
  29. package/licenses/micro-qr.license +79 -0
  30. package/licenses/micropdf417.license +52 -67
  31. package/licenses/msi-plessey.license +13 -11
  32. package/licenses/pdf417.license +78 -37
  33. package/licenses/pharmacode.license +14 -12
  34. package/licenses/qr-code.license +9 -7
  35. package/licenses/rmqr.license +79 -0
  36. package/licenses/upc-a.license +12 -10
  37. package/licenses/upc-e.license +10 -8
  38. package/package.json +109 -88
  39. package/src/aztec/decoder.js +1 -0
  40. package/src/aztec/detector.js +1 -0
  41. package/src/aztec/encoder.js +1 -0
  42. package/src/aztec/high-level.js +1 -0
  43. package/src/aztec/index.js +1 -0
  44. package/src/aztec/tables.js +1 -0
  45. package/src/aztecrune/decoder.js +156 -0
  46. package/src/aztecrune/detector.js +167 -0
  47. package/src/aztecrune/encoder.js +122 -0
  48. package/src/aztecrune/index.js +50 -0
  49. package/src/aztecrune/tables.js +138 -0
  50. package/src/compactpdf417/decoder.js +129 -0
  51. package/src/compactpdf417/detector.js +140 -0
  52. package/src/compactpdf417/encoder.js +141 -0
  53. package/src/compactpdf417/index.js +22 -0
  54. package/src/compactpdf417/tables.js +174 -0
  55. package/src/core/bit-buffer.js +1 -0
  56. package/src/core/bit-matrix.js +1 -0
  57. package/src/core/errors.js +1 -0
  58. package/src/core/galois-field.js +1 -0
  59. package/src/core/index.js +1 -0
  60. package/src/core/reed-solomon.js +1 -0
  61. package/src/databar/codec.js +182 -0
  62. package/src/databar/decoder.js +115 -0
  63. package/src/databar/encoder.js +97 -0
  64. package/src/databar/gs1.js +178 -0
  65. package/src/databar/index.js +69 -0
  66. package/src/databar/patterns.js +144 -0
  67. package/src/databar/tables.js +125 -0
  68. package/src/datamatrix/decoder.js +263 -262
  69. package/src/datamatrix/detector.js +226 -225
  70. package/src/datamatrix/encoder.js +192 -191
  71. package/src/datamatrix/index.js +43 -42
  72. package/src/datamatrix/tables.js +124 -123
  73. package/src/frameqr/decoder.js +240 -0
  74. package/src/frameqr/detector.js +193 -0
  75. package/src/frameqr/encoder.js +157 -0
  76. package/src/frameqr/index.js +43 -0
  77. package/src/frameqr/tables.js +271 -0
  78. package/src/image/binarizer.js +1 -0
  79. package/src/image/grid-sampler.js +1 -0
  80. package/src/image/index.js +1 -0
  81. package/src/image/luminance.js +1 -0
  82. package/src/image/perspective.js +1 -0
  83. package/src/index.js +155 -2
  84. package/src/micropdf417/compaction.js +1 -0
  85. package/src/micropdf417/decoder.js +1 -0
  86. package/src/micropdf417/detector.js +1 -0
  87. package/src/micropdf417/encoder.js +1 -0
  88. package/src/micropdf417/error-correction.js +1 -0
  89. package/src/micropdf417/index.js +1 -0
  90. package/src/micropdf417/tables.js +1 -0
  91. package/src/microqr/decoder.js +246 -0
  92. package/src/microqr/detector.js +356 -0
  93. package/src/microqr/encoder.js +270 -0
  94. package/src/microqr/index.js +37 -0
  95. package/src/microqr/tables.js +317 -0
  96. package/src/oned/addons.js +421 -0
  97. package/src/oned/index.js +77 -59
  98. package/src/oned/patterns.js +1 -0
  99. package/src/oned/reader.js +1 -0
  100. package/src/oned/writers.js +1 -0
  101. package/src/pdf417/compaction.js +1 -0
  102. package/src/pdf417/decoder.js +1 -0
  103. package/src/pdf417/detector.js +1 -0
  104. package/src/pdf417/encoder.js +1 -0
  105. package/src/pdf417/error-correction.js +1 -0
  106. package/src/pdf417/index.js +5 -0
  107. package/src/pdf417/tables.js +1 -0
  108. package/src/qr/decoder.js +1 -0
  109. package/src/qr/detector.js +1 -0
  110. package/src/qr/encoder.js +1 -0
  111. package/src/qr/index.js +1 -0
  112. package/src/qr/tables.js +1 -0
  113. package/src/render/image-data.js +1 -0
  114. package/src/render/index.js +1 -0
  115. package/src/render/options.js +1 -0
  116. package/src/render/png.js +1 -0
  117. package/src/render/svg.js +1 -0
  118. package/src/render/webgl.js +1 -0
  119. package/src/render/webgpu.js +1 -0
  120. package/src/rmqr/decoder.js +102 -0
  121. package/src/rmqr/detector.js +91 -0
  122. package/src/rmqr/encoder.js +173 -0
  123. package/src/rmqr/index.js +38 -0
  124. package/src/rmqr/tables.js +155 -0
@@ -0,0 +1,140 @@
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
+ /** Compact PDF417 detector for clean, integer-scaled rasters. @module compactpdf417/detector */
33
+
34
+ import { BitMatrix } from '../core/bit-matrix.js';
35
+ import { compactPdf417Width } from './tables.js';
36
+ import { decodeCompactPDF417 } from './decoder.js';
37
+
38
+ function rotateClockwise(source) {
39
+ const out = new BitMatrix(source.height, source.width);
40
+ for (let y = 0; y < source.height; y++) {
41
+ for (let x = 0; x < source.width; x++) {
42
+ if (source.get(x, y)) out.set(source.height - 1 - y, x);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ function cropAndDownsample(source, box, width, height, scale) {
48
+ const out = new BitMatrix(width, height);
49
+ for (let y = 0; y < height; y++) {
50
+ for (let x = 0; x < width; x++) {
51
+ let dark = 0;
52
+ for (let dy = 0; dy < scale; dy++) {
53
+ for (let dx = 0; dx < scale; dx++) {
54
+ if (source.get(box.x + x * scale + dx, box.y + y * scale + dy)) dark++;
55
+ }
56
+ }
57
+ if (dark * 2 >= scale * scale) out.set(x, y);
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ function cornersFor(box) {
64
+ return [
65
+ { x: box.x, y: box.y },
66
+ { x: box.x + box.width, y: box.y },
67
+ { x: box.x + box.width, y: box.y + box.height },
68
+ { x: box.x, y: box.y + box.height },
69
+ ];
70
+ }
71
+
72
+ function mapCorners(corners, toOriginal) {
73
+ return corners.map((point) => toOriginal(point));
74
+ }
75
+
76
+ /**
77
+ * Detect one Compact PDF417 symbol in a binarized raster.
78
+ *
79
+ * The clean-raster detector uses the dark bounding box and enumerates legal
80
+ * compact widths, integer module scales, row counts and row heights. It is
81
+ * deliberately conservative: arbitrary perspective, non-integer resampling,
82
+ * grayscale thresholding and damaged stop bars belong to a future photo
83
+ * detector and are not claimed here.
84
+ *
85
+ * @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
86
+ * @param {object} [options]
87
+ * @param {number} [options.rowHeight] Restrict the row-height search.
88
+ * @returns {object|null}
89
+ */
90
+ export function detectCompactPDF417(binaryImage, options = {}) {
91
+ if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function') return null;
92
+
93
+ let oriented = binaryImage;
94
+ let toOriginal = (point) => ({ x: point.x, y: point.y });
95
+ for (let rotation = 0; rotation < 4; rotation++) {
96
+ const bounds = oriented.getBounds();
97
+ if (bounds) {
98
+ for (let columns = 1; columns <= 30; columns++) {
99
+ const width = compactPdf417Width(columns);
100
+ const scale = bounds.width / width;
101
+ if (!Number.isInteger(scale) || scale < 1) continue;
102
+ const baseHeight = bounds.height / scale;
103
+ const rowHeights = Number.isInteger(options.rowHeight)
104
+ ? [options.rowHeight]
105
+ : Array.from({ length: 18 }, (_, index) => index + 3);
106
+ for (const rowHeight of rowHeights) {
107
+ if (!Number.isInteger(baseHeight / rowHeight)) continue;
108
+ const rows = baseHeight / rowHeight;
109
+ if (rows < 3 || rows > 90) continue;
110
+ const matrix = cropAndDownsample(oriented, bounds, width, baseHeight, scale);
111
+ try {
112
+ const result = decodeCompactPDF417(matrix, { rowHeight });
113
+ const corners = mapCorners(cornersFor(bounds), toOriginal);
114
+ return {
115
+ ...result,
116
+ matrix,
117
+ corners,
118
+ rotation: rotation * 90,
119
+ moduleSize: scale,
120
+ compact: true,
121
+ };
122
+ } catch {
123
+ // Try another geometry; a standard PDF417 or random artwork should
124
+ // never be accepted unless the compact decoder validates every row.
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ const previous = oriented;
131
+ const previousToOriginal = toOriginal;
132
+ oriented = rotateClockwise(previous);
133
+ toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
134
+ }
135
+ return null;
136
+ }
137
+
138
+ export function detectAndDecodeCompactPDF417(binaryImage, options = {}) {
139
+ return detectCompactPDF417(binaryImage, options);
140
+ }
@@ -0,0 +1,141 @@
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
+ /** Compact PDF417 (truncated PDF417) encoder. @module compactpdf417/encoder */
33
+
34
+ import { BitMatrix } from '../core/bit-matrix.js';
35
+ import { EncodeError } from '../core/errors.js';
36
+ import { compactPdf417 } from '../pdf417/compaction.js';
37
+ import { pdf417ErrorCorrection, pdf417EccLength } from '../pdf417/error-correction.js';
38
+ import { pdf417PatternForCodeword } from '../pdf417/tables.js';
39
+ import {
40
+ COMPACT_PDF417_START,
41
+ compactPdf417Indicators,
42
+ compactPdf417Width,
43
+ validateCompactPdf417Options,
44
+ } from './tables.js';
45
+
46
+ function appendWidths(matrix, y, x, sequence, height) {
47
+ let dark = true;
48
+ for (const digit of sequence) {
49
+ const width = digit.charCodeAt(0) - 48;
50
+ if (!Number.isInteger(width) || width < 1 || width > 8) {
51
+ throw new EncodeError('Compact PDF417: invalid module-width sequence');
52
+ }
53
+ if (dark) matrix.setRegion(x, y, width, height);
54
+ x += width;
55
+ dark = !dark;
56
+ }
57
+ return x;
58
+ }
59
+
60
+ function patternSequence(pattern) {
61
+ return pattern.toString(2).padStart(17, '0').replace(/0+|1+/g, (run) => String(run.length));
62
+ }
63
+
64
+ function dimensions(needed, level, options, rowHeight) {
65
+ const ecc = pdf417EccLength(level);
66
+ let best = null;
67
+ for (let rows = options.rows ?? 3; rows <= (options.rows ?? 90); rows++) {
68
+ for (let columns = options.columns ?? 1; columns <= (options.columns ?? 30); columns++) {
69
+ if (rows * columns > 928 || rows * columns - ecc < needed) continue;
70
+ const ratio = compactPdf417Width(columns) / (rows * rowHeight);
71
+ const score = (rows * columns - ecc - needed) * 10 +
72
+ Math.abs(ratio - (options.aspectRatio ?? 3));
73
+ if (!best || score < best.score) best = { rows, columns, score };
74
+ }
75
+ }
76
+ if (!best) {
77
+ throw new EncodeError(
78
+ 'Compact PDF417: payload does not fit the requested dimensions and error correction level'
79
+ );
80
+ }
81
+ return best;
82
+ }
83
+
84
+ /**
85
+ * Encode Compact PDF417.
86
+ *
87
+ * High-level Text/Byte/Numeric compaction is intentionally delegated to the
88
+ * existing PDF417 compaction implementation. `compact` here describes only
89
+ * the physical truncated layout: right row indicators are omitted and the
90
+ * stop pattern is reduced to one dark module.
91
+ *
92
+ * @param {string|Uint8Array|number[]} value
93
+ * @param {object} [options]
94
+ * @returns {import('../core/bit-matrix.js').BitMatrix}
95
+ */
96
+ export function encodeCompactPDF417(value, options = {}) {
97
+ const rowHeight = validateCompactPdf417Options(options);
98
+ const level = options.eccLevel ?? 2;
99
+ if (!Number.isInteger(level) || level < 0 || level > 8) {
100
+ throw new EncodeError('Compact PDF417: eccLevel must be an integer in 0..8');
101
+ }
102
+
103
+ const payload = compactPdf417(value, { compaction: options.compaction, charset: options.charset });
104
+ const { rows, columns } = dimensions(payload.length + 1, level, options, rowHeight);
105
+ const eccLength = pdf417EccLength(level);
106
+ const dataLength = rows * columns - eccLength;
107
+ const data = [dataLength, ...payload];
108
+ while (data.length < dataLength) data.push(900);
109
+ const codewords = data.concat(pdf417ErrorCorrection(data, level));
110
+
111
+ const matrix = new BitMatrix(compactPdf417Width(columns), rows * rowHeight);
112
+ for (let row = 0; row < rows; row++) {
113
+ const y = row * rowHeight;
114
+ const cluster = (row % 3) * 3;
115
+ let x = appendWidths(matrix, y, 0, COMPACT_PDF417_START, rowHeight);
116
+ const left = compactPdf417Indicators(row, rows, columns, level);
117
+ x = appendWidths(matrix, y, x, patternSequence(pdf417PatternForCodeword(left, cluster)), rowHeight);
118
+ for (let column = 0; column < columns; column++) {
119
+ const codeword = codewords[row * columns + column];
120
+ x = appendWidths(matrix, y, x,
121
+ patternSequence(pdf417PatternForCodeword(codeword, cluster)), rowHeight);
122
+ }
123
+ // Compact PDF417 terminates every row with one dark module, not the normal
124
+ // right indicator plus 18-module stop pattern.
125
+ matrix.setRegion(x, y, 1, rowHeight);
126
+ x++;
127
+ if (x !== matrix.width) throw new EncodeError('Compact PDF417: internal row width mismatch');
128
+ }
129
+
130
+ matrix.compactPdf417 = {
131
+ rows,
132
+ columns,
133
+ eccLevel: level,
134
+ rowHeight,
135
+ codewords,
136
+ layout: 'compact',
137
+ };
138
+ return matrix;
139
+ }
140
+
141
+ export { dimensions as compactPdf417Dimensions };
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ /* Compact PDF417 public module exports. */
7
+
8
+ export {
9
+ COMPACT_PDF417_START,
10
+ COMPACT_PDF417_START_BITS,
11
+ COMPACT_PDF417_STOP_MODULES,
12
+ COMPACT_PDF417_LIMITS,
13
+ compactPdf417Width,
14
+ compactPdf417Geometry,
15
+ compactPdf417Indicators,
16
+ compactPdf417MatchingLevels,
17
+ validateCompactPdf417Options,
18
+ validateCompactPdf417Tables,
19
+ } from './tables.js';
20
+ export { encodeCompactPDF417, compactPdf417Dimensions } from './encoder.js';
21
+ export { decodeCompactPDF417 } from './decoder.js';
22
+ export { detectCompactPDF417, detectAndDecodeCompactPDF417 } from './detector.js';
@@ -0,0 +1,174 @@
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
+ * Compact (truncated) PDF417 layout.
34
+ *
35
+ * Compact PDF417 is a geometric variant of PDF417, not a new high-level
36
+ * compaction mode. It keeps the same codeword, cluster, row-indicator,
37
+ * Reed–Solomon and Text/Byte/Numeric compaction rules, while omitting each
38
+ * row's right row indicator and replacing the normal stop pattern with a
39
+ * single dark module. The resulting symbol remains distinguishable from the
40
+ * ordinary PDF417 layout by its width and row terminator.
41
+ *
42
+ * @module compactpdf417/tables
43
+ */
44
+
45
+ import { EncodeError } from '../core/errors.js';
46
+
47
+ /** PDF417 start pattern, in module widths. */
48
+ export const COMPACT_PDF417_START = '81111113';
49
+
50
+ /** Binary module representation of the start pattern. */
51
+ export const COMPACT_PDF417_START_BITS = '11111111010101000';
52
+
53
+ /** Compact PDF417's reduced stop is one dark module. */
54
+ export const COMPACT_PDF417_STOP_MODULES = 1;
55
+
56
+ /** Minimum/maximum number of rows and data columns. */
57
+ export const COMPACT_PDF417_LIMITS = Object.freeze({
58
+ minRows: 3,
59
+ maxRows: 90,
60
+ minColumns: 1,
61
+ maxColumns: 30,
62
+ maxCodewords: 928,
63
+ });
64
+
65
+ /**
66
+ * Return the printed width in modules for a compact symbol.
67
+ *
68
+ * 17 start + 17 left indicator + 17 per codeword column + 1 reduced stop.
69
+ * @param {number} columns
70
+ */
71
+ export function compactPdf417Width(columns) {
72
+ if (!Number.isInteger(columns) || columns < 1 || columns > 30) {
73
+ throw new RangeError(`Compact PDF417: columns must be an integer in 1..30, got ${columns}`);
74
+ }
75
+ return 35 + columns * 17;
76
+ }
77
+
78
+ /**
79
+ * Validate a compact matrix geometry and return its dimensions.
80
+ *
81
+ * @param {number} width
82
+ * @param {number} height
83
+ * @param {number} rowHeight
84
+ */
85
+ export function compactPdf417Geometry(width, height, rowHeight = 3) {
86
+ if (!Number.isInteger(width) || !Number.isInteger(height)) {
87
+ throw new RangeError('Compact PDF417: matrix dimensions must be integers');
88
+ }
89
+ if (!Number.isInteger(rowHeight) || rowHeight < 1) {
90
+ throw new RangeError('Compact PDF417: rowHeight must be a positive integer');
91
+ }
92
+ if ((width - 35) % 17 !== 0) {
93
+ throw new RangeError(`Compact PDF417: width ${width} does not match the compact layout`);
94
+ }
95
+ const columns = (width - 35) / 17;
96
+ const rows = height / rowHeight;
97
+ if (!Number.isInteger(rows) || rows < 3 || rows > 90) {
98
+ throw new RangeError(`Compact PDF417: rows must be an integer in 3..90, got ${rows}`);
99
+ }
100
+ if (columns < 1 || columns > 30) {
101
+ throw new RangeError(`Compact PDF417: columns must be an integer in 1..30, got ${columns}`);
102
+ }
103
+ return { rows, columns, rowHeight, width, height };
104
+ }
105
+
106
+ /**
107
+ * Validate encoder dimensions before codeword packing.
108
+ * @param {object} options
109
+ */
110
+ export function validateCompactPdf417Options(options = {}) {
111
+ const { minRows, maxRows, minColumns, maxColumns } = COMPACT_PDF417_LIMITS;
112
+ const rowHeight = options.rowHeight ?? 3;
113
+ if (!Number.isInteger(rowHeight) || rowHeight < 3) {
114
+ throw new EncodeError('Compact PDF417: rowHeight must be an integer of at least 3');
115
+ }
116
+ for (const [name, value, min, max] of [
117
+ ['rows', options.rows, minRows, maxRows],
118
+ ['columns', options.columns, minColumns, maxColumns],
119
+ ]) {
120
+ if (value !== undefined && (!Number.isInteger(value) || value < min || value > max)) {
121
+ throw new EncodeError(`Compact PDF417: ${name} must be an integer in ${min}..${max}`);
122
+ }
123
+ }
124
+ if (options.aspectRatio !== undefined &&
125
+ (!Number.isFinite(options.aspectRatio) || options.aspectRatio <= 0)) {
126
+ throw new EncodeError('Compact PDF417: aspectRatio must be positive');
127
+ }
128
+ return rowHeight;
129
+ }
130
+
131
+ /**
132
+ * The row indicator values used by ordinary PDF417 are retained unchanged.
133
+ * Compact symbols omit only the right indicator, so the left indicator still
134
+ * carries the row group, row count, error-correction level and column count.
135
+ */
136
+ export function compactPdf417Indicators(row, rows, columns, level) {
137
+ const group = Math.floor(row / 3);
138
+ const y = Math.floor((rows - 1) / 3);
139
+ const z = level * 3 + (rows - 1) % 3;
140
+ const v = columns - 1;
141
+ if (row % 3 === 0) return 30 * group + y;
142
+ if (row % 3 === 1) return 30 * group + z;
143
+ return 30 * group + v;
144
+ }
145
+
146
+ /**
147
+ * Return all standard ECC levels which agree with the observed left
148
+ * indicators. This is kept as a table helper so decoder and tests share the
149
+ * same compact-layout rule.
150
+ */
151
+ export function compactPdf417MatchingLevels(leftIndicators, rows, columns) {
152
+ const matches = [];
153
+ for (let level = 0; level <= 8; level++) {
154
+ if (leftIndicators.every((value, row) =>
155
+ value === compactPdf417Indicators(row, rows, columns, level))) matches.push(level);
156
+ }
157
+ return matches;
158
+ }
159
+
160
+ /** Validate representative layout invariants. */
161
+ export function validateCompactPdf417Tables() {
162
+ const problems = [];
163
+ for (const columns of [1, 2, 3, 10, 30]) {
164
+ const width = compactPdf417Width(columns);
165
+ if (width !== 35 + columns * 17) problems.push(`columns ${columns}: width mismatch`);
166
+ }
167
+ for (const rows of [3, 4, 5, 6, 90]) {
168
+ const left = Array.from({ length: rows }, (_, row) => compactPdf417Indicators(row, rows, 3, 2));
169
+ if (compactPdf417MatchingLevels(left, rows, 3).length !== 1) {
170
+ problems.push(`rows ${rows}: row indicators do not identify one ECC level`);
171
+ }
172
+ }
173
+ return problems;
174
+ }
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.
package/src/core/index.js CHANGED
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.
@@ -23,6 +23,7 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
26
27
  * SPDX-License-Identifier: MIT
27
28
  *
28
29
  * Original work. No code from any other barcode implementation.