@sythos/js_barcode_universal 1.5.8 → 1.5.10

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 (179) hide show
  1. package/LICENSE +5 -4
  2. package/NOTICE.md +2 -2
  3. package/README.md +61 -13
  4. package/bundle/sythos-barcode.esm.js +11658 -11244
  5. package/bundle/sythos-barcode.js +11658 -11244
  6. package/package.json +10 -3
  7. package/src/index.d.ts +1 -1
  8. package/src/index.js +508 -516
  9. package/src/js/aztec/decoder.js +208 -199
  10. package/src/js/aztec/detector.js +153 -126
  11. package/src/js/aztec/encoder.js +172 -153
  12. package/src/js/aztec/high-level.js +106 -101
  13. package/src/js/aztec/index.js +1 -12
  14. package/src/js/aztec/tables.js +106 -120
  15. package/src/js/aztecrune/decoder.js +76 -85
  16. package/src/js/aztecrune/detector.js +107 -93
  17. package/src/js/aztecrune/encoder.js +50 -68
  18. package/src/js/aztecrune/index.js +1 -16
  19. package/src/js/aztecrune/tables.js +59 -54
  20. package/src/js/compactpdf417/decoder.js +69 -74
  21. package/src/js/compactpdf417/detector.js +74 -75
  22. package/src/js/compactpdf417/encoder.js +68 -81
  23. package/src/js/compactpdf417/index.js +1 -13
  24. package/src/js/compactpdf417/tables.js +71 -80
  25. package/src/js/core/bit-buffer.js +106 -122
  26. package/src/js/core/bit-matrix.js +196 -194
  27. package/src/js/core/errors.js +13 -15
  28. package/src/js/core/galois-field.js +119 -133
  29. package/src/js/core/index.js +2 -19
  30. package/src/js/core/reed-solomon.js +195 -204
  31. package/src/js/databar/codec.js +93 -108
  32. package/src/js/databar/decoder.js +149 -140
  33. package/src/js/databar/encoder.js +47 -55
  34. package/src/js/databar/gs1.js +122 -123
  35. package/src/js/databar/index.js +4 -36
  36. package/src/js/databar/patterns.js +81 -85
  37. package/src/js/databar/tables.js +51 -52
  38. package/src/js/datamatrix/decoder.js +216 -182
  39. package/src/js/datamatrix/detector.js +161 -132
  40. package/src/js/datamatrix/encoder.js +192 -133
  41. package/src/js/datamatrix/index.js +1 -9
  42. package/src/js/datamatrix/tables.js +68 -69
  43. package/src/js/frameqr/decoder.js +102 -125
  44. package/src/js/frameqr/detector.js +101 -94
  45. package/src/js/frameqr/encoder.js +67 -80
  46. package/src/js/frameqr/index.js +1 -10
  47. package/src/js/frameqr/tables.js +145 -163
  48. package/src/js/image/binarizer.js +146 -162
  49. package/src/js/image/grid-sampler.js +63 -79
  50. package/src/js/image/index.js +0 -2
  51. package/src/js/image/luminance.js +129 -140
  52. package/src/js/image/perspective.js +112 -149
  53. package/src/js/micropdf417/compaction.js +64 -65
  54. package/src/js/micropdf417/decoder.js +106 -109
  55. package/src/js/micropdf417/detector.js +73 -70
  56. package/src/js/micropdf417/encoder.js +129 -154
  57. package/src/js/micropdf417/error-correction.js +6 -11
  58. package/src/js/micropdf417/index.js +2 -16
  59. package/src/js/micropdf417/tables.js +112 -99
  60. package/src/js/microqr/decoder.js +177 -182
  61. package/src/js/microqr/detector.js +240 -256
  62. package/src/js/microqr/encoder.js +239 -194
  63. package/src/js/microqr/index.js +0 -2
  64. package/src/js/microqr/tables.js +208 -200
  65. package/src/js/oned/addons.js +167 -191
  66. package/src/js/oned/index.js +27 -58
  67. package/src/js/oned/patterns.js +194 -213
  68. package/src/js/oned/reader.js +1003 -984
  69. package/src/js/oned/writers.js +411 -466
  70. package/src/js/pdf417/compaction.js +315 -222
  71. package/src/js/pdf417/decoder.js +71 -36
  72. package/src/js/pdf417/detector.js +377 -361
  73. package/src/js/pdf417/encoder.js +57 -46
  74. package/src/js/pdf417/error-correction.js +5 -8
  75. package/src/js/pdf417/tables.js +248 -250
  76. package/src/js/qr/decoder.js +311 -347
  77. package/src/js/qr/detector.js +378 -395
  78. package/src/js/qr/encoder.js +514 -599
  79. package/src/js/qr/index.js +0 -2
  80. package/src/js/qr/tables.js +375 -422
  81. package/src/js/render/image-data.js +57 -62
  82. package/src/js/render/index.js +21 -23
  83. package/src/js/render/options.js +82 -90
  84. package/src/js/render/png.js +144 -169
  85. package/src/js/render/svg.js +41 -44
  86. package/src/js/render/webgl.js +103 -109
  87. package/src/js/render/webgpu.js +205 -217
  88. package/src/js/rmqr/decoder.js +161 -53
  89. package/src/js/rmqr/detector.js +73 -41
  90. package/src/js/rmqr/encoder.js +253 -104
  91. package/src/js/rmqr/index.js +1 -5
  92. package/src/js/rmqr/tables.js +108 -85
  93. package/src/ts/aztec/decoder.ts +317 -0
  94. package/src/ts/aztec/detector.ts +224 -0
  95. package/src/ts/aztec/encoder.ts +257 -0
  96. package/src/ts/aztec/high-level.ts +211 -0
  97. package/src/ts/aztec/index.ts +45 -0
  98. package/src/ts/aztec/tables.ts +210 -0
  99. package/src/ts/aztecrune/decoder.ts +155 -0
  100. package/src/ts/aztecrune/detector.ts +166 -0
  101. package/src/ts/aztecrune/encoder.ts +121 -0
  102. package/src/ts/aztecrune/index.ts +49 -0
  103. package/src/ts/aztecrune/tables.ts +137 -0
  104. package/src/ts/compactpdf417/decoder.ts +128 -0
  105. package/src/ts/compactpdf417/detector.ts +139 -0
  106. package/src/ts/compactpdf417/encoder.ts +140 -0
  107. package/src/ts/compactpdf417/index.ts +17 -0
  108. package/src/ts/compactpdf417/tables.ts +173 -0
  109. package/src/ts/core/bit-buffer.ts +174 -0
  110. package/src/ts/core/bit-matrix.ts +241 -0
  111. package/src/ts/core/errors.ts +61 -0
  112. package/src/ts/core/galois-field.ts +207 -0
  113. package/src/ts/core/index.ts +56 -0
  114. package/src/ts/core/reed-solomon.ts +327 -0
  115. package/src/ts/databar/codec.ts +181 -0
  116. package/src/ts/databar/decoder.ts +215 -0
  117. package/src/ts/databar/encoder.ts +96 -0
  118. package/src/ts/databar/gs1.ts +177 -0
  119. package/src/ts/databar/index.ts +68 -0
  120. package/src/ts/databar/patterns.ts +143 -0
  121. package/src/ts/databar/tables.ts +124 -0
  122. package/src/ts/datamatrix/decoder.ts +262 -0
  123. package/src/ts/datamatrix/detector.ts +225 -0
  124. package/src/ts/datamatrix/encoder.ts +191 -0
  125. package/src/ts/datamatrix/index.ts +42 -0
  126. package/src/ts/datamatrix/tables.ts +123 -0
  127. package/src/ts/frameqr/decoder.ts +239 -0
  128. package/src/ts/frameqr/detector.ts +192 -0
  129. package/src/ts/frameqr/encoder.ts +156 -0
  130. package/src/ts/frameqr/index.ts +42 -0
  131. package/src/ts/frameqr/tables.ts +270 -0
  132. package/src/ts/image/binarizer.ts +270 -0
  133. package/src/ts/image/grid-sampler.ts +164 -0
  134. package/src/ts/image/index.ts +40 -0
  135. package/src/ts/image/luminance.ts +196 -0
  136. package/src/ts/image/perspective.ts +195 -0
  137. package/src/ts/index.d.ts +1 -1
  138. package/src/ts/index.ts +790 -0
  139. package/src/ts/micropdf417/compaction.ts +116 -0
  140. package/src/ts/micropdf417/decoder.ts +183 -0
  141. package/src/ts/micropdf417/detector.ts +149 -0
  142. package/src/ts/micropdf417/encoder.ts +209 -0
  143. package/src/ts/micropdf417/error-correction.ts +55 -0
  144. package/src/ts/micropdf417/index.ts +49 -0
  145. package/src/ts/micropdf417/tables.ts +184 -0
  146. package/src/ts/microqr/decoder.ts +245 -0
  147. package/src/ts/microqr/detector.ts +355 -0
  148. package/src/ts/microqr/encoder.ts +269 -0
  149. package/src/ts/microqr/index.ts +36 -0
  150. package/src/ts/microqr/tables.ts +316 -0
  151. package/src/ts/oned/addons.ts +420 -0
  152. package/src/ts/oned/index.ts +106 -0
  153. package/src/ts/oned/patterns.ts +384 -0
  154. package/src/ts/oned/reader.ts +1364 -0
  155. package/src/ts/oned/writers.ts +746 -0
  156. package/src/ts/pdf417/compaction.ts +298 -0
  157. package/src/ts/pdf417/decoder.ts +75 -0
  158. package/src/ts/pdf417/detector.ts +468 -0
  159. package/src/ts/pdf417/encoder.ts +91 -0
  160. package/src/ts/pdf417/error-correction.ts +47 -0
  161. package/src/ts/pdf417/index.ts +6 -0
  162. package/src/ts/pdf417/tables.ts +317 -0
  163. package/src/ts/qr/decoder.ts +575 -0
  164. package/src/ts/qr/detector.ts +630 -0
  165. package/src/ts/qr/encoder.ts +958 -0
  166. package/src/ts/qr/index.ts +44 -0
  167. package/src/ts/qr/tables.ts +737 -0
  168. package/src/ts/render/image-data.ts +125 -0
  169. package/src/ts/render/index.ts +130 -0
  170. package/src/ts/render/options.ts +160 -0
  171. package/src/ts/render/png.ts +295 -0
  172. package/src/ts/render/svg.ts +120 -0
  173. package/src/ts/render/webgl.ts +206 -0
  174. package/src/ts/render/webgpu.ts +369 -0
  175. package/src/ts/rmqr/decoder.ts +101 -0
  176. package/src/ts/rmqr/detector.ts +90 -0
  177. package/src/ts/rmqr/encoder.ts +172 -0
  178. package/src/ts/rmqr/index.ts +37 -0
  179. package/src/ts/rmqr/tables.ts +154 -0
