@inner-dj/server 0.1.2 → 0.1.3

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 (36) hide show
  1. package/assets/css/styles.css +1 -1
  2. package/assets/js/totp.js +1 -1
  3. package/lib/src/browser/totp.js +3 -16
  4. package/lib/src/browser/totp.js.map +1 -1
  5. package/lib/src/config.d.ts +2 -3
  6. package/lib/src/config.js +3 -4
  7. package/lib/src/config.js.map +1 -1
  8. package/lib/src/web.d.ts +3 -12
  9. package/lib/src/web.js +12 -102
  10. package/lib/src/web.js.map +1 -1
  11. package/package.json +4 -4
  12. package/assets/images/avif/favicon/apple-touch-icon.avif +0 -0
  13. package/assets/images/avif/favicon/favicon-96x96.avif +0 -0
  14. package/assets/images/avif/favicon/web-app-manifest-192x192.avif +0 -0
  15. package/assets/images/avif/favicon/web-app-manifest-512x512.avif +0 -0
  16. package/assets/images/webp/favicon/apple-touch-icon.webp +0 -0
  17. package/assets/images/webp/favicon/favicon-96x96.webp +0 -0
  18. package/assets/images/webp/favicon/web-app-manifest-192x192.webp +0 -0
  19. package/assets/images/webp/favicon/web-app-manifest-512x512.webp +0 -0
  20. package/assets/js/controls.js +0 -1
  21. package/assets/js/index.global.js +0 -1
  22. package/assets/js/qr.js +0 -1
  23. package/lib/src/browser/controls.d.ts +0 -1
  24. package/lib/src/browser/controls.js +0 -86
  25. package/lib/src/browser/controls.js.map +0 -1
  26. package/lib/src/browser/index.global.js +0 -10
  27. package/lib/src/browser/qr.js +0 -1636
  28. package/lib/src/cwd-checker.d.ts +0 -1
  29. package/lib/src/cwd-checker.js +0 -7
  30. package/lib/src/cwd-checker.js.map +0 -1
  31. package/lib/src/receivers/ireceiver.d.ts +0 -17
  32. package/lib/src/receivers/ireceiver.js +0 -18
  33. package/lib/src/receivers/ireceiver.js.map +0 -1
  34. package/lib/src/receivers/smtp-receiver.d.ts +0 -10
  35. package/lib/src/receivers/smtp-receiver.js +0 -38
  36. package/lib/src/receivers/smtp-receiver.js.map +0 -1
