@sythos/js_barcode_universal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +215 -0
- package/NOTICE.md +106 -0
- package/README.md +433 -0
- package/bundle/sythos-barcode.esm.js +7998 -0
- package/bundle/sythos-barcode.js +7948 -0
- package/examples/create.html +731 -0
- package/examples/read.html +341 -0
- package/licenses/README.md +42 -0
- package/licenses/codabar.license +74 -0
- package/licenses/code-11.license +69 -0
- package/licenses/code-128.license +69 -0
- package/licenses/code-39.license +70 -0
- package/licenses/code-93.license +71 -0
- package/licenses/ean-13.license +70 -0
- package/licenses/ean-8.license +70 -0
- package/licenses/gs1-128.license +71 -0
- package/licenses/isbn.license +76 -0
- package/licenses/itf-14.license +69 -0
- package/licenses/itf.license +70 -0
- package/licenses/msi-plessey.license +72 -0
- package/licenses/pharmacode.license +71 -0
- package/licenses/qr-code.license +75 -0
- package/licenses/upc-a.license +72 -0
- package/licenses/upc-e.license +69 -0
- package/package.json +89 -0
- package/src/core/bit-buffer.js +174 -0
- package/src/core/bit-matrix.js +241 -0
- package/src/core/errors.js +61 -0
- package/src/core/galois-field.js +204 -0
- package/src/core/index.js +56 -0
- package/src/core/reed-solomon.js +313 -0
- package/src/image/binarizer.js +270 -0
- package/src/image/grid-sampler.js +164 -0
- package/src/image/index.js +40 -0
- package/src/image/luminance.js +196 -0
- package/src/image/perspective.js +195 -0
- package/src/index.js +240 -0
- package/src/oned/index.js +89 -0
- package/src/oned/patterns.js +384 -0
- package/src/oned/reader.js +918 -0
- package/src/oned/writers.js +741 -0
- package/src/qr/decoder.js +575 -0
- package/src/qr/detector.js +630 -0
- package/src/qr/encoder.js +958 -0
- package/src/qr/index.js +44 -0
- package/src/qr/tables.js +737 -0
- package/src/render/image-data.js +125 -0
- package/src/render/index.js +130 -0
- package/src/render/options.js +160 -0
- package/src/render/png.js +295 -0
- package/src/render/svg.js +120 -0
- package/src/render/webgl.js +206 -0
- package/src/render/webgpu.js +369 -0
package/src/qr/tables.js
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
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
|
+
* QR Code structural tables.
|
|
33
|
+
*
|
|
34
|
+
* The design principle here is that as little as possible is *recalled* and as
|
|
35
|
+
* much as possible is *derived*, because a barcode table is the one place where
|
|
36
|
+
* a single mistyped digit produces a symbol that looks perfect and scans as
|
|
37
|
+
* garbage — or, worse, scans correctly for the payload you tested and fails for
|
|
38
|
+
* the payload your user sends.
|
|
39
|
+
*
|
|
40
|
+
* So:
|
|
41
|
+
*
|
|
42
|
+
* - Symbol size, function-pattern layout and total codeword capacity are
|
|
43
|
+
* computed from geometry. Nothing is tabulated that the module grid already
|
|
44
|
+
* knows.
|
|
45
|
+
* - Alignment centres come from the spec's spacing rule, not a 40-row table.
|
|
46
|
+
* - The group-1 / group-2 block split is arithmetic, not data.
|
|
47
|
+
*
|
|
48
|
+
* That leaves exactly three recalled numbers per (version, level): the error
|
|
49
|
+
* correction codewords per block, the block count, and the total data codeword
|
|
50
|
+
* count. Those three are deliberately redundant — they must satisfy
|
|
51
|
+
*
|
|
52
|
+
* blocks * eccPerBlock + totalDataCodewords === geometricTotalCodewords(v)
|
|
53
|
+
*
|
|
54
|
+
* for all 160 combinations, where the right-hand side is counted off the module
|
|
55
|
+
* grid. Any single typo on either side breaks the identity. {@link validateTables}
|
|
56
|
+
* enforces it, and the test suite asserts it returns no problems.
|
|
57
|
+
*
|
|
58
|
+
* @module qr/tables
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
62
|
+
|
|
63
|
+
/** Error correction levels, weakest to strongest. */
|
|
64
|
+
export const ECC_LEVELS = ['L', 'M', 'Q', 'H'];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Two-bit level indicator used in the format information.
|
|
68
|
+
* Note this is *not* the L/M/Q/H ordering — the spec assigns them out of order.
|
|
69
|
+
*/
|
|
70
|
+
export const ECC_LEVEL_BITS = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 };
|
|
71
|
+
|
|
72
|
+
/** Inverse of {@link ECC_LEVEL_BITS}, indexed by the 2-bit value. */
|
|
73
|
+
export const ECC_LEVEL_BY_BITS = ['M', 'L', 'H', 'Q'];
|
|
74
|
+
|
|
75
|
+
export const MIN_VERSION = 1;
|
|
76
|
+
export const MAX_VERSION = 40;
|
|
77
|
+
|
|
78
|
+
/** Version at and above which an 18-bit version information block is carried. */
|
|
79
|
+
export const VERSION_INFO_MIN = 7;
|
|
80
|
+
|
|
81
|
+
/** Mode indicator nibbles. */
|
|
82
|
+
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,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Character count indicator width, in bits, by mode and version band.
|
|
96
|
+
*
|
|
97
|
+
* The bands are versions 1-9, 10-26 and 27-40. They are the reason segment
|
|
98
|
+
* selection and version selection are mutually dependent: widening the count
|
|
99
|
+
* field can push a payload over a version boundary, which widens it again.
|
|
100
|
+
*/
|
|
101
|
+
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],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {number} version 1-40
|
|
110
|
+
* @returns {number} Modules per side.
|
|
111
|
+
*/
|
|
112
|
+
export function versionSize(version) {
|
|
113
|
+
return 17 + 4 * version;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Bits in the character count indicator.
|
|
118
|
+
*
|
|
119
|
+
* @param {number} mode One of {@link MODE}.
|
|
120
|
+
* @param {number} version
|
|
121
|
+
* @returns {number}
|
|
122
|
+
*/
|
|
123
|
+
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];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Centre coordinates of the alignment patterns for a version.
|
|
133
|
+
*
|
|
134
|
+
* The spec's rule: the first centre is always 6 and the last is always
|
|
135
|
+
* `size - 7`; the count grows by one every seven versions; and the centres are
|
|
136
|
+
* evenly spaced with the *first* gap absorbing the rounding slack. Expressing
|
|
137
|
+
* that as arithmetic rather than a 40-row table means there is no table to
|
|
138
|
+
* mistype, and {@link validateTables} can then assert the shape of the result.
|
|
139
|
+
*
|
|
140
|
+
* @param {number} version
|
|
141
|
+
* @returns {number[]} Ascending centres. Empty for version 1.
|
|
142
|
+
*/
|
|
143
|
+
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;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Centres of the alignment patterns actually drawn, as [x, y] pairs.
|
|
164
|
+
*
|
|
165
|
+
* The three combinations that would sit on top of a finder pattern are omitted.
|
|
166
|
+
*
|
|
167
|
+
* @param {number} version
|
|
168
|
+
* @returns {Array<[number, number]>}
|
|
169
|
+
*/
|
|
170
|
+
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]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const reservedCache = new Map();
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Map of modules that carry function patterns rather than payload.
|
|
196
|
+
*
|
|
197
|
+
* A set bit means "reserved": finder, separator, timing, alignment, format
|
|
198
|
+
* information, the dark module, and the version information blocks. This is the
|
|
199
|
+
* single source of truth used by the encoder to skip modules while laying out
|
|
200
|
+
* the bitstream, by the decoder to read them back in the same order, and by
|
|
201
|
+
* {@link geometricTotalCodewords} to count what is left.
|
|
202
|
+
*
|
|
203
|
+
* Deriving capacity this way rather than by hand arithmetic is what makes the
|
|
204
|
+
* awkward cases free: an alignment pattern that overlaps the timing pattern is
|
|
205
|
+
* counted once because it is the same set of modules, not because anyone
|
|
206
|
+
* remembered to subtract five.
|
|
207
|
+
*
|
|
208
|
+
* @param {number} version
|
|
209
|
+
* @returns {BitMatrix} Shared, cached — treat as immutable.
|
|
210
|
+
*/
|
|
211
|
+
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;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Module positions of the two format information copies.
|
|
259
|
+
*
|
|
260
|
+
* Index `i` in each array is bit `i` of the 15-bit format value, bit 0 being
|
|
261
|
+
* the least significant.
|
|
262
|
+
*
|
|
263
|
+
* CAVEAT WORTH READING: the *direction* of this numbering is the one thing in
|
|
264
|
+
* this file that a round-trip test cannot falsify. Encoder and decoder share
|
|
265
|
+
* these tables, so a mirrored layout would pass every test in the suite and
|
|
266
|
+
* fail only against a real scanner. The layout below is the standard one; both
|
|
267
|
+
* sides deliberately consume this single definition so there is no second place
|
|
268
|
+
* for the convention to drift.
|
|
269
|
+
*
|
|
270
|
+
* @param {number} size Modules per side.
|
|
271
|
+
* @returns {[Array<[number, number]>, Array<[number, number]>]} [copyA, copyB]
|
|
272
|
+
*/
|
|
273
|
+
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];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Modules available to data and error correction, counted off the grid.
|
|
299
|
+
*
|
|
300
|
+
* @param {number} version
|
|
301
|
+
* @returns {number}
|
|
302
|
+
*/
|
|
303
|
+
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++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return free;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Total codewords (data + error correction) a version holds.
|
|
317
|
+
*
|
|
318
|
+
* Geometric, not tabulated — this is the reference the ECC table is checked
|
|
319
|
+
* against.
|
|
320
|
+
*
|
|
321
|
+
* @param {number} version
|
|
322
|
+
* @returns {number}
|
|
323
|
+
*/
|
|
324
|
+
export function geometricTotalCodewords(version) {
|
|
325
|
+
return Math.floor(freeModuleCount(version) / 8);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Bits left over after the last whole codeword, written as zeroes.
|
|
330
|
+
*
|
|
331
|
+
* @param {number} version
|
|
332
|
+
* @returns {number} 0, 3, 4 or 7.
|
|
333
|
+
*/
|
|
334
|
+
export function remainderBits(version) {
|
|
335
|
+
return freeModuleCount(version) % 8;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const orderCache = new Map();
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Module positions in bitstream order, as interleaved x, y pairs.
|
|
342
|
+
*
|
|
343
|
+
* The layout walks two-module-wide columns from the bottom-right corner
|
|
344
|
+
* leftward, alternating upward and downward, right module of the pair before
|
|
345
|
+
* the left, skipping the vertical timing column and every reserved module.
|
|
346
|
+
*
|
|
347
|
+
* Encoder and decoder both consume this one function. That is not tidiness: a
|
|
348
|
+
* placement order that disagrees between the two would still round-trip
|
|
349
|
+
* perfectly within this library while producing symbols no scanner can read.
|
|
350
|
+
* There is only one order because there is only one implementation of it.
|
|
351
|
+
*
|
|
352
|
+
* @param {number} version
|
|
353
|
+
* @returns {Int32Array} Shared, cached — treat as immutable. Length is
|
|
354
|
+
* `2 * freeModuleCount(version)`.
|
|
355
|
+
*/
|
|
356
|
+
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
|
+
}
|
|
381
|
+
}
|
|
382
|
+
upward = !upward;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
orderCache.set(version, out);
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The eight data mask predicates.
|
|
391
|
+
*
|
|
392
|
+
* A true result means the module at (x, y) is inverted. Masks apply to payload
|
|
393
|
+
* modules only; function patterns are laid down after masking and are never
|
|
394
|
+
* touched.
|
|
395
|
+
*
|
|
396
|
+
* @param {number} mask 0-7
|
|
397
|
+
* @param {number} x Column.
|
|
398
|
+
* @param {number} y Row.
|
|
399
|
+
* @returns {boolean}
|
|
400
|
+
*/
|
|
401
|
+
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
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Error correction parameters, indexed `[version - 1]` then by level.
|
|
418
|
+
*
|
|
419
|
+
* Each entry is `[eccCodewordsPerBlock, blockCount, totalDataCodewords]`.
|
|
420
|
+
*
|
|
421
|
+
* The third number is redundant with the first two given the geometric
|
|
422
|
+
* capacity, and that is the entire point: it turns a silent typo into a loud
|
|
423
|
+
* failure. See the module note.
|
|
424
|
+
*
|
|
425
|
+
* @type {Array<{L: number[], M: number[], Q: number[], H: number[]}>}
|
|
426
|
+
*/
|
|
427
|
+
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] },
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @typedef {object} BlockLayout
|
|
472
|
+
* @property {number} version
|
|
473
|
+
* @property {string} ecc
|
|
474
|
+
* @property {number} totalCodewords Data + error correction.
|
|
475
|
+
* @property {number} totalDataCodewords
|
|
476
|
+
* @property {number} eccPerBlock
|
|
477
|
+
* @property {number} blockCount
|
|
478
|
+
* @property {number} group1Blocks Blocks holding the smaller data count.
|
|
479
|
+
* @property {number} group1DataCount
|
|
480
|
+
* @property {number} group2Blocks Blocks holding one extra data codeword.
|
|
481
|
+
* @property {number} group2DataCount
|
|
482
|
+
* @property {number} remainderBits
|
|
483
|
+
*/
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Block structure for a (version, level).
|
|
487
|
+
*
|
|
488
|
+
* The group split is derived: the spec distributes the remainder of
|
|
489
|
+
* `data / blocks` one codeword at a time into the *trailing* blocks, so the
|
|
490
|
+
* short blocks come first. That is arithmetic, and tabulating it would only
|
|
491
|
+
* create somewhere else for a typo to hide.
|
|
492
|
+
*
|
|
493
|
+
* @param {number} version
|
|
494
|
+
* @param {string} ecc 'L' | 'M' | 'Q' | 'H'
|
|
495
|
+
* @returns {BlockLayout}
|
|
496
|
+
*/
|
|
497
|
+
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
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Data capacity in codewords.
|
|
528
|
+
*
|
|
529
|
+
* @param {number} version
|
|
530
|
+
* @param {string} ecc
|
|
531
|
+
* @returns {number}
|
|
532
|
+
*/
|
|
533
|
+
export function dataCodewords(version, ecc) {
|
|
534
|
+
return ECC_TABLE[version - 1][ecc][2];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Data capacity in bits.
|
|
539
|
+
*
|
|
540
|
+
* @param {number} version
|
|
541
|
+
* @param {string} ecc
|
|
542
|
+
* @returns {number}
|
|
543
|
+
*/
|
|
544
|
+
export function dataBitCapacity(version, ecc) {
|
|
545
|
+
return dataCodewords(version, ecc) * 8;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** Error correction codewords per block are drawn from this set and no other. */
|
|
549
|
+
const VALID_ECC_PER_BLOCK = [7, 10, 13, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30];
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Remainder bits by version band, from the spec. Independent of anything the
|
|
553
|
+
* ECC table says, so it validates the *geometry* — chiefly the alignment
|
|
554
|
+
* pattern spacing rule, which is otherwise only checked by its own shape.
|
|
555
|
+
*
|
|
556
|
+
* @param {number} version
|
|
557
|
+
* @returns {number}
|
|
558
|
+
*/
|
|
559
|
+
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;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Self-check every table in this file.
|
|
571
|
+
*
|
|
572
|
+
* The load-bearing assertion is the capacity identity across all 160
|
|
573
|
+
* (version, level) combinations, but a pair of compensating typos could in
|
|
574
|
+
* principle slip past it, so the surrounding checks each constrain a different
|
|
575
|
+
* axis: monotonicity, the closed set of ECC block sizes, the field size limit,
|
|
576
|
+
* and the shape of the alignment coordinate sequence.
|
|
577
|
+
*
|
|
578
|
+
* @returns {string[]} Human-readable problems; empty means everything holds.
|
|
579
|
+
*/
|
|
580
|
+
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}`);
|
|
648
|
+
}
|
|
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
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (coords[1] - coords[0] > step) {
|
|
662
|
+
problems.push(
|
|
663
|
+
`v${version}: first alignment gap ${coords[1] - coords[0]} exceeds step ${step}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (step % 2 !== 0) {
|
|
667
|
+
problems.push(`v${version}: alignment step ${step} is odd`);
|
|
668
|
+
}
|
|
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
|
+
);
|
|
720
|
+
}
|
|
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
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return problems;
|
|
737
|
+
}
|