@@ -27,7 +27,6 @@
27
27
  *
28
28
  * Original work. No code from any other barcode implementation.
29
29
  */
30
-
31
30
  /**
32
31
  * QR Code structural tables.
33
32
  *
@@ -57,40 +56,32 @@
57
56
  *
58
57
  * @module qr/tables
59
58
  */
60
-
61
59
  import { BitMatrix } from '../core/bit-matrix.js';
62
-
63
60
  /** Error correction levels, weakest to strongest. */
64
61
  export const ECC_LEVELS = ['L', 'M', 'Q', 'H'];
65
-
66
62
  /**
67
63
  * Two-bit level indicator used in the format information.
68
64
  * Note this is *not* the L/M/Q/H ordering — the spec assigns them out of order.
69
65
  */
70
66
  export const ECC_LEVEL_BITS = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 };
71
-
72
67
  /** Inverse of {@link ECC_LEVEL_BITS}, indexed by the 2-bit value. */
73
68
  export const ECC_LEVEL_BY_BITS = ['M', 'L', 'H', 'Q'];
74
-
75
69
  export const MIN_VERSION = 1;
76
70
  export const MAX_VERSION = 40;
77
-
78
71
  /** Version at and above which an 18-bit version information block is carried. */
79
72
  export const VERSION_INFO_MIN = 7;
80
-
81
73
  /** Mode indicator nibbles. */
82
74
  export const MODE = {
83
- TERMINATOR: 0x0,
84
- NUMERIC: 0x1,
85
- ALPHANUMERIC: 0x2,
86
- STRUCTURED_APPEND: 0x3,
87
- BYTE: 0x4,
88
- FNC1_FIRST: 0x5,
89
- ECI: 0x7,
90
- KANJI: 0x8,
91
- FNC1_SECOND: 0x9,
75
+ TERMINATOR: 0x0,
76
+ NUMERIC: 0x1,
77
+ ALPHANUMERIC: 0x2,
78
+ STRUCTURED_APPEND: 0x3,
79
+ BYTE: 0x4,
80
+ FNC1_FIRST: 0x5,
81
+ ECI: 0x7,
82
+ KANJI: 0x8,
83
+ FNC1_SECOND: 0x9,
92
84
  };
93
-
94
85
  /**
95
86
  * Character count indicator width, in bits, by mode and version band.
96
87
  *
@@ -99,20 +90,18 @@ export const MODE = {
99
90
  * field can push a payload over a version boundary, which widens it again.
100
91
  */
101
92
  const COUNT_BITS = {
102
- [MODE.NUMERIC]: [10, 12, 14],
103
- [MODE.ALPHANUMERIC]: [9, 11, 13],
104
- [MODE.BYTE]: [8, 16, 16],
105
- [MODE.KANJI]: [8, 10, 12],
93
+ [MODE.NUMERIC]: [10, 12, 14],
94
+ [MODE.ALPHANUMERIC]: [9, 11, 13],
95
+ [MODE.BYTE]: [8, 16, 16],
96
+ [MODE.KANJI]: [8, 10, 12],
106
97
  };
107
-
108
98
  /**
109
99
  * @param {number} version 1-40
110
100
  * @returns {number} Modules per side.
111
101
  */
112
102
  export function versionSize(version) {
113
- return 17 + 4 * version;
103
+ return 17 + 4 * version;
114
104
  }
115
-
116
105
  /**
117
106
  * Bits in the character count indicator.
118
107
  *
@@ -121,13 +110,15 @@ export function versionSize(version) {
121
110
  * @returns {number}
122
111
  */
123
112
  export function countBits(mode, version) {
124
- const widths = COUNT_BITS[mode];
125
- if (!widths) return 0;
126
- if (version <= 9) return widths[0];
127
- if (version <= 26) return widths[1];
128
- return widths[2];
113
+ const widths = COUNT_BITS[mode];
114
+ if (!widths)
115
+ return 0;
116
+ if (version <= 9)
117
+ return widths[0];
118
+ if (version <= 26)
119
+ return widths[1];
120
+ return widths[2];
129
121
  }
130
-
131
122
  /**
132
123
  * Centre coordinates of the alignment patterns for a version.
133
124
  *
@@ -141,24 +132,22 @@ export function countBits(mode, version) {
141
132
  * @returns {number[]} Ascending centres. Empty for version 1.
142
133
  */
