@foxford/qr-code 1.1.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.
Files changed (4) hide show
  1. package/README.mdx +78 -0
  2. package/index.cjs +1438 -0
  3. package/index.js +1403 -0
  4. package/package.json +40 -0
package/index.cjs ADDED
@@ -0,0 +1,1438 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ var __copyProps = (to, from, except, desc) => {
27
+ if (from && typeof from === "object" || typeof from === "function") {
28
+ for (let key of __getOwnPropNames(from))
29
+ if (!__hasOwnProp.call(to, key) && key !== except)
30
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
31
+ }
32
+ return to;
33
+ };
34
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
35
+ // If the importer is in node compatibility mode or this is not an ESM
36
+ // file that has been converted to a CommonJS file using a Babel-
37
+ // compatible transform (i.e. "__esModule" has not been set), then set
38
+ // "default" to the CommonJS "module.exports" for node compatibility.
39
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
40
+ mod
41
+ ));
42
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
43
+
44
+ // src/index.ts
45
+ var index_exports = {};
46
+ __export(index_exports, {
47
+ QRCode: () => QRCode,
48
+ renderQr: () => renderQr
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/lib/ecc.ts
53
+ var _Ecc = class _Ecc {
54
+ // QR-код может выдержать около 30% ошибочных кодовых слов
55
+ constructor(ordinal, formatBits) {
56
+ this.ordinal = ordinal;
57
+ this.formatBits = formatBits;
58
+ }
59
+ };
60
+ _Ecc.LOW = new _Ecc(0, 1);
61
+ // QR-код может выдержать около 7% ошибочных кодовых слов
62
+ _Ecc.MEDIUM = new _Ecc(1, 0);
63
+ // QR-код может выдержать около 15% ошибочных кодовых слов
64
+ _Ecc.QUARTILE = new _Ecc(2, 3);
65
+ // QR-код может выдержать около 25% ошибочных кодовых слов
66
+ _Ecc.HIGH = new _Ecc(3, 2);
67
+ var Ecc = _Ecc;
68
+
69
+ // src/lib/mode.ts
70
+ var _Mode = class _Mode {
71
+ constructor(modeBits, numBitsCharCount) {
72
+ this.modeBits = modeBits;
73
+ this.numBitsCharCount = numBitsCharCount;
74
+ }
75
+ // (Package-private) Возвращает битовую ширину поля счетчика символов для сегмента в
76
+ // этом режиме в QR-коде с заданным номером версии. Результат находится в диапазоне [0, 16].
77
+ numCharCountBits(ver) {
78
+ const result = this.numBitsCharCount[Math.floor((ver + 7) / 17)];
79
+ if (result === void 0) throw new Error("Assertion Error");
80
+ return result;
81
+ }
82
+ };
83
+ _Mode.NUMERIC = new _Mode(1, [10, 12, 14]);
84
+ _Mode.ALPHANUMERIC = new _Mode(2, [9, 11, 13]);
85
+ _Mode.BYTE = new _Mode(4, [8, 16, 16]);
86
+ _Mode.KANJI = new _Mode(8, [8, 10, 12]);
87
+ _Mode.ECI = new _Mode(7, [0, 0, 0]);
88
+ var Mode = _Mode;
89
+
90
+ // src/lib/utils.ts
91
+ function appendBits(val, len, bb) {
92
+ if (len < 0 || len > 31 || val >>> len != 0) throw new RangeError("Value out of range");
93
+ for (let i = len - 1; i >= 0; i--)
94
+ bb.push(val >>> i & 1);
95
+ }
96
+ function getBit(x, i) {
97
+ return (x >>> i & 1) != 0;
98
+ }
99
+ function assert(cond) {
100
+ if (!cond) throw new Error("Assertion error");
101
+ }
102
+
103
+ // src/lib/qr-segment.ts
104
+ var _QrSegment = class _QrSegment {
105
+ // Создает новый сегмент QR-кода с заданными атрибутами и данными.
106
+ // Количество символов (numChars) должно соответствовать режиму и длине битового буфера,
107
+ // но это ограничение не проверяется. Данный битовый буфер клонируется и сохраняется.
108
+ constructor(mode, numChars, bitData) {
109
+ this.mode = mode;
110
+ this.numChars = numChars;
111
+ this.bitData = bitData;
112
+ if (numChars < 0) throw new RangeError("Invalid argument");
113
+ this.bitData = bitData.slice();
114
+ }
115
+ // Возвращает сегмент, представляющий данные двоичные данные, закодированные в
116
+ // байтовом режиме. Допустимы все входные байтовые массивы. Любая текстовая строка
117
+ // может быть преобразована в байты UTF-8 и закодирована как сегмент в байтовом режиме.
118
+ static makeBytes(data) {
119
+ const bb = [];
120
+ for (const b of data) appendBits(b, 8, bb);
121
+ return new _QrSegment(Mode.BYTE, data.length, bb);
122
+ }
123
+ // Возвращает сегмент, представляющий данную строку десятичных цифр, закодированную в числовом режиме.
124
+ static makeNumeric(digits) {
125
+ if (!_QrSegment.isNumeric(digits)) throw new RangeError("String contains non-numeric characters");
126
+ const bb = [];
127
+ for (let i = 0; i < digits.length; ) {
128
+ const n = Math.min(digits.length - i, 3);
129
+ appendBits(parseInt(digits.substring(i, i + n), 10), n * 3 + 1, bb);
130
+ i += n;
131
+ }
132
+ return new _QrSegment(Mode.NUMERIC, digits.length, bb);
133
+ }
134
+ // Возвращает сегмент, представляющий данную текстовую строку, закодированную в буквенно-цифровом режиме.
135
+ // Допустимые символы: от 0 до 9, от A до Z (только в верхнем регистре), пробел,
136
+ // доллар, процент, звездочка, плюс, дефис, точка, слэш, двоеточие.
137
+ static makeAlphanumeric(text) {
138
+ if (!_QrSegment.isAlphanumeric(text))
139
+ throw new RangeError("String contains unencodable characters in alphanumeric mode");
140
+ const bb = [];
141
+ let i;
142
+ for (i = 0; i + 2 <= text.length; i += 2) {
143
+ let temp = _QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
144
+ temp += _QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
145
+ appendBits(temp, 11, bb);
146
+ }
147
+ if (i < text.length)
148
+ appendBits(_QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6, bb);
149
+ return new _QrSegment(Mode.ALPHANUMERIC, text.length, bb);
150
+ }
151
+ // Возвращает новый изменяемый список из нуля или более сегментов для представления данной текстовой строки Unicode.
152
+ // Результат может использовать различные режимы сегментов и переключать режимы для оптимизации длины битового потока.
153
+ static makeSegments(text) {
154
+ if (text == "") return [];
155
+ else if (_QrSegment.isNumeric(text)) return [_QrSegment.makeNumeric(text)];
156
+ else if (_QrSegment.isAlphanumeric(text)) return [_QrSegment.makeAlphanumeric(text)];
157
+ else return [_QrSegment.makeBytes(_QrSegment.toUtf8ByteArray(text))];
158
+ }
159
+ // Возвращает сегмент, представляющий Extended Channel Interpretation
160
+ // (ECI) с заданным значением.
161
+ static makeEci(assignVal) {
162
+ const bb = [];
163
+ if (assignVal < 0) throw new RangeError("ECI assignment value out of range");
164
+ else if (assignVal < 1 << 7) appendBits(assignVal, 8, bb);
165
+ else if (assignVal < 1 << 14) {
166
+ appendBits(2, 2, bb);
167
+ appendBits(assignVal, 14, bb);
168
+ } else if (assignVal < 1e6) {
169
+ appendBits(6, 3, bb);
170
+ appendBits(assignVal, 21, bb);
171
+ } else throw new RangeError("ECI assignment value out of range");
172
+ return new _QrSegment(Mode.ECI, 0, bb);
173
+ }
174
+ // Проверяет, может ли данная строка быть закодирована как сегмент в числовом режиме.
175
+ // Строка кодируема, если каждый символ находится в диапазоне от 0 до 9.
176
+ static isNumeric(text) {
177
+ return _QrSegment.NUMERIC_REGEX.test(text);
178
+ }
179
+ // Проверяет, может ли данная строка быть закодирована как сегмент в буквенно-цифровом режиме.
180
+ // Строка кодируема, если каждый символ принадлежит следующему набору: от 0 до 9, от A до Z
181
+ // (только в верхнем регистре), пробел, доллар, процент, звездочка, плюс, дефис, точка, слэш, двоеточие.
182
+ static isAlphanumeric(text) {
183
+ return _QrSegment.ALPHANUMERIC_REGEX.test(text);
184
+ }
185
+ // Возвращает новую копию битов данных этого сегмента.
186
+ getData() {
187
+ return this.bitData.slice();
188
+ }
189
+ // (Package-private) Вычисляет и возвращает количество битов, необходимое для кодирования данных сегментов
190
+ // в данной версии. Результат - бесконечность, если у сегмента слишком много символов для поля длины.
191
+ static getTotalBits(segs, version) {
192
+ let result = 0;
193
+ for (const seg of segs) {
194
+ const ccbits = seg.mode.numCharCountBits(version);
195
+ if (seg.numChars >= 1 << ccbits) return Infinity;
196
+ result += 4 + ccbits + seg.bitData.length;
197
+ }
198
+ return result;
199
+ }
200
+ // Возвращает новый массив байтов, представляющий данную строку, закодированную в UTF-8.
201
+ static toUtf8ByteArray(str) {
202
+ str = encodeURI(str);
203
+ const result = [];
204
+ for (let i = 0; i < str.length; i++) {
205
+ if (str.charAt(i) != "%") result.push(str.charCodeAt(i));
206
+ else {
207
+ result.push(parseInt(str.substring(i + 1, i + 3), 16));
208
+ i += 2;
209
+ }
210
+ }
211
+ return result;
212
+ }
213
+ };
214
+ // Точно описывает все строки, которые можно закодировать в числовом режиме.
215
+ _QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
216
+ // Точно описывает все строки, которые можно закодировать в буквенно-цифровом режиме.
217
+ _QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+./:-]*$/;
218
+ // Набор всех допустимых символов в буквенно-цифровом режиме,
219
+ // где значение каждого символа соответствует индексу в строке.
220
+ _QrSegment.ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
221
+ var QrSegment = _QrSegment;
222
+
223
+ // src/lib/index.ts
224
+ var _QrCode = class _QrCode {
225
+ // Создает новый QR-код с заданным номером версии,
226
+ // уровнем коррекции ошибок, байтами кодовых слов данных и номером маски.
227
+ // Это низкоуровневый API, который большинству пользователей не следует использовать напрямую.
228
+ // API среднего уровня - это функция encodeSegments().
229
+ constructor(version, errorCorrectionLevel, dataCodewords, msk) {
230
+ this.version = version;
231
+ this.errorCorrectionLevel = errorCorrectionLevel;
232
+ // Модули этого QR-кода (false = светлый, true = темный).
233
+ // Неизменяемы после завершения конструктора. Доступ через getModule().
234
+ this.modules = [];
235
+ // Указывает функциональные модули, которые не подвергаются маскированию. Отбрасывается после завершения конструктора.
236
+ this.isFunction = [];
237
+ if (version < _QrCode.MIN_VERSION || version > _QrCode.MAX_VERSION) throw new RangeError("Version value out of range");
238
+ if (msk < -1 || msk > 7) throw new RangeError("Mask value out of range");
239
+ this.size = version * 4 + 17;
240
+ const row = [];
241
+ for (let i = 0; i < this.size; i++) row.push(false);
242
+ for (let i = 0; i < this.size; i++) {
243
+ this.modules.push(row.slice());
244
+ this.isFunction.push(row.slice());
245
+ }
246
+ this.drawFunctionPatterns();
247
+ const allCodewords = this.addEccAndInterleave(dataCodewords);
248
+ this.drawCodewords(allCodewords);
249
+ if (msk == -1) {
250
+ let minPenalty = 1e9;
251
+ for (let i = 0; i < 8; i++) {
252
+ this.applyMask(i);
253
+ this.drawFormatBits(i);
254
+ const penalty = this.getPenaltyScore();
255
+ if (penalty < minPenalty) {
256
+ msk = i;
257
+ minPenalty = penalty;
258
+ }
259
+ this.applyMask(i);
260
+ }
261
+ }
262
+ assert(0 <= msk && msk <= 7);
263
+ this.mask = msk;
264
+ this.applyMask(msk);
265
+ this.drawFormatBits(msk);
266
+ this.isFunction = [];
267
+ }
268
+ // Возвращает QR-код, представляющий данную текстовую строку Unicode с заданным уровнем коррекции ошибок.
269
+ // В качестве консервативной верхней границы, эта функция гарантированно успешно работает для строк, содержащих 738 или меньше
270
+ // кодовых точек Unicode (не кодовых единиц UTF-16), если используется низкий уровень коррекции ошибок. Наименьшая возможная
271
+ // версия QR-кода выбирается автоматически для вывода. Уровень ECC результата может быть выше, чем
272
+ // аргумент ecl, если это можно сделать без увеличения версии.
273
+ static encodeText(text, ecl) {
274
+ const segs = QrSegment.makeSegments(text);
275
+ return _QrCode.encodeSegments(segs, ecl);
276
+ }
277
+ // Возвращает QR-код, представляющий данные двоичные данные с заданным уровнем коррекции ошибок.
278
+ // Эта функция всегда кодирует в двоичном режиме сегмента, а не в текстовом. Максимальное количество
279
+ // разрешенных байтов - 2953. Наименьшая возможная версия QR-кода выбирается автоматически для вывода.
280
+ // Уровень ECC результата может быть выше, чем аргумент ecl, если это можно сделать без увеличения версии.
281
+ static encodeBinary(data, ecl) {
282
+ const seg = QrSegment.makeBytes(data);
283
+ return _QrCode.encodeSegments([seg], ecl);
284
+ }
285
+ // Возвращает QR-код, представляющий данные сегменты с заданными параметрами кодирования.
286
+ // Наименьшая возможная версия QR-кода в заданном диапазоне автоматически
287
+ // выбирается для вывода. Если boostEcl истинно, то уровень ECC результата
288
+ // может быть выше, чем аргумент ecl, если это можно сделать без увеличения
289
+ // версии. Номер маски либо от 0 до 7 (включительно), чтобы принудительно использовать эту
290
+ // маску, либо -1 для автоматического выбора подходящей маски (что может быть медленно).
291
+ // Эта функция позволяет пользователю создавать настраиваемую последовательность сегментов, которая переключается
292
+ // между режимами (например, буквенно-цифровым и байтовым) для кодирования текста с меньшими затратами.
293
+ // Это API среднего уровня; API высокого уровня - encodeText() и encodeBinary().
294
+ static encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
295
+ if (!(_QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= _QrCode.MAX_VERSION) || mask < -1 || mask > 7)
296
+ throw new RangeError("Invalid value");
297
+ let version;
298
+ let dataUsedBits;
299
+ for (version = minVersion; ; version++) {
300
+ const dataCapacityBits2 = _QrCode.getNumDataCodewords(version, ecl) * 8;
301
+ const usedBits = QrSegment.getTotalBits(segs, version);
302
+ if (usedBits <= dataCapacityBits2) {
303
+ dataUsedBits = usedBits;
304
+ break;
305
+ }
306
+ if (version >= maxVersion)
307
+ throw new RangeError("Data too long");
308
+ }
309
+ for (const newEcl of [Ecc.MEDIUM, Ecc.QUARTILE, Ecc.HIGH]) {
310
+ if (boostEcl && dataUsedBits <= _QrCode.getNumDataCodewords(version, newEcl) * 8) ecl = newEcl;
311
+ }
312
+ const bb = [];
313
+ for (const seg of segs) {
314
+ appendBits(seg.mode.modeBits, 4, bb);
315
+ appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);
316
+ for (const b of seg.getData()) bb.push(b);
317
+ }
318
+ assert(bb.length == dataUsedBits);
319
+ const dataCapacityBits = _QrCode.getNumDataCodewords(version, ecl) * 8;
320
+ assert(bb.length <= dataCapacityBits);
321
+ appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
322
+ appendBits(0, (8 - bb.length % 8) % 8, bb);
323
+ assert(bb.length % 8 == 0);
324
+ for (let padByte = 236; bb.length < dataCapacityBits; padByte ^= 236 ^ 17) appendBits(padByte, 8, bb);
325
+ const dataCodewords = [];
326
+ while (dataCodewords.length * 8 < bb.length) dataCodewords.push(0);
327
+ bb.forEach((b, i) => {
328
+ var _a;
329
+ if (dataCodewords[i >>> 3] !== void 0) {
330
+ const idx = i >>> 3;
331
+ dataCodewords[idx] = ((_a = dataCodewords[idx]) != null ? _a : 0) | b << 7 - (i & 7);
332
+ }
333
+ });
334
+ return new _QrCode(version, ecl, dataCodewords, mask);
335
+ }
336
+ // Возвращает цвет модуля (пикселя) по заданным координатам, который равен false
337
+ // для светлого или true для темного. Левый верхний угол имеет координаты (x=0, y=0).
338
+ // Если заданные координаты выходят за пределы, возвращается false (светлый).
339
+ getModule(x, y) {
340
+ if (0 <= x && x < this.size && 0 <= y && y < this.size) {
341
+ const row = this.modules[y];
342
+ if (row !== void 0) {
343
+ const module2 = row[x];
344
+ if (module2 !== void 0) {
345
+ return module2;
346
+ }
347
+ }
348
+ }
349
+ return false;
350
+ }
351
+ // Считывает поле версии этого объекта, а затем рисует и помечает все функциональные модули.
352
+ drawFunctionPatterns() {
353
+ for (let i = 0; i < this.size; i++) {
354
+ this.setFunctionModule(6, i, i % 2 == 0);
355
+ this.setFunctionModule(i, 6, i % 2 == 0);
356
+ }
357
+ this.drawFinderPattern(3, 3);
358
+ this.drawFinderPattern(this.size - 4, 3);
359
+ this.drawFinderPattern(3, this.size - 4);
360
+ const alignPatPos = this.getAlignmentPatternPositions();
361
+ const numAlign = alignPatPos.length;
362
+ for (let i = 0; i < numAlign; i++) {
363
+ for (let j = 0; j < numAlign; j++) {
364
+ const iPos = alignPatPos[i];
365
+ const jPos = alignPatPos[j];
366
+ if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0) && iPos !== void 0 && jPos !== void 0)
367
+ this.drawAlignmentPattern(iPos, jPos);
368
+ }
369
+ }
370
+ this.drawFormatBits(0);
371
+ this.drawVersion();
372
+ }
373
+ // Рисует две копии битов формата (с собственным кодом коррекции ошибок)
374
+ // на основе данной маски и поля уровня коррекции ошибок этого объекта.
375
+ drawFormatBits(mask) {
376
+ const data = this.errorCorrectionLevel.formatBits << 3 | mask;
377
+ let rem = data;
378
+ for (let i = 0; i < 10; i++) rem = rem << 1 ^ (rem >>> 9) * 1335;
379
+ const bits = (data << 10 | rem) ^ 21522;
380
+ assert(bits >>> 15 == 0);
381
+ for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));
382
+ this.setFunctionModule(8, 7, getBit(bits, 6));
383
+ this.setFunctionModule(8, 8, getBit(bits, 7));
384
+ this.setFunctionModule(7, 8, getBit(bits, 8));
385
+ for (let i = 9; i < 15; i++) this.setFunctionModule(14 - i, 8, getBit(bits, i));
386
+ for (let i = 0; i < 8; i++) this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
387
+ for (let i = 8; i < 15; i++) this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
388
+ this.setFunctionModule(8, this.size - 8, true);
389
+ }
390
+ // Рисует две копии битов версии (с собственным кодом коррекции ошибок),
391
+ // на основе поля версии этого объекта, если 7 <= version <= 40.
392
+ drawVersion() {
393
+ if (this.version < 7) return;
394
+ let rem = this.version;
395
+ for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >>> 11) * 7973;
396
+ const bits = this.version << 12 | rem;
397
+ assert(bits >>> 18 == 0);
398
+ for (let i = 0; i < 18; i++) {
399
+ const color = getBit(bits, i);
400
+ const a = this.size - 11 + i % 3;
401
+ const b = Math.floor(i / 3);
402
+ this.setFunctionModule(a, b, color);
403
+ this.setFunctionModule(b, a, color);
404
+ }
405
+ }
406
+ // Рисует поисковый узор 9x9, включая разделитель границы,
407
+ // с центральным модулем в (x, y). Модули могут выходить за пределы.
408
+ drawFinderPattern(x, y) {
409
+ for (let dy = -4; dy <= 4; dy++) {
410
+ for (let dx = -4; dx <= 4; dx++) {
411
+ const dist = Math.max(Math.abs(dx), Math.abs(dy));
412
+ const xx = x + dx;
413
+ const yy = y + dy;
414
+ if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size)
415
+ this.setFunctionModule(xx, yy, dist != 2 && dist != 4);
416
+ }
417
+ }
418
+ }
419
+ // Рисует выравнивающий узор 5x5 с центральным модулем
420
+ // в (x, y). Все модули должны быть в пределах границ.
421
+ drawAlignmentPattern(x, y) {
422
+ for (let dy = -2; dy <= 2; dy++) {
423
+ for (let dx = -2; dx <= 2; dx++) this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
424
+ }
425
+ }
426
+ // Устанавливает цвет модуля и помечает его как функциональный модуль.
427
+ // Используется только конструктором. Координаты должны быть в пределах границ.
428
+ setFunctionModule(x, y, isDark) {
429
+ const row = this.modules[y];
430
+ const funcRow = this.isFunction[y];
431
+ if (row !== void 0 && funcRow !== void 0) {
432
+ row[x] = isDark;
433
+ funcRow[x] = true;
434
+ }
435
+ }
436
+ // Возвращает новую байтовую строку, представляющую данные с добавленными
437
+ // соответствующими кодовыми словами коррекции ошибок, на основе версии и уровня коррекции ошибок этого объекта.
438
+ addEccAndInterleave(data) {
439
+ var _a, _b, _c, _d;
440
+ const ver = this.version;
441
+ const ecl = this.errorCorrectionLevel;
442
+ if (data.length != _QrCode.getNumDataCodewords(ver, ecl)) throw new RangeError("Invalid argument");
443
+ const numBlocks = (_b = (_a = _QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal]) == null ? void 0 : _a[ver]) != null ? _b : 0;
444
+ const blockEccLen = (_d = (_c = _QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal]) == null ? void 0 : _c[ver]) != null ? _d : 0;
445
+ const rawCodewords = Math.floor(_QrCode.getNumRawDataModules(ver) / 8);
446
+ const numShortBlocks = numBlocks - rawCodewords % numBlocks;
447
+ const shortBlockLen = Math.floor(rawCodewords / numBlocks);
448
+ const blocks = [];
449
+ const rsDiv = _QrCode.reedSolomonComputeDivisor(blockEccLen);
450
+ for (let i = 0, k = 0; i < numBlocks; i++) {
451
+ const dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
452
+ k += dat.length;
453
+ const ecc = _QrCode.reedSolomonComputeRemainder(dat, rsDiv);
454
+ if (i < numShortBlocks) dat.push(0);
455
+ blocks.push(dat.concat(ecc));
456
+ }
457
+ const result = [];
458
+ const firstBlock = blocks[0];
459
+ if (firstBlock !== void 0) {
460
+ for (let i = 0; i < firstBlock.length; i++) {
461
+ blocks.forEach((block, j) => {
462
+ const blockI = block[i];
463
+ if ((i != shortBlockLen - blockEccLen || j >= numShortBlocks) && blockI !== void 0) result.push(blockI);
464
+ });
465
+ }
466
+ }
467
+ assert(result.length == rawCodewords);
468
+ return result;
469
+ }
470
+ // Рисует данную последовательность 8-битных кодовых слов (данные и коррекция ошибок) на всей
471
+ // области данных этого QR-кода. Функциональные модули должны быть отмечены до вызова этого метода.
472
+ drawCodewords(data) {
473
+ if (data.length != Math.floor(_QrCode.getNumRawDataModules(this.version) / 8))
474
+ throw new RangeError("Invalid argument");
475
+ let i = 0;
476
+ for (let right = this.size - 1; right >= 1; right -= 2) {
477
+ if (right == 6) right = 5;
478
+ for (let vert = 0; vert < this.size; vert++) {
479
+ for (let j = 0; j < 2; j++) {
480
+ const x = right - j;
481
+ const upward = (right + 1 & 2) == 0;
482
+ const y = upward ? this.size - 1 - vert : vert;
483
+ const funcRow = this.isFunction[y];
484
+ const dataByte = data[i >>> 3];
485
+ if (funcRow !== void 0 && !funcRow[x] && i < data.length * 8 && dataByte !== void 0) {
486
+ const row = this.modules[y];
487
+ if (row !== void 0) {
488
+ row[x] = getBit(dataByte, 7 - (i & 7));
489
+ i++;
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
495
+ assert(i == data.length * 8);
496
+ }
497
+ // Выполняет операцию XOR для модулей кодовых слов в этом QR-коде с заданным шаблоном маски.
498
+ // Функциональные модули должны быть отмечены, а биты кодовых слов нарисованы
499
+ // перед маскированием. Из-за арифметики XOR повторный вызов applyMask() с
500
+ // тем же значением маски отменит маску. Окончательный правильно сформированный
501
+ // QR-код требует применения ровно одной (не ноль, двух и т.д.) маски.
502
+ applyMask(mask) {
503
+ if (mask < 0 || mask > 7) throw new RangeError("Mask value out of range");
504
+ for (let y = 0; y < this.size; y++) {
505
+ for (let x = 0; x < this.size; x++) {
506
+ let invert;
507
+ switch (mask) {
508
+ case 0:
509
+ invert = (x + y) % 2 == 0;
510
+ break;
511
+ case 1:
512
+ invert = y % 2 == 0;
513
+ break;
514
+ case 2:
515
+ invert = x % 3 == 0;
516
+ break;
517
+ case 3:
518
+ invert = (x + y) % 3 == 0;
519
+ break;
520
+ case 4:
521
+ invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
522
+ break;
523
+ case 5:
524
+ invert = x * y % 2 + x * y % 3 == 0;
525
+ break;
526
+ case 6:
527
+ invert = (x * y % 2 + x * y % 3) % 2 == 0;
528
+ break;
529
+ case 7:
530
+ invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
531
+ break;
532
+ default:
533
+ throw new Error("Unreachable");
534
+ }
535
+ const funcRow = this.isFunction[y];
536
+ if (funcRow !== void 0 && !funcRow[x] && invert) {
537
+ const row = this.modules[y];
538
+ if (row !== void 0) {
539
+ const val = row[x];
540
+ if (val !== void 0) row[x] = !val;
541
+ }
542
+ }
543
+ }
544
+ }
545
+ }
546
+ // Вычисляет и возвращает штрафной балл на основе состояния текущих модулей этого QR-кода.
547
+ // Это используется алгоритмом автоматического выбора маски для поиска шаблона маски, который дает наименьший балл.
548
+ getPenaltyScore() {
549
+ let result = 0;
550
+ for (let y = 0; y < this.size; y++) {
551
+ let runColor = false;
552
+ let runX = 0;
553
+ const runHistory = [0, 0, 0, 0, 0, 0, 0];
554
+ const row = this.modules[y];
555
+ if (row === void 0) continue;
556
+ for (let x = 0; x < this.size; x++) {
557
+ if (row[x] == runColor) {
558
+ runX++;
559
+ if (runX == 5) result += _QrCode.PENALTY_N1;
560
+ else if (runX > 5) result++;
561
+ } else {
562
+ this.finderPenaltyAddHistory(runX, runHistory);
563
+ if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * _QrCode.PENALTY_N3;
564
+ const val = row[x];
565
+ if (val !== void 0) {
566
+ runColor = val;
567
+ runX = 1;
568
+ }
569
+ }
570
+ }
571
+ result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * _QrCode.PENALTY_N3;
572
+ }
573
+ for (let x = 0; x < this.size; x++) {
574
+ let runColor = false;
575
+ let runY = 0;
576
+ const runHistory = [0, 0, 0, 0, 0, 0, 0];
577
+ for (let y = 0; y < this.size; y++) {
578
+ const row = this.modules[y];
579
+ if (row === void 0) continue;
580
+ if (row[x] == runColor) {
581
+ runY++;
582
+ if (runY == 5) result += _QrCode.PENALTY_N1;
583
+ else if (runY > 5) result++;
584
+ } else {
585
+ this.finderPenaltyAddHistory(runY, runHistory);
586
+ if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * _QrCode.PENALTY_N3;
587
+ const val = row[x];
588
+ if (val !== void 0) {
589
+ runColor = val;
590
+ runY = 1;
591
+ }
592
+ }
593
+ }
594
+ result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * _QrCode.PENALTY_N3;
595
+ }
596
+ for (let y = 0; y < this.size - 1; y++) {
597
+ const row = this.modules[y];
598
+ const nextRow = this.modules[y + 1];
599
+ if (row === void 0 || nextRow === void 0) continue;
600
+ for (let x = 0; x < this.size - 1; x++) {
601
+ const color = row[x];
602
+ if (color !== void 0 && color == row[x + 1] && color == nextRow[x] && color == nextRow[x + 1])
603
+ result += _QrCode.PENALTY_N2;
604
+ }
605
+ }
606
+ let dark = 0;
607
+ for (const row of this.modules) dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
608
+ const total = this.size * this.size;
609
+ const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
610
+ assert(0 <= k && k <= 9);
611
+ result += k * _QrCode.PENALTY_N4;
612
+ assert(0 <= result && result <= 2568888);
613
+ return result;
614
+ }
615
+ // Возвращает возрастающий список позиций выравнивающих узоров для этого номера версии.
616
+ // Каждая позиция находится в диапазоне [0,177) и используется как для оси x, так и для оси y.
617
+ // Это может быть реализовано как таблица поиска из 40 списков целых чисел переменной длины.
618
+ getAlignmentPatternPositions() {
619
+ if (this.version == 1) return [];
620
+ else {
621
+ const numAlign = Math.floor(this.version / 7) + 2;
622
+ const step = this.version == 32 ? 26 : Math.ceil((this.size - 13) / (numAlign * 2 - 2)) * 2;
623
+ const result = [6];
624
+ for (let pos = this.size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
625
+ return result;
626
+ }
627
+ }
628
+ // Возвращает количество битов данных, которые можно сохранить в QR-коде данной версии, после
629
+ // исключения всех функциональных модулей. Включает остаточные биты, поэтому может быть не кратным 8.
630
+ // Результат находится в диапазоне [208, 29648]. Это может быть реализовано как таблица поиска на 40 записей.
631
+ static getNumRawDataModules(ver) {
632
+ if (ver < _QrCode.MIN_VERSION || ver > _QrCode.MAX_VERSION) throw new RangeError("Version number out of range");
633
+ let result = (16 * ver + 128) * ver + 64;
634
+ if (ver >= 2) {
635
+ const numAlign = Math.floor(ver / 7) + 2;
636
+ result -= (25 * numAlign - 10) * numAlign - 55;
637
+ if (ver >= 7) result -= 36;
638
+ }
639
+ assert(208 <= result && result <= 29648);
640
+ return result;
641
+ }
642
+ // Возвращает количество 8-битных кодовых слов данных (т.е. не для коррекции ошибок)
643
+ // в любом QR-коде для данной версии и уровня коррекции ошибок, с отброшенными остаточными битами.
644
+ // Эта чистая функция без состояния может быть реализована как таблица поиска (40*4).
645
+ static getNumDataCodewords(ver, ecl) {
646
+ var _a, _b, _c, _d;
647
+ const eccPerBlock = (_b = (_a = _QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal]) == null ? void 0 : _a[ver]) != null ? _b : 0;
648
+ const numBlocks = (_d = (_c = _QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal]) == null ? void 0 : _c[ver]) != null ? _d : 0;
649
+ return Math.floor(_QrCode.getNumRawDataModules(ver) / 8) - eccPerBlock * numBlocks;
650
+ }
651
+ // Возвращает порождающий полином для ECC Рида-Соломона для данной степени.
652
+ // Может быть реализована как таблица поиска по всем возможным значениям параметров, а не как алгоритм.
653
+ static reedSolomonComputeDivisor(degree) {
654
+ var _a;
655
+ if (degree < 1 || degree > 255) throw new RangeError("Degree out of range");
656
+ const result = [];
657
+ for (let i = 0; i < degree - 1; i++) result.push(0);
658
+ result.push(1);
659
+ let root = 1;
660
+ for (let i = 0; i < degree; i++) {
661
+ for (let j = 0; j < result.length; j++) {
662
+ const val = result[j];
663
+ if (val !== void 0) {
664
+ result[j] = _QrCode.reedSolomonMultiply(val, root);
665
+ if (j + 1 < result.length) {
666
+ const nextVal = result[j + 1];
667
+ if (nextVal !== void 0) result[j] = ((_a = result[j]) != null ? _a : 0) ^ nextVal;
668
+ }
669
+ }
670
+ }
671
+ root = _QrCode.reedSolomonMultiply(root, 2);
672
+ }
673
+ return result;
674
+ }
675
+ // Возвращает кодовое слово коррекции ошибок Рида-Соломона для данных полиномов данных и делителя.
676
+ static reedSolomonComputeRemainder(data, divisor) {
677
+ var _a;
678
+ const result = divisor.map((_) => 0);
679
+ for (const b of data) {
680
+ const factor = b ^ ((_a = result.shift()) != null ? _a : 0);
681
+ result.push(0);
682
+ divisor.forEach((coef, i) => {
683
+ const res = result[i];
684
+ if (res !== void 0) result[i] = res ^ _QrCode.reedSolomonMultiply(coef, factor);
685
+ });
686
+ }
687
+ return result;
688
+ }
689
+ // Возвращает произведение двух данных элементов поля по модулю GF(2^8/0x11D). Аргументы и результат
690
+ // являются беззнаковыми 8-битными целыми числами. Это может быть реализовано как таблица поиска 256*256 записей uint8.
691
+ static reedSolomonMultiply(x, y) {
692
+ if (x >>> 8 != 0 || y >>> 8 != 0) throw new RangeError("Byte out of range");
693
+ let z = 0;
694
+ for (let i = 7; i >= 0; i--) {
695
+ z = z << 1 ^ (z >>> 7) * 285;
696
+ z ^= (y >>> i & 1) * x;
697
+ }
698
+ assert(z >>> 8 == 0);
699
+ return z;
700
+ }
701
+ // Может быть вызвана только сразу после добавления светлой серии,
702
+ // и возвращает 0, 1 или 2. Вспомогательная функция для getPenaltyScore().
703
+ finderPenaltyCountPatterns(runHistory) {
704
+ var _a, _b;
705
+ const n = runHistory[1];
706
+ if (n === void 0) return 0;
707
+ assert(n <= this.size * 3);
708
+ const core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
709
+ const h0 = (_a = runHistory[0]) != null ? _a : 0;
710
+ const h6 = (_b = runHistory[6]) != null ? _b : 0;
711
+ return (core && h0 >= n * 4 && h6 >= n ? 1 : 0) + (core && h6 >= n * 4 && h0 >= n ? 1 : 0);
712
+ }
713
+ // Должна быть вызвана в конце строки (ряда или столбца) модулей. Вспомогательная функция для getPenaltyScore().
714
+ finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) {
715
+ if (currentRunColor) {
716
+ this.finderPenaltyAddHistory(currentRunLength, runHistory);
717
+ currentRunLength = 0;
718
+ }
719
+ currentRunLength += this.size;
720
+ this.finderPenaltyAddHistory(currentRunLength, runHistory);
721
+ return this.finderPenaltyCountPatterns(runHistory);
722
+ }
723
+ // Добавляет данное значение в начало и удаляет последнее значение. Вспомогательная функция для getPenaltyScore().
724
+ finderPenaltyAddHistory(currentRunLength, runHistory) {
725
+ if (runHistory[0] == 0) currentRunLength += this.size;
726
+ runHistory.pop();
727
+ runHistory.unshift(currentRunLength);
728
+ }
729
+ };
730
+ // Минимальный номер версии, поддерживаемый в стандарте QR Code Model 2.
731
+ _QrCode.MIN_VERSION = 1;
732
+ // Максимальный номер версии, поддерживаемый в стандарте QR Code Model 2.
733
+ _QrCode.MAX_VERSION = 40;
734
+ // Для использования в getPenaltyScore() при оценке, какая маска лучше.
735
+ _QrCode.PENALTY_N1 = 3;
736
+ _QrCode.PENALTY_N2 = 3;
737
+ _QrCode.PENALTY_N3 = 40;
738
+ _QrCode.PENALTY_N4 = 10;
739
+ _QrCode.ECC_CODEWORDS_PER_BLOCK = [
740
+ // Версия: (обратите внимание, что индекс 0 используется для заполнения и имеет недопустимое значение)
741
+ //0, 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 Уровень коррекции ошибок
742
+ [
743
+ -1,
744
+ 7,
745
+ 10,
746
+ 15,
747
+ 20,
748
+ 26,
749
+ 18,
750
+ 20,
751
+ 24,
752
+ 30,
753
+ 18,
754
+ 20,
755
+ 24,
756
+ 26,
757
+ 30,
758
+ 22,
759
+ 24,
760
+ 28,
761
+ 30,
762
+ 28,
763
+ 28,
764
+ 28,
765
+ 28,
766
+ 30,
767
+ 30,
768
+ 26,
769
+ 28,
770
+ 30,
771
+ 30,
772
+ 30,
773
+ 30,
774
+ 30,
775
+ 30,
776
+ 30,
777
+ 30,
778
+ 30,
779
+ 30,
780
+ 30,
781
+ 30,
782
+ 30,
783
+ 30
784
+ ],
785
+ // Низкий
786
+ [
787
+ -1,
788
+ 10,
789
+ 16,
790
+ 26,
791
+ 18,
792
+ 24,
793
+ 16,
794
+ 18,
795
+ 22,
796
+ 22,
797
+ 26,
798
+ 30,
799
+ 22,
800
+ 22,
801
+ 24,
802
+ 24,
803
+ 28,
804
+ 28,
805
+ 26,
806
+ 26,
807
+ 26,
808
+ 26,
809
+ 28,
810
+ 28,
811
+ 28,
812
+ 28,
813
+ 28,
814
+ 28,
815
+ 28,
816
+ 28,
817
+ 28,
818
+ 28,
819
+ 28,
820
+ 28,
821
+ 28,
822
+ 28,
823
+ 28,
824
+ 28,
825
+ 28,
826
+ 28,
827
+ 28
828
+ ],
829
+ // Средний
830
+ [
831
+ -1,
832
+ 13,
833
+ 22,
834
+ 18,
835
+ 26,
836
+ 18,
837
+ 24,
838
+ 18,
839
+ 22,
840
+ 20,
841
+ 24,
842
+ 28,
843
+ 26,
844
+ 24,
845
+ 20,
846
+ 30,
847
+ 24,
848
+ 28,
849
+ 28,
850
+ 26,
851
+ 30,
852
+ 28,
853
+ 30,
854
+ 30,
855
+ 30,
856
+ 30,
857
+ 28,
858
+ 30,
859
+ 30,
860
+ 30,
861
+ 30,
862
+ 30,
863
+ 30,
864
+ 30,
865
+ 30,
866
+ 30,
867
+ 30,
868
+ 30,
869
+ 30,
870
+ 30,
871
+ 30
872
+ ],
873
+ // Квартильный
874
+ [
875
+ -1,
876
+ 17,
877
+ 28,
878
+ 22,
879
+ 16,
880
+ 22,
881
+ 28,
882
+ 26,
883
+ 26,
884
+ 24,
885
+ 28,
886
+ 24,
887
+ 28,
888
+ 22,
889
+ 24,
890
+ 24,
891
+ 30,
892
+ 28,
893
+ 28,
894
+ 26,
895
+ 28,
896
+ 30,
897
+ 24,
898
+ 30,
899
+ 30,
900
+ 30,
901
+ 30,
902
+ 30,
903
+ 30,
904
+ 30,
905
+ 30,
906
+ 30,
907
+ 30,
908
+ 30,
909
+ 30,
910
+ 30,
911
+ 30,
912
+ 30,
913
+ 30,
914
+ 30,
915
+ 30
916
+ ]
917
+ // Высокий
918
+ ];
919
+ _QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
920
+ // Версия: (обратите внимание, что индекс 0 используется для заполнения и имеет недопустимое значение)
921
+ //0, 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 Уровень коррекции ошибок
922
+ [
923
+ -1,
924
+ 1,
925
+ 1,
926
+ 1,
927
+ 1,
928
+ 1,
929
+ 2,
930
+ 2,
931
+ 2,
932
+ 2,
933
+ 4,
934
+ 4,
935
+ 4,
936
+ 4,
937
+ 4,
938
+ 6,
939
+ 6,
940
+ 6,
941
+ 6,
942
+ 7,
943
+ 8,
944
+ 8,
945
+ 9,
946
+ 9,
947
+ 10,
948
+ 12,
949
+ 12,
950
+ 12,
951
+ 13,
952
+ 14,
953
+ 15,
954
+ 16,
955
+ 17,
956
+ 18,
957
+ 19,
958
+ 19,
959
+ 20,
960
+ 21,
961
+ 22,
962
+ 24,
963
+ 25
964
+ ],
965
+ // Низкий
966
+ [
967
+ -1,
968
+ 1,
969
+ 1,
970
+ 1,
971
+ 2,
972
+ 2,
973
+ 4,
974
+ 4,
975
+ 4,
976
+ 5,
977
+ 5,
978
+ 5,
979
+ 8,
980
+ 9,
981
+ 9,
982
+ 10,
983
+ 10,
984
+ 11,
985
+ 13,
986
+ 14,
987
+ 16,
988
+ 17,
989
+ 17,
990
+ 18,
991
+ 20,
992
+ 21,
993
+ 23,
994
+ 25,
995
+ 26,
996
+ 28,
997
+ 29,
998
+ 31,
999
+ 33,
1000
+ 35,
1001
+ 37,
1002
+ 38,
1003
+ 40,
1004
+ 43,
1005
+ 45,
1006
+ 47,
1007
+ 49
1008
+ ],
1009
+ // Средний
1010
+ [
1011
+ -1,
1012
+ 1,
1013
+ 1,
1014
+ 2,
1015
+ 2,
1016
+ 4,
1017
+ 4,
1018
+ 6,
1019
+ 6,
1020
+ 8,
1021
+ 8,
1022
+ 8,
1023
+ 10,
1024
+ 12,
1025
+ 16,
1026
+ 12,
1027
+ 17,
1028
+ 16,
1029
+ 18,
1030
+ 21,
1031
+ 20,
1032
+ 23,
1033
+ 23,
1034
+ 25,
1035
+ 27,
1036
+ 29,
1037
+ 34,
1038
+ 34,
1039
+ 35,
1040
+ 38,
1041
+ 40,
1042
+ 43,
1043
+ 45,
1044
+ 48,
1045
+ 51,
1046
+ 53,
1047
+ 56,
1048
+ 59,
1049
+ 62,
1050
+ 65,
1051
+ 68
1052
+ ],
1053
+ // Квартильный
1054
+ [
1055
+ -1,
1056
+ 1,
1057
+ 1,
1058
+ 2,
1059
+ 4,
1060
+ 4,
1061
+ 4,
1062
+ 5,
1063
+ 6,
1064
+ 8,
1065
+ 8,
1066
+ 11,
1067
+ 11,
1068
+ 16,
1069
+ 16,
1070
+ 18,
1071
+ 16,
1072
+ 19,
1073
+ 21,
1074
+ 25,
1075
+ 25,
1076
+ 25,
1077
+ 34,
1078
+ 30,
1079
+ 32,
1080
+ 35,
1081
+ 37,
1082
+ 40,
1083
+ 42,
1084
+ 45,
1085
+ 48,
1086
+ 51,
1087
+ 54,
1088
+ 57,
1089
+ 60,
1090
+ 63,
1091
+ 66,
1092
+ 70,
1093
+ 74,
1094
+ 77,
1095
+ 81
1096
+ ]
1097
+ // Высокий
1098
+ ];
1099
+ var QrCode = _QrCode;
1100
+
1101
+ // src/service/common-utils.ts
1102
+ var isModuleVisible = (cellRect, logoRect) => {
1103
+ if (!logoRect) return true;
1104
+ return !(cellRect.x >= logoRect.x && cellRect.x + cellRect.w <= logoRect.x + logoRect.w && cellRect.y >= logoRect.y && cellRect.y + cellRect.h <= logoRect.y + logoRect.h);
1105
+ };
1106
+ var getLogoModuleSize = (aspectRatio, padModules) => {
1107
+ let logoWidthModules, logoHeightModules;
1108
+ if (aspectRatio >= 1) {
1109
+ logoWidthModules = padModules - 2;
1110
+ logoHeightModules = logoWidthModules / aspectRatio;
1111
+ if (logoHeightModules > padModules - 2) {
1112
+ logoHeightModules = padModules - 2;
1113
+ logoWidthModules = logoHeightModules * aspectRatio;
1114
+ }
1115
+ } else {
1116
+ logoHeightModules = padModules - 2;
1117
+ logoWidthModules = logoHeightModules * aspectRatio;
1118
+ if (logoWidthModules > padModules - 2) {
1119
+ logoWidthModules = padModules - 2;
1120
+ logoHeightModules = logoWidthModules / aspectRatio;
1121
+ }
1122
+ }
1123
+ return {
1124
+ logoHeightModules,
1125
+ logoWidthModules
1126
+ };
1127
+ };
1128
+ var prepareQRCodeData = ({
1129
+ text,
1130
+ logo,
1131
+ logoAspectRatio = 1,
1132
+ logoSizePercent = 30,
1133
+ size = 320
1134
+ }) => {
1135
+ if (!text || !text.trim()) return null;
1136
+ const qr = QrCode.encodeText(text, Ecc.HIGH);
1137
+ const cellCount = qr.size;
1138
+ const cellSize = size / cellCount;
1139
+ const qrPixelSize = size;
1140
+ const offset = 0;
1141
+ const maxPadModules = cellCount - 8;
1142
+ const maxPadPx = maxPadModules * cellSize;
1143
+ const areaLimit = 0.2;
1144
+ const maxPadPxByArea = Math.sqrt(qrPixelSize * qrPixelSize * areaLimit);
1145
+ const maxPadPxFinal = Math.min(maxPadPx, maxPadPxByArea);
1146
+ const padSizeRaw = logoSizePercent / 100 * qrPixelSize;
1147
+ let padModules = Math.round(padSizeRaw / cellSize);
1148
+ if (padModules < 3) padModules = 3;
1149
+ if (padModules % 2 === 0) padModules += 1;
1150
+ padModules = Math.min(padModules, Math.floor(maxPadPxFinal / cellSize));
1151
+ if (padModules % 2 === 0) padModules -= 1;
1152
+ const padCenterX = offset + qrPixelSize / 2;
1153
+ const padCenterY = offset + qrPixelSize / 2;
1154
+ if (!logo) {
1155
+ return {
1156
+ cellCount,
1157
+ cellSize,
1158
+ logoRect: null,
1159
+ offset,
1160
+ padCenterX,
1161
+ padCenterY,
1162
+ padModules,
1163
+ qr,
1164
+ qrPixelSize
1165
+ };
1166
+ }
1167
+ const { logoHeightModules, logoWidthModules } = getLogoModuleSize(logoAspectRatio, padModules);
1168
+ const padWidth = (logoWidthModules + 2) * cellSize;
1169
+ const padHeight = (logoHeightModules + 2) * cellSize;
1170
+ const padX = padCenterX - padWidth / 2;
1171
+ const padY = padCenterY - padHeight / 2;
1172
+ const logoDrawWidth = logoWidthModules * cellSize;
1173
+ const logoDrawHeight = logoHeightModules * cellSize;
1174
+ const logoX = padX + cellSize;
1175
+ const logoY = padY + cellSize;
1176
+ const logoRect = { h: padHeight, w: padWidth, x: padX, y: padY };
1177
+ return {
1178
+ aspectRatio: logoAspectRatio,
1179
+ cellCount,
1180
+ cellSize,
1181
+ logoDrawHeight,
1182
+ logoDrawWidth,
1183
+ logoRect,
1184
+ logoX,
1185
+ logoY,
1186
+ offset,
1187
+ padCenterX,
1188
+ padCenterY,
1189
+ padHeight,
1190
+ padModules,
1191
+ padWidth,
1192
+ qr,
1193
+ qrPixelSize
1194
+ };
1195
+ };
1196
+
1197
+ // src/constants.ts
1198
+ var DEFAULT_LOGO_PROPS = { logo: null, logoAspectRatio: 1 };
1199
+ var DEFAULT_QRCODE_SIZE = 200;
1200
+ var DEFAULT_QRCODE_COLOR = "#000";
1201
+ var DEFAULT_QRCODE_BG_COLOR = "transparent";
1202
+
1203
+ // src/service/draw-utils.ts
1204
+ function drawRoundRect(ctx, x, y, w, h, r) {
1205
+ ctx.beginPath();
1206
+ ctx.moveTo(x + r, y);
1207
+ ctx.lineTo(x + w - r, y);
1208
+ ctx.arcTo(x + w, y, x + w, y + r, r);
1209
+ ctx.lineTo(x + w, y + h - r);
1210
+ ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
1211
+ ctx.lineTo(x + r, y + h);
1212
+ ctx.arcTo(x, y + h, x, y + h - r, r);
1213
+ ctx.lineTo(x, y + r);
1214
+ ctx.arcTo(x, y, x + r, y, r);
1215
+ ctx.closePath();
1216
+ }
1217
+ function drawSmartRoundRect(ctx, x, y, size, rad, tl, tr, br, bl) {
1218
+ ctx.beginPath();
1219
+ ctx.moveTo(x + (tl ? rad : 0), y);
1220
+ ctx.lineTo(x + size - (tr ? rad : 0), y);
1221
+ if (tr) ctx.quadraticCurveTo(x + size, y, x + size, y + rad);
1222
+ else ctx.lineTo(x + size, y);
1223
+ ctx.lineTo(x + size, y + size - (br ? rad : 0));
1224
+ if (br) ctx.quadraticCurveTo(x + size, y + size, x + size - rad, y + size);
1225
+ else ctx.lineTo(x + size, y + size);
1226
+ ctx.lineTo(x + (bl ? rad : 0), y + size);
1227
+ if (bl) ctx.quadraticCurveTo(x, y + size, x, y + size - rad);
1228
+ else ctx.lineTo(x, y + size);
1229
+ ctx.lineTo(x, y + (tl ? rad : 0));
1230
+ if (tl) ctx.quadraticCurveTo(x, y, x + (tl ? rad : 0), y);
1231
+ else ctx.lineTo(x, y);
1232
+ ctx.closePath();
1233
+ }
1234
+ function drawFinderPatternRound(ctx, x, y, size, color, finderRadius, bgColor) {
1235
+ const rad7 = finderRadius / 100 * (size * 7 / 2);
1236
+ const rad5 = finderRadius / 100 * (size * 5 / 2);
1237
+ const rad3 = finderRadius / 100 * (size * 3 / 2);
1238
+ ctx.save();
1239
+ ctx.fillStyle = color;
1240
+ drawRoundRect(ctx, x, y, size * 7, size * 7, rad7);
1241
+ ctx.fill();
1242
+ ctx.save();
1243
+ ctx.globalCompositeOperation = "destination-out";
1244
+ drawRoundRect(ctx, x + size, y + size, size * 5, size * 5, rad5);
1245
+ ctx.fill();
1246
+ ctx.restore();
1247
+ if (bgColor !== "transparent") {
1248
+ ctx.fillStyle = bgColor;
1249
+ drawRoundRect(ctx, x + size, y + size, size * 5, size * 5, rad5);
1250
+ ctx.fill();
1251
+ }
1252
+ ctx.fillStyle = color;
1253
+ drawRoundRect(ctx, x + size * 2, y + size * 2, size * 3, size * 3, rad3);
1254
+ ctx.fill();
1255
+ ctx.restore();
1256
+ }
1257
+ function drawLogoImage(ctx, qrData, image) {
1258
+ const { cellSize, padModules } = qrData;
1259
+ const aspectRatio = image.width / image.height || 1;
1260
+ const { logoHeightModules, logoWidthModules } = getLogoModuleSize(aspectRatio, padModules);
1261
+ const padWidth = (logoWidthModules + 2) * cellSize;
1262
+ const padHeight = (logoHeightModules + 2) * cellSize;
1263
+ const padX = qrData.padCenterX - padWidth / 2;
1264
+ const padY = qrData.padCenterY - padHeight / 2;
1265
+ const logoDrawWidth = logoWidthModules * cellSize;
1266
+ const logoDrawHeight = logoHeightModules * cellSize;
1267
+ const logoX = padX + cellSize;
1268
+ const logoY = padY + cellSize;
1269
+ ctx.save();
1270
+ ctx.fillStyle = "transparent";
1271
+ ctx.fillRect(padX, padY, padWidth, padHeight);
1272
+ ctx.drawImage(image, logoX, logoY, logoDrawWidth, logoDrawHeight);
1273
+ ctx.restore();
1274
+ }
1275
+ function drawLogo(ctx, qrData, logo) {
1276
+ if (!logo || !qrData.logoRect || !qrData.cellSize) return;
1277
+ const image = new window.Image();
1278
+ image.onload = () => drawLogoImage(ctx, qrData, image);
1279
+ image.src = logo;
1280
+ if (image.complete && image.naturalWidth) {
1281
+ drawLogoImage(ctx, qrData, image);
1282
+ }
1283
+ }
1284
+ function drawBackground(ctx, props, qrData) {
1285
+ const { size = DEFAULT_QRCODE_SIZE, finderRadius = 0, bgColor } = props;
1286
+ if (!bgColor || bgColor === "transparent") return;
1287
+ const { cellSize } = qrData;
1288
+ const finderSize = 7 * cellSize;
1289
+ const cornerRadius = finderRadius / 100 * (finderSize / 2);
1290
+ ctx.save();
1291
+ ctx.fillStyle = bgColor;
1292
+ drawRoundRect(ctx, 0, 0, size, size, cornerRadius);
1293
+ ctx.fill();
1294
+ ctx.restore();
1295
+ }
1296
+ function drawModules(ctx, qrData, props) {
1297
+ const { qr, cellCount, cellSize, offset, logoRect } = qrData;
1298
+ const { dotColor = "#000", radius = 0, smoothDots = false } = props;
1299
+ const dotRad = radius / 100 * (cellSize / 2);
1300
+ for (let r = 0; r < cellCount; r++) {
1301
+ for (let c = 0; c < cellCount; c++) {
1302
+ if (qr.getModule(c, r)) {
1303
+ const inFinder = r < 7 && c < 7 || r < 7 && c >= cellCount - 7 || r >= cellCount - 7 && c < 7;
1304
+ if (inFinder) continue;
1305
+ const x = c * cellSize + offset;
1306
+ const y = r * cellSize + offset;
1307
+ const cellRect = { h: cellSize, w: cellSize, x, y };
1308
+ if (!isModuleVisible(cellRect, logoRect)) continue;
1309
+ ctx.fillStyle = dotColor;
1310
+ if (smoothDots) {
1311
+ const isActiveNeighbor = (c2, r2) => {
1312
+ if (c2 < 0 || c2 >= cellCount || r2 < 0 || r2 >= cellCount) return false;
1313
+ if (!qr.getModule(c2, r2)) return false;
1314
+ if (logoRect) {
1315
+ const x2 = c2 * cellSize + offset;
1316
+ const y2 = r2 * cellSize + offset;
1317
+ const neighborRect = { h: cellSize, w: cellSize, x: x2, y: y2 };
1318
+ if (!isModuleVisible(neighborRect, logoRect)) return false;
1319
+ }
1320
+ return true;
1321
+ };
1322
+ const left = isActiveNeighbor(c - 1, r);
1323
+ const right = isActiveNeighbor(c + 1, r);
1324
+ const top = isActiveNeighbor(c, r - 1);
1325
+ const bottom = isActiveNeighbor(c, r + 1);
1326
+ const tl = !(top || left);
1327
+ const tr = !(top || right);
1328
+ const br = !(bottom || right);
1329
+ const bl = !(bottom || left);
1330
+ drawSmartRoundRect(ctx, x, y, cellSize, dotRad, tl, tr, br, bl);
1331
+ ctx.fill();
1332
+ } else {
1333
+ if (dotRad === 0) {
1334
+ ctx.fillRect(x, y, cellSize, cellSize);
1335
+ } else {
1336
+ drawRoundRect(ctx, x, y, cellSize, cellSize, dotRad);
1337
+ ctx.fill();
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ // src/service/index.ts
1346
+ var renderQr = ({ ctx, params }) => {
1347
+ const {
1348
+ text,
1349
+ dotColor = DEFAULT_QRCODE_COLOR,
1350
+ finderRadius = 0,
1351
+ logo,
1352
+ logoAspectRatio,
1353
+ logoSizePercent = 30,
1354
+ size = DEFAULT_QRCODE_SIZE,
1355
+ bgColor = DEFAULT_QRCODE_BG_COLOR
1356
+ } = params;
1357
+ ctx.lineJoin = "round";
1358
+ ctx.imageSmoothingEnabled = false;
1359
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1360
+ if (!text || !text.trim()) {
1361
+ ctx.fillStyle = "#888";
1362
+ ctx.font = "bold 18px sans-serif";
1363
+ ctx.textAlign = "center";
1364
+ ctx.textBaseline = "middle";
1365
+ ctx.fillText("\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445", size / 2, size / 2);
1366
+ return;
1367
+ }
1368
+ const qrData = prepareQRCodeData({ logo, logoAspectRatio, logoSizePercent, size, text });
1369
+ if (!qrData) return;
1370
+ drawBackground(ctx, params, qrData);
1371
+ drawModules(ctx, qrData, params);
1372
+ const { cellSize, offset, cellCount } = qrData;
1373
+ drawFinderPatternRound(ctx, offset, offset, cellSize, dotColor, finderRadius, bgColor);
1374
+ drawFinderPatternRound(ctx, offset + (cellCount - 7) * cellSize, offset, cellSize, dotColor, finderRadius, bgColor);
1375
+ drawFinderPatternRound(ctx, offset, offset + (cellCount - 7) * cellSize, cellSize, dotColor, finderRadius, bgColor);
1376
+ if (logo) {
1377
+ drawLogo(ctx, qrData, logo);
1378
+ }
1379
+ };
1380
+
1381
+ // src/ui/index.tsx
1382
+ var import_react = require("react");
1383
+
1384
+ // src/ui/styled.ts
1385
+ var import_styled_components = __toESM(require("styled-components"), 1);
1386
+ var Canvas = (0, import_styled_components.default)("canvas")`
1387
+ ${({ $size }) => `
1388
+ width: ${$size}px;
1389
+ height: ${$size}px;
1390
+ `}
1391
+ `;
1392
+
1393
+ // src/ui/index.tsx
1394
+ var QRCode = (props) => {
1395
+ const { size = DEFAULT_QRCODE_SIZE, logo } = props;
1396
+ const canvasRef = (0, import_react.useRef)(null);
1397
+ const [asyncProps, setAsyncProps] = (0, import_react.useState)(logo ? null : DEFAULT_LOGO_PROPS);
1398
+ (0, import_react.useEffect)(() => {
1399
+ if (logo) {
1400
+ const img = new window.Image();
1401
+ img.onload = () => {
1402
+ setAsyncProps({
1403
+ logo,
1404
+ logoAspectRatio: img.naturalWidth / img.naturalHeight
1405
+ });
1406
+ };
1407
+ img.onerror = () => {
1408
+ setAsyncProps({
1409
+ logo: null,
1410
+ logoAspectRatio: 1
1411
+ });
1412
+ };
1413
+ img.src = logo;
1414
+ } else {
1415
+ setAsyncProps(DEFAULT_LOGO_PROPS);
1416
+ }
1417
+ }, [logo]);
1418
+ (0, import_react.useEffect)(() => {
1419
+ const canvas = canvasRef.current;
1420
+ if (!canvas || !asyncProps) {
1421
+ return;
1422
+ }
1423
+ const ctx = canvas.getContext("2d");
1424
+ if (!ctx) return;
1425
+ const dpr = window.devicePixelRatio || 1;
1426
+ const newSize = size * dpr;
1427
+ canvas.width = newSize;
1428
+ canvas.height = newSize;
1429
+ ctx.scale(dpr, dpr);
1430
+ renderQr({ ctx, params: __spreadValues(__spreadValues({}, props), asyncProps) });
1431
+ }, [props, asyncProps, size]);
1432
+ return /* @__PURE__ */ React.createElement(Canvas, { ref: canvasRef, $size: size });
1433
+ };
1434
+ // Annotate the CommonJS export names for ESM import in node:
1435
+ 0 && (module.exports = {
1436
+ QRCode,
1437
+ renderQr
1438
+ });