atris 3.71.3 → 3.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/qr.js ADDED
@@ -0,0 +1,338 @@
1
+ 'use strict';
2
+
3
+ // A small QR code encoder: byte mode, error correction level M, versions 1
4
+ // to 10 (up to 213 bytes, plenty for a link). Follows the QR spec (ISO 18004)
5
+ // the same way most small encoders do: pick the smallest version that fits,
6
+ // add Reed-Solomon error correction per block, interleave, place the bits,
7
+ // then try all eight masks and keep the one with the lowest penalty.
8
+ //
9
+ // encodeQr(text) -> { version, size, mask, modules } where modules[y][x] is
10
+ // true for a dark square. qrLines(text) renders it for a terminal with half
11
+ // block characters, two rows of squares per line, quiet zone included.
12
+
13
+ const MAX_VERSION = 10;
14
+ // Level M tables, index = version (index 0 unused).
15
+ const ECC_PER_BLOCK = [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26];
16
+ const ECC_BLOCKS = [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5];
17
+ const FORMAT_BITS_M = 0;
18
+
19
+ function rawDataModules(ver) {
20
+ let result = (16 * ver + 128) * ver + 64;
21
+ if (ver >= 2) {
22
+ const numAlign = Math.floor(ver / 7) + 2;
23
+ result -= (25 * numAlign - 10) * numAlign - 55;
24
+ if (ver >= 7) result -= 36;
25
+ }
26
+ return result;
27
+ }
28
+
29
+ function dataCodewords(ver) {
30
+ return Math.floor(rawDataModules(ver) / 8) - ECC_PER_BLOCK[ver] * ECC_BLOCKS[ver];
31
+ }
32
+
33
+ // Multiply in GF(2^8) with the QR polynomial x^8 + x^4 + x^3 + x^2 + 1.
34
+ function gfMul(x, y) {
35
+ let z = 0;
36
+ for (let i = 7; i >= 0; i -= 1) {
37
+ z = (z << 1) ^ ((z >>> 7) * 0x11d);
38
+ z ^= ((y >>> i) & 1) * x;
39
+ }
40
+ return z & 0xff;
41
+ }
42
+
43
+ function rsDivisor(degree) {
44
+ const result = new Array(degree).fill(0);
45
+ result[degree - 1] = 1;
46
+ let root = 1;
47
+ for (let i = 0; i < degree; i += 1) {
48
+ for (let j = 0; j < result.length; j += 1) {
49
+ result[j] = gfMul(result[j], root);
50
+ if (j + 1 < result.length) result[j] ^= result[j + 1];
51
+ }
52
+ root = gfMul(root, 0x02);
53
+ }
54
+ return result;
55
+ }
56
+
57
+ function rsRemainder(data, divisor) {
58
+ const result = new Array(divisor.length).fill(0);
59
+ for (const b of data) {
60
+ const factor = b ^ result.shift();
61
+ result.push(0);
62
+ divisor.forEach((coef, i) => { result[i] ^= gfMul(coef, factor); });
63
+ }
64
+ return result;
65
+ }
66
+
67
+ function getBit(x, i) {
68
+ return ((x >>> i) & 1) !== 0;
69
+ }
70
+
71
+ // Data bits for byte mode, padded to the version's data capacity.
72
+ function dataBytes(bytes, ver) {
73
+ const bits = [];
74
+ const push = (value, len) => {
75
+ for (let i = len - 1; i >= 0; i -= 1) bits.push((value >>> i) & 1);
76
+ };
77
+ push(0b0100, 4);
78
+ push(bytes.length, ver <= 9 ? 8 : 16);
79
+ for (const b of bytes) push(b, 8);
80
+ const capacity = dataCodewords(ver) * 8;
81
+ push(0, Math.min(4, capacity - bits.length));
82
+ push(0, (8 - (bits.length % 8)) % 8);
83
+ for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11) push(pad, 8);
84
+ const out = [];
85
+ for (let i = 0; i < bits.length; i += 8) {
86
+ let byte = 0;
87
+ for (let j = 0; j < 8; j += 1) byte = (byte << 1) | bits[i + j];
88
+ out.push(byte);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ // Split into blocks, add error correction to each, and interleave.
94
+ function withEcc(data, ver) {
95
+ const numBlocks = ECC_BLOCKS[ver];
96
+ const eccLen = ECC_PER_BLOCK[ver];
97
+ const raw = Math.floor(rawDataModules(ver) / 8);
98
+ const numShort = numBlocks - (raw % numBlocks);
99
+ const shortLen = Math.floor(raw / numBlocks);
100
+ const divisor = rsDivisor(eccLen);
101
+ const blocks = [];
102
+ for (let i = 0, k = 0; i < numBlocks; i += 1) {
103
+ const dat = data.slice(k, k + shortLen - eccLen + (i < numShort ? 0 : 1));
104
+ k += dat.length;
105
+ const ecc = rsRemainder(dat, divisor);
106
+ if (i < numShort) dat.push(0);
107
+ blocks.push(dat.concat(ecc));
108
+ }
109
+ const result = [];
110
+ for (let i = 0; i < blocks[0].length; i += 1) {
111
+ blocks.forEach((block, j) => {
112
+ if (i !== shortLen - eccLen || j >= numShort) result.push(block[i]);
113
+ });
114
+ }
115
+ return result;
116
+ }
117
+
118
+ function alignmentPositions(ver) {
119
+ if (ver === 1) return [];
120
+ const size = ver * 4 + 17;
121
+ const numAlign = Math.floor(ver / 7) + 2;
122
+ const step = Math.ceil((ver * 4 + 4) / (numAlign * 2 - 2)) * 2;
123
+ const result = [6];
124
+ for (let pos = size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
125
+ return result;
126
+ }
127
+
128
+ function makeGrid(ver) {
129
+ const size = ver * 4 + 17;
130
+ const modules = Array.from({ length: size }, () => new Array(size).fill(false));
131
+ const isFunction = Array.from({ length: size }, () => new Array(size).fill(false));
132
+ const set = (x, y, dark) => {
133
+ modules[y][x] = dark;
134
+ isFunction[y][x] = true;
135
+ };
136
+ return { ver, size, modules, isFunction, set };
137
+ }
138
+
139
+ function drawFormatBits(grid, mask) {
140
+ const { size, set } = grid;
141
+ const data = (FORMAT_BITS_M << 3) | mask;
142
+ let rem = data;
143
+ for (let i = 0; i < 10; i += 1) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
144
+ const bits = ((data << 10) | rem) ^ 0x5412;
145
+ for (let i = 0; i <= 5; i += 1) set(8, i, getBit(bits, i));
146
+ set(8, 7, getBit(bits, 6));
147
+ set(8, 8, getBit(bits, 7));
148
+ set(7, 8, getBit(bits, 8));
149
+ for (let i = 9; i < 15; i += 1) set(14 - i, 8, getBit(bits, i));
150
+ for (let i = 0; i < 8; i += 1) set(size - 1 - i, 8, getBit(bits, i));
151
+ for (let i = 8; i < 15; i += 1) set(8, size - 15 + i, getBit(bits, i));
152
+ set(8, size - 8, true);
153
+ }
154
+
155
+ function drawVersion(grid) {
156
+ const { ver, size, set } = grid;
157
+ if (ver < 7) return;
158
+ let rem = ver;
159
+ for (let i = 0; i < 12; i += 1) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
160
+ const bits = (ver << 12) | rem;
161
+ for (let i = 0; i < 18; i += 1) {
162
+ const bit = getBit(bits, i);
163
+ const a = size - 11 + (i % 3);
164
+ const b = Math.floor(i / 3);
165
+ set(a, b, bit);
166
+ set(b, a, bit);
167
+ }
168
+ }
169
+
170
+ function drawFunctionPatterns(grid) {
171
+ const { size, set } = grid;
172
+ for (let i = 0; i < size; i += 1) {
173
+ set(6, i, i % 2 === 0);
174
+ set(i, 6, i % 2 === 0);
175
+ }
176
+ const finder = (x, y) => {
177
+ for (let dy = -4; dy <= 4; dy += 1) {
178
+ for (let dx = -4; dx <= 4; dx += 1) {
179
+ const dist = Math.max(Math.abs(dx), Math.abs(dy));
180
+ const xx = x + dx;
181
+ const yy = y + dy;
182
+ if (xx >= 0 && xx < size && yy >= 0 && yy < size) set(xx, yy, dist !== 2 && dist !== 4);
183
+ }
184
+ }
185
+ };
186
+ finder(3, 3);
187
+ finder(size - 4, 3);
188
+ finder(3, size - 4);
189
+ const pos = alignmentPositions(grid.ver);
190
+ const n = pos.length;
191
+ for (let i = 0; i < n; i += 1) {
192
+ for (let j = 0; j < n; j += 1) {
193
+ if ((i === 0 && j === 0) || (i === 0 && j === n - 1) || (i === n - 1 && j === 0)) continue;
194
+ for (let dy = -2; dy <= 2; dy += 1) {
195
+ for (let dx = -2; dx <= 2; dx += 1) set(pos[i] + dx, pos[j] + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
196
+ }
197
+ }
198
+ }
199
+ drawFormatBits(grid, 0);
200
+ drawVersion(grid);
201
+ }
202
+
203
+ function drawCodewords(grid, data) {
204
+ const { size, modules, isFunction } = grid;
205
+ let i = 0;
206
+ for (let right = size - 1; right >= 1; right -= 2) {
207
+ if (right === 6) right = 5;
208
+ for (let vert = 0; vert < size; vert += 1) {
209
+ for (let j = 0; j < 2; j += 1) {
210
+ const x = right - j;
211
+ const upward = ((right + 1) & 2) === 0;
212
+ const y = upward ? size - 1 - vert : vert;
213
+ if (!isFunction[y][x] && i < data.length * 8) {
214
+ modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
215
+ i += 1;
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ const MASKS = [
223
+ (x, y) => (x + y) % 2 === 0,
224
+ (x, y) => y % 2 === 0,
225
+ x => x % 3 === 0,
226
+ (x, y) => (x + y) % 3 === 0,
227
+ (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
228
+ (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
229
+ (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
230
+ (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
231
+ ];
232
+
233
+ function applyMask(grid, mask) {
234
+ const { size, modules, isFunction } = grid;
235
+ const test = MASKS[mask];
236
+ for (let y = 0; y < size; y += 1) {
237
+ for (let x = 0; x < size; x += 1) {
238
+ if (!isFunction[y][x] && test(x, y)) modules[y][x] = !modules[y][x];
239
+ }
240
+ }
241
+ }
242
+
243
+ // The spec's four penalty rules: runs of 5+ in a line, 2x2 blocks, finder
244
+ // look-alikes (1:1:3:1:1 with 4 light on a side), and dark/light balance.
245
+ function penalty(modules) {
246
+ const size = modules.length;
247
+ let score = 0;
248
+ const line = get => {
249
+ let run = 1;
250
+ for (let i = 1; i < size; i += 1) {
251
+ if (get(i) === get(i - 1)) {
252
+ run += 1;
253
+ if (run === 5) score += 3;
254
+ else if (run > 5) score += 1;
255
+ } else {
256
+ run = 1;
257
+ }
258
+ }
259
+ for (let i = 0; i + 10 < size + 4; i += 1) {
260
+ const at = k => (i + k >= 0 && i + k < size ? get(i + k) : false);
261
+ const core = at(0) && !at(1) && at(2) && at(3) && at(4) && !at(5) && at(6);
262
+ if (!core) continue;
263
+ const lightBefore = [1, 2, 3, 4].every(k => !at(-k));
264
+ const lightAfter = [7, 8, 9, 10].every(k => !at(k));
265
+ if (lightBefore || lightAfter) score += 40;
266
+ }
267
+ };
268
+ for (let y = 0; y < size; y += 1) line(x => modules[y][x]);
269
+ for (let x = 0; x < size; x += 1) line(y => modules[y][x]);
270
+ for (let y = 0; y < size - 1; y += 1) {
271
+ for (let x = 0; x < size - 1; x += 1) {
272
+ const c = modules[y][x];
273
+ if (c === modules[y][x + 1] && c === modules[y + 1][x] && c === modules[y + 1][x + 1]) score += 3;
274
+ }
275
+ }
276
+ let dark = 0;
277
+ for (const row of modules) for (const m of row) if (m) dark += 1;
278
+ const total = size * size;
279
+ const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
280
+ score += Math.max(0, k) * 10;
281
+ return score;
282
+ }
283
+
284
+ function encodeQr(text, { mask: forcedMask = null } = {}) {
285
+ const bytes = [...Buffer.from(String(text), 'utf8')];
286
+ let ver = 1;
287
+ for (; ver <= MAX_VERSION; ver += 1) {
288
+ const needed = 4 + (ver <= 9 ? 8 : 16) + bytes.length * 8;
289
+ if (needed <= dataCodewords(ver) * 8) break;
290
+ }
291
+ if (ver > MAX_VERSION) throw new Error(`text too long for a QR code here (${bytes.length} bytes, max 213)`);
292
+ const grid = makeGrid(ver);
293
+ drawFunctionPatterns(grid);
294
+ drawCodewords(grid, withEcc(dataBytes(bytes, ver), ver));
295
+ let mask = forcedMask;
296
+ if (mask == null) {
297
+ let best = Infinity;
298
+ for (let m = 0; m < 8; m += 1) {
299
+ applyMask(grid, m);
300
+ drawFormatBits(grid, m);
301
+ const p = penalty(grid.modules);
302
+ if (p < best) {
303
+ best = p;
304
+ mask = m;
305
+ }
306
+ applyMask(grid, m);
307
+ }
308
+ }
309
+ applyMask(grid, mask);
310
+ drawFormatBits(grid, mask);
311
+ return { version: ver, size: grid.size, mask, modules: grid.modules };
312
+ }
313
+
314
+ // Terminal art: two rows of squares per line with half block characters.
315
+ // With color on (the default) each line forces black on bright white, so the
316
+ // code scans the same on a dark or a light terminal. With color off, light
317
+ // squares print as blocks, which reads right on a dark terminal.
318
+ const FULL = '█';
319
+ const UPPER = '▀';
320
+ const LOWER = '▄';
321
+
322
+ function qrLines(text, { quiet = 2, color = true } = {}) {
323
+ const { modules, size } = encodeQr(text);
324
+ const at = (x, y) => (x >= 0 && y >= 0 && x < size && y < size ? modules[y][x] : false);
325
+ const lines = [];
326
+ for (let y = -quiet; y < size + quiet; y += 2) {
327
+ let line = '';
328
+ for (let x = -quiet; x < size + quiet; x += 1) {
329
+ const top = color ? at(x, y) : !at(x, y);
330
+ const bottom = color ? at(x, y + 1) : !at(x, y + 1);
331
+ line += top && bottom ? FULL : top ? UPPER : bottom ? LOWER : ' ';
332
+ }
333
+ lines.push(color ? `\x1b[30;107m${line}\x1b[0m` : line);
334
+ }
335
+ return lines;
336
+ }
337
+
338
+ module.exports = { encodeQr, qrLines, rsDivisor, rsRemainder, gfMul, dataCodewords, alignmentPositions };