qr 0.5.5 → 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/index.js CHANGED
@@ -14,22 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  */
17
- /**
18
- * Methods for encoding (generating) QR code patterns.
19
- * Check out decode.ts for decoding (reading).
20
- * @module
21
- * @example
22
- ```js
23
- import encodeQR from 'qr';
24
- const txt = 'Hello world';
25
- const ascii = encodeQR(txt, 'ascii'); // Not all fonts are supported
26
- const terminalFriendly = encodeQR(txt, 'term'); // 2x larger, all fonts are OK
27
- const gifBytes = encodeQR(txt, 'gif'); // Uncompressed GIF
28
- const svgElement = encodeQR(txt, 'svg'); // SVG vector image element
29
- const array = encodeQR(txt, 'raw'); // 2d array for canvas or other libs
30
- // import decodeQR from 'qr/decode.js';
31
- ```
32
- */
33
17
  // We do not use newline escape code directly in strings because it's not parser-friendly
34
18
  const chCodes = { newline: 10, reset: 27 };
35
19
  function assertNumber(n) {
@@ -48,6 +32,7 @@ function mod(a, b) {
48
32
  return result >= 0 ? result : b + result;
49
33
  }
50
34
  function fillArr(length, val) {
35
+ // Current callers only pass primitive fill values; object fills would alias references.
51
36
  return new Array(length).fill(val);
52
37
  }
53
38
  function popcnt(n) {
@@ -69,6 +54,8 @@ function interleaveBytes(blocks) {
69
54
  }
70
55
  const result = new Uint8Array(totalLen);
71
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).
72
59
  for (let i = 0; i < maxLen; i++) {
73
60
  for (const block of blocks) {
74
61
  if (i < block.length)
@@ -83,6 +70,7 @@ function best() {
83
70
  let bestScore = Infinity;
84
71
  return {
85
72
  add(score, value) {
73
+ // Ties keep the first candidate so equal-score selections stay deterministic.
86
74
  if (score >= bestScore)
87
75
  return;
88
76
  best = value;
@@ -94,7 +82,8 @@ function best() {
94
82
  }
95
83
  // Based on https://github.com/paulmillr/scure-base/blob/main/index.ts
96
84
  function alphabet(alphabet) {
97
- return {
85
+ // Character order defines the numeric values used by the target QR mode.
86
+ return Object.freeze({
98
87
  has: (char) => alphabet.includes(char),
99
88
  decode: (input) => {
100
89
  if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
@@ -118,7 +107,7 @@ function alphabet(alphabet) {
118
107
  return alphabet[i];
119
108
  });
120
109
  },
121
- };
110
+ });
122
111
  }
123
112
  // Transpose 32x32 bit matrix in-place
124
113
  // a[0..31] are 32 rows of 32 bits each; after transpose they become 32 columns.
@@ -149,10 +138,24 @@ const rangeMask = (shift, len) => {
149
138
  // len in [0..32], shift in [0..31]
150
139
  if (len === 0)
151
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.
152
143
  if (len === 32)
153
144
  return 0xffffffff;
154
145
  return (((1 << len) - 1) << shift) >>> 0;
155
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
+ */
156
159
  export class Bitmap {
157
160
  static size(size, limit) {
158
161
  if (typeof size === 'number')
@@ -172,6 +175,8 @@ export class Bitmap {
172
175
  }
173
176
  static fromString(s) {
174
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.
175
180
  s = s.replace(/^\n+/g, '').replace(/\n+$/g, '');
176
181
  const lines = s.split(String.fromCharCode(chCodes.newline));
177
182
  const height = lines.length;
@@ -209,6 +214,14 @@ export class Bitmap {
209
214
  width;
210
215
  constructor(size, data) {
211
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`);
212
225
  this.height = height;
213
226
  this.width = width;
214
227
  this.tailMask = rangeMask(0, width & 31 || 32);
@@ -230,8 +243,13 @@ export class Bitmap {
230
243
  }
231
244
  }
232
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.
233
250
  return this.get(p.x, p.y);
234
251
  }
252
+ // Raw bounds check for scan loops; unlike `xy()`, this does not wrap or normalize coordinates.
235
253
  isInside(p) {
236
254
  return 0 <= p.x && p.x < this.width && 0 <= p.y && p.y < this.height;
237
255
  }
@@ -248,7 +266,8 @@ export class Bitmap {
248
266
  throw new Error(`Bitmap: invalid x=${c.x}`);
249
267
  if (!Number.isSafeInteger(c.y))
250
268
  throw new Error(`Bitmap: invalid y=${c.y}`);
251
- // Do modulo, so we can use negative positions
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.
252
271
  c.x = mod(c.x, this.width);
253
272
  c.y = mod(c.y, this.height);
254
273
  return c;
@@ -263,6 +282,10 @@ export class Bitmap {
263
282
  return { word: this.wordIndex(x, y), bit: x & 31 };
264
283
  }
265
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.
266
289
  const wi = this.wordIndex(x, y);
267
290
  const m = bitMask(x);
268
291
  return (this.defined[wi] & m) !== 0;
@@ -275,11 +298,15 @@ export class Bitmap {
275
298
  maskWord(wi, mask, v) {
276
299
  const { defined, value } = this;
277
300
  defined[wi] |= mask;
301
+ // `-v` expands the boolean to either all-zero or all-one bits before masking it into the selected lanes.
278
302
  value[wi] = (value[wi] & ~mask) | (-v & mask);
279
303
  }
280
304
  set(x, y, v) {
305
+ // `undefined` means "leave the current cell unchanged", not "clear it back to undefined".
281
306
  if (v === undefined)
282
307
  return;
308
+ // Like `get()` / `isDefined()`, this is a raw in-bounds bitset accessor;
309
+ // check `isInside()` before passing untrusted coordinates.
283
310
  this.maskWord(this.wordIndex(x, y), bitMask(x), v);
284
311
  }
285
312
  // word-span fill for constant values (fast path)
@@ -301,6 +328,7 @@ export class Bitmap {
301
328
  continue;
302
329
  }
303
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.
304
332
  for (let i = startWord + 1; i < endWord; i++) {
305
333
  defined[rowBase + i] = 0xffffffff;
306
334
  value[rowBase + i] = v ? 0xffffffff : 0;
@@ -315,6 +343,7 @@ export class Bitmap {
315
343
  const bitX = x + xPos;
316
344
  const { bit, word } = this.bitIndex(bitX, Py);
317
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.
318
347
  cb(word, bitX, xPos, yPos, bitsPerWord);
319
348
  xPos += bitsPerWord;
320
349
  }
@@ -334,7 +363,11 @@ export class Bitmap {
334
363
  let valWord = value[wi];
335
364
  for (let b = 0; b < n; b++) {
336
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()`.
337
369
  const res = fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
370
+ // Returning undefined from the callback keeps the existing cell unchanged.
338
371
  if (res === undefined)
339
372
  continue;
340
373
  defWord |= mask;
@@ -354,6 +387,8 @@ export class Bitmap {
354
387
  const valWord = value[wi];
355
388
  for (let b = 0; b < n; b++) {
356
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()`.
357
392
  fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
358
393
  }
359
394
  });
@@ -368,6 +403,10 @@ export class Bitmap {
368
403
  }
369
404
  // add border
370
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}`);
371
410
  const height = this.height + 2 * border;
372
411
  const width = this.width + 2 * border;
373
412
  const out = new Bitmap({ height, width });
@@ -384,6 +423,10 @@ export class Bitmap {
384
423
  return this;
385
424
  const { value, defined } = this;
386
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.
387
430
  for (let yPos = 0; yPos < height; yPos++) {
388
431
  const srcRow = yPos * srcStride;
389
432
  for (let xPos = 0; xPos < width;) {
@@ -393,6 +436,7 @@ export class Bitmap {
393
436
  const len = Math.min(32 - dstBit, width - xPos);
394
437
  const w0 = srcValue[srcWord];
395
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.
396
440
  const sVal = srcBit ? ((w0 >>> srcBit) | (w1 << (32 - srcBit))) >>> 0 : w0;
397
441
  const dstMask = rangeMask(dstBit, len);
398
442
  const valBits = ((sVal & rangeMask(0, len)) << dstBit) >>> 0;
@@ -409,6 +453,7 @@ export class Bitmap {
409
453
  const { height, width } = Bitmap.size(size, this.size({ x, y }));
410
454
  const rect = new Bitmap({ height, width });
411
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.
412
457
  if (this.isDefined(x + p.x, y + p.y)) {
413
458
  rect.set(p.x, p.y, cur);
414
459
  }
@@ -453,6 +498,9 @@ export class Bitmap {
453
498
  negate() {
454
499
  const n = this.defined.length;
455
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.
456
504
  this.value[i] = ~this.value[i];
457
505
  this.defined[i] = 0xffffffff;
458
506
  }
@@ -463,6 +511,11 @@ export class Bitmap {
463
511
  if (!Number.isSafeInteger(factor) || factor > 1024)
464
512
  throw new Error(`invalid scale factor: ${factor}`);
465
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.
466
519
  const res = new Bitmap({ height: factor * height, width: factor * width });
467
520
  return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.get((x / factor) | 0, (y / factor) | 0));
468
521
  }
@@ -488,10 +541,14 @@ export class Bitmap {
488
541
  }
489
542
  }
490
543
  countPatternInRow(y, patternLen, ...patterns) {
491
- if (patternLen <= 0 || patternLen >= 32)
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)
492
547
  throw new Error('wrong patternLen');
493
548
  const mask = (1 << patternLen) - 1;
494
- const { width, value, words } = this;
549
+ const { height, width, value, words } = this;
550
+ if (!Number.isSafeInteger(y) || y < 0 || y >= height)
551
+ return 0;
495
552
  let count = 0;
496
553
  const rowBase = this.wordIndex(0, y);
497
554
  for (let i = 0, window = 0; i < words; i++) {
@@ -512,9 +569,14 @@ export class Bitmap {
512
569
  return count;
513
570
  }
514
571
  getRuns(y, fn) {
515
- const { width, value, words } = this;
572
+ const { height, width, value, words } = this;
516
573
  if (width === 0)
517
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;
518
580
  let runLen = 0;
519
581
  let runValue;
520
582
  const rowBase = this.wordIndex(0, y);
@@ -551,11 +613,13 @@ export class Bitmap {
551
613
  return count;
552
614
  }
553
615
  countBoxes2x2(y) {
554
- const { width, words } = this;
555
- if (width < 2 || (y | 0) < 0 || y + 1 >= this.height)
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)
556
620
  return 0;
557
- const base0 = this.wordIndex(0, y) | 0;
558
- const base1 = this.wordIndex(0, y + 1) | 0;
621
+ const base0 = this.wordIndex(0, y);
622
+ const base1 = this.wordIndex(0, y + 1);
559
623
  // valid "left-edge" positions x in [0 .. W-2]
560
624
  const tailBits = width & 31;
561
625
  const validLast = tailBits === 0 ? 0x7fffffff : rangeMask(0, (width - 1) & 31);
@@ -594,6 +658,9 @@ export class Bitmap {
594
658
  const out = Array.from({ length: this.height }, () => new Array(this.width));
595
659
  for (let y = 0; y < this.height; y++) {
596
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.
597
664
  for (let x = 0; x < this.width; x++)
598
665
  row[x] = this.get(x, y);
599
666
  }
@@ -639,6 +706,8 @@ export class Bitmap {
639
706
  }
640
707
  toSVG(optimize = true) {
641
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.
642
711
  // Construct optimized SVG path data.
643
712
  let pathData = '';
644
713
  let prevPoint;
@@ -682,7 +751,9 @@ export class Bitmap {
682
751
  const u16le = (i) => [i & 0xff, (i >>> 8) & 0xff];
683
752
  const dims = [...u16le(this.width), ...u16le(this.height)];
684
753
  const data = [];
754
+ // Palette index 0 is white/light and index 1 is black/dark; rectRead maps undefined cells to light.
685
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.
686
757
  const N = 126; // Block size
687
758
  // prettier-ignore
688
759
  const bytes = [
@@ -717,11 +788,16 @@ export class Bitmap {
717
788
  }
718
789
  // End of utils
719
790
  // Runtime type-checking
720
- /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30% */
721
- export const ECMode = ['low', 'medium', 'quartile', 'high'];
722
- /** QR Code encoding */
723
- export const Encoding = ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'];
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']);
724
799
  // Various constants & tables
800
+ // ISO/IEC 18004:2024 Table 1: QR symbol codeword capacity by version (data plus error correction).
725
801
  // prettier-ignore
726
802
  const BYTES = [
727
803
  // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
@@ -729,6 +805,7 @@ const BYTES = [
729
805
  // 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
730
806
  1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706,
731
807
  ];
808
+ // ISO/IEC 18004:2024 Table 9: error correction codewords per block by version and level.
732
809
  // prettier-ignore
733
810
  const WORDS_PER_BLOCK = {
734
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
@@ -737,6 +814,7 @@ const WORDS_PER_BLOCK = {
737
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],
738
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],
739
816
  };
817
+ // ISO/IEC 18004:2024 Table 9: error correction block count by version and level.
740
818
  // prettier-ignore
741
819
  const ECC_BLOCKS = {
742
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
@@ -745,12 +823,15 @@ const ECC_BLOCKS = {
745
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],
746
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],
747
825
  };
748
- const info = {
749
- size: {
750
- encode: (ver) => 21 + 4 * (ver - 1), // ver1 = 21, ver40=177 blocks
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
751
830
  decode: (size) => (size - 17) / 4,
752
- },
831
+ }),
832
+ // ISO/IEC 18004:2024 Table 3: map version ranges 1-9, 10-26, and 27-40 to count-width indexes.
753
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.
754
835
  // Based on https://codereview.stackexchange.com/questions/74925/algorithm-to-generate-this-alignment-pattern-locations-table-for-qr-codes
755
836
  alignmentPatterns(ver) {
756
837
  if (ver === 1)
@@ -770,13 +851,16 @@ const info = {
770
851
  res.push(last);
771
852
  return res;
772
853
  },
773
- ECCode: {
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({
774
856
  low: 0b01,
775
857
  medium: 0b00,
776
858
  quartile: 0b11,
777
859
  high: 0b10,
778
- },
860
+ }),
861
+ // ISO/IEC 18004:2024 §7.9.1 final paragraph: XOR the 15-bit format information with mask pattern 101010000010010.
779
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.
780
864
  formatBits(ecc, maskIdx) {
781
865
  const data = (info.ECCode[ecc] << 3) | maskIdx;
782
866
  let d = data;
@@ -784,16 +868,21 @@ const info = {
784
868
  d = (d << 1) ^ ((d >> 9) * 0b10100110111);
785
869
  return ((data << 10) | d) ^ info.formatMask;
786
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.
787
872
  versionBits(ver) {
788
873
  let d = ver;
789
874
  for (let i = 0; i < 12; i++)
790
875
  d = (d << 1) ^ ((d >> 11) * 0b1111100100101);
791
876
  return (ver << 12) | d;
792
877
  },
793
- alphabet: {
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.
794
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.
795
883
  alphanumerc: alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'),
796
- }, // as Record<EncodingType, ReturnType<typeof alphabet>>,
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.
797
886
  lengthBits(ver, type) {
798
887
  const table = {
799
888
  numeric: [10, 12, 14],
@@ -804,13 +893,15 @@ const info = {
804
893
  };
805
894
  return table[type][info.sizeType(ver)];
806
895
  },
807
- modeBits: {
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({
808
898
  numeric: '0001',
809
899
  alphanumeric: '0010',
810
900
  byte: '0100',
811
901
  kanji: '1000',
812
902
  eci: '0111',
813
- },
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.
814
905
  capacity(ver, ecc) {
815
906
  const bytes = BYTES[ver - 1];
816
907
  const words = WORDS_PER_BLOCK[ecc][ver - 1];
@@ -826,8 +917,9 @@ const info = {
826
917
  total: (words + blockLen) * numBlocks + numBlocks - shortBlocks,
827
918
  };
828
919
  },
829
- };
830
- const PATTERNS = [
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([
831
923
  (x, y) => (x + y) % 2 == 0,
832
924
  (_x, y) => y % 2 == 0,
833
925
  (x, _y) => x % 3 == 0,
@@ -836,8 +928,9 @@ const PATTERNS = [
836
928
  (x, y) => ((x * y) % 2) + ((x * y) % 3) == 0,
837
929
  (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0,
838
930
  (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0,
839
- ];
931
+ ]);
840
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.
841
934
  const GF = {
842
935
  tables: ((p_poly) => {
843
936
  const exp = fillArr(256, 0);
@@ -849,26 +942,43 @@ const GF = {
849
942
  if (x & 0x100)
850
943
  x ^= p_poly;
851
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.
852
947
  return { exp, log };
853
948
  })(0x11d),
949
+ // Raw α^i lookup from the precomputed field table; callers are expected
950
+ // to reduce / validate exponents before indexing it.
854
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.
855
954
  log(x) {
856
955
  if (x === 0)
857
956
  throw new Error(`GF.log: invalid arg=${x}`);
858
957
  return GF.tables.log[x] % 255;
859
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.
860
961
  mul(x, y) {
861
962
  if (x === 0 || y === 0)
862
963
  return 0;
863
964
  return GF.tables.exp[(GF.tables.log[x] + GF.tables.log[y]) % 255];
864
965
  },
966
+ // In characteristic 2 fields, addition and subtraction are the same
967
+ // bitwise XOR operation used by the QR Reed-Solomon arithmetic.
865
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.
866
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).
867
974
  inv(x) {
868
975
  if (x === 0)
869
976
  throw new Error(`GF.inverse: invalid arg=${x}`);
870
977
  return GF.tables.exp[255 - GF.tables.log[x]];
871
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.
872
982
  polynomial(poly) {
873
983
  if (poly.length == 0)
874
984
  throw new Error('GF.polymomial: invalid length');
@@ -880,6 +990,8 @@ const GF = {
880
990
  ;
881
991
  return poly.slice(i);
882
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]`.
883
995
  monomial(degree, coefficient) {
884
996
  if (degree < 0)
885
997
  throw new Error(`GF.monomial: invalid degree=${degree}`);
@@ -889,8 +1001,14 @@ const GF = {
889
1001
  coefficients[0] = coefficient;
890
1002
  return GF.polynomial(coefficients);
891
1003
  },
1004
+ // Canonical polynomials keep the highest-order coefficient first and use
1005
+ // `[0]` for zero, so degree is just `length - 1`.
892
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`.
893
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.
894
1012
  mulPoly(a, b) {
895
1013
  if (a[0] === 0 || b[0] === 0)
896
1014
  return [0];
@@ -902,6 +1020,8 @@ const GF = {
902
1020
  }
903
1021
  return GF.polynomial(res);
904
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.
905
1025
  mulPolyScalar(a, scalar) {
906
1026
  if (scalar == 0)
907
1027
  return [0];
@@ -912,6 +1032,8 @@ const GF = {
912
1032
  res[i] = GF.mul(a[i], scalar);
913
1033
  return GF.polynomial(res);
914
1034
  },
1035
+ // Multiply a polynomial by c*x^degree in descending-power coefficient form.
1036
+ // This scales existing coefficients, then appends trailing zero coefficients.
915
1037
  mulPolyMonomial(a, degree, coefficient) {
916
1038
  if (degree < 0)
917
1039
  throw new Error('GF.mulPolyMonomial: invalid degree');
@@ -922,6 +1044,8 @@ const GF = {
922
1044
  res[i] = GF.mul(a[i], coefficient);
923
1045
  return GF.polynomial(res);
924
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.
925
1049
  addPoly(a, b) {
926
1050
  if (a[0] === 0)
927
1051
  return b;
@@ -940,6 +1064,8 @@ const GF = {
940
1064
  sumDiff[i] = GF.add(smaller[i - lengthDiff], larger[i]);
941
1065
  return GF.polynomial(sumDiff);
942
1066
  },
1067
+ // Synthetic division for monic divisors in descending-power coefficient form.
1068
+ // Callers are expected to append `divisor.length - 1` zero coefficients first.
943
1069
  remainderPoly(data, divisor) {
944
1070
  const out = Array.from(data);
945
1071
  for (let i = 0; i < data.length - divisor.length + 1; i++) {
@@ -953,12 +1079,16 @@ const GF = {
953
1079
  }
954
1080
  return out.slice(data.length - divisor.length + 1, out.length);
955
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.
956
1084
  divisorPoly(degree) {
957
1085
  let g = [1];
958
1086
  for (let i = 0; i < degree; i++)
959
1087
  g = GF.mulPoly(g, [1, GF.pow(2, i)]);
960
1088
  return g;
961
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.
962
1092
  evalPoly(poly, a) {
963
1093
  if (a == 0)
964
1094
  return GF.coefficient(poly, 0); // Just return the x^0 coefficient
@@ -968,6 +1098,8 @@ const GF = {
968
1098
  return res;
969
1099
  },
970
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.
971
1103
  euclidian(a, b, R) {
972
1104
  // Force degree(a) >= degree(b)
973
1105
  if (GF.degree(a) < GF.degree(b))
@@ -1005,6 +1137,8 @@ const GF = {
1005
1137
  return [GF.mulPolyScalar(t, inverse), GF.mulPolyScalar(r, inverse)];
1006
1138
  },
1007
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.
1008
1142
  function RS(eccWords) {
1009
1143
  return {
1010
1144
  encode(from) {
@@ -1057,11 +1191,15 @@ function RS(eccWords) {
1057
1191
  };
1058
1192
  }
1059
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.
1060
1196
  function interleave(ver, ecc) {
1061
1197
  const { words, shortBlocks, numBlocks, blockLen, total } = info.capacity(ver, ecc);
1062
1198
  const rs = RS(words);
1063
1199
  return {
1064
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.
1065
1203
  // Add error correction to bytes
1066
1204
  const blocks = [];
1067
1205
  const eccBlocks = [];
@@ -1114,6 +1252,8 @@ function interleave(ver, ecc) {
1114
1252
  }
1115
1253
  // Draw
1116
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`.
1117
1257
  function drawTemplate(ver, ecc, maskIdx, test = false) {
1118
1258
  const size = info.size.encode(ver);
1119
1259
  let b = new Bitmap(size + 2);
@@ -1175,9 +1315,11 @@ function drawTemplate(ver, ecc, maskIdx, test = false) {
1175
1315
  }
1176
1316
  return b;
1177
1317
  }
1178
- // zigzag: bottom->top && top->bottom
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.
1179
1320
  function zigzag(tpl, maskIdx, fn) {
1180
- const size = tpl.height;
1321
+ const bm = tpl;
1322
+ const size = bm.height;
1181
1323
  const pattern = PATTERNS[maskIdx];
1182
1324
  // zig-zag pattern
1183
1325
  let dir = -1;
@@ -1189,7 +1331,7 @@ function zigzag(tpl, maskIdx, fn) {
1189
1331
  for (;; y += dir) {
1190
1332
  for (let j = 0; j < 2; j += 1) {
1191
1333
  const x = xOffset - j;
1192
- if (tpl.isDefined(x, y))
1334
+ if (bm.isDefined(x, y))
1193
1335
  continue; // skip already written elements
1194
1336
  fn(x, y, pattern(x, y));
1195
1337
  }
@@ -1201,6 +1343,8 @@ function zigzag(tpl, maskIdx, fn) {
1201
1343
  }
1202
1344
  // NOTE: byte encoding is just representation, QR works with strings only. Most decoders will fail on raw byte array,
1203
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.
1204
1348
  function detectType(str) {
1205
1349
  let type = 'numeric';
1206
1350
  for (let x of str) {
@@ -1213,13 +1357,27 @@ function detectType(str) {
1213
1357
  return type;
1214
1358
  }
1215
1359
  /**
1216
- * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
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
+ * ```
1217
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.
1218
1374
  export function utf8ToBytes(str) {
1219
1375
  if (typeof str !== 'string')
1220
1376
  throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
1221
1377
  return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
1222
1378
  }
1379
+ // Build one QR mode/count/data segment, then append the terminator, zero padding,
1380
+ // and alternating pad codewords before RS interleaving.
1223
1381
  function encode(ver, ecc, data, type, encoder = utf8ToBytes) {
1224
1382
  let encoded = '';
1225
1383
  let dataLen = data.length;
@@ -1244,6 +1402,7 @@ function encode(ver, ecc, data, type, encoder = utf8ToBytes) {
1244
1402
  encoded += bin(t[n - 1], 6); // pad if odd number of chars
1245
1403
  }
1246
1404
  else if (type === 'byte') {
1405
+ // The default encoder is intentionally UTF-8-without-ECI; see utf8ToBytes().
1247
1406
  const utf8 = encoder(data);
1248
1407
  dataLen = utf8.length;
1249
1408
  encoded = Array.from(utf8)
@@ -1272,6 +1431,8 @@ function encode(ver, ecc, data, type, encoder = utf8ToBytes) {
1272
1431
  return interleave(ver, ecc).encode(bytes);
1273
1432
  }
1274
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.
1275
1436
  function drawQR(ver, ecc, data, maskIdx, test = false) {
1276
1437
  const b = drawTemplate(ver, ecc, maskIdx, test);
1277
1438
  let i = 0;
@@ -1288,6 +1449,8 @@ function drawQR(ver, ecc, data, maskIdx, test = false) {
1288
1449
  throw new Error('QR: bytes left after draw');
1289
1450
  return b;
1290
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.
1291
1454
  const mkPattern = (pattern) => {
1292
1455
  const s = pattern.map((i) => (i ? '1' : '0')).join('');
1293
1456
  return { len: s.length, n: Number(`0b${s}`) };
@@ -1295,15 +1458,16 @@ const mkPattern = (pattern) => {
1295
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
1296
1459
  const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
1297
1460
  const lightPattern = [false, false, false, false]; // light area 4 modules wide
1298
- const P1 = mkPattern([...finderPattern, ...lightPattern]);
1299
- const P2 = mkPattern([...lightPattern, ...finderPattern]);
1461
+ const P1 = /* @__PURE__ */ (() => mkPattern([...finderPattern, ...lightPattern]))();
1462
+ const P2 = /* @__PURE__ */ (() => mkPattern([...lightPattern, ...finderPattern]))();
1300
1463
  function penalty(bm) {
1301
- const { width, height } = bm;
1302
- const transposed = bm.transpose();
1464
+ const b = bm;
1465
+ const { width, height } = b;
1466
+ const transposed = b.transpose();
1303
1467
  // Adjacent modules in row/column in same | No. of modules = (5 + i) color
1304
1468
  let adjacent = 0;
1305
1469
  for (let y = 0; y < height; y++) {
1306
- bm.getRuns(y, (len) => {
1470
+ b.getRuns(y, (len) => {
1307
1471
  if (len >= 5)
1308
1472
  adjacent += 3 + (len - 5);
1309
1473
  });
@@ -1317,29 +1481,28 @@ function penalty(bm) {
1317
1481
  // Block of modules in same color (Block size = 2x2)
1318
1482
  let box = 0;
1319
1483
  for (let y = 0; y < height - 1; y++)
1320
- box += 3 * bm.countBoxes2x2(y);
1484
+ box += 3 * b.countBoxes2x2(y);
1321
1485
  let finder = 0;
1322
1486
  for (let y = 0; y < height; y++)
1323
- finder += 40 * bm.countPatternInRow(y, P1.len, P1.n, P2.n);
1487
+ finder += 40 * b.countPatternInRow(y, P1.len, P1.n, P2.n);
1324
1488
  for (let y = 0; y < width; y++)
1325
1489
  finder += 40 * transposed.countPatternInRow(y, P1.len, P1.n, P2.n);
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);
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;
1337
1497
  return adjacent + box + finder + dark;
1338
1498
  }
1339
1499
  // Selects best mask according to penalty, if no mask is provided
1340
1500
  function drawQRBest(ver, ecc, data, maskIdx) {
1341
1501
  if (maskIdx === undefined) {
1342
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.
1343
1506
  for (let mask = 0; mask < PATTERNS.length; mask++)
1344
1507
  bestMask.add(penalty(drawQR(ver, ecc, data, mask, true)), mask);
1345
1508
  maskIdx = bestMask.get();
@@ -1363,24 +1526,25 @@ function validateMask(mask) {
1363
1526
  throw new Error(`Invalid mask=${mask}. Expected number [0..7]`);
1364
1527
  }
1365
1528
  export function encodeQR(text, output = 'raw', opts = {}) {
1366
- const ecc = opts.ecc !== undefined ? opts.ecc : 'medium';
1529
+ const _opts = opts;
1530
+ const ecc = _opts.ecc !== undefined ? _opts.ecc : 'medium';
1367
1531
  validateECC(ecc);
1368
- const encoding = opts.encoding !== undefined ? opts.encoding : detectType(text);
1532
+ const encoding = _opts.encoding !== undefined ? _opts.encoding : detectType(text);
1369
1533
  validateEncoding(encoding);
1370
- if (opts.mask !== undefined)
1371
- validateMask(opts.mask);
1372
- let ver = opts.version;
1534
+ if (_opts.mask !== undefined)
1535
+ validateMask(_opts.mask);
1536
+ let ver = _opts.version;
1373
1537
  let data, err = new Error('Unknown error');
1374
1538
  if (ver !== undefined) {
1375
1539
  validateVersion(ver);
1376
- data = encode(ver, ecc, text, encoding, opts.textEncoder);
1540
+ data = encode(ver, ecc, text, encoding, _opts.textEncoder);
1377
1541
  }
1378
1542
  else {
1379
1543
  // If no version is provided, try to find smallest one which fits
1380
1544
  // Currently just scans all version, can be significantly speedup if needed
1381
1545
  for (let i = 1; i <= 40; i++) {
1382
1546
  try {
1383
- data = encode(i, ecc, text, encoding, opts.textEncoder);
1547
+ data = encode(i, ecc, text, encoding, _opts.textEncoder);
1384
1548
  ver = i;
1385
1549
  break;
1386
1550
  }
@@ -1391,20 +1555,24 @@ export function encodeQR(text, output = 'raw', opts = {}) {
1391
1555
  }
1392
1556
  if (!ver || !data)
1393
1557
  throw err;
1394
- let res = drawQRBest(ver, ecc, data, opts.mask);
1558
+ let res = drawQRBest(ver, ecc, data, _opts.mask);
1395
1559
  res.assertDrawn();
1396
- const border = opts.border === undefined ? 2 : opts.border;
1397
- if (!Number.isSafeInteger(border))
1398
- throw new Error(`invalid border type=${typeof border}`);
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}`);
1399
1567
  res = res.border(border, false); // Add border
1400
- if (opts.scale !== undefined)
1401
- res = res.scale(opts.scale); // Scale image
1568
+ if (_opts.scale !== undefined)
1569
+ res = res.scale(_opts.scale); // Scale image
1402
1570
  if (output === 'raw')
1403
1571
  return res.toRaw();
1404
1572
  else if (output === 'ascii')
1405
1573
  return res.toASCII();
1406
1574
  else if (output === 'svg')
1407
- return res.toSVG(opts.optimize);
1575
+ return res.toSVG(_opts.optimize);
1408
1576
  else if (output === 'gif')
1409
1577
  return res.toGIF();
1410
1578
  else if (output === 'term')
@@ -1412,8 +1580,32 @@ export function encodeQR(text, output = 'raw', opts = {}) {
1412
1580
  else
1413
1581
  throw new Error(`Unknown output: ${output}`);
1414
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
+ */
1415
1597
  export default encodeQR;
1416
- export const utils = {
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({
1417
1609
  best,
1418
1610
  bin,
1419
1611
  popcnt,
@@ -1423,9 +1615,10 @@ export const utils = {
1423
1615
  interleave,
1424
1616
  validateVersion,
1425
1617
  zigzag,
1426
- };
1618
+ });
1427
1619
  // Unsafe API utils, exported only for tests
1428
- export const _tests = {
1620
+ // Exposes the shared internal helpers/tables through a frozen container.
1621
+ export const _tests = /* @__PURE__ */ Object.freeze({
1429
1622
  Bitmap,
1430
1623
  info,
1431
1624
  detectType,
@@ -1433,7 +1626,7 @@ export const _tests = {
1433
1626
  drawQR,
1434
1627
  penalty,
1435
1628
  PATTERNS,
1436
- };
1629
+ });
1437
1630
  // Type tests
1438
1631
  // const o1 = qr('test', 'ascii');
1439
1632
  // const o2 = qr('test', 'raw');