qr 0.5.4 → 0.6.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/src/index.ts CHANGED
@@ -32,11 +32,128 @@ const array = encodeQR(txt, 'raw'); // 2d array for canvas or other libs
32
32
  ```
33
33
  */
34
34
 
35
+ /**
36
+ * Bytes API type helpers for old + new TypeScript.
37
+ *
38
+ * TS 5.6 has `Uint8Array`, while TS 5.9+ made it generic `Uint8Array<ArrayBuffer>`.
39
+ * We can't use specific return type, because TS 5.6 will error.
40
+ * We can't use generic return type, because most TS 5.9 software will expect specific type.
41
+ *
42
+ * Maps typed-array input leaves to broad forms.
43
+ * These are compatibility adapters, not ownership guarantees.
44
+ *
45
+ * - `TArg` keeps byte inputs broad.
46
+ * - `TRet` marks byte outputs for TS 5.6 and TS 5.9+ compatibility.
47
+ */
48
+ export type TypedArg<T> = T extends BigInt64Array
49
+ ? BigInt64Array
50
+ : T extends BigUint64Array
51
+ ? BigUint64Array
52
+ : T extends Float32Array
53
+ ? Float32Array
54
+ : T extends Float64Array
55
+ ? Float64Array
56
+ : T extends Int16Array
57
+ ? Int16Array
58
+ : T extends Int32Array
59
+ ? Int32Array
60
+ : T extends Int8Array
61
+ ? Int8Array
62
+ : T extends Uint16Array
63
+ ? Uint16Array
64
+ : T extends Uint32Array
65
+ ? Uint32Array
66
+ : T extends Uint8ClampedArray
67
+ ? Uint8ClampedArray
68
+ : T extends Uint8Array
69
+ ? Uint8Array
70
+ : never;
71
+ /** Maps typed-array output leaves to narrow TS-compatible forms. */
72
+ export type TypedRet<T> = T extends BigInt64Array
73
+ ? ReturnType<typeof BigInt64Array.of>
74
+ : T extends BigUint64Array
75
+ ? ReturnType<typeof BigUint64Array.of>
76
+ : T extends Float32Array
77
+ ? ReturnType<typeof Float32Array.of>
78
+ : T extends Float64Array
79
+ ? ReturnType<typeof Float64Array.of>
80
+ : T extends Int16Array
81
+ ? ReturnType<typeof Int16Array.of>
82
+ : T extends Int32Array
83
+ ? ReturnType<typeof Int32Array.of>
84
+ : T extends Int8Array
85
+ ? ReturnType<typeof Int8Array.of>
86
+ : T extends Uint16Array
87
+ ? ReturnType<typeof Uint16Array.of>
88
+ : T extends Uint32Array
89
+ ? ReturnType<typeof Uint32Array.of>
90
+ : T extends Uint8ClampedArray
91
+ ? ReturnType<typeof Uint8ClampedArray.of>
92
+ : T extends Uint8Array
93
+ ? ReturnType<typeof Uint8Array.of>
94
+ : never;
95
+ /** Recursively adapts byte-carrying API input types. See {@link TypedArg}. */
96
+ export type TArg<T> =
97
+ | T
98
+ | ([TypedArg<T>] extends [never]
99
+ ? T extends (...args: infer A) => infer R
100
+ ? ((...args: { [K in keyof A]: TRet<A[K]> }) => TArg<R>) & {
101
+ [K in keyof T]: T[K] extends (...args: any) => any ? T[K] : TArg<T[K]>;
102
+ }
103
+ : T extends [infer A, ...infer R]
104
+ ? [TArg<A>, ...{ [K in keyof R]: TArg<R[K]> }]
105
+ : T extends readonly [infer A, ...infer R]
106
+ ? readonly [TArg<A>, ...{ [K in keyof R]: TArg<R[K]> }]
107
+ : T extends (infer A)[]
108
+ ? TArg<A>[]
109
+ : T extends readonly (infer A)[]
110
+ ? readonly TArg<A>[]
111
+ : T extends Promise<infer A>
112
+ ? Promise<TArg<A>>
113
+ : T extends object
114
+ ? { [K in keyof T]: TArg<T[K]> }
115
+ : T
116
+ : TypedArg<T>);
117
+ /** Recursively adapts byte-carrying API output types. See {@link TypedArg}. */
118
+ export type TRet<T> = T extends unknown
119
+ ? T &
120
+ ([TypedRet<T>] extends [never]
121
+ ? T extends (...args: infer A) => infer R
122
+ ? ((...args: { [K in keyof A]: TArg<A[K]> }) => TRet<R>) & {
123
+ [K in keyof T]: T[K] extends (...args: any) => any ? T[K] : TRet<T[K]>;
124
+ }
125
+ : T extends [infer A, ...infer R]
126
+ ? [TRet<A>, ...{ [K in keyof R]: TRet<R[K]> }]
127
+ : T extends readonly [infer A, ...infer R]
128
+ ? readonly [TRet<A>, ...{ [K in keyof R]: TRet<R[K]> }]
129
+ : T extends (infer A)[]
130
+ ? TRet<A>[]
131
+ : T extends readonly (infer A)[]
132
+ ? readonly TRet<A>[]
133
+ : T extends Promise<infer A>
134
+ ? Promise<TRet<A>>
135
+ : T extends object
136
+ ? { [K in keyof T]: TRet<T[K]> }
137
+ : T
138
+ : TypedRet<T>)
139
+ : never;
140
+
35
141
  // We do not use newline escape code directly in strings because it's not parser-friendly
36
142
  const chCodes = { newline: 10, reset: 27 };
37
143
 
144
+ /** Bidirectional codec interface. */
38
145
  export interface Coder<F, T> {
146
+ /**
147
+ * Encodes a source value into the target representation.
148
+ * @param from - Source value to encode.
149
+ * @returns Encoded representation.
150
+ */
39
151
  encode(from: F): T;
152
+ /**
153
+ * Decodes a target value back into the source representation.
154
+ * @param to - Encoded representation to decode.
155
+ * @returns Decoded source value.
156
+ */
40
157
  decode(to: T): F;
41
158
  }
42
159
 
@@ -59,6 +176,7 @@ function mod(a: number, b: number): number {
59
176
  }
60
177
 
61
178
  function fillArr<T>(length: number, val: T): T[] {
179
+ // Current callers only pass primitive fill values; object fills would alias references.
62
180
  return new Array(length).fill(val);
63
181
  }
64
182
 
@@ -73,7 +191,7 @@ function popcnt(n: number): number {
73
191
  * @param blocks [[1, 2, 3], [4, 5, 6]]
74
192
  * @returns [1, 4, 2, 5, 3, 6]
75
193
  */
76
- function interleaveBytes(blocks: Uint8Array[]): Uint8Array {
194
+ function interleaveBytes(blocks: TArg<Uint8Array[]>): TRet<Uint8Array> {
77
195
  let maxLen = 0;
78
196
  let totalLen = 0;
79
197
  for (const block of blocks) {
@@ -83,13 +201,15 @@ function interleaveBytes(blocks: Uint8Array[]): Uint8Array {
83
201
 
84
202
  const result = new Uint8Array(totalLen);
85
203
  let idx = 0;
204
+ // When block lengths differ, callers must pass the shorter blocks first so
205
+ // the interleaving order matches ISO/IEC 18004 §7.6 c).
86
206
  for (let i = 0; i < maxLen; i++) {
87
207
  for (const block of blocks) {
88
208
  if (i < block.length) result[idx++] = block[i];
89
209
  }
90
210
  }
91
211
 
92
- return result;
212
+ return result as TRet<Uint8Array>;
93
213
  }
94
214
 
95
215
  // Optimize for minimal score/penalty
@@ -102,6 +222,7 @@ function best<T>(): {
102
222
  let bestScore = Infinity;
103
223
  return {
104
224
  add(score: number, value: T): void {
225
+ // Ties keep the first candidate so equal-score selections stay deterministic.
105
226
  if (score >= bestScore) return;
106
227
  best = value;
107
228
  bestScore = score;
@@ -115,7 +236,8 @@ function best<T>(): {
115
236
  function alphabet(
116
237
  alphabet: string
117
238
  ): Coder<number[], string[]> & { has: (char: string) => boolean } {
118
- return {
239
+ // Character order defines the numeric values used by the target QR mode.
240
+ return Object.freeze({
119
241
  has: (char: string) => alphabet.includes(char),
120
242
  decode: (input: string[]) => {
121
243
  if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
@@ -138,12 +260,12 @@ function alphabet(
138
260
  return alphabet[i];
139
261
  });
140
262
  },
141
- };
263
+ });
142
264
  }
143
265
 
144
266
  // Transpose 32x32 bit matrix in-place
145
267
  // a[0..31] are 32 rows of 32 bits each; after transpose they become 32 columns.
146
- function transpose32(a: Uint32Array) {
268
+ function transpose32(a: TArg<Uint32Array>) {
147
269
  if (a.length !== 32) throw new Error('expects 32 element matrix');
148
270
  const masks = [0x55555555, 0x33333333, 0x0f0f0f0f, 0x00ff00ff, 0x0000ffff] as const;
149
271
  // Hello again, FFT
@@ -168,6 +290,8 @@ const bitMask = (x: number): number => (1 << (x & 31)) >>> 0;
168
290
  const rangeMask = (shift: number, len: number): number => {
169
291
  // len in [0..32], shift in [0..31]
170
292
  if (len === 0) return 0;
293
+ // Callers only request len=32 for word-aligned spans; JS shift counts wrap at 32,
294
+ // so full-word masks must bypass the generic `(1 << len)` path.
171
295
  if (len === 32) return 0xffffffff;
172
296
  return (((1 << len) - 1) << shift) >>> 0;
173
297
  };
@@ -186,13 +310,41 @@ Basic bitmap structure for two colors (black & white) small images.
186
310
  will work on a single bit anyway. It will only reduce storage without
187
311
  significant performance impact, but will increase code complexity
188
312
  */
189
- export type Point = { x: number; y: number };
190
- export type Size = { height: number; width: number };
191
- export type Image = Size & { data: Uint8Array | Uint8ClampedArray | number[] };
313
+ /** Two-dimensional point. */
314
+ export type Point = {
315
+ /** Horizontal coordinate. */
316
+ x: number;
317
+ /** Vertical coordinate. */
318
+ y: number;
319
+ };
320
+ /** Width and height pair. */
321
+ export type Size = {
322
+ /** Pixel height. */
323
+ height: number;
324
+ /** Pixel width. */
325
+ width: number;
326
+ };
327
+ /** Raster image used by the encoder and decoder. */
328
+ export type Image = Size & {
329
+ /** Row-major RGB or RGBA pixel data. */
330
+ data: Uint8Array | Uint8ClampedArray | number[];
331
+ };
192
332
  type DrawValue = boolean | undefined; // undefined=not written, true=foreground, false=background
193
333
  // value or fn returning value based on coords
194
334
  type DrawFn = DrawValue | ((c: Point, curr: DrawValue) => DrawValue);
195
335
  type ReadFn = (c: Point, curr: DrawValue) => void;
336
+ /**
337
+ * Mutable monochrome bitmap used as the internal QR representation.
338
+ * @param size - Square edge length or explicit bitmap dimensions.
339
+ * @param data - Optional row-major pixel matrix using `true`, `false`, or `undefined`.
340
+ * @example
341
+ * Create a bitmap, then scale it for display.
342
+ * ```ts
343
+ * import { Bitmap } from 'qr';
344
+ * const bitmap = Bitmap.fromString('X \n X');
345
+ * bitmap.scale(2);
346
+ * ```
347
+ */
196
348
  export class Bitmap {
197
349
  private static size(size: Size | number, limit?: Size) {
198
350
  if (typeof size === 'number') size = { height: size, width: size };
@@ -211,6 +363,8 @@ export class Bitmap {
211
363
  }
212
364
  static fromString(s: string): Bitmap {
213
365
  // Remove linebreaks on start and end, so we draw in `` section
366
+ // Fixture strings use LF-delimited rows of X / space / ? characters; callers
367
+ // must normalize CRLF input before handing it to this debug parser.
214
368
  s = s.replace(/^\n+/g, '').replace(/\n+$/g, '');
215
369
  const lines = s.split(String.fromCharCode(chCodes.newline));
216
370
  const height = lines.length;
@@ -244,6 +398,14 @@ export class Bitmap {
244
398
  width: number;
245
399
  constructor(size: Size | number, data?: DrawValue[][]) {
246
400
  const { height, width } = Bitmap.size(size);
401
+ // Bitmap coordinates wrap through modulo for negative positions, so invalid
402
+ // dimensions produce NaN, aliasing, or unsafe allocation sizes before later
403
+ // drawing no-op guards can run. `Infinity` is only valid for rectangle sizes
404
+ // that are clamped against an existing positive bitmap.
405
+ if (!Number.isSafeInteger(height) || height <= 0)
406
+ throw new Error(`Bitmap: invalid height=${height}, expected positive safe integer dimension`);
407
+ if (!Number.isSafeInteger(width) || width <= 0)
408
+ throw new Error(`Bitmap: invalid width=${width}, expected positive safe integer dimension`);
247
409
  this.height = height;
248
410
  this.width = width;
249
411
  this.tailMask = rangeMask(0, width & 31 || 32);
@@ -264,8 +426,13 @@ export class Bitmap {
264
426
  }
265
427
  }
266
428
  point(p: Point): DrawValue {
429
+ // The storage docs above say "undefined is used as a marker whether cell
430
+ // was written or not"; `point()` is the detector's dark-module read and
431
+ // intentionally treats both undefined and false as not-dark. Use
432
+ // `isDefined()` when the written/undefined distinction matters.
267
433
  return this.get(p.x, p.y);
268
434
  }
435
+ // Raw bounds check for scan loops; unlike `xy()`, this does not wrap or normalize coordinates.
269
436
  isInside(p: Point): boolean {
270
437
  return 0 <= p.x && p.x < this.width && 0 <= p.y && p.y < this.height;
271
438
  }
@@ -278,7 +445,8 @@ export class Bitmap {
278
445
  if (typeof c === 'number') c = { x: c, y: c };
279
446
  if (!Number.isSafeInteger(c.x)) throw new Error(`Bitmap: invalid x=${c.x}`);
280
447
  if (!Number.isSafeInteger(c.y)) throw new Error(`Bitmap: invalid y=${c.y}`);
281
- // Do modulo, so we can use negative positions
448
+ // Bitmap's class docs say "For most `draw` calls, structure is mutable";
449
+ // coordinate objects follow that hot-path policy too and are normalized in place.
282
450
  c.x = mod(c.x, this.width);
283
451
  c.y = mod(c.y, this.height);
284
452
  return c;
@@ -293,6 +461,10 @@ export class Bitmap {
293
461
  return { word: this.wordIndex(x, y), bit: x & 31 };
294
462
  }
295
463
  isDefined(x: number, y: number): boolean {
464
+ // `isInside()` is the raw bounds check; keep these bitset accessors
465
+ // bounds-check-free for hot paths. Invalid tail coordinates may observe
466
+ // backing-word bits, so callers that accept untrusted coordinates must
467
+ // check `isInside()` first.
296
468
  const wi = this.wordIndex(x, y);
297
469
  const m = bitMask(x);
298
470
  return (this.defined[wi] & m) !== 0;
@@ -305,10 +477,14 @@ export class Bitmap {
305
477
  private maskWord(wi: number, mask: number, v: boolean): void {
306
478
  const { defined, value } = this;
307
479
  defined[wi] |= mask;
480
+ // `-v` expands the boolean to either all-zero or all-one bits before masking it into the selected lanes.
308
481
  value[wi] = (value[wi] & ~mask) | (-v & mask);
309
482
  }
310
483
  set(x: number, y: number, v: DrawValue): void {
484
+ // `undefined` means "leave the current cell unchanged", not "clear it back to undefined".
311
485
  if (v === undefined) return;
486
+ // Like `get()` / `isDefined()`, this is a raw in-bounds bitset accessor;
487
+ // check `isInside()` before passing untrusted coordinates.
312
488
  this.maskWord(this.wordIndex(x, y), bitMask(x), v);
313
489
  }
314
490
  // word-span fill for constant values (fast path)
@@ -328,6 +504,7 @@ export class Bitmap {
328
504
  continue;
329
505
  }
330
506
  this.maskWord(rowBase + startWord, rangeMask(startBit, 32 - startBit), v);
507
+ // Whole interior words can be written directly: every bit in the span becomes defined and equal to v.
331
508
  for (let i = startWord + 1; i < endWord; i++) {
332
509
  defined[rowBase + i] = 0xffffffff;
333
510
  value[rowBase + i] = v ? 0xffffffff : 0;
@@ -348,6 +525,7 @@ export class Bitmap {
348
525
  const bitX = x + xPos;
349
526
  const { bit, word } = this.bitIndex(bitX, Py);
350
527
  const bitsPerWord = Math.min(32 - bit, width - xPos);
528
+ // bitX stays absolute for word-local masks; xPos/yPos stay rectangle-local for rect callbacks.
351
529
  cb(word, bitX, xPos, yPos, bitsPerWord);
352
530
  xPos += bitsPerWord;
353
531
  }
@@ -367,7 +545,11 @@ export class Bitmap {
367
545
  let valWord = value[wi];
368
546
  for (let b = 0; b < n; b++) {
369
547
  const mask = bitMask(bitX + b);
548
+ // As with `point()`, callback `cur` is a dark/not-dark read; the
549
+ // storage-level "undefined is used as a marker whether cell was
550
+ // written or not" distinction is checked separately with `isDefined()`.
370
551
  const res = fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
552
+ // Returning undefined from the callback keeps the existing cell unchanged.
371
553
  if (res === undefined) continue;
372
554
  defWord |= mask;
373
555
  valWord = (valWord & ~mask) | (-res & mask);
@@ -386,6 +568,8 @@ export class Bitmap {
386
568
  const valWord = value[wi];
387
569
  for (let b = 0; b < n; b++) {
388
570
  const mask = bitMask(bitX + b);
571
+ // rectRead is non-mutating; callback coordinates are rectangle-local,
572
+ // and `cur` is the same dark/not-dark read as `point()`.
389
573
  fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
390
574
  }
391
575
  });
@@ -400,6 +584,10 @@ export class Bitmap {
400
584
  }
401
585
  // add border
402
586
  border(border = 2, value: DrawValue): Bitmap {
587
+ // `border` is used both as output-size delta and as embed coordinate; keep
588
+ // it a positive safe integer before those paths allocate or normalize.
589
+ if (!Number.isSafeInteger(border) || border <= 0)
590
+ throw new Error(`Bitmap.border: invalid size=${border}`);
403
591
  const height = this.height + 2 * border;
404
592
  const width = this.width + 2 * border;
405
593
  const out = new Bitmap({ height, width });
@@ -415,6 +603,10 @@ export class Bitmap {
415
603
  if (width <= 0 || height <= 0) return this;
416
604
  const { value, defined } = this;
417
605
  const { words: srcStride, value: srcValue } = src;
606
+ // The Bitmap storage docs say "undefined is used as a marker whether cell
607
+ // was written or not"; `embed()` is the packed blit path for materialized
608
+ // source bitmaps, so it flattens the source rectangle to defined dark/light
609
+ // bits instead of treating undefined cells as transparent.
418
610
  for (let yPos = 0; yPos < height; yPos++) {
419
611
  const srcRow = yPos * srcStride;
420
612
  for (let xPos = 0; xPos < width; ) {
@@ -424,6 +616,7 @@ export class Bitmap {
424
616
  const len = Math.min(32 - dstBit, width - xPos);
425
617
  const w0 = srcValue[srcWord];
426
618
  const w1 = srcBit && srcWord + 1 < srcRow + srcStride ? srcValue[srcWord + 1] : 0;
619
+ // Source and destination bit offsets may differ, so assemble the source span from up to two words.
427
620
  const sVal = srcBit ? ((w0 >>> srcBit) | (w1 << (32 - srcBit))) >>> 0 : w0;
428
621
  const dstMask = rangeMask(dstBit, len);
429
622
  const valBits = ((sVal & rangeMask(0, len)) << dstBit) >>> 0;
@@ -440,6 +633,7 @@ export class Bitmap {
440
633
  const { height, width } = Bitmap.size(size, this.size({ x, y }));
441
634
  const rect = new Bitmap({ height, width });
442
635
  this.rectRead({ x, y }, { height, width }, (p, cur) => {
636
+ // rectRead reports undefined cells as false, so copy only when the source defined bit is set.
443
637
  if (this.isDefined(x + p.x, y + p.y)) {
444
638
  rect.set(p.x, p.y, cur);
445
639
  }
@@ -483,6 +677,9 @@ export class Bitmap {
483
677
  negate(): Bitmap {
484
678
  const n = this.defined.length;
485
679
  for (let i = 0; i < n; i++) {
680
+ // ISO/IEC 18004:2024 §12 b)5 says to "reverse the colouring of the light
681
+ // and dark pixels"; this dense scratch-bitmap operation materializes every
682
+ // backing bit as defined and does not preserve sparse/undefined cells.
486
683
  this.value[i] = ~this.value[i];
487
684
  this.defined[i] = 0xffffffff;
488
685
  }
@@ -493,6 +690,11 @@ export class Bitmap {
493
690
  if (!Number.isSafeInteger(factor) || factor > 1024)
494
691
  throw new Error(`invalid scale factor: ${factor}`);
495
692
  const { height, width } = this;
693
+ // Bitmap storage docs say "undefined is used as a marker whether cell was
694
+ // written or not"; `scale()` is an output materialization path and samples
695
+ // with `get()`, so sparse cells become defined light cells. Positive output
696
+ // dimensions stay validated by the Bitmap constructor instead of duplicating
697
+ // dimension checks in every caller that computes a new bitmap size.
496
698
  const res = new Bitmap({ height: factor * height, width: factor * width });
497
699
  return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) =>
498
700
  this.get((x / factor) | 0, (y / factor) | 0)
@@ -518,9 +720,13 @@ export class Bitmap {
518
720
  }
519
721
  }
520
722
  countPatternInRow(y: number, patternLen: number, ...patterns: number[]): number {
521
- if (patternLen <= 0 || patternLen >= 32) throw new Error('wrong patternLen');
723
+ // Penalty scanning only passes Table 11 windows over bounded symbol rows;
724
+ // validate this public helper before JS shifts / typed-array reads coerce bad inputs.
725
+ if (!Number.isSafeInteger(patternLen) || patternLen <= 0 || patternLen >= 32)
726
+ throw new Error('wrong patternLen');
522
727
  const mask = (1 << patternLen) - 1;
523
- const { width, value, words } = this;
728
+ const { height, width, value, words } = this;
729
+ if (!Number.isSafeInteger(y) || y < 0 || y >= height) return 0;
524
730
  let count = 0;
525
731
  const rowBase = this.wordIndex(0, y);
526
732
  for (let i = 0, window = 0; i < words; i++) {
@@ -539,8 +745,12 @@ export class Bitmap {
539
745
  return count;
540
746
  }
541
747
  getRuns(y: number, fn: (len: number, value: boolean) => void): void {
542
- const { width, value, words } = this;
748
+ const { height, width, value, words } = this;
543
749
  if (width === 0) return;
750
+ // ISO/IEC 18004:2024 §7.8.3.1 N1 scans adjacent modules in bounded rows
751
+ // and columns; validate this public helper before missing typed-array rows
752
+ // are coerced into all-light runs by bitwise operators.
753
+ if (!Number.isSafeInteger(y) || y < 0 || y >= height) return;
544
754
  let runLen = 0;
545
755
  let runValue: boolean | undefined;
546
756
  const rowBase = this.wordIndex(0, y);
@@ -572,10 +782,12 @@ export class Bitmap {
572
782
  return count;
573
783
  }
574
784
  countBoxes2x2(y: number): number {
575
- const { width, words } = this;
576
- if (width < 2 || (y | 0) < 0 || y + 1 >= this.height) return 0;
577
- const base0 = this.wordIndex(0, y) | 0;
578
- const base1 = this.wordIndex(0, y + 1) | 0;
785
+ const { height, width, words } = this;
786
+ // ISO/IEC 18004:2024 §7.8.3.1 N2 counts 2 x 2 module blocks in bounded
787
+ // rows; reject non-integer scan rows before bitwise coercions truncate them.
788
+ if (width < 2 || !Number.isSafeInteger(y) || y < 0 || y + 1 >= height) return 0;
789
+ const base0 = this.wordIndex(0, y);
790
+ const base1 = this.wordIndex(0, y + 1);
579
791
  // valid "left-edge" positions x in [0 .. W-2]
580
792
  const tailBits = width & 31;
581
793
  const validLast = tailBits === 0 ? 0x7fffffff : rangeMask(0, (width - 1) & 31);
@@ -613,6 +825,9 @@ export class Bitmap {
613
825
  const out: DrawValue[][] = Array.from({ length: this.height }, () => new Array(this.width));
614
826
  for (let y = 0; y < this.height; y++) {
615
827
  const row = out[y];
828
+ // Bitmap storage docs say "undefined is used as a marker whether cell was
829
+ // written or not"; `toRaw()` is the materialized dark/not-dark output path
830
+ // used after `encodeQR()` asserts the QR symbol is fully drawn.
616
831
  for (let x = 0; x < this.width; x++) row[x] = this.get(x, y);
617
832
  }
618
833
  return out;
@@ -656,6 +871,8 @@ export class Bitmap {
656
871
  }
657
872
  toSVG(optimize = true): string {
658
873
  let out = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`;
874
+ // ISO/IEC 18004:2024 §5.1 c) / §5.3.8: this SVG draws only dark modules;
875
+ // callers must render it on a light background for light modules and the quiet zone.
659
876
  // Construct optimized SVG path data.
660
877
  let pathData = '';
661
878
  let prevPoint: Point | undefined;
@@ -701,7 +918,9 @@ export class Bitmap {
701
918
  const u16le = (i: number) => [i & 0xff, (i >>> 8) & 0xff];
702
919
  const dims = [...u16le(this.width), ...u16le(this.height)];
703
920
  const data: number[] = [];
921
+ // Palette index 0 is white/light and index 1 is black/dark; rectRead maps undefined cells to light.
704
922
  this.rectRead(0, Infinity, (_, cur) => data.push(+(cur === true)));
923
+ // Each chunk starts with an LZW clear code; 126 raw pixels keep codes at 8 bits until the next clear.
705
924
  const N = 126; // Block size
706
925
  // prettier-ignore
707
926
  const bytes = [
@@ -736,20 +955,27 @@ export class Bitmap {
736
955
  // End of utils
737
956
 
738
957
  // Runtime type-checking
739
- /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30% */
740
- export const ECMode = ['low', 'medium', 'quartile', 'high'] as const;
741
- /** Error correction mode. */
958
+ /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30%. */
959
+ export const ECMode: readonly ['low', 'medium', 'quartile', 'high'] = /* @__PURE__ */ Object.freeze(
960
+ ['low', 'medium', 'quartile', 'high']
961
+ );
962
+ /** Error correction mode name. */
742
963
  export type ErrorCorrection = (typeof ECMode)[number];
743
- /** QR Code version. */
964
+ /** QR Code version in the `[1..40]` range. */
744
965
  export type Version = number; // 1..40
745
- /** QR Code mask type */
966
+ /** QR Code mask index. */
746
967
  export type Mask = (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7) & keyof typeof PATTERNS; // 0..7
747
- /** QR Code encoding */
748
- export const Encoding = ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'] as const;
749
- /** QR Code encoding type */
968
+ /**
969
+ * QR payload compaction mode names recognized by the type/validator.
970
+ * `kanji` and `eci` are spec modes, but `encodeQR` currently rejects them until implemented.
971
+ */
972
+ export const Encoding: readonly ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'] =
973
+ /* @__PURE__ */ Object.freeze(['numeric', 'alphanumeric', 'byte', 'kanji', 'eci']);
974
+ /** QR payload encoding name. */
750
975
  export type EncodingType = (typeof Encoding)[number];
751
976
 
752
977
  // Various constants & tables
978
+ // ISO/IEC 18004:2024 Table 1: QR symbol codeword capacity by version (data plus error correction).
753
979
  // prettier-ignore
754
980
  const BYTES = [
755
981
  // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
@@ -757,6 +983,7 @@ const BYTES = [
757
983
  // 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
758
984
  1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706,
759
985
  ];
986
+ // ISO/IEC 18004:2024 Table 9: error correction codewords per block by version and level.
760
987
  // prettier-ignore
761
988
  const WORDS_PER_BLOCK = {
762
989
  // 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
@@ -765,6 +992,7 @@ const WORDS_PER_BLOCK = {
765
992
  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],
766
993
  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],
767
994
  };
995
+ // ISO/IEC 18004:2024 Table 9: error correction block count by version and level.
768
996
  // prettier-ignore
769
997
  const ECC_BLOCKS = {
770
998
  // 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
@@ -774,12 +1002,15 @@ const ECC_BLOCKS = {
774
1002
  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],
775
1003
  };
776
1004
 
777
- const info = {
778
- size: {
779
- encode: (ver: Version) => 21 + 4 * (ver - 1), // ver1 = 21, ver40=177 blocks
1005
+ // ISO/IEC 18004:2024 sections 5.3/7.4/7.5/7.9/7.10: QR layout, segment, format/version, and capacity helpers.
1006
+ const info = /* @__PURE__ */ Object.freeze({
1007
+ size: /* @__PURE__ */ Object.freeze({
1008
+ encode: (ver: Version) => 21 + 4 * (ver - 1), // ver1 = 21, ver40 = 177 modules per side
780
1009
  decode: (size: number) => (size - 17) / 4,
781
- } as Coder<Version, number>,
1010
+ } as Coder<Version, number>),
1011
+ // ISO/IEC 18004:2024 Table 3: map version ranges 1-9, 10-26, and 27-40 to count-width indexes.
782
1012
  sizeType: (ver: Version) => Math.floor((ver + 7) / 17),
1013
+ // ISO/IEC 18004:2024 Annex E Table E.1: row/column coordinate list of alignment-pattern centres.
783
1014
  // Based on https://codereview.stackexchange.com/questions/74925/algorithm-to-generate-this-alignment-pattern-locations-table-for-qr-codes
784
1015
  alignmentPatterns(ver: Version) {
785
1016
  if (ver === 1) return [];
@@ -795,28 +1026,36 @@ const info = {
795
1026
  res.push(last);
796
1027
  return res;
797
1028
  },
798
- ECCode: {
1029
+ // ISO/IEC 18004:2024 §7.9.1 Table 12: error-correction-level indicators for the top two format-information data bits.
1030
+ ECCode: /* @__PURE__ */ Object.freeze({
799
1031
  low: 0b01,
800
1032
  medium: 0b00,
801
1033
  quartile: 0b11,
802
1034
  high: 0b10,
803
- } as Record<ErrorCorrection, number>,
1035
+ } as Record<ErrorCorrection, number>),
1036
+ // ISO/IEC 18004:2024 §7.9.1 final paragraph: XOR the 15-bit format information with mask pattern 101010000010010.
804
1037
  formatMask: 0b101010000010010,
1038
+ // 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.
805
1039
  formatBits(ecc: ErrorCorrection, maskIdx: Mask) {
806
1040
  const data = (info.ECCode[ecc] << 3) | maskIdx;
807
1041
  let d = data;
808
1042
  for (let i = 0; i < 10; i++) d = (d << 1) ^ ((d >> 9) * 0b10100110111);
809
1043
  return ((data << 10) | d) ^ info.formatMask;
810
1044
  },
1045
+ // 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.
811
1046
  versionBits(ver: Version) {
812
1047
  let d = ver;
813
1048
  for (let i = 0; i < 12; i++) d = (d << 1) ^ ((d >> 11) * 0b1111100100101);
814
1049
  return (ver << 12) | d;
815
1050
  },
816
- alphabet: {
1051
+ // 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.
1052
+ alphabet: /* @__PURE__ */ Object.freeze({
1053
+ // ISO/IEC 18004:2024 §7.3.3 / §7.4.4: numeric-mode digits map directly to values 0..9 before 3-digit grouping.
817
1054
  numeric: alphabet('0123456789'),
1055
+ // 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.
818
1056
  alphanumerc: alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'),
819
- }, // as Record<EncodingType, ReturnType<typeof alphabet>>,
1057
+ }), // as Record<EncodingType, ReturnType<typeof alphabet>>,
1058
+ // 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.
820
1059
  lengthBits(ver: Version, type: EncodingType) {
821
1060
  const table: Record<EncodingType, [number, number, number]> = {
822
1061
  numeric: [10, 12, 14],
@@ -827,13 +1066,15 @@ const info = {
827
1066
  };
828
1067
  return table[type][info.sizeType(ver)];
829
1068
  },
830
- modeBits: {
1069
+ // ISO/IEC 18004:2024 §7.4.2 Table 2: 4-bit QR mode indicators for the segment types this library models.
1070
+ modeBits: /* @__PURE__ */ Object.freeze({
831
1071
  numeric: '0001',
832
1072
  alphanumeric: '0010',
833
1073
  byte: '0100',
834
1074
  kanji: '1000',
835
1075
  eci: '0111',
836
- },
1076
+ }),
1077
+ // 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.
837
1078
  capacity(ver: Version, ecc: ErrorCorrection) {
838
1079
  const bytes = BYTES[ver - 1];
839
1080
  const words = WORDS_PER_BLOCK[ecc][ver - 1];
@@ -849,20 +1090,22 @@ const info = {
849
1090
  total: (words + blockLen) * numBlocks + numBlocks - shortBlocks,
850
1091
  };
851
1092
  },
852
- };
1093
+ });
853
1094
 
854
- const PATTERNS: readonly ((x: number, y: number) => boolean)[] = [
855
- (x, y) => (x + y) % 2 == 0,
856
- (_x, y) => y % 2 == 0,
857
- (x, _y) => x % 3 == 0,
858
- (x, y) => (x + y) % 3 == 0,
859
- (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 == 0,
860
- (x, y) => ((x * y) % 2) + ((x * y) % 3) == 0,
861
- (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0,
862
- (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0,
863
- ] as const;
1095
+ // ISO/IEC 18004:2024 Table 10: QR data-mask predicates 000..111, written here in (x column, y row) form.
1096
+ const PATTERNS: readonly ((x: number, y: number) => boolean)[] = /* @__PURE__ */ Object.freeze([
1097
+ (x: number, y: number) => (x + y) % 2 == 0,
1098
+ (_x: number, y: number) => y % 2 == 0,
1099
+ (x: number, _y: number) => x % 3 == 0,
1100
+ (x: number, y: number) => (x + y) % 3 == 0,
1101
+ (x: number, y: number) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 == 0,
1102
+ (x: number, y: number) => ((x * y) % 2) + ((x * y) % 3) == 0,
1103
+ (x: number, y: number) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0,
1104
+ (x: number, y: number) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0,
1105
+ ] as const);
864
1106
 
865
1107
  // Galois field && reed-solomon encoding
1108
+ // 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.
866
1109
  const GF = {
867
1110
  tables: ((p_poly) => {
868
1111
  const exp = fillArr(256, 0);
@@ -873,23 +1116,40 @@ const GF = {
873
1116
  x <<= 1;
874
1117
  if (x & 0x100) x ^= p_poly;
875
1118
  }
1119
+ // Keep α^255 = 1 in exp[255]; GF.log() folds the matching log[1] = 255
1120
+ // back to 0 with `% 255`, so later helpers can wrap exponents without a special case.
876
1121
  return { exp, log };
877
1122
  })(0x11d),
1123
+ // Raw α^i lookup from the precomputed field table; callers are expected
1124
+ // to reduce / validate exponents before indexing it.
878
1125
  exp: (x: number) => GF.tables.exp[x],
1126
+ // log(0) is undefined in GF(2^8); `% 255` also folds the wrapped table
1127
+ // entry for α^255 = 1 back to exponent 0.
879
1128
  log(x: number) {
880
1129
  if (x === 0) throw new Error(`GF.log: invalid arg=${x}`);
881
1130
  return GF.tables.log[x] % 255;
882
1131
  },
1132
+ // Zero has no logarithm in GF(2^8), so it must short-circuit here; all
1133
+ // other products are α^(log(x) + log(y) mod 255) in the reviewed field.
883
1134
  mul(x: number, y: number) {
884
1135
  if (x === 0 || y === 0) return 0;
885
1136
  return GF.tables.exp[(GF.tables.log[x] + GF.tables.log[y]) % 255];
886
1137
  },
1138
+ // In characteristic 2 fields, addition and subtraction are the same
1139
+ // bitwise XOR operation used by the QR Reed-Solomon arithmetic.
887
1140
  add: (x: number, y: number) => x ^ y,
1141
+ // Raw nonzero field power helper. Current QR use is GF.pow(2, i) for the
1142
+ // Annex A generator factors; x = 0 or negative exponents are not validated.
888
1143
  pow: (x: number, e: number) => GF.tables.exp[(GF.tables.log[x] * e) % 255],
1144
+ // Multiplicative inverse for nonzero field elements. Current callers only
1145
+ // use it on values already known to be nonzero; 0 has no inverse in GF(2^8).
889
1146
  inv(x: number) {
890
1147
  if (x === 0) throw new Error(`GF.inverse: invalid arg=${x}`);
891
1148
  return GF.tables.exp[255 - GF.tables.log[x]];
892
1149
  },
1150
+ // Canonicalize coefficient arrays by trimming leading zero coefficients
1151
+ // while preserving `[0]` as the zero polynomial; already-normalized inputs
1152
+ // are returned by reference.
893
1153
  polynomial(poly: number[]) {
894
1154
  if (poly.length == 0) throw new Error('GF.polymomial: invalid length');
895
1155
  if (poly[0] !== 0) return poly;
@@ -898,6 +1158,8 @@ const GF = {
898
1158
  for (; i < poly.length - 1 && poly[i] == 0; i++);
899
1159
  return poly.slice(i);
900
1160
  },
1161
+ // Represent c*x^degree in the descending-power coefficient layout used
1162
+ // by the QR Reed-Solomon helpers; coefficient 0 canonicalizes to `[0]`.
901
1163
  monomial(degree: number, coefficient: number) {
902
1164
  if (degree < 0) throw new Error(`GF.monomial: invalid degree=${degree}`);
903
1165
  if (coefficient == 0) return [0];
@@ -905,8 +1167,14 @@ const GF = {
905
1167
  coefficients[0] = coefficient;
906
1168
  return GF.polynomial(coefficients);
907
1169
  },
1170
+ // Canonical polynomials keep the highest-order coefficient first and use
1171
+ // `[0]` for zero, so degree is just `length - 1`.
908
1172
  degree: (a: number[]) => a.length - 1,
1173
+ // Read the coefficient for x^degree from the descending-power array layout.
1174
+ // Canonical arrays make this a direct index; out-of-range degrees return `undefined`.
909
1175
  coefficient: (a: any, degree: number) => a[GF.degree(a) - degree],
1176
+ // Multiply descending-power coefficient arrays by convolution over GF(2^8).
1177
+ // Zero short-circuits here before the log-based field multiply is consulted.
910
1178
  mulPoly(a: number[], b: number[]) {
911
1179
  if (a[0] === 0 || b[0] === 0) return [0];
912
1180
  const res = fillArr(a.length + b.length - 1, 0);
@@ -917,6 +1185,8 @@ const GF = {
917
1185
  }
918
1186
  return GF.polynomial(res);
919
1187
  },
1188
+ // Scale every coefficient by the same field element in descending-power order.
1189
+ // Scalar 0 canonicalizes to `[0]`, and scalar 1 reuses the original array.
920
1190
  mulPolyScalar(a: number[], scalar: number) {
921
1191
  if (scalar == 0) return [0];
922
1192
  if (scalar == 1) return a;
@@ -924,6 +1194,8 @@ const GF = {
924
1194
  for (let i = 0; i < a.length; i++) res[i] = GF.mul(a[i], scalar);
925
1195
  return GF.polynomial(res);
926
1196
  },
1197
+ // Multiply a polynomial by c*x^degree in descending-power coefficient form.
1198
+ // This scales existing coefficients, then appends trailing zero coefficients.
927
1199
  mulPolyMonomial(a: number[], degree: number, coefficient: number) {
928
1200
  if (degree < 0) throw new Error('GF.mulPolyMonomial: invalid degree');
929
1201
  if (coefficient == 0) return [0];
@@ -931,6 +1203,8 @@ const GF = {
931
1203
  for (let i = 0; i < a.length; i++) res[i] = GF.mul(a[i], coefficient);
932
1204
  return GF.polynomial(res);
933
1205
  },
1206
+ // Add descending-power coefficient arrays with GF(2^8) XOR on the aligned
1207
+ // suffix; `[0]` short-circuits by returning the other array unchanged.
934
1208
  addPoly(a: number[], b: number[]) {
935
1209
  if (a[0] === 0) return b;
936
1210
  if (b[0] === 0) return a;
@@ -945,6 +1219,8 @@ const GF = {
945
1219
  sumDiff[i] = GF.add(smaller[i - lengthDiff], larger[i]);
946
1220
  return GF.polynomial(sumDiff);
947
1221
  },
1222
+ // Synthetic division for monic divisors in descending-power coefficient form.
1223
+ // Callers are expected to append `divisor.length - 1` zero coefficients first.
948
1224
  remainderPoly(data: number[], divisor: number[]) {
949
1225
  const out = Array.from(data);
950
1226
  for (let i = 0; i < data.length - divisor.length + 1; i++) {
@@ -956,11 +1232,15 @@ const GF = {
956
1232
  }
957
1233
  return out.slice(data.length - divisor.length + 1, out.length);
958
1234
  },
1235
+ // Build Annex A's monic generator polynomial g_n(x) = Π(x - 2^i).
1236
+ // degree=0 returns `[1]`; callers are expected to validate degree bounds.
959
1237
  divisorPoly(degree: number) {
960
1238
  let g = [1];
961
1239
  for (let i = 0; i < degree; i++) g = GF.mulPoly(g, [1, GF.pow(2, i)]);
962
1240
  return g;
963
1241
  },
1242
+ // Evaluate a descending-power coefficient array at `a` with Horner's rule.
1243
+ // The `a == 0` fast-path returns the x^0 coefficient directly.
964
1244
  evalPoly(poly: any, a: number) {
965
1245
  if (a == 0) return GF.coefficient(poly, 0); // Just return the x^0 coefficient
966
1246
  let res = poly[0];
@@ -968,6 +1248,8 @@ const GF = {
968
1248
  return res;
969
1249
  },
970
1250
  // TODO: cleanup
1251
+ // Extended Euclidean RS step: derive the locator/evaluator pair from x^R
1252
+ // and the syndrome polynomial, then normalize sigma(0) to 1.
971
1253
  euclidian(a: number[], b: number[], R: number) {
972
1254
  // Force degree(a) >= degree(b)
973
1255
  if (GF.degree(a) < GF.degree(b)) [a, b] = [b, a];
@@ -1004,15 +1286,17 @@ const GF = {
1004
1286
  },
1005
1287
  };
1006
1288
 
1007
- function RS(eccWords: number): Coder<Uint8Array, Uint8Array> {
1289
+ // Per-block Reed-Solomon coder: encode emits only the parity bytes for one
1290
+ // data block, while decode expects data+parity bytes and returns the corrected full block.
1291
+ function RS(eccWords: number): TRet<Coder<Uint8Array, Uint8Array>> {
1008
1292
  return {
1009
- encode(from: Uint8Array) {
1293
+ encode(from: TArg<Uint8Array>): TRet<Uint8Array> {
1010
1294
  const d = GF.divisorPoly(eccWords);
1011
1295
  const pol = Array.from(from);
1012
1296
  pol.push(...d.slice(0, -1).fill(0));
1013
- return Uint8Array.from(GF.remainderPoly(pol, d));
1297
+ return Uint8Array.from(GF.remainderPoly(pol, d)) as TRet<Uint8Array>;
1014
1298
  },
1015
- decode(to: Uint8Array) {
1299
+ decode(to: TArg<Uint8Array>): TRet<Uint8Array> {
1016
1300
  const res = to.slice();
1017
1301
  const poly = GF.polynomial(Array.from(to));
1018
1302
  // Find errors
@@ -1048,17 +1332,21 @@ function RS(eccWords: number): Coder<Uint8Array, Uint8Array> {
1048
1332
  GF.mul(GF.evalPoly(errorEvaluator, xiInverse), GF.inv(denominator))
1049
1333
  );
1050
1334
  }
1051
- return res;
1335
+ return res as TRet<Uint8Array>;
1052
1336
  },
1053
- };
1337
+ } as TRet<Coder<Uint8Array, Uint8Array>>;
1054
1338
  }
1055
1339
 
1056
1340
  // Interleaves blocks
1057
- function interleave(ver: Version, ecc: ErrorCorrection): Coder<Uint8Array, Uint8Array> {
1341
+ // QR block interleaver / deinterleaver. Shorter data blocks stay first so
1342
+ // encode matches ISO/IEC 18004 §7.6 c) and decode can reverse it via §12 z)1.
1343
+ function interleave(ver: Version, ecc: ErrorCorrection): TRet<Coder<Uint8Array, Uint8Array>> {
1058
1344
  const { words, shortBlocks, numBlocks, blockLen, total } = info.capacity(ver, ecc);
1059
1345
  const rs = RS(words);
1060
1346
  return {
1061
- encode(bytes: Uint8Array) {
1347
+ encode(bytes: TArg<Uint8Array>): TRet<Uint8Array> {
1348
+ // Caller must pass exactly the data codewords for this version/ecc;
1349
+ // this helper only splits blocks and interleaves them with RS parity.
1062
1350
  // Add error correction to bytes
1063
1351
  const blocks: Uint8Array[] = [];
1064
1352
  const eccBlocks: Uint8Array[] = [];
@@ -1074,9 +1362,9 @@ function interleave(ver: Version, ecc: ErrorCorrection): Coder<Uint8Array, Uint8
1074
1362
  const res = new Uint8Array(resBlocks.length + resECC.length);
1075
1363
  res.set(resBlocks);
1076
1364
  res.set(resECC, resBlocks.length);
1077
- return res;
1365
+ return res as TRet<Uint8Array>;
1078
1366
  },
1079
- decode(data: Uint8Array) {
1367
+ decode(data: TArg<Uint8Array>): TRet<Uint8Array> {
1080
1368
  if (data.length !== total)
1081
1369
  throw new Error(`interleave.decode: len(data)=${data.length}, total=${total}`);
1082
1370
  const blocks = [];
@@ -1102,19 +1390,21 @@ function interleave(ver: Version, ecc: ErrorCorrection): Coder<Uint8Array, Uint8
1102
1390
  // Error-correct and copy data blocks together into a stream of bytes
1103
1391
  const res: number[] = [];
1104
1392
  for (const block of blocks) res.push(...Array.from(rs.decode(block)).slice(0, -words));
1105
- return Uint8Array.from(res);
1393
+ return Uint8Array.from(res) as TRet<Uint8Array>;
1106
1394
  },
1107
- };
1395
+ } as TRet<Coder<Uint8Array, Uint8Array>>;
1108
1396
  }
1109
1397
 
1110
1398
  // Draw
1111
1399
  // Generic template per version+ecc+mask. Can be cached, to speedup calculations.
1400
+ // Function-pattern template plus reserved format/version areas; data modules
1401
+ // are filled later by zigzag placement in `drawQR`.
1112
1402
  function drawTemplate(
1113
1403
  ver: Version,
1114
1404
  ecc: ErrorCorrection,
1115
1405
  maskIdx: Mask,
1116
1406
  test: boolean = false
1117
- ): Bitmap {
1407
+ ): TRet<Bitmap> {
1118
1408
  const size = info.size.encode(ver);
1119
1409
  let b = new Bitmap(size + 2);
1120
1410
  // Finder patterns
@@ -1166,15 +1456,17 @@ function drawTemplate(
1166
1456
  b.set(x, y, bit);
1167
1457
  }
1168
1458
  }
1169
- return b;
1459
+ return b as TRet<Bitmap>;
1170
1460
  }
1171
- // zigzag: bottom->top && top->bottom
1461
+ // Walk undefined data modules in the QR two-column zigzag order from the
1462
+ // lower right, skipping function patterns and the vertical timing column.
1172
1463
  function zigzag(
1173
- tpl: Bitmap,
1464
+ tpl: TArg<Bitmap>,
1174
1465
  maskIdx: Mask,
1175
1466
  fn: (x: number, y: number, mask: boolean) => void
1176
1467
  ): void {
1177
- const size = tpl.height;
1468
+ const bm = tpl as Bitmap;
1469
+ const size = bm.height;
1178
1470
  const pattern = PATTERNS[maskIdx];
1179
1471
  // zig-zag pattern
1180
1472
  let dir = -1;
@@ -1185,7 +1477,7 @@ function zigzag(
1185
1477
  for (; ; y += dir) {
1186
1478
  for (let j = 0; j < 2; j += 1) {
1187
1479
  const x = xOffset - j;
1188
- if (tpl.isDefined(x, y)) continue; // skip already written elements
1480
+ if (bm.isDefined(x, y)) continue; // skip already written elements
1189
1481
  fn(x, y, pattern(x, y));
1190
1482
  }
1191
1483
  if (y + dir < 0 || y + dir >= size) break;
@@ -1196,6 +1488,8 @@ function zigzag(
1196
1488
 
1197
1489
  // NOTE: byte encoding is just representation, QR works with strings only. Most decoders will fail on raw byte array,
1198
1490
  // since they expect unicode or other text encoding inside bytes
1491
+ // Auto-pick among the currently supported single-segment modes only.
1492
+ // Empty strings stay numeric, and any non-alphanumeric character falls back to byte.
1199
1493
  function detectType(str: string): EncodingType {
1200
1494
  let type: EncodingType = 'numeric';
1201
1495
  for (let x of str) {
@@ -1210,20 +1504,34 @@ function detectType(str: string): EncodingType {
1210
1504
  // See https://github.com/microsoft/TypeScript/issues/31535
1211
1505
  declare const TextEncoder: any;
1212
1506
  /**
1213
- * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
1507
+ * Encode a string as UTF-8 bytes.
1508
+ * @param str - Text to encode into UTF-8.
1509
+ * @returns UTF-8 bytes for the provided string.
1510
+ * @throws If the input is not a string. {@link Error}
1511
+ * @example
1512
+ * Encode a string as UTF-8 bytes.
1513
+ * ```ts
1514
+ * const bytes = utf8ToBytes('abc'); // new Uint8Array([97, 98, 99])
1515
+ * ```
1214
1516
  */
1215
- export function utf8ToBytes(str: string): Uint8Array {
1517
+ // ISO/IEC 18004:2024 §7.3.2 says QR's default interpretation is
1518
+ // "ECI 000003 representing the ISO/IEC 8859-1 character set"; §7.4.2 says
1519
+ // non-default initial ECI data starts with an ECI header. Keep UTF-8 bytes
1520
+ // without that header for compatibility with existing emoji/qrcode fixtures.
1521
+ export function utf8ToBytes(str: string): TRet<Uint8Array> {
1216
1522
  if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
1217
- return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
1523
+ return new Uint8Array(new TextEncoder().encode(str)) as TRet<Uint8Array>; // https://bugzil.la/1681809
1218
1524
  }
1219
1525
 
1526
+ // Build one QR mode/count/data segment, then append the terminator, zero padding,
1527
+ // and alternating pad codewords before RS interleaving.
1220
1528
  function encode(
1221
1529
  ver: Version,
1222
1530
  ecc: ErrorCorrection,
1223
1531
  data: string,
1224
1532
  type: EncodingType,
1225
- encoder: (value: string) => Uint8Array = utf8ToBytes
1226
- ): Uint8Array {
1533
+ encoder: TArg<(value: string) => Uint8Array> = utf8ToBytes
1534
+ ): TRet<Uint8Array> {
1227
1535
  let encoded = '';
1228
1536
  let dataLen = data.length;
1229
1537
  if (type === 'numeric') {
@@ -1241,6 +1549,7 @@ function encode(
1241
1549
  for (let i = 0; i < n - 1; i += 2) encoded += bin(t[i] * 45 + t[i + 1], 11);
1242
1550
  if (n % 2 == 1) encoded += bin(t[n - 1], 6); // pad if odd number of chars
1243
1551
  } else if (type === 'byte') {
1552
+ // The default encoder is intentionally UTF-8-without-ECI; see utf8ToBytes().
1244
1553
  const utf8 = encoder(data);
1245
1554
  dataLen = utf8.length;
1246
1555
  encoded = Array.from(utf8)
@@ -1262,19 +1571,20 @@ function encode(
1262
1571
  for (let idx = 0; bits.length !== capacity; idx++) bits += padding[idx % padding.length];
1263
1572
  // Convert a bitstring to array of bytes
1264
1573
  const bytes = Uint8Array.from(bits.match(/(.{8})/g)!.map((i) => Number(`0b${i}`)));
1265
- return interleave(ver, ecc).encode(bytes);
1574
+ return interleave(ver, ecc).encode(bytes) as TRet<Uint8Array>;
1266
1575
  }
1267
1576
 
1268
1577
  // DRAW
1269
-
1578
+ // Stream interleaved codeword bits MSB-first through zigzag; any leftover
1579
+ // cells after the final codeword become zero-valued remainder bits before masking.
1270
1580
  function drawQR(
1271
1581
  ver: Version,
1272
1582
  ecc: ErrorCorrection,
1273
- data: Uint8Array,
1583
+ data: TArg<Uint8Array>,
1274
1584
  maskIdx: Mask,
1275
1585
  test: boolean = false
1276
- ): Bitmap {
1277
- const b = drawTemplate(ver, ecc, maskIdx, test);
1586
+ ): TRet<Bitmap> {
1587
+ const b = drawTemplate(ver, ecc, maskIdx, test) as Bitmap;
1278
1588
  let i = 0;
1279
1589
  const need = 8 * data.length;
1280
1590
  zigzag(b, maskIdx, (x, y, mask) => {
@@ -1286,9 +1596,11 @@ function drawQR(
1286
1596
  b.set(x, y, value !== mask); // !== as xor
1287
1597
  });
1288
1598
  if (i !== need) throw new Error('QR: bytes left after draw');
1289
- return b;
1599
+ return b as TRet<Bitmap>;
1290
1600
  }
1291
1601
 
1602
+ // Pack a left-to-right row pattern for `Bitmap.countPatternInRow()`; keep the
1603
+ // explicit width because leading light modules vanish from the numeric value.
1292
1604
  const mkPattern = (pattern: boolean[]) => {
1293
1605
  const s = pattern.map((i) => (i ? '1' : '0')).join('');
1294
1606
  return { len: s.length, n: Number(`0b${s}`) };
@@ -1296,16 +1608,17 @@ const mkPattern = (pattern: boolean[]) => {
1296
1608
  // 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide
1297
1609
  const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
1298
1610
  const lightPattern = [false, false, false, false]; // light area 4 modules wide
1299
- const P1 = mkPattern([...finderPattern, ...lightPattern]);
1300
- const P2 = mkPattern([...lightPattern, ...finderPattern]);
1611
+ const P1 = /* @__PURE__ */ (() => mkPattern([...finderPattern, ...lightPattern]))();
1612
+ const P2 = /* @__PURE__ */ (() => mkPattern([...lightPattern, ...finderPattern]))();
1301
1613
 
1302
- function penalty(bm: Bitmap): number {
1303
- const { width, height } = bm;
1304
- const transposed = bm.transpose();
1614
+ function penalty(bm: TArg<Bitmap>): number {
1615
+ const b = bm as Bitmap;
1616
+ const { width, height } = b;
1617
+ const transposed = b.transpose();
1305
1618
  // Adjacent modules in row/column in same | No. of modules = (5 + i) color
1306
1619
  let adjacent = 0;
1307
1620
  for (let y = 0; y < height; y++) {
1308
- bm.getRuns(y, (len) => {
1621
+ b.getRuns(y, (len) => {
1309
1622
  if (len >= 5) adjacent += 3 + (len - 5);
1310
1623
  });
1311
1624
  }
@@ -1316,31 +1629,37 @@ function penalty(bm: Bitmap): number {
1316
1629
  }
1317
1630
  // Block of modules in same color (Block size = 2x2)
1318
1631
  let box = 0;
1319
- for (let y = 0; y < height - 1; y++) box += 3 * bm.countBoxes2x2(y);
1632
+ for (let y = 0; y < height - 1; y++) box += 3 * b.countBoxes2x2(y);
1320
1633
 
1321
1634
  let finder = 0;
1322
- for (let y = 0; y < height; y++) finder += 40 * bm.countPatternInRow(y, P1.len, P1.n, P2.n);
1635
+ for (let y = 0; y < height; y++) finder += 40 * b.countPatternInRow(y, P1.len, P1.n, P2.n);
1323
1636
  for (let y = 0; y < width; y++)
1324
1637
  finder += 40 * transposed.countPatternInRow(y, P1.len, P1.n, P2.n);
1325
1638
 
1326
- // Proportion of dark modules in entire symbol
1327
- // Add 10 points to a deviation of 5% increment or decrement in the proportion
1328
- // ratio of dark module from the referential 50%
1329
- let darkPixels = 0;
1330
- darkPixels = bm.popcnt();
1331
- //bm.rectRead(0, Infinity, (_c, val) => (darkPixels += val ? 1 : 0));
1332
- // for (let y = 0; y < height; y++) {
1333
- // for (let x = 0; x < width; x++) if (bm.get(x, y)) darkPixels++;
1334
- // }
1335
- const darkPercent = (darkPixels / (height * width)) * 100;
1336
- const dark = 10 * Math.floor(Math.abs(darkPercent - 50) / 5);
1639
+ const total = height * width;
1640
+ const darkPixels = b.popcnt();
1641
+ // ISO/IEC 18004:2024 §7.8.3.1 NOTE 4 assigns "0 points" when the dark ratio
1642
+ // is "between 45 % and 55 %"; subtract that first 5% deviation band before
1643
+ // rating further 5% steps, so exact 45/55 and 40/60 boundaries stay in-band.
1644
+ const darkSteps = Math.ceil(
1645
+ Math.max(0, Math.abs(darkPixels * 100 - total * 50) - total * 5) / (total * 5)
1646
+ );
1647
+ const dark = 10 * darkSteps;
1337
1648
  return adjacent + box + finder + dark;
1338
1649
  }
1339
1650
 
1340
1651
  // Selects best mask according to penalty, if no mask is provided
1341
- function drawQRBest(ver: Version, ecc: ErrorCorrection, data: Uint8Array, maskIdx?: Mask) {
1652
+ function drawQRBest(
1653
+ ver: Version,
1654
+ ecc: ErrorCorrection,
1655
+ data: TArg<Uint8Array>,
1656
+ maskIdx?: Mask
1657
+ ): TRet<Bitmap> {
1342
1658
  if (maskIdx === undefined) {
1343
1659
  const bestMask = best<Mask>();
1660
+ // ISO/IEC 18004:2024 §7.8.3.1 says mask penalty area is "the complete symbol",
1661
+ // but python-qrcode scores this placeholder form. Keep that output for compatibility
1662
+ // with common QR generators and to avoid fingerprinting this implementation.
1344
1663
  for (let mask = 0; mask < PATTERNS.length; mask++)
1345
1664
  bestMask.add(penalty(drawQR(ver, ecc, data, mask as Mask, true)), mask as Mask);
1346
1665
  maskIdx = bestMask.get();
@@ -1351,31 +1670,45 @@ function drawQRBest(ver: Version, ecc: ErrorCorrection, data: Uint8Array, maskId
1351
1670
 
1352
1671
  /** QR Code generation options. */
1353
1672
  export type QrOpts = {
1673
+ /** Error-correction level to encode into the symbol. */
1354
1674
  ecc?: ErrorCorrection | undefined;
1675
+ /** Explicit payload encoding, otherwise detected from the input text. */
1355
1676
  encoding?: EncodingType | undefined;
1356
- textEncoder?: (text: string) => Uint8Array;
1677
+ /**
1678
+ * Custom text encoder used for `byte` payloads.
1679
+ *
1680
+ * Receives the text payload and returns the encoded byte sequence.
1681
+ * @param text - Text payload to encode.
1682
+ * @returns Encoded byte sequence for the payload.
1683
+ */
1684
+ textEncoder?: TArg<(text: string) => Uint8Array>;
1685
+ /** Explicit QR version to use instead of auto-fitting. */
1357
1686
  version?: Version | undefined;
1687
+ /** Explicit mask pattern to apply instead of choosing the best one. */
1358
1688
  mask?: number | undefined;
1689
+ /** Quiet-zone border width in modules. */
1359
1690
  border?: number | undefined;
1691
+ /** Output scale multiplier for raster formats. */
1360
1692
  scale?: number | undefined;
1361
1693
  };
1694
+ /** SVG-specific QR output options. */
1362
1695
  export type SvgQrOpts = {
1363
1696
  /**
1364
1697
  * Controls how cells are generated within the SVG.
1365
1698
  *
1366
1699
  * If `true`:
1367
1700
  * - Cells are drawn using a single `path` element.
1368
- * - Pro: significantly reduces the size of the QR code (>70% smaller than
1701
+ * - Pro: significantly reduces the size of the QR code (`70%` smaller than
1369
1702
  * unoptimized).
1370
1703
  * - Con: less flexible with visually customizing cell shapes.
1371
1704
  *
1372
1705
  * If `false`:
1373
1706
  * - Each cell is drawn with its own `rect` element.
1374
1707
  * - Pro: allows more flexibility with visually customizing cells shapes.
1375
- * - Con: significantly increases the QR code size (>230% larger than
1708
+ * - Con: significantly increases the QR code size (`230%` larger than
1376
1709
  * optimized).
1377
1710
  *
1378
- * @default true
1711
+ * Default is `true`.
1379
1712
  */
1380
1713
  optimize?: boolean | undefined;
1381
1714
  };
@@ -1393,45 +1726,54 @@ function validateMask(mask: Mask) {
1393
1726
  if (![0, 1, 2, 3, 4, 5, 6, 7].includes(mask) || !PATTERNS[mask])
1394
1727
  throw new Error(`Invalid mask=${mask}. Expected number [0..7]`);
1395
1728
  }
1729
+ /** Supported encoder outputs. */
1396
1730
  export type Output = 'raw' | 'ascii' | 'term' | 'gif' | 'svg';
1397
1731
 
1398
1732
  /**
1399
1733
  * Encodes (creates / generates) QR code.
1400
- * @param text text that would be encoded
1401
- * @param output output type: raw, ascii, svg, gif, or term
1402
- * @param opts
1734
+ * @param text - Text payload that should be encoded into the QR symbol.
1735
+ * @param output - Output format to generate: raw matrix, ASCII, terminal ANSI, GIF, or SVG.
1736
+ * @param opts - Encoding and rendering options. See {@link QrOpts} and {@link SvgQrOpts}.
1737
+ * @returns Encoded QR data in the format selected by `output`.
1738
+ * @throws If the payload, options, QR capacity, or output format are invalid. {@link Error}
1403
1739
  * @example
1404
- ```js
1405
- const txt = 'Hello world';
1406
- const ascii = encodeQR(txt, 'ascii'); // Not all fonts are supported
1407
- const terminalFriendly = encodeQR(txt, 'term'); // 2x larger, all fonts are OK
1408
- const gifBytes = encodeQR(txt, 'gif'); // Uncompressed GIF
1409
- const svgElement = encodeQR(txt, 'svg'); // SVG vector image element
1410
- const array = encodeQR(txt, 'raw'); // 2d array for canvas or other libs
1411
- ```
1740
+ * Encode one text payload into several QR output formats.
1741
+ * ```ts
1742
+ * const txt = 'Hello world';
1743
+ * const ascii = encodeQR(txt, 'ascii'); // Not all fonts are supported
1744
+ * const terminalFriendly = encodeQR(txt, 'term'); // 2x larger, all fonts are OK
1745
+ * const gifBytes = encodeQR(txt, 'gif'); // Uncompressed GIF
1746
+ * const svgElement = encodeQR(txt, 'svg'); // SVG vector image element
1747
+ * const array = encodeQR(txt, 'raw'); // 2d array for canvas or other libs
1748
+ * ```
1412
1749
  */
1413
- export function encodeQR(text: string, output: 'raw', opts?: QrOpts): boolean[][];
1414
- export function encodeQR(text: string, output: 'ascii' | 'term', opts?: QrOpts): string;
1415
- export function encodeQR(text: string, output: 'svg', opts?: QrOpts & SvgQrOpts): string;
1416
- export function encodeQR(text: string, output: 'gif', opts?: QrOpts): Uint8Array;
1417
- export function encodeQR(text: string, output: Output = 'raw', opts: QrOpts & SvgQrOpts = {}) {
1418
- const ecc = opts.ecc !== undefined ? opts.ecc : 'medium';
1750
+ export function encodeQR(text: string, output: 'raw', opts?: TArg<QrOpts>): boolean[][];
1751
+ export function encodeQR(text: string, output: 'ascii' | 'term', opts?: TArg<QrOpts>): string;
1752
+ export function encodeQR(text: string, output: 'svg', opts?: TArg<QrOpts & SvgQrOpts>): string;
1753
+ export function encodeQR(text: string, output: 'gif', opts?: TArg<QrOpts>): TRet<Uint8Array>;
1754
+ export function encodeQR(
1755
+ text: string,
1756
+ output: Output = 'raw',
1757
+ opts: TArg<QrOpts & SvgQrOpts> = {}
1758
+ ) {
1759
+ const _opts = opts as QrOpts & SvgQrOpts;
1760
+ const ecc = _opts.ecc !== undefined ? _opts.ecc : 'medium';
1419
1761
  validateECC(ecc);
1420
- const encoding = opts.encoding !== undefined ? opts.encoding : detectType(text);
1762
+ const encoding = _opts.encoding !== undefined ? _opts.encoding : detectType(text);
1421
1763
  validateEncoding(encoding);
1422
- if (opts.mask !== undefined) validateMask(opts.mask as Mask);
1423
- let ver = opts.version;
1764
+ if (_opts.mask !== undefined) validateMask(_opts.mask as Mask);
1765
+ let ver = _opts.version;
1424
1766
  let data,
1425
1767
  err = new Error('Unknown error');
1426
1768
  if (ver !== undefined) {
1427
1769
  validateVersion(ver);
1428
- data = encode(ver, ecc, text, encoding, opts.textEncoder);
1770
+ data = encode(ver, ecc, text, encoding, _opts.textEncoder);
1429
1771
  } else {
1430
1772
  // If no version is provided, try to find smallest one which fits
1431
1773
  // Currently just scans all version, can be significantly speedup if needed
1432
1774
  for (let i = 1; i <= 40; i++) {
1433
1775
  try {
1434
- data = encode(i, ecc, text, encoding, opts.textEncoder);
1776
+ data = encode(i, ecc, text, encoding, _opts.textEncoder);
1435
1777
  ver = i;
1436
1778
  break;
1437
1779
  } catch (e) {
@@ -1440,22 +1782,50 @@ export function encodeQR(text: string, output: Output = 'raw', opts: QrOpts & Sv
1440
1782
  }
1441
1783
  }
1442
1784
  if (!ver || !data) throw err;
1443
- let res = drawQRBest(ver, ecc, data, opts.mask as Mask);
1785
+ let res = drawQRBest(ver, ecc, data, _opts.mask as Mask) as Bitmap;
1444
1786
  res.assertDrawn();
1445
- const border = opts.border === undefined ? 2 : opts.border;
1446
- if (!Number.isSafeInteger(border)) throw new Error(`invalid border type=${typeof border}`);
1787
+ // ISO/IEC 18004:2024 §5.3.8 says a QR quiet zone's "width shall be 4X",
1788
+ // and §9.1 requires 4X "on all four sides". Keep the compact historical
1789
+ // 2-module default to avoid changing encoder output; callers that need a
1790
+ // standards-conformant quiet zone must pass `border: 4` explicitly.
1791
+ const border = _opts.border === undefined ? 2 : _opts.border;
1792
+ if (!Number.isSafeInteger(border) || border <= 0) throw new Error(`invalid border=${border}`);
1447
1793
  res = res.border(border, false); // Add border
1448
- if (opts.scale !== undefined) res = res.scale(opts.scale); // Scale image
1794
+ if (_opts.scale !== undefined) res = res.scale(_opts.scale); // Scale image
1449
1795
  if (output === 'raw') return res.toRaw();
1450
1796
  else if (output === 'ascii') return res.toASCII();
1451
- else if (output === 'svg') return res.toSVG(opts.optimize);
1797
+ else if (output === 'svg') return res.toSVG(_opts.optimize);
1452
1798
  else if (output === 'gif') return res.toGIF();
1453
1799
  else if (output === 'term') return res.toTerm();
1454
1800
  else throw new Error(`Unknown output: ${output}`);
1455
1801
  }
1456
1802
 
1803
+ /**
1804
+ * Default export alias for {@link encodeQR}.
1805
+ * @param text - Text payload that should be encoded into the QR symbol.
1806
+ * @param output - Output format to generate: raw matrix, ASCII, terminal ANSI, GIF, or SVG.
1807
+ * @param opts - Encoding and rendering options. See {@link QrOpts} and {@link SvgQrOpts}.
1808
+ * @returns Encoded QR data in the format selected by `output`.
1809
+ * @throws If the payload, options, QR capacity, or output format are invalid. {@link Error}
1810
+ * @example
1811
+ * Encode text into the default export from the package root.
1812
+ * ```ts
1813
+ * import encodeQR from 'qr';
1814
+ * encodeQR('Hello world', 'ascii');
1815
+ * ```
1816
+ */
1457
1817
  export default encodeQR;
1458
1818
 
1819
+ /**
1820
+ * Low-level helpers used by the encoder and test suite.
1821
+ * Exports the shared helper tables/functions through a frozen container.
1822
+ * @example
1823
+ * Read low-level QR metadata tables.
1824
+ * ```ts
1825
+ * import { utils } from 'qr';
1826
+ * const size = utils.info.size.encode(1); // 21
1827
+ * ```
1828
+ */
1459
1829
  export const utils: {
1460
1830
  best: typeof best;
1461
1831
  bin: typeof bin;
@@ -1502,7 +1872,7 @@ export const utils: {
1502
1872
  interleave: typeof interleave;
1503
1873
  validateVersion: typeof validateVersion;
1504
1874
  zigzag: typeof zigzag;
1505
- } = {
1875
+ } = /* @__PURE__ */ Object.freeze({
1506
1876
  best,
1507
1877
  bin,
1508
1878
  popcnt,
@@ -1512,9 +1882,10 @@ export const utils: {
1512
1882
  interleave,
1513
1883
  validateVersion,
1514
1884
  zigzag,
1515
- };
1885
+ });
1516
1886
 
1517
1887
  // Unsafe API utils, exported only for tests
1888
+ // Exposes the shared internal helpers/tables through a frozen container.
1518
1889
  export const _tests: {
1519
1890
  Bitmap: typeof Bitmap;
1520
1891
  info: {
@@ -1559,7 +1930,7 @@ export const _tests: {
1559
1930
  drawQR: typeof drawQR;
1560
1931
  penalty: typeof penalty;
1561
1932
  PATTERNS: readonly ((x: number, y: number) => boolean)[];
1562
- } = {
1933
+ } = /* @__PURE__ */ Object.freeze({
1563
1934
  Bitmap,
1564
1935
  info,
1565
1936
  detectType,
@@ -1567,7 +1938,7 @@ export const _tests: {
1567
1938
  drawQR,
1568
1939
  penalty,
1569
1940
  PATTERNS,
1570
- };
1941
+ });
1571
1942
  // Type tests
1572
1943
  // const o1 = qr('test', 'ascii');
1573
1944
  // const o2 = qr('test', 'raw');