qr 0.5.2 → 0.5.4
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/README.md +28 -26
- package/decode.d.ts +2 -1
- package/decode.d.ts.map +1 -1
- package/decode.js +73 -23
- package/decode.js.map +1 -1
- package/dom.d.ts +1 -0
- package/dom.d.ts.map +1 -1
- package/dom.js +4 -1
- package/dom.js.map +1 -1
- package/index.d.ts +29 -5
- package/index.d.ts.map +1 -1
- package/index.js +456 -139
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/src/decode.ts +72 -21
- package/src/dom.ts +5 -1
- package/src/index.ts +450 -135
package/index.js
CHANGED
|
@@ -50,32 +50,32 @@ function mod(a, b) {
|
|
|
50
50
|
function fillArr(length, val) {
|
|
51
51
|
return new Array(length).fill(val);
|
|
52
52
|
}
|
|
53
|
+
function popcnt(n) {
|
|
54
|
+
n = n - ((n >>> 1) & 0x55555555);
|
|
55
|
+
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
|
|
56
|
+
return (((n + (n >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
|
|
57
|
+
}
|
|
53
58
|
/**
|
|
54
59
|
* Interleaves byte blocks.
|
|
55
60
|
* @param blocks [[1, 2, 3], [4, 5, 6]]
|
|
56
61
|
* @returns [1, 4, 2, 5, 3, 6]
|
|
57
62
|
*/
|
|
58
|
-
function interleaveBytes(
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
function interleaveBytes(blocks) {
|
|
64
|
+
let maxLen = 0;
|
|
65
|
+
let totalLen = 0;
|
|
66
|
+
for (const block of blocks) {
|
|
67
|
+
maxLen = Math.max(maxLen, block.length);
|
|
68
|
+
totalLen += block.length;
|
|
69
|
+
}
|
|
70
|
+
const result = new Uint8Array(totalLen);
|
|
71
|
+
let idx = 0;
|
|
72
|
+
for (let i = 0; i < maxLen; i++) {
|
|
73
|
+
for (const block of blocks) {
|
|
74
|
+
if (i < block.length)
|
|
75
|
+
result[idx++] = block[i];
|
|
68
76
|
}
|
|
69
77
|
}
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
function includesAt(lst, pattern, index) {
|
|
73
|
-
if (index < 0 || index + pattern.length > lst.length)
|
|
74
|
-
return false;
|
|
75
|
-
for (let i = 0; i < pattern.length; i++)
|
|
76
|
-
if (pattern[i] !== lst[index + i])
|
|
77
|
-
return false;
|
|
78
|
-
return true;
|
|
78
|
+
return result;
|
|
79
79
|
}
|
|
80
80
|
// Optimize for minimal score/penalty
|
|
81
81
|
function best() {
|
|
@@ -120,6 +120,39 @@ function alphabet(alphabet) {
|
|
|
120
120
|
},
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
+
// Transpose 32x32 bit matrix in-place
|
|
124
|
+
// a[0..31] are 32 rows of 32 bits each; after transpose they become 32 columns.
|
|
125
|
+
function transpose32(a) {
|
|
126
|
+
if (a.length !== 32)
|
|
127
|
+
throw new Error('expects 32 element matrix');
|
|
128
|
+
const masks = [0x55555555, 0x33333333, 0x0f0f0f0f, 0x00ff00ff, 0x0000ffff];
|
|
129
|
+
// Hello again, FFT
|
|
130
|
+
for (let stage = 0; stage < 5; stage++) {
|
|
131
|
+
const m = masks[stage] >>> 0;
|
|
132
|
+
const s = 1 << stage; // 1,2,4,8,16
|
|
133
|
+
const step = s << 1; // 2,4,8,16,32
|
|
134
|
+
for (let i = 0; i < 32; i += step) {
|
|
135
|
+
for (let k = 0; k < s; k++) {
|
|
136
|
+
const i0 = i + k;
|
|
137
|
+
const i1 = i0 + s;
|
|
138
|
+
const x = a[i0] >>> 0;
|
|
139
|
+
const y = a[i1] >>> 0;
|
|
140
|
+
const t = ((x >>> s) ^ y) & m;
|
|
141
|
+
a[i0] = (x ^ (t << s)) >>> 0;
|
|
142
|
+
a[i1] = (y ^ t) >>> 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const bitMask = (x) => (1 << (x & 31)) >>> 0;
|
|
148
|
+
const rangeMask = (shift, len) => {
|
|
149
|
+
// len in [0..32], shift in [0..31]
|
|
150
|
+
if (len === 0)
|
|
151
|
+
return 0;
|
|
152
|
+
if (len === 32)
|
|
153
|
+
return 0xffffffff;
|
|
154
|
+
return (((1 << len) - 1) << shift) >>> 0;
|
|
155
|
+
};
|
|
123
156
|
export class Bitmap {
|
|
124
157
|
static size(size, limit) {
|
|
125
158
|
if (typeof size === 'number')
|
|
@@ -142,8 +175,8 @@ export class Bitmap {
|
|
|
142
175
|
s = s.replace(/^\n+/g, '').replace(/\n+$/g, '');
|
|
143
176
|
const lines = s.split(String.fromCharCode(chCodes.newline));
|
|
144
177
|
const height = lines.length;
|
|
145
|
-
const data = new Array(height);
|
|
146
178
|
let width;
|
|
179
|
+
const rows = [];
|
|
147
180
|
for (const line of lines) {
|
|
148
181
|
const row = line.split('').map((i) => {
|
|
149
182
|
if (i === 'X')
|
|
@@ -154,26 +187,50 @@ export class Bitmap {
|
|
|
154
187
|
return undefined;
|
|
155
188
|
throw new Error(`Bitmap.fromString: unknown symbol=${i}`);
|
|
156
189
|
});
|
|
157
|
-
if (width && row.length !== width)
|
|
190
|
+
if (width !== undefined && row.length !== width)
|
|
158
191
|
throw new Error(`Bitmap.fromString different row sizes: width=${width} cur=${row.length}`);
|
|
159
192
|
width = row.length;
|
|
160
|
-
|
|
193
|
+
rows.push(row);
|
|
161
194
|
}
|
|
162
|
-
if (
|
|
195
|
+
if (width === undefined)
|
|
163
196
|
width = 0;
|
|
164
|
-
return new Bitmap({ height, width },
|
|
197
|
+
return new Bitmap({ height, width }, rows);
|
|
165
198
|
}
|
|
166
|
-
|
|
199
|
+
// Two bitsets:
|
|
200
|
+
// defined=0 -> undefined
|
|
201
|
+
// defined=1,value=0 -> false
|
|
202
|
+
// defined=1,value=1 -> true
|
|
203
|
+
defined;
|
|
204
|
+
value;
|
|
205
|
+
tailMask;
|
|
206
|
+
words;
|
|
207
|
+
fullWords;
|
|
167
208
|
height;
|
|
168
209
|
width;
|
|
169
210
|
constructor(size, data) {
|
|
170
211
|
const { height, width } = Bitmap.size(size);
|
|
171
|
-
this.data = data || Array.from({ length: height }, () => fillArr(width, undefined));
|
|
172
212
|
this.height = height;
|
|
173
213
|
this.width = width;
|
|
214
|
+
this.tailMask = rangeMask(0, width & 31 || 32);
|
|
215
|
+
this.words = Math.ceil(width / 32) | 0;
|
|
216
|
+
this.fullWords = Math.floor(width / 32) | 0;
|
|
217
|
+
this.value = new Uint32Array(this.words * height);
|
|
218
|
+
this.defined = new Uint32Array(this.value.length);
|
|
219
|
+
if (data) {
|
|
220
|
+
// accept same semantics as old version
|
|
221
|
+
if (data.length !== height)
|
|
222
|
+
throw new Error(`Bitmap: data height mismatch: exp=${height} got=${data.length}`);
|
|
223
|
+
for (let y = 0; y < height; y++) {
|
|
224
|
+
const row = data[y];
|
|
225
|
+
if (!row || row.length !== width)
|
|
226
|
+
throw new Error(`Bitmap: data width mismatch at y=${y}: exp=${width} got=${row?.length}`);
|
|
227
|
+
for (let x = 0; x < width; x++)
|
|
228
|
+
this.set(x, y, row[x]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
174
231
|
}
|
|
175
232
|
point(p) {
|
|
176
|
-
return this.
|
|
233
|
+
return this.get(p.x, p.y);
|
|
177
234
|
}
|
|
178
235
|
isInside(p) {
|
|
179
236
|
return 0 <= p.x && p.x < this.width && 0 <= p.y && p.y < this.height;
|
|
@@ -196,27 +253,111 @@ export class Bitmap {
|
|
|
196
253
|
c.y = mod(c.y, this.height);
|
|
197
254
|
return c;
|
|
198
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Return pixel bit index
|
|
258
|
+
*/
|
|
259
|
+
wordIndex(x, y) {
|
|
260
|
+
return y * this.words + (x >>> 5);
|
|
261
|
+
}
|
|
262
|
+
bitIndex(x, y) {
|
|
263
|
+
return { word: this.wordIndex(x, y), bit: x & 31 };
|
|
264
|
+
}
|
|
265
|
+
isDefined(x, y) {
|
|
266
|
+
const wi = this.wordIndex(x, y);
|
|
267
|
+
const m = bitMask(x);
|
|
268
|
+
return (this.defined[wi] & m) !== 0;
|
|
269
|
+
}
|
|
270
|
+
get(x, y) {
|
|
271
|
+
const wi = this.wordIndex(x, y);
|
|
272
|
+
const m = bitMask(x);
|
|
273
|
+
return (this.value[wi] & m) !== 0;
|
|
274
|
+
}
|
|
275
|
+
maskWord(wi, mask, v) {
|
|
276
|
+
const { defined, value } = this;
|
|
277
|
+
defined[wi] |= mask;
|
|
278
|
+
value[wi] = (value[wi] & ~mask) | (-v & mask);
|
|
279
|
+
}
|
|
280
|
+
set(x, y, v) {
|
|
281
|
+
if (v === undefined)
|
|
282
|
+
return;
|
|
283
|
+
this.maskWord(this.wordIndex(x, y), bitMask(x), v);
|
|
284
|
+
}
|
|
285
|
+
// word-span fill for constant values (fast path)
|
|
286
|
+
fillRectConst(x0, y0, w, h, v) {
|
|
287
|
+
if (w <= 0 || h <= 0)
|
|
288
|
+
return;
|
|
289
|
+
if (v === undefined)
|
|
290
|
+
return;
|
|
291
|
+
const { value, defined, words } = this;
|
|
292
|
+
const startWord = x0 >>> 5;
|
|
293
|
+
const endWord = (x0 + w - 1) >>> 5;
|
|
294
|
+
const startBit = x0 & 31;
|
|
295
|
+
const endBit = (x0 + w - 1) & 31;
|
|
296
|
+
for (let ry = 0; ry < h; ry++) {
|
|
297
|
+
const rowBase = (y0 + ry) * words;
|
|
298
|
+
if (startWord === endWord) {
|
|
299
|
+
const mask = rangeMask(startBit, endBit - startBit + 1);
|
|
300
|
+
this.maskWord(rowBase + startWord, mask, v);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
this.maskWord(rowBase + startWord, rangeMask(startBit, 32 - startBit), v);
|
|
304
|
+
for (let i = startWord + 1; i < endWord; i++) {
|
|
305
|
+
defined[rowBase + i] = 0xffffffff;
|
|
306
|
+
value[rowBase + i] = v ? 0xffffffff : 0;
|
|
307
|
+
}
|
|
308
|
+
this.maskWord(rowBase + endWord, rangeMask(0, endBit + 1), v);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
rectWords(x, y, width, height, cb) {
|
|
312
|
+
for (let yPos = 0; yPos < height; yPos++) {
|
|
313
|
+
const Py = y + yPos;
|
|
314
|
+
for (let xPos = 0; xPos < width;) {
|
|
315
|
+
const bitX = x + xPos;
|
|
316
|
+
const { bit, word } = this.bitIndex(bitX, Py);
|
|
317
|
+
const bitsPerWord = Math.min(32 - bit, width - xPos);
|
|
318
|
+
cb(word, bitX, xPos, yPos, bitsPerWord);
|
|
319
|
+
xPos += bitsPerWord;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
199
323
|
// Basically every operation can be represented as rect
|
|
200
|
-
rect(c, size,
|
|
324
|
+
rect(c, size, fn) {
|
|
201
325
|
const { x, y } = this.xy(c);
|
|
202
326
|
const { height, width } = Bitmap.size(size, this.size({ x, y }));
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.data[y + yPos][x + xPos] =
|
|
207
|
-
typeof value === 'function'
|
|
208
|
-
? value({ x: xPos, y: yPos }, this.data[y + yPos][x + xPos])
|
|
209
|
-
: value;
|
|
210
|
-
}
|
|
327
|
+
if (typeof fn !== 'function') {
|
|
328
|
+
this.fillRectConst(x, y, width, height, fn);
|
|
329
|
+
return this;
|
|
211
330
|
}
|
|
331
|
+
const { defined, value } = this;
|
|
332
|
+
this.rectWords(x, y, width, height, (wi, bitX, xPos, yPos, n) => {
|
|
333
|
+
let defWord = 0;
|
|
334
|
+
let valWord = value[wi];
|
|
335
|
+
for (let b = 0; b < n; b++) {
|
|
336
|
+
const mask = bitMask(bitX + b);
|
|
337
|
+
const res = fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
|
|
338
|
+
if (res === undefined)
|
|
339
|
+
continue;
|
|
340
|
+
defWord |= mask;
|
|
341
|
+
valWord = (valWord & ~mask) | (-res & mask);
|
|
342
|
+
}
|
|
343
|
+
defined[wi] |= defWord;
|
|
344
|
+
value[wi] = valWord;
|
|
345
|
+
});
|
|
212
346
|
return this;
|
|
213
347
|
}
|
|
214
348
|
// returns rectangular part of bitmap
|
|
215
349
|
rectRead(c, size, fn) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
350
|
+
const { x, y } = this.xy(c);
|
|
351
|
+
const { height, width } = Bitmap.size(size, this.size({ x, y }));
|
|
352
|
+
const { value } = this;
|
|
353
|
+
this.rectWords(x, y, width, height, (wi, bitX, xPos, yPos, n) => {
|
|
354
|
+
const valWord = value[wi];
|
|
355
|
+
for (let b = 0; b < n; b++) {
|
|
356
|
+
const mask = bitMask(bitX + b);
|
|
357
|
+
fn({ x: xPos + b, y: yPos }, (valWord & mask) !== 0);
|
|
358
|
+
}
|
|
219
359
|
});
|
|
360
|
+
return this;
|
|
220
361
|
}
|
|
221
362
|
// Horizontal & vertical lines
|
|
222
363
|
hLine(c, len, value) {
|
|
@@ -229,25 +370,93 @@ export class Bitmap {
|
|
|
229
370
|
border(border = 2, value) {
|
|
230
371
|
const height = this.height + 2 * border;
|
|
231
372
|
const width = this.width + 2 * border;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
373
|
+
const out = new Bitmap({ height, width });
|
|
374
|
+
// fill everything with border value, then embed original
|
|
375
|
+
out.rect(0, Infinity, value);
|
|
376
|
+
out.embed({ x: border, y: border }, this);
|
|
377
|
+
return out;
|
|
235
378
|
}
|
|
236
379
|
// Embed another bitmap on coordinates
|
|
237
|
-
embed(c,
|
|
238
|
-
|
|
380
|
+
embed(c, src) {
|
|
381
|
+
const { x, y } = this.xy(c);
|
|
382
|
+
const { height, width } = Bitmap.size(src.size(), this.size({ x, y }));
|
|
383
|
+
if (width <= 0 || height <= 0)
|
|
384
|
+
return this;
|
|
385
|
+
const { value, defined } = this;
|
|
386
|
+
const { words: srcStride, value: srcValue } = src;
|
|
387
|
+
for (let yPos = 0; yPos < height; yPos++) {
|
|
388
|
+
const srcRow = yPos * srcStride;
|
|
389
|
+
for (let xPos = 0; xPos < width;) {
|
|
390
|
+
const dstX = x + xPos;
|
|
391
|
+
const { word: dstWord, bit: dstBit } = this.bitIndex(dstX, y + yPos);
|
|
392
|
+
const { word: srcWord, bit: srcBit } = src.bitIndex(xPos, yPos);
|
|
393
|
+
const len = Math.min(32 - dstBit, width - xPos);
|
|
394
|
+
const w0 = srcValue[srcWord];
|
|
395
|
+
const w1 = srcBit && srcWord + 1 < srcRow + srcStride ? srcValue[srcWord + 1] : 0;
|
|
396
|
+
const sVal = srcBit ? ((w0 >>> srcBit) | (w1 << (32 - srcBit))) >>> 0 : w0;
|
|
397
|
+
const dstMask = rangeMask(dstBit, len);
|
|
398
|
+
const valBits = ((sVal & rangeMask(0, len)) << dstBit) >>> 0;
|
|
399
|
+
defined[dstWord] |= dstMask;
|
|
400
|
+
value[dstWord] = (value[dstWord] & ~dstMask) | valBits;
|
|
401
|
+
xPos += len;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return this;
|
|
239
405
|
}
|
|
240
406
|
// returns rectangular part of bitmap
|
|
241
407
|
rectSlice(c, size = this.size()) {
|
|
242
|
-
const
|
|
243
|
-
|
|
408
|
+
const { x, y } = this.xy(c);
|
|
409
|
+
const { height, width } = Bitmap.size(size, this.size({ x, y }));
|
|
410
|
+
const rect = new Bitmap({ height, width });
|
|
411
|
+
this.rectRead({ x, y }, { height, width }, (p, cur) => {
|
|
412
|
+
if (this.isDefined(x + p.x, y + p.y)) {
|
|
413
|
+
rect.set(p.x, p.y, cur);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
244
416
|
return rect;
|
|
245
417
|
}
|
|
246
418
|
// Change shape, replace rows with columns (data[y][x] -> data[x][y])
|
|
247
|
-
|
|
248
|
-
const { height, width } = this;
|
|
249
|
-
const
|
|
250
|
-
|
|
419
|
+
transpose() {
|
|
420
|
+
const { height, width, value, defined, words } = this;
|
|
421
|
+
const dst = new Bitmap({ height: width, width: height });
|
|
422
|
+
const { words: dstStride, value: dstValue, defined: dstDefined, tailMask: dstTail } = dst;
|
|
423
|
+
const tmpV = new Uint32Array(32);
|
|
424
|
+
const tmpD = new Uint32Array(32);
|
|
425
|
+
// Process src in blocks: y in [by..by+31], x in 32-bit words
|
|
426
|
+
for (let by = 0; by < height; by += 32) {
|
|
427
|
+
for (let bx = 0; bx < words; bx++) {
|
|
428
|
+
const rows = Math.min(32, height - by);
|
|
429
|
+
for (let r = 0; r < rows; r++) {
|
|
430
|
+
const wi = this.wordIndex(32 * bx, by + r);
|
|
431
|
+
tmpV[r] = value[wi];
|
|
432
|
+
tmpD[r] = defined[wi];
|
|
433
|
+
}
|
|
434
|
+
// zero-pad remainder
|
|
435
|
+
tmpV.fill(0, rows);
|
|
436
|
+
tmpD.fill(0, rows);
|
|
437
|
+
transpose32(tmpV);
|
|
438
|
+
transpose32(tmpD);
|
|
439
|
+
for (let i = 0; i < 32; i++) {
|
|
440
|
+
const dstY = bx * 32 + i;
|
|
441
|
+
if (dstY >= width)
|
|
442
|
+
break;
|
|
443
|
+
const dstPos = dst.wordIndex(by, dstY);
|
|
444
|
+
const curMask = by >>> 5 === dstStride - 1 ? dstTail : 0xffffffff;
|
|
445
|
+
dstValue[dstPos] = tmpV[i] & curMask;
|
|
446
|
+
dstDefined[dstPos] = tmpD[i] & curMask;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return dst;
|
|
451
|
+
}
|
|
452
|
+
// black <-> white (inplace)
|
|
453
|
+
negate() {
|
|
454
|
+
const n = this.defined.length;
|
|
455
|
+
for (let i = 0; i < n; i++) {
|
|
456
|
+
this.value[i] = ~this.value[i];
|
|
457
|
+
this.defined[i] = 0xffffffff;
|
|
458
|
+
}
|
|
459
|
+
return this;
|
|
251
460
|
}
|
|
252
461
|
// Each pixel size is multiplied by factor
|
|
253
462
|
scale(factor) {
|
|
@@ -255,34 +464,150 @@ export class Bitmap {
|
|
|
255
464
|
throw new Error(`invalid scale factor: ${factor}`);
|
|
256
465
|
const { height, width } = this;
|
|
257
466
|
const res = new Bitmap({ height: factor * height, width: factor * width });
|
|
258
|
-
return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.
|
|
467
|
+
return res.rect({ x: 0, y: 0 }, Infinity, ({ x, y }) => this.get((x / factor) | 0, (y / factor) | 0));
|
|
259
468
|
}
|
|
260
469
|
clone() {
|
|
261
470
|
const res = new Bitmap(this.size());
|
|
262
|
-
|
|
471
|
+
res.defined.set(this.defined);
|
|
472
|
+
res.value.set(this.value);
|
|
473
|
+
return res;
|
|
263
474
|
}
|
|
264
475
|
// Ensure that there is no undefined values left
|
|
265
476
|
assertDrawn() {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
477
|
+
const { height, width, defined, tailMask, fullWords, words } = this;
|
|
478
|
+
if (!height || !width)
|
|
479
|
+
return;
|
|
480
|
+
for (let y = 0; y < height; y++) {
|
|
481
|
+
const rowBase = y * words;
|
|
482
|
+
for (let wi = 0; wi < fullWords; wi++) {
|
|
483
|
+
if (defined[rowBase + wi] !== 0xffffffff)
|
|
484
|
+
throw new Error(`Invalid color type=undefined`);
|
|
485
|
+
}
|
|
486
|
+
if (words !== fullWords && (defined[rowBase + fullWords] & tailMask) !== tailMask)
|
|
487
|
+
throw new Error(`Invalid color type=undefined`);
|
|
488
|
+
}
|
|
270
489
|
}
|
|
271
|
-
|
|
490
|
+
countPatternInRow(y, patternLen, ...patterns) {
|
|
491
|
+
if (patternLen <= 0 || patternLen >= 32)
|
|
492
|
+
throw new Error('wrong patternLen');
|
|
493
|
+
const mask = (1 << patternLen) - 1;
|
|
494
|
+
const { width, value, words } = this;
|
|
495
|
+
let count = 0;
|
|
496
|
+
const rowBase = this.wordIndex(0, y);
|
|
497
|
+
for (let i = 0, window = 0; i < words; i++) {
|
|
498
|
+
const w = value[rowBase + i];
|
|
499
|
+
const bitEnd = i === words - 1 ? width & 31 || 32 : 32;
|
|
500
|
+
for (let b = 0; b < bitEnd; b++) {
|
|
501
|
+
window = ((window << 1) | ((w >>> b) & 1)) & mask;
|
|
502
|
+
if (i * 32 + b + 1 < patternLen)
|
|
503
|
+
continue;
|
|
504
|
+
for (const p of patterns) {
|
|
505
|
+
if (window !== p)
|
|
506
|
+
continue;
|
|
507
|
+
count++;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return count;
|
|
513
|
+
}
|
|
514
|
+
getRuns(y, fn) {
|
|
515
|
+
const { width, value, words } = this;
|
|
516
|
+
if (width === 0)
|
|
517
|
+
return;
|
|
518
|
+
let runLen = 0;
|
|
519
|
+
let runValue;
|
|
520
|
+
const rowBase = this.wordIndex(0, y);
|
|
521
|
+
for (let i = 0; i < words; i++) {
|
|
522
|
+
const word = value[rowBase + i];
|
|
523
|
+
const bitEnd = i === words - 1 ? width & 31 || 32 : 32;
|
|
524
|
+
for (let b = 0; b < bitEnd; b++) {
|
|
525
|
+
const bit = (word & (1 << b)) !== 0;
|
|
526
|
+
if (bit === runValue) {
|
|
527
|
+
runLen++;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (runValue !== undefined)
|
|
531
|
+
fn(runLen, runValue);
|
|
532
|
+
runValue = bit;
|
|
533
|
+
runLen = 1;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (runValue !== undefined)
|
|
537
|
+
fn(runLen, runValue);
|
|
538
|
+
}
|
|
539
|
+
popcnt() {
|
|
540
|
+
const { height, width, words, fullWords, tailMask } = this;
|
|
541
|
+
if (!height || !width)
|
|
542
|
+
return 0;
|
|
543
|
+
let count = 0;
|
|
544
|
+
for (let y = 0; y < height; y++) {
|
|
545
|
+
const rowBase = y * words;
|
|
546
|
+
for (let wi = 0; wi < fullWords; wi++)
|
|
547
|
+
count += popcnt(this.value[rowBase + wi]);
|
|
548
|
+
if (words !== fullWords)
|
|
549
|
+
count += popcnt(this.value[rowBase + fullWords] & tailMask);
|
|
550
|
+
}
|
|
551
|
+
return count;
|
|
552
|
+
}
|
|
553
|
+
countBoxes2x2(y) {
|
|
554
|
+
const { width, words } = this;
|
|
555
|
+
if (width < 2 || (y | 0) < 0 || y + 1 >= this.height)
|
|
556
|
+
return 0;
|
|
557
|
+
const base0 = this.wordIndex(0, y) | 0;
|
|
558
|
+
const base1 = this.wordIndex(0, y + 1) | 0;
|
|
559
|
+
// valid "left-edge" positions x in [0 .. W-2]
|
|
560
|
+
const tailBits = width & 31;
|
|
561
|
+
const validLast = tailBits === 0 ? 0x7fffffff : rangeMask(0, (width - 1) & 31);
|
|
562
|
+
let boxes = 0;
|
|
563
|
+
for (let wi = 0; wi < words; wi++) {
|
|
564
|
+
const a0 = this.value[base0 + wi];
|
|
565
|
+
const a1 = this.value[base1 + wi];
|
|
566
|
+
// Compare bit x with bit x+1 at same bit position.
|
|
567
|
+
const eqV = ~(a0 ^ a1) >>> 0; // row0[x] == row1[x]
|
|
568
|
+
const n0 = wi + 1 < words ? this.value[base0 + wi + 1] >>> 0 : 0;
|
|
569
|
+
const eqH0 = ~(a0 ^ (((a0 >>> 1) | ((n0 & 1) << 31)) >>> 0)) >>> 0; // row0[x] == row0[x+1]
|
|
570
|
+
const n1 = wi + 1 < words ? this.value[base1 + wi + 1] >>> 0 : 0;
|
|
571
|
+
const eqH1 = ~(a1 ^ (((a1 >>> 1) | ((n1 & 1) << 31)) >>> 0)) >>> 0; // row1[x] == row1[x+1]
|
|
572
|
+
let m = (eqV & eqH0 & eqH1) >>> 0;
|
|
573
|
+
if (wi === words - 1)
|
|
574
|
+
m &= validLast;
|
|
575
|
+
boxes += popcnt(m);
|
|
576
|
+
}
|
|
577
|
+
return boxes;
|
|
578
|
+
}
|
|
579
|
+
// Export
|
|
272
580
|
toString() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
581
|
+
const nl = String.fromCharCode(chCodes.newline);
|
|
582
|
+
let out = '';
|
|
583
|
+
for (let y = 0; y < this.height; y++) {
|
|
584
|
+
let line = '';
|
|
585
|
+
for (let x = 0; x < this.width; x++) {
|
|
586
|
+
const v = this.get(x, y);
|
|
587
|
+
line += !this.isDefined(x, y) ? '?' : v ? 'X' : ' ';
|
|
588
|
+
}
|
|
589
|
+
out += line + (y + 1 === this.height ? '' : nl);
|
|
590
|
+
}
|
|
591
|
+
return out;
|
|
592
|
+
}
|
|
593
|
+
toRaw() {
|
|
594
|
+
const out = Array.from({ length: this.height }, () => new Array(this.width));
|
|
595
|
+
for (let y = 0; y < this.height; y++) {
|
|
596
|
+
const row = out[y];
|
|
597
|
+
for (let x = 0; x < this.width; x++)
|
|
598
|
+
row[x] = this.get(x, y);
|
|
599
|
+
}
|
|
600
|
+
return out;
|
|
276
601
|
}
|
|
277
602
|
toASCII() {
|
|
278
|
-
const { height, width
|
|
603
|
+
const { height, width } = this;
|
|
279
604
|
let out = '';
|
|
280
605
|
// Terminal character height is x2 of character width, so we process two rows of bitmap
|
|
281
606
|
// to produce one row of ASCII
|
|
282
607
|
for (let y = 0; y < height; y += 2) {
|
|
283
608
|
for (let x = 0; x < width; x++) {
|
|
284
|
-
const first =
|
|
285
|
-
const second = y + 1 >= height ? true :
|
|
609
|
+
const first = this.get(x, y);
|
|
610
|
+
const second = y + 1 >= height ? true : this.get(x, y + 1); // if last row outside bitmap, make it black
|
|
286
611
|
if (!first && !second)
|
|
287
612
|
out += '█'; // both rows white (empty)
|
|
288
613
|
else if (!first && second)
|
|
@@ -301,9 +626,16 @@ export class Bitmap {
|
|
|
301
626
|
const reset = cc + '[0m';
|
|
302
627
|
const whiteBG = cc + '[1;47m ' + reset;
|
|
303
628
|
const darkBG = cc + `[40m ` + reset;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
629
|
+
const nl = String.fromCharCode(chCodes.newline);
|
|
630
|
+
let out = '';
|
|
631
|
+
for (let y = 0; y < this.height; y++) {
|
|
632
|
+
for (let x = 0; x < this.width; x++) {
|
|
633
|
+
const v = this.get(x, y); // undefined -> white
|
|
634
|
+
out += v ? darkBG : whiteBG;
|
|
635
|
+
}
|
|
636
|
+
out += nl;
|
|
637
|
+
}
|
|
638
|
+
return out;
|
|
307
639
|
}
|
|
308
640
|
toSVG(optimize = true) {
|
|
309
641
|
let out = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`;
|
|
@@ -372,7 +704,7 @@ export class Bitmap {
|
|
|
372
704
|
let i = 0;
|
|
373
705
|
for (let y = 0; y < height; y++) {
|
|
374
706
|
for (let x = 0; x < width; x++) {
|
|
375
|
-
const value =
|
|
707
|
+
const value = this.get(x, y) ? 0 : 255; // undefined -> white
|
|
376
708
|
data[i++] = value;
|
|
377
709
|
data[i++] = value;
|
|
378
710
|
data[i++] = value;
|
|
@@ -740,8 +1072,8 @@ function interleave(ver, ecc) {
|
|
|
740
1072
|
eccBlocks.push(rs.encode(bytes.subarray(0, len)));
|
|
741
1073
|
bytes = bytes.subarray(len);
|
|
742
1074
|
}
|
|
743
|
-
const resBlocks = interleaveBytes(
|
|
744
|
-
const resECC = interleaveBytes(
|
|
1075
|
+
const resBlocks = interleaveBytes(blocks);
|
|
1076
|
+
const resECC = interleaveBytes(eccBlocks);
|
|
745
1077
|
const res = new Uint8Array(resBlocks.length + resECC.length);
|
|
746
1078
|
res.set(resBlocks);
|
|
747
1079
|
res.set(resECC, resBlocks.length);
|
|
@@ -798,36 +1130,36 @@ function drawTemplate(ver, ecc, maskIdx, test = false) {
|
|
|
798
1130
|
const alignPos = info.alignmentPatterns(ver);
|
|
799
1131
|
for (const y of alignPos) {
|
|
800
1132
|
for (const x of alignPos) {
|
|
801
|
-
if (b.
|
|
1133
|
+
if (b.isDefined(x, y))
|
|
802
1134
|
continue;
|
|
803
1135
|
b.embed({ x: x - 2, y: y - 2 }, align); // center of pattern should be at position
|
|
804
1136
|
}
|
|
805
1137
|
}
|
|
806
1138
|
// Timing patterns
|
|
807
1139
|
b = b
|
|
808
|
-
.hLine({ x: 0, y: 6 }, Infinity, ({ x }
|
|
809
|
-
.vLine({ x: 6, y: 0 }, Infinity, ({ y }
|
|
1140
|
+
.hLine({ x: 0, y: 6 }, Infinity, ({ x }) => (b.isDefined(x, 6) ? undefined : x % 2 == 0))
|
|
1141
|
+
.vLine({ x: 6, y: 0 }, Infinity, ({ y }) => (b.isDefined(6, y) ? undefined : y % 2 == 0));
|
|
810
1142
|
// Format information
|
|
811
1143
|
{
|
|
812
1144
|
const bits = info.formatBits(ecc, maskIdx);
|
|
813
1145
|
const getBit = (i) => !test && ((bits >> i) & 1) == 1;
|
|
814
1146
|
// vertical
|
|
815
1147
|
for (let i = 0; i < 6; i++)
|
|
816
|
-
b.
|
|
1148
|
+
b.set(8, i, getBit(i)); // right of top-left finder
|
|
817
1149
|
// TODO: re-write as lines, like:
|
|
818
1150
|
// b.vLine({ x: 8, y: 0 }, 6, ({ x, y }) => getBit(y));
|
|
819
1151
|
for (let i = 6; i < 8; i++)
|
|
820
|
-
b.
|
|
1152
|
+
b.set(8, i + 1, getBit(i)); // after timing pattern
|
|
821
1153
|
for (let i = 8; i < 15; i++)
|
|
822
|
-
b.
|
|
1154
|
+
b.set(8, size - 15 + i, getBit(i)); // right of bottom-left finder
|
|
823
1155
|
// horizontal
|
|
824
1156
|
for (let i = 0; i < 8; i++)
|
|
825
|
-
b.
|
|
1157
|
+
b.set(size - i - 1, 8, getBit(i)); // under top-right finder
|
|
826
1158
|
for (let i = 8; i < 9; i++)
|
|
827
|
-
b.
|
|
1159
|
+
b.set(15 - i - 1 + 1, 8, getBit(i)); // VVV, after timing
|
|
828
1160
|
for (let i = 9; i < 15; i++)
|
|
829
|
-
b.
|
|
830
|
-
b.
|
|
1161
|
+
b.set(15 - i - 1, 8, getBit(i)); // under top-left finder
|
|
1162
|
+
b.set(8, size - 8, !test); // bottom-left finder, right
|
|
831
1163
|
}
|
|
832
1164
|
// Version information
|
|
833
1165
|
if (ver >= 7) {
|
|
@@ -837,8 +1169,8 @@ function drawTemplate(ver, ecc, maskIdx, test = false) {
|
|
|
837
1169
|
const x = Math.floor(i / 3);
|
|
838
1170
|
const y = (i % 3) + size - 8 - 3;
|
|
839
1171
|
// two copies
|
|
840
|
-
b.
|
|
841
|
-
b.
|
|
1172
|
+
b.set(y, x, bit);
|
|
1173
|
+
b.set(x, y, bit);
|
|
842
1174
|
}
|
|
843
1175
|
}
|
|
844
1176
|
return b;
|
|
@@ -857,7 +1189,7 @@ function zigzag(tpl, maskIdx, fn) {
|
|
|
857
1189
|
for (;; y += dir) {
|
|
858
1190
|
for (let j = 0; j < 2; j += 1) {
|
|
859
1191
|
const x = xOffset - j;
|
|
860
|
-
if (tpl.
|
|
1192
|
+
if (tpl.isDefined(x, y))
|
|
861
1193
|
continue; // skip already written elements
|
|
862
1194
|
fn(x, y, pattern(x, y));
|
|
863
1195
|
}
|
|
@@ -888,7 +1220,7 @@ export function utf8ToBytes(str) {
|
|
|
888
1220
|
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
889
1221
|
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
890
1222
|
}
|
|
891
|
-
function encode(ver, ecc, data, type) {
|
|
1223
|
+
function encode(ver, ecc, data, type, encoder = utf8ToBytes) {
|
|
892
1224
|
let encoded = '';
|
|
893
1225
|
let dataLen = data.length;
|
|
894
1226
|
if (type === 'numeric') {
|
|
@@ -912,7 +1244,7 @@ function encode(ver, ecc, data, type) {
|
|
|
912
1244
|
encoded += bin(t[n - 1], 6); // pad if odd number of chars
|
|
913
1245
|
}
|
|
914
1246
|
else if (type === 'byte') {
|
|
915
|
-
const utf8 =
|
|
1247
|
+
const utf8 = encoder(data);
|
|
916
1248
|
dataLen = utf8.length;
|
|
917
1249
|
encoded = Array.from(utf8)
|
|
918
1250
|
.map((i) => bin(i, 8))
|
|
@@ -950,73 +1282,57 @@ function drawQR(ver, ecc, data, maskIdx, test = false) {
|
|
|
950
1282
|
value = ((data[i >>> 3] >> ((7 - i) & 7)) & 1) !== 0;
|
|
951
1283
|
i++;
|
|
952
1284
|
}
|
|
953
|
-
b.
|
|
1285
|
+
b.set(x, y, value !== mask); // !== as xor
|
|
954
1286
|
});
|
|
955
1287
|
if (i !== need)
|
|
956
1288
|
throw new Error('QR: bytes left after draw');
|
|
957
1289
|
return b;
|
|
958
1290
|
}
|
|
1291
|
+
const mkPattern = (pattern) => {
|
|
1292
|
+
const s = pattern.map((i) => (i ? '1' : '0')).join('');
|
|
1293
|
+
return { len: s.length, n: Number(`0b${s}`) };
|
|
1294
|
+
};
|
|
1295
|
+
// 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide
|
|
1296
|
+
const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
|
|
1297
|
+
const lightPattern = [false, false, false, false]; // light area 4 modules wide
|
|
1298
|
+
const P1 = mkPattern([...finderPattern, ...lightPattern]);
|
|
1299
|
+
const P2 = mkPattern([...lightPattern, ...finderPattern]);
|
|
959
1300
|
function penalty(bm) {
|
|
960
|
-
const
|
|
1301
|
+
const { width, height } = bm;
|
|
1302
|
+
const transposed = bm.transpose();
|
|
961
1303
|
// Adjacent modules in row/column in same | No. of modules = (5 + i) color
|
|
962
|
-
const sameColor = (row) => {
|
|
963
|
-
let res = 0;
|
|
964
|
-
for (let i = 0, same = 1, last = undefined; i < row.length; i++) {
|
|
965
|
-
if (last === row[i]) {
|
|
966
|
-
same++;
|
|
967
|
-
if (i !== row.length - 1)
|
|
968
|
-
continue; // handle last element
|
|
969
|
-
}
|
|
970
|
-
if (same >= 5)
|
|
971
|
-
res += 3 + (same - 5);
|
|
972
|
-
last = row[i];
|
|
973
|
-
same = 1;
|
|
974
|
-
}
|
|
975
|
-
return res;
|
|
976
|
-
};
|
|
977
1304
|
let adjacent = 0;
|
|
978
|
-
|
|
979
|
-
|
|
1305
|
+
for (let y = 0; y < height; y++) {
|
|
1306
|
+
bm.getRuns(y, (len) => {
|
|
1307
|
+
if (len >= 5)
|
|
1308
|
+
adjacent += 3 + (len - 5);
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
for (let y = 0; y < width; y++) {
|
|
1312
|
+
transposed.getRuns(y, (len) => {
|
|
1313
|
+
if (len >= 5)
|
|
1314
|
+
adjacent += 3 + (len - 5);
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
980
1317
|
// Block of modules in same color (Block size = 2x2)
|
|
981
1318
|
let box = 0;
|
|
982
|
-
let
|
|
983
|
-
|
|
984
|
-
const lastH = bm.height - 1;
|
|
985
|
-
for (let x = 0; x < lastW; x++) {
|
|
986
|
-
for (let y = 0; y < lastH; y++) {
|
|
987
|
-
const x1 = x + 1;
|
|
988
|
-
const y1 = y + 1;
|
|
989
|
-
if (b[x][y] === b[x1][y] && b[x1][y] === b[x][y1] && b[x1][y] === b[x1][y1]) {
|
|
990
|
-
box += 3;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
// 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide
|
|
995
|
-
const finderPattern = (row) => {
|
|
996
|
-
const finderPattern = [true, false, true, true, true, false, true]; // dark:light:dark:light:dark
|
|
997
|
-
const lightPattern = [false, false, false, false]; // light area 4 modules wide
|
|
998
|
-
const p1 = [...finderPattern, ...lightPattern];
|
|
999
|
-
const p2 = [...lightPattern, ...finderPattern];
|
|
1000
|
-
let res = 0;
|
|
1001
|
-
for (let i = 0; i < row.length; i++) {
|
|
1002
|
-
if (includesAt(row, p1, i))
|
|
1003
|
-
res += 40;
|
|
1004
|
-
if (includesAt(row, p2, i))
|
|
1005
|
-
res += 40;
|
|
1006
|
-
}
|
|
1007
|
-
return res;
|
|
1008
|
-
};
|
|
1319
|
+
for (let y = 0; y < height - 1; y++)
|
|
1320
|
+
box += 3 * bm.countBoxes2x2(y);
|
|
1009
1321
|
let finder = 0;
|
|
1010
|
-
for (
|
|
1011
|
-
finder +=
|
|
1012
|
-
for (
|
|
1013
|
-
finder +=
|
|
1322
|
+
for (let y = 0; y < height; y++)
|
|
1323
|
+
finder += 40 * bm.countPatternInRow(y, P1.len, P1.n, P2.n);
|
|
1324
|
+
for (let y = 0; y < width; y++)
|
|
1325
|
+
finder += 40 * transposed.countPatternInRow(y, P1.len, P1.n, P2.n);
|
|
1014
1326
|
// Proportion of dark modules in entire symbol
|
|
1015
1327
|
// Add 10 points to a deviation of 5% increment or decrement in the proportion
|
|
1016
1328
|
// ratio of dark module from the referential 50%
|
|
1017
1329
|
let darkPixels = 0;
|
|
1018
|
-
bm.
|
|
1019
|
-
|
|
1330
|
+
darkPixels = bm.popcnt();
|
|
1331
|
+
//bm.rectRead(0, Infinity, (_c, val) => (darkPixels += val ? 1 : 0));
|
|
1332
|
+
// for (let y = 0; y < height; y++) {
|
|
1333
|
+
// for (let x = 0; x < width; x++) if (bm.get(x, y)) darkPixels++;
|
|
1334
|
+
// }
|
|
1335
|
+
const darkPercent = (darkPixels / (height * width)) * 100;
|
|
1020
1336
|
const dark = 10 * Math.floor(Math.abs(darkPercent - 50) / 5);
|
|
1021
1337
|
return adjacent + box + finder + dark;
|
|
1022
1338
|
}
|
|
@@ -1057,14 +1373,14 @@ export function encodeQR(text, output = 'raw', opts = {}) {
|
|
|
1057
1373
|
let data, err = new Error('Unknown error');
|
|
1058
1374
|
if (ver !== undefined) {
|
|
1059
1375
|
validateVersion(ver);
|
|
1060
|
-
data = encode(ver, ecc, text, encoding);
|
|
1376
|
+
data = encode(ver, ecc, text, encoding, opts.textEncoder);
|
|
1061
1377
|
}
|
|
1062
1378
|
else {
|
|
1063
1379
|
// If no version is provided, try to find smallest one which fits
|
|
1064
1380
|
// Currently just scans all version, can be significantly speedup if needed
|
|
1065
1381
|
for (let i = 1; i <= 40; i++) {
|
|
1066
1382
|
try {
|
|
1067
|
-
data = encode(i, ecc, text, encoding);
|
|
1383
|
+
data = encode(i, ecc, text, encoding, opts.textEncoder);
|
|
1068
1384
|
ver = i;
|
|
1069
1385
|
break;
|
|
1070
1386
|
}
|
|
@@ -1084,7 +1400,7 @@ export function encodeQR(text, output = 'raw', opts = {}) {
|
|
|
1084
1400
|
if (opts.scale !== undefined)
|
|
1085
1401
|
res = res.scale(opts.scale); // Scale image
|
|
1086
1402
|
if (output === 'raw')
|
|
1087
|
-
return res.
|
|
1403
|
+
return res.toRaw();
|
|
1088
1404
|
else if (output === 'ascii')
|
|
1089
1405
|
return res.toASCII();
|
|
1090
1406
|
else if (output === 'svg')
|
|
@@ -1100,6 +1416,7 @@ export default encodeQR;
|
|
|
1100
1416
|
export const utils = {
|
|
1101
1417
|
best,
|
|
1102
1418
|
bin,
|
|
1419
|
+
popcnt,
|
|
1103
1420
|
drawTemplate,
|
|
1104
1421
|
fillArr,
|
|
1105
1422
|
info,
|