@@ -1,1636 +0,0 @@
1
- /*!
2
- Copyright (c) 2023 Paul Miller (paulmillr.com)
3
- The library paulmillr-qr is dual-licensed under the Apache 2.0 OR MIT license.
4
- You can select a license of your choice.
5
- Licensed under the Apache License, Version 2.0 (the "License");
6
- you may not use this file except in compliance with the License.
7
- You may obtain a copy of the License at
8
-
9
- http://www.apache.org/licenses/LICENSE-2.0
10
-
11
- Unless required by applicable law or agreed to in writing, software
12
- distributed under the License is distributed on an "AS IS" BASIS,
13
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- See the License for the specific language governing permissions and
15
- limitations under the License.
16
- */
17
- // We do not use newline escape code directly in strings because it's not parser-friendly
18
- const chCodes = { newline: 10, reset: 27 };
19
- function assertNumber(n) {
20
- if (!Number.isSafeInteger(n))
21
- throw new Error(`integer expected: ${n}`);
22
- }
23
- function validateVersion(ver) {
24
- if (!Number.isSafeInteger(ver) || ver < 1 || ver > 40)
25
- throw new Error(`Invalid version=${ver}. Expected number [1..40]`);
26
- }
27
- function bin(dec, pad) {
28
- return dec.toString(2).padStart(pad, '0');
29
- }
30
- function mod(a, b) {
31
- const result = a % b;
32
- return result >= 0 ? result : b + result;
33
- }
34
- function fillArr(length, val) {
35
- // Current callers only pass primitive fill values; object fills would alias references.
36
- return new Array(length).fill(val);
37
- }
38
- function popcnt(n) {
39
- n = n - ((n >>> 1) & 0x55555555);
40
- n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
41
- return (((n + (n >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
42
- }
43
- /**
44
- * Interleaves byte blocks.
45
- * @param blocks [[1, 2, 3], [4, 5, 6]]
46
- * @returns [1, 4, 2, 5, 3, 6]
47
- */
48
- function interleaveBytes(blocks) {
49
- let maxLen = 0;
50
- let totalLen = 0;
51
- for (const block of blocks) {
52
- maxLen = Math.max(maxLen, block.length);
53
- totalLen += block.length;
54
- }
55
- const result = new Uint8Array(totalLen);
56
- let idx = 0;
57
- // When block lengths differ, callers must pass the shorter blocks first so
58
- // the interleaving order matches ISO/IEC 18004 §7.6 c).
59
- for (let i = 0; i < maxLen; i++) {
60
- for (const block of blocks) {
61
- if (i < block.length)
62
- result[idx++] = block[i];
63
- }
64
- }
65
- return result;
66
- }
67
- // Optimize for minimal score/penalty
68
- function best() {
69
- let best;
70
- let bestScore = Infinity;
71
- return {
72
- add(score, value) {
73
- // Ties keep the first candidate so equal-score selections stay deterministic.
74
- if (score >= bestScore)
75
- return;
76
- best = value;
77
- bestScore = score;
78
- },
79
- get: () => best,
80
- score: () => bestScore,
81
- };
82
- }
83
- // Based on https://github.com/paulmillr/scure-base/blob/main/index.ts
84
- function alphabet(alphabet) {
85
- // Character order defines the numeric values used by the target QR mode.
86
- return Object.freeze({
87
- has: (char) => alphabet.includes(char),
88
- decode: (input) => {
89
- if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
90
- throw new Error('alphabet.decode input should be array of strings');
91
- return input.map((letter) => {
92
- if (typeof letter !== 'string')
93
- throw new Error(`alphabet.decode: not string element=${letter}`);
94
- const index = alphabet.indexOf(letter);
95
- if (index === -1)
96
- throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
97
- return index;
98
- });
99
- },
100
- encode: (digits) => {
101
- if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
102
- throw new Error('alphabet.encode input should be an array of numbers');
103
- return digits.map((i) => {
104
- assertNumber(i);
105
- if (i < 0 || i >= alphabet.length)
106
- throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
107
- return alphabet[i];
108
- });
109
- },
110
- });
111
- }
112
- // Transpose 32x32 bit matrix in-place
113
- // a[0..31] are 32 rows of 32 bits each; after transpose they become 32 columns.
114
- function transpose32(a) {
115
- if (a.length !== 32)
116
- throw new Error('expects 32 element matrix');
117
- const masks = [0x55555555, 0x33333333, 0x0f0f0f0f, 0x00ff00ff, 0x0000ffff];
118
- // Hello again, FFT
119
- for (let stage = 0; stage < 5; stage++) {
120
- const m = masks[stage] >>> 0;
121
- const s = 1 << stage; // 1,2,4,8,16
122
- const step = s << 1; // 2,4,8,16,32
123
- for (let i = 0; i < 32; i += step) {
124
- for (let k = 0; k < s; k++) {
125
- const i0 = i + k;
126
- const i1 = i0 + s;
127
- const x = a[i0] >>> 0;
128
- const y = a[i1] >>> 0;
129
- const t = ((x >>> s) ^ y) & m;
130
- a[i0] = (x ^ (t << s)) >>> 0;
131
- a[i1] = (y ^ t) >>> 0;
132
- }
133
- }
134
- }
135
- }
136
- const bitMask = (x) => (1 << (x & 31)) >>> 0;
137
- const rangeMask = (shift, len) => {
138
- // len in [0..32], shift in [0..31]
139
- if (len === 0)
140
- return 0;
141
- // Callers only request len=32 for word-aligned spans; JS shift counts wrap at 32,
142
- // so full-word masks must bypass the generic `(1 << len)` path.
143
- if (len === 32)
144
- return 0xffffffff;
145
- return (((1 << len) - 1) << shift) >>> 0;
146
- };
147
- /**
148
- * Mutable monochrome bitmap used as the internal QR representation.
149
- * @param size - Square edge length or explicit bitmap dimensions.
150
- * @param data - Optional row-major pixel matrix using `true`, `false`, or `undefined`.
151
- * @example
152
- * Create a bitmap, then scale it for display.
153
- * ```ts
154
- * import { Bitmap } from 'qr';
155
- * const bitmap = Bitmap.fromString('X \n X');
156
- * bitmap.scale(2);
157
- * ```
158
- */
159
- export class Bitmap {
160
- static size(size, limit) {
161
- if (typeof size === 'number')
162
- size = { height: size, width: size };
163
- if (!Number.isSafeInteger(size.height) && size.height !== Infinity)
164
- throw new Error(`Bitmap: invalid height=${size.height} (${typeof size.height})`);
165
- if (!Number.isSafeInteger(size.width) && size.width !== Infinity)
166
- throw new Error(`Bitmap: invalid width=${size.width} (${typeof size.width})`);
167
- if (limit !== undefined) {
168
- // Clamp length, so it won't overflow, also allows to use Infinity, so we draw until end
169
- size = {
170
- width: Math.min(size.width, limit.width),
171
- height: Math.min(size.height, limit.height),
172
- };
173
- }
174
- return size;
175
- }
176
- static fromString(s) {
177
- // Remove linebreaks on start and end, so we draw in `` section
178
- // Fixture strings use LF-delimited rows of X / space / ? characters; callers
179
- // must normalize CRLF input before handing it to this debug parser.
180
- s = s.replace(/^\n+/g, '').replace(/\n+$/g, '');
181
- const lines = s.split(String.fromCharCode(chCodes.newline));
182
- const height = lines.length;
183
- let width;
184
- const rows = [];
185
- for (const line of lines) {
186
- const row = line.split('').map((i) => {
187
- if (i === 'X')
188
- return true;
189
- if (i === ' ')
190
- return false;
191
- if (i === '?')
192
- return undefined;
193
- throw new Error(`Bitmap.fromString: unknown symbol=${i}`);
194
- });
195
- if (width !== undefined && row.length !== width)
196
- throw new Error(`Bitmap.fromString different row sizes: width=${width} cur=${row.length}`);
197
- width = row.length;
198
- rows.push(row);
199
- }
200
- if (width === undefined)
201
- width = 0;
202
- return new Bitmap({ height, width }, rows);
203
- }
204
- // Two bitsets:
205
- // defined=0 -> undefined
206
- // defined=1,value=0 -> false
207
- // defined=1,value=1 -> true
208
- defined;
209
- value;
210
- tailMask;
211
- words;
212
- fullWords;
213
- height;
214
- width;
215
- constructor(size, data) {
216
- const { height, width } = Bitmap.size(size);
217
- // Bitmap coordinates wrap through modulo for negative positions, so invalid
218
- // dimensions produce NaN, aliasing, or unsafe allocation sizes before later
219
- // drawing no-op guards can run. `Infinity` is only valid for rectangle sizes
220
- // that are clamped against an existing positive bitmap.
221
- if (!Number.isSafeInteger(height) || height <= 0)
222
- throw new Error(`Bitmap: invalid height=${height}, expected positive safe integer dimension`);
223
- if (!Number.isSafeInteger(width) || width <= 0)
224
- throw new Error(`Bitmap: invalid width=${width}, expected positive safe integer dimension`);
225
- this.height = height;
226
- this.width = width;
227
- this.tailMask = rangeMask(0, width & 31 || 32);
228
- this.words = Math.ceil(width / 32) | 0;
229
- this.fullWords = Math.floor(width / 32) | 0;
230
- this.value = new Uint32Array(this.words * height);
231
- this.defined = new Uint32Array(this.value.length);
232
- if (data) {
233
- // accept same semantics as old version
234
- if (data.length !== height)
235
- throw new Error(`Bitmap: data height mismatch: exp=${height} got=${data.length}`);
236
- for (let y = 0; y < height; y++) {
237
- const row = data[y];
238
- if (!row || row.length !== width)
239
- throw new Error(`Bitmap: data width mismatch at y=${y}: exp=${width} got=${row?.length}`);
240
- for (let x = 0; x < width; x++)
241
- this.set(x, y, row[x]);
242
- }
243
- }
244
- }
245
- point(p) {
246
- // The storage docs above say "undefined is used as a marker whether cell
247
- // was written or not"; `point()` is the detector's dark-module read and
248
- // intentionally treats both undefined and false as not-dark. Use
249
- // `isDefined()` when the written/undefined distinction matters.
250
- return this.get(p.x, p.y);
251
- }
252
- // Raw bounds check for scan loops; unlike `xy()`, this does not wrap or normalize coordinates.
253
- isInside(p) {
254
- return 0 <= p.x && p.x < this.width && 0 <= p.y && p.y < this.height;
255
- }
256
- size(offset) {
257
- if (!offset)
258
- return { height: this.height, width: this.width };
259
- const { x, y } = this.xy(offset);
260
- return { height: this.height - y, width: this.width - x };
261
- }
262
- xy(c) {
263
- if (typeof c === 'number')
264
- c = { x: c, y: c };
265
- if (!Number.isSafeInteger(c.x))
266
- throw new Error(`Bitmap: invalid x=${c.x}`);
267
- if (!Number.isSafeInteger(c.y))
268
- throw new Error(`Bitmap: invalid y=${c.y}`);
269
- // Bitmap's class docs say "For most `draw` calls, structure is mutable";
270
- // coordinate objects follow that hot-path policy too and are normalized in place.
271
- c.x = mod(c.x, this.width);
272
- c.y = mod(c.y, this.height);
273
- return c;
274
- }
275
- /**
276
- * Return pixel bit index
277
- */
278
- wordIndex(x, y) {
279
- return y * this.words + (x >>> 5);
280
- }
281
- bitIndex(x, y) {
282
- return { word: this.wordIndex(x, y), bit: x & 31 };
283
- }
284
- isDefined(x, y) {
285
- // `isInside()` is the raw bounds check; keep these bitset accessors
286
- // bounds-check-free for hot paths. Invalid tail coordinates may observe
287
- // backing-word bits, so callers that accept untrusted coordinates must
288
- // check `isInside()` first.
289
- const wi = this.wordIndex(x, y);
290
- const m = bitMask(x);
291
- return (this.defined[wi] & m) !== 0;
292
- }
293
- get(x, y) {
294
- const wi = this.wordIndex(x, y);
295
- const m = bitMask(x);
296
- return (this.value[wi] & m) !== 0;
297
- }
298
- maskWord(wi, mask, v) {
299
- const { defined, value } = this;
300
- defined[wi] |= mask;
301
- // `-v` expands the boolean to either all-zero or all-one bits before masking it into the selected lanes.
302
- value[wi] = (value[wi] & ~mask) | (-v & mask);
303
- }
304
- set(x, y, v) {
305
- // `undefined` means "leave the current cell unchanged", not "clear it back to undefined".
306
- if (v === undefined)
307
- return;
308
- // Like `get()` / `isDefined()`, this is a raw in-bounds bitset accessor;
309
- // check `isInside()` before passing untrusted coordinates.
310
- this.maskWord(this.wordIndex(x, y), bitMask(x), v);
311
- }
312
- // word-span fill for constant values (fast path)
313
- fillRectConst(x0, y0, w, h, v) {
314
- if (w <= 0 || h <= 0)
315
- return;
316
- if (v === undefined)
317
- return;
318
- const { value, defined, words } = this;
319
- const startWord = x0 >>> 5;
320
- const endWord = (x0 + w - 1) >>> 5;
321
- const startBit = x0 & 31;
322
- const endBit = (x0 + w - 1) & 31;
323
- for (let ry = 0; ry < h; ry++) {
324
- const rowBase = (y0 + ry) * words;
325
- if (startWord === endWord) {
326
- const mask = rangeMask(startBit, endBit - startBit + 1);
327
- this.maskWord(rowBase + startWord, mask, v);
328
- continue;
329
- }
330
- this.maskWord(rowBase + startWord, rangeMask(startBit, 32 - startBit), v);
331
- // Whole interior words can be written directly: every bit in the span becomes defined and equal to v.
332
- for (let i = startWord + 1; i < endWord; i++) {
333
- defined[rowBase + i] = 0xffffffff;
334
- value[rowBase + i] = v ? 0xffffffff : 0;
335
- }
336
- this.maskWord(rowBase + endWord, rangeMask(0, endBit + 1), v);
337
- }
338
- }
339
- rectWords(x, y, width, height, cb) {
340
- for (let yPos = 0; yPos < height; yPos++) {
341
- const Py = y + yPos;
342
- for (let xPos = 0; xPos < width;) {
343
- const bitX = x + xPos;
344
- const { bit, word } = this.bitIndex(bitX, Py);
345
- const bitsPerWord = Math.min(32 - bit, width - xPos);
346
- // bitX stays absolute for word-local masks; xPos/yPos stay rectangle-local for rect callbacks.
347
- cb(word, bitX, xPos, yPos, bitsPerWord);
348
- xPos += bitsPerWord;
349
- }
350
- }
351
- }
352
- // Basically every operation can be represented as rect
353
- rect(c, size, fn) {
354
- const { x, y } = this.xy(c);
355
- const { height, width } = Bitmap.size(size, this.size({ x, y }));
356
- if (typeof fn !== 'function') {
357
- this.fillRectConst(x, y, width, height, fn);
358
- return this;
359
- }
360
- const { defined, value } = this;
361
- this.rectWords(x, y, width, height, (wi, bitX, xPos, yPos, n) => {
362
- let defWord = 0;
363
- let valWord = value[wi];
364
- for (let b = 0; b < n; b++) {
365
- const mask = bitMask(bitX + b);
366
- // As with `point()`, callback `cur` is a dark/not-dark read; the
367
- // storage-level "undefined is used as a marker whether cell was
368
- // written or not" distinction is checked separately with `isDefined()`.
369
- const res = fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
370
- // Returning undefined from the callback keeps the existing cell unchanged.
371
- if (res === undefined)
372
- continue;
373
- defWord |= mask;
374
- valWord = (valWord & ~mask) | (-res & mask);
375
- }
376
- defined[wi] |= defWord;
377
- value[wi] = valWord;
378
- });
379
- return this;
380
- }
381
- // returns rectangular part of bitmap
382
- rectRead(c, size, fn) {
383
- const { x, y } = this.xy(c);
384
- const { height, width } = Bitmap.size(size, this.size({ x, y }));
385
- const { value } = this;
386
- this.rectWords(x, y, width, height, (wi, bitX, xPos, yPos, n) => {
387
- const valWord = value[wi];
388
- for (let b = 0; b < n; b++) {
389
- const mask = bitMask(bitX + b);
390
- // rectRead is non-mutating; callback coordinates are rectangle-local,
391
- // and `cur` is the same dark/not-dark read as `point()`.
392
- fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
393
- }
394
- });
395
- return this;
396
- }
397
- // Horizontal & vertical lines
398
- hLine(c, len, value) {
399
- return this.rect(c, { width: len, height: 1 }, value);
400
- }
401
- vLine(c, len, value) {
402
- return this.rect(c, { width: 1, height: len }, value);
403
- }
404
- // add border
405
- border(border = 2, value) {
406
- // `border` is used both as output-size delta and as embed coordinate; keep
407
- // it a positive safe integer before those paths allocate or normalize.
408
- if (!Number.isSafeInteger(border) || border <= 0)
409
- throw new Error(`Bitmap.border: invalid size=${border}`);
410
- const height = this.height + 2 * border;
411
- const width = this.width + 2 * border;
412
- const out = new Bitmap({ height, width });
413
- // fill everything with border value, then embed original
414
- out.rect(0, Infinity, value);
415
- out.embed({ x: border, y: border }, this);
416
- return out;
417
- }
418
- // Embed another bitmap on coordinates
419
- embed(c, src) {
420
- const { x, y } = this.xy(c);
421
- const { height, width } = Bitmap.size(src.size(), this.size({ x, y }));
422
- if (width <= 0 || height <= 0)
423
- return this;
424
- const { value, defined } = this;
425
- const { words: srcStride, value: srcValue } = src;
426
- // The Bitmap storage docs say "undefined is used as a marker whether cell
427
- // was written or not"; `embed()` is the packed blit path for materialized
428
- // source bitmaps, so it flattens the source rectangle to defined dark/light
429
- // bits instead of treating undefined cells as transparent.
430
- for (let yPos = 0; yPos < height; yPos++) {
431
- const srcRow = yPos * srcStride;
432
- for (let xPos = 0; xPos < width;) {
433
- const dstX = x + xPos;
434
- const { word: dstWord, bit: dstBit } = this.bitIndex(dstX, y + yPos);
435
- const { word: srcWord, bit: srcBit } = src.bitIndex(xPos, yPos);
436
- const len = Math.min(32 - dstBit, width - xPos);
437
- const w0 = srcValue[srcWord];
438
- const w1 = srcBit && srcWord + 1 < srcRow + srcStride ? srcValue[srcWord + 1] : 0;
439
- // Source and destination bit offsets may differ, so assemble the source span from up to two words.
440
- const sVal = srcBit ? ((w0 >>> srcBit) | (w1 << (32 - srcBit))) >>> 0 : w0;
441
- const dstMask = rangeMask(dstBit, len);
442
- const valBits = ((sVal & rangeMask(0, len)) << dstBit) >>> 0;
443
- defined[dstWord] |= dstMask;
444
- value[dstWord] = (value[dstWord] & ~dstMask) | valBits;
445
- xPos += len;
446
- }
447
- }
448
- return this;
449
- }
450
- // returns rectangular part of bitmap
451
- rectSlice(c, size = this.size()) {
452
- const { x, y } = this.xy(c);
453
- const { height, width } = Bitmap.size(size, this.size({ x, y }));
454
- const rect = new Bitmap({ height, width });
455
- this.rectRead({ x, y }, { height, width }, (p, cur) => {
456
- // rectRead reports undefined cells as false, so copy only when the source defined bit is set.
457
- if (this.isDefined(x + p.x, y + p.y)) {
458
- rect.set(p.x, p.y, cur);
459
- }
460
- });
461
- return rect;
462
- }
463
- // Change shape, replace rows with columns (data[y][x] -> data[x][y])
464
- transpose() {
465
- const { height, width, value, defined, words } = this;
466
- const dst = new Bitmap({ height: width, width: height });
467
- const { words: dstStride, value: dstValue, defined: dstDefined, tailMask: dstTail } = dst;
468
- const tmpV = new Uint32Array(32);
469
- const tmpD = new Uint32Array(32);
470
- // Process src in blocks: y in [by..by+31], x in 32-bit words
471
- for (let by = 0; by < height; by += 32) {
472
- for (let bx = 0; bx < words; bx++) {
473
- const rows = Math.min(32, height - by);
474
- for (let r = 0; r < rows; r++) {
475
- const wi = this.wordIndex(32 * bx, by + r);
476
- tmpV[r] = value[wi];
477
- tmpD[r] = defined[wi];
478
- }
479
- // zero-pad remainder
480
- tmpV.fill(0, rows);
481
- tmpD.fill(0, rows);
482
- transpose32(tmpV);
483
- transpose32(tmpD);
484
- for (let i = 0; i < 32; i++) {
485
- const dstY = bx * 32 + i;
486
- if (dstY >= width)
487
- break;
488
- const dstPos = dst.wordIndex(by, dstY);
489
- const curMask = by >>> 5 === dstStride - 1 ? dstTail : 0xffffffff;
490
- dstValue[dstPos] = tmpV[i] & curMask;
491
- dstDefined[dstPos] = tmpD[i] & curMask;
492
- }
493
- }
494
- }
495
- return dst;
496
- }
497
- // black <-> white (inplace)
498
- negate() {
499
- const n = this.defined.length;
500
- for (let i = 0; i < n; i++) {
501
- // ISO/IEC 18004:2024 §12 b)5 says to "reverse the colouring of the light
502
- // and dark pixels"; this dense scratch-bitmap operation materializes every
503
- // backing bit as defined and does not preserve sparse/undefined cells.
504
- this.value[i] = ~this.value[i];
505
- this.defined[i] = 0xffffffff;
506
- }
507
- return this;
508
- }
509
- // Each pixel size is multiplied by factor
510
- scale(factor) {
511
- if (!Number.isSafeInteger(factor) || factor > 1024)
512
- throw new Error(`invalid scale factor: ${factor}`);
513
- const { height, width } = this;
514
- // Bitmap storage docs say "undefined is used as a marker whether cell was
515
- // written or not"; `scale()` is an output materialization path and samples
516
- // with `get()`, so sparse cells become defined light cells. Positive output
517
- // dimensions stay validated by the Bitmap constructor instead of duplicating
518
- // dimension checks in every caller that computes a new bitmap size.
519
- const res = new Bitmap({ height: factor * height, width: factor * width });
520
- return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.get((x / factor) | 0, (y / factor) | 0));
521
- }
522
- clone() {
523
- const res = new Bitmap(this.size());
524
- res.defined.set(this.defined);
525
- res.value.set(this.value);
526
- return res;
527
- }
528
- // Ensure that there is no undefined values left
529
- assertDrawn() {
530
- const { height, width, defined, tailMask, fullWords, words } = this;
531
- if (!height || !width)
532
- return;
533
- for (let y = 0; y < height; y++) {
534
- const rowBase = y * words;
535
- for (let wi = 0; wi < fullWords; wi++) {
536
- if (defined[rowBase + wi] !== 0xffffffff)
537
- throw new Error(`Invalid color type=undefined`);
538
- }
539
- if (words !== fullWords && (defined[rowBase + fullWords] & tailMask) !== tailMask)
540
- throw new Error(`Invalid color type=undefined`);
541
- }
542
- }
543
- countPatternInRow(y, patternLen, ...patterns) {
544
- // Penalty scanning only passes Table 11 windows over bounded symbol rows;
545
- // validate this public helper before JS shifts / typed-array reads coerce bad inputs.
546
- if (!Number.isSafeInteger(patternLen) || patternLen <= 0 || patternLen >= 32)
547
- throw new Error('wrong patternLen');
548
- const mask = (1 << patternLen) - 1;
549
- const { height, width, value, words } = this;
550
- if (!Number.isSafeInteger(y) || y < 0 || y >= height)
551
- return 0;
552
- let count = 0;
553
- const rowBase = this.wordIndex(0, y);
554
- for (let i = 0, window = 0; i < words; i++) {
555
- const w = value[rowBase + i];
556
- const bitEnd = i === words - 1 ? width & 31 || 32 : 32;
557
- for (let b = 0; b < bitEnd; b++) {
558
- window = ((window << 1) | ((w >>> b) & 1)) & mask;
559
- if (i * 32 + b + 1 < patternLen)
560
- continue;
561
- for (const p of patterns) {
562
- if (window !== p)
563
- continue;
564
- count++;
565
- break;
566
- }
567
- }
568
- }
569
- return count;
570
- }
571
- getRuns(y, fn) {
572
- const { height, width, value, words } = this;
573
- if (width === 0)
574
- return;
575
- // ISO/IEC 18004:2024 §7.8.3.1 N1 scans adjacent modules in bounded rows
576
- // and columns; validate this public helper before missing typed-array rows
577
- // are coerced into all-light runs by bitwise operators.
578
- if (!Number.isSafeInteger(y) || y < 0 || y >= height)
579
- return;
580
- let runLen = 0;
581
- let runValue;
582
- const rowBase = this.wordIndex(0, y);
583
- for (let i = 0; i < words; i++) {
584
- const word = value[rowBase + i];
585
- const bitEnd = i === words - 1 ? width & 31 || 32 : 32;
586
- for (let b = 0; b < bitEnd; b++) {
587
- const bit = (word & (1 << b)) !== 0;
588
- if (bit === runValue) {
589
- runLen++;
590
- continue;
591
- }
592
- if (runValue !== undefined)
593
- fn(runLen, runValue);
594
- runValue = bit;
595
- runLen = 1;
596
- }
597
- }
598
- if (runValue !== undefined)
599
- fn(runLen, runValue);
600
- }
601
- popcnt() {
602
- const { height, width, words, fullWords, tailMask } = this;
603
- if (!height || !width)
604
- return 0;
605
- let count = 0;
606
- for (let y = 0; y < height; y++) {
607
- const rowBase = y * words;
608
- for (let wi = 0; wi < fullWords; wi++)
609
- count += popcnt(this.value[rowBase + wi]);
610
- if (words !== fullWords)
611
- count += popcnt(this.value[rowBase + fullWords] & tailMask);
612
- }
613
- return count;
614
- }
615
- countBoxes2x2(y) {
616
- const { height, width, words } = this;
617
- // ISO/IEC 18004:2024 §7.8.3.1 N2 counts 2 x 2 module blocks in bounded
618
- // rows; reject non-integer scan rows before bitwise coercions truncate them.
619
- if (width < 2 || !Number.isSafeInteger(y) || y < 0 || y + 1 >= height)
620
- return 0;
621
- const base0 = this.wordIndex(0, y);
622
- const base1 = this.wordIndex(0, y + 1);
623
- // valid "left-edge" positions x in [0 .. W-2]
624
- const tailBits = width & 31;
625
- const validLast = tailBits === 0 ? 0x7fffffff : rangeMask(0, (width - 1) & 31);
626
- let boxes = 0;
627
- for (let wi = 0; wi < words; wi++) {
628
- const a0 = this.value[base0 + wi];
629
- const a1 = this.value[base1 + wi];
630
- // Compare bit x with bit x+1 at same bit position.
631
- const eqV = ~(a0 ^ a1) >>> 0; // row0[x] == row1[x]
632
- const n0 = wi + 1 < words ? this.value[base0 + wi + 1] >>> 0 : 0;
633
- const eqH0 = ~(a0 ^ (((a0 >>> 1) | ((n0 & 1) << 31)) >>> 0)) >>> 0; // row0[x] == row0[x+1]
634
- const n1 = wi + 1 < words ? this.value[base1 + wi + 1] >>> 0 : 0;
635
- const eqH1 = ~(a1 ^ (((a1 >>> 1) | ((n1 & 1) << 31)) >>> 0)) >>> 0; // row1[x] == row1[x+1]
636
- let m = (eqV & eqH0 & eqH1) >>> 0;
637
- if (wi === words - 1)
638
- m &= validLast;
639
- boxes += popcnt(m);
640
- }
641
- return boxes;
642
- }
643
- // Export
644
- toString() {
645
- const nl = String.fromCharCode(chCodes.newline);
646
- let out = '';
647
- for (let y = 0; y < this.height; y++) {
648
- let line = '';
649
- for (let x = 0; x < this.width; x++) {
650
- const v = this.get(x, y);
651
- line += !this.isDefined(x, y) ? '?' : v ? 'X' : ' ';
652
- }
653
- out += line + (y + 1 === this.height ? '' : nl);
654
- }
655
- return out;
656
- }
657
- toRaw() {
658
- const out = Array.from({ length: this.height }, () => new Array(this.width));
659
- for (let y = 0; y < this.height; y++) {
660
- const row = out[y];
661
- // Bitmap storage docs say "undefined is used as a marker whether cell was
662
- // written or not"; `toRaw()` is the materialized dark/not-dark output path
663
- // used after `encodeQR()` asserts the QR symbol is fully drawn.
664
- for (let x = 0; x < this.width; x++)
665
- row[x] = this.get(x, y);
666
- }
667
- return out;
668
- }
669
- toASCII() {
670
- const { height, width } = this;
671
- let out = '';
672
- // Terminal character height is x2 of character width, so we process two rows of bitmap
673
- // to produce one row of ASCII
674
- for (let y = 0; y < height; y += 2) {
675
- for (let x = 0; x < width; x++) {
676
- const first = this.get(x, y);
677
- const second = y + 1 >= height ? true : this.get(x, y + 1); // if last row outside bitmap, make it black
678
- if (!first && !second)
679
- out += '█'; // both rows white (empty)
680
- else if (!first && second)
681
- out += '▀'; // top row white
682
- else if (first && !second)
683
- out += '▄'; // down row white
684
- else if (first && second)
685
- out += ' '; // both rows black
686
- }
687
- out += String.fromCharCode(chCodes.newline);
688
- }
689
- return out;
690
- }
691
- toTerm() {
692
- const cc = String.fromCharCode(chCodes.reset);
693
- const reset = cc + '[0m';
694
- const whiteBG = cc + '[1;47m ' + reset;
695
- const darkBG = cc + `[40m ` + reset;
696
- const nl = String.fromCharCode(chCodes.newline);
697
- let out = '';
698
- for (let y = 0; y < this.height; y++) {
699
- for (let x = 0; x < this.width; x++) {
700
- const v = this.get(x, y); // undefined -> white
701
- out += v ? darkBG : whiteBG;
702
- }
703
- out += nl;
704
- }
705
- return out;
706
- }
707
- toSVG(optimize = true) {
708
- let out = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`;
709
- // ISO/IEC 18004:2024 §5.1 c) / §5.3.8: this SVG draws only dark modules;
710
- // callers must render it on a light background for light modules and the quiet zone.
711
- // Construct optimized SVG path data.
712
- let pathData = '';
713
- let prevPoint;
714
- this.rectRead(0, Infinity, (point, val) => {
715
- if (!val)
716
- return;
717
- const { x, y } = point;
718
- if (!optimize) {
719
- out += `<rect x="${x}" y="${y}" width="1" height="1" />`;
720
- return;
721
- }
722
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/d#path_commands
723
- // Determine the shortest way to represent the initial cursor movement.
724
- // M - Move cursor (without drawing) to absolute coordinate pair.
725
- let m = `M${x} ${y}`;
726
- // Only allow using the relative cursor move command if previous points
727
- // were drawn.
728
- if (prevPoint) {
729
- // m - Move cursor (without drawing) to relative coordinate pair.
730
- const relM = `m${x - prevPoint.x} ${y - prevPoint.y}`;
731
- if (relM.length <= m.length)
732
- m = relM;
733
- }
734
- // Determine the shortest way to represent the cell's bottom line draw.
735
- // H - Draw line from cursor position to absolute x coordinate.
736
- // h - Draw line from cursor position to relative x coordinate.
737
- const bH = x < 10 ? `H${x}` : 'h-1';
738
- // v - Draw line from cursor position to relative y coordinate.
739
- // Z - Close path (draws line from cursor position to M coordinate).
740
- pathData += `${m}h1v1${bH}Z`;
741
- prevPoint = point;
742
- });
743
- if (optimize)
744
- out += `<path d="${pathData}"/>`;
745
- out += `</svg>`;
746
- return out;
747
- }
748
- toGIF() {
749
- // NOTE: Small, but inefficient implementation.
750
- // Uses 1 byte per pixel.
751
- const u16le = (i) => [i & 0xff, (i >>> 8) & 0xff];
752
- const dims = [...u16le(this.width), ...u16le(this.height)];
753
- const data = [];
754
- // Palette index 0 is white/light and index 1 is black/dark; rectRead maps undefined cells to light.
755
- this.rectRead(0, Infinity, (_, cur) => data.push(+(cur === true)));
756
- // Each chunk starts with an LZW clear code; 126 raw pixels keep codes at 8 bits until the next clear.
757
- const N = 126; // Block size
758
- // prettier-ignore
759
- const bytes = [
760
- 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, ...dims, 0xf6, 0x00, 0x00, 0xff, 0xff, 0xff,
761
- ...fillArr(3 * 127, 0x00), 0x2c, 0x00, 0x00, 0x00, 0x00, ...dims, 0x00, 0x07
762
- ];
763
- const fullChunks = Math.floor(data.length / N);
764
- // Full blocks
765
- for (let i = 0; i < fullChunks; i++)
766
- bytes.push(N + 1, 0x80, ...data.slice(N * i, N * (i + 1)).map((i) => +i));
767
- // Remaining bytes
768
- bytes.push((data.length % N) + 1, 0x80, ...data.slice(fullChunks * N).map((i) => +i));
769
- bytes.push(0x01, 0x81, 0x00, 0x3b);
770
- return new Uint8Array(bytes);
771
- }
772
- toImage(isRGB = false) {
773
- const { height, width } = this.size();
774
- const data = new Uint8Array(height * width * (isRGB ? 3 : 4));
775
- let i = 0;
776
- for (let y = 0; y < height; y++) {
777
- for (let x = 0; x < width; x++) {
778
- const value = this.get(x, y) ? 0 : 255; // undefined -> white
779
- data[i++] = value;
780
- data[i++] = value;
781
- data[i++] = value;
782
- if (!isRGB)
783
- data[i++] = 255; // alpha channel
784
- }
785
- }
786
- return { height, width, data };
787
- }
788
- }
789
- // End of utils
790
- // Runtime type-checking
791
- /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30%. */
792
- export const ECMode = /* @__PURE__ */ Object.freeze(['low', 'medium', 'quartile', 'high']);
793
- /**
794
- * QR payload compaction mode names recognized by the type/validator.
795
- * `kanji` and `eci` are spec modes, but `encodeQR` currently rejects them until implemented.
796
- */
797
- export const Encoding =
798
- /* @__PURE__ */ Object.freeze(['numeric', 'alphanumeric', 'byte', 'kanji', 'eci']);
799
- // Various constants & tables
800
- // ISO/IEC 18004:2024 Table 1: QR symbol codeword capacity by version (data plus error correction).
801
- // prettier-ignore
802
- const BYTES = [
803
- // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
804
- 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085,
805
- // 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
806
- 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706,
807
- ];
808
- // ISO/IEC 18004:2024 Table 9: error correction codewords per block by version and level.
809
- // prettier-ignore
810
- const WORDS_PER_BLOCK = {
811
- // Version 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
812
- low: [7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
813
- medium: [10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
814
- quartile: [13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
815
- high: [17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
816
- };
817
- // ISO/IEC 18004:2024 Table 9: error correction block count by version and level.
818
- // prettier-ignore
819
- const ECC_BLOCKS = {
820
- // Version 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
821
- low: [1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
822
- medium: [1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
823
- quartile: [1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
824
- high: [1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81],
825
- };
826
- // ISO/IEC 18004:2024 sections 5.3/7.4/7.5/7.9/7.10: QR layout, segment, format/version, and capacity helpers.
827
- const info = /* @__PURE__ */ Object.freeze({
828
- size: /* @__PURE__ */ Object.freeze({
829
- encode: (ver) => 21 + 4 * (ver - 1), // ver1 = 21, ver40 = 177 modules per side
830
- decode: (size) => (size - 17) / 4,
831
- }),
832
- // ISO/IEC 18004:2024 Table 3: map version ranges 1-9, 10-26, and 27-40 to count-width indexes.
833
- sizeType: (ver) => Math.floor((ver + 7) / 17),
834
- // ISO/IEC 18004:2024 Annex E Table E.1: row/column coordinate list of alignment-pattern centres.
835
- // Based on https://codereview.stackexchange.com/questions/74925/algorithm-to-generate-this-alignment-pattern-locations-table-for-qr-codes
836
- alignmentPatterns(ver) {
837
- if (ver === 1)
838
- return [];
839
- const first = 6;
840
- const last = info.size.encode(ver) - first - 1;
841
- const distance = last - first;
842
- const count = Math.ceil(distance / 28);
843
- let interval = Math.floor(distance / count);
844
- if (interval % 2)
845
- interval += 1;
846
- else if ((distance % count) * 2 >= count)
847
- interval += 2;
848
- const res = [first];
849
- for (let m = 1; m < count; m++)
850
- res.push(last - (count - m) * interval);
851
- res.push(last);
852
- return res;
853
- },
854
- // ISO/IEC 18004:2024 §7.9.1 Table 12: error-correction-level indicators for the top two format-information data bits.
855
- ECCode: /* @__PURE__ */ Object.freeze({
856
- low: 0b01,
857
- medium: 0b00,
858
- quartile: 0b11,
859
- high: 0b10,
860
- }),
861
- // ISO/IEC 18004:2024 §7.9.1 final paragraph: XOR the 15-bit format information with mask pattern 101010000010010.
862
- formatMask: 0b101010000010010,
863
- // ISO/IEC 18004:2024 §7.9.1 / Annex C.2: append the 10-bit BCH remainder for the 5 data bits, then apply the fixed QR format mask.
864
- formatBits(ecc, maskIdx) {
865
- const data = (info.ECCode[ecc] << 3) | maskIdx;
866
- let d = data;
867
- for (let i = 0; i < 10; i++)
868
- d = (d << 1) ^ ((d >> 9) * 0b10100110111);
869
- return ((data << 10) | d) ^ info.formatMask;
870
- },
871
- // ISO/IEC 18004:2024 §7.10 / Annex D.2: append the 12-bit Golay remainder to the 6-bit version word; version information is not masked.
872
- versionBits(ver) {
873
- let d = ver;
874
- for (let i = 0; i < 12; i++)
875
- d = (d << 1) ^ ((d >> 11) * 0b1111100100101);
876
- return (ver << 12) | d;
877
- },
878
- // ISO/IEC 18004:2024 §7.3.3 / §7.3.4 / §7.4.5 Table 5: character-set membership and value codecs for numeric and alphanumeric QR modes.
879
- alphabet: /* @__PURE__ */ Object.freeze({
880
- // ISO/IEC 18004:2024 §7.3.3 / §7.4.4: numeric-mode digits map directly to values 0..9 before 3-digit grouping.
881
- numeric: alphabet('0123456789'),
882
- // ISO/IEC 18004:2024 §7.3.4 / §7.4.5 Table 5: 45-character alphanumeric-mode value order used for 11-bit pair packing. Keep the legacy `alphanumerc` key name in sync with existing callers.
883
- alphanumerc: alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'),
884
- }), // as Record<EncodingType, ReturnType<typeof alphabet>>,
885
- // ISO/IEC 18004:2024 Table 3 gives QR character-count widths for data modes; ECI headers instead carry only the mode indicator plus the designator from §7.4.3.
886
- lengthBits(ver, type) {
887
- const table = {
888
- numeric: [10, 12, 14],
889
- alphanumeric: [9, 11, 13],
890
- byte: [8, 16, 16],
891
- kanji: [8, 10, 12],
892
- eci: [0, 0, 0],
893
- };
894
- return table[type][info.sizeType(ver)];
895
- },
896
- // ISO/IEC 18004:2024 §7.4.2 Table 2: 4-bit QR mode indicators for the segment types this library models.
897
- modeBits: /* @__PURE__ */ Object.freeze({
898
- numeric: '0001',
899
- alphanumeric: '0010',
900
- byte: '0100',
901
- kanji: '1000',
902
- eci: '0111',
903
- }),
904
- // ISO/IEC 18004:2024 Table 1 / §7.5.1 Table 9: derive total data bits and short/long RS block layout from total codewords, ECC words per block, and block counts.
905
- capacity(ver, ecc) {
906
- const bytes = BYTES[ver - 1];
907
- const words = WORDS_PER_BLOCK[ecc][ver - 1];
908
- const numBlocks = ECC_BLOCKS[ecc][ver - 1];
909
- const blockLen = Math.floor(bytes / numBlocks) - words;
910
- const shortBlocks = numBlocks - (bytes % numBlocks);
911
- return {
912
- words,
913
- numBlocks,
914
- shortBlocks,
915
- blockLen,
916
- capacity: (bytes - words * numBlocks) * 8,
917
- total: (words + blockLen) * numBlocks + numBlocks - shortBlocks,
918
- };
919
- },
920
- });
921
- // ISO/IEC 18004:2024 Table 10: QR data-mask predicates 000..111, written here in (x column, y row) form.
922
- const PATTERNS = /* @__PURE__ */ Object.freeze([
923
- (x, y) => (x + y) % 2 == 0,
924
- (_x, y) => y % 2 == 0,
925
- (x, _y) => x % 3 == 0,
926
- (x, y) => (x + y) % 3 == 0,
927
- (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 == 0,
928
- (x, y) => ((x * y) % 2) + ((x * y) % 3) == 0,
929
- (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0,
930
- (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0,
931
- ]);
932
- // Galois field && reed-solomon encoding
933
- // ISO/IEC 18004:2024 §7.5.2 / Annex A / Annex B: GF(2^8) field and polynomial helpers shared by QR Reed-Solomon parity generation and decoding.
934
- const GF = {
935
- tables: ((p_poly) => {
936
- const exp = fillArr(256, 0);
937
- const log = fillArr(256, 0);
938
- for (let i = 0, x = 1; i < 256; i++) {
939
- exp[i] = x;
940
- log[x] = i;
941
- x <<= 1;
942
- if (x & 0x100)
943
- x ^= p_poly;
944
- }
945
- // Keep α^255 = 1 in exp[255]; GF.log() folds the matching log[1] = 255
946
- // back to 0 with `% 255`, so later helpers can wrap exponents without a special case.
947
- return { exp, log };
948
- })(0x11d),
949
- // Raw α^i lookup from the precomputed field table; callers are expected
950
- // to reduce / validate exponents before indexing it.
951
- exp: (x) => GF.tables.exp[x],
952
- // log(0) is undefined in GF(2^8); `% 255` also folds the wrapped table
953
- // entry for α^255 = 1 back to exponent 0.
954
- log(x) {
955
- if (x === 0)
956
- throw new Error(`GF.log: invalid arg=${x}`);
957
- return GF.tables.log[x] % 255;
958
- },
959
- // Zero has no logarithm in GF(2^8), so it must short-circuit here; all
960
- // other products are α^(log(x) + log(y) mod 255) in the reviewed field.
961
- mul(x, y) {
962
- if (x === 0 || y === 0)
963
- return 0;
964
- return GF.tables.exp[(GF.tables.log[x] + GF.tables.log[y]) % 255];
965
- },
966
- // In characteristic 2 fields, addition and subtraction are the same
967
- // bitwise XOR operation used by the QR Reed-Solomon arithmetic.
968
- add: (x, y) => x ^ y,
969
- // Raw nonzero field power helper. Current QR use is GF.pow(2, i) for the
970
- // Annex A generator factors; x = 0 or negative exponents are not validated.
971
- pow: (x, e) => GF.tables.exp[(GF.tables.log[x] * e) % 255],
972
- // Multiplicative inverse for nonzero field elements. Current callers only
973
- // use it on values already known to be nonzero; 0 has no inverse in GF(2^8).
974
- inv(x) {
975
- if (x === 0)
976
- throw new Error(`GF.inverse: invalid arg=${x}`);
977
- return GF.tables.exp[255 - GF.tables.log[x]];
978
- },
979
- // Canonicalize coefficient arrays by trimming leading zero coefficients
980
- // while preserving `[0]` as the zero polynomial; already-normalized inputs
981
- // are returned by reference.
982
- polynomial(poly) {
983
- if (poly.length == 0)
984
- throw new Error('GF.polymomial: invalid length');
985
- if (poly[0] !== 0)
986
- return poly;
987
- // Strip leading zeros
988
- let i = 0;
989
- for (; i < poly.length - 1 && poly[i] == 0; i++)
990
- ;
991
- return poly.slice(i);
992
- },
993
- // Represent c*x^degree in the descending-power coefficient layout used
994
- // by the QR Reed-Solomon helpers; coefficient 0 canonicalizes to `[0]`.
995
- monomial(degree, coefficient) {
996
- if (degree < 0)
997
- throw new Error(`GF.monomial: invalid degree=${degree}`);
998
- if (coefficient == 0)
999
- return [0];
1000
- let coefficients = fillArr(degree + 1, 0);
1001
- coefficients[0] = coefficient;
1002
- return GF.polynomial(coefficients);
1003
- },
1004
- // Canonical polynomials keep the highest-order coefficient first and use
1005
- // `[0]` for zero, so degree is just `length - 1`.
1006
- degree: (a) => a.length - 1,
1007
- // Read the coefficient for x^degree from the descending-power array layout.
1008
- // Canonical arrays make this a direct index; out-of-range degrees return `undefined`.
1009
- coefficient: (a, degree) => a[GF.degree(a) - degree],
1010
- // Multiply descending-power coefficient arrays by convolution over GF(2^8).
1011
- // Zero short-circuits here before the log-based field multiply is consulted.
1012
- mulPoly(a, b) {
1013
- if (a[0] === 0 || b[0] === 0)
1014
- return [0];
1015
- const res = fillArr(a.length + b.length - 1, 0);
1016
- for (let i = 0; i < a.length; i++) {
1017
- for (let j = 0; j < b.length; j++) {
1018
- res[i + j] = GF.add(res[i + j], GF.mul(a[i], b[j]));
1019
- }
1020
- }
1021
- return GF.polynomial(res);
1022
- },
1023
- // Scale every coefficient by the same field element in descending-power order.
1024
- // Scalar 0 canonicalizes to `[0]`, and scalar 1 reuses the original array.
1025
- mulPolyScalar(a, scalar) {
1026
- if (scalar == 0)
1027
- return [0];
1028
- if (scalar == 1)
1029
- return a;
1030
- const res = fillArr(a.length, 0);
1031
- for (let i = 0; i < a.length; i++)
1032
- res[i] = GF.mul(a[i], scalar);
1033
- return GF.polynomial(res);
1034
- },
1035
- // Multiply a polynomial by c*x^degree in descending-power coefficient form.
1036
- // This scales existing coefficients, then appends trailing zero coefficients.
1037
- mulPolyMonomial(a, degree, coefficient) {
1038
- if (degree < 0)
1039
- throw new Error('GF.mulPolyMonomial: invalid degree');
1040
- if (coefficient == 0)
1041
- return [0];
1042
- const res = fillArr(a.length + degree, 0);
1043
- for (let i = 0; i < a.length; i++)
1044
- res[i] = GF.mul(a[i], coefficient);
1045
- return GF.polynomial(res);
1046
- },
1047
- // Add descending-power coefficient arrays with GF(2^8) XOR on the aligned
1048
- // suffix; `[0]` short-circuits by returning the other array unchanged.
1049
- addPoly(a, b) {
1050
- if (a[0] === 0)
1051
- return b;
1052
- if (b[0] === 0)
1053
- return a;
1054
- let smaller = a;
1055
- let larger = b;
1056
- if (smaller.length > larger.length)
1057
- [smaller, larger] = [larger, smaller];
1058
- let sumDiff = fillArr(larger.length, 0);
1059
- let lengthDiff = larger.length - smaller.length;
1060
- let s = larger.slice(0, lengthDiff);
1061
- for (let i = 0; i < s.length; i++)
1062
- sumDiff[i] = s[i];
1063
- for (let i = lengthDiff; i < larger.length; i++)
1064
- sumDiff[i] = GF.add(smaller[i - lengthDiff], larger[i]);
1065
- return GF.polynomial(sumDiff);
1066
- },
1067
- // Synthetic division for monic divisors in descending-power coefficient form.
1068
- // Callers are expected to append `divisor.length - 1` zero coefficients first.
1069
- remainderPoly(data, divisor) {
1070
- const out = Array.from(data);
1071
- for (let i = 0; i < data.length - divisor.length + 1; i++) {
1072
- const elm = out[i];
1073
- if (elm === 0)
1074
- continue;
1075
- for (let j = 1; j < divisor.length; j++) {
1076
- if (divisor[j] !== 0)
1077
- out[i + j] = GF.add(out[i + j], GF.mul(divisor[j], elm));
1078
- }
1079
- }
1080
- return out.slice(data.length - divisor.length + 1, out.length);
1081
- },
1082
- // Build Annex A's monic generator polynomial g_n(x) = Π(x - 2^i).
1083
- // degree=0 returns `[1]`; callers are expected to validate degree bounds.
1084
- divisorPoly(degree) {
1085
- let g = [1];
1086
- for (let i = 0; i < degree; i++)
1087
- g = GF.mulPoly(g, [1, GF.pow(2, i)]);
1088
- return g;
1089
- },
1090
- // Evaluate a descending-power coefficient array at `a` with Horner's rule.
1091
- // The `a == 0` fast-path returns the x^0 coefficient directly.
1092
- evalPoly(poly, a) {
1093
- if (a == 0)
1094
- return GF.coefficient(poly, 0); // Just return the x^0 coefficient
1095
- let res = poly[0];
1096
- for (let i = 1; i < poly.length; i++)
1097
- res = GF.add(GF.mul(a, res), poly[i]);
1098
- return res;
1099
- },
1100
- // TODO: cleanup
1101
- // Extended Euclidean RS step: derive the locator/evaluator pair from x^R
1102
- // and the syndrome polynomial, then normalize sigma(0) to 1.
1103
- euclidian(a, b, R) {
1104
- // Force degree(a) >= degree(b)
1105
- if (GF.degree(a) < GF.degree(b))
1106
- [a, b] = [b, a];
1107
- let rLast = a;
1108
- let r = b;
1109
- let tLast = [0];
1110
- let t = [1];
1111
- // while degree of Ri ≥ t/2
1112
- while (2 * GF.degree(r) >= R) {
1113
- let rLastLast = rLast;
1114
- let tLastLast = tLast;
1115
- rLast = r;
1116
- tLast = t;
1117
- if (rLast[0] === 0)
1118
- throw new Error('rLast[0] === 0');
1119
- r = rLastLast;
1120
- let q = [0];
1121
- const dltInverse = GF.inv(rLast[0]);
1122
- while (GF.degree(r) >= GF.degree(rLast) && r[0] !== 0) {
1123
- const degreeDiff = GF.degree(r) - GF.degree(rLast);
1124
- const scale = GF.mul(r[0], dltInverse);
1125
- q = GF.addPoly(q, GF.monomial(degreeDiff, scale));
1126
- r = GF.addPoly(r, GF.mulPolyMonomial(rLast, degreeDiff, scale));
1127
- }
1128
- q = GF.mulPoly(q, tLast);
1129
- t = GF.addPoly(q, tLastLast);
1130
- if (GF.degree(r) >= GF.degree(rLast))
1131
- throw new Error(`Division failed r: ${r}, rLast: ${rLast}`);
1132
- }
1133
- const sigmaTildeAtZero = GF.coefficient(t, 0);
1134
- if (sigmaTildeAtZero == 0)
1135
- throw new Error('sigmaTilde(0) was zero');
1136
- const inverse = GF.inv(sigmaTildeAtZero);
1137
- return [GF.mulPolyScalar(t, inverse), GF.mulPolyScalar(r, inverse)];
1138
- },
1139
- };
1140
- // Per-block Reed-Solomon coder: encode emits only the parity bytes for one
1141
- // data block, while decode expects data+parity bytes and returns the corrected full block.
1142
- function RS(eccWords) {
1143
- return {
1144
- encode(from) {
1145
- const d = GF.divisorPoly(eccWords);
1146
- const pol = Array.from(from);
1147
- pol.push(...d.slice(0, -1).fill(0));
1148
- return Uint8Array.from(GF.remainderPoly(pol, d));
1149
- },
1150
- decode(to) {
1151
- const res = to.slice();
1152
- const poly = GF.polynomial(Array.from(to));
1153
- // Find errors
1154
- let syndrome = fillArr(eccWords, 0);
1155
- let hasError = false;
1156
- for (let i = 0; i < eccWords; i++) {
1157
- const evl = GF.evalPoly(poly, GF.exp(i));
1158
- syndrome[syndrome.length - 1 - i] = evl;
1159
- if (evl !== 0)
1160
- hasError = true;
1161
- }
1162
- if (!hasError)
1163
- return res;
1164
- syndrome = GF.polynomial(syndrome);
1165
- const monomial = GF.monomial(eccWords, 1);
1166
- const [errorLocator, errorEvaluator] = GF.euclidian(monomial, syndrome, eccWords);
1167
- // Error locations
1168
- const locations = fillArr(GF.degree(errorLocator), 0);
1169
- let e = 0;
1170
- for (let i = 1; i < 256 && e < locations.length; i++) {
1171
- if (GF.evalPoly(errorLocator, i) === 0)
1172
- locations[e++] = GF.inv(i);
1173
- }
1174
- if (e !== locations.length)
1175
- throw new Error('RS.decode: invalid errors number');
1176
- for (let i = 0; i < locations.length; i++) {
1177
- const pos = res.length - 1 - GF.log(locations[i]);
1178
- if (pos < 0)
1179
- throw new Error('RS.decode: invalid error location');
1180
- const xiInverse = GF.inv(locations[i]);
1181
- let denominator = 1;
1182
- for (let j = 0; j < locations.length; j++) {
1183
- if (i === j)
1184
- continue;
1185
- denominator = GF.mul(denominator, GF.add(1, GF.mul(locations[j], xiInverse)));
1186
- }
1187
- res[pos] = GF.add(res[pos], GF.mul(GF.evalPoly(errorEvaluator, xiInverse), GF.inv(denominator)));
1188
- }
1189
- return res;
1190
- },
1191
- };
1192
- }
1193
- // Interleaves blocks
1194
- // QR block interleaver / deinterleaver. Shorter data blocks stay first so
1195
- // encode matches ISO/IEC 18004 §7.6 c) and decode can reverse it via §12 z)1.
1196
- function interleave(ver, ecc) {
1197
- const { words, shortBlocks, numBlocks, blockLen, total } = info.capacity(ver, ecc);
1198
- const rs = RS(words);
1199
- return {
1200
- encode(bytes) {
1201
- // Caller must pass exactly the data codewords for this version/ecc;
1202
- // this helper only splits blocks and interleaves them with RS parity.
1203
- // Add error correction to bytes
1204
- const blocks = [];
1205
- const eccBlocks = [];
1206
- for (let i = 0; i < numBlocks; i++) {
1207
- const isShort = i < shortBlocks;
1208
- const len = blockLen + (isShort ? 0 : 1);
1209
- blocks.push(bytes.subarray(0, len));
1210
- eccBlocks.push(rs.encode(bytes.subarray(0, len)));
1211
- bytes = bytes.subarray(len);
1212
- }
1213
- const resBlocks = interleaveBytes(blocks);
1214
- const resECC = interleaveBytes(eccBlocks);
1215
- const res = new Uint8Array(resBlocks.length + resECC.length);
1216
- res.set(resBlocks);
1217
- res.set(resECC, resBlocks.length);
1218
- return res;
1219
- },
1220
- decode(data) {
1221
- if (data.length !== total)
1222
- throw new Error(`interleave.decode: len(data)=${data.length}, total=${total}`);
1223
- const blocks = [];
1224
- for (let i = 0; i < numBlocks; i++) {
1225
- const isShort = i < shortBlocks;
1226
- blocks.push(new Uint8Array(words + blockLen + (isShort ? 0 : 1)));
1227
- }
1228
- // Short blocks
1229
- let pos = 0;
1230
- for (let i = 0; i < blockLen; i++) {
1231
- for (let j = 0; j < numBlocks; j++)
1232
- blocks[j][i] = data[pos++];
1233
- }
1234
- // Long blocks
1235
- for (let j = shortBlocks; j < numBlocks; j++)
1236
- blocks[j][blockLen] = data[pos++];
1237
- // ECC
1238
- for (let i = blockLen; i < blockLen + words; i++) {
1239
- for (let j = 0; j < numBlocks; j++) {
1240
- const isShort = j < shortBlocks;
1241
- blocks[j][i + (isShort ? 0 : 1)] = data[pos++];
1242
- }
1243
- }
1244
- // Decode
1245
- // Error-correct and copy data blocks together into a stream of bytes
1246
- const res = [];
1247
- for (const block of blocks)
1248
- res.push(...Array.from(rs.decode(block)).slice(0, -words));
1249
- return Uint8Array.from(res);
1250
- },
1251
- };
1252
- }
1253
- // Draw
1254
- // Generic template per version+ecc+mask. Can be cached, to speedup calculations.
1255
- // Function-pattern template plus reserved format/version areas; data modules
1256
- // are filled later by zigzag placement in `drawQR`.
1257
- function drawTemplate(ver, ecc, maskIdx, test = false) {
1258
- const size = info.size.encode(ver);
1259
- let b = new Bitmap(size + 2);
1260
- // Finder patterns
1261
- // We draw full pattern and later slice, since before addition of borders finder is truncated by one pixel on sides
1262
- const finder = new Bitmap(3).rect(0, 3, true).border(1, false).border(1, true).border(1, false);
1263
- b = b
1264
- .embed(0, finder) // top left
1265
- .embed({ x: -finder.width, y: 0 }, finder) // top right
1266
- .embed({ x: 0, y: -finder.height }, finder); // bottom left
1267
- b = b.rectSlice(1, size);
1268
- // Alignment patterns
1269
- const align = new Bitmap(1).rect(0, 1, true).border(1, false).border(1, true);
1270
- const alignPos = info.alignmentPatterns(ver);
1271
- for (const y of alignPos) {
1272
- for (const x of alignPos) {
1273
- if (b.isDefined(x, y))
1274
- continue;
1275
- b.embed({ x: x - 2, y: y - 2 }, align); // center of pattern should be at position
1276
- }
1277
- }
1278
- // Timing patterns
1279
- b = b
1280
- .hLine({ x: 0, y: 6 }, Infinity, ({ x }) => (b.isDefined(x, 6) ? undefined : x % 2 == 0))
1281
- .vLine({ x: 6, y: 0 }, Infinity, ({ y }) => (b.isDefined(6, y) ? undefined : y % 2 == 0));
1282
- // Format information
1283
- {
1284
- const bits = info.formatBits(ecc, maskIdx);
1285
- const getBit = (i) => !test && ((bits >> i) & 1) == 1;
1286
- // vertical
1287
- for (let i = 0; i < 6; i++)
1288
- b.set(8, i, getBit(i)); // right of top-left finder
1289
- // TODO: re-write as lines, like:
1290
- // b.vLine({ x: 8, y: 0 }, 6, ({ x, y }) => getBit(y));
1291
- for (let i = 6; i < 8; i++)
1292
- b.set(8, i + 1, getBit(i)); // after timing pattern
1293
- for (let i = 8; i < 15; i++)
1294
- b.set(8, size - 15 + i, getBit(i)); // right of bottom-left finder
1295
- // horizontal
1296
- for (let i = 0; i < 8; i++)
1297
- b.set(size - i - 1, 8, getBit(i)); // under top-right finder
1298
- for (let i = 8; i < 9; i++)
1299
- b.set(15 - i - 1 + 1, 8, getBit(i)); // VVV, after timing
1300
- for (let i = 9; i < 15; i++)
1301
- b.set(15 - i - 1, 8, getBit(i)); // under top-left finder
1302
- b.set(8, size - 8, !test); // bottom-left finder, right
1303
- }
1304
- // Version information
1305
- if (ver >= 7) {
1306
- const bits = info.versionBits(ver);
1307
- for (let i = 0; i < 18; i += 1) {
1308
- const bit = !test && ((bits >> i) & 1) == 1;
1309
- const x = Math.floor(i / 3);
1310
- const y = (i % 3) + size - 8 - 3;
1311
- // two copies
1312
- b.set(y, x, bit);
1313
- b.set(x, y, bit);
1314
- }
1315
- }
1316
- return b;
1317
- }
1318
- // Walk undefined data modules in the QR two-column zigzag order from the
1319
- // lower right, skipping function patterns and the vertical timing column.
1320
- function zigzag(tpl, maskIdx, fn) {
1321
- const bm = tpl;
1322
- const size = bm.height;
1323
- const pattern = PATTERNS[maskIdx];
1324
- // zig-zag pattern
1325
- let dir = -1;
1326
- let y = size - 1;
1327
- // two columns at time
1328
- for (let xOffset = size - 1; xOffset > 0; xOffset -= 2) {
1329
- if (xOffset == 6)
1330
- xOffset = 5; // skip vertical timing pattern
1331
- for (;; y += dir) {
1332
- for (let j = 0; j < 2; j += 1) {
1333
- const x = xOffset - j;
1334
- if (bm.isDefined(x, y))
1335
- continue; // skip already written elements
1336
- fn(x, y, pattern(x, y));
1337
- }
1338
- if (y + dir < 0 || y + dir >= size)
1339
- break;
1340
- }
1341
- dir = -dir; // change direction
1342
- }
1343
- }
1344
- // NOTE: byte encoding is just representation, QR works with strings only. Most decoders will fail on raw byte array,
1345
- // since they expect unicode or other text encoding inside bytes
1346
- // Auto-pick among the currently supported single-segment modes only.
1347
- // Empty strings stay numeric, and any non-alphanumeric character falls back to byte.
1348
- function detectType(str) {
1349
- let type = 'numeric';
1350
- for (let x of str) {
1351
- if (info.alphabet.numeric.has(x))
1352
- continue;
1353
- type = 'alphanumeric';
1354
- if (!info.alphabet.alphanumerc.has(x))
1355
- return 'byte';
1356
- }
1357
- return type;
1358
- }
1359
- /**
1360
- * Encode a string as UTF-8 bytes.
1361
- * @param str - Text to encode into UTF-8.
1362
- * @returns UTF-8 bytes for the provided string.
1363
- * @throws If the input is not a string. {@link Error}
1364
- * @example
1365
- * Encode a string as UTF-8 bytes.
1366
- * ```ts
1367
- * const bytes = utf8ToBytes('abc'); // new Uint8Array([97, 98, 99])
1368
- * ```
1369
- */
1370
- // ISO/IEC 18004:2024 §7.3.2 says QR's default interpretation is
1371
- // "ECI 000003 representing the ISO/IEC 8859-1 character set"; §7.4.2 says
1372
- // non-default initial ECI data starts with an ECI header. Keep UTF-8 bytes
1373
- // without that header for compatibility with existing emoji/qrcode fixtures.
1374
- export function utf8ToBytes(str) {
1375
- if (typeof str !== 'string')
1376
- throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
1377
- return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
1378
- }
1379
- // Build one QR mode/count/data segment, then append the terminator, zero padding,
1380
- // and alternating pad codewords before RS interleaving.
1381
- function encode(ver, ecc, data, type, encoder = utf8ToBytes) {
1382
- let encoded = '';
1383
- let dataLen = data.length;
1384
- if (type === 'numeric') {
1385
- const t = info.alphabet.numeric.decode(data.split(''));
1386
- const n = t.length;
1387
- for (let i = 0; i < n - 2; i += 3)
1388
- encoded += bin(t[i] * 100 + t[i + 1] * 10 + t[i + 2], 10);
1389
- if (n % 3 === 1) {
1390
- encoded += bin(t[n - 1], 4);
1391
- }
1392
- else if (n % 3 === 2) {
1393
- encoded += bin(t[n - 2] * 10 + t[n - 1], 7);
1394
- }
1395
- }
1396
- else if (type === 'alphanumeric') {
1397
- const t = info.alphabet.alphanumerc.decode(data.split(''));
1398
- const n = t.length;
1399
- for (let i = 0; i < n - 1; i += 2)
1400
- encoded += bin(t[i] * 45 + t[i + 1], 11);
1401
- if (n % 2 == 1)
1402
- encoded += bin(t[n - 1], 6); // pad if odd number of chars
1403
- }
1404
- else if (type === 'byte') {
1405
- // The default encoder is intentionally UTF-8-without-ECI; see utf8ToBytes().
1406
- const utf8 = encoder(data);
1407
- dataLen = utf8.length;
1408
- encoded = Array.from(utf8)
1409
- .map((i) => bin(i, 8))
1410
- .join('');
1411
- }
1412
- else {
1413
- throw new Error('encode: unsupported type');
1414
- }
1415
- const { capacity } = info.capacity(ver, ecc);
1416
- const len = bin(dataLen, info.lengthBits(ver, type));
1417
- let bits = info.modeBits[type] + len + encoded;
1418
- if (bits.length > capacity)
1419
- throw new Error('Capacity overflow');
1420
- // Terminator
1421
- bits += '0'.repeat(Math.min(4, Math.max(0, capacity - bits.length)));
1422
- // Pad bits string untill full byte
1423
- if (bits.length % 8)
1424
- bits += '0'.repeat(8 - (bits.length % 8));
1425
- // Add padding until capacity is full
1426
- const padding = '1110110000010001';
1427
- for (let idx = 0; bits.length !== capacity; idx++)
1428
- bits += padding[idx % padding.length];
1429
- // Convert a bitstring to array of bytes
1430
- const bytes = Uint8Array.from(bits.match(/(.{8})/g).map((i) => Number(`0b${i}`)));
1431
- return interleave(ver, ecc).encode(bytes);
1432
- }
1433
- // DRAW
1434
- // Stream interleaved codeword bits MSB-first through zigzag; any leftover
1435
- // cells after the final codeword become zero-valued remainder bits before masking.
1436
- function drawQR(ver, ecc, data, maskIdx, test = false) {
1437
- const b = drawTemplate(ver, ecc, maskIdx, test);
1438
- let i = 0;
1439
- const need = 8 * data.length;
1440
- zigzag(b, maskIdx, (x, y, mask) => {
1441
- let value = false;
1442
- if (i < need) {
1443
- value = ((data[i >>> 3] >> ((7 - i) & 7)) & 1) !== 0;
1444
- i++;
1445
- }
1446
- b.set(x, y, value !== mask); // !== as xor
1447
- });
1448
- if (i !== need)
1449
- throw new Error('QR: bytes left after draw');
1450
- return b;
1451
- }
1452
- // Pack a left-to-right row pattern for `Bitmap.countPatternInRow()`; keep the
1453
- // explicit width because leading light modules vanish from the numeric value.
1454
- const mkPattern = (pattern) => {
1455
- const s = pattern.map((i) => (i ? '1' : '0')).join('');
1456
- return { len: s.length, n: Number(`0b${s}`) };
1457
- };
1458
- // 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide
1459
- const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
1460
- const lightPattern = [false, false, false, false]; // light area 4 modules wide
1461
- const P1 = /* @__PURE__ */ (() => mkPattern([...finderPattern, ...lightPattern]))();
1462
- const P2 = /* @__PURE__ */ (() => mkPattern([...lightPattern, ...finderPattern]))();
1463
- function penalty(bm) {
1464
- const b = bm;
1465
- const { width, height } = b;
1466
- const transposed = b.transpose();
1467
- // Adjacent modules in row/column in same | No. of modules = (5 + i) color
1468
- let adjacent = 0;
1469
- for (let y = 0; y < height; y++) {
1470
- b.getRuns(y, (len) => {
1471
- if (len >= 5)
1472
- adjacent += 3 + (len - 5);
1473
- });
1474
- }
1475
- for (let y = 0; y < width; y++) {
1476
- transposed.getRuns(y, (len) => {
1477
- if (len >= 5)
1478
- adjacent += 3 + (len - 5);
1479
- });
1480
- }
1481
- // Block of modules in same color (Block size = 2x2)
1482
- let box = 0;
1483
- for (let y = 0; y < height - 1; y++)
1484
- box += 3 * b.countBoxes2x2(y);
1485
- let finder = 0;
1486
- for (let y = 0; y < height; y++)
1487
- finder += 40 * b.countPatternInRow(y, P1.len, P1.n, P2.n);
1488
- for (let y = 0; y < width; y++)
1489
- finder += 40 * transposed.countPatternInRow(y, P1.len, P1.n, P2.n);
1490
- const total = height * width;
1491
- const darkPixels = b.popcnt();
1492
- // ISO/IEC 18004:2024 §7.8.3.1 NOTE 4 assigns "0 points" when the dark ratio
1493
- // is "between 45 % and 55 %"; subtract that first 5% deviation band before
1494
- // rating further 5% steps, so exact 45/55 and 40/60 boundaries stay in-band.
1495
- const darkSteps = Math.ceil(Math.max(0, Math.abs(darkPixels * 100 - total * 50) - total * 5) / (total * 5));
1496
- const dark = 10 * darkSteps;
1497
- return adjacent + box + finder + dark;
1498
- }
1499
- // Selects best mask according to penalty, if no mask is provided
1500
- function drawQRBest(ver, ecc, data, maskIdx) {
1501
- if (maskIdx === undefined) {
1502
- const bestMask = best();
1503
- // ISO/IEC 18004:2024 §7.8.3.1 says mask penalty area is "the complete symbol",
1504
- // but python-qrcode scores this placeholder form. Keep that output for compatibility
1505
- // with common QR generators and to avoid fingerprinting this implementation.
1506
- for (let mask = 0; mask < PATTERNS.length; mask++)
1507
- bestMask.add(penalty(drawQR(ver, ecc, data, mask, true)), mask);
1508
- maskIdx = bestMask.get();
1509
- }
1510
- if (maskIdx === undefined)
1511
- throw new Error('Cannot find mask'); // Should never happen
1512
- return drawQR(ver, ecc, data, maskIdx);
1513
- }
1514
- function validateECC(ec) {
1515
- if (!ECMode.includes(ec))
1516
- throw new Error(`Invalid error correction mode=${ec}. Expected: ${ECMode}`);
1517
- }
1518
- function validateEncoding(enc) {
1519
- if (!Encoding.includes(enc))
1520
- throw new Error(`Encoding: invalid mode=${enc}. Expected: ${Encoding}`);
1521
- if (enc === 'kanji' || enc === 'eci')
1522
- throw new Error(`Encoding: ${enc} is not supported (yet?).`);
1523
- }
1524
- function validateMask(mask) {
1525
- if (![0, 1, 2, 3, 4, 5, 6, 7].includes(mask) || !PATTERNS[mask])
1526
- throw new Error(`Invalid mask=${mask}. Expected number [0..7]`);
1527
- }
1528
- export function encodeQR(text, output = 'raw', opts = {}) {
1529
- const _opts = opts;
1530
- const ecc = _opts.ecc !== undefined ? _opts.ecc : 'medium';
1531
- validateECC(ecc);
1532
- const encoding = _opts.encoding !== undefined ? _opts.encoding : detectType(text);
1533
- validateEncoding(encoding);
1534
- if (_opts.mask !== undefined)
1535
- validateMask(_opts.mask);
1536
- let ver = _opts.version;
1537
- let data, err = new Error('Unknown error');
1538
- if (ver !== undefined) {
1539
- validateVersion(ver);
1540
- data = encode(ver, ecc, text, encoding, _opts.textEncoder);
1541
- }
1542
- else {
1543
- // If no version is provided, try to find smallest one which fits
1544
- // Currently just scans all version, can be significantly speedup if needed
1545
- for (let i = 1; i <= 40; i++) {
1546
- try {
1547
- data = encode(i, ecc, text, encoding, _opts.textEncoder);
1548
- ver = i;
1549
- break;
1550
- }
1551
- catch (e) {
1552
- err = e;
1553
- }
1554
- }
1555
- }
1556
- if (!ver || !data)
1557
- throw err;
1558
- let res = drawQRBest(ver, ecc, data, _opts.mask);
1559
- res.assertDrawn();
1560
- // ISO/IEC 18004:2024 §5.3.8 says a QR quiet zone's "width shall be 4X",
1561
- // and §9.1 requires 4X "on all four sides". Keep the compact historical
1562
- // 2-module default to avoid changing encoder output; callers that need a
1563
- // standards-conformant quiet zone must pass `border: 4` explicitly.
1564
- const border = _opts.border === undefined ? 2 : _opts.border;
1565
- if (!Number.isSafeInteger(border) || border <= 0)
1566
- throw new Error(`invalid border=${border}`);
1567
- res = res.border(border, false); // Add border
1568
- if (_opts.scale !== undefined)
1569
- res = res.scale(_opts.scale); // Scale image
1570
- if (output === 'raw')
1571
- return res.toRaw();
1572
- else if (output === 'ascii')
1573
- return res.toASCII();
1574
- else if (output === 'svg')
1575
- return res.toSVG(_opts.optimize);
1576
- else if (output === 'gif')
1577
- return res.toGIF();
1578
- else if (output === 'term')
1579
- return res.toTerm();
1580
- else
1581
- throw new Error(`Unknown output: ${output}`);
1582
- }
1583
- /**
1584
- * Default export alias for {@link encodeQR}.
1585
- * @param text - Text payload that should be encoded into the QR symbol.
1586
- * @param output - Output format to generate: raw matrix, ASCII, terminal ANSI, GIF, or SVG.
1587
- * @param opts - Encoding and rendering options. See {@link QrOpts} and {@link SvgQrOpts}.
1588
- * @returns Encoded QR data in the format selected by `output`.
1589
- * @throws If the payload, options, QR capacity, or output format are invalid. {@link Error}
1590
- * @example
1591
- * Encode text into the default export from the package root.
1592
- * ```ts
1593
- * import encodeQR from 'qr';
1594
- * encodeQR('Hello world', 'ascii');
1595
- * ```
1596
- */
1597
- export default encodeQR;
1598
- /**
1599
- * Low-level helpers used by the encoder and test suite.
1600
- * Exports the shared helper tables/functions through a frozen container.
1601
- * @example
1602
- * Read low-level QR metadata tables.
1603
- * ```ts
1604
- * import { utils } from 'qr';
1605
- * const size = utils.info.size.encode(1); // 21
1606
- * ```
1607
- */
1608
- export const utils = /* @__PURE__ */ Object.freeze({
1609
- best,
1610
- bin,
1611
- popcnt,
1612
- drawTemplate,
1613
- fillArr,
1614
- info,
1615
- interleave,
1616
- validateVersion,
1617
- zigzag,
1618
- });
1619
- // Unsafe API utils, exported only for tests
1620
- // Exposes the shared internal helpers/tables through a frozen container.
1621
- export const _tests = /* @__PURE__ */ Object.freeze({
1622
- Bitmap,
1623
- info,
1624
- detectType,
1625
- encode,
1626
- drawQR,
1627
- penalty,
1628
- PATTERNS,
1629
- });
1630
- // Type tests
1631
- // const o1 = qr('test', 'ascii');
1632
- // const o2 = qr('test', 'raw');
1633
- // const o3 = qr('test', 'gif');
1634
- // const o4 = qr('test', 'svg');
1635
- // const o5 = qr('test', 'term');
1636
- //# sourceMappingURL=index.js.map