143
134
  export function alignmentCoordinates(version) {
144
- if (version < 2) return [];
145
-
146
- const size = versionSize(version);
147
- const count = Math.floor(version / 7) + 2;
148
- const last = size - 7;
149
-
150
- // Spacing is rounded up to an even number of modules so every centre lands on
151
- // the same parity as the timing pattern, which is what keeps the patterns
152
- // aligned with the module grid rather than straddling it.
153
- const step = Math.ceil((size - 13) / (2 * count - 2)) * 2;
154
-
155
- const coords = [6];
156
- // Walk backwards from the final centre so the slack lands in the first gap.
157
- for (let i = count - 1; i >= 1; i--) coords.push(last - (count - 1 - i) * step);
158
- coords.sort((a, b) => a - b);
159
- return coords;
135
+ if (version < 2)
136
+ return [];
137
+ const size = versionSize(version);
138
+ const count = Math.floor(version / 7) + 2;
139
+ const last = size - 7;
140
+ // Spacing is rounded up to an even number of modules so every centre lands on
141
+ // the same parity as the timing pattern, which is what keeps the patterns
142
+ // aligned with the module grid rather than straddling it.
143
+ const step = Math.ceil((size - 13) / (2 * count - 2)) * 2;
144
+ const coords = [6];
145
+ // Walk backwards from the final centre so the slack lands in the first gap.
146
+ for (let i = count - 1; i >= 1; i--)
147
+ coords.push(last - (count - 1 - i) * step);
148
+ coords.sort((a, b) => a - b);
149
+ return coords;
160
150
  }
161
-
162
151
  /**
163
152
  * Centres of the alignment patterns actually drawn, as [x, y] pairs.
164
153
  *
@@ -168,29 +157,30 @@ export function alignmentCoordinates(version) {
168
157
  * @returns {Array<[number, number]>}
169
158
  */
170
159
  export function alignmentCentres(version) {
171
- const coords = alignmentCoordinates(version);
172
- if (coords.length === 0) return [];
173
-
174
- const size = versionSize(version);
175
- const lo = 6;
176
- const hi = size - 7;
177
- const out = [];
178
- for (let i = 0; i < coords.length; i++) {
179
- for (let j = 0; j < coords.length; j++) {
180
- const x = coords[j];
181
- const y = coords[i];
182
- // Skip the three finder corners.
183
- if (x === lo && y === lo) continue;
184
- if (x === lo && y === hi) continue;
185
- if (x === hi && y === lo) continue;
186
- out.push([x, y]);
160
+ const coords = alignmentCoordinates(version);
161
+ if (coords.length === 0)
162
+ return [];
163
+ const size = versionSize(version);
164
+ const lo = 6;
165
+ const hi = size - 7;
166
+ const out = [];
167
+ for (let i = 0; i < coords.length; i++) {
168
+ for (let j = 0; j < coords.length; j++) {
169
+ const x = coords[j];
170
+ const y = coords[i];
171
+ // Skip the three finder corners.
172
+ if (x === lo && y === lo)
173
+ continue;
174
+ if (x === lo && y === hi)
175
+ continue;
176
+ if (x === hi && y === lo)
177
+ continue;
178
+ out.push([x, y]);
179
+ }
187
180
  }
188
- }
189
- return out;
181
+ return out;
190
182
  }
191
-
192
183
  const reservedCache = new Map();
193
-
194
184
  /**
195
185
  * Map of modules that carry function patterns rather than payload.
196
186
  *
@@ -209,51 +199,44 @@ const reservedCache = new Map();
209
199
  * @returns {BitMatrix} Shared, cached — treat as immutable.
210
200
  */
211
201
  export function reservedModules(version) {
212
- const cached = reservedCache.get(version);
213
- if (cached) return cached;
214
-
215
- const size = versionSize(version);
216
- const m = new BitMatrix(size, size);
217
-
218
- // Finder patterns with their separators: an 8x8 reserved block at each of
219
- // three corners (7x7 pattern plus a one-module light border on the inner
220
- // sides, which the corner blocks absorb).
221
- m.setRegion(0, 0, 8, 8);
222
- m.setRegion(size - 8, 0, 8, 8);
223
- m.setRegion(0, size - 8, 8, 8);
224
-
225
- // Timing patterns, spanning the gap between the separators.
226
- for (let i = 8; i < size - 8; i++) {
227
- m.set(i, 6);
228
- m.set(6, i);
229
- }
230
-
231
- // Alignment patterns, 5x5 each.
232
- const centres = alignmentCentres(version);
233
- for (let i = 0; i < centres.length; i++) {
234
- m.setRegion(centres[i][0] - 2, centres[i][1] - 2, 5, 5);
235
- }
236
-
237
- // Format information: two copies plus the dark module. The copies partly
238
- // fall inside the 8x8 finder blocks already reserved; setting them again is
239
- // harmless and keeps the intent explicit.
240
- const [copyA, copyB] = formatInfoPositions(size);
241
- for (let i = 0; i < 15; i++) {
242
- m.set(copyA[i][0], copyA[i][1]);
243
- m.set(copyB[i][0], copyB[i][1]);
244
- }
245
- m.set(8, size - 8); // dark module
246
-
247
- // Version information, two 6x3 blocks.
248
- if (version >= VERSION_INFO_MIN) {
249
- m.setRegion(size - 11, 0, 3, 6);
250
- m.setRegion(0, size - 11, 6, 3);
251
- }
252
-
253
- reservedCache.set(version, m);
254
- return m;
202
+ const cached = reservedCache.get(version);
203
+ if (cached)
204
+ return cached;
205
+ const size = versionSize(version);
206
+ const m = new BitMatrix(size, size);
207
+ // Finder patterns with their separators: an 8x8 reserved block at each of
208
+ // three corners (7x7 pattern plus a one-module light border on the inner
209
+ // sides, which the corner blocks absorb).
210
+ m.setRegion(0, 0, 8, 8);
211
+ m.setRegion(size - 8, 0, 8, 8);
212
+ m.setRegion(0, size - 8, 8, 8);
213
+ // Timing patterns, spanning the gap between the separators.
214
+ for (let i = 8; i < size - 8; i++) {
215
+ m.set(i, 6);
216
+ m.set(6, i);
217
+ }
218
+ // Alignment patterns, 5x5 each.
219
+ const centres = alignmentCentres(version);
220
+ for (let i = 0; i < centres.length; i++) {
221
+ m.setRegion(centres[i][0] - 2, centres[i][1] - 2, 5, 5);
222
+ }
223
+ // Format information: two copies plus the dark module. The copies partly
224
+ // fall inside the 8x8 finder blocks already reserved; setting them again is
225
+ // harmless and keeps the intent explicit.
226
+ const [copyA, copyB] = formatInfoPositions(size);
227
+ for (let i = 0; i < 15; i++) {
228
+ m.set(copyA[i][0], copyA[i][1]);
229
+ m.set(copyB[i][0], copyB[i][1]);
230
+ }
231
+ m.set(8, size - 8); // dark module
232
+ // Version information, two 6x3 blocks.
233
+ if (version >= VERSION_INFO_MIN) {
234
+ m.setRegion(size - 11, 0, 3, 6);
235
+ m.setRegion(0, size - 11, 6, 3);
236
+ }
237
+ reservedCache.set(version, m);
238
+ return m;
255
239
  }
256
-
257
240
  /**
258
241
  * Module positions of the two format information copies.
259
242
  *
@@ -271,29 +254,32 @@ export function reservedModules(version) {
271
254
  * @returns {[Array<[number, number]>, Array<[number, number]>]} [copyA, copyB]
272
255
  */
273
256
  export function formatInfoPositions(size) {
274
- /** @type {Array<[number, number]>} */
275
- const a = [];
276
- /** @type {Array<[number, number]>} */
277
- const b = [];
278
-
279
- for (let i = 0; i < 15; i++) {
280
- // Copy A wraps the top-left finder: down column 8, then left along row 8,
281
- // stepping over the two timing modules.
282
- if (i < 6) a.push([8, i]);
283
- else if (i === 6) a.push([8, 7]);
284
- else if (i === 7) a.push([8, 8]);
285
- else if (i === 8) a.push([7, 8]);
286
- else a.push([14 - i, 8]);
287
-
288
- // Copy B is split: the low bits run right-to-left along row 8 beside the
289
- // top-right finder, the high bits run bottom-up beside the bottom-left one.
290
- if (i < 8) b.push([size - 1 - i, 8]);
291
- else b.push([8, size - 15 + i]);
292
- }
293
-
294
- return [a, b];
257
+ /** @type {Array<[number, number]>} */
258
+ const a = [];
259
+ /** @type {Array<[number, number]>} */
260
+ const b = [];
261
+ for (let i = 0; i < 15; i++) {
262
+ // Copy A wraps the top-left finder: down column 8, then left along row 8,
263
+ // stepping over the two timing modules.
264
+ if (i < 6)
265
+ a.push([8, i]);
266
+ else if (i === 6)
267
+ a.push([8, 7]);
268
+ else if (i === 7)
269
+ a.push([8, 8]);
270
+ else if (i === 8)
271
+ a.push([7, 8]);
272
+ else
273
+ a.push([14 - i, 8]);
274
+ // Copy B is split: the low bits run right-to-left along row 8 beside the
275
+ // top-right finder, the high bits run bottom-up beside the bottom-left one.
276
+ if (i < 8)
277
+ b.push([size - 1 - i, 8]);
278
+ else
279
+ b.push([8, size - 15 + i]);
280
+ }
281
+ return [a, b];
295
282
  }
296
-
297
283
  /**
298
284
  * Modules available to data and error correction, counted off the grid.
299
285
  *
@@ -301,17 +287,17 @@ export function formatInfoPositions(size) {
301
287
  * @returns {number}
302
288
  */
303
289
  export function freeModuleCount(version) {
304
- const reserved = reservedModules(version);
305
- const size = versionSize(version);
306
- let free = 0;
307
- for (let y = 0; y < size; y++) {
308
- for (let x = 0; x < size; x++) {
309
- if (!reserved.get(x, y)) free++;
290
+ const reserved = reservedModules(version);
291
+ const size = versionSize(version);
292
+ let free = 0;
293
+ for (let y = 0; y < size; y++) {
294
+ for (let x = 0; x < size; x++) {
295
+ if (!reserved.get(x, y))
296
+ free++;
297
+ }
310
298
  }
311
- }
312
- return free;
299
+ return free;
313
300
  }
314
-
315
301
  /**
316
302
  * Total codewords (data + error correction) a version holds.
317
303
  *
@@ -322,9 +308,8 @@ export function freeModuleCount(version) {
322
308
  * @returns {number}
323
309
  */
324
310
  export function geometricTotalCodewords(version) {
325
- return Math.floor(freeModuleCount(version) / 8);
311
+ return Math.floor(freeModuleCount(version) / 8);
326
312
  }
327
-
328
313
  /**
329
314
  * Bits left over after the last whole codeword, written as zeroes.
330
315
  *
@@ -332,11 +317,9 @@ export function geometricTotalCodewords(version) {
332
317
  * @returns {number} 0, 3, 4 or 7.
333
318
  */
334
319
  export function remainderBits(version) {
335
- return freeModuleCount(version) % 8;
320
+ return freeModuleCount(version) % 8;
336
321
  }
337
-
338
322
  const orderCache = new Map();
339
-
340
323
  /**
341
324
  * Module positions in bitstream order, as interleaved x, y pairs.
342
325
  *
@@ -354,38 +337,37 @@ const orderCache = new Map();
354
337
  * `2 * freeModuleCount(version)`.
355
338
  */
356
339
  export function dataModuleOrder(version) {
357
- const cached = orderCache.get(version);
358
- if (cached) return cached;
359
-
360
- const size = versionSize(version);
361
- const reserved = reservedModules(version);
362
- const out = new Int32Array(freeModuleCount(version) * 2);
363
- let n = 0;
364
-
365
- let upward = true;
366
- for (let col = size - 1; col > 0; col -= 2) {
367
- // Column 6 is the vertical timing pattern. Stepping over it shifts the
368
- // whole remaining schedule left by one, so the loop variable itself has to
369
- // move adjusting only the current pair would visit column 4 twice and
370
- // column 0 never, which is self-consistent between encoder and decoder and
371
- // therefore invisible to a round-trip test.
372
- if (col === 6) col--;
373
- for (let i = 0; i < size; i++) {
374
- const y = upward ? size - 1 - i : i;
375
- for (let c = 0; c < 2; c++) {
376
- const x = col - c;
377
- if (reserved.get(x, y)) continue;
378
- out[n++] = x;
379
- out[n++] = y;
380
- }
340
+ const cached = orderCache.get(version);
341
+ if (cached)
342
+ return cached;
343
+ const size = versionSize(version);
344
+ const reserved = reservedModules(version);
345
+ const out = new Int32Array(freeModuleCount(version) * 2);
346
+ let n = 0;
347
+ let upward = true;
348
+ for (let col = size - 1; col > 0; col -= 2) {
349
+ // Column 6 is the vertical timing pattern. Stepping over it shifts the
350
+ // whole remaining schedule left by one, so the loop variable itself has to
351
+ // move adjusting only the current pair would visit column 4 twice and
352
+ // column 0 never, which is self-consistent between encoder and decoder and
353
+ // therefore invisible to a round-trip test.
354
+ if (col === 6)
355
+ col--;
356
+ for (let i = 0; i < size; i++) {
357
+ const y = upward ? size - 1 - i : i;
358
+ for (let c = 0; c < 2; c++) {
359
+ const x = col - c;
360
+ if (reserved.get(x, y))
361
+ continue;
362
+ out[n++] = x;
363
+ out[n++] = y;
364
+ }
365
+ }
366
+ upward = !upward;
381
367
  }
382
- upward = !upward;
383
- }
384
-
385
- orderCache.set(version, out);
386
- return out;
368
+ orderCache.set(version, out);
369
+ return out;
387
370
  }
388
-
389
371
  /**
390
372
  * The eight data mask predicates.
391
373
  *
@@ -399,20 +381,19 @@ export function dataModuleOrder(version) {
399
381
  * @returns {boolean}
400
382
  */
401
383
  export function maskBit(mask, x, y) {
402
- switch (mask) {
403
- case 0: return ((y + x) & 1) === 0;
404
- case 1: return (y & 1) === 0;
405
- case 2: return x % 3 === 0;
406
- case 3: return (y + x) % 3 === 0;
407
- case 4: return (((y >> 1) + Math.floor(x / 3)) & 1) === 0;
408
- case 5: return ((y * x) & 1) + ((y * x) % 3) === 0;
409
- case 6: return ((((y * x) & 1) + ((y * x) % 3)) & 1) === 0;
410
- case 7: return ((((y + x) & 1) + ((y * x) % 3)) & 1) === 0;
411
- default:
412
- throw new RangeError(`QR: mask must be 0-7, got ${mask}`);
413
- }
384
+ switch (mask) {
385
+ case 0: return ((y + x) & 1) === 0;
386
+ case 1: return (y & 1) === 0;
387
+ case 2: return x % 3 === 0;
388
+ case 3: return (y + x) % 3 === 0;
389
+ case 4: return (((y >> 1) + Math.floor(x / 3)) & 1) === 0;
390
+ case 5: return ((y * x) & 1) + ((y * x) % 3) === 0;
391
+ case 6: return ((((y * x) & 1) + ((y * x) % 3)) & 1) === 0;
392
+ case 7: return ((((y + x) & 1) + ((y * x) % 3)) & 1) === 0;
393
+ default:
394
+ throw new RangeError(`QR: mask must be 0-7, got ${mask}`);
395
+ }
414
396
  }
415
-
416
397
  /**
417
398
  * Error correction parameters, indexed `[version - 1]` then by level.
418
399
  *
@@ -425,48 +406,47 @@ export function maskBit(mask, x, y) {
425
406
  * @type {Array<{L: number[], M: number[], Q: number[], H: number[]}>}
426
407
  */
427
408
  const ECC_TABLE = [
428
- /* 1 */ { L: [7, 1, 19], M: [10, 1, 16], Q: [13, 1, 13], H: [17, 1, 9] },
429
- /* 2 */ { L: [10, 1, 34], M: [16, 1, 28], Q: [22, 1, 22], H: [28, 1, 16] },
430
- /* 3 */ { L: [15, 1, 55], M: [26, 1, 44], Q: [18, 2, 34], H: [22, 2, 26] },
431
- /* 4 */ { L: [20, 1, 80], M: [18, 2, 64], Q: [26, 2, 48], H: [16, 4, 36] },
432
- /* 5 */ { L: [26, 1, 108], M: [24, 2, 86], Q: [18, 4, 62], H: [22, 4, 46] },
433
- /* 6 */ { L: [18, 2, 136], M: [16, 4, 108], Q: [24, 4, 76], H: [28, 4, 60] },
434
- /* 7 */ { L: [20, 2, 156], M: [18, 4, 124], Q: [18, 6, 88], H: [26, 5, 66] },
435
- /* 8 */ { L: [24, 2, 194], M: [22, 4, 154], Q: [22, 6, 110], H: [26, 6, 86] },
436
- /* 9 */ { L: [30, 2, 232], M: [22, 5, 182], Q: [20, 8, 132], H: [24, 8, 100] },
437
- /* 10 */ { L: [18, 4, 274], M: [26, 5, 216], Q: [24, 8, 154], H: [28, 8, 122] },
438
- /* 11 */ { L: [20, 4, 324], M: [30, 5, 254], Q: [28, 8, 180], H: [24, 11, 140] },
439
- /* 12 */ { L: [24, 4, 370], M: [22, 8, 290], Q: [26, 10, 206], H: [28, 11, 158] },
440
- /* 13 */ { L: [26, 4, 428], M: [22, 9, 334], Q: [24, 12, 244], H: [22, 16, 180] },
441
- /* 14 */ { L: [30, 4, 461], M: [24, 9, 365], Q: [20, 16, 261], H: [24, 16, 197] },
442
- /* 15 */ { L: [22, 6, 523], M: [24, 10, 415], Q: [30, 12, 295], H: [24, 18, 223] },
443
- /* 16 */ { L: [24, 6, 589], M: [28, 10, 453], Q: [24, 17, 325], H: [30, 16, 253] },
444
- /* 17 */ { L: [28, 6, 647], M: [28, 11, 507], Q: [28, 16, 367], H: [28, 19, 283] },
445
- /* 18 */ { L: [30, 6, 721], M: [26, 13, 563], Q: [28, 18, 397], H: [28, 21, 313] },
446
- /* 19 */ { L: [28, 7, 795], M: [26, 14, 627], Q: [26, 21, 445], H: [26, 25, 341] },
447
- /* 20 */ { L: [28, 8, 861], M: [26, 16, 669], Q: [30, 20, 485], H: [28, 25, 385] },
448
- /* 21 */ { L: [28, 8, 932], M: [26, 17, 714], Q: [28, 23, 512], H: [30, 25, 406] },
449
- /* 22 */ { L: [28, 9, 1006], M: [28, 17, 782], Q: [30, 23, 568], H: [24, 34, 442] },
450
- /* 23 */ { L: [30, 9, 1094], M: [28, 18, 860], Q: [30, 25, 614], H: [30, 30, 464] },
451
- /* 24 */ { L: [30, 10, 1174], M: [28, 20, 914], Q: [30, 27, 664], H: [30, 32, 514] },
452
- /* 25 */ { L: [26, 12, 1276], M: [28, 21, 1000], Q: [30, 29, 718], H: [30, 35, 538] },
453
- /* 26 */ { L: [28, 12, 1370], M: [28, 23, 1062], Q: [28, 34, 754], H: [30, 37, 596] },
454
- /* 27 */ { L: [30, 12, 1468], M: [28, 25, 1128], Q: [30, 34, 808], H: [30, 40, 628] },
455
- /* 28 */ { L: [30, 13, 1531], M: [28, 26, 1193], Q: [30, 35, 871], H: [30, 42, 661] },
456
- /* 29 */ { L: [30, 14, 1631], M: [28, 28, 1267], Q: [30, 38, 911], H: [30, 45, 701] },
457
- /* 30 */ { L: [30, 15, 1735], M: [28, 29, 1373], Q: [30, 40, 985], H: [30, 48, 745] },
458
- /* 31 */ { L: [30, 16, 1843], M: [28, 31, 1455], Q: [30, 43, 1033], H: [30, 51, 793] },
459
- /* 32 */ { L: [30, 17, 1955], M: [28, 33, 1541], Q: [30, 45, 1115], H: [30, 54, 845] },
460
- /* 33 */ { L: [30, 18, 2071], M: [28, 35, 1631], Q: [30, 48, 1171], H: [30, 57, 901] },
461
- /* 34 */ { L: [30, 19, 2191], M: [28, 37, 1725], Q: [30, 51, 1231], H: [30, 60, 961] },
462
- /* 35 */ { L: [30, 19, 2306], M: [28, 38, 1812], Q: [30, 53, 1286], H: [30, 63, 986] },
463
- /* 36 */ { L: [30, 20, 2434], M: [28, 40, 1914], Q: [30, 56, 1354], H: [30, 66, 1054] },
464
- /* 37 */ { L: [30, 21, 2566], M: [28, 43, 1992], Q: [30, 59, 1426], H: [30, 70, 1096] },
465
- /* 38 */ { L: [30, 22, 2702], M: [28, 45, 2102], Q: [30, 62, 1502], H: [30, 74, 1142] },
466
- /* 39 */ { L: [30, 24, 2812], M: [28, 47, 2216], Q: [30, 65, 1582], H: [30, 77, 1222] },
467
- /* 40 */ { L: [30, 25, 2956], M: [28, 49, 2334], Q: [30, 68, 1666], H: [30, 81, 1276] },
409
+ /* 1 */ { L: [7, 1, 19], M: [10, 1, 16], Q: [13, 1, 13], H: [17, 1, 9] },
410
+ /* 2 */ { L: [10, 1, 34], M: [16, 1, 28], Q: [22, 1, 22], H: [28, 1, 16] },
411
+ /* 3 */ { L: [15, 1, 55], M: [26, 1, 44], Q: [18, 2, 34], H: [22, 2, 26] },
412
+ /* 4 */ { L: [20, 1, 80], M: [18, 2, 64], Q: [26, 2, 48], H: [16, 4, 36] },
413
+ /* 5 */ { L: [26, 1, 108], M: [24, 2, 86], Q: [18, 4, 62], H: [22, 4, 46] },
414
+ /* 6 */ { L: [18, 2, 136], M: [16, 4, 108], Q: [24, 4, 76], H: [28, 4, 60] },
415
+ /* 7 */ { L: [20, 2, 156], M: [18, 4, 124], Q: [18, 6, 88], H: [26, 5, 66] },
416
+ /* 8 */ { L: [24, 2, 194], M: [22, 4, 154], Q: [22, 6, 110], H: [26, 6, 86] },
417
+ /* 9 */ { L: [30, 2, 232], M: [22, 5, 182], Q: [20, 8, 132], H: [24, 8, 100] },
418
+ /* 10 */ { L: [18, 4, 274], M: [26, 5, 216], Q: [24, 8, 154], H: [28, 8, 122] },
419
+ /* 11 */ { L: [20, 4, 324], M: [30, 5, 254], Q: [28, 8, 180], H: [24, 11, 140] },
420
+ /* 12 */ { L: [24, 4, 370], M: [22, 8, 290], Q: [26, 10, 206], H: [28, 11, 158] },
421
+ /* 13 */ { L: [26, 4, 428], M: [22, 9, 334], Q: [24, 12, 244], H: [22, 16, 180] },
422
+ /* 14 */ { L: [30, 4, 461], M: [24, 9, 365], Q: [20, 16, 261], H: [24, 16, 197] },
423
+ /* 15 */ { L: [22, 6, 523], M: [24, 10, 415], Q: [30, 12, 295], H: [24, 18, 223] },
424
+ /* 16 */ { L: [24, 6, 589], M: [28, 10, 453], Q: [24, 17, 325], H: [30, 16, 253] },
425
+ /* 17 */ { L: [28, 6, 647], M: [28, 11, 507], Q: [28, 16, 367], H: [28, 19, 283] },
426
+ /* 18 */ { L: [30, 6, 721], M: [26, 13, 563], Q: [28, 18, 397], H: [28, 21, 313] },
427
+ /* 19 */ { L: [28, 7, 795], M: [26, 14, 627], Q: [26, 21, 445], H: [26, 25, 341] },
428
+ /* 20 */ { L: [28, 8, 861], M: [26, 16, 669], Q: [30, 20, 485], H: [28, 25, 385] },
429
+ /* 21 */ { L: [28, 8, 932], M: [26, 17, 714], Q: [28, 23, 512], H: [30, 25, 406] },
430
+ /* 22 */ { L: [28, 9, 1006], M: [28, 17, 782], Q: [30, 23, 568], H: [24, 34, 442] },
431
+ /* 23 */ { L: [30, 9, 1094], M: [28, 18, 860], Q: [30, 25, 614], H: [30, 30, 464] },
432
+ /* 24 */ { L: [30, 10, 1174], M: [28, 20, 914], Q: [30, 27, 664], H: [30, 32, 514] },
433
+ /* 25 */ { L: [26, 12, 1276], M: [28, 21, 1000], Q: [30, 29, 718], H: [30, 35, 538] },
434
+ /* 26 */ { L: [28, 12, 1370], M: [28, 23, 1062], Q: [28, 34, 754], H: [30, 37, 596] },
435
+ /* 27 */ { L: [30, 12, 1468], M: [28, 25, 1128], Q: [30, 34, 808], H: [30, 40, 628] },
436
+ /* 28 */ { L: [30, 13, 1531], M: [28, 26, 1193], Q: [30, 35, 871], H: [30, 42, 661] },
437
+ /* 29 */ { L: [30, 14, 1631], M: [28, 28, 1267], Q: [30, 38, 911], H: [30, 45, 701] },
438
+ /* 30 */ { L: [30, 15, 1735], M: [28, 29, 1373], Q: [30, 40, 985], H: [30, 48, 745] },
439
+ /* 31 */ { L: [30, 16, 1843], M: [28, 31, 1455], Q: [30, 43, 1033], H: [30, 51, 793] },
440
+ /* 32 */ { L: [30, 17, 1955], M: [28, 33, 1541], Q: [30, 45, 1115], H: [30, 54, 845] },
441
+ /* 33 */ { L: [30, 18, 2071], M: [28, 35, 1631], Q: [30, 48, 1171], H: [30, 57, 901] },
442
+ /* 34 */ { L: [30, 19, 2191], M: [28, 37, 1725], Q: [30, 51, 1231], H: [30, 60, 961] },
443
+ /* 35 */ { L: [30, 19, 2306], M: [28, 38, 1812], Q: [30, 53, 1286], H: [30, 63, 986] },
444
+ /* 36 */ { L: [30, 20, 2434], M: [28, 40, 1914], Q: [30, 56, 1354], H: [30, 66, 1054] },
445
+ /* 37 */ { L: [30, 21, 2566], M: [28, 43, 1992], Q: [30, 59, 1426], H: [30, 70, 1096] },
446
+ /* 38 */ { L: [30, 22, 2702], M: [28, 45, 2102], Q: [30, 62, 1502], H: [30, 74, 1142] },
447
+ /* 39 */ { L: [30, 24, 2812], M: [28, 47, 2216], Q: [30, 65, 1582], H: [30, 77, 1222] },
448
+ /* 40 */ { L: [30, 25, 2956], M: [28, 49, 2334], Q: [30, 68, 1666], H: [30, 81, 1276] },
468
449
  ];
469
-
470
450
  /**
471
451
  * @typedef {object} BlockLayout
472
452
  * @property {number} version
@@ -481,7 +461,6 @@ const ECC_TABLE = [
481
461
  * @property {number} group2DataCount
482
462
  * @property {number} remainderBits
483
463
  */
484
-
485
464
  /**
486
465
  * Block structure for a (version, level).
487
466
  *
@@ -495,34 +474,31 @@ const ECC_TABLE = [
495
474
  * @returns {BlockLayout}
496
475
  */
497
476
  export function blockLayout(version, ecc) {
498
- if (version < MIN_VERSION || version > MAX_VERSION || (version | 0) !== version) {
499
- throw new RangeError(`QR: version must be an integer 1-40, got ${version}`);
500
- }
501
- const entry = ECC_TABLE[version - 1][ecc];
502
- if (!entry) throw new RangeError(`QR: unknown error correction level "${ecc}"`);
503
-
504
- const eccPerBlock = entry[0];
505
- const blockCount = entry[1];
506
- const totalDataCodewords = entry[2];
507
-
508
- const base = Math.floor(totalDataCodewords / blockCount);
509
- const extra = totalDataCodewords % blockCount;
510
-
511
- return {
512
- version,
513
- ecc,
514
- totalCodewords: totalDataCodewords + eccPerBlock * blockCount,
515
- totalDataCodewords,
516
- eccPerBlock,
517
- blockCount,
518
- group1Blocks: blockCount - extra,
519
- group1DataCount: base,
520
- group2Blocks: extra,
521
- group2DataCount: base + 1,
522
- remainderBits: remainderBits(version),
523
- };
477
+ if (version < MIN_VERSION || version > MAX_VERSION || (version | 0) !== version) {
478
+ throw new RangeError(`QR: version must be an integer 1-40, got ${version}`);
479
+ }
480
+ const entry = ECC_TABLE[version - 1][ecc];
481
+ if (!entry)
482
+ throw new RangeError(`QR: unknown error correction level "${ecc}"`);
483
+ const eccPerBlock = entry[0];
484
+ const blockCount = entry[1];
485
+ const totalDataCodewords = entry[2];
486
+ const base = Math.floor(totalDataCodewords / blockCount);
487
+ const extra = totalDataCodewords % blockCount;
488
+ return {
489
+ version,
490
+ ecc,
491
+ totalCodewords: totalDataCodewords + eccPerBlock * blockCount,
492
+ totalDataCodewords,
493
+ eccPerBlock,
494
+ blockCount,
495
+ group1Blocks: blockCount - extra,
496
+ group1DataCount: base,
497
+ group2Blocks: extra,
498
+ group2DataCount: base + 1,
499
+ remainderBits: remainderBits(version),
500
+ };
524
501
  }
525
-
526
502
  /**
527
503
  * Data capacity in codewords.
528
504
  *
@@ -531,9 +507,8 @@ export function blockLayout(version, ecc) {
531
507
  * @returns {number}
532
508
  */
533
509
  export function dataCodewords(version, ecc) {
534
- return ECC_TABLE[version - 1][ecc][2];
510
+ return ECC_TABLE[version - 1][ecc][2];
535
511
  }
536
-
537
512
  /**
538
513
  * Data capacity in bits.
539
514
  *
@@ -542,12 +517,10 @@ export function dataCodewords(version, ecc) {
542
517
  * @returns {number}
543
518
  */
544
519
  export function dataBitCapacity(version, ecc) {
545
- return dataCodewords(version, ecc) * 8;
520
+ return dataCodewords(version, ecc) * 8;
546
521
  }
547
-
548
522
  /** Error correction codewords per block are drawn from this set and no other. */
549
523
  const VALID_ECC_PER_BLOCK = [7, 10, 13, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30];
550
-
551
524
  /**
552
525
  * Remainder bits by version band, from the spec. Independent of anything the
553
526
  * ECC table says, so it validates the *geometry* — chiefly the alignment
@@ -557,15 +530,20 @@ const VALID_ECC_PER_BLOCK = [7, 10, 13, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30];
557
530
  * @returns {number}
558
531
  */
559
532
  function expectedRemainderBits(version) {
560
- if (version === 1) return 0;
561
- if (version <= 6) return 7;
562
- if (version <= 13) return 0;
563
- if (version <= 20) return 3;
564
- if (version <= 27) return 4;
565
- if (version <= 34) return 3;
566
- return 0;
533
+ if (version === 1)
534
+ return 0;
535
+ if (version <= 6)
536
+ return 7;
537
+ if (version <= 13)
538
+ return 0;
539
+ if (version <= 20)
540
+ return 3;
541
+ if (version <= 27)
542
+ return 4;
543
+ if (version <= 34)
544
+ return 3;
545
+ return 0;
567
546
  }
568
-
569
547
  /**
570
548
  * Self-check every table in this file.
571
549
  *
@@ -578,160 +556,135 @@ function expectedRemainderBits(version) {
578
556
  * @returns {string[]} Human-readable problems; empty means everything holds.
579
557
  */
580
558
  export function validateTables() {
581
- const problems = [];
582
-
583
- for (let version = MIN_VERSION; version <= MAX_VERSION; version++) {
584
- const size = versionSize(version);
585
- const total = geometricTotalCodewords(version);
586
-
587
- // --- Geometry ------------------------------------------------------
588
- const rem = remainderBits(version);
589
- if (rem !== expectedRemainderBits(version)) {
590
- problems.push(
591
- `v${version}: remainder bits ${rem}, expected ${expectedRemainderBits(version)} ` +
592
- `(free modules ${freeModuleCount(version)}) — check the alignment spacing rule`
593
- );
594
- }
595
-
596
- // --- Placement order ----------------------------------------------
597
- // Every free module must be visited exactly once. A placement order that
598
- // skips one module and visits another twice is still perfectly
599
- // self-consistent — the encoder writes and the decoder reads the same
600
- // wrong sequence — so a round-trip test cannot see it. What it does is
601
- // silently burn error correction budget on every symbol produced. This is
602
- // the only check that catches it.
603
- const order = dataModuleOrder(version);
604
- const free = freeModuleCount(version);
605
- if (order.length !== free * 2) {
606
- problems.push(
607
- `v${version}: placement order visits ${order.length / 2} modules, expected ${free}`
608
- );
609
- } else {
610
- const seen = new Uint8Array(size * size);
611
- let duplicates = 0;
612
- let reservedHits = 0;
613
- const reserved = reservedModules(version);
614
- for (let p = 0; p < order.length; p += 2) {
615
- const x = order[p];
616
- const y = order[p + 1];
617
- if (reserved.get(x, y)) reservedHits++;
618
- if (seen[y * size + x]++) duplicates++;
619
- }
620
- if (duplicates > 0) {
621
- problems.push(`v${version}: placement order visits ${duplicates} modules more than once`);
622
- }
623
- if (reservedHits > 0) {
624
- problems.push(`v${version}: placement order includes ${reservedHits} function modules`);
625
- }
626
- }
627
-
628
- // --- Alignment coordinates ----------------------------------------
629
- const coords = alignmentCoordinates(version);
630
- if (version === 1) {
631
- if (coords.length !== 0) problems.push(`v1: expected no alignment coordinates, got ${coords.length}`);
632
- } else {
633
- const expectedCount = Math.floor(version / 7) + 2;
634
- if (coords.length !== expectedCount) {
635
- problems.push(`v${version}: ${coords.length} alignment coordinates, expected ${expectedCount}`);
636
- }
637
- if (coords[0] !== 6) {
638
- problems.push(`v${version}: first alignment coordinate ${coords[0]}, expected 6`);
639
- }
640
- if (coords[coords.length - 1] !== size - 7) {
641
- problems.push(
642
- `v${version}: last alignment coordinate ${coords[coords.length - 1]}, expected ${size - 7}`
643
- );
644
- }
645
- for (let i = 1; i < coords.length; i++) {
646
- if (coords[i] <= coords[i - 1]) {
647
- problems.push(`v${version}: alignment coordinates not strictly increasing at index ${i}`);
559
+ const problems = [];
560
+ for (let version = MIN_VERSION; version <= MAX_VERSION; version++) {
561
+ const size = versionSize(version);
562
+ const total = geometricTotalCodewords(version);
563
+ // --- Geometry ------------------------------------------------------
564
+ const rem = remainderBits(version);
565
+ if (rem !== expectedRemainderBits(version)) {
566
+ problems.push(`v${version}: remainder bits ${rem}, expected ${expectedRemainderBits(version)} ` +
567
+ `(free modules ${freeModuleCount(version)}) — check the alignment spacing rule`);
648
568
  }
649
- }
650
- if (coords.length >= 3) {
651
- // Every gap after the first must be identical, and the first gap must
652
- // not exceed it — the slack is absorbed at the start, never the end.
653
- const step = coords[2] - coords[1];
654
- for (let i = 3; i < coords.length; i++) {
655
- if (coords[i] - coords[i - 1] !== step) {
656
- problems.push(
657
- `v${version}: alignment gap ${coords[i] - coords[i - 1]} at index ${i}, expected ${step}`
658
- );
659
- }
569
+ // --- Placement order ----------------------------------------------
570
+ // Every free module must be visited exactly once. A placement order that
571
+ // skips one module and visits another twice is still perfectly
572
+ // self-consistent — the encoder writes and the decoder reads the same
573
+ // wrong sequence so a round-trip test cannot see it. What it does is
574
+ // silently burn error correction budget on every symbol produced. This is
575
+ // the only check that catches it.
576
+ const order = dataModuleOrder(version);
577
+ const free = freeModuleCount(version);
578
+ if (order.length !== free * 2) {
579
+ problems.push(`v${version}: placement order visits ${order.length / 2} modules, expected ${free}`);
660
580
  }
661
- if (coords[1] - coords[0] > step) {
662
- problems.push(
663
- `v${version}: first alignment gap ${coords[1] - coords[0]} exceeds step ${step}`
664
- );
581
+ else {
582
+ const seen = new Uint8Array(size * size);
583
+ let duplicates = 0;
584
+ let reservedHits = 0;
585
+ const reserved = reservedModules(version);
586
+ for (let p = 0; p < order.length; p += 2) {
587
+ const x = order[p];
588
+ const y = order[p + 1];
589
+ if (reserved.get(x, y))
590
+ reservedHits++;
591
+ if (seen[y * size + x]++)
592
+ duplicates++;
593
+ }
594
+ if (duplicates > 0) {
595
+ problems.push(`v${version}: placement order visits ${duplicates} modules more than once`);
596
+ }
597
+ if (reservedHits > 0) {
598
+ problems.push(`v${version}: placement order includes ${reservedHits} function modules`);
599
+ }
665
600
  }
666
- if (step % 2 !== 0) {
667
- problems.push(`v${version}: alignment step ${step} is odd`);
601
+ // --- Alignment coordinates ----------------------------------------
602
+ const coords = alignmentCoordinates(version);
603
+ if (version === 1) {
604
+ if (coords.length !== 0)
605
+ problems.push(`v1: expected no alignment coordinates, got ${coords.length}`);
668
606
  }
669
- }
670
- }
671
-
672
- // --- Error correction table ---------------------------------------
673
- for (let l = 0; l < ECC_LEVELS.length; l++) {
674
- const ecc = ECC_LEVELS[l];
675
- const layout = blockLayout(version, ecc);
676
- const tag = `v${version}-${ecc}`;
677
-
678
- // THE identity. Everything else is a supporting check.
679
- if (layout.totalCodewords !== total) {
680
- problems.push(
681
- `${tag}: ${layout.blockCount} blocks x ${layout.eccPerBlock} ECC + ` +
682
- `${layout.totalDataCodewords} data = ${layout.totalCodewords} codewords, ` +
683
- `but the grid holds ${total}`
684
- );
685
- }
686
-
687
- if (VALID_ECC_PER_BLOCK.indexOf(layout.eccPerBlock) === -1) {
688
- problems.push(`${tag}: ${layout.eccPerBlock} ECC codewords per block is not a valid value`);
689
- }
690
- if (layout.blockCount < 1) {
691
- problems.push(`${tag}: block count ${layout.blockCount}`);
692
- }
693
- if (layout.group1DataCount < 1) {
694
- problems.push(`${tag}: ${layout.totalDataCodewords} data codewords across ${layout.blockCount} blocks`);
695
- }
696
- if (layout.group1Blocks + layout.group2Blocks !== layout.blockCount) {
697
- problems.push(`${tag}: group split does not sum to the block count`);
698
- }
699
- if (
700
- layout.group1Blocks * layout.group1DataCount +
701
- layout.group2Blocks * layout.group2DataCount !== layout.totalDataCodewords
702
- ) {
703
- problems.push(`${tag}: group sizes do not sum to the data codeword count`);
704
- }
705
- // Reed-Solomon over GF(256) cannot address a codeword longer than 255.
706
- if (layout.group2DataCount + layout.eccPerBlock > 255) {
707
- problems.push(
708
- `${tag}: block length ${layout.group2DataCount + layout.eccPerBlock} exceeds GF(256)`
709
- );
710
- }
711
-
712
- // Stronger correction must cost capacity, never gain it.
713
- if (l > 0) {
714
- const weaker = dataCodewords(version, ECC_LEVELS[l - 1]);
715
- if (layout.totalDataCodewords >= weaker) {
716
- problems.push(
717
- `${tag}: ${layout.totalDataCodewords} data codewords is not less than ` +
718
- `${ECC_LEVELS[l - 1]}'s ${weaker}`
719
- );
607
+ else {
608
+ const expectedCount = Math.floor(version / 7) + 2;
609
+ if (coords.length !== expectedCount) {
610
+ problems.push(`v${version}: ${coords.length} alignment coordinates, expected ${expectedCount}`);
611
+ }
612
+ if (coords[0] !== 6) {
613
+ problems.push(`v${version}: first alignment coordinate ${coords[0]}, expected 6`);
614
+ }
615
+ if (coords[coords.length - 1] !== size - 7) {
616
+ problems.push(`v${version}: last alignment coordinate ${coords[coords.length - 1]}, expected ${size - 7}`);
617
+ }
618
+ for (let i = 1; i < coords.length; i++) {
619
+ if (coords[i] <= coords[i - 1]) {
620
+ problems.push(`v${version}: alignment coordinates not strictly increasing at index ${i}`);
621
+ }
622
+ }
623
+ if (coords.length >= 3) {
624
+ // Every gap after the first must be identical, and the first gap must
625
+ // not exceed it — the slack is absorbed at the start, never the end.
626
+ const step = coords[2] - coords[1];
627
+ for (let i = 3; i < coords.length; i++) {
628
+ if (coords[i] - coords[i - 1] !== step) {
629
+ problems.push(`v${version}: alignment gap ${coords[i] - coords[i - 1]} at index ${i}, expected ${step}`);
630
+ }
631
+ }
632
+ if (coords[1] - coords[0] > step) {
633
+ problems.push(`v${version}: first alignment gap ${coords[1] - coords[0]} exceeds step ${step}`);
634
+ }
635
+ if (step % 2 !== 0) {
636
+ problems.push(`v${version}: alignment step ${step} is odd`);
637
+ }
638
+ }
720
639
  }
721
- }
722
-
723
- // Capacity must grow with version.
724
- if (version > MIN_VERSION) {
725
- const smaller = dataCodewords(version - 1, ecc);
726
- if (layout.totalDataCodewords <= smaller) {
727
- problems.push(
728
- `${tag}: ${layout.totalDataCodewords} data codewords is not more than ` +
729
- `v${version - 1}-${ecc}'s ${smaller}`
730
- );
640
+ // --- Error correction table ---------------------------------------
641
+ for (let l = 0; l < ECC_LEVELS.length; l++) {
642
+ const ecc = ECC_LEVELS[l];
643
+ const layout = blockLayout(version, ecc);
644
+ const tag = `v${version}-${ecc}`;
645
+ // THE identity. Everything else is a supporting check.
646
+ if (layout.totalCodewords !== total) {
647
+ problems.push(`${tag}: ${layout.blockCount} blocks x ${layout.eccPerBlock} ECC + ` +
648
+ `${layout.totalDataCodewords} data = ${layout.totalCodewords} codewords, ` +
649
+ `but the grid holds ${total}`);
650
+ }
651
+ if (VALID_ECC_PER_BLOCK.indexOf(layout.eccPerBlock) === -1) {
652
+ problems.push(`${tag}: ${layout.eccPerBlock} ECC codewords per block is not a valid value`);
653
+ }
654
+ if (layout.blockCount < 1) {
655
+ problems.push(`${tag}: block count ${layout.blockCount}`);
656
+ }
657
+ if (layout.group1DataCount < 1) {
658
+ problems.push(`${tag}: ${layout.totalDataCodewords} data codewords across ${layout.blockCount} blocks`);
659
+ }
660
+ if (layout.group1Blocks + layout.group2Blocks !== layout.blockCount) {
661
+ problems.push(`${tag}: group split does not sum to the block count`);
662
+ }
663
+ if (layout.group1Blocks * layout.group1DataCount +
664
+ layout.group2Blocks * layout.group2DataCount !== layout.totalDataCodewords) {
665
+ problems.push(`${tag}: group sizes do not sum to the data codeword count`);
666
+ }
667
+ // Reed-Solomon over GF(256) cannot address a codeword longer than 255.
668
+ if (layout.group2DataCount + layout.eccPerBlock > 255) {
669
+ problems.push(`${tag}: block length ${layout.group2DataCount + layout.eccPerBlock} exceeds GF(256)`);
670
+ }
671
+ // Stronger correction must cost capacity, never gain it.
672
+ if (l > 0) {
673
+ const weaker = dataCodewords(version, ECC_LEVELS[l - 1]);
674
+ if (layout.totalDataCodewords >= weaker) {
675
+ problems.push(`${tag}: ${layout.totalDataCodewords} data codewords is not less than ` +
676
+ `${ECC_LEVELS[l - 1]}'s ${weaker}`);
677
+ }
678
+ }
679
+ // Capacity must grow with version.
680
+ if (version > MIN_VERSION) {
681
+ const smaller = dataCodewords(version - 1, ecc);
682
+ if (layout.totalDataCodewords <= smaller) {
683
+ problems.push(`${tag}: ${layout.totalDataCodewords} data codewords is not more than ` +
684
+ `v${version - 1}-${ecc}'s ${smaller}`);
685
+ }
686
+ }
731
687
  }
732
- }
733
688
  }
734
- }
735
-
736
- return problems;
689
+ return problems;
737
690
  }