@naturalcycles/js-lib 15.77.0 → 15.79.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/dist/qr/qr.js ADDED
@@ -0,0 +1,945 @@
1
+ // Vendored & modernized from https://github.com/kazuhikoarase/qrcode-generator
2
+ // Original: QR Code Generator for JavaScript, Copyright (c) 2009 Kazuhiko Arase, MIT license.
3
+ // The word 'QR Code' is a registered trademark of DENSO WAVE INCORPORATED.
4
+ //
5
+ // Reason for vendoring: the upstream package is unmaintained, ships as legacy closure-based
6
+ // JS, and carries a lot of dead weight (GIF/LZW encoder, base64 streams, HTML <table> renderer,
7
+ // Kanji/SJIS conversion tables). This is a compact, typed, tree-shakeable rewrite that keeps the
8
+ // proven core (Galois-field math, Reed-Solomon ECC, masking, data encoding) and drops everything
9
+ // that modern browsers/Node make trivial:
10
+ // - Byte mode now encodes UTF-8 via TextEncoder (the original mangled non-ASCII via `c & 0xff`).
11
+ // - Output is SVG (string or data URL), ASCII (terminal), or a 2d canvas context - no GIF/LZW.
12
+ //
13
+ // The generated module matrix is byte-for-byte identical to the upstream library (verified by
14
+ // differential tests against qrcode-generator@2.0.4), so any scanner-validated output stays valid.
15
+ /// <reference lib="dom" preserve="true" />
16
+ /**
17
+ * Create a QR code from a string or raw bytes.
18
+ *
19
+ * @example
20
+ * createQrCode('https://example.com').toDataUrl()
21
+ * createQrCode('HELLO', { ecl: 'H' }).toSvg({ scale: 8 })
22
+ */
23
+ export function createQrCode(content, opt = {}) {
24
+ const ecl = opt.ecl ?? 'M';
25
+ const segment = makeSegment(content, opt.mode);
26
+ const { size, modules } = generate(segment, opt.typeNumber ?? 0, ecl);
27
+ return new QrCode(size, modules, ecl);
28
+ }
29
+ /**
30
+ * An immutable QR code: a square matrix of dark/light modules, plus renderers.
31
+ */
32
+ export class QrCode {
33
+ size;
34
+ modules;
35
+ ecl;
36
+ constructor(
37
+ /** Width/height of the matrix in modules (`typeNumber * 4 + 17`). */
38
+ size,
39
+ /** `modules[row][col]` - `true` = dark. */
40
+ modules,
41
+ /** Error correction level used. */
42
+ ecl) {
43
+ this.size = size;
44
+ this.modules = modules;
45
+ this.ecl = ecl;
46
+ }
47
+ /** Whether the module at `[row, col]` is dark. */
48
+ isDark(row, col) {
49
+ return this.modules[row][col] === true;
50
+ }
51
+ /**
52
+ * Render as a standalone SVG document string.
53
+ */
54
+ toSvg(opt = {}) {
55
+ const { scale = 4, border = 4, dark = '#000000', light = '#ffffff' } = opt;
56
+ const dim = this.size + border * 2;
57
+ const px = dim * scale;
58
+ let path = '';
59
+ for (let row = 0; row < this.size; row++) {
60
+ for (let col = 0; col < this.size; col++) {
61
+ if (this.modules[row][col]) {
62
+ // a 1x1 module rect, in module-unit coordinates (the viewBox scales it up)
63
+ path += `M${col + border},${row + border}h1v1h-1z`;
64
+ }
65
+ }
66
+ }
67
+ return (`<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" ` +
68
+ `viewBox="0 0 ${dim} ${dim}" shape-rendering="crispEdges">` +
69
+ `<rect width="${dim}" height="${dim}" fill="${light}"/>` +
70
+ `<path d="${path}" fill="${dark}"/>` +
71
+ `</svg>`);
72
+ }
73
+ /**
74
+ * Render as an `data:image/svg+xml` URL, ready for `<img src>` or CSS `background`.
75
+ */
76
+ toDataUrl(opt = {}) {
77
+ return `data:image/svg+xml,${encodeURIComponent(this.toSvg(opt))}`;
78
+ }
79
+ /**
80
+ * Render as ASCII art using block characters - handy for terminals and logs.
81
+ * Each module is 2 chars wide so the output keeps a square aspect ratio.
82
+ */
83
+ toAscii(opt = {}) {
84
+ const { border = 2, invert = false } = opt;
85
+ const darkCell = invert ? ' ' : '██';
86
+ const lightCell = invert ? '██' : ' ';
87
+ const lines = [];
88
+ for (let row = -border; row < this.size + border; row++) {
89
+ let line = '';
90
+ for (let col = -border; col < this.size + border; col++) {
91
+ const dark = row >= 0 && row < this.size && col >= 0 && col < this.size && this.modules[row][col];
92
+ line += dark ? darkCell : lightCell;
93
+ }
94
+ lines.push(line);
95
+ }
96
+ return lines.join('\n');
97
+ }
98
+ /** Same as {@link toAscii} with defaults. */
99
+ toString() {
100
+ return this.toAscii();
101
+ }
102
+ /**
103
+ * Paint the QR code onto a 2d canvas context (browser).
104
+ */
105
+ renderToCanvas(ctx, opt = {}) {
106
+ const { scale = 4, border = 4, dark = '#000000', light = '#ffffff' } = opt;
107
+ const px = (this.size + border * 2) * scale;
108
+ ctx.fillStyle = light;
109
+ ctx.fillRect(0, 0, px, px);
110
+ ctx.fillStyle = dark;
111
+ for (let row = 0; row < this.size; row++) {
112
+ for (let col = 0; col < this.size; col++) {
113
+ if (this.modules[row][col]) {
114
+ ctx.fillRect((col + border) * scale, (row + border) * scale, scale, scale);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ // --- internals -------------------------------------------------------------
121
+ const MODE_NUMERIC = 1 << 0;
122
+ const MODE_ALPHANUMERIC = 1 << 1;
123
+ const MODE_BYTE = 1 << 2;
124
+ const PAD0 = 0xec;
125
+ const PAD1 = 0x11;
126
+ /** ECC level as encoded in the format-info bits. */
127
+ const ECL_BITS = { L: 1, M: 0, Q: 3, H: 2 };
128
+ /** ECC level as a row offset into the RS block table (ordered L, M, Q, H per version). */
129
+ const ECL_OFFSET = { L: 0, M: 1, Q: 2, H: 3 };
130
+ const NUMERIC_RE = /^\d+$/;
131
+ const ALPHANUMERIC_RE = /^[\dA-Z $%*+./:-]+$/;
132
+ const ALPHANUMERIC_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
133
+ const textEncoder = /* @__PURE__ */ new TextEncoder();
134
+ function makeSegment(content, mode) {
135
+ if (typeof content !== 'string')
136
+ return byteSegment(content);
137
+ const resolved = mode ?? detectMode(content);
138
+ if (resolved === 'numeric')
139
+ return numericSegment(content);
140
+ if (resolved === 'alphanumeric')
141
+ return alphanumericSegment(content);
142
+ return byteSegment(textEncoder.encode(content));
143
+ }
144
+ function detectMode(s) {
145
+ if (NUMERIC_RE.test(s))
146
+ return 'numeric';
147
+ if (ALPHANUMERIC_RE.test(s))
148
+ return 'alphanumeric';
149
+ return 'byte';
150
+ }
151
+ function numericSegment(data) {
152
+ return {
153
+ mode: MODE_NUMERIC,
154
+ length: data.length,
155
+ write(bb) {
156
+ let i = 0;
157
+ // 3 digits -> 10 bits
158
+ for (; i + 2 < data.length; i += 3) {
159
+ bb.put(Number(data.slice(i, i + 3)), 10);
160
+ }
161
+ // tail: 2 digits -> 7 bits, 1 digit -> 4 bits
162
+ if (data.length - i === 2) {
163
+ bb.put(Number(data.slice(i, i + 2)), 7);
164
+ }
165
+ else if (data.length - i === 1) {
166
+ bb.put(Number(data.slice(i, i + 1)), 4);
167
+ }
168
+ },
169
+ };
170
+ }
171
+ function alphanumericSegment(data) {
172
+ return {
173
+ mode: MODE_ALPHANUMERIC,
174
+ length: data.length,
175
+ write(bb) {
176
+ let i = 0;
177
+ // 2 chars -> 11 bits (c0 * 45 + c1)
178
+ for (; i + 1 < data.length; i += 2) {
179
+ bb.put(alphanumericCode(data[i]) * 45 + alphanumericCode(data[i + 1]), 11);
180
+ }
181
+ // tail: 1 char -> 6 bits
182
+ if (i < data.length) {
183
+ bb.put(alphanumericCode(data[i]), 6);
184
+ }
185
+ },
186
+ };
187
+ }
188
+ function alphanumericCode(c) {
189
+ const code = ALPHANUMERIC_CHARS.indexOf(c);
190
+ if (code === -1)
191
+ throw new Error(`qr: illegal alphanumeric char: ${c}`);
192
+ return code;
193
+ }
194
+ function byteSegment(bytes) {
195
+ return {
196
+ mode: MODE_BYTE,
197
+ length: bytes.length,
198
+ write(bb) {
199
+ for (const b of bytes)
200
+ bb.put(b, 8);
201
+ },
202
+ };
203
+ }
204
+ /**
205
+ * Build the final module matrix: resolve version, encode data, then pick the mask pattern with the
206
+ * lowest penalty score (matching the reference scoring, incl. tie-break to the lowest mask index).
207
+ */
208
+ function generate(segment, typeNumber, ecl) {
209
+ const resolvedType = typeNumber >= 1 ? typeNumber : bestTypeNumber(segment, ecl);
210
+ const data = createData(resolvedType, ecl, segment);
211
+ let bestMask = 0;
212
+ let minLostPoint = Number.POSITIVE_INFINITY;
213
+ for (let mask = 0; mask < 8; mask++) {
214
+ // scoring uses "test" modules (format/version bits blanked), as the original does
215
+ const testModules = renderModules(resolvedType, ecl, data, mask, true);
216
+ const lostPoint = getLostPoint(testModules);
217
+ if (lostPoint < minLostPoint) {
218
+ minLostPoint = lostPoint;
219
+ bestMask = mask;
220
+ }
221
+ }
222
+ const modules = renderModules(resolvedType, ecl, data, bestMask, false);
223
+ return { size: modules.length, modules };
224
+ }
225
+ /** Smallest version (1..40) whose data capacity fits the segment at the given ECC level. */
226
+ function bestTypeNumber(segment, ecl) {
227
+ for (let type = 1; type <= 40; type++) {
228
+ const bb = new BitBuffer();
229
+ writeSegment(bb, segment, type);
230
+ let totalDataCount = 0;
231
+ for (const block of getRsBlocks(type, ecl))
232
+ totalDataCount += block.dataCount;
233
+ if (bb.lengthInBits <= totalDataCount * 8)
234
+ return type;
235
+ }
236
+ throw new Error('qr: data too long for a single QR code');
237
+ }
238
+ function renderModules(typeNumber, ecl, data, maskPattern, test) {
239
+ const size = typeNumber * 4 + 17;
240
+ // `undefined` = "not yet filled" (function patterns must not be overwritten by data/timing)
241
+ const modules = Array.from({ length: size }, () => new Array(size).fill(undefined));
242
+ setupPositionProbePattern(modules, 0, 0);
243
+ setupPositionProbePattern(modules, size - 7, 0);
244
+ setupPositionProbePattern(modules, 0, size - 7);
245
+ setupPositionAdjustPattern(modules, typeNumber);
246
+ setupTimingPattern(modules);
247
+ setupTypeInfo(modules, test, maskPattern, ecl);
248
+ if (typeNumber >= 7) {
249
+ setupTypeNumber(modules, test, typeNumber);
250
+ }
251
+ mapData(modules, data, maskPattern);
252
+ // every cell is filled by now; coerce the sentinel away
253
+ return modules;
254
+ }
255
+ function setupPositionProbePattern(modules, row, col) {
256
+ const size = modules.length;
257
+ for (let r = -1; r <= 7; r++) {
258
+ if (row + r <= -1 || size <= row + r)
259
+ continue;
260
+ for (let c = -1; c <= 7; c++) {
261
+ if (col + c <= -1 || size <= col + c)
262
+ continue;
263
+ modules[row + r][col + c] =
264
+ (r >= 0 && r <= 6 && (c === 0 || c === 6)) ||
265
+ (c >= 0 && c <= 6 && (r === 0 || r === 6)) ||
266
+ (r >= 2 && r <= 4 && c >= 2 && c <= 4);
267
+ }
268
+ }
269
+ }
270
+ function setupPositionAdjustPattern(modules, typeNumber) {
271
+ const pos = PATTERN_POSITION_TABLE[typeNumber - 1];
272
+ for (const row of pos) {
273
+ for (const col of pos) {
274
+ if (modules[row][col] !== undefined)
275
+ continue;
276
+ for (let r = -2; r <= 2; r++) {
277
+ for (let c = -2; c <= 2; c++) {
278
+ modules[row + r][col + c] =
279
+ r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0);
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ function setupTimingPattern(modules) {
286
+ const size = modules.length;
287
+ for (let r = 8; r < size - 8; r++) {
288
+ if (modules[r][6] === undefined)
289
+ modules[r][6] = r % 2 === 0;
290
+ }
291
+ for (let c = 8; c < size - 8; c++) {
292
+ if (modules[6][c] === undefined)
293
+ modules[6][c] = c % 2 === 0;
294
+ }
295
+ }
296
+ function setupTypeInfo(modules, test, maskPattern, ecl) {
297
+ const size = modules.length;
298
+ const data = (ECL_BITS[ecl] << 3) | maskPattern;
299
+ const bits = getBchTypeInfo(data);
300
+ for (let i = 0; i < 15; i++) {
301
+ const mod = !test && ((bits >> i) & 1) === 1;
302
+ // vertical
303
+ if (i < 6) {
304
+ modules[i][8] = mod;
305
+ }
306
+ else if (i < 8) {
307
+ modules[i + 1][8] = mod;
308
+ }
309
+ else {
310
+ modules[size - 15 + i][8] = mod;
311
+ }
312
+ // horizontal
313
+ if (i < 8) {
314
+ modules[8][size - i - 1] = mod;
315
+ }
316
+ else if (i < 9) {
317
+ modules[8][15 - i - 1 + 1] = mod;
318
+ }
319
+ else {
320
+ modules[8][15 - i - 1] = mod;
321
+ }
322
+ }
323
+ // fixed dark module
324
+ modules[size - 8][8] = !test;
325
+ }
326
+ function setupTypeNumber(modules, test, typeNumber) {
327
+ const size = modules.length;
328
+ const bits = getBchTypeNumber(typeNumber);
329
+ for (let i = 0; i < 18; i++) {
330
+ const mod = !test && ((bits >> i) & 1) === 1;
331
+ modules[Math.floor(i / 3)][(i % 3) + size - 8 - 3] = mod;
332
+ modules[(i % 3) + size - 8 - 3][Math.floor(i / 3)] = mod;
333
+ }
334
+ }
335
+ function mapData(modules, data, maskPattern) {
336
+ const size = modules.length;
337
+ let inc = -1;
338
+ let row = size - 1;
339
+ let bitIndex = 7;
340
+ let byteIndex = 0;
341
+ const maskFunc = MASK_FUNCTIONS[maskPattern];
342
+ for (let col = size - 1; col > 0; col -= 2) {
343
+ if (col === 6)
344
+ col -= 1;
345
+ for (;;) {
346
+ for (let c = 0; c < 2; c++) {
347
+ if (modules[row][col - c] === undefined) {
348
+ let dark = false;
349
+ if (byteIndex < data.length) {
350
+ dark = ((data[byteIndex] >>> bitIndex) & 1) === 1;
351
+ }
352
+ if (maskFunc(row, col - c))
353
+ dark = !dark;
354
+ modules[row][col - c] = dark;
355
+ bitIndex--;
356
+ if (bitIndex === -1) {
357
+ byteIndex++;
358
+ bitIndex = 7;
359
+ }
360
+ }
361
+ }
362
+ row += inc;
363
+ if (row < 0 || size <= row) {
364
+ row -= inc;
365
+ inc = -inc;
366
+ break;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ /** Mask penalty score - lower is better. Sum of the 4 penalty rules from the QR spec. */
372
+ function getLostPoint(modules) {
373
+ return (lostPointAdjacent(modules) +
374
+ lostPointBlocks(modules) +
375
+ lostPointFinderLike(modules) +
376
+ lostPointDarkRatio(modules));
377
+ }
378
+ /** LEVEL 1: runs of same-colored modules in a 3x3 neighbourhood. */
379
+ function lostPointAdjacent(modules) {
380
+ const size = modules.length;
381
+ let lostPoint = 0;
382
+ for (let row = 0; row < size; row++) {
383
+ for (let col = 0; col < size; col++) {
384
+ let sameCount = 0;
385
+ const dark = modules[row][col];
386
+ for (let r = -1; r <= 1; r++) {
387
+ if (row + r < 0 || size <= row + r)
388
+ continue;
389
+ for (let c = -1; c <= 1; c++) {
390
+ if (col + c < 0 || size <= col + c)
391
+ continue;
392
+ if (r === 0 && c === 0)
393
+ continue;
394
+ if (dark === modules[row + r][col + c])
395
+ sameCount++;
396
+ }
397
+ }
398
+ if (sameCount > 5)
399
+ lostPoint += 3 + sameCount - 5;
400
+ }
401
+ }
402
+ return lostPoint;
403
+ }
404
+ /** LEVEL 2: 2x2 blocks of one color. */
405
+ function lostPointBlocks(modules) {
406
+ const size = modules.length;
407
+ let lostPoint = 0;
408
+ for (let row = 0; row < size - 1; row++) {
409
+ for (let col = 0; col < size - 1; col++) {
410
+ let count = 0;
411
+ if (modules[row][col])
412
+ count++;
413
+ if (modules[row + 1][col])
414
+ count++;
415
+ if (modules[row][col + 1])
416
+ count++;
417
+ if (modules[row + 1][col + 1])
418
+ count++;
419
+ if (count === 0 || count === 4)
420
+ lostPoint += 3;
421
+ }
422
+ }
423
+ return lostPoint;
424
+ }
425
+ /** LEVEL 3: finder-like 1:1:3:1:1 patterns, horizontal and vertical. */
426
+ function lostPointFinderLike(modules) {
427
+ const size = modules.length;
428
+ let lostPoint = 0;
429
+ const isDark = (r, c) => modules[r][c] === true;
430
+ for (let row = 0; row < size; row++) {
431
+ for (let col = 0; col < size - 6; col++) {
432
+ if (isDark(row, col) &&
433
+ !isDark(row, col + 1) &&
434
+ isDark(row, col + 2) &&
435
+ isDark(row, col + 3) &&
436
+ isDark(row, col + 4) &&
437
+ !isDark(row, col + 5) &&
438
+ isDark(row, col + 6)) {
439
+ lostPoint += 40;
440
+ }
441
+ }
442
+ }
443
+ for (let col = 0; col < size; col++) {
444
+ for (let row = 0; row < size - 6; row++) {
445
+ if (isDark(row, col) &&
446
+ !isDark(row + 1, col) &&
447
+ isDark(row + 2, col) &&
448
+ isDark(row + 3, col) &&
449
+ isDark(row + 4, col) &&
450
+ !isDark(row + 5, col) &&
451
+ isDark(row + 6, col)) {
452
+ lostPoint += 40;
453
+ }
454
+ }
455
+ }
456
+ return lostPoint;
457
+ }
458
+ /** LEVEL 4: deviation of the dark-module ratio from 50%. */
459
+ function lostPointDarkRatio(modules) {
460
+ const size = modules.length;
461
+ let darkCount = 0;
462
+ for (let col = 0; col < size; col++) {
463
+ for (let row = 0; row < size; row++) {
464
+ if (modules[row][col])
465
+ darkCount++;
466
+ }
467
+ }
468
+ const ratio = Math.abs((100 * darkCount) / size / size - 50) / 5;
469
+ return ratio * 10;
470
+ }
471
+ // --- data encoding (ECC) ---------------------------------------------------
472
+ function createData(typeNumber, ecl, segment) {
473
+ const rsBlocks = getRsBlocks(typeNumber, ecl);
474
+ const bb = new BitBuffer();
475
+ writeSegment(bb, segment, typeNumber);
476
+ let totalDataCount = 0;
477
+ for (const block of rsBlocks)
478
+ totalDataCount += block.dataCount;
479
+ if (bb.lengthInBits > totalDataCount * 8) {
480
+ throw new Error(`qr: code length overflow (${bb.lengthInBits} > ${totalDataCount * 8})`);
481
+ }
482
+ // terminator
483
+ if (bb.lengthInBits + 4 <= totalDataCount * 8)
484
+ bb.put(0, 4);
485
+ // pad to byte boundary
486
+ while (bb.lengthInBits % 8 !== 0)
487
+ bb.putBit(false);
488
+ // pad with alternating PAD0/PAD1 to capacity
489
+ for (;;) {
490
+ if (bb.lengthInBits >= totalDataCount * 8)
491
+ break;
492
+ bb.put(PAD0, 8);
493
+ if (bb.lengthInBits >= totalDataCount * 8)
494
+ break;
495
+ bb.put(PAD1, 8);
496
+ }
497
+ return createBytes(bb, rsBlocks);
498
+ }
499
+ function writeSegment(bb, segment, typeNumber) {
500
+ bb.put(segment.mode, 4);
501
+ bb.put(segment.length, getLengthInBits(segment.mode, typeNumber));
502
+ segment.write(bb);
503
+ }
504
+ /** Interleave data & error-correction codewords across the RS blocks. */
505
+ function createBytes(buffer, rsBlocks) {
506
+ let offset = 0;
507
+ let maxDcCount = 0;
508
+ let maxEcCount = 0;
509
+ const dcdata = new Array(rsBlocks.length);
510
+ const ecdata = new Array(rsBlocks.length);
511
+ const src = buffer.buffer;
512
+ for (let r = 0; r < rsBlocks.length; r++) {
513
+ const dcCount = rsBlocks[r].dataCount;
514
+ const ecCount = rsBlocks[r].totalCount - dcCount;
515
+ maxDcCount = Math.max(maxDcCount, dcCount);
516
+ maxEcCount = Math.max(maxEcCount, ecCount);
517
+ const dc = new Array(dcCount);
518
+ for (let i = 0; i < dcCount; i++)
519
+ dc[i] = 0xff & src[i + offset];
520
+ dcdata[r] = dc;
521
+ offset += dcCount;
522
+ const rsPoly = getErrorCorrectPolynomial(ecCount);
523
+ const modPoly = polyMod(newPolynomial(dc, rsPoly.length - 1), rsPoly);
524
+ const ec = new Array(rsPoly.length - 1);
525
+ for (let i = 0; i < ec.length; i++) {
526
+ const modIndex = i + modPoly.length - ec.length;
527
+ ec[i] = modIndex >= 0 ? modPoly[modIndex] : 0;
528
+ }
529
+ ecdata[r] = ec;
530
+ }
531
+ let totalCodeCount = 0;
532
+ for (const block of rsBlocks)
533
+ totalCodeCount += block.totalCount;
534
+ const data = new Array(totalCodeCount);
535
+ let index = 0;
536
+ for (let i = 0; i < maxDcCount; i++) {
537
+ for (let r = 0; r < rsBlocks.length; r++) {
538
+ if (i < dcdata[r].length)
539
+ data[index++] = dcdata[r][i];
540
+ }
541
+ }
542
+ for (let i = 0; i < maxEcCount; i++) {
543
+ for (let r = 0; r < rsBlocks.length; r++) {
544
+ if (i < ecdata[r].length)
545
+ data[index++] = ecdata[r][i];
546
+ }
547
+ }
548
+ return data;
549
+ }
550
+ /** Bits in the character-count field, by mode, for version ranges [1-9], [10-26], [27-40]. */
551
+ const LENGTH_BITS = {
552
+ [MODE_NUMERIC]: [10, 12, 14],
553
+ [MODE_ALPHANUMERIC]: [9, 11, 13],
554
+ [MODE_BYTE]: [8, 16, 16],
555
+ };
556
+ /** Number of bits in the character-count field, per mode & version range. */
557
+ function getLengthInBits(mode, type) {
558
+ if (type < 1 || type > 40)
559
+ throw new Error(`qr: bad type number: ${type}`);
560
+ const bits = LENGTH_BITS[mode];
561
+ if (!bits)
562
+ throw new Error(`qr: bad mode: ${mode}`);
563
+ const range = type < 10 ? 0 : type < 27 ? 1 : 2;
564
+ return bits[range];
565
+ }
566
+ // --- Reed-Solomon polynomials over GF(256) ---------------------------------
567
+ /** Drop leading zeros, then right-pad with `shift` zeros. */
568
+ function newPolynomial(num, shift) {
569
+ let offset = 0;
570
+ while (offset < num.length && num[offset] === 0)
571
+ offset++;
572
+ const poly = new Array(num.length - offset + shift).fill(0);
573
+ for (let i = 0; i < num.length - offset; i++)
574
+ poly[i] = num[i + offset];
575
+ return poly;
576
+ }
577
+ function polyMultiply(a, b) {
578
+ const num = new Array(a.length + b.length - 1).fill(0);
579
+ for (let i = 0; i < a.length; i++) {
580
+ for (let j = 0; j < b.length; j++) {
581
+ num[i + j] ^= gexp(glog(a[i]) + glog(b[j]));
582
+ }
583
+ }
584
+ return newPolynomial(num, 0);
585
+ }
586
+ function polyMod(a, b) {
587
+ if (a.length - b.length < 0)
588
+ return a;
589
+ const ratio = glog(a[0]) - glog(b[0]);
590
+ const num = a.slice();
591
+ for (let i = 0; i < b.length; i++) {
592
+ num[i] ^= gexp(glog(b[i]) + ratio);
593
+ }
594
+ // recurse until degree drops below the divisor
595
+ return polyMod(newPolynomial(num, 0), b);
596
+ }
597
+ function getErrorCorrectPolynomial(ecLength) {
598
+ let poly = [1];
599
+ for (let i = 0; i < ecLength; i++) {
600
+ poly = polyMultiply(poly, [1, gexp(i)]);
601
+ }
602
+ return poly;
603
+ }
604
+ // --- BCH codes (format & version info) -------------------------------------
605
+ const G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
606
+ const G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
607
+ const G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
608
+ function getBchDigit(data) {
609
+ let digit = 0;
610
+ while (data !== 0) {
611
+ digit++;
612
+ data >>>= 1;
613
+ }
614
+ return digit;
615
+ }
616
+ function getBchTypeInfo(data) {
617
+ let d = data << 10;
618
+ while (getBchDigit(d) - getBchDigit(G15) >= 0) {
619
+ d ^= G15 << (getBchDigit(d) - getBchDigit(G15));
620
+ }
621
+ return ((data << 10) | d) ^ G15_MASK;
622
+ }
623
+ function getBchTypeNumber(data) {
624
+ let d = data << 12;
625
+ while (getBchDigit(d) - getBchDigit(G18) >= 0) {
626
+ d ^= G18 << (getBchDigit(d) - getBchDigit(G18));
627
+ }
628
+ return (data << 12) | d;
629
+ }
630
+ // --- Galois field GF(256) math ---------------------------------------------
631
+ const EXP_TABLE = new Uint8Array(256);
632
+ const LOG_TABLE = new Uint8Array(256);
633
+ for (let i = 0; i < 8; i++)
634
+ EXP_TABLE[i] = 1 << i;
635
+ for (let i = 8; i < 256; i++) {
636
+ EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8];
637
+ }
638
+ for (let i = 0; i < 255; i++)
639
+ LOG_TABLE[EXP_TABLE[i]] = i;
640
+ function glog(n) {
641
+ if (n < 1)
642
+ throw new Error(`qr: glog(${n})`);
643
+ return LOG_TABLE[n];
644
+ }
645
+ function gexp(n) {
646
+ while (n < 0)
647
+ n += 255;
648
+ while (n >= 256)
649
+ n -= 255;
650
+ return EXP_TABLE[n];
651
+ }
652
+ // --- bit buffer ------------------------------------------------------------
653
+ class BitBuffer {
654
+ buffer = [];
655
+ lengthInBits = 0;
656
+ put(num, length) {
657
+ for (let i = 0; i < length; i++) {
658
+ this.putBit(((num >>> (length - i - 1)) & 1) === 1);
659
+ }
660
+ }
661
+ putBit(bit) {
662
+ const bufIndex = Math.floor(this.lengthInBits / 8);
663
+ if (this.buffer.length <= bufIndex)
664
+ this.buffer.push(0);
665
+ if (bit)
666
+ this.buffer[bufIndex] |= 0x80 >>> (this.lengthInBits % 8);
667
+ this.lengthInBits++;
668
+ }
669
+ }
670
+ function getRsBlocks(typeNumber, ecl) {
671
+ const rsBlock = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + ECL_OFFSET[ecl]];
672
+ if (!rsBlock) {
673
+ throw new Error(`qr: bad rs block @ typeNumber:${typeNumber}/ecl:${ecl}`);
674
+ }
675
+ const blocks = [];
676
+ // each row is a flat list of [count, totalCount, dataCount] triples
677
+ for (let i = 0; i < rsBlock.length; i += 3) {
678
+ const count = rsBlock[i];
679
+ const totalCount = rsBlock[i + 1];
680
+ const dataCount = rsBlock[i + 2];
681
+ for (let j = 0; j < count; j++)
682
+ blocks.push({ totalCount, dataCount });
683
+ }
684
+ return blocks;
685
+ }
686
+ /** Alignment-pattern centre coordinates, indexed by `typeNumber - 1`. */
687
+ const PATTERN_POSITION_TABLE = [
688
+ [],
689
+ [6, 18],
690
+ [6, 22],
691
+ [6, 26],
692
+ [6, 30],
693
+ [6, 34],
694
+ [6, 22, 38],
695
+ [6, 24, 42],
696
+ [6, 26, 46],
697
+ [6, 28, 50],
698
+ [6, 30, 54],
699
+ [6, 32, 58],
700
+ [6, 34, 62],
701
+ [6, 26, 46, 66],
702
+ [6, 26, 48, 70],
703
+ [6, 26, 50, 74],
704
+ [6, 30, 54, 78],
705
+ [6, 30, 56, 82],
706
+ [6, 30, 58, 86],
707
+ [6, 34, 62, 90],
708
+ [6, 28, 50, 72, 94],
709
+ [6, 26, 50, 74, 98],
710
+ [6, 30, 54, 78, 102],
711
+ [6, 28, 54, 80, 106],
712
+ [6, 32, 58, 84, 110],
713
+ [6, 30, 58, 86, 114],
714
+ [6, 34, 62, 90, 118],
715
+ [6, 26, 50, 74, 98, 122],
716
+ [6, 30, 54, 78, 102, 126],
717
+ [6, 26, 52, 78, 104, 130],
718
+ [6, 30, 56, 82, 108, 134],
719
+ [6, 34, 60, 86, 112, 138],
720
+ [6, 30, 58, 86, 114, 142],
721
+ [6, 34, 62, 90, 118, 146],
722
+ [6, 30, 54, 78, 102, 126, 150],
723
+ [6, 24, 50, 76, 102, 128, 154],
724
+ [6, 28, 54, 80, 106, 132, 158],
725
+ [6, 32, 58, 84, 110, 136, 162],
726
+ [6, 26, 54, 82, 110, 138, 166],
727
+ [6, 30, 58, 86, 114, 142, 170],
728
+ ];
729
+ /** The 8 data-masking functions, indexed by mask pattern (0..7). */
730
+ const MASK_FUNCTIONS = [
731
+ (i, j) => (i + j) % 2 === 0,
732
+ (i, _j) => i % 2 === 0,
733
+ (_i, j) => j % 3 === 0,
734
+ (i, j) => (i + j) % 3 === 0,
735
+ (i, j) => (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0,
736
+ (i, j) => ((i * j) % 2) + ((i * j) % 3) === 0,
737
+ (i, j) => (((i * j) % 2) + ((i * j) % 3)) % 2 === 0,
738
+ (i, j) => (((i * j) % 3) + ((i + j) % 2)) % 2 === 0,
739
+ ];
740
+ /**
741
+ * RS block layout, 4 rows per version (L, M, Q, H), each a flat list of
742
+ * `[count, totalCount, dataCount]` triples. Verbatim from the QR spec.
743
+ */
744
+ const RS_BLOCK_TABLE = [
745
+ // 1
746
+ [1, 26, 19],
747
+ [1, 26, 16],
748
+ [1, 26, 13],
749
+ [1, 26, 9],
750
+ // 2
751
+ [1, 44, 34],
752
+ [1, 44, 28],
753
+ [1, 44, 22],
754
+ [1, 44, 16],
755
+ // 3
756
+ [1, 70, 55],
757
+ [1, 70, 44],
758
+ [2, 35, 17],
759
+ [2, 35, 13],
760
+ // 4
761
+ [1, 100, 80],
762
+ [2, 50, 32],
763
+ [2, 50, 24],
764
+ [4, 25, 9],
765
+ // 5
766
+ [1, 134, 108],
767
+ [2, 67, 43],
768
+ [2, 33, 15, 2, 34, 16],
769
+ [2, 33, 11, 2, 34, 12],
770
+ // 6
771
+ [2, 86, 68],
772
+ [4, 43, 27],
773
+ [4, 43, 19],
774
+ [4, 43, 15],
775
+ // 7
776
+ [2, 98, 78],
777
+ [4, 49, 31],
778
+ [2, 32, 14, 4, 33, 15],
779
+ [4, 39, 13, 1, 40, 14],
780
+ // 8
781
+ [2, 121, 97],
782
+ [2, 60, 38, 2, 61, 39],
783
+ [4, 40, 18, 2, 41, 19],
784
+ [4, 40, 14, 2, 41, 15],
785
+ // 9
786
+ [2, 146, 116],
787
+ [3, 58, 36, 2, 59, 37],
788
+ [4, 36, 16, 4, 37, 17],
789
+ [4, 36, 12, 4, 37, 13],
790
+ // 10
791
+ [2, 86, 68, 2, 87, 69],
792
+ [4, 69, 43, 1, 70, 44],
793
+ [6, 43, 19, 2, 44, 20],
794
+ [6, 43, 15, 2, 44, 16],
795
+ // 11
796
+ [4, 101, 81],
797
+ [1, 80, 50, 4, 81, 51],
798
+ [4, 50, 22, 4, 51, 23],
799
+ [3, 36, 12, 8, 37, 13],
800
+ // 12
801
+ [2, 116, 92, 2, 117, 93],
802
+ [6, 58, 36, 2, 59, 37],
803
+ [4, 46, 20, 6, 47, 21],
804
+ [7, 42, 14, 4, 43, 15],
805
+ // 13
806
+ [4, 133, 107],
807
+ [8, 59, 37, 1, 60, 38],
808
+ [8, 44, 20, 4, 45, 21],
809
+ [12, 33, 11, 4, 34, 12],
810
+ // 14
811
+ [3, 145, 115, 1, 146, 116],
812
+ [4, 64, 40, 5, 65, 41],
813
+ [11, 36, 16, 5, 37, 17],
814
+ [11, 36, 12, 5, 37, 13],
815
+ // 15
816
+ [5, 109, 87, 1, 110, 88],
817
+ [5, 65, 41, 5, 66, 42],
818
+ [5, 54, 24, 7, 55, 25],
819
+ [11, 36, 12, 7, 37, 13],
820
+ // 16
821
+ [5, 122, 98, 1, 123, 99],
822
+ [7, 73, 45, 3, 74, 46],
823
+ [15, 43, 19, 2, 44, 20],
824
+ [3, 45, 15, 13, 46, 16],
825
+ // 17
826
+ [1, 135, 107, 5, 136, 108],
827
+ [10, 74, 46, 1, 75, 47],
828
+ [1, 50, 22, 15, 51, 23],
829
+ [2, 42, 14, 17, 43, 15],
830
+ // 18
831
+ [5, 150, 120, 1, 151, 121],
832
+ [9, 69, 43, 4, 70, 44],
833
+ [17, 50, 22, 1, 51, 23],
834
+ [2, 42, 14, 19, 43, 15],
835
+ // 19
836
+ [3, 141, 113, 4, 142, 114],
837
+ [3, 70, 44, 11, 71, 45],
838
+ [17, 47, 21, 4, 48, 22],
839
+ [9, 39, 13, 16, 40, 14],
840
+ // 20
841
+ [3, 135, 107, 5, 136, 108],
842
+ [3, 67, 41, 13, 68, 42],
843
+ [15, 54, 24, 5, 55, 25],
844
+ [15, 43, 15, 10, 44, 16],
845
+ // 21
846
+ [4, 144, 116, 4, 145, 117],
847
+ [17, 68, 42],
848
+ [17, 50, 22, 6, 51, 23],
849
+ [19, 46, 16, 6, 47, 17],
850
+ // 22
851
+ [2, 139, 111, 7, 140, 112],
852
+ [17, 74, 46],
853
+ [7, 54, 24, 16, 55, 25],
854
+ [34, 37, 13],
855
+ // 23
856
+ [4, 151, 121, 5, 152, 122],
857
+ [4, 75, 47, 14, 76, 48],
858
+ [11, 54, 24, 14, 55, 25],
859
+ [16, 45, 15, 14, 46, 16],
860
+ // 24
861
+ [6, 147, 117, 4, 148, 118],
862
+ [6, 73, 45, 14, 74, 46],
863
+ [11, 54, 24, 16, 55, 25],
864
+ [30, 46, 16, 2, 47, 17],
865
+ // 25
866
+ [8, 132, 106, 4, 133, 107],
867
+ [8, 75, 47, 13, 76, 48],
868
+ [7, 54, 24, 22, 55, 25],
869
+ [22, 45, 15, 13, 46, 16],
870
+ // 26
871
+ [10, 142, 114, 2, 143, 115],
872
+ [19, 74, 46, 4, 75, 47],
873
+ [28, 50, 22, 6, 51, 23],
874
+ [33, 46, 16, 4, 47, 17],
875
+ // 27
876
+ [8, 152, 122, 4, 153, 123],
877
+ [22, 73, 45, 3, 74, 46],
878
+ [8, 53, 23, 26, 54, 24],
879
+ [12, 45, 15, 28, 46, 16],
880
+ // 28
881
+ [3, 147, 117, 10, 148, 118],
882
+ [3, 73, 45, 23, 74, 46],
883
+ [4, 54, 24, 31, 55, 25],
884
+ [11, 45, 15, 31, 46, 16],
885
+ // 29
886
+ [7, 146, 116, 7, 147, 117],
887
+ [21, 73, 45, 7, 74, 46],
888
+ [1, 53, 23, 37, 54, 24],
889
+ [19, 45, 15, 26, 46, 16],
890
+ // 30
891
+ [5, 145, 115, 10, 146, 116],
892
+ [19, 75, 47, 10, 76, 48],
893
+ [15, 54, 24, 25, 55, 25],
894
+ [23, 45, 15, 25, 46, 16],
895
+ // 31
896
+ [13, 145, 115, 3, 146, 116],
897
+ [2, 74, 46, 29, 75, 47],
898
+ [42, 54, 24, 1, 55, 25],
899
+ [23, 45, 15, 28, 46, 16],
900
+ // 32
901
+ [17, 145, 115],
902
+ [10, 74, 46, 23, 75, 47],
903
+ [10, 54, 24, 35, 55, 25],
904
+ [19, 45, 15, 35, 46, 16],
905
+ // 33
906
+ [17, 145, 115, 1, 146, 116],
907
+ [14, 74, 46, 21, 75, 47],
908
+ [29, 54, 24, 19, 55, 25],
909
+ [11, 45, 15, 46, 46, 16],
910
+ // 34
911
+ [13, 145, 115, 6, 146, 116],
912
+ [14, 74, 46, 23, 75, 47],
913
+ [44, 54, 24, 7, 55, 25],
914
+ [59, 46, 16, 1, 47, 17],
915
+ // 35
916
+ [12, 151, 121, 7, 152, 122],
917
+ [12, 75, 47, 26, 76, 48],
918
+ [39, 54, 24, 14, 55, 25],
919
+ [22, 45, 15, 41, 46, 16],
920
+ // 36
921
+ [6, 151, 121, 14, 152, 122],
922
+ [6, 75, 47, 34, 76, 48],
923
+ [46, 54, 24, 10, 55, 25],
924
+ [2, 45, 15, 64, 46, 16],
925
+ // 37
926
+ [17, 152, 122, 4, 153, 123],
927
+ [29, 74, 46, 14, 75, 47],
928
+ [49, 54, 24, 10, 55, 25],
929
+ [24, 45, 15, 46, 46, 16],
930
+ // 38
931
+ [4, 152, 122, 18, 153, 123],
932
+ [13, 74, 46, 32, 75, 47],
933
+ [48, 54, 24, 14, 55, 25],
934
+ [42, 45, 15, 32, 46, 16],
935
+ // 39
936
+ [20, 147, 117, 4, 148, 118],
937
+ [40, 75, 47, 7, 76, 48],
938
+ [43, 54, 24, 22, 55, 25],
939
+ [10, 45, 15, 67, 46, 16],
940
+ // 40
941
+ [19, 148, 118, 6, 149, 119],
942
+ [18, 75, 47, 31, 76, 48],
943
+ [34, 54, 24, 34, 55, 25],
944
+ [20, 45, 15, 61, 46, 16],
945
+ ];