qr 0.2.4 → 0.4.2

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/esm/index.js ADDED
@@ -0,0 +1,1123 @@
1
+ /*!
2
+ Copyright (c) 2023 Paul Miller (paulmillr.com)
3
+ The library paulmillr-qr is dual-licensed under the Apache 2.0 OR MIT license.
4
+ You can select a license of your choice.
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+ /**
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 '@paulmillr/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 '@paulmillr/qr/decode';
31
+ ```
32
+ */
33
+ // We do not use newline escape code directly in strings because it's not parser-friendly
34
+ const chCodes = { newline: 10, reset: 27 };
35
+ function assertNumber(n) {
36
+ if (!Number.isSafeInteger(n))
37
+ throw new Error(`integer expected: ${n}`);
38
+ }
39
+ function validateVersion(ver) {
40
+ if (!Number.isSafeInteger(ver) || ver < 1 || ver > 40)
41
+ throw new Error(`Invalid version=${ver}. Expected number [1..40]`);
42
+ }
43
+ function bin(dec, pad) {
44
+ return dec.toString(2).padStart(pad, '0');
45
+ }
46
+ function mod(a, b) {
47
+ const result = a % b;
48
+ return result >= 0 ? result : b + result;
49
+ }
50
+ function fillArr(length, val) {
51
+ return new Array(length).fill(val);
52
+ }
53
+ /**
54
+ * Interleaves byte blocks.
55
+ * @param blocks [[1, 2, 3], [4, 5, 6]]
56
+ * @returns [1, 4, 2, 5, 3, 6]
57
+ */
58
+ function interleaveBytes(...blocks) {
59
+ let len = 0;
60
+ for (const b of blocks)
61
+ len = Math.max(len, b.length);
62
+ const res = [];
63
+ for (let i = 0; i < len; i++) {
64
+ for (const b of blocks) {
65
+ if (i >= b.length)
66
+ continue; // outside of block, skip
67
+ res.push(b[i]);
68
+ }
69
+ }
70
+ return new Uint8Array(res);
71
+ }
72
+ function includesAt(lst, pattern, index) {
73
+ if (index < 0 || index + pattern.length > lst.length)
74
+ return false;
75
+ for (let i = 0; i < pattern.length; i++)
76
+ if (pattern[i] !== lst[index + i])
77
+ return false;
78
+ return true;
79
+ }
80
+ // Optimize for minimal score/penalty
81
+ function best() {
82
+ let best;
83
+ let bestScore = Infinity;
84
+ return {
85
+ add(score, value) {
86
+ if (score >= bestScore)
87
+ return;
88
+ best = value;
89
+ bestScore = score;
90
+ },
91
+ get: () => best,
92
+ score: () => bestScore,
93
+ };
94
+ }
95
+ // Based on https://github.com/paulmillr/scure-base/blob/main/index.ts
96
+ function alphabet(alphabet) {
97
+ return {
98
+ has: (char) => alphabet.includes(char),
99
+ decode: (input) => {
100
+ if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
101
+ throw new Error('alphabet.decode input should be array of strings');
102
+ return input.map((letter) => {
103
+ if (typeof letter !== 'string')
104
+ throw new Error(`alphabet.decode: not string element=${letter}`);
105
+ const index = alphabet.indexOf(letter);
106
+ if (index === -1)
107
+ throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
108
+ return index;
109
+ });
110
+ },
111
+ encode: (digits) => {
112
+ if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
113
+ throw new Error('alphabet.encode input should be an array of numbers');
114
+ return digits.map((i) => {
115
+ assertNumber(i);
116
+ if (i < 0 || i >= alphabet.length)
117
+ throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
118
+ return alphabet[i];
119
+ });
120
+ },
121
+ };
122
+ }
123
+ export class Bitmap {
124
+ static size(size, limit) {
125
+ if (typeof size === 'number')
126
+ size = { height: size, width: size };
127
+ if (!Number.isSafeInteger(size.height) && size.height !== Infinity)
128
+ throw new Error(`Bitmap: invalid height=${size.height} (${typeof size.height})`);
129
+ if (!Number.isSafeInteger(size.width) && size.width !== Infinity)
130
+ throw new Error(`Bitmap: invalid width=${size.width} (${typeof size.width})`);
131
+ if (limit !== undefined) {
132
+ // Clamp length, so it won't overflow, also allows to use Infinity, so we draw until end
133
+ size = {
134
+ width: Math.min(size.width, limit.width),
135
+ height: Math.min(size.height, limit.height),
136
+ };
137
+ }
138
+ return size;
139
+ }
140
+ static fromString(s) {
141
+ // Remove linebreaks on start and end, so we draw in `` section
142
+ s = s.replace(/^\n+/g, '').replace(/\n+$/g, '');
143
+ const lines = s.split(String.fromCharCode(chCodes.newline));
144
+ const height = lines.length;
145
+ const data = new Array(height);
146
+ let width;
147
+ for (const line of lines) {
148
+ const row = line.split('').map((i) => {
149
+ if (i === 'X')
150
+ return true;
151
+ if (i === ' ')
152
+ return false;
153
+ if (i === '?')
154
+ return undefined;
155
+ throw new Error(`Bitmap.fromString: unknown symbol=${i}`);
156
+ });
157
+ if (width && row.length !== width)
158
+ throw new Error(`Bitmap.fromString different row sizes: width=${width} cur=${row.length}`);
159
+ width = row.length;
160
+ data.push(row);
161
+ }
162
+ if (!width)
163
+ width = 0;
164
+ return new Bitmap({ height, width }, data);
165
+ }
166
+ constructor(size, data) {
167
+ const { height, width } = Bitmap.size(size);
168
+ this.data = data || Array.from({ length: height }, () => fillArr(width, undefined));
169
+ this.height = height;
170
+ this.width = width;
171
+ }
172
+ point(p) {
173
+ return this.data[p.y][p.x];
174
+ }
175
+ isInside(p) {
176
+ return 0 <= p.x && p.x < this.width && 0 <= p.y && p.y < this.height;
177
+ }
178
+ size(offset) {
179
+ if (!offset)
180
+ return { height: this.height, width: this.width };
181
+ const { x, y } = this.xy(offset);
182
+ return { height: this.height - y, width: this.width - x };
183
+ }
184
+ xy(c) {
185
+ if (typeof c === 'number')
186
+ c = { x: c, y: c };
187
+ if (!Number.isSafeInteger(c.x))
188
+ throw new Error(`Bitmap: invalid x=${c.x}`);
189
+ if (!Number.isSafeInteger(c.y))
190
+ throw new Error(`Bitmap: invalid y=${c.y}`);
191
+ // Do modulo, so we can use negative positions
192
+ c.x = mod(c.x, this.width);
193
+ c.y = mod(c.y, this.height);
194
+ return c;
195
+ }
196
+ // Basically every operation can be represented as rect
197
+ rect(c, size, value) {
198
+ const { x, y } = this.xy(c);
199
+ const { height, width } = Bitmap.size(size, this.size({ x, y }));
200
+ for (let yPos = 0; yPos < height; yPos++) {
201
+ for (let xPos = 0; xPos < width; xPos++) {
202
+ // NOTE: we use give function relative coordinates inside box
203
+ this.data[y + yPos][x + xPos] =
204
+ typeof value === 'function'
205
+ ? value({ x: xPos, y: yPos }, this.data[y + yPos][x + xPos])
206
+ : value;
207
+ }
208
+ }
209
+ return this;
210
+ }
211
+ // returns rectangular part of bitmap
212
+ rectRead(c, size, fn) {
213
+ return this.rect(c, size, (c, cur) => {
214
+ fn(c, cur);
215
+ return cur;
216
+ });
217
+ }
218
+ // Horizontal & vertical lines
219
+ hLine(c, len, value) {
220
+ return this.rect(c, { width: len, height: 1 }, value);
221
+ }
222
+ vLine(c, len, value) {
223
+ return this.rect(c, { width: 1, height: len }, value);
224
+ }
225
+ // add border
226
+ border(border = 2, value) {
227
+ const height = this.height + 2 * border;
228
+ const width = this.width + 2 * border;
229
+ const v = fillArr(border, value);
230
+ const h = Array.from({ length: border }, () => fillArr(width, value));
231
+ return new Bitmap({ height, width }, [...h, ...this.data.map((i) => [...v, ...i, ...v]), ...h]);
232
+ }
233
+ // Embed another bitmap on coordinates
234
+ embed(c, bm) {
235
+ return this.rect(c, bm.size(), ({ x, y }) => bm.data[y][x]);
236
+ }
237
+ // returns rectangular part of bitmap
238
+ rectSlice(c, size = this.size()) {
239
+ const rect = new Bitmap(Bitmap.size(size, this.size(this.xy(c))));
240
+ this.rect(c, size, ({ x, y }, cur) => (rect.data[y][x] = cur));
241
+ return rect;
242
+ }
243
+ // Change shape, replace rows with columns (data[y][x] -> data[x][y])
244
+ inverse() {
245
+ const { height, width } = this;
246
+ const res = new Bitmap({ height: width, width: height });
247
+ return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.data[x][y]);
248
+ }
249
+ // Each pixel size is multiplied by factor
250
+ scale(factor) {
251
+ if (!Number.isSafeInteger(factor) || factor > 1024)
252
+ throw new Error(`invalid scale factor: ${factor}`);
253
+ const { height, width } = this;
254
+ const res = new Bitmap({ height: factor * height, width: factor * width });
255
+ return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.data[Math.floor(y / factor)][Math.floor(x / factor)]);
256
+ }
257
+ clone() {
258
+ const res = new Bitmap(this.size());
259
+ return res.rect({ x: 0, y: 0 }, this.size(), ({ x, y }) => this.data[y][x]);
260
+ }
261
+ // Ensure that there is no undefined values left
262
+ assertDrawn() {
263
+ this.rectRead(0, Infinity, (_, cur) => {
264
+ if (typeof cur !== 'boolean')
265
+ throw new Error(`Invalid color type=${typeof cur}`);
266
+ });
267
+ }
268
+ // Simple string representation for debugging
269
+ toString() {
270
+ return this.data
271
+ .map((i) => i.map((j) => (j === undefined ? '?' : j ? 'X' : ' ')).join(''))
272
+ .join(String.fromCharCode(chCodes.newline));
273
+ }
274
+ toASCII() {
275
+ const { height, width, data } = this;
276
+ let out = '';
277
+ // Terminal character height is x2 of character width, so we process two rows of bitmap
278
+ // to produce one row of ASCII
279
+ for (let y = 0; y < height; y += 2) {
280
+ for (let x = 0; x < width; x++) {
281
+ const first = data[y][x];
282
+ const second = y + 1 >= height ? true : data[y + 1][x]; // if last row outside bitmap, make it black
283
+ if (!first && !second)
284
+ out += '█'; // both rows white (empty)
285
+ else if (!first && second)
286
+ out += '▀'; // top row white
287
+ else if (first && !second)
288
+ out += '▄'; // down row white
289
+ else if (first && second)
290
+ out += ' '; // both rows black
291
+ }
292
+ out += String.fromCharCode(chCodes.newline);
293
+ }
294
+ return out;
295
+ }
296
+ toTerm() {
297
+ const cc = String.fromCharCode(chCodes.reset);
298
+ const reset = cc + '[0m';
299
+ const whiteBG = cc + '[1;47m ' + reset;
300
+ const darkBG = cc + `[40m ` + reset;
301
+ return this.data
302
+ .map((i) => i.map((j) => (j ? darkBG : whiteBG)).join(''))
303
+ .join(String.fromCharCode(chCodes.newline));
304
+ }
305
+ toSVG(optimize = true) {
306
+ let out = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`;
307
+ // Construct optimized SVG path data.
308
+ let pathData = '';
309
+ let prevPoint;
310
+ this.rectRead(0, Infinity, (point, val) => {
311
+ if (!val)
312
+ return;
313
+ const { x, y } = point;
314
+ if (!optimize) {
315
+ out += `<rect x="${x}" y="${y}" width="1" height="1" />`;
316
+ return;
317
+ }
318
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/d#path_commands
319
+ // Determine the shortest way to represent the initial cursor movement.
320
+ // M - Move cursor (without drawing) to absolute coordinate pair.
321
+ let m = `M${x} ${y}`;
322
+ // Only allow using the relative cursor move command if previous points
323
+ // were drawn.
324
+ if (prevPoint) {
325
+ // m - Move cursor (without drawing) to relative coordinate pair.
326
+ const relM = `m${x - prevPoint.x} ${y - prevPoint.y}`;
327
+ if (relM.length <= m.length)
328
+ m = relM;
329
+ }
330
+ // Determine the shortest way to represent the cell's bottom line draw.
331
+ // H - Draw line from cursor position to absolute x coordinate.
332
+ // h - Draw line from cursor position to relative x coordinate.
333
+ const bH = x < 10 ? `H${x}` : 'h-1';
334
+ // v - Draw line from cursor position to relative y coordinate.
335
+ // Z - Close path (draws line from cursor position to M coordinate).
336
+ pathData += `${m}h1v1${bH}Z`;
337
+ prevPoint = point;
338
+ });
339
+ if (optimize)
340
+ out += `<path d="${pathData}"/>`;
341
+ out += `</svg>`;
342
+ return out;
343
+ }
344
+ toGIF() {
345
+ // NOTE: Small, but inefficient implementation.
346
+ // Uses 1 byte per pixel.
347
+ const u16le = (i) => [i & 0xff, (i >>> 8) & 0xff];
348
+ const dims = [...u16le(this.width), ...u16le(this.height)];
349
+ const data = [];
350
+ this.rectRead(0, Infinity, (_, cur) => data.push(+(cur === true)));
351
+ const N = 126; // Block size
352
+ // prettier-ignore
353
+ const bytes = [
354
+ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, ...dims, 0xf6, 0x00, 0x00, 0xff, 0xff, 0xff,
355
+ ...fillArr(3 * 127, 0x00), 0x2c, 0x00, 0x00, 0x00, 0x00, ...dims, 0x00, 0x07
356
+ ];
357
+ const fullChunks = Math.floor(data.length / N);
358
+ // Full blocks
359
+ for (let i = 0; i < fullChunks; i++)
360
+ bytes.push(N + 1, 0x80, ...data.slice(N * i, N * (i + 1)).map((i) => +i));
361
+ // Remaining bytes
362
+ bytes.push((data.length % N) + 1, 0x80, ...data.slice(fullChunks * N).map((i) => +i));
363
+ bytes.push(0x01, 0x81, 0x00, 0x3b);
364
+ return new Uint8Array(bytes);
365
+ }
366
+ toImage(isRGB = false) {
367
+ const { height, width } = this.size();
368
+ const data = new Uint8Array(height * width * (isRGB ? 3 : 4));
369
+ let i = 0;
370
+ for (let y = 0; y < height; y++) {
371
+ for (let x = 0; x < width; x++) {
372
+ const value = !!this.data[y][x] ? 0 : 255;
373
+ data[i++] = value;
374
+ data[i++] = value;
375
+ data[i++] = value;
376
+ if (!isRGB)
377
+ data[i++] = 255; // alpha channel
378
+ }
379
+ }
380
+ return { height, width, data };
381
+ }
382
+ }
383
+ // End of utils
384
+ // Runtime type-checking
385
+ /** Error correction mode. low: 7%, medium: 15%, quartile: 25%, high: 30% */
386
+ export const ECMode = ['low', 'medium', 'quartile', 'high'];
387
+ /** QR Code encoding */
388
+ export const Encoding = ['numeric', 'alphanumeric', 'byte', 'kanji', 'eci'];
389
+ // Various constants & tables
390
+ // prettier-ignore
391
+ const BYTES = [
392
+ // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
393
+ 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085,
394
+ // 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
395
+ 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706,
396
+ ];
397
+ // prettier-ignore
398
+ const WORDS_PER_BLOCK = {
399
+ // 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
400
+ low: [7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
401
+ medium: [10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
402
+ 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],
403
+ 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],
404
+ };
405
+ // prettier-ignore
406
+ const ECC_BLOCKS = {
407
+ // 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
408
+ low: [1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
409
+ medium: [1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
410
+ 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],
411
+ 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],
412
+ };
413
+ const info = {
414
+ size: {
415
+ encode: (ver) => 21 + 4 * (ver - 1), // ver1 = 21, ver40=177 blocks
416
+ decode: (size) => (size - 17) / 4,
417
+ },
418
+ sizeType: (ver) => Math.floor((ver + 7) / 17),
419
+ // Based on https://codereview.stackexchange.com/questions/74925/algorithm-to-generate-this-alignment-pattern-locations-table-for-qr-codes
420
+ alignmentPatterns(ver) {
421
+ if (ver === 1)
422
+ return [];
423
+ const first = 6;
424
+ const last = info.size.encode(ver) - first - 1;
425
+ const distance = last - first;
426
+ const count = Math.ceil(distance / 28);
427
+ let interval = Math.floor(distance / count);
428
+ if (interval % 2)
429
+ interval += 1;
430
+ else if ((distance % count) * 2 >= count)
431
+ interval += 2;
432
+ const res = [first];
433
+ for (let m = 1; m < count; m++)
434
+ res.push(last - (count - m) * interval);
435
+ res.push(last);
436
+ return res;
437
+ },
438
+ ECCode: {
439
+ low: 0b01,
440
+ medium: 0b00,
441
+ quartile: 0b11,
442
+ high: 0b10,
443
+ },
444
+ formatMask: 0b101010000010010,
445
+ formatBits(ecc, maskIdx) {
446
+ const data = (info.ECCode[ecc] << 3) | maskIdx;
447
+ let d = data;
448
+ for (let i = 0; i < 10; i++)
449
+ d = (d << 1) ^ ((d >> 9) * 0b10100110111);
450
+ return ((data << 10) | d) ^ info.formatMask;
451
+ },
452
+ versionBits(ver) {
453
+ let d = ver;
454
+ for (let i = 0; i < 12; i++)
455
+ d = (d << 1) ^ ((d >> 11) * 0b1111100100101);
456
+ return (ver << 12) | d;
457
+ },
458
+ alphabet: {
459
+ numeric: alphabet('0123456789'),
460
+ alphanumerc: alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'),
461
+ }, // as Record<EncodingType, ReturnType<typeof alphabet>>,
462
+ lengthBits(ver, type) {
463
+ const table = {
464
+ numeric: [10, 12, 14],
465
+ alphanumeric: [9, 11, 13],
466
+ byte: [8, 16, 16],
467
+ kanji: [8, 10, 12],
468
+ eci: [0, 0, 0],
469
+ };
470
+ return table[type][info.sizeType(ver)];
471
+ },
472
+ modeBits: {
473
+ numeric: '0001',
474
+ alphanumeric: '0010',
475
+ byte: '0100',
476
+ kanji: '1000',
477
+ eci: '0111',
478
+ },
479
+ capacity(ver, ecc) {
480
+ const bytes = BYTES[ver - 1];
481
+ const words = WORDS_PER_BLOCK[ecc][ver - 1];
482
+ const numBlocks = ECC_BLOCKS[ecc][ver - 1];
483
+ const blockLen = Math.floor(bytes / numBlocks) - words;
484
+ const shortBlocks = numBlocks - (bytes % numBlocks);
485
+ return {
486
+ words,
487
+ numBlocks,
488
+ shortBlocks,
489
+ blockLen,
490
+ capacity: (bytes - words * numBlocks) * 8,
491
+ total: (words + blockLen) * numBlocks + numBlocks - shortBlocks,
492
+ };
493
+ },
494
+ };
495
+ const PATTERNS = [
496
+ (x, y) => (x + y) % 2 == 0,
497
+ (_x, y) => y % 2 == 0,
498
+ (x, _y) => x % 3 == 0,
499
+ (x, y) => (x + y) % 3 == 0,
500
+ (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 == 0,
501
+ (x, y) => ((x * y) % 2) + ((x * y) % 3) == 0,
502
+ (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0,
503
+ (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0,
504
+ ];
505
+ // Galois field && reed-solomon encoding
506
+ const GF = {
507
+ tables: ((p_poly) => {
508
+ const exp = fillArr(256, 0);
509
+ const log = fillArr(256, 0);
510
+ for (let i = 0, x = 1; i < 256; i++) {
511
+ exp[i] = x;
512
+ log[x] = i;
513
+ x <<= 1;
514
+ if (x & 0x100)
515
+ x ^= p_poly;
516
+ }
517
+ return { exp, log };
518
+ })(0x11d),
519
+ exp: (x) => GF.tables.exp[x],
520
+ log(x) {
521
+ if (x === 0)
522
+ throw new Error(`GF.log: invalid arg=${x}`);
523
+ return GF.tables.log[x] % 255;
524
+ },
525
+ mul(x, y) {
526
+ if (x === 0 || y === 0)
527
+ return 0;
528
+ return GF.tables.exp[(GF.tables.log[x] + GF.tables.log[y]) % 255];
529
+ },
530
+ add: (x, y) => x ^ y,
531
+ pow: (x, e) => GF.tables.exp[(GF.tables.log[x] * e) % 255],
532
+ inv(x) {
533
+ if (x === 0)
534
+ throw new Error(`GF.inverse: invalid arg=${x}`);
535
+ return GF.tables.exp[255 - GF.tables.log[x]];
536
+ },
537
+ polynomial(poly) {
538
+ if (poly.length == 0)
539
+ throw new Error('GF.polymomial: invalid length');
540
+ if (poly[0] !== 0)
541
+ return poly;
542
+ // Strip leading zeros
543
+ let i = 0;
544
+ for (; i < poly.length - 1 && poly[i] == 0; i++)
545
+ ;
546
+ return poly.slice(i);
547
+ },
548
+ monomial(degree, coefficient) {
549
+ if (degree < 0)
550
+ throw new Error(`GF.monomial: invalid degree=${degree}`);
551
+ if (coefficient == 0)
552
+ return [0];
553
+ let coefficients = fillArr(degree + 1, 0);
554
+ coefficients[0] = coefficient;
555
+ return GF.polynomial(coefficients);
556
+ },
557
+ degree: (a) => a.length - 1,
558
+ coefficient: (a, degree) => a[GF.degree(a) - degree],
559
+ mulPoly(a, b) {
560
+ if (a[0] === 0 || b[0] === 0)
561
+ return [0];
562
+ const res = fillArr(a.length + b.length - 1, 0);
563
+ for (let i = 0; i < a.length; i++) {
564
+ for (let j = 0; j < b.length; j++) {
565
+ res[i + j] = GF.add(res[i + j], GF.mul(a[i], b[j]));
566
+ }
567
+ }
568
+ return GF.polynomial(res);
569
+ },
570
+ mulPolyScalar(a, scalar) {
571
+ if (scalar == 0)
572
+ return [0];
573
+ if (scalar == 1)
574
+ return a;
575
+ const res = fillArr(a.length, 0);
576
+ for (let i = 0; i < a.length; i++)
577
+ res[i] = GF.mul(a[i], scalar);
578
+ return GF.polynomial(res);
579
+ },
580
+ mulPolyMonomial(a, degree, coefficient) {
581
+ if (degree < 0)
582
+ throw new Error('GF.mulPolyMonomial: invalid degree');
583
+ if (coefficient == 0)
584
+ return [0];
585
+ const res = fillArr(a.length + degree, 0);
586
+ for (let i = 0; i < a.length; i++)
587
+ res[i] = GF.mul(a[i], coefficient);
588
+ return GF.polynomial(res);
589
+ },
590
+ addPoly(a, b) {
591
+ if (a[0] === 0)
592
+ return b;
593
+ if (b[0] === 0)
594
+ return a;
595
+ let smaller = a;
596
+ let larger = b;
597
+ if (smaller.length > larger.length)
598
+ [smaller, larger] = [larger, smaller];
599
+ let sumDiff = fillArr(larger.length, 0);
600
+ let lengthDiff = larger.length - smaller.length;
601
+ let s = larger.slice(0, lengthDiff);
602
+ for (let i = 0; i < s.length; i++)
603
+ sumDiff[i] = s[i];
604
+ for (let i = lengthDiff; i < larger.length; i++)
605
+ sumDiff[i] = GF.add(smaller[i - lengthDiff], larger[i]);
606
+ return GF.polynomial(sumDiff);
607
+ },
608
+ remainderPoly(data, divisor) {
609
+ const out = Array.from(data);
610
+ for (let i = 0; i < data.length - divisor.length + 1; i++) {
611
+ const elm = out[i];
612
+ if (elm === 0)
613
+ continue;
614
+ for (let j = 1; j < divisor.length; j++) {
615
+ if (divisor[j] !== 0)
616
+ out[i + j] = GF.add(out[i + j], GF.mul(divisor[j], elm));
617
+ }
618
+ }
619
+ return out.slice(data.length - divisor.length + 1, out.length);
620
+ },
621
+ divisorPoly(degree) {
622
+ let g = [1];
623
+ for (let i = 0; i < degree; i++)
624
+ g = GF.mulPoly(g, [1, GF.pow(2, i)]);
625
+ return g;
626
+ },
627
+ evalPoly(poly, a) {
628
+ if (a == 0)
629
+ return GF.coefficient(poly, 0); // Just return the x^0 coefficient
630
+ let res = poly[0];
631
+ for (let i = 1; i < poly.length; i++)
632
+ res = GF.add(GF.mul(a, res), poly[i]);
633
+ return res;
634
+ },
635
+ // TODO: cleanup
636
+ euclidian(a, b, R) {
637
+ // Force degree(a) >= degree(b)
638
+ if (GF.degree(a) < GF.degree(b))
639
+ [a, b] = [b, a];
640
+ let rLast = a;
641
+ let r = b;
642
+ let tLast = [0];
643
+ let t = [1];
644
+ // while degree of Ri ≥ t/2
645
+ while (2 * GF.degree(r) >= R) {
646
+ let rLastLast = rLast;
647
+ let tLastLast = tLast;
648
+ rLast = r;
649
+ tLast = t;
650
+ if (rLast[0] === 0)
651
+ throw new Error('rLast[0] === 0');
652
+ r = rLastLast;
653
+ let q = [0];
654
+ const dltInverse = GF.inv(rLast[0]);
655
+ while (GF.degree(r) >= GF.degree(rLast) && r[0] !== 0) {
656
+ const degreeDiff = GF.degree(r) - GF.degree(rLast);
657
+ const scale = GF.mul(r[0], dltInverse);
658
+ q = GF.addPoly(q, GF.monomial(degreeDiff, scale));
659
+ r = GF.addPoly(r, GF.mulPolyMonomial(rLast, degreeDiff, scale));
660
+ }
661
+ q = GF.mulPoly(q, tLast);
662
+ t = GF.addPoly(q, tLastLast);
663
+ if (GF.degree(r) >= GF.degree(rLast))
664
+ throw new Error(`Division failed r: ${r}, rLast: ${rLast}`);
665
+ }
666
+ const sigmaTildeAtZero = GF.coefficient(t, 0);
667
+ if (sigmaTildeAtZero == 0)
668
+ throw new Error('sigmaTilde(0) was zero');
669
+ const inverse = GF.inv(sigmaTildeAtZero);
670
+ return [GF.mulPolyScalar(t, inverse), GF.mulPolyScalar(r, inverse)];
671
+ },
672
+ };
673
+ function RS(eccWords) {
674
+ return {
675
+ encode(from) {
676
+ const d = GF.divisorPoly(eccWords);
677
+ const pol = Array.from(from);
678
+ pol.push(...d.slice(0, -1).fill(0));
679
+ return Uint8Array.from(GF.remainderPoly(pol, d));
680
+ },
681
+ decode(to) {
682
+ const res = to.slice();
683
+ const poly = GF.polynomial(Array.from(to));
684
+ // Find errors
685
+ let syndrome = fillArr(eccWords, 0);
686
+ let hasError = false;
687
+ for (let i = 0; i < eccWords; i++) {
688
+ const evl = GF.evalPoly(poly, GF.exp(i));
689
+ syndrome[syndrome.length - 1 - i] = evl;
690
+ if (evl !== 0)
691
+ hasError = true;
692
+ }
693
+ if (!hasError)
694
+ return res;
695
+ syndrome = GF.polynomial(syndrome);
696
+ const monomial = GF.monomial(eccWords, 1);
697
+ const [errorLocator, errorEvaluator] = GF.euclidian(monomial, syndrome, eccWords);
698
+ // Error locations
699
+ const locations = fillArr(GF.degree(errorLocator), 0);
700
+ let e = 0;
701
+ for (let i = 1; i < 256 && e < locations.length; i++) {
702
+ if (GF.evalPoly(errorLocator, i) === 0)
703
+ locations[e++] = GF.inv(i);
704
+ }
705
+ if (e !== locations.length)
706
+ throw new Error('RS.decode: invalid errors number');
707
+ for (let i = 0; i < locations.length; i++) {
708
+ const pos = res.length - 1 - GF.log(locations[i]);
709
+ if (pos < 0)
710
+ throw new Error('RS.decode: invalid error location');
711
+ const xiInverse = GF.inv(locations[i]);
712
+ let denominator = 1;
713
+ for (let j = 0; j < locations.length; j++) {
714
+ if (i === j)
715
+ continue;
716
+ denominator = GF.mul(denominator, GF.add(1, GF.mul(locations[j], xiInverse)));
717
+ }
718
+ res[pos] = GF.add(res[pos], GF.mul(GF.evalPoly(errorEvaluator, xiInverse), GF.inv(denominator)));
719
+ }
720
+ return res;
721
+ },
722
+ };
723
+ }
724
+ // Interleaves blocks
725
+ function interleave(ver, ecc) {
726
+ const { words, shortBlocks, numBlocks, blockLen, total } = info.capacity(ver, ecc);
727
+ const rs = RS(words);
728
+ return {
729
+ encode(bytes) {
730
+ // Add error correction to bytes
731
+ const blocks = [];
732
+ const eccBlocks = [];
733
+ for (let i = 0; i < numBlocks; i++) {
734
+ const isShort = i < shortBlocks;
735
+ const len = blockLen + (isShort ? 0 : 1);
736
+ blocks.push(bytes.subarray(0, len));
737
+ eccBlocks.push(rs.encode(bytes.subarray(0, len)));
738
+ bytes = bytes.subarray(len);
739
+ }
740
+ const resBlocks = interleaveBytes(...blocks);
741
+ const resECC = interleaveBytes(...eccBlocks);
742
+ const res = new Uint8Array(resBlocks.length + resECC.length);
743
+ res.set(resBlocks);
744
+ res.set(resECC, resBlocks.length);
745
+ return res;
746
+ },
747
+ decode(data) {
748
+ if (data.length !== total)
749
+ throw new Error(`interleave.decode: len(data)=${data.length}, total=${total}`);
750
+ const blocks = [];
751
+ for (let i = 0; i < numBlocks; i++) {
752
+ const isShort = i < shortBlocks;
753
+ blocks.push(new Uint8Array(words + blockLen + (isShort ? 0 : 1)));
754
+ }
755
+ // Short blocks
756
+ let pos = 0;
757
+ for (let i = 0; i < blockLen; i++) {
758
+ for (let j = 0; j < numBlocks; j++)
759
+ blocks[j][i] = data[pos++];
760
+ }
761
+ // Long blocks
762
+ for (let j = shortBlocks; j < numBlocks; j++)
763
+ blocks[j][blockLen] = data[pos++];
764
+ // ECC
765
+ for (let i = blockLen; i < blockLen + words; i++) {
766
+ for (let j = 0; j < numBlocks; j++) {
767
+ const isShort = j < shortBlocks;
768
+ blocks[j][i + (isShort ? 0 : 1)] = data[pos++];
769
+ }
770
+ }
771
+ // Decode
772
+ // Error-correct and copy data blocks together into a stream of bytes
773
+ const res = [];
774
+ for (const block of blocks)
775
+ res.push(...Array.from(rs.decode(block)).slice(0, -words));
776
+ return Uint8Array.from(res);
777
+ },
778
+ };
779
+ }
780
+ // Draw
781
+ // Generic template per version+ecc+mask. Can be cached, to speedup calculations.
782
+ function drawTemplate(ver, ecc, maskIdx, test = false) {
783
+ const size = info.size.encode(ver);
784
+ let b = new Bitmap(size + 2);
785
+ // Finder patterns
786
+ // We draw full pattern and later slice, since before addition of borders finder is truncated by one pixel on sides
787
+ const finder = new Bitmap(3).rect(0, 3, true).border(1, false).border(1, true).border(1, false);
788
+ b = b
789
+ .embed(0, finder) // top left
790
+ .embed({ x: -finder.width, y: 0 }, finder) // top right
791
+ .embed({ x: 0, y: -finder.height }, finder); // bottom left
792
+ b = b.rectSlice(1, size);
793
+ // Alignment patterns
794
+ const align = new Bitmap(1).rect(0, 1, true).border(1, false).border(1, true);
795
+ const alignPos = info.alignmentPatterns(ver);
796
+ for (const y of alignPos) {
797
+ for (const x of alignPos) {
798
+ if (b.data[y][x] !== undefined)
799
+ continue;
800
+ b.embed({ x: x - 2, y: y - 2 }, align); // center of pattern should be at position
801
+ }
802
+ }
803
+ // Timing patterns
804
+ b = b
805
+ .hLine({ x: 0, y: 6 }, Infinity, ({ x }, cur) => (cur === undefined ? x % 2 == 0 : cur))
806
+ .vLine({ x: 6, y: 0 }, Infinity, ({ y }, cur) => (cur === undefined ? y % 2 == 0 : cur));
807
+ // Format information
808
+ {
809
+ const bits = info.formatBits(ecc, maskIdx);
810
+ const getBit = (i) => !test && ((bits >> i) & 1) == 1;
811
+ // vertical
812
+ for (let i = 0; i < 6; i++)
813
+ b.data[i][8] = getBit(i); // right of top-left finder
814
+ // TODO: re-write as lines, like:
815
+ // b.vLine({ x: 8, y: 0 }, 6, ({ x, y }) => getBit(y));
816
+ for (let i = 6; i < 8; i++)
817
+ b.data[i + 1][8] = getBit(i); // after timing pattern
818
+ for (let i = 8; i < 15; i++)
819
+ b.data[size - 15 + i][8] = getBit(i); // right of bottom-left finder
820
+ // horizontal
821
+ for (let i = 0; i < 8; i++)
822
+ b.data[8][size - i - 1] = getBit(i); // under top-right finder
823
+ for (let i = 8; i < 9; i++)
824
+ b.data[8][15 - i - 1 + 1] = getBit(i); // VVV, after timing
825
+ for (let i = 9; i < 15; i++)
826
+ b.data[8][15 - i - 1] = getBit(i); // under top-left finder
827
+ b.data[size - 8][8] = !test; // bottom-left finder, right
828
+ }
829
+ // Version information
830
+ if (ver >= 7) {
831
+ const bits = info.versionBits(ver);
832
+ for (let i = 0; i < 18; i += 1) {
833
+ const bit = !test && ((bits >> i) & 1) == 1;
834
+ const x = Math.floor(i / 3);
835
+ const y = (i % 3) + size - 8 - 3;
836
+ // two copies
837
+ b.data[x][y] = bit;
838
+ b.data[y][x] = bit;
839
+ }
840
+ }
841
+ return b;
842
+ }
843
+ // zigzag: bottom->top && top->bottom
844
+ function zigzag(tpl, maskIdx, fn) {
845
+ const size = tpl.height;
846
+ const pattern = PATTERNS[maskIdx];
847
+ // zig-zag pattern
848
+ let dir = -1;
849
+ let y = size - 1;
850
+ // two columns at time
851
+ for (let xOffset = size - 1; xOffset > 0; xOffset -= 2) {
852
+ if (xOffset == 6)
853
+ xOffset = 5; // skip vertical timing pattern
854
+ for (;; y += dir) {
855
+ for (let j = 0; j < 2; j += 1) {
856
+ const x = xOffset - j;
857
+ if (tpl.data[y][x] !== undefined)
858
+ continue; // skip already written elements
859
+ fn(x, y, pattern(x, y));
860
+ }
861
+ if (y + dir < 0 || y + dir >= size)
862
+ break;
863
+ }
864
+ dir = -dir; // change direction
865
+ }
866
+ }
867
+ // NOTE: byte encoding is just representation, QR works with strings only. Most decoders will fail on raw byte array,
868
+ // since they expect unicode or other text encoding inside bytes
869
+ function detectType(str) {
870
+ let type = 'numeric';
871
+ for (let x of str) {
872
+ if (info.alphabet.numeric.has(x))
873
+ continue;
874
+ type = 'alphanumeric';
875
+ if (!info.alphabet.alphanumerc.has(x))
876
+ return 'byte';
877
+ }
878
+ return type;
879
+ }
880
+ /**
881
+ * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
882
+ */
883
+ export function utf8ToBytes(str) {
884
+ if (typeof str !== 'string')
885
+ throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
886
+ return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
887
+ }
888
+ function encode(ver, ecc, data, type) {
889
+ let encoded = '';
890
+ let dataLen = data.length;
891
+ if (type === 'numeric') {
892
+ const t = info.alphabet.numeric.decode(data.split(''));
893
+ const n = t.length;
894
+ for (let i = 0; i < n - 2; i += 3)
895
+ encoded += bin(t[i] * 100 + t[i + 1] * 10 + t[i + 2], 10);
896
+ if (n % 3 === 1) {
897
+ encoded += bin(t[n - 1], 4);
898
+ }
899
+ else if (n % 3 === 2) {
900
+ encoded += bin(t[n - 2] * 10 + t[n - 1], 7);
901
+ }
902
+ }
903
+ else if (type === 'alphanumeric') {
904
+ const t = info.alphabet.alphanumerc.decode(data.split(''));
905
+ const n = t.length;
906
+ for (let i = 0; i < n - 1; i += 2)
907
+ encoded += bin(t[i] * 45 + t[i + 1], 11);
908
+ if (n % 2 == 1)
909
+ encoded += bin(t[n - 1], 6); // pad if odd number of chars
910
+ }
911
+ else if (type === 'byte') {
912
+ const utf8 = utf8ToBytes(data);
913
+ dataLen = utf8.length;
914
+ encoded = Array.from(utf8)
915
+ .map((i) => bin(i, 8))
916
+ .join('');
917
+ }
918
+ else {
919
+ throw new Error('encode: unsupported type');
920
+ }
921
+ const { capacity } = info.capacity(ver, ecc);
922
+ const len = bin(dataLen, info.lengthBits(ver, type));
923
+ let bits = info.modeBits[type] + len + encoded;
924
+ if (bits.length > capacity)
925
+ throw new Error('Capacity overflow');
926
+ // Terminator
927
+ bits += '0'.repeat(Math.min(4, Math.max(0, capacity - bits.length)));
928
+ // Pad bits string untill full byte
929
+ if (bits.length % 8)
930
+ bits += '0'.repeat(8 - (bits.length % 8));
931
+ // Add padding until capacity is full
932
+ const padding = '1110110000010001';
933
+ for (let idx = 0; bits.length !== capacity; idx++)
934
+ bits += padding[idx % padding.length];
935
+ // Convert a bitstring to array of bytes
936
+ const bytes = Uint8Array.from(bits.match(/(.{8})/g).map((i) => Number(`0b${i}`)));
937
+ return interleave(ver, ecc).encode(bytes);
938
+ }
939
+ // DRAW
940
+ function drawQR(ver, ecc, data, maskIdx, test = false) {
941
+ const b = drawTemplate(ver, ecc, maskIdx, test);
942
+ let i = 0;
943
+ const need = 8 * data.length;
944
+ zigzag(b, maskIdx, (x, y, mask) => {
945
+ let value = false;
946
+ if (i < need) {
947
+ value = ((data[i >>> 3] >> ((7 - i) & 7)) & 1) !== 0;
948
+ i++;
949
+ }
950
+ b.data[y][x] = value !== mask; // !== as xor
951
+ });
952
+ if (i !== need)
953
+ throw new Error('QR: bytes left after draw');
954
+ return b;
955
+ }
956
+ function penalty(bm) {
957
+ const inverse = bm.inverse();
958
+ // Adjacent modules in row/column in same | No. of modules = (5 + i) color
959
+ const sameColor = (row) => {
960
+ let res = 0;
961
+ for (let i = 0, same = 1, last = undefined; i < row.length; i++) {
962
+ if (last === row[i]) {
963
+ same++;
964
+ if (i !== row.length - 1)
965
+ continue; // handle last element
966
+ }
967
+ if (same >= 5)
968
+ res += 3 + (same - 5);
969
+ last = row[i];
970
+ same = 1;
971
+ }
972
+ return res;
973
+ };
974
+ let adjacent = 0;
975
+ bm.data.forEach((row) => (adjacent += sameColor(row)));
976
+ inverse.data.forEach((column) => (adjacent += sameColor(column)));
977
+ // Block of modules in same color (Block size = 2x2)
978
+ let box = 0;
979
+ let b = bm.data;
980
+ const lastW = bm.width - 1;
981
+ const lastH = bm.height - 1;
982
+ for (let x = 0; x < lastW; x++) {
983
+ for (let y = 0; y < lastH; y++) {
984
+ const x1 = x + 1;
985
+ const y1 = y + 1;
986
+ if (b[x][y] === b[x1][y] && b[x1][y] === b[x][y1] && b[x1][y] === b[x1][y1]) {
987
+ box += 3;
988
+ }
989
+ }
990
+ }
991
+ // 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide
992
+ const finderPattern = (row) => {
993
+ const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
994
+ const lightPattern = [false, false, false, false]; // light area 4 modules wide
995
+ const p1 = [...finderPattern, ...lightPattern];
996
+ const p2 = [...lightPattern, ...finderPattern];
997
+ let res = 0;
998
+ for (let i = 0; i < row.length; i++) {
999
+ if (includesAt(row, p1, i))
1000
+ res += 40;
1001
+ if (includesAt(row, p2, i))
1002
+ res += 40;
1003
+ }
1004
+ return res;
1005
+ };
1006
+ let finder = 0;
1007
+ for (const row of bm.data)
1008
+ finder += finderPattern(row);
1009
+ for (const column of inverse.data)
1010
+ finder += finderPattern(column);
1011
+ // Proportion of dark modules in entire symbol
1012
+ // Add 10 points to a deviation of 5% increment or decrement in the proportion
1013
+ // ratio of dark module from the referential 50%
1014
+ let darkPixels = 0;
1015
+ bm.rectRead(0, Infinity, (_c, val) => (darkPixels += val ? 1 : 0));
1016
+ const darkPercent = (darkPixels / (bm.height * bm.width)) * 100;
1017
+ const dark = 10 * Math.floor(Math.abs(darkPercent - 50) / 5);
1018
+ return adjacent + box + finder + dark;
1019
+ }
1020
+ // Selects best mask according to penalty, if no mask is provided
1021
+ function drawQRBest(ver, ecc, data, maskIdx) {
1022
+ if (maskIdx === undefined) {
1023
+ const bestMask = best();
1024
+ for (let mask = 0; mask < PATTERNS.length; mask++)
1025
+ bestMask.add(penalty(drawQR(ver, ecc, data, mask, true)), mask);
1026
+ maskIdx = bestMask.get();
1027
+ }
1028
+ if (maskIdx === undefined)
1029
+ throw new Error('Cannot find mask'); // Should never happen
1030
+ return drawQR(ver, ecc, data, maskIdx);
1031
+ }
1032
+ function validateECC(ec) {
1033
+ if (!ECMode.includes(ec))
1034
+ throw new Error(`Invalid error correction mode=${ec}. Expected: ${ECMode}`);
1035
+ }
1036
+ function validateEncoding(enc) {
1037
+ if (!Encoding.includes(enc))
1038
+ throw new Error(`Encoding: invalid mode=${enc}. Expected: ${Encoding}`);
1039
+ if (enc === 'kanji' || enc === 'eci')
1040
+ throw new Error(`Encoding: ${enc} is not supported (yet?).`);
1041
+ }
1042
+ function validateMask(mask) {
1043
+ if (![0, 1, 2, 3, 4, 5, 6, 7].includes(mask) || !PATTERNS[mask])
1044
+ throw new Error(`Invalid mask=${mask}. Expected number [0..7]`);
1045
+ }
1046
+ export function encodeQR(text, output = 'raw', opts = {}) {
1047
+ const ecc = opts.ecc !== undefined ? opts.ecc : 'medium';
1048
+ validateECC(ecc);
1049
+ const encoding = opts.encoding !== undefined ? opts.encoding : detectType(text);
1050
+ validateEncoding(encoding);
1051
+ if (opts.mask !== undefined)
1052
+ validateMask(opts.mask);
1053
+ let ver = opts.version;
1054
+ let data, err = new Error('Unknown error');
1055
+ if (ver !== undefined) {
1056
+ validateVersion(ver);
1057
+ data = encode(ver, ecc, text, encoding);
1058
+ }
1059
+ else {
1060
+ // If no version is provided, try to find smallest one which fits
1061
+ // Currently just scans all version, can be significantly speedup if needed
1062
+ for (let i = 1; i <= 40; i++) {
1063
+ try {
1064
+ data = encode(i, ecc, text, encoding);
1065
+ ver = i;
1066
+ break;
1067
+ }
1068
+ catch (e) {
1069
+ err = e;
1070
+ }
1071
+ }
1072
+ }
1073
+ if (!ver || !data)
1074
+ throw err;
1075
+ let res = drawQRBest(ver, ecc, data, opts.mask);
1076
+ res.assertDrawn();
1077
+ const border = opts.border === undefined ? 2 : opts.border;
1078
+ if (!Number.isSafeInteger(border))
1079
+ throw new Error(`invalid border type=${typeof border}`);
1080
+ res = res.border(border, false); // Add border
1081
+ if (opts.scale !== undefined)
1082
+ res = res.scale(opts.scale); // Scale image
1083
+ if (output === 'raw')
1084
+ return res.data;
1085
+ else if (output === 'ascii')
1086
+ return res.toASCII();
1087
+ else if (output === 'svg')
1088
+ return res.toSVG(opts.optimize);
1089
+ else if (output === 'gif')
1090
+ return res.toGIF();
1091
+ else if (output === 'term')
1092
+ return res.toTerm();
1093
+ else
1094
+ throw new Error(`Unknown output: ${output}`);
1095
+ }
1096
+ export default encodeQR;
1097
+ export const utils = {
1098
+ best,
1099
+ bin,
1100
+ drawTemplate,
1101
+ fillArr,
1102
+ info,
1103
+ interleave,
1104
+ validateVersion,
1105
+ zigzag,
1106
+ };
1107
+ // Unsafe API utils, exported only for tests
1108
+ export const _tests = {
1109
+ Bitmap,
1110
+ info,
1111
+ detectType,
1112
+ encode,
1113
+ drawQR,
1114
+ penalty,
1115
+ PATTERNS,
1116
+ };
1117
+ // Type tests
1118
+ // const o1 = qr('test', 'ascii');
1119
+ // const o2 = qr('test', 'raw');
1120
+ // const o3 = qr('test', 'gif');
1121
+ // const o4 = qr('test', 'svg');
1122
+ // const o5 = qr('test', 'term');
1123
+ //# sourceMappingURL=index.js.map