snowkit 0.0.0-stage → 0.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.
@@ -0,0 +1,1205 @@
1
+ const ECC = [
2
+ [-1, 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],
3
+ [-1, 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],
4
+ [-1, 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],
5
+ [-1, 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],
6
+ ];
7
+ const BLOCKS = [
8
+ [-1, 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],
9
+ [-1, 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],
10
+ [-1, 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],
11
+ [-1, 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],
12
+ ];
13
+ const FORMAT_ECL = [1, 0, 3, 2];
14
+ const FINDER_LIKE = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
15
+ const ALNUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
16
+ const CHARSETS = { 1: "iso-8859-1", 3: "iso-8859-1", 4: "iso-8859-2", 7: "iso-8859-5", 9: "iso-8859-7", 20: "shift_jis", 21: "windows-1250", 22: "windows-1251", 23: "windows-1252", 24: "windows-1256", 25: "utf-16be", 26: "utf-8", 28: "big5", 29: "gb18030", 30: "euc-kr" };
17
+ const MASKS = [
18
+ (x, y) => (x + y) % 2 === 0,
19
+ (x, y) => y % 2 === 0,
20
+ (x, y) => x % 3 === 0,
21
+ (x, y) => (x + y) % 3 === 0,
22
+ (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
23
+ (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
24
+ (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
25
+ (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
26
+ ];
27
+
28
+ export const LEVELS = ["L", "M", "Q", "H"];
29
+
30
+ const EXP = new Uint8Array(512);
31
+ const LOG = new Uint8Array(256);
32
+ for (let i = 0, v = 1; i < 255; i++) {
33
+ EXP[i] = v;
34
+ LOG[v] = i;
35
+ v <<= 1;
36
+ if (v & 256) v ^= 0x11d;
37
+ }
38
+ for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
39
+
40
+ const gmul = (a, b) => (a && b ? EXP[LOG[a] + LOG[b]] : 0);
41
+ const gdiv = (a, b) => (a ? EXP[LOG[a] + 255 - LOG[b]] : 0);
42
+
43
+ function levelIndex(ecl) {
44
+ if (typeof ecl === "number") return Math.max(0, Math.min(3, ecl | 0));
45
+ const i = LEVELS.indexOf(String(ecl || "M").toUpperCase());
46
+ return i < 0 ? 1 : i;
47
+ }
48
+
49
+ function rawModules(ver) {
50
+ let n = (16 * ver + 128) * ver + 64;
51
+ if (ver >= 2) {
52
+ const a = Math.floor(ver / 7) + 2;
53
+ n -= (25 * a - 10) * a - 55;
54
+ if (ver >= 7) n -= 36;
55
+ }
56
+ return n;
57
+ }
58
+
59
+ const dataBytes = (ver, ecl) => Math.floor(rawModules(ver) / 8) - ECC[ecl][ver] * BLOCKS[ecl][ver];
60
+
61
+ function countBits(mode, ver) {
62
+ const i = ver < 10 ? 0 : ver < 27 ? 1 : 2;
63
+ if (mode === 1) return [10, 12, 14][i];
64
+ if (mode === 2) return [9, 11, 13][i];
65
+ if (mode === 8) return [8, 10, 12][i];
66
+ return [8, 16, 16][i];
67
+ }
68
+
69
+ function alignments(ver) {
70
+ if (ver === 1) return [];
71
+ const n = Math.floor(ver / 7) + 2;
72
+ const step = Math.floor((ver * 8 + n * 3 + 5) / (n * 4 - 4)) * 2;
73
+ const out = [6];
74
+ for (let pos = ver * 4 + 10; out.length < n; pos -= step) out.splice(1, 0, pos);
75
+ return out;
76
+ }
77
+
78
+ function formatBits(ecl, mask) {
79
+ const data = (FORMAT_ECL[ecl] << 3) | mask;
80
+ let rem = data;
81
+ for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
82
+ return ((data << 10) | rem) ^ 0x5412;
83
+ }
84
+
85
+ function versionBits(ver) {
86
+ let rem = ver;
87
+ for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
88
+ return (ver << 12) | rem;
89
+ }
90
+
91
+ function formatSpots(size) {
92
+ const a = [];
93
+ const b = [];
94
+ for (let i = 0; i < 15; i++) {
95
+ if (i < 6) a.push([8, i]);
96
+ else if (i === 6) a.push([8, 7]);
97
+ else if (i === 7) a.push([8, 8]);
98
+ else if (i === 8) a.push([7, 8]);
99
+ else a.push([14 - i, 8]);
100
+ b.push(i < 8 ? [size - 1 - i, 8] : [8, size - 15 + i]);
101
+ }
102
+ return [a, b];
103
+ }
104
+
105
+ function versionSpots(size) {
106
+ const a = [];
107
+ const b = [];
108
+ for (let i = 0; i < 18; i++) {
109
+ const p = size - 11 + (i % 3);
110
+ const q = Math.floor(i / 3);
111
+ a.push([p, q]);
112
+ b.push([q, p]);
113
+ }
114
+ return [a, b];
115
+ }
116
+
117
+ const layouts = new Map();
118
+
119
+ function layout(ver) {
120
+ if (layouts.has(ver)) return layouts.get(ver);
121
+ const size = ver * 4 + 17;
122
+ const mod = new Uint8Array(size * size);
123
+ const fn = new Uint8Array(size * size);
124
+ const set = (x, y, dark) => {
125
+ mod[y * size + x] = dark ? 1 : 0;
126
+ fn[y * size + x] = 1;
127
+ };
128
+ for (let i = 0; i < size; i++) {
129
+ set(6, i, i % 2 === 0);
130
+ set(i, 6, i % 2 === 0);
131
+ }
132
+ for (const [cx, cy] of [[3, 3], [size - 4, 3], [3, size - 4]]) {
133
+ for (let dy = -4; dy <= 4; dy++) {
134
+ for (let dx = -4; dx <= 4; dx++) {
135
+ const x = cx + dx;
136
+ const y = cy + dy;
137
+ if (x < 0 || y < 0 || x >= size || y >= size) continue;
138
+ const d = Math.max(Math.abs(dx), Math.abs(dy));
139
+ set(x, y, d !== 2 && d !== 4);
140
+ }
141
+ }
142
+ }
143
+ const pos = alignments(ver);
144
+ const n = pos.length;
145
+ for (let i = 0; i < n; i++) {
146
+ for (let j = 0; j < n; j++) {
147
+ if ((i === 0 && j === 0) || (i === 0 && j === n - 1) || (i === n - 1 && j === 0)) continue;
148
+ for (let dy = -2; dy <= 2; dy++) {
149
+ for (let dx = -2; dx <= 2; dx++) set(pos[i] + dx, pos[j] + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
150
+ }
151
+ }
152
+ }
153
+ const [fa, fb] = formatSpots(size);
154
+ for (const [x, y] of fa.concat(fb)) set(x, y, false);
155
+ set(8, size - 8, true);
156
+ if (ver >= 7) {
157
+ const bits = versionBits(ver);
158
+ const [va, vb] = versionSpots(size);
159
+ for (let i = 0; i < 18; i++) {
160
+ const dark = (bits >>> i) & 1;
161
+ set(va[i][0], va[i][1], dark);
162
+ set(vb[i][0], vb[i][1], dark);
163
+ }
164
+ }
165
+ const order = [];
166
+ for (let right = size - 1; right >= 1; right -= 2) {
167
+ if (right === 6) right = 5;
168
+ const up = ((right + 1) & 2) === 0;
169
+ for (let v = 0; v < size; v++) {
170
+ const y = up ? size - 1 - v : v;
171
+ for (let j = 0; j < 2; j++) {
172
+ const k = y * size + right - j;
173
+ if (!fn[k]) order.push(k);
174
+ }
175
+ }
176
+ }
177
+ const result = { ver, size, mod, fn, order };
178
+ layouts.set(ver, result);
179
+ return result;
180
+ }
181
+
182
+ function divisor(degree) {
183
+ const out = new Uint8Array(degree);
184
+ out[degree - 1] = 1;
185
+ let root = 1;
186
+ for (let i = 0; i < degree; i++) {
187
+ for (let j = 0; j < degree; j++) {
188
+ out[j] = gmul(out[j], root);
189
+ if (j + 1 < degree) out[j] ^= out[j + 1];
190
+ }
191
+ root = gmul(root, 2);
192
+ }
193
+ return out;
194
+ }
195
+
196
+ function remainder(data, div) {
197
+ const out = new Uint8Array(div.length);
198
+ for (const byte of data) {
199
+ const factor = byte ^ out[0];
200
+ out.copyWithin(0, 1);
201
+ out[out.length - 1] = 0;
202
+ for (let i = 0; i < div.length; i++) out[i] ^= gmul(div[i], factor);
203
+ }
204
+ return out;
205
+ }
206
+
207
+ function blockPlan(ver, ecl) {
208
+ const count = BLOCKS[ecl][ver];
209
+ const ec = ECC[ecl][ver];
210
+ const raw = Math.floor(rawModules(ver) / 8);
211
+ return { count, ec, shortCount: count - (raw % count), shortLen: Math.floor(raw / count) };
212
+ }
213
+
214
+ function interleave(data, ver, ecl) {
215
+ const { count, ec, shortCount, shortLen } = blockPlan(ver, ecl);
216
+ const div = divisor(ec);
217
+ const blocks = [];
218
+ let k = 0;
219
+ for (let i = 0; i < count; i++) {
220
+ const len = shortLen - ec + (i < shortCount ? 0 : 1);
221
+ const dat = data.slice(k, k + len);
222
+ k += len;
223
+ blocks.push({ dat, ecc: remainder(dat, div) });
224
+ }
225
+ const out = [];
226
+ for (let i = 0; i <= shortLen - ec; i++) {
227
+ for (const b of blocks) if (i < b.dat.length) out.push(b.dat[i]);
228
+ }
229
+ for (let i = 0; i < ec; i++) {
230
+ for (const b of blocks) out.push(b.ecc[i]);
231
+ }
232
+ return out;
233
+ }
234
+
235
+ function segment(text) {
236
+ const bits = [];
237
+ const put = (v, n) => {
238
+ for (let i = n - 1; i >= 0; i--) bits.push((v >>> i) & 1);
239
+ };
240
+ if (/^[0-9]*$/.test(text)) {
241
+ for (let i = 0; i < text.length; i += 3) {
242
+ const part = text.slice(i, i + 3);
243
+ put(parseInt(part, 10), part.length * 3 + 1);
244
+ }
245
+ return { mode: 1, count: text.length, bits };
246
+ }
247
+ if (/^[0-9A-Z $%*+\-./:]*$/.test(text)) {
248
+ for (let i = 0; i + 1 < text.length; i += 2) put(ALNUM.indexOf(text[i]) * 45 + ALNUM.indexOf(text[i + 1]), 11);
249
+ if (text.length % 2) put(ALNUM.indexOf(text[text.length - 1]), 6);
250
+ return { mode: 2, count: text.length, bits };
251
+ }
252
+ const bytes = new TextEncoder().encode(text);
253
+ for (const b of bytes) put(b, 8);
254
+ return { mode: 4, count: bytes.length, bits };
255
+ }
256
+
257
+ export function capacity(mode, ecl) {
258
+ const e = levelIndex(ecl);
259
+ const bits = dataBytes(40, e) * 8 - 4 - countBits(mode, 40);
260
+ if (mode === 1) return Math.floor(bits / 10) * 3 + (bits % 10 >= 7 ? 2 : bits % 10 >= 4 ? 1 : 0);
261
+ if (mode === 2) return Math.floor(bits / 11) * 2 + (bits % 11 >= 6 ? 1 : 0);
262
+ return Math.floor(bits / 8);
263
+ }
264
+
265
+ function penalty(m, size) {
266
+ let score = 0;
267
+ for (let pass = 0; pass < 2; pass++) {
268
+ for (let a = 0; a < size; a++) {
269
+ let run = 1;
270
+ const line = new Uint8Array(size);
271
+ for (let b = 0; b < size; b++) line[b] = pass ? m[b * size + a] : m[a * size + b];
272
+ for (let b = 1; b < size; b++) {
273
+ if (line[b] === line[b - 1]) {
274
+ run++;
275
+ if (run === 5) score += 3;
276
+ else if (run > 5) score++;
277
+ } else {
278
+ run = 1;
279
+ }
280
+ }
281
+ for (let b = 0; b + 10 < size; b++) {
282
+ let ahead = true;
283
+ let behind = true;
284
+ for (let i = 0; i < 11 && (ahead || behind); i++) {
285
+ if (line[b + i] !== FINDER_LIKE[i]) ahead = false;
286
+ if (line[b + i] !== FINDER_LIKE[10 - i]) behind = false;
287
+ }
288
+ if (ahead) score += 40;
289
+ if (behind) score += 40;
290
+ }
291
+ }
292
+ }
293
+ let dark = 0;
294
+ for (let y = 0; y < size; y++) {
295
+ for (let x = 0; x < size; x++) {
296
+ const c = m[y * size + x];
297
+ dark += c;
298
+ if (y + 1 < size && x + 1 < size && c === m[y * size + x + 1] && c === m[(y + 1) * size + x] && c === m[(y + 1) * size + x + 1]) score += 3;
299
+ }
300
+ }
301
+ const total = size * size;
302
+ score += Math.max(0, Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1) * 10;
303
+ return score;
304
+ }
305
+
306
+ export function encode(text, eclIn) {
307
+ const ecl = levelIndex(eclIn);
308
+ const seg = segment(String(text));
309
+ let ver = 1;
310
+ for (; ver <= 40; ver++) {
311
+ const cb = countBits(seg.mode, ver);
312
+ if (seg.count < 2 ** cb && 4 + cb + seg.bits.length <= dataBytes(ver, ecl) * 8) break;
313
+ }
314
+ if (ver > 40) {
315
+ const error = new Error("Too long for one QR code");
316
+ error.kind = "long";
317
+ error.count = seg.count;
318
+ error.max = capacity(seg.mode, ecl);
319
+ error.bytes = seg.mode === 4;
320
+ throw error;
321
+ }
322
+ const cap = dataBytes(ver, ecl) * 8;
323
+ const bits = [];
324
+ const put = (v, n) => {
325
+ for (let i = n - 1; i >= 0; i--) bits.push((v >>> i) & 1);
326
+ };
327
+ put(seg.mode, 4);
328
+ put(seg.count, countBits(seg.mode, ver));
329
+ for (const b of seg.bits) bits.push(b);
330
+ put(0, Math.min(4, cap - bits.length));
331
+ put(0, (8 - (bits.length % 8)) % 8);
332
+ for (let pad = 0xec; bits.length < cap; pad ^= 0xec ^ 0x11) put(pad, 8);
333
+ const data = new Uint8Array(cap / 8);
334
+ bits.forEach((b, i) => {
335
+ data[i >>> 3] |= b << (7 - (i & 7));
336
+ });
337
+ const codes = interleave(data, ver, ecl);
338
+ const L = layout(ver);
339
+ const base = L.mod.slice();
340
+ L.order.forEach((k, i) => {
341
+ base[k] = i < codes.length * 8 ? (codes[i >>> 3] >>> (7 - (i & 7))) & 1 : 0;
342
+ });
343
+ const [fa, fb] = formatSpots(L.size);
344
+ let best = null;
345
+ let bestScore = Infinity;
346
+ for (let mask = 0; mask < 8; mask++) {
347
+ const mod = base.slice();
348
+ const test = MASKS[mask];
349
+ for (const k of L.order) if (test(k % L.size, Math.floor(k / L.size))) mod[k] ^= 1;
350
+ const fbits = formatBits(ecl, mask);
351
+ for (let i = 0; i < 15; i++) {
352
+ const v = (fbits >>> i) & 1;
353
+ mod[fa[i][1] * L.size + fa[i][0]] = v;
354
+ mod[fb[i][1] * L.size + fb[i][0]] = v;
355
+ }
356
+ const score = penalty(mod, L.size);
357
+ if (score < bestScore) {
358
+ bestScore = score;
359
+ best = { mod, mask };
360
+ }
361
+ }
362
+ return { ver, size: L.size, modules: best.mod, mask: best.mask, ecl, level: LEVELS[ecl] };
363
+ }
364
+
365
+ function correct(block, ec) {
366
+ const n = block.length;
367
+ const synd = new Uint8Array(ec);
368
+ let bad = false;
369
+ for (let i = 0; i < ec; i++) {
370
+ let s = 0;
371
+ const a = EXP[i];
372
+ for (let k = 0; k < n; k++) s = gmul(s, a) ^ block[k];
373
+ synd[i] = s;
374
+ if (s) bad = true;
375
+ }
376
+ if (!bad) return true;
377
+ let C = [1];
378
+ let B = [1];
379
+ let L = 0;
380
+ let m = 1;
381
+ let b = 1;
382
+ for (let k = 0; k < ec; k++) {
383
+ let d = synd[k];
384
+ for (let i = 1; i <= L; i++) d ^= gmul(C[i] || 0, synd[k - i]);
385
+ if (d === 0) {
386
+ m++;
387
+ continue;
388
+ }
389
+ const coef = gdiv(d, b);
390
+ const T = C.slice();
391
+ while (C.length < B.length + m) C.push(0);
392
+ for (let i = 0; i < B.length; i++) C[i + m] ^= gmul(coef, B[i]);
393
+ if (2 * L <= k) {
394
+ L = k + 1 - L;
395
+ B = T;
396
+ b = d;
397
+ m = 1;
398
+ } else {
399
+ m++;
400
+ }
401
+ }
402
+ C = C.slice(0, L + 1);
403
+ if (2 * L > ec) return false;
404
+ const spots = [];
405
+ for (let k = 0; k < n; k++) {
406
+ const xinv = EXP[(255 - ((n - 1 - k) % 255)) % 255];
407
+ let v = 0;
408
+ for (let i = C.length - 1; i >= 0; i--) v = gmul(v, xinv) ^ C[i];
409
+ if (v === 0) spots.push(k);
410
+ }
411
+ if (spots.length !== L) return false;
412
+ const omega = new Uint8Array(ec);
413
+ for (let i = 0; i < ec; i++) {
414
+ for (let j = 0; j < C.length && i + j < ec; j++) omega[i + j] ^= gmul(synd[i], C[j]);
415
+ }
416
+ for (const k of spots) {
417
+ const p = (n - 1 - k) % 255;
418
+ const X = EXP[p];
419
+ const xinv = EXP[(255 - p) % 255];
420
+ let num = 0;
421
+ for (let i = ec - 1; i >= 0; i--) num = gmul(num, xinv) ^ omega[i];
422
+ let den = 0;
423
+ for (let i = 1; i < C.length; i += 2) den ^= gmul(C[i], i === 1 ? 1 : EXP[(LOG[xinv] * (i - 1)) % 255]);
424
+ if (!den) return false;
425
+ block[k] ^= gmul(X, gdiv(num, den));
426
+ }
427
+ for (let i = 0; i < ec; i++) {
428
+ let s = 0;
429
+ const a = EXP[i];
430
+ for (let k = 0; k < n; k++) s = gmul(s, a) ^ block[k];
431
+ if (s) return false;
432
+ }
433
+ return true;
434
+ }
435
+
436
+ function decodeBytes(bytes, charset) {
437
+ if (charset) {
438
+ try {
439
+ return new TextDecoder(charset).decode(bytes);
440
+ } catch {
441
+ charset = "";
442
+ }
443
+ }
444
+ try {
445
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
446
+ } catch {
447
+ return new TextDecoder("windows-1252").decode(bytes);
448
+ }
449
+ }
450
+
451
+ function parse(bytes, ver) {
452
+ const total = bytes.length * 8;
453
+ let pos = 0;
454
+ const read = (n) => {
455
+ if (pos + n > total) throw new RangeError("end");
456
+ let v = 0;
457
+ for (let i = 0; i < n; i++, pos++) v = (v << 1) | ((bytes[pos >>> 3] >>> (7 - (pos & 7))) & 1);
458
+ return v;
459
+ };
460
+ let text = "";
461
+ let charset = "";
462
+ try {
463
+ while (pos + 4 <= total) {
464
+ const mode = read(4);
465
+ if (mode === 0) break;
466
+ if (mode === 7) {
467
+ let v = read(8);
468
+ if ((v & 0xc0) === 0x80) v = ((v & 0x3f) << 8) | read(8);
469
+ else if ((v & 0xe0) === 0xc0) v = ((v & 0x1f) << 16) | read(16);
470
+ charset = CHARSETS[v] || "";
471
+ continue;
472
+ }
473
+ if (mode === 3) {
474
+ read(16);
475
+ continue;
476
+ }
477
+ if (mode === 5) continue;
478
+ if (mode === 9) {
479
+ read(8);
480
+ continue;
481
+ }
482
+ if (mode !== 1 && mode !== 2 && mode !== 4 && mode !== 8) return null;
483
+ let n = read(countBits(mode, ver));
484
+ if (mode === 1) {
485
+ while (n >= 3) {
486
+ const v = read(10);
487
+ if (v > 999) return null;
488
+ text += String(v).padStart(3, "0");
489
+ n -= 3;
490
+ }
491
+ if (n === 2) {
492
+ const v = read(7);
493
+ if (v > 99) return null;
494
+ text += String(v).padStart(2, "0");
495
+ } else if (n === 1) {
496
+ const v = read(4);
497
+ if (v > 9) return null;
498
+ text += String(v);
499
+ }
500
+ } else if (mode === 2) {
501
+ while (n >= 2) {
502
+ const v = read(11);
503
+ if (v >= 45 * 45) return null;
504
+ text += ALNUM[Math.floor(v / 45)] + ALNUM[v % 45];
505
+ n -= 2;
506
+ }
507
+ if (n === 1) {
508
+ const v = read(6);
509
+ if (v >= 45) return null;
510
+ text += ALNUM[v];
511
+ }
512
+ } else if (mode === 4) {
513
+ const out = new Uint8Array(n);
514
+ for (let i = 0; i < n; i++) out[i] = read(8);
515
+ text += decodeBytes(out, charset);
516
+ } else {
517
+ const out = new Uint8Array(n * 2);
518
+ for (let i = 0; i < n; i++) {
519
+ const v = read(13);
520
+ let code = (Math.floor(v / 0xc0) << 8) | (v % 0xc0);
521
+ code += code < 0x1f00 ? 0x8140 : 0xc140;
522
+ out[i * 2] = code >> 8;
523
+ out[i * 2 + 1] = code & 255;
524
+ }
525
+ text += decodeBytes(out, "shift_jis");
526
+ }
527
+ }
528
+ } catch {
529
+ return text || null;
530
+ }
531
+ return text;
532
+ }
533
+
534
+ function bitCount(v) {
535
+ let c = 0;
536
+ for (; v; v &= v - 1) c++;
537
+ return c;
538
+ }
539
+
540
+ function readGrid(grid, d) {
541
+ const ver = (d - 17) / 4;
542
+ const [fa, fb] = formatSpots(d);
543
+ const read = (spots) => spots.reduce((v, [x, y], i) => v | (grid[y * d + x] << i), 0);
544
+ const f1 = read(fa);
545
+ const f2 = read(fb);
546
+ let ecl = -1;
547
+ let mask = -1;
548
+ let low = 99;
549
+ for (let e = 0; e < 4; e++) {
550
+ for (let k = 0; k < 8; k++) {
551
+ const code = formatBits(e, k);
552
+ const dist = Math.min(bitCount(code ^ f1), bitCount(code ^ f2));
553
+ if (dist < low) {
554
+ low = dist;
555
+ ecl = e;
556
+ mask = k;
557
+ }
558
+ }
559
+ }
560
+ if (low > 3) return null;
561
+ if (ver >= 7) {
562
+ const [va, vb] = versionSpots(d);
563
+ const v1 = read(va);
564
+ const v2 = read(vb);
565
+ let found = 0;
566
+ let near = 99;
567
+ for (let v = 7; v <= 40; v++) {
568
+ const code = versionBits(v);
569
+ const dist = Math.min(bitCount(code ^ v1), bitCount(code ^ v2));
570
+ if (dist < near) {
571
+ near = dist;
572
+ found = v;
573
+ }
574
+ }
575
+ if (near <= 3 && found !== ver) return { retry: found };
576
+ }
577
+ const L = layout(ver);
578
+ const test = MASKS[mask];
579
+ const raw = Math.floor(rawModules(ver) / 8);
580
+ const codes = new Uint8Array(raw);
581
+ for (let i = 0; i < raw * 8; i++) {
582
+ const k = L.order[i];
583
+ const bit = grid[k] ^ (test(k % d, Math.floor(k / d)) ? 1 : 0);
584
+ codes[i >>> 3] |= bit << (7 - (i & 7));
585
+ }
586
+ const { count, ec, shortCount, shortLen } = blockPlan(ver, ecl);
587
+ const blocks = [];
588
+ for (let i = 0; i < count; i++) blocks.push({ len: shortLen - ec + (i < shortCount ? 0 : 1), bytes: [] });
589
+ let k = 0;
590
+ for (let i = 0; i <= shortLen - ec; i++) {
591
+ for (const b of blocks) if (i < b.len) b.bytes.push(codes[k++]);
592
+ }
593
+ for (let i = 0; i < ec; i++) {
594
+ for (const b of blocks) b.bytes.push(codes[k++]);
595
+ }
596
+ const data = [];
597
+ for (const b of blocks) {
598
+ const arr = Uint8Array.from(b.bytes);
599
+ if (!correct(arr, ec)) return null;
600
+ for (let i = 0; i < b.len; i++) data.push(arr[i]);
601
+ }
602
+ const text = parse(Uint8Array.from(data), ver);
603
+ return text === null ? null : { text, ver, ecl: LEVELS[ecl] };
604
+ }
605
+
606
+ export function toGray(image) {
607
+ const { data, width, height } = image;
608
+ const gray = new Uint8Array(width * height);
609
+ for (let i = 0, j = 0; i < gray.length; i++, j += 4) {
610
+ let v = (data[j] * 77 + data[j + 1] * 150 + data[j + 2] * 29) >> 8;
611
+ const a = data[j + 3];
612
+ if (a < 255) v = 255 - Math.round(((255 - v) * a) / 255);
613
+ gray[i] = v;
614
+ }
615
+ return gray;
616
+ }
617
+
618
+ export function otsu(gray) {
619
+ const hist = new Uint32Array(256);
620
+ for (const v of gray) hist[v]++;
621
+ const total = gray.length;
622
+ let sum = 0;
623
+ for (let i = 0; i < 256; i++) sum += i * hist[i];
624
+ let sumB = 0;
625
+ let wB = 0;
626
+ let best = 0;
627
+ let level = 128;
628
+ for (let i = 0; i < 256; i++) {
629
+ wB += hist[i];
630
+ if (!wB) continue;
631
+ const wF = total - wB;
632
+ if (!wF) break;
633
+ sumB += i * hist[i];
634
+ const mB = sumB / wB;
635
+ const mF = (sum - sumB) / wF;
636
+ const between = wB * wF * (mB - mF) * (mB - mF);
637
+ if (between > best) {
638
+ best = between;
639
+ level = i;
640
+ }
641
+ }
642
+ return level;
643
+ }
644
+
645
+ function binarizeLocal(gray, w, h, global) {
646
+ const S = 8;
647
+ const bw = Math.ceil(w / S);
648
+ const bh = Math.ceil(h / S);
649
+ const lo = new Uint8Array(bw * bh);
650
+ const hi = new Uint8Array(bw * bh);
651
+ for (let by = 0; by < bh; by++) {
652
+ for (let bx = 0; bx < bw; bx++) {
653
+ let mn = 255;
654
+ let mx = 0;
655
+ const y1 = Math.min(h, by * S + S);
656
+ const x1 = Math.min(w, bx * S + S);
657
+ for (let y = by * S; y < y1; y++) {
658
+ for (let x = bx * S; x < x1; x++) {
659
+ const v = gray[y * w + x];
660
+ if (v < mn) mn = v;
661
+ if (v > mx) mx = v;
662
+ }
663
+ }
664
+ lo[by * bw + bx] = mn;
665
+ hi[by * bw + bx] = mx;
666
+ }
667
+ }
668
+ const out = new Uint8Array(w * h);
669
+ for (let by = 0; by < bh; by++) {
670
+ for (let bx = 0; bx < bw; bx++) {
671
+ let mn = 255;
672
+ let mx = 0;
673
+ for (let y = Math.max(0, by - 2); y <= Math.min(bh - 1, by + 2); y++) {
674
+ for (let x = Math.max(0, bx - 2); x <= Math.min(bw - 1, bx + 2); x++) {
675
+ if (lo[y * bw + x] < mn) mn = lo[y * bw + x];
676
+ if (hi[y * bw + x] > mx) mx = hi[y * bw + x];
677
+ }
678
+ }
679
+ const t = mx - mn >= 40 ? (mn + mx) >> 1 : global;
680
+ const y1 = Math.min(h, by * S + S);
681
+ const x1 = Math.min(w, bx * S + S);
682
+ for (let y = by * S; y < y1; y++) {
683
+ for (let x = bx * S; x < x1; x++) out[y * w + x] = gray[y * w + x] <= t ? 1 : 0;
684
+ }
685
+ }
686
+ }
687
+ return out;
688
+ }
689
+
690
+ function finderRatio(c) {
691
+ const total = c[0] + c[1] + c[2] + c[3] + c[4];
692
+ if (total < 7) return false;
693
+ const ms = total / 7;
694
+ const v = ms / 2;
695
+ return Math.abs(ms - c[0]) < v && Math.abs(ms - c[1]) < v && Math.abs(3 * ms - c[2]) < 3 * v && Math.abs(ms - c[3]) < v && Math.abs(ms - c[4]) < v;
696
+ }
697
+
698
+ function cross(img, x, y, dx, dy, ref) {
699
+ const { bits, w, h } = img;
700
+ const at = (i) => {
701
+ const px = x + i * dx;
702
+ const py = y + i * dy;
703
+ return px < 0 || py < 0 || px >= w || py >= h ? -1 : bits[py * w + px];
704
+ };
705
+ if (at(0) !== 1) return null;
706
+ const c = [0, 0, 0, 0, 0];
707
+ let i = 0;
708
+ while (at(-i) === 1) {
709
+ c[2]++;
710
+ i++;
711
+ }
712
+ const back = i;
713
+ while (at(-i) === 0) {
714
+ c[1]++;
715
+ i++;
716
+ }
717
+ if (at(-i) !== 1) return null;
718
+ while (at(-i) === 1) {
719
+ c[0]++;
720
+ i++;
721
+ }
722
+ let j = 1;
723
+ while (at(j) === 1) {
724
+ c[2]++;
725
+ j++;
726
+ }
727
+ const fwd = j - 1;
728
+ while (at(j) === 0) {
729
+ c[3]++;
730
+ j++;
731
+ }
732
+ if (at(j) !== 1) return null;
733
+ while (at(j) === 1) {
734
+ c[4]++;
735
+ j++;
736
+ }
737
+ if (!finderRatio(c)) return null;
738
+ const total = c[0] + c[1] + c[2] + c[3] + c[4];
739
+ if (5 * Math.abs(total - ref) >= 2 * ref) return null;
740
+ return { at: (dx ? x : y) - back + 1 + (back + fwd) / 2, total };
741
+ }
742
+
743
+ function findFinders(img) {
744
+ const { bits, w, h } = img;
745
+ const found = [];
746
+ const step = h > 900 ? 2 : 1;
747
+ const starts = new Int32Array(w + 1);
748
+ const lens = new Int32Array(w + 1);
749
+ const cols = new Uint8Array(w + 1);
750
+ for (let y = 0; y < h; y += step) {
751
+ let n = 0;
752
+ const row = y * w;
753
+ for (let x = 0; x < w; x++) {
754
+ const c = bits[row + x];
755
+ if (n && cols[n - 1] === c) lens[n - 1]++;
756
+ else {
757
+ starts[n] = x;
758
+ lens[n] = 1;
759
+ cols[n] = c;
760
+ n++;
761
+ }
762
+ }
763
+ for (let i = 0; i + 4 < n; i++) {
764
+ if (!cols[i]) continue;
765
+ const c = [lens[i], lens[i + 1], lens[i + 2], lens[i + 3], lens[i + 4]];
766
+ if (!finderRatio(c)) continue;
767
+ const total = c[0] + c[1] + c[2] + c[3] + c[4];
768
+ const cx = Math.floor(starts[i + 2] + lens[i + 2] / 2);
769
+ const v = cross(img, cx, y, 0, 1, total);
770
+ if (!v) continue;
771
+ const hz = cross(img, cx, Math.floor(v.at), 1, 0, total);
772
+ if (!hz) continue;
773
+ const v2 = cross(img, Math.floor(hz.at), Math.floor(v.at), 0, 1, total);
774
+ if (!v2) continue;
775
+ const fx = hz.at;
776
+ const fy = v2.at;
777
+ const ms = (hz.total + v2.total) / 14;
778
+ const near = found.find((f) => Math.abs(f.x - fx) <= ms && Math.abs(f.y - fy) <= ms && Math.abs(f.ms - ms) <= Math.max(1, f.ms * 0.5));
779
+ if (near) {
780
+ near.x = (near.x * near.n + fx) / (near.n + 1);
781
+ near.y = (near.y * near.n + fy) / (near.n + 1);
782
+ near.ms = (near.ms * near.n + ms) / (near.n + 1);
783
+ near.n++;
784
+ } else {
785
+ found.push({ x: fx, y: fy, ms, n: 1 });
786
+ }
787
+ }
788
+ }
789
+ return found;
790
+ }
791
+
792
+ function edgeRun(img, x, y, ux, uy) {
793
+ const { bits, w, h } = img;
794
+ let state = 0;
795
+ for (let t = 0; t < 4000; t++) {
796
+ const px = Math.floor(x + ux * t);
797
+ const py = Math.floor(y + uy * t);
798
+ if (px < 0 || py < 0 || px >= w || py >= h) return state === 2 ? t : NaN;
799
+ const c = bits[py * w + px];
800
+ if (state === 0 && !c) state = 1;
801
+ else if (state === 1 && c) state = 2;
802
+ else if (state === 2 && !c) return t;
803
+ }
804
+ return NaN;
805
+ }
806
+
807
+ function moduleAlong(img, a, b) {
808
+ const len = Math.hypot(b.x - a.x, b.y - a.y);
809
+ const ux = (b.x - a.x) / len;
810
+ const uy = (b.y - a.y) / len;
811
+ return (edgeRun(img, a.x, a.y, ux, uy) + edgeRun(img, a.x, a.y, -ux, -uy)) / 7;
812
+ }
813
+
814
+ function averageOf(list, fallback) {
815
+ const good = list.filter(Number.isFinite);
816
+ return good.length ? good.reduce((s, v) => s + v, 0) / good.length : fallback;
817
+ }
818
+
819
+ function crossAlign(img, x, y, dx, dy, ms) {
820
+ const { bits, w, h } = img;
821
+ const at = (i) => {
822
+ const px = x + i * dx;
823
+ const py = y + i * dy;
824
+ return px < 0 || py < 0 || px >= w || py >= h ? -1 : bits[py * w + px];
825
+ };
826
+ if (at(0) !== 1) return null;
827
+ const near = (n) => Math.abs(n - ms) < ms * 0.7;
828
+ let mid = 0;
829
+ let i = 0;
830
+ while (at(-i) === 1) {
831
+ mid++;
832
+ i++;
833
+ }
834
+ const back = i;
835
+ let left = 0;
836
+ while (at(-i) === 0) {
837
+ left++;
838
+ i++;
839
+ }
840
+ if (at(-i) !== 1) return null;
841
+ let j = 1;
842
+ while (at(j) === 1) {
843
+ mid++;
844
+ j++;
845
+ }
846
+ const fwd = j - 1;
847
+ let right = 0;
848
+ while (at(j) === 0) {
849
+ right++;
850
+ j++;
851
+ }
852
+ if (at(j) !== 1) return null;
853
+ if (!near(mid) || !near(left) || !near(right)) return null;
854
+ return (dx ? x : y) - back + 1 + (back + fwd) / 2;
855
+ }
856
+
857
+ function findAlign(img, ex, ey, ms) {
858
+ const { bits, w, h } = img;
859
+ for (const f of [4, 8, 14]) {
860
+ const r = Math.ceil(f * ms);
861
+ const x0 = Math.max(0, Math.floor(ex - r));
862
+ const x1 = Math.min(w - 1, Math.ceil(ex + r));
863
+ const y0 = Math.max(0, Math.floor(ey - r));
864
+ const y1 = Math.min(h - 1, Math.ceil(ey + r));
865
+ if (x1 - x0 < 3 * ms || y1 - y0 < 3 * ms) continue;
866
+ let best = null;
867
+ let bestD = Infinity;
868
+ for (let y = y0; y <= y1; y++) {
869
+ let start = x0;
870
+ const runs = [];
871
+ for (let x = x0 + 1; x <= x1 + 1; x++) {
872
+ if (x > x1 || bits[y * w + x] !== bits[y * w + x - 1]) {
873
+ runs.push([start, x - start, bits[y * w + start]]);
874
+ start = x;
875
+ }
876
+ }
877
+ for (let i = 1; i + 2 < runs.length; i++) {
878
+ if (runs[i][2] !== 0) continue;
879
+ const near = (n) => Math.abs(n - ms) < ms * 0.7;
880
+ if (!near(runs[i][1]) || !near(runs[i + 1][1]) || !near(runs[i + 2][1])) continue;
881
+ const cx = Math.floor(runs[i + 1][0] + runs[i + 1][1] / 2);
882
+ const cy = crossAlign(img, cx, y, 0, 1, ms);
883
+ if (cy === null) continue;
884
+ const cx2 = crossAlign(img, cx, Math.floor(cy), 1, 0, ms);
885
+ if (cx2 === null) continue;
886
+ const dist = Math.hypot(cx2 - ex, cy - ey);
887
+ if (dist < bestD) {
888
+ bestD = dist;
889
+ best = { x: cx2, y: cy };
890
+ }
891
+ }
892
+ }
893
+ if (best) return best;
894
+ }
895
+ return null;
896
+ }
897
+
898
+ function squareToQuad(p) {
899
+ const [x0, y0, x1, y1, x2, y2, x3, y3] = p;
900
+ const dx3 = x0 - x1 + x2 - x3;
901
+ const dy3 = y0 - y1 + y2 - y3;
902
+ if (Math.abs(dx3) < 1e-9 && Math.abs(dy3) < 1e-9) return [x1 - x0, x3 - x0, x0, y1 - y0, y3 - y0, y0, 0, 0, 1];
903
+ const dx1 = x1 - x2;
904
+ const dx2 = x3 - x2;
905
+ const dy1 = y1 - y2;
906
+ const dy2 = y3 - y2;
907
+ const den = dx1 * dy2 - dx2 * dy1;
908
+ const g = (dx3 * dy2 - dx2 * dy3) / den;
909
+ const k = (dx1 * dy3 - dx3 * dy1) / den;
910
+ return [x1 - x0 + g * x1, x3 - x0 + k * x3, x0, y1 - y0 + g * y1, y3 - y0 + k * y3, y0, g, k, 1];
911
+ }
912
+
913
+ function adjugate(m) {
914
+ const [a, b, c, d, e, f, g, h, i] = m;
915
+ return [e * i - f * h, c * h - b * i, b * f - c * e, f * g - d * i, a * i - c * g, c * d - a * f, d * h - e * g, b * g - a * h, a * e - b * d];
916
+ }
917
+
918
+ function times(p, q) {
919
+ const out = new Array(9).fill(0);
920
+ for (let r = 0; r < 3; r++) {
921
+ for (let c = 0; c < 3; c++) {
922
+ for (let k = 0; k < 3; k++) out[r * 3 + c] += p[r * 3 + k] * q[k * 3 + c];
923
+ }
924
+ }
925
+ return out;
926
+ }
927
+
928
+ function sample(img, H, d) {
929
+ const { bits, w, h } = img;
930
+ const grid = new Uint8Array(d * d);
931
+ for (let y = 0; y < d; y++) {
932
+ for (let x = 0; x < d; x++) {
933
+ const u = x + 0.5;
934
+ const v = y + 0.5;
935
+ const z = H[6] * u + H[7] * v + H[8];
936
+ const px = Math.floor((H[0] * u + H[1] * v + H[2]) / z);
937
+ const py = Math.floor((H[3] * u + H[4] * v + H[5]) / z);
938
+ grid[y * d + x] = px >= 0 && py >= 0 && px < w && py < h ? bits[py * w + px] : 0;
939
+ }
940
+ }
941
+ return grid;
942
+ }
943
+
944
+ function atDim(img, tl, tr, bl, ms, d) {
945
+ const ver = (d - 17) / 4;
946
+ const bx = tr.x + bl.x - tl.x;
947
+ const by = tr.y + bl.y - tl.y;
948
+ const corners = [];
949
+ if (ver >= 2) {
950
+ const k = 1 - 3 / (d - 7);
951
+ const al = findAlign(img, tl.x + k * (bx - tl.x), tl.y + k * (by - tl.y), ms);
952
+ if (al) corners.push([al.x, al.y, d - 6.5]);
953
+ }
954
+ corners.push([bx, by, d - 3.5]);
955
+ for (const [qx, qy, qm] of corners) {
956
+ const from = squareToQuad([3.5, 3.5, d - 3.5, 3.5, qm, qm, 3.5, d - 3.5]);
957
+ const to = squareToQuad([tl.x, tl.y, tr.x, tr.y, qx, qy, bl.x, bl.y]);
958
+ const H = times(to, adjugate(from));
959
+ const result = readGrid(sample(img, H, d), d);
960
+ if (result) return result;
961
+ }
962
+ return null;
963
+ }
964
+
965
+ function tryTriple(img, tl, tr, bl) {
966
+ const top = averageOf([moduleAlong(img, tl, tr), moduleAlong(img, tr, tl)], (tl.ms + tr.ms) / 2);
967
+ const side = averageOf([moduleAlong(img, tl, bl), moduleAlong(img, bl, tl)], (tl.ms + bl.ms) / 2);
968
+ const ms = (top + side) / 2;
969
+ const est = Math.round((Math.hypot(tr.x - tl.x, tr.y - tl.y) / top + Math.hypot(bl.x - tl.x, bl.y - tl.y) / side) / 2) + 7;
970
+ const r = est & 3;
971
+ const dims = r === 1 ? [est, est + 4, est - 4] : r === 0 ? [est + 1, est - 3] : r === 2 ? [est - 1, est + 3] : [est - 2, est + 2];
972
+ const seen = new Set();
973
+ for (const first of dims) {
974
+ let d = first;
975
+ for (let hop = 0; hop < 2; hop++) {
976
+ if (d < 21 || d > 177 || seen.has(d)) break;
977
+ seen.add(d);
978
+ const result = atDim(img, tl, tr, bl, ms, d);
979
+ if (!result) break;
980
+ if (result.retry) {
981
+ d = result.retry * 4 + 17;
982
+ continue;
983
+ }
984
+ return result;
985
+ }
986
+ }
987
+ return null;
988
+ }
989
+
990
+ function detect(img) {
991
+ const found = findFinders(img).sort((a, b) => b.n - a.n).slice(0, 12);
992
+ if (found.length < 3) return null;
993
+ const triples = [];
994
+ for (let i = 0; i < found.length; i++) {
995
+ for (let j = i + 1; j < found.length; j++) {
996
+ for (let k = j + 1; k < found.length; k++) {
997
+ const t = [found[i], found[j], found[k]];
998
+ const sizes = t.map((f) => f.ms);
999
+ const spread = Math.max(...sizes) / Math.min(...sizes);
1000
+ if (spread > 1.7) continue;
1001
+ const mean = (sizes[0] + sizes[1] + sizes[2]) / 3;
1002
+ const dd = [[0, 1], [1, 2], [0, 2]].map(([p, q]) => Math.hypot(t[p].x - t[q].x, t[p].y - t[q].y)).sort((p, q) => p - q);
1003
+ if (dd[0] < 10 * mean) continue;
1004
+ const score = Math.abs(dd[2] * dd[2] - dd[0] * dd[0] - dd[1] * dd[1]) / (dd[2] * dd[2]) + Math.abs(dd[0] - dd[1]) / dd[1] + (spread - 1) / 2;
1005
+ if (score < 0.9) triples.push({ t, score });
1006
+ }
1007
+ }
1008
+ }
1009
+ triples.sort((a, b) => a.score - b.score);
1010
+ for (const { t } of triples.slice(0, 6)) {
1011
+ const d01 = Math.hypot(t[0].x - t[1].x, t[0].y - t[1].y);
1012
+ const d12 = Math.hypot(t[1].x - t[2].x, t[1].y - t[2].y);
1013
+ const d02 = Math.hypot(t[0].x - t[2].x, t[0].y - t[2].y);
1014
+ let a;
1015
+ let b;
1016
+ let c;
1017
+ if (d12 >= d01 && d12 >= d02) [b, a, c] = [t[0], t[1], t[2]];
1018
+ else if (d02 >= d01 && d02 >= d12) [b, a, c] = [t[1], t[0], t[2]];
1019
+ else [b, a, c] = [t[2], t[0], t[1]];
1020
+ if ((c.x - b.x) * (a.y - b.y) - (c.y - b.y) * (a.x - b.x) < 0) [a, c] = [c, a];
1021
+ const result = tryTriple(img, b, c, a);
1022
+ if (result) return result;
1023
+ }
1024
+ return null;
1025
+ }
1026
+
1027
+ export function decodeImage(image) {
1028
+ const w = image.width;
1029
+ const h = image.height;
1030
+ const gray = toGray(image);
1031
+ const level = otsu(gray);
1032
+ const local = binarizeLocal(gray, w, h, level);
1033
+ const global = new Uint8Array(gray.length);
1034
+ for (let i = 0; i < gray.length; i++) global[i] = gray[i] <= level ? 1 : 0;
1035
+ for (const bits of [local, global]) {
1036
+ const plain = detect({ bits, w, h });
1037
+ if (plain) return plain;
1038
+ const flipped = bits.map((v) => v ^ 1);
1039
+ const inverse = detect({ bits: flipped, w, h });
1040
+ if (inverse) return inverse;
1041
+ }
1042
+ return null;
1043
+ }
1044
+
1045
+ export const SHAPES = ["square", "rounded", "dots"];
1046
+ export const EYES = ["square", "rounded", "circle"];
1047
+
1048
+ const num = (v) => Number(v.toFixed(3));
1049
+
1050
+ export function logoBox(code, frac) {
1051
+ if (!frac) return null;
1052
+ const n = code.size;
1053
+ let span = Math.max(3, Math.round(n * frac));
1054
+ if ((n - span) % 2) span += 1;
1055
+ const start = (n - span) / 2;
1056
+ return { x: start, y: start, span };
1057
+ }
1058
+
1059
+ function dark(code, hole, x, y) {
1060
+ if (x < 0 || y < 0 || x >= code.size || y >= code.size) return false;
1061
+ if (hole && x >= hole.x && x < hole.x + hole.span && y >= hole.y && y < hole.y + hole.span) return false;
1062
+ return code.modules[y * code.size + x] === 1;
1063
+ }
1064
+
1065
+ function inFinder(n, x, y) {
1066
+ return (x < 7 && y < 7) || (x >= n - 7 && y < 7) || (x < 7 && y >= n - 7);
1067
+ }
1068
+
1069
+ export function separateEyes(style) {
1070
+ return (style.eye || "square") !== "square" || style.shape === "dots";
1071
+ }
1072
+
1073
+ export function roundRect(x, y, w, h, r) {
1074
+ if (!r) return `M${x} ${y}h${w}v${h}h-${w}z`;
1075
+ return `M${x + r} ${y}H${x + w - r}a${r} ${r} 0 0 1 ${r} ${r}V${y + h - r}a${r} ${r} 0 0 1 -${r} ${r}H${x + r}a${r} ${r} 0 0 1 -${r} -${r}V${y + r}a${r} ${r} 0 0 1 ${r} -${r}z`;
1076
+ }
1077
+
1078
+ export function circlePath(cx, cy, r) {
1079
+ return `M${cx - r} ${cy}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 -${2 * r} 0z`;
1080
+ }
1081
+
1082
+ export function modulePath(code, style, hole) {
1083
+ const m = style.margin || 0;
1084
+ const parts = [];
1085
+ const n = code.size;
1086
+ const sep = separateEyes(style);
1087
+ const on = (x, y) => dark(code, hole, x, y) && !(sep && inFinder(n, x, y));
1088
+ const shape = style.shape || "square";
1089
+ if (shape === "dots") {
1090
+ const r = 0.43;
1091
+ for (let y = 0; y < n; y++) {
1092
+ for (let x = 0; x < n; x++) {
1093
+ if (!on(x, y)) continue;
1094
+ const cx = x + m + 0.5;
1095
+ const cy = y + m + 0.5;
1096
+ parts.push(`M${num(cx - r)} ${cy}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 -${2 * r} 0z`);
1097
+ }
1098
+ }
1099
+ return parts.join("");
1100
+ }
1101
+ if (shape !== "rounded") {
1102
+ for (let y = 0; y < n; y++) {
1103
+ for (let x = 0; x < n; x++) {
1104
+ if (!on(x, y)) continue;
1105
+ let len = 1;
1106
+ while (on(x + len, y)) len++;
1107
+ parts.push(`M${x + m} ${y + m}h${len}v1h-${len}z`);
1108
+ x += len - 1;
1109
+ }
1110
+ }
1111
+ return parts.join("");
1112
+ }
1113
+ const r = 0.5;
1114
+ for (let y = 0; y < n; y++) {
1115
+ for (let x = 0; x < n; x++) {
1116
+ if (!on(x, y)) continue;
1117
+ const up = on(x, y - 1);
1118
+ const down = on(x, y + 1);
1119
+ const left = on(x - 1, y);
1120
+ const right = on(x + 1, y);
1121
+ const tl = !up && !left ? r : 0;
1122
+ const tr = !up && !right ? r : 0;
1123
+ const br = !down && !right ? r : 0;
1124
+ const bl = !down && !left ? r : 0;
1125
+ const X = x + m;
1126
+ const Y = y + m;
1127
+ let d = `M${X + tl} ${Y}H${X + 1 - tr}`;
1128
+ if (tr) d += `a${r} ${r} 0 0 1 ${r} ${r}`;
1129
+ d += `V${Y + 1 - br}`;
1130
+ if (br) d += `a${r} ${r} 0 0 1 -${r} ${r}`;
1131
+ d += `H${X + bl}`;
1132
+ if (bl) d += `a${r} ${r} 0 0 1 -${r} -${r}`;
1133
+ d += `V${Y + tl}`;
1134
+ if (tl) d += `a${r} ${r} 0 0 1 ${r} -${r}`;
1135
+ parts.push(`${d}z`);
1136
+ }
1137
+ }
1138
+ return parts.join("");
1139
+ }
1140
+
1141
+ export function eyePath(code, style) {
1142
+ if (!separateEyes(style)) return "";
1143
+ const n = code.size;
1144
+ const m = style.margin || 0;
1145
+ const parts = [];
1146
+ for (const [fx, fy] of [[0, 0], [n - 7, 0], [0, n - 7]]) {
1147
+ const x = fx + m;
1148
+ const y = fy + m;
1149
+ if (style.eye === "circle") parts.push(circlePath(x + 3.5, y + 3.5, 3.5), circlePath(x + 3.5, y + 3.5, 2.5), circlePath(x + 3.5, y + 3.5, 1.5));
1150
+ else if (style.eye === "rounded") parts.push(roundRect(x, y, 7, 7, 2), roundRect(x + 1, y + 1, 5, 5, 1.3), roundRect(x + 2, y + 2, 3, 3, 0.9));
1151
+ else parts.push(roundRect(x, y, 7, 7, 0), roundRect(x + 1, y + 1, 5, 5, 0), roundRect(x + 2, y + 2, 3, 3, 0));
1152
+ }
1153
+ return parts.join("");
1154
+ }
1155
+
1156
+ export function logoRect(box, margin, ratio) {
1157
+ const inner = box.span - 0.8;
1158
+ const r = ratio > 0 ? ratio : 1;
1159
+ const w = r >= 1 ? inner : inner * r;
1160
+ const h = r >= 1 ? inner / r : inner;
1161
+ return { x: num(box.x + margin + (box.span - w) / 2), y: num(box.y + margin + (box.span - h) / 2), w: num(w), h: num(h) };
1162
+ }
1163
+
1164
+ export function qrScene(code, style = {}) {
1165
+ const margin = style.margin || 0;
1166
+ const total = code.size + margin * 2;
1167
+ const logo = style.logo && style.logo.frac ? style.logo : null;
1168
+ const hole = logo ? logoBox(code, logo.frac) : null;
1169
+ const fg = style.fg || "#000000";
1170
+ const items = [];
1171
+ if (!style.clear) items.push({ type: "rect", x: 0, y: 0, w: total, h: total, fill: style.bg || "#ffffff" });
1172
+ items.push({ type: "path", d: modulePath(code, style, hole), fill: fg });
1173
+ const eyes = eyePath(code, style);
1174
+ if (eyes) items.push({ type: "path", d: eyes, fill: fg, rule: "evenodd" });
1175
+ if (hole) items.push({ type: "image", ...logoRect(hole, margin, logo.ratio), ref: "logo" });
1176
+ return { width: total, height: total, items, crisp: (style.shape || "square") === "square" && !separateEyes(style) };
1177
+ }
1178
+
1179
+ const escXml = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1180
+
1181
+ export function sceneSvg(scene, { width, height, images = {} } = {}) {
1182
+ const w = width === undefined ? scene.width : width;
1183
+ const h = height === undefined ? (typeof w === "number" ? num((w * scene.height) / scene.width) : scene.height) : height;
1184
+ const parts = [`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${num(scene.width)} ${num(scene.height)}" width="${w}" height="${h}"${scene.crisp ? ' shape-rendering="crispEdges"' : ""}>`];
1185
+ for (const it of scene.items) {
1186
+ if (it.type === "rect") parts.push(`<rect x="${num(it.x)}" y="${num(it.y)}" width="${num(it.w)}" height="${num(it.h)}" fill="${escXml(it.fill)}"/>`);
1187
+ else if (it.type === "path") parts.push(`<path fill="${escXml(it.fill)}"${it.rule === "evenodd" ? ' fill-rule="evenodd"' : ""} d="${it.d}"/>`);
1188
+ else if (it.type === "stroke") parts.push(`<path fill="none" stroke="${escXml(it.stroke)}" stroke-width="${num(it.width)}" d="${it.d}"/>`);
1189
+ else if (it.type === "text") parts.push(`<text x="${num(it.x)}" y="${num(it.y)}" fill="${escXml(it.fill)}" font-family="Helvetica, Arial, sans-serif" font-weight="${it.bold ? 700 : 400}" font-size="${num(it.size)}" text-anchor="${it.anchor === "start" ? "start" : "middle"}" dominant-baseline="central">${escXml(it.text)}</text>`);
1190
+ else if (it.type === "image" && images[it.ref]) parts.push(`<image href="${escXml(images[it.ref])}" x="${it.x}" y="${it.y}" width="${it.w}" height="${it.h}" preserveAspectRatio="xMidYMid meet"/>`);
1191
+ }
1192
+ parts.push("</svg>");
1193
+ return parts.join("") + "\n";
1194
+ }
1195
+
1196
+ export function slugName(text, max = 40) {
1197
+ return String(text)
1198
+ .normalize("NFKD")
1199
+ .replace(/[\u0300-\u036f]/g, "")
1200
+ .toLowerCase()
1201
+ .replace(/[^a-z0-9]+/g, "-")
1202
+ .replace(/^-+|-+$/g, "")
1203
+ .slice(0, max)
1204
+ .replace(/-+$/, "");
1205
+ }