webspresso 0.0.98 → 0.0.99

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 (56) hide show
  1. package/.agents/ADMIN.md +11 -1
  2. package/.agents/AGENTS.md +1 -1
  3. package/.agents/AUTH.md +169 -0
  4. package/.agents/ORM.md +88 -4
  5. package/.agents/PLUGINS.md +73 -5
  6. package/.agents/QUEUE.md +84 -0
  7. package/.agents/ROUTING.md +127 -7
  8. package/AGENTS.md +1 -0
  9. package/core/auth/index.js +60 -0
  10. package/core/auth/manager.js +134 -0
  11. package/core/auth/middleware.js +88 -0
  12. package/core/auth/oauth.js +517 -0
  13. package/core/auth/qrcode.js +499 -0
  14. package/core/auth/totp.js +504 -0
  15. package/core/orm/eager-loader.js +144 -10
  16. package/core/orm/index.js +8 -0
  17. package/core/orm/model.js +4 -0
  18. package/core/orm/query-builder.js +90 -4
  19. package/core/orm/repository.js +44 -3
  20. package/core/orm/search.js +213 -0
  21. package/core/queue/cron.js +331 -0
  22. package/core/queue/index.js +8 -1
  23. package/core/queue/manager.js +82 -1
  24. package/core/queue/scheduler.js +359 -0
  25. package/core/sse/index.js +19 -0
  26. package/core/sse/stream.js +430 -0
  27. package/index.d.ts +528 -0
  28. package/index.js +44 -0
  29. package/package.json +1 -1
  30. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +323 -31
  31. package/plugins/admin-panel/client/parts/08-record-list.js +5 -0
  32. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  33. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  34. package/plugins/admin-panel/field-renderers/index.js +2 -0
  35. package/plugins/api-key/crypto.js +101 -0
  36. package/plugins/api-key/index.js +288 -0
  37. package/plugins/api-key/middleware.js +461 -0
  38. package/plugins/api-key/migration-template.js +48 -0
  39. package/plugins/api-key/model.js +50 -0
  40. package/plugins/api-key/scopes.js +125 -0
  41. package/plugins/audit-log/admin-component.js +487 -70
  42. package/plugins/audit-log/api-handlers.js +114 -0
  43. package/plugins/audit-log/index.js +24 -0
  44. package/plugins/file-manager/admin-component.js +20 -4
  45. package/plugins/file-manager/index.js +21 -4
  46. package/plugins/index.js +6 -0
  47. package/plugins/oauth/index.js +338 -0
  48. package/plugins/queue/index.js +4 -0
  49. package/src/api/api-loader.js +1 -0
  50. package/src/discovery/file-route-parser.js +44 -1
  51. package/src/discovery/scan-routes.js +2 -2
  52. package/src/file-router.js +137 -100
  53. package/src/helpers.js +9 -0
  54. package/src/routing/cascading.js +378 -0
  55. package/src/routing/middleware-resolver.js +92 -0
  56. package/src/server.js +6 -0
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Minimal Zero-Dependency QR Code Generator
3
+ * Pure JavaScript implementation of QR Code Model 2 (ISO/IEC 18004)
4
+ * Supports Byte mode (UTF-8 / ASCII) with Error Correction Levels (L, M, Q, H)
5
+ * Versions 1 to 10 (sufficient for URIs up to 271 bytes)
6
+ */
7
+
8
+ // Error correction levels
9
+ const ECC_LEVELS = {
10
+ L: 1, // Low (~7%)
11
+ M: 0, // Medium (~15%)
12
+ Q: 3, // Quartile (~25%)
13
+ H: 2, // High (~30%)
14
+ };
15
+
16
+ // Format info bits for each (ECC, mask) combination (15 bits, BCH encoded)
17
+ const FORMAT_INFO = [
18
+ [0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976], // M (0)
19
+ [0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0], // L (1)
20
+ [0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed], // H (2)
21
+ [0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b], // Q (3)
22
+ ];
23
+
24
+ // Capacity and ECC table for Versions 1-10:
25
+ // [version, totalDataBytes, ecBytesPerBlock, numBlocksGroup1, dataBytesBlock1, numBlocksGroup2, dataBytesBlock2]
26
+ // ECC order: [M, L, H, Q]
27
+ const VERSION_TABLE = {
28
+ 1: {
29
+ M: { totalCodewords: 26, ecPerBlock: 10, b1: 1, d1: 16, b2: 0, d2: 0 },
30
+ L: { totalCodewords: 26, ecPerBlock: 7, b1: 1, d1: 19, b2: 0, d2: 0 },
31
+ H: { totalCodewords: 26, ecPerBlock: 17, b1: 1, d1: 9, b2: 0, d2: 0 },
32
+ Q: { totalCodewords: 26, ecPerBlock: 13, b1: 1, d1: 13, b2: 0, d2: 0 },
33
+ },
34
+ 2: {
35
+ M: { totalCodewords: 44, ecPerBlock: 16, b1: 1, d1: 28, b2: 0, d2: 0 },
36
+ L: { totalCodewords: 44, ecPerBlock: 10, b1: 1, d1: 34, b2: 0, d2: 0 },
37
+ H: { totalCodewords: 44, ecPerBlock: 28, b1: 1, d1: 16, b2: 0, d2: 0 },
38
+ Q: { totalCodewords: 44, ecPerBlock: 22, b1: 1, d1: 22, b2: 0, d2: 0 },
39
+ },
40
+ 3: {
41
+ M: { totalCodewords: 70, ecPerBlock: 26, b1: 1, d1: 44, b2: 0, d2: 0 },
42
+ L: { totalCodewords: 70, ecPerBlock: 15, b1: 1, d1: 55, b2: 0, d2: 0 },
43
+ H: { totalCodewords: 70, ecPerBlock: 22, b1: 2, d1: 13, b2: 0, d2: 0 },
44
+ Q: { totalCodewords: 70, ecPerBlock: 18, b1: 2, d1: 17, b2: 0, d2: 0 },
45
+ },
46
+ 4: {
47
+ M: { totalCodewords: 100, ecPerBlock: 18, b1: 2, d1: 32, b2: 0, d2: 0 },
48
+ L: { totalCodewords: 100, ecPerBlock: 20, b1: 1, d1: 80, b2: 0, d2: 0 },
49
+ H: { totalCodewords: 100, ecPerBlock: 16, b1: 4, d1: 9, b2: 0, d2: 0 },
50
+ Q: { totalCodewords: 100, ecPerBlock: 26, b1: 2, d1: 24, b2: 0, d2: 0 },
51
+ },
52
+ 5: {
53
+ M: { totalCodewords: 134, ecPerBlock: 24, b1: 2, d1: 43, b2: 0, d2: 0 },
54
+ L: { totalCodewords: 134, ecPerBlock: 26, b1: 1, d1: 108, b2: 0, d2: 0 },
55
+ H: { totalCodewords: 134, ecPerBlock: 22, b1: 2, d1: 11, b2: 2, d2: 12 },
56
+ Q: { totalCodewords: 134, ecPerBlock: 18, b1: 2, d1: 15, b2: 2, d2: 16 },
57
+ },
58
+ 6: {
59
+ M: { totalCodewords: 172, ecPerBlock: 16, b1: 4, d1: 27, b2: 0, d2: 0 },
60
+ L: { totalCodewords: 172, ecPerBlock: 18, b1: 2, d1: 68, b2: 0, d2: 0 },
61
+ H: { totalCodewords: 172, ecPerBlock: 28, b1: 4, d1: 15, b2: 0, d2: 0 },
62
+ Q: { totalCodewords: 172, ecPerBlock: 24, b1: 4, d1: 19, b2: 0, d2: 0 },
63
+ },
64
+ 7: {
65
+ M: { totalCodewords: 196, ecPerBlock: 18, b1: 4, d1: 31, b2: 0, d2: 0 },
66
+ L: { totalCodewords: 196, ecPerBlock: 20, b1: 2, d1: 78, b2: 0, d2: 0 },
67
+ H: { totalCodewords: 196, ecPerBlock: 26, b1: 4, d1: 13, b2: 1, d2: 14 },
68
+ Q: { totalCodewords: 196, ecPerBlock: 18, b1: 2, d1: 14, b2: 4, d2: 15 },
69
+ },
70
+ 8: {
71
+ M: { totalCodewords: 242, ecPerBlock: 22, b1: 2, d1: 38, b2: 2, d2: 39 },
72
+ L: { totalCodewords: 242, ecPerBlock: 24, b1: 2, d1: 97, b2: 0, d2: 0 },
73
+ H: { totalCodewords: 242, ecPerBlock: 26, b1: 4, d1: 14, b2: 2, d2: 15 },
74
+ Q: { totalCodewords: 242, ecPerBlock: 22, b1: 4, d1: 18, b2: 2, d2: 19 },
75
+ },
76
+ 9: {
77
+ M: { totalCodewords: 292, ecPerBlock: 22, b1: 3, d1: 36, b2: 2, d2: 37 },
78
+ L: { totalCodewords: 292, ecPerBlock: 30, b1: 2, d1: 116, b2: 0, d2: 0 },
79
+ H: { totalCodewords: 292, ecPerBlock: 24, b1: 4, d1: 12, b2: 4, d2: 13 },
80
+ Q: { totalCodewords: 292, ecPerBlock: 20, b1: 4, d1: 16, b2: 4, d2: 17 },
81
+ },
82
+ 10: {
83
+ M: { totalCodewords: 346, ecPerBlock: 26, b1: 4, d1: 43, b2: 1, d2: 44 },
84
+ L: { totalCodewords: 346, ecPerBlock: 18, b1: 2, d1: 68, b2: 2, d2: 69 },
85
+ H: { totalCodewords: 346, ecPerBlock: 28, b1: 6, d1: 15, b2: 2, d2: 16 },
86
+ Q: { totalCodewords: 346, ecPerBlock: 24, b1: 6, d1: 19, b2: 2, d2: 20 },
87
+ },
88
+ };
89
+
90
+ // Alignment pattern center positions for versions 2-10
91
+ const ALIGNMENT_PATTERN_POSITIONS = {
92
+ 2: [6, 18],
93
+ 3: [6, 22],
94
+ 4: [6, 26],
95
+ 5: [6, 30],
96
+ 6: [6, 34],
97
+ 7: [6, 22, 38],
98
+ 8: [6, 24, 42],
99
+ 9: [6, 26, 46],
100
+ 10: [6, 28, 50],
101
+ };
102
+
103
+ // Precompute GF(256) log and exp tables
104
+ const GF256_EXP = new Uint8Array(512);
105
+ const GF256_LOG = new Uint8Array(256);
106
+ let gfVal = 1;
107
+ for (let i = 0; i < 255; i++) {
108
+ GF256_EXP[i] = gfVal;
109
+ GF256_EXP[i + 255] = gfVal;
110
+ GF256_LOG[gfVal] = i;
111
+ gfVal <<= 1;
112
+ if (gfVal & 256) gfVal ^= 0x11d;
113
+ }
114
+
115
+ function gmul(a, b) {
116
+ if (a === 0 || b === 0) return 0;
117
+ return GF256_EXP[GF256_LOG[a] + GF256_LOG[b]];
118
+ }
119
+
120
+ function rsGenPoly(numEc) {
121
+ let poly = [1];
122
+ for (let i = 0; i < numEc; i++) {
123
+ const next = new Array(poly.length + 1).fill(0);
124
+ const root = GF256_EXP[i];
125
+ for (let j = 0; j < poly.length; j++) {
126
+ next[j] ^= gmul(poly[j], root);
127
+ next[j + 1] ^= poly[j];
128
+ }
129
+ poly = next;
130
+ }
131
+ return poly;
132
+ }
133
+
134
+ function rsCompute(data, numEc) {
135
+ const gen = rsGenPoly(numEc);
136
+ const result = new Array(numEc).fill(0);
137
+ for (const b of data) {
138
+ const factor = b ^ result.shift();
139
+ result.push(0);
140
+ for (let i = 0; i < numEc; i++) {
141
+ result[i] ^= gmul(gen[i], factor);
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Determine the smallest QR Version that can hold the data bytes
149
+ */
150
+ function findBestVersion(dataLength, ecc = 'M') {
151
+ for (let v = 1; v <= 10; v++) {
152
+ const table = VERSION_TABLE[v][ecc];
153
+ const maxDataBytes = table.b1 * table.d1 + table.b2 * table.d2;
154
+ // Overhead: 4 bits mode + (8 or 16 bits count)
155
+ const countBits = v <= 9 ? 8 : 16;
156
+ const available = Math.floor((maxDataBytes * 8 - 4 - countBits) / 8);
157
+ if (dataLength <= available) {
158
+ return v;
159
+ }
160
+ }
161
+ throw new Error(`Data is too long for QR Code versions 1-10 (${dataLength} bytes)`);
162
+ }
163
+
164
+ /**
165
+ * Encode raw text to bitstream with padding and interleaving
166
+ */
167
+ function encodeData(text, version, ecc = 'M') {
168
+ const bytes = Buffer.from(text, 'utf8');
169
+ const table = VERSION_TABLE[version][ecc];
170
+ const totalDataBytes = table.b1 * table.d1 + table.b2 * table.d2;
171
+
172
+ // Bit buffer
173
+ let bits = '';
174
+ // 1. Mode indicator: 0100 for 8-bit byte mode
175
+ bits += '0100';
176
+ // 2. Character count indicator
177
+ const countBits = version <= 9 ? 8 : 16;
178
+ bits += bytes.length.toString(2).padStart(countBits, '0');
179
+ // 3. Data bytes
180
+ for (let i = 0; i < bytes.length; i++) {
181
+ bits += bytes[i].toString(2).padStart(8, '0');
182
+ }
183
+
184
+ // 4. Terminator (up to 4 zeros)
185
+ const maxBits = totalDataBytes * 8;
186
+ const termLen = Math.min(4, maxBits - bits.length);
187
+ bits += '0'.repeat(termLen);
188
+
189
+ // 5. Pad to multiple of 8
190
+ while (bits.length % 8 !== 0) {
191
+ bits += '0';
192
+ }
193
+
194
+ // 6. Pad bytes (0xEC, 0x11 alternating)
195
+ const dataBytes = [];
196
+ for (let i = 0; i < bits.length; i += 8) {
197
+ dataBytes.push(parseInt(bits.substring(i, i + 8), 2));
198
+ }
199
+ const padBytes = [0xec, 0x11];
200
+ let padIdx = 0;
201
+ while (dataBytes.length < totalDataBytes) {
202
+ dataBytes.push(padBytes[padIdx % 2]);
203
+ padIdx++;
204
+ }
205
+
206
+ // 7. Group into blocks and calculate ECC
207
+ const blocks = [];
208
+ const ecBlocks = [];
209
+ let byteOffset = 0;
210
+
211
+ for (let i = 0; i < table.b1; i++) {
212
+ const block = dataBytes.slice(byteOffset, byteOffset + table.d1);
213
+ blocks.push(block);
214
+ ecBlocks.push(rsCompute(block, table.ecPerBlock));
215
+ byteOffset += table.d1;
216
+ }
217
+ for (let i = 0; i < table.b2; i++) {
218
+ const block = dataBytes.slice(byteOffset, byteOffset + table.d2);
219
+ blocks.push(block);
220
+ ecBlocks.push(rsCompute(block, table.ecPerBlock));
221
+ byteOffset += table.d2;
222
+ }
223
+
224
+ // 8. Interleave data bytes
225
+ const finalCodewords = [];
226
+ const maxDataLen = Math.max(table.d1, table.d2 || 0);
227
+ for (let j = 0; j < maxDataLen; j++) {
228
+ for (let i = 0; i < blocks.length; i++) {
229
+ if (j < blocks[i].length) {
230
+ finalCodewords.push(blocks[i][j]);
231
+ }
232
+ }
233
+ }
234
+
235
+ // 9. Interleave error correction bytes
236
+ for (let j = 0; j < table.ecPerBlock; j++) {
237
+ for (let i = 0; i < ecBlocks.length; i++) {
238
+ finalCodewords.push(ecBlocks[i][j]);
239
+ }
240
+ }
241
+
242
+ return finalCodewords;
243
+ }
244
+
245
+ /**
246
+ * Generate 2D QR matrix
247
+ */
248
+ function createQrMatrix(text, options = {}) {
249
+ const ecc = (options.ecc || 'M').toUpperCase();
250
+ const version = options.version || findBestVersion(Buffer.byteLength(text, 'utf8'), ecc);
251
+ const size = 17 + version * 4;
252
+ const codewords = encodeData(text, version, ecc);
253
+
254
+ // Initialize matrix: -1 represents unset, 0 false/white, 1 true/black
255
+ const matrix = Array.from({ length: size }, () => new Int8Array(size).fill(-1));
256
+ const isFunction = Array.from({ length: size }, () => new Uint8Array(size).fill(0));
257
+
258
+ function setFunction(r, c, val) {
259
+ matrix[r][c] = val ? 1 : 0;
260
+ isFunction[r][c] = 1;
261
+ }
262
+
263
+ // 1. Finder patterns (7x7) + separators
264
+ function drawFinder(row, col) {
265
+ for (let r = -1; r <= 7; r++) {
266
+ for (let c = -1; c <= 7; c++) {
267
+ const nr = row + r;
268
+ const nc = col + c;
269
+ if (nr >= 0 && nr < size && nc >= 0 && nc < size) {
270
+ if (r >= 0 && r <= 6 && c >= 0 && c <= 6) {
271
+ const isBorder = r === 0 || r === 6 || c === 0 || c === 6;
272
+ const isCenter = r >= 2 && r <= 4 && c >= 2 && c <= 4;
273
+ setFunction(nr, nc, isBorder || isCenter);
274
+ } else {
275
+ setFunction(nr, nc, 0); // Separator
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }
281
+ drawFinder(0, 0);
282
+ drawFinder(0, size - 7);
283
+ drawFinder(size - 7, 0);
284
+
285
+ // 2. Alignment patterns for Version >= 2
286
+ if (version >= 2) {
287
+ const coords = ALIGNMENT_PATTERN_POSITIONS[version];
288
+ for (const r of coords) {
289
+ for (const c of coords) {
290
+ // Skip if overlaps finder
291
+ if ((r < 9 && c < 9) || (r < 9 && c > size - 10) || (r > size - 10 && c < 9)) {
292
+ continue;
293
+ }
294
+ for (let dr = -2; dr <= 2; dr++) {
295
+ for (let dc = -2; dc <= 2; dc++) {
296
+ const isBorder = Math.abs(dr) === 2 || Math.abs(dc) === 2;
297
+ const isCenter = dr === 0 && dc === 0;
298
+ setFunction(r + dr, c + dc, isBorder || isCenter);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ // 3. Timing patterns
306
+ for (let i = 8; i < size - 8; i++) {
307
+ if (!isFunction[6][i]) setFunction(6, i, i % 2 === 0);
308
+ if (!isFunction[i][6]) setFunction(i, 6, i % 2 === 0);
309
+ }
310
+
311
+ // 4. Dark module
312
+ setFunction(size - 8, 8, 1);
313
+
314
+ // 5. Reserve format info areas
315
+ for (let i = 0; i < 9; i++) {
316
+ if (!isFunction[8][i]) isFunction[8][i] = 1;
317
+ if (!isFunction[i][8]) isFunction[i][8] = 1;
318
+ }
319
+ for (let i = size - 8; i < size; i++) {
320
+ if (!isFunction[8][i]) isFunction[8][i] = 1;
321
+ if (!isFunction[i][8]) isFunction[i][8] = 1;
322
+ }
323
+
324
+ // 6. Place data bits in matrix (zig-zag from right to left)
325
+ let bitIdx = 0;
326
+ const totalBits = codewords.length * 8;
327
+
328
+ function getBit(idx) {
329
+ if (idx >= totalBits) return 0;
330
+ const byte = codewords[Math.floor(idx / 8)];
331
+ return (byte >> (7 - (idx % 8))) & 1;
332
+ }
333
+
334
+ let upward = true;
335
+ for (let right = size - 1; right > 0; right -= 2) {
336
+ if (right === 6) right--; // Skip vertical timing line
337
+ const rows = [];
338
+ if (upward) {
339
+ for (let r = size - 1; r >= 0; r--) rows.push(r);
340
+ } else {
341
+ for (let r = 0; r < size; r++) rows.push(r);
342
+ }
343
+ for (const r of rows) {
344
+ for (let c = right; c >= right - 1; c--) {
345
+ if (!isFunction[r][c]) {
346
+ matrix[r][c] = getBit(bitIdx);
347
+ bitIdx++;
348
+ }
349
+ }
350
+ }
351
+ upward = !upward;
352
+ }
353
+
354
+ // 7. Choose mask pattern with lowest penalty (or mask 0 by default)
355
+ const maskPattern = options.mask != null ? options.mask : chooseBestMask(matrix, isFunction, size);
356
+
357
+ // Apply mask and write format info
358
+ const eccIdx = ECC_LEVELS[ecc];
359
+ const formatInfo = FORMAT_INFO[eccIdx][maskPattern];
360
+
361
+ // Write format info to reserved areas
362
+ for (let i = 0; i < 15; i++) {
363
+ const bit = (formatInfo >> (14 - i)) & 1;
364
+ // Around top-left
365
+ if (i < 6) matrix[8][i] = bit;
366
+ else if (i === 6) matrix[8][7] = bit;
367
+ else if (i === 7) matrix[8][8] = bit;
368
+ else if (i === 8) matrix[7][8] = bit;
369
+ else matrix[14 - i][8] = bit;
370
+
371
+ // Around top-right and bottom-left
372
+ if (i < 8) {
373
+ matrix[size - 1 - i][8] = bit;
374
+ } else {
375
+ matrix[8][size - 15 + i] = bit;
376
+ }
377
+ }
378
+
379
+ // Final boolean matrix
380
+ const result = [];
381
+ for (let r = 0; r < size; r++) {
382
+ const row = [];
383
+ for (let c = 0; c < size; c++) {
384
+ if (isFunction[r][c]) {
385
+ row.push(matrix[r][c] === 1);
386
+ } else {
387
+ const masked = maskCondition(maskPattern, r, c);
388
+ const bit = matrix[r][c] ^ (masked ? 1 : 0);
389
+ row.push(bit === 1);
390
+ }
391
+ }
392
+ result.push(row);
393
+ }
394
+
395
+ return { matrix: result, size, version };
396
+ }
397
+
398
+ function maskCondition(pattern, r, c) {
399
+ switch (pattern) {
400
+ case 0: return (r + c) % 2 === 0;
401
+ case 1: return r % 2 === 0;
402
+ case 2: return c % 3 === 0;
403
+ case 3: return (r + c) % 3 === 0;
404
+ case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
405
+ case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
406
+ case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
407
+ case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
408
+ default: return false;
409
+ }
410
+ }
411
+
412
+ function chooseBestMask(matrix, isFunction, size) {
413
+ // Evaluates penalty scores for masks 0-7, returning the one with lowest penalty
414
+ let bestMask = 0;
415
+ let minPenalty = Infinity;
416
+
417
+ for (let mask = 0; mask < 8; mask++) {
418
+ let penalty = 0;
419
+ // Calculate penalty rule 1 (runs of 5+ same color) and rule 2 (2x2 blocks)
420
+ for (let r = 0; r < size; r++) {
421
+ let runColor = -1;
422
+ let runLen = 0;
423
+ for (let c = 0; c < size; c++) {
424
+ const val = isFunction[r][c] ? matrix[r][c] : (matrix[r][c] ^ (maskCondition(mask, r, c) ? 1 : 0));
425
+ if (val === runColor) {
426
+ runLen++;
427
+ } else {
428
+ if (runLen >= 5) penalty += 3 + (runLen - 5);
429
+ runColor = val;
430
+ runLen = 1;
431
+ }
432
+ // 2x2 blocks
433
+ if (r < size - 1 && c < size - 1) {
434
+ const v2 = isFunction[r][c+1] ? matrix[r][c+1] : (matrix[r][c+1] ^ (maskCondition(mask, r, c+1) ? 1 : 0));
435
+ const v3 = isFunction[r+1][c] ? matrix[r+1][c] : (matrix[r+1][c] ^ (maskCondition(mask, r+1, c) ? 1 : 0));
436
+ const v4 = isFunction[r+1][c+1] ? matrix[r+1][c+1] : (matrix[r+1][c+1] ^ (maskCondition(mask, r+1, c+1) ? 1 : 0));
437
+ if (val === v2 && val === v3 && val === v4) {
438
+ penalty += 3;
439
+ }
440
+ }
441
+ }
442
+ if (runLen >= 5) penalty += 3 + (runLen - 5);
443
+ }
444
+ if (penalty < minPenalty) {
445
+ minPenalty = penalty;
446
+ bestMask = mask;
447
+ }
448
+ }
449
+ return bestMask;
450
+ }
451
+
452
+ /**
453
+ * Generate SVG string from QR Code
454
+ * @param {string} text - Text to encode
455
+ * @param {Object} [options] - Options
456
+ * @param {number} [options.margin=4] - Quiet zone margin (modules)
457
+ * @param {number} [options.size=256] - Width and height in px
458
+ * @param {string} [options.darkColor='#000000'] - Dark module color
459
+ * @param {string} [options.lightColor='#ffffff'] - Light module color (or 'none')
460
+ * @returns {string} SVG markup
461
+ */
462
+ function generateQrSvg(text, options = {}) {
463
+ const { matrix, size } = createQrMatrix(text, options);
464
+ const margin = options.margin != null ? options.margin : 4;
465
+ const viewBoxSize = size + margin * 2;
466
+ const darkColor = options.darkColor || '#000000';
467
+ const lightColor = options.lightColor || '#ffffff';
468
+ const width = options.size || 256;
469
+ const height = options.size || 256;
470
+
471
+ let pathData = '';
472
+ for (let r = 0; r < size; r++) {
473
+ for (let c = 0; c < size; c++) {
474
+ if (matrix[r][c]) {
475
+ pathData += `M${c + margin} ${r + margin}h1v1h-1z `;
476
+ }
477
+ }
478
+ }
479
+
480
+ const bgRect = lightColor && lightColor !== 'none'
481
+ ? `<rect width="${viewBoxSize}" height="${viewBoxSize}" fill="${lightColor}"/>`
482
+ : '';
483
+
484
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${viewBoxSize} ${viewBoxSize}" width="${width}" height="${height}" shape-rendering="crispEdges">${bgRect}<path d="${pathData.trim()}" fill="${darkColor}"/></svg>`;
485
+ }
486
+
487
+ /**
488
+ * Generate Data URI from SVG
489
+ */
490
+ function generateQrDataUri(text, options = {}) {
491
+ const svg = generateQrSvg(text, options);
492
+ return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
493
+ }
494
+
495
+ module.exports = {
496
+ createQrMatrix,
497
+ generateQrSvg,
498
+ generateQrDataUri,
499
+ };