@sythos/js_barcode_universal 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -425
- package/bundle/sythos-barcode.esm.js +3089 -1181
- package/bundle/sythos-barcode.js +3081 -1181
- package/examples/create.html +732 -730
- package/licenses/README.md +2 -0
- package/licenses/aztec-code.license +74 -0
- package/licenses/data-matrix.license +82 -0
- package/package.json +94 -88
- package/src/aztec/decoder.js +317 -0
- package/src/aztec/detector.js +224 -0
- package/src/aztec/encoder.js +257 -0
- package/src/aztec/high-level.js +211 -0
- package/src/aztec/index.js +45 -0
- package/src/aztec/tables.js +210 -0
- package/src/core/galois-field.js +3 -0
- package/src/core/reed-solomon.js +313 -313
- package/src/datamatrix/decoder.js +262 -0
- package/src/datamatrix/detector.js +225 -0
- package/src/datamatrix/encoder.js +191 -0
- package/src/datamatrix/index.js +42 -0
- package/src/datamatrix/tables.js +123 -0
- package/src/index.js +70 -2
package/bundle/sythos-barcode.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Sythos Barcode Suite
|
|
2
|
+
* Sythos Barcode Suite v1.1.0
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
5
|
*
|
|
@@ -2752,155 +2752,6 @@ __modules["oned/index.js"] = function (__require, __exports) {
|
|
|
2752
2752
|
__exports.ONED_FORMATS = ONED_FORMATS;
|
|
2753
2753
|
};
|
|
2754
2754
|
|
|
2755
|
-
__modules["core/bit-buffer.js"] = function (__require, __exports) {
|
|
2756
|
-
/**
|
|
2757
|
-
* Bit-level writing and reading, MSB-first.
|
|
2758
|
-
*
|
|
2759
|
-
* Every 2D symbology serialises its payload as a bitstream that does not
|
|
2760
|
-
* respect byte boundaries — QR alone mixes 4-bit mode indicators, 10-bit
|
|
2761
|
-
* character-count fields and 11-bit alphanumeric pairs. These two classes are
|
|
2762
|
-
* the write and read halves of that.
|
|
2763
|
-
*
|
|
2764
|
-
* @module core/bit-buffer
|
|
2765
|
-
*/
|
|
2766
|
-
const { FormatError } = __require("core/errors.js");
|
|
2767
|
-
|
|
2768
|
-
/** Growable MSB-first bit writer. */
|
|
2769
|
-
class BitWriter {
|
|
2770
|
-
constructor() {
|
|
2771
|
-
/** @type {number[]} Packed bytes; the last one may be partially filled. */
|
|
2772
|
-
this.bytes = [];
|
|
2773
|
-
this.bitLength = 0;
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
/** @returns {number} Bits written so far. */
|
|
2777
|
-
get length() {
|
|
2778
|
-
return this.bitLength;
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
/**
|
|
2782
|
-
* Append the low `count` bits of `value`, most significant first.
|
|
2783
|
-
*
|
|
2784
|
-
* @param {number} value
|
|
2785
|
-
* @param {number} count
|
|
2786
|
-
*/
|
|
2787
|
-
put(value, count) {
|
|
2788
|
-
for (let i = count - 1; i >= 0; i--) {
|
|
2789
|
-
this.putBit(((value >>> i) & 1) === 1);
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
|
|
2793
|
-
/** @param {boolean} bit */
|
|
2794
|
-
putBit(bit) {
|
|
2795
|
-
const idx = this.bitLength >>> 3;
|
|
2796
|
-
if (this.bytes.length <= idx) this.bytes.push(0);
|
|
2797
|
-
if (bit) this.bytes[idx] |= 0x80 >>> (this.bitLength & 7);
|
|
2798
|
-
this.bitLength++;
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
|
-
/** @param {ArrayLike<number>} data */
|
|
2802
|
-
putBytes(data) {
|
|
2803
|
-
for (let i = 0; i < data.length; i++) this.put(data[i], 8);
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
/** Pad with zero bits until the length is a multiple of 8. */
|
|
2807
|
-
padToByte() {
|
|
2808
|
-
while (this.bitLength & 7) this.putBit(false);
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
/**
|
|
2812
|
-
* @returns {Uint8Array} Byte view; trailing bits of the final byte are zero.
|
|
2813
|
-
*/
|
|
2814
|
-
toBytes() {
|
|
2815
|
-
return Uint8Array.from(this.bytes);
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
|
-
/** @returns {string} Debug view, e.g. "0100 0011 0101". */
|
|
2819
|
-
toString() {
|
|
2820
|
-
let s = '';
|
|
2821
|
-
for (let i = 0; i < this.bitLength; i++) {
|
|
2822
|
-
if (i && i % 4 === 0) s += ' ';
|
|
2823
|
-
s += (this.bytes[i >>> 3] >>> (7 - (i & 7))) & 1;
|
|
2824
|
-
}
|
|
2825
|
-
return s;
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
/** MSB-first bit reader over a byte array. */
|
|
2830
|
-
class BitReader {
|
|
2831
|
-
/** @param {ArrayLike<number>} bytes */
|
|
2832
|
-
constructor(bytes) {
|
|
2833
|
-
this.bytes = bytes;
|
|
2834
|
-
this.byteOffset = 0;
|
|
2835
|
-
this.bitOffset = 0;
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
/** @returns {number} Bits not yet consumed. */
|
|
2839
|
-
available() {
|
|
2840
|
-
return 8 * (this.bytes.length - this.byteOffset) - this.bitOffset;
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
/**
|
|
2844
|
-
* Read `count` bits (1..32) as an unsigned integer, most significant first.
|
|
2845
|
-
*
|
|
2846
|
-
* @param {number} count
|
|
2847
|
-
* @returns {number}
|
|
2848
|
-
* @throws {FormatError} If the stream is exhausted.
|
|
2849
|
-
*/
|
|
2850
|
-
read(count) {
|
|
2851
|
-
if (count < 1 || count > 32) {
|
|
2852
|
-
throw new FormatError(`BitReader: cannot read ${count} bits`);
|
|
2853
|
-
}
|
|
2854
|
-
if (count > this.available()) {
|
|
2855
|
-
throw new FormatError(
|
|
2856
|
-
`BitReader: needed ${count} bits, ${this.available()} remain`
|
|
2857
|
-
);
|
|
2858
|
-
}
|
|
2859
|
-
|
|
2860
|
-
let result = 0;
|
|
2861
|
-
let remaining = count;
|
|
2862
|
-
|
|
2863
|
-
// Finish the partially consumed byte first, then take whole bytes.
|
|
2864
|
-
if (this.bitOffset > 0) {
|
|
2865
|
-
const inCurrent = 8 - this.bitOffset;
|
|
2866
|
-
const take = Math.min(remaining, inCurrent);
|
|
2867
|
-
const shift = inCurrent - take;
|
|
2868
|
-
const mask = (0xff >> this.bitOffset) & ~((1 << shift) - 1);
|
|
2869
|
-
result = (this.bytes[this.byteOffset] & mask) >> shift;
|
|
2870
|
-
remaining -= take;
|
|
2871
|
-
this.bitOffset += take;
|
|
2872
|
-
if (this.bitOffset === 8) {
|
|
2873
|
-
this.bitOffset = 0;
|
|
2874
|
-
this.byteOffset++;
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
while (remaining >= 8) {
|
|
2879
|
-
result = (result << 8) | (this.bytes[this.byteOffset] & 0xff);
|
|
2880
|
-
this.byteOffset++;
|
|
2881
|
-
remaining -= 8;
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
if (remaining > 0) {
|
|
2885
|
-
const shift = 8 - remaining;
|
|
2886
|
-
const mask = ~((1 << shift) - 1) & 0xff;
|
|
2887
|
-
result = (result << remaining) | ((this.bytes[this.byteOffset] & mask) >> shift);
|
|
2888
|
-
this.bitOffset += remaining;
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
return result >>> 0;
|
|
2892
|
-
}
|
|
2893
|
-
|
|
2894
|
-
/** @returns {boolean} */
|
|
2895
|
-
readBit() {
|
|
2896
|
-
return this.read(1) === 1;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
__exports.BitWriter = BitWriter;
|
|
2901
|
-
__exports.BitReader = BitReader;
|
|
2902
|
-
};
|
|
2903
|
-
|
|
2904
2755
|
__modules["core/galois-field.js"] = function (__require, __exports) {
|
|
2905
2756
|
/**
|
|
2906
2757
|
* Finite field arithmetic.
|
|
@@ -3067,6 +2918,9 @@ const GF256_QR = new GaloisField({ size: 256, primitive: 0x011d, name: 'GF(256)/
|
|
|
3067
2918
|
/** Data Matrix ECC200. x^8 + x^5 + x^3 + x^2 + 1 */
|
|
3068
2919
|
const GF256_DM = new GaloisField({ size: 256, primitive: 0x012d, name: 'GF(256)/DataMatrix' });
|
|
3069
2920
|
|
|
2921
|
+
/** Aztec's eight-bit data field is algebraically identical to Data Matrix's. */
|
|
2922
|
+
const GF256_AZTEC = GF256_DM;
|
|
2923
|
+
|
|
3070
2924
|
/** PDF417. Prime field; 3 is a primitive root modulo 929. */
|
|
3071
2925
|
const GF929 = new GaloisField({ size: 929, prime: true, generator: 3, name: 'GF(929)' });
|
|
3072
2926
|
|
|
@@ -3079,6 +2933,7 @@ const GF4096 = new GaloisField({ size: 4096, primitive: 0x1069, name: 'GF(4096)'
|
|
|
3079
2933
|
__exports.GaloisField = GaloisField;
|
|
3080
2934
|
__exports.GF256_QR = GF256_QR;
|
|
3081
2935
|
__exports.GF256_DM = GF256_DM;
|
|
2936
|
+
__exports.GF256_AZTEC = GF256_AZTEC;
|
|
3082
2937
|
__exports.GF929 = GF929;
|
|
3083
2938
|
__exports.GF16 = GF16;
|
|
3084
2939
|
__exports.GF64 = GF64;
|
|
@@ -3114,7 +2969,7 @@ const { ChecksumError } = __require("core/errors.js");
|
|
|
3114
2969
|
*
|
|
3115
2970
|
* g(x) = product over i of (x - a^(base + i)), i = 0 .. eccLen-1
|
|
3116
2971
|
*
|
|
3117
|
-
* `base` is 0 for QR, Data Matrix and
|
|
2972
|
+
* `base` is 0 for QR; 1 for Aztec, Data Matrix and PDF417.
|
|
3118
2973
|
*
|
|
3119
2974
|
* @param {number} eccLen
|
|
3120
2975
|
* @param {import('./galois-field.js').GaloisField} field
|
|
@@ -3375,349 +3230,1521 @@ __exports.rsEncode = rsEncode;
|
|
|
3375
3230
|
__exports.rsDecode = rsDecode;
|
|
3376
3231
|
};
|
|
3377
3232
|
|
|
3378
|
-
__modules["
|
|
3233
|
+
__modules["datamatrix/tables.js"] = function (__require, __exports) {
|
|
3379
3234
|
/**
|
|
3380
|
-
*
|
|
3381
|
-
*
|
|
3382
|
-
* The design principle here is that as little as possible is *recalled* and as
|
|
3383
|
-
* much as possible is *derived*, because a barcode table is the one place where
|
|
3384
|
-
* a single mistyped digit produces a symbol that looks perfect and scans as
|
|
3385
|
-
* garbage — or, worse, scans correctly for the payload you tested and fails for
|
|
3386
|
-
* the payload your user sends.
|
|
3387
|
-
*
|
|
3388
|
-
* So:
|
|
3235
|
+
* Data Matrix ECC 200 symbol parameters.
|
|
3389
3236
|
*
|
|
3390
|
-
*
|
|
3391
|
-
*
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3394
|
-
* - The group-1 / group-2 block split is arithmetic, not data.
|
|
3395
|
-
*
|
|
3396
|
-
* That leaves exactly three recalled numbers per (version, level): the error
|
|
3397
|
-
* correction codewords per block, the block count, and the total data codeword
|
|
3398
|
-
* count. Those three are deliberately redundant — they must satisfy
|
|
3399
|
-
*
|
|
3400
|
-
* blocks * eccPerBlock + totalDataCodewords === geometricTotalCodewords(v)
|
|
3401
|
-
*
|
|
3402
|
-
* for all 160 combinations, where the right-hand side is counted off the module
|
|
3403
|
-
* grid. Any single typo on either side breaks the identity. {@link validateTables}
|
|
3404
|
-
* enforces it, and the test suite asserts it returns no problems.
|
|
3237
|
+
* Width and height include finder borders. `regionWidth` and `regionHeight`
|
|
3238
|
+
* describe the usable modules inside one data region. The last three columns
|
|
3239
|
+
* make the Reed-Solomon block split explicit instead of hiding the 144x144
|
|
3240
|
+
* exception in encoder control flow.
|
|
3405
3241
|
*
|
|
3406
|
-
* @module
|
|
3242
|
+
* @module datamatrix/tables
|
|
3407
3243
|
*/
|
|
3244
|
+
|
|
3245
|
+
function symbol(width, height, dataRegionWidth, dataRegionHeight, dataCodewords, errorCodewords, dataBlockLengths) {
|
|
3246
|
+
const blockCount = dataBlockLengths.length;
|
|
3247
|
+
return Object.freeze({
|
|
3248
|
+
width, height, rows: height, columns: width,
|
|
3249
|
+
// Region dimensions include their one-module finder border on each side;
|
|
3250
|
+
// dataRegion* expose the inner placement lattice explicitly.
|
|
3251
|
+
regionWidth: dataRegionWidth + 2, regionHeight: dataRegionHeight + 2,
|
|
3252
|
+
dataRegionWidth, dataRegionHeight,
|
|
3253
|
+
dataRegionRows: dataRegionHeight, dataRegionColumns: dataRegionWidth,
|
|
3254
|
+
dataCodewords, errorCodewords, blockCount,
|
|
3255
|
+
eccPerBlock: errorCodewords / blockCount,
|
|
3256
|
+
dataBlockLengths: Object.freeze(dataBlockLengths),
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
/** Classic ISO/IEC 16022 ECC 200 symbols; DMRE is deliberately excluded. */
|
|
3261
|
+
const DATAMATRIX_SYMBOLS = Object.freeze([
|
|
3262
|
+
symbol(10, 10, 8, 8, 3, 5, [3]),
|
|
3263
|
+
symbol(12, 12, 10, 10, 5, 7, [5]),
|
|
3264
|
+
symbol(14, 14, 12, 12, 8, 10, [8]),
|
|
3265
|
+
symbol(16, 16, 14, 14, 12, 12, [12]),
|
|
3266
|
+
symbol(18, 18, 16, 16, 18, 14, [18]),
|
|
3267
|
+
symbol(20, 20, 18, 18, 22, 18, [22]),
|
|
3268
|
+
symbol(22, 22, 20, 20, 30, 20, [30]),
|
|
3269
|
+
symbol(24, 24, 22, 22, 36, 24, [36]),
|
|
3270
|
+
symbol(26, 26, 24, 24, 44, 28, [44]),
|
|
3271
|
+
symbol(32, 32, 14, 14, 62, 36, [62]),
|
|
3272
|
+
symbol(36, 36, 16, 16, 86, 42, [86]),
|
|
3273
|
+
symbol(40, 40, 18, 18, 114, 48, [114]),
|
|
3274
|
+
symbol(44, 44, 20, 20, 144, 56, [144]),
|
|
3275
|
+
symbol(48, 48, 22, 22, 174, 68, [174]),
|
|
3276
|
+
symbol(52, 52, 24, 24, 204, 84, [102, 102]),
|
|
3277
|
+
symbol(64, 64, 14, 14, 280, 112, [140, 140]),
|
|
3278
|
+
symbol(72, 72, 16, 16, 368, 144, [92, 92, 92, 92]),
|
|
3279
|
+
symbol(80, 80, 18, 18, 456, 192, [114, 114, 114, 114]),
|
|
3280
|
+
symbol(88, 88, 20, 20, 576, 224, [144, 144, 144, 144]),
|
|
3281
|
+
symbol(96, 96, 22, 22, 696, 272, [174, 174, 174, 174]),
|
|
3282
|
+
symbol(104, 104, 24, 24, 816, 336, [136, 136, 136, 136, 136, 136]),
|
|
3283
|
+
symbol(120, 120, 18, 18, 1050, 408, [175, 175, 175, 175, 175, 175]),
|
|
3284
|
+
symbol(132, 132, 20, 20, 1304, 496, [163, 163, 163, 163, 163, 163, 163, 163]),
|
|
3285
|
+
symbol(144, 144, 22, 22, 1558, 620, [156, 156, 156, 156, 156, 156, 156, 156, 155, 155]),
|
|
3286
|
+
symbol(18, 8, 16, 6, 5, 7, [5]),
|
|
3287
|
+
symbol(32, 8, 14, 6, 10, 11, [10]),
|
|
3288
|
+
symbol(26, 12, 24, 10, 16, 14, [16]),
|
|
3289
|
+
symbol(36, 12, 16, 10, 22, 18, [22]),
|
|
3290
|
+
symbol(36, 16, 16, 14, 32, 24, [32]),
|
|
3291
|
+
symbol(48, 16, 22, 14, 49, 28, [49]),
|
|
3292
|
+
]);
|
|
3293
|
+
|
|
3294
|
+
/** Compatibility alias. */
|
|
3295
|
+
const SYMBOLS = DATAMATRIX_SYMBOLS;
|
|
3296
|
+
|
|
3297
|
+
/** Return the smallest permitted symbol that holds `count` data codewords. */
|
|
3298
|
+
function symbolForDataCodewords(count, shape = 'any') {
|
|
3299
|
+
for (const s of DATAMATRIX_SYMBOLS) {
|
|
3300
|
+
const rectangular = s.width !== s.height;
|
|
3301
|
+
if ((shape === 'square' && rectangular) || (shape === 'rectangular' && !rectangular)) continue;
|
|
3302
|
+
if (count <= s.dataCodewords) return s;
|
|
3303
|
+
}
|
|
3304
|
+
throw new RangeError(`Data Matrix: ${count} data codewords do not fit an ECC 200 ${shape} symbol`);
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
/** Check redundant geometry and block identities in the static table. */
|
|
3308
|
+
function validateDataMatrixTables() {
|
|
3309
|
+
const issues = [];
|
|
3310
|
+
for (const s of DATAMATRIX_SYMBOLS) {
|
|
3311
|
+
const regionsX = s.width / s.regionWidth;
|
|
3312
|
+
const regionsY = s.height / s.regionHeight;
|
|
3313
|
+
if (!Number.isInteger(regionsX) || !Number.isInteger(regionsY)) issues.push(`${s.width}x${s.height}: non-integral regions`);
|
|
3314
|
+
const modules = regionsX * regionsY * s.dataRegionWidth * s.dataRegionHeight;
|
|
3315
|
+
// Annex F reserves four terminal modules on a few lattice dimensions.
|
|
3316
|
+
// They are set to dark after codeword placement and do not carry data.
|
|
3317
|
+
const unused = modules - (s.dataCodewords + s.errorCodewords) * 8;
|
|
3318
|
+
if (unused !== 0 && unused !== 4) issues.push(`${s.width}x${s.height}: geometry/codeword mismatch`);
|
|
3319
|
+
if (s.dataBlockLengths.reduce((a, b) => a + b, 0) !== s.dataCodewords) issues.push(`${s.width}x${s.height}: data block mismatch`);
|
|
3320
|
+
if (s.eccPerBlock * s.blockCount !== s.errorCodewords) issues.push(`${s.width}x${s.height}: ecc block mismatch`);
|
|
3321
|
+
}
|
|
3322
|
+
return issues;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
/** Compatibility alias. */
|
|
3326
|
+
const validateTables = validateDataMatrixTables;
|
|
3327
|
+
|
|
3328
|
+
__exports.DATAMATRIX_SYMBOLS = DATAMATRIX_SYMBOLS;
|
|
3329
|
+
__exports.SYMBOLS = SYMBOLS;
|
|
3330
|
+
__exports.symbolForDataCodewords = symbolForDataCodewords;
|
|
3331
|
+
__exports.validateDataMatrixTables = validateDataMatrixTables;
|
|
3332
|
+
__exports.validateTables = validateTables;
|
|
3333
|
+
};
|
|
3334
|
+
|
|
3335
|
+
__modules["datamatrix/encoder.js"] = function (__require, __exports) {
|
|
3336
|
+
/** Data Matrix ECC 200 encoder: ASCII/Base256, RS interleaving and Annex F placement. */
|
|
3408
3337
|
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
3338
|
+
const { EncodeError } = __require("core/errors.js");
|
|
3339
|
+
const { GF256_DM } = __require("core/galois-field.js");
|
|
3340
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
3341
|
+
const { symbolForDataCodewords } = __require("datamatrix/tables.js");
|
|
3409
3342
|
|
|
3410
|
-
|
|
3411
|
-
const
|
|
3343
|
+
function asciiCodewords(text) {
|
|
3344
|
+
const out = [];
|
|
3345
|
+
for (let i = 0; i < text.length;) {
|
|
3346
|
+
const a = text.charCodeAt(i);
|
|
3347
|
+
if (a > 255) throw new EncodeError('Data Matrix ASCII: characters must fit ISO-8859-1; use Base256 for UTF-8');
|
|
3348
|
+
if (i + 1 < text.length) {
|
|
3349
|
+
const b = text.charCodeAt(i + 1);
|
|
3350
|
+
if (a >= 48 && a <= 57 && b >= 48 && b <= 57) { out.push(130 + (a - 48) * 10 + b - 48); i += 2; continue; }
|
|
3351
|
+
}
|
|
3352
|
+
if (a <= 127) out.push(a + 1);
|
|
3353
|
+
else out.push(235, a - 127);
|
|
3354
|
+
i++;
|
|
3355
|
+
}
|
|
3356
|
+
return out;
|
|
3357
|
+
}
|
|
3412
3358
|
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3359
|
+
function bytesFor(value) {
|
|
3360
|
+
if (value instanceof Uint8Array) return value;
|
|
3361
|
+
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
3362
|
+
if (typeof value !== 'string') throw new EncodeError('Data Matrix: value must be a string or byte array');
|
|
3363
|
+
return new TextEncoder().encode(value);
|
|
3364
|
+
}
|
|
3418
3365
|
|
|
3419
|
-
|
|
3420
|
-
const
|
|
3421
|
-
|
|
3422
|
-
|
|
3366
|
+
function randomize255(value, position) {
|
|
3367
|
+
const pseudo = (149 * position) % 255 + 1;
|
|
3368
|
+
return value + pseudo <= 255 ? value + pseudo : value + pseudo - 256;
|
|
3369
|
+
}
|
|
3423
3370
|
|
|
3424
|
-
|
|
3425
|
-
const
|
|
3371
|
+
function base256Codewords(value, prefixLength = 0) {
|
|
3372
|
+
const bytes = bytesFor(value);
|
|
3373
|
+
if (bytes.length > 1555) throw new EncodeError('Data Matrix Base256: payload exceeds ECC 200 capacity');
|
|
3374
|
+
const out = [231];
|
|
3375
|
+
if (bytes.length <= 249) out.push(bytes.length);
|
|
3376
|
+
else out.push(Math.floor(bytes.length / 250) + 249, bytes.length % 250);
|
|
3377
|
+
// Base256 randomization uses the absolute 1-based codeword position in the
|
|
3378
|
+
// symbol. A leading GS1 FNC1 therefore shifts every randomized codeword.
|
|
3379
|
+
for (let i = 1; i < out.length; i++) out[i] = randomize255(out[i], prefixLength + i + 1);
|
|
3380
|
+
for (const b of bytes) out.push(randomize255(b, prefixLength + out.length + 1));
|
|
3381
|
+
return out;
|
|
3382
|
+
}
|
|
3426
3383
|
|
|
3427
|
-
|
|
3428
|
-
const
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
};
|
|
3384
|
+
function pad(data, capacity) {
|
|
3385
|
+
const out = data.slice();
|
|
3386
|
+
if (out.length < capacity) out.push(129);
|
|
3387
|
+
while (out.length < capacity) {
|
|
3388
|
+
const position = out.length + 1;
|
|
3389
|
+
const pseudo = (149 * position) % 253 + 1;
|
|
3390
|
+
const v = 129 + pseudo;
|
|
3391
|
+
out.push(v <= 254 ? v : v - 254);
|
|
3392
|
+
}
|
|
3393
|
+
return out;
|
|
3394
|
+
}
|
|
3439
3395
|
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3396
|
+
function interleave(data, symbol) {
|
|
3397
|
+
const blocks = symbol.dataBlockLengths.map((length) => ({ data: new Array(length), ecc: null }));
|
|
3398
|
+
let at = 0;
|
|
3399
|
+
const longest = Math.max(...symbol.dataBlockLengths);
|
|
3400
|
+
// Data codewords are dealt across the RS blocks by column. Splitting the
|
|
3401
|
+
// stream into consecutive chunks and then interleaving those chunks looks
|
|
3402
|
+
// self-consistent to a decoder doing the same inverse operation, but it is
|
|
3403
|
+
// not ECC 200's wire order once a symbol has multiple blocks.
|
|
3404
|
+
for (let i = 0; i < longest; i++) {
|
|
3405
|
+
for (const block of blocks) if (i < block.data.length) block.data[i] = data[at++];
|
|
3406
|
+
}
|
|
3407
|
+
for (const block of blocks) {
|
|
3408
|
+
// ECC 200 starts its generator roots at alpha^1 (generator base 1).
|
|
3409
|
+
block.ecc = rsEncode(block.data, symbol.eccPerBlock, GF256_DM, 1);
|
|
3410
|
+
}
|
|
3453
3411
|
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3412
|
+
const out = [];
|
|
3413
|
+
for (let i = 0; i < longest; i++) for (const b of blocks) if (i < b.data.length) out.push(b.data[i]);
|
|
3414
|
+
// The 144x144 symbol has eight long and two short data blocks. Its parity
|
|
3415
|
+
// interleave begins with the first short block; deriving the rotation from
|
|
3416
|
+
// the declarative lengths keeps that exception out of a size-specific test.
|
|
3417
|
+
const eccOffset = blocks.findIndex((block) => block.data.length < longest);
|
|
3418
|
+
const rotation = eccOffset < 0 ? 0 : eccOffset;
|
|
3419
|
+
for (let i = 0; i < symbol.eccPerBlock; i++) {
|
|
3420
|
+
for (let b = 0; b < blocks.length; b++) out.push(blocks[(b + rotation) % blocks.length].ecc[i]);
|
|
3421
|
+
}
|
|
3422
|
+
return out;
|
|
3460
3423
|
}
|
|
3461
3424
|
|
|
3425
|
+
function place(codewords, rows, cols) {
|
|
3426
|
+
const cells = new Int8Array(rows * cols).fill(-1);
|
|
3427
|
+
const bit = (row, col, pos, n) => {
|
|
3428
|
+
if (row < 0) { row += rows; col += 4 - ((rows + 4) % 8); }
|
|
3429
|
+
if (col < 0) { col += cols; row += 4 - ((cols + 4) % 8); }
|
|
3430
|
+
cells[row * cols + col] = (codewords[pos] >>> (8 - n)) & 1;
|
|
3431
|
+
};
|
|
3432
|
+
const utah = (r, c, p) => { bit(r - 2, c - 2, p, 1); bit(r - 2, c - 1, p, 2); bit(r - 1, c - 2, p, 3); bit(r - 1, c - 1, p, 4); bit(r - 1, c, p, 5); bit(r, c - 2, p, 6); bit(r, c - 1, p, 7); bit(r, c, p, 8); };
|
|
3433
|
+
const corner1 = (p) => { bit(rows - 1, 0, p, 1); bit(rows - 1, 1, p, 2); bit(rows - 1, 2, p, 3); bit(0, cols - 2, p, 4); bit(0, cols - 1, p, 5); bit(1, cols - 1, p, 6); bit(2, cols - 1, p, 7); bit(3, cols - 1, p, 8); };
|
|
3434
|
+
const corner2 = (p) => { bit(rows - 3, 0, p, 1); bit(rows - 2, 0, p, 2); bit(rows - 1, 0, p, 3); bit(0, cols - 4, p, 4); bit(0, cols - 3, p, 5); bit(0, cols - 2, p, 6); bit(0, cols - 1, p, 7); bit(1, cols - 1, p, 8); };
|
|
3435
|
+
const corner3 = (p) => { bit(rows - 3, 0, p, 1); bit(rows - 2, 0, p, 2); bit(rows - 1, 0, p, 3); bit(0, cols - 2, p, 4); bit(0, cols - 1, p, 5); bit(1, cols - 1, p, 6); bit(2, cols - 1, p, 7); bit(3, cols - 1, p, 8); };
|
|
3436
|
+
const corner4 = (p) => { bit(rows - 1, 0, p, 1); bit(rows - 1, cols - 1, p, 2); bit(0, cols - 3, p, 3); bit(0, cols - 2, p, 4); bit(0, cols - 1, p, 5); bit(1, cols - 3, p, 6); bit(1, cols - 2, p, 7); bit(1, cols - 1, p, 8); };
|
|
3437
|
+
let row = 4, col = 0, pos = 0;
|
|
3438
|
+
do {
|
|
3439
|
+
if (row === rows && col === 0) corner1(pos++);
|
|
3440
|
+
if (row === rows - 2 && col === 0 && cols % 4 !== 0) corner2(pos++);
|
|
3441
|
+
if (row === rows - 2 && col === 0 && cols % 8 === 4) corner3(pos++);
|
|
3442
|
+
if (row === rows + 4 && col === 2 && cols % 8 === 0) corner4(pos++);
|
|
3443
|
+
do { if (row < rows && col >= 0 && cells[row * cols + col] < 0) utah(row, col, pos++); row -= 2; col += 2; } while (row >= 0 && col < cols);
|
|
3444
|
+
row += 1; col += 3;
|
|
3445
|
+
do { if (row >= 0 && col < cols && cells[row * cols + col] < 0) utah(row, col, pos++); row += 2; col -= 2; } while (row < rows && col >= 0);
|
|
3446
|
+
row += 3; col += 1;
|
|
3447
|
+
} while (row < rows || col < cols);
|
|
3448
|
+
if (cells[cells.length - 1] < 0) { cells[cells.length - 1] = 1; cells[cells.length - cols - 2] = 1; }
|
|
3449
|
+
if (pos !== codewords.length) throw new EncodeError(`Data Matrix: placement consumed ${pos} of ${codewords.length} codewords`);
|
|
3450
|
+
return cells;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
function buildMatrix(codewords, symbol) {
|
|
3454
|
+
const regionCols = symbol.width / symbol.regionWidth;
|
|
3455
|
+
const regionRows = symbol.height / symbol.regionHeight;
|
|
3456
|
+
const dataWidth = symbol.dataRegionColumns;
|
|
3457
|
+
const dataHeight = symbol.dataRegionRows;
|
|
3458
|
+
const data = place(codewords, regionRows * dataHeight, regionCols * dataWidth);
|
|
3459
|
+
const matrix = new BitMatrix(symbol.width, symbol.height);
|
|
3460
|
+
for (let ry = 0; ry < regionRows; ry++) for (let rx = 0; rx < regionCols; rx++) {
|
|
3461
|
+
const x0 = rx * symbol.regionWidth, y0 = ry * symbol.regionHeight;
|
|
3462
|
+
for (let x = 0; x < symbol.regionWidth; x++) { if ((x & 1) === 0) matrix.set(x0 + x, y0); matrix.set(x0 + x, y0 + dataHeight + 1); }
|
|
3463
|
+
// The top and right timing borders are complementary: top-left is dark,
|
|
3464
|
+
// top-right is light, and the solid bottom-right corner remains dark.
|
|
3465
|
+
for (let y = 0; y < symbol.regionHeight; y++) { matrix.set(x0, y0 + y); if ((y & 1) === 1) matrix.set(x0 + dataWidth + 1, y0 + y); }
|
|
3466
|
+
for (let y = 0; y < dataHeight; y++) for (let x = 0; x < dataWidth; x++) if (data[(ry * dataHeight + y) * (regionCols * dataWidth) + rx * dataWidth + x]) matrix.set(x0 + 1 + x, y0 + 1 + y);
|
|
3467
|
+
}
|
|
3468
|
+
return matrix;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
/** Encode a string (ASCII mode) or byte payload (Base256) into Data Matrix ECC 200. */
|
|
3472
|
+
function encodeDataMatrix(value, options = {}) {
|
|
3473
|
+
const encoding = options.encoding ?? (value instanceof Uint8Array ? 'base256' : 'ascii');
|
|
3474
|
+
let raw;
|
|
3475
|
+
if (encoding === 'ascii') {
|
|
3476
|
+
if (typeof value !== 'string') throw new EncodeError('Data Matrix ASCII: value must be a string');
|
|
3477
|
+
raw = asciiCodewords(value);
|
|
3478
|
+
} else if (encoding === 'base256') raw = base256Codewords(value, options.gs1 === true ? 1 : 0);
|
|
3479
|
+
else throw new EncodeError(`Data Matrix: unsupported encoding "${encoding}"`);
|
|
3480
|
+
// GS1 DataMatrix is ECC 200 with FNC1 in the first codeword position.
|
|
3481
|
+
if (options.gs1 === true) raw.unshift(232);
|
|
3482
|
+
const shape = options.shape ?? 'any';
|
|
3483
|
+
if (shape !== 'any' && shape !== 'square' && shape !== 'rectangular') throw new EncodeError(`Data Matrix: invalid shape "${shape}"`);
|
|
3484
|
+
const symbol = symbolForDataCodewords(raw.length, shape);
|
|
3485
|
+
if (!symbol) throw new EncodeError(`Data Matrix: ${raw.length} data codewords do not fit an ECC 200 ${shape} symbol`);
|
|
3486
|
+
return buildMatrix(interleave(pad(raw, symbol.dataCodewords), symbol), symbol);
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
/** Encode already compacted ASCII/Base256 codewords, primarily for conformance tests. */
|
|
3490
|
+
function encodeDataMatrixCodewords(codewords, options = {}) {
|
|
3491
|
+
if (!Array.isArray(codewords) && !(codewords instanceof Uint8Array)) throw new EncodeError('Data Matrix: codewords must be an array');
|
|
3492
|
+
for (const c of codewords) if (!Number.isInteger(c) || c < 0 || c > 255) throw new EncodeError('Data Matrix: codewords must be bytes');
|
|
3493
|
+
const symbol = symbolForDataCodewords(codewords.length, options.shape ?? 'any');
|
|
3494
|
+
if (!symbol) throw new EncodeError('Data Matrix: codewords do not fit ECC 200');
|
|
3495
|
+
return buildMatrix(interleave(pad(Array.from(codewords), symbol.dataCodewords), symbol), symbol);
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
__exports.encodeDataMatrix = encodeDataMatrix;
|
|
3499
|
+
__exports.encodeDataMatrixCodewords = encodeDataMatrixCodewords;
|
|
3500
|
+
};
|
|
3501
|
+
|
|
3502
|
+
__modules["datamatrix/decoder.js"] = function (__require, __exports) {
|
|
3462
3503
|
/**
|
|
3463
|
-
*
|
|
3504
|
+
* Data Matrix ECC 200 decoder for an already sampled symbol.
|
|
3464
3505
|
*
|
|
3465
|
-
*
|
|
3466
|
-
*
|
|
3467
|
-
*
|
|
3506
|
+
* The detector owns locating, perspective correction and orientation. This
|
|
3507
|
+
* module starts with the complete, upright symbol including its finder borders.
|
|
3508
|
+
* The table entry is deliberately read through a small normalizer so table data
|
|
3509
|
+
* remains declarative: it needs total rows/columns, one data-region's rows and
|
|
3510
|
+
* columns, data/ECC codeword counts, and either a block count or data block
|
|
3511
|
+
* lengths. The standard 144x144 uneven data blocks are supported.
|
|
3512
|
+
*
|
|
3513
|
+
* @module datamatrix/decoder
|
|
3468
3514
|
*/
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
/**
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
const
|
|
3493
|
-
|
|
3494
|
-
const
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3515
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
3516
|
+
const { GF256_DM } = __require("core/galois-field.js");
|
|
3517
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
3518
|
+
const { SYMBOLS } = __require("datamatrix/tables.js");
|
|
3519
|
+
|
|
3520
|
+
const CW_PAD = 129;
|
|
3521
|
+
const CW_BASE256 = 231;
|
|
3522
|
+
|
|
3523
|
+
/** @param {object} entry @param {...string} names @returns {number | undefined} */
|
|
3524
|
+
function numberField(entry, ...names) {
|
|
3525
|
+
for (const name of names) if (Number.isInteger(entry[name])) return entry[name];
|
|
3526
|
+
return undefined;
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
/** Normalize the public table entry into the decoder's geometry contract. */
|
|
3530
|
+
function layoutFor(width, height) {
|
|
3531
|
+
const entry = SYMBOLS.find((s) =>
|
|
3532
|
+
numberField(s, 'columns', 'cols', 'matrixColumns', 'width') === width &&
|
|
3533
|
+
numberField(s, 'rows', 'matrixRows', 'height') === height);
|
|
3534
|
+
if (!entry) throw new FormatError(`Data Matrix: ${width}x${height} is not an ECC 200 symbol size`);
|
|
3535
|
+
|
|
3536
|
+
const regionRows = numberField(entry, 'dataRegionRows', 'regionRows') ??
|
|
3537
|
+
(numberField(entry, 'regionHeight') ? numberField(entry, 'regionHeight') - 2 : undefined);
|
|
3538
|
+
const regionCols = numberField(entry, 'dataRegionColumns', 'dataRegionCols', 'regionColumns') ??
|
|
3539
|
+
(numberField(entry, 'regionWidth') ? numberField(entry, 'regionWidth') - 2 : undefined);
|
|
3540
|
+
const dataCount = numberField(entry, 'dataCodewords', 'dataCapacity');
|
|
3541
|
+
const eccCount = numberField(entry, 'errorCodewords', 'eccCodewords');
|
|
3542
|
+
const blockCount = numberField(entry, 'interleavedBlocks', 'interleavedBlockCount', 'blockCount', 'rsBlocks') || 1;
|
|
3543
|
+
if (!regionRows || !regionCols || dataCount === undefined || eccCount === undefined ||
|
|
3544
|
+
height % (regionRows + 2) || width % (regionCols + 2) || eccCount % blockCount) {
|
|
3545
|
+
throw new FormatError(`Data Matrix: invalid table layout for ${width}x${height}`);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
const rows = height / (regionRows + 2);
|
|
3549
|
+
const cols = width / (regionCols + 2);
|
|
3550
|
+
const blockData = Array.isArray(entry.blockDataCodewords) ? entry.blockDataCodewords.slice() :
|
|
3551
|
+
Array.isArray(entry.dataCodewordsPerBlock) ? entry.dataCodewordsPerBlock.slice() : null;
|
|
3552
|
+
let dataLengths;
|
|
3553
|
+
if (blockData) {
|
|
3554
|
+
dataLengths = blockData;
|
|
3555
|
+
} else {
|
|
3556
|
+
// The sole uneven ECC 200 distribution is 144x144: its first eight of ten
|
|
3557
|
+
// blocks contain one extra data codeword. This derives it instead of hiding
|
|
3558
|
+
// a magic size check in the deinterleaver.
|
|
3559
|
+
const short = Math.floor(dataCount / blockCount);
|
|
3560
|
+
dataLengths = new Array(blockCount).fill(short);
|
|
3561
|
+
for (let i = 0; i < dataCount % blockCount; i++) dataLengths[i]++;
|
|
3562
|
+
}
|
|
3563
|
+
if (dataLengths.length !== blockCount || dataLengths.reduce((a, b) => a + b, 0) !== dataCount) {
|
|
3564
|
+
throw new FormatError(`Data Matrix: inconsistent block layout for ${width}x${height}`);
|
|
3565
|
+
}
|
|
3566
|
+
return { entry, regionRows, regionCols, regionRowCount: rows, regionColCount: cols,
|
|
3567
|
+
dataRows: rows * regionRows, dataCols: cols * regionCols, dataCount, eccCount,
|
|
3568
|
+
blockCount, eccPerBlock: eccCount / blockCount, dataLengths };
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
/** Remove the L/finders from every data region, retaining only placement modules. */
|
|
3572
|
+
function extractDataModules(matrix, layout) {
|
|
3573
|
+
const data = new Uint8Array(layout.dataRows * layout.dataCols);
|
|
3574
|
+
for (let regionY = 0; regionY < layout.regionRowCount; regionY++) {
|
|
3575
|
+
for (let regionX = 0; regionX < layout.regionColCount; regionX++) {
|
|
3576
|
+
const sourceX = regionX * (layout.regionCols + 2) + 1;
|
|
3577
|
+
const sourceY = regionY * (layout.regionRows + 2) + 1;
|
|
3578
|
+
for (let y = 0; y < layout.regionRows; y++) {
|
|
3579
|
+
const targetY = regionY * layout.regionRows + y;
|
|
3580
|
+
for (let x = 0; x < layout.regionCols; x++) {
|
|
3581
|
+
data[targetY * layout.dataCols + regionX * layout.regionCols + x] =
|
|
3582
|
+
matrix.get(sourceX + x, sourceY + y) ? 1 : 0;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
return data;
|
|
3506
3588
|
}
|
|
3507
3589
|
|
|
3508
|
-
/**
|
|
3509
|
-
|
|
3510
|
-
*
|
|
3511
|
-
|
|
3512
|
-
*
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
if (coords.length === 0) return [];
|
|
3519
|
-
|
|
3520
|
-
const size = versionSize(version);
|
|
3521
|
-
const lo = 6;
|
|
3522
|
-
const hi = size - 7;
|
|
3523
|
-
const out = [];
|
|
3524
|
-
for (let i = 0; i < coords.length; i++) {
|
|
3525
|
-
for (let j = 0; j < coords.length; j++) {
|
|
3526
|
-
const x = coords[j];
|
|
3527
|
-
const y = coords[i];
|
|
3528
|
-
// Skip the three finder corners.
|
|
3529
|
-
if (x === lo && y === lo) continue;
|
|
3530
|
-
if (x === lo && y === hi) continue;
|
|
3531
|
-
if (x === hi && y === lo) continue;
|
|
3532
|
-
out.push([x, y]);
|
|
3590
|
+
/** Read placement codewords using the ECC 200 Utah sweep (the inverse writer path). */
|
|
3591
|
+
function readPlacement(modules, rows, cols, count) {
|
|
3592
|
+
const seen = new Uint8Array(rows * cols);
|
|
3593
|
+
const out = new Uint8Array(count);
|
|
3594
|
+
const get = (row, col) => modules[row * cols + col] !== 0;
|
|
3595
|
+
const module = (row, col) => {
|
|
3596
|
+
if (row < 0) { row += rows; col += 4 - ((rows + 4) % 8); }
|
|
3597
|
+
if (col < 0) { col += cols; row += 4 - ((cols + 4) % 8); }
|
|
3598
|
+
if (row < 0 || row >= rows || col < 0 || col >= cols) {
|
|
3599
|
+
throw new FormatError('Data Matrix: placement coordinate escaped data region');
|
|
3533
3600
|
}
|
|
3534
|
-
|
|
3601
|
+
seen[row * cols + col] = 1;
|
|
3602
|
+
return get(row, col) ? 1 : 0;
|
|
3603
|
+
};
|
|
3604
|
+
const bits = (coords) => coords.reduce((value, p) => (value << 1) | module(p[0], p[1]), 0);
|
|
3605
|
+
const utah = (row, col) => bits([[row - 2, col - 2], [row - 2, col - 1], [row - 1, col - 2], [row - 1, col - 1],
|
|
3606
|
+
[row - 1, col], [row, col - 2], [row, col - 1], [row, col]]);
|
|
3607
|
+
const corner1 = () => bits([[rows - 1, 0], [rows - 1, 1], [rows - 1, 2], [0, cols - 2], [0, cols - 1], [1, cols - 1], [2, cols - 1], [3, cols - 1]]);
|
|
3608
|
+
const corner2 = () => bits([[rows - 3, 0], [rows - 2, 0], [rows - 1, 0], [0, cols - 4], [0, cols - 3], [0, cols - 2], [0, cols - 1], [1, cols - 1]]);
|
|
3609
|
+
const corner3 = () => bits([[rows - 3, 0], [rows - 2, 0], [rows - 1, 0], [0, cols - 2], [0, cols - 1], [1, cols - 1], [2, cols - 1], [3, cols - 1]]);
|
|
3610
|
+
const corner4 = () => bits([[rows - 1, 0], [rows - 1, cols - 1], [0, cols - 3], [0, cols - 2], [0, cols - 1], [1, cols - 3], [1, cols - 2], [1, cols - 1]]);
|
|
3611
|
+
|
|
3612
|
+
let row = 4, col = 0, n = 0;
|
|
3613
|
+
const put = (value) => { if (n < count) out[n++] = value; };
|
|
3614
|
+
do {
|
|
3615
|
+
if (row === rows && col === 0) put(corner1());
|
|
3616
|
+
if (row === rows - 2 && col === 0 && cols % 4 !== 0) put(corner2());
|
|
3617
|
+
if (row === rows - 2 && col === 0 && cols % 8 === 4) put(corner3());
|
|
3618
|
+
if (row === rows + 4 && col === 2 && cols % 8 === 0) put(corner4());
|
|
3619
|
+
do { if (row < rows && col >= 0 && !seen[row * cols + col]) put(utah(row, col)); row -= 2; col += 2; } while (row >= 0 && col < cols);
|
|
3620
|
+
row += 1; col += 3;
|
|
3621
|
+
do { if (row >= 0 && col < cols && !seen[row * cols + col]) put(utah(row, col)); row += 2; col -= 2; } while (row < rows && col >= 0);
|
|
3622
|
+
row += 3; col += 1;
|
|
3623
|
+
} while (row < rows || col < cols);
|
|
3624
|
+
if (n !== count) throw new FormatError(`Data Matrix: placement yielded ${n}, expected ${count} codewords`);
|
|
3535
3625
|
return out;
|
|
3536
3626
|
}
|
|
3537
3627
|
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
// Finder patterns with their separators: an 8x8 reserved block at each of
|
|
3565
|
-
// three corners (7x7 pattern plus a one-module light border on the inner
|
|
3566
|
-
// sides, which the corner blocks absorb).
|
|
3567
|
-
m.setRegion(0, 0, 8, 8);
|
|
3568
|
-
m.setRegion(size - 8, 0, 8, 8);
|
|
3569
|
-
m.setRegion(0, size - 8, 8, 8);
|
|
3570
|
-
|
|
3571
|
-
// Timing patterns, spanning the gap between the separators.
|
|
3572
|
-
for (let i = 8; i < size - 8; i++) {
|
|
3573
|
-
m.set(i, 6);
|
|
3574
|
-
m.set(6, i);
|
|
3575
|
-
}
|
|
3576
|
-
|
|
3577
|
-
// Alignment patterns, 5x5 each.
|
|
3578
|
-
const centres = alignmentCentres(version);
|
|
3579
|
-
for (let i = 0; i < centres.length; i++) {
|
|
3580
|
-
m.setRegion(centres[i][0] - 2, centres[i][1] - 2, 5, 5);
|
|
3628
|
+
/** Restore RS blocks, correct them, then concatenate their data portions. */
|
|
3629
|
+
function deinterleaveAndCorrect(codewords, layout) {
|
|
3630
|
+
if (codewords.length !== layout.dataCount + layout.eccCount) throw new FormatError('Data Matrix: codeword count mismatch');
|
|
3631
|
+
const blocks = layout.dataLengths.map((len) => new Uint8Array(len + layout.eccPerBlock));
|
|
3632
|
+
// Data codewords arrive in their original stream order. ECC 200 deals that
|
|
3633
|
+
// stream round-robin across the RS blocks, so the inverse is determined by
|
|
3634
|
+
// the wire index rather than by splitting it into consecutive block-sized
|
|
3635
|
+
// chunks. For 144x144, indices 1550..1557 naturally land in the eight long
|
|
3636
|
+
// blocks while the two short blocks remain at 155 data codewords.
|
|
3637
|
+
for (let i = 0; i < layout.dataCount; i++) {
|
|
3638
|
+
blocks[i % layout.blockCount][Math.floor(i / layout.blockCount)] = codewords[i];
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
// Parity normally begins with block zero. The uneven 144x144 layout rotates
|
|
3642
|
+
// the parity wire order to begin with its first short block; derive the same
|
|
3643
|
+
// mapping from the declarative lengths instead of keying it to dimensions.
|
|
3644
|
+
const longest = Math.max(...layout.dataLengths);
|
|
3645
|
+
const firstShort = layout.dataLengths.findIndex((length) => length < longest);
|
|
3646
|
+
const rotation = firstShort < 0 ? 0 : firstShort;
|
|
3647
|
+
let at = layout.dataCount;
|
|
3648
|
+
for (let i = 0; i < layout.eccPerBlock; i++) {
|
|
3649
|
+
for (let slot = 0; slot < layout.blockCount; slot++) {
|
|
3650
|
+
const block = (slot + rotation) % layout.blockCount;
|
|
3651
|
+
blocks[block][layout.dataLengths[block] + i] = codewords[at++];
|
|
3652
|
+
}
|
|
3581
3653
|
}
|
|
3582
3654
|
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
for (let i = 0; i < 15; i++) {
|
|
3588
|
-
m.set(copyA[i][0], copyA[i][1]);
|
|
3589
|
-
m.set(copyB[i][0], copyB[i][1]);
|
|
3655
|
+
let corrections = 0;
|
|
3656
|
+
const data = new Uint8Array(layout.dataCount);
|
|
3657
|
+
for (let b = 0; b < blocks.length; b++) {
|
|
3658
|
+
corrections += rsDecode(blocks[b], layout.eccPerBlock, GF256_DM, 1);
|
|
3590
3659
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
//
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
m.setRegion(0, size - 11, 6, 3);
|
|
3660
|
+
// Rebuild the original high-level codeword stream after correction. Keeping
|
|
3661
|
+
// this in wire-index order is essential: concatenating block data passes
|
|
3662
|
+
// single-block round trips but scrambles every multi-block payload.
|
|
3663
|
+
for (let i = 0; i < layout.dataCount; i++) {
|
|
3664
|
+
data[i] = blocks[i % layout.blockCount][Math.floor(i / layout.blockCount)];
|
|
3597
3665
|
}
|
|
3598
|
-
|
|
3599
|
-
reservedCache.set(version, m);
|
|
3600
|
-
return m;
|
|
3666
|
+
return { data, corrections };
|
|
3601
3667
|
}
|
|
3602
3668
|
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
* Index `i` in each array is bit `i` of the 15-bit format value, bit 0 being
|
|
3607
|
-
* the least significant.
|
|
3608
|
-
*
|
|
3609
|
-
* CAVEAT WORTH READING: the *direction* of this numbering is the one thing in
|
|
3610
|
-
* this file that a round-trip test cannot falsify. Encoder and decoder share
|
|
3611
|
-
* these tables, so a mirrored layout would pass every test in the suite and
|
|
3612
|
-
* fail only against a real scanner. The layout below is the standard one; both
|
|
3613
|
-
* sides deliberately consume this single definition so there is no second place
|
|
3614
|
-
* for the convention to drift.
|
|
3615
|
-
*
|
|
3616
|
-
* @param {number} size Modules per side.
|
|
3617
|
-
* @returns {[Array<[number, number]>, Array<[number, number]>]} [copyA, copyB]
|
|
3618
|
-
*/
|
|
3619
|
-
function formatInfoPositions(size) {
|
|
3620
|
-
/** @type {Array<[number, number]>} */
|
|
3621
|
-
const a = [];
|
|
3622
|
-
/** @type {Array<[number, number]>} */
|
|
3623
|
-
const b = [];
|
|
3624
|
-
|
|
3625
|
-
for (let i = 0; i < 15; i++) {
|
|
3626
|
-
// Copy A wraps the top-left finder: down column 8, then left along row 8,
|
|
3627
|
-
// stepping over the two timing modules.
|
|
3628
|
-
if (i < 6) a.push([8, i]);
|
|
3629
|
-
else if (i === 6) a.push([8, 7]);
|
|
3630
|
-
else if (i === 7) a.push([8, 8]);
|
|
3631
|
-
else if (i === 8) a.push([7, 8]);
|
|
3632
|
-
else a.push([14 - i, 8]);
|
|
3633
|
-
|
|
3634
|
-
// Copy B is split: the low bits run right-to-left along row 8 beside the
|
|
3635
|
-
// top-right finder, the high bits run bottom-up beside the bottom-left one.
|
|
3636
|
-
if (i < 8) b.push([size - 1 - i, 8]);
|
|
3637
|
-
else b.push([8, size - 15 + i]);
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
return [a, b];
|
|
3669
|
+
function unrandomize(value, position) {
|
|
3670
|
+
const pseudo = ((149 * position) % 255) + 1;
|
|
3671
|
+
return value - pseudo >= 0 ? value - pseudo : value - pseudo + 256;
|
|
3641
3672
|
}
|
|
3642
3673
|
|
|
3643
|
-
/**
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3674
|
+
/** Decode ASCII plus Base 256, preserving semantic bytes alongside text. */
|
|
3675
|
+
function parseData(data) {
|
|
3676
|
+
let text = '';
|
|
3677
|
+
const bytes = [];
|
|
3678
|
+
let upperShift = false;
|
|
3679
|
+
let gs1 = false;
|
|
3680
|
+
for (let i = 0; i < data.length;) {
|
|
3681
|
+
const cw = data[i++];
|
|
3682
|
+
if (cw === CW_PAD) break;
|
|
3683
|
+
if (cw <= 128) {
|
|
3684
|
+
const value = cw - 1 + (upperShift ? 128 : 0);
|
|
3685
|
+
upperShift = false;
|
|
3686
|
+
text += String.fromCharCode(value); bytes.push(value); continue;
|
|
3687
|
+
}
|
|
3688
|
+
if (cw <= 229) {
|
|
3689
|
+
const pair = cw - 130;
|
|
3690
|
+
const digits = String(pair).padStart(2, '0'); text += digits; bytes.push(digits.charCodeAt(0), digits.charCodeAt(1)); continue;
|
|
3691
|
+
}
|
|
3692
|
+
if (cw === 232) {
|
|
3693
|
+
if (i === 1) gs1 = true;
|
|
3694
|
+
else { text += '\x1d'; bytes.push(29); }
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
if (cw === 235) { upperShift = true; continue; }
|
|
3698
|
+
if (cw === CW_BASE256) {
|
|
3699
|
+
if (i >= data.length) throw new FormatError('Data Matrix: Base 256 length is missing');
|
|
3700
|
+
let length = unrandomize(data[i], i + 1); i++;
|
|
3701
|
+
if (length === 0) length = data.length - i;
|
|
3702
|
+
else if (length >= 250) {
|
|
3703
|
+
if (i >= data.length) throw new FormatError('Data Matrix: Base 256 extended length is missing');
|
|
3704
|
+
length = 250 * (length - 249) + unrandomize(data[i], i + 1); i++;
|
|
3705
|
+
}
|
|
3706
|
+
if (i + length > data.length) throw new FormatError('Data Matrix: Base 256 segment exceeds data capacity');
|
|
3707
|
+
const segment = new Uint8Array(length);
|
|
3708
|
+
for (let n = 0; n < length; n++, i++) segment[n] = unrandomize(data[i], i + 1);
|
|
3709
|
+
bytes.push(...segment);
|
|
3710
|
+
for (let n = 0; n < segment.length; n++) text += String.fromCharCode(segment[n]);
|
|
3711
|
+
continue;
|
|
3656
3712
|
}
|
|
3713
|
+
throw new FormatError(`Data Matrix: unsupported encoding codeword ${cw}`);
|
|
3657
3714
|
}
|
|
3658
|
-
return
|
|
3659
|
-
}
|
|
3660
|
-
|
|
3661
|
-
/**
|
|
3662
|
-
* Total codewords (data + error correction) a version holds.
|
|
3663
|
-
*
|
|
3664
|
-
* Geometric, not tabulated — this is the reference the ECC table is checked
|
|
3665
|
-
* against.
|
|
3666
|
-
*
|
|
3667
|
-
* @param {number} version
|
|
3668
|
-
* @returns {number}
|
|
3669
|
-
*/
|
|
3670
|
-
function geometricTotalCodewords(version) {
|
|
3671
|
-
return Math.floor(freeModuleCount(version) / 8);
|
|
3715
|
+
return { text, bytes: Uint8Array.from(bytes), gs1 };
|
|
3672
3716
|
}
|
|
3673
3717
|
|
|
3674
3718
|
/**
|
|
3675
|
-
*
|
|
3719
|
+
* Decode an upright, sampled Data Matrix ECC 200 symbol.
|
|
3676
3720
|
*
|
|
3677
|
-
* @param {
|
|
3678
|
-
* @returns {
|
|
3721
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix Full symbol, no quiet zone.
|
|
3722
|
+
* @returns {{text: string, bytes: Uint8Array, correctedErrors: number, symbol: object}}
|
|
3679
3723
|
*/
|
|
3680
|
-
function
|
|
3681
|
-
|
|
3724
|
+
function decodeDataMatrix(matrix) {
|
|
3725
|
+
if (!matrix || !Number.isInteger(matrix.width) || !Number.isInteger(matrix.height)) throw new FormatError('Data Matrix: no matrix supplied');
|
|
3726
|
+
const layout = layoutFor(matrix.width, matrix.height);
|
|
3727
|
+
const placement = readPlacement(extractDataModules(matrix, layout), layout.dataRows, layout.dataCols, layout.dataCount + layout.eccCount);
|
|
3728
|
+
const { data, corrections } = deinterleaveAndCorrect(placement, layout);
|
|
3729
|
+
const result = parseData(data);
|
|
3730
|
+
return { ...result, corrections, correctedErrors: corrections, symbol: layout.entry };
|
|
3682
3731
|
}
|
|
3732
|
+
__exports.ChecksumError = ChecksumError;
|
|
3683
3733
|
|
|
3684
|
-
|
|
3734
|
+
__exports.decodeDataMatrix = decodeDataMatrix;
|
|
3735
|
+
};
|
|
3685
3736
|
|
|
3737
|
+
__modules["image/perspective.js"] = function (__require, __exports) {
|
|
3686
3738
|
/**
|
|
3687
|
-
*
|
|
3739
|
+
* Projective (perspective) transforms.
|
|
3688
3740
|
*
|
|
3689
|
-
*
|
|
3690
|
-
*
|
|
3691
|
-
*
|
|
3741
|
+
* A 2D symbol photographed off-axis is not a rotated square — it is a
|
|
3742
|
+
* quadrilateral with converging edges. Correcting that needs a full projective
|
|
3743
|
+
* map, not an affine one; an affine approximation reads the near edge of a
|
|
3744
|
+
* tilted symbol correctly and drifts a module or more by the far edge.
|
|
3692
3745
|
*
|
|
3693
|
-
*
|
|
3694
|
-
*
|
|
3695
|
-
* perfectly within this library while producing symbols no scanner can read.
|
|
3696
|
-
* There is only one order because there is only one implementation of it.
|
|
3746
|
+
* The map is a 3x3 homogeneous matrix. Points are transformed as
|
|
3747
|
+
* (x, y, 1) * M, then divided through by the resulting w.
|
|
3697
3748
|
*
|
|
3698
|
-
* @
|
|
3699
|
-
* @returns {Int32Array} Shared, cached — treat as immutable. Length is
|
|
3700
|
-
* `2 * freeModuleCount(version)`.
|
|
3749
|
+
* @module image/perspective
|
|
3701
3750
|
*/
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
let n = 0;
|
|
3751
|
+
class PerspectiveTransform {
|
|
3752
|
+
/* eslint-disable-next-line max-params */
|
|
3753
|
+
constructor(a11, a21, a31, a12, a22, a32, a13, a23, a33) {
|
|
3754
|
+
this.a11 = a11; this.a21 = a21; this.a31 = a31;
|
|
3755
|
+
this.a12 = a12; this.a22 = a22; this.a32 = a32;
|
|
3756
|
+
this.a13 = a13; this.a23 = a23; this.a33 = a33;
|
|
3757
|
+
}
|
|
3710
3758
|
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
for (let i = 0; i <
|
|
3720
|
-
const
|
|
3759
|
+
/**
|
|
3760
|
+
* Transform points in place.
|
|
3761
|
+
*
|
|
3762
|
+
* @param {Float32Array | number[]} points Interleaved [x0, y0, x1, y1, ...].
|
|
3763
|
+
* @returns {Float32Array | number[]} The same array.
|
|
3764
|
+
*/
|
|
3765
|
+
transform(points) {
|
|
3766
|
+
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
3767
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
3768
|
+
const x = points[i];
|
|
3769
|
+
const y = points[i + 1];
|
|
3770
|
+
const w = a13 * x + a23 * y + a33;
|
|
3771
|
+
points[i] = (a11 * x + a21 * y + a31) / w;
|
|
3772
|
+
points[i + 1] = (a12 * x + a22 * y + a32) / w;
|
|
3773
|
+
}
|
|
3774
|
+
return points;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
/**
|
|
3778
|
+
* Transform a single point.
|
|
3779
|
+
*
|
|
3780
|
+
* @param {number} x @param {number} y
|
|
3781
|
+
* @returns {{x: number, y: number}}
|
|
3782
|
+
*/
|
|
3783
|
+
transformPoint(x, y) {
|
|
3784
|
+
const w = this.a13 * x + this.a23 * y + this.a33;
|
|
3785
|
+
return {
|
|
3786
|
+
x: (this.a11 * x + this.a21 * y + this.a31) / w,
|
|
3787
|
+
y: (this.a12 * x + this.a22 * y + this.a32) / w,
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
/**
|
|
3792
|
+
* Map the unit square — (0,0), (1,0), (1,1), (0,1) — onto an arbitrary quad.
|
|
3793
|
+
*
|
|
3794
|
+
* Corners are given in that same order, i.e. going around the quad, not
|
|
3795
|
+
* as opposite pairs.
|
|
3796
|
+
*
|
|
3797
|
+
* @returns {PerspectiveTransform}
|
|
3798
|
+
*/
|
|
3799
|
+
/* eslint-disable-next-line max-params */
|
|
3800
|
+
static squareToQuad(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
3801
|
+
const dx3 = x0 - x1 + x2 - x3;
|
|
3802
|
+
const dy3 = y0 - y1 + y2 - y3;
|
|
3803
|
+
|
|
3804
|
+
if (dx3 === 0 && dy3 === 0) {
|
|
3805
|
+
// The quad is a parallelogram, so the map is affine and the projective
|
|
3806
|
+
// terms vanish. Worth special-casing: it is the common case for flat
|
|
3807
|
+
// scans, and the general solution divides by zero here.
|
|
3808
|
+
return new PerspectiveTransform(
|
|
3809
|
+
x1 - x0, x2 - x1, x0,
|
|
3810
|
+
y1 - y0, y2 - y1, y0,
|
|
3811
|
+
0, 0, 1
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
const dx1 = x1 - x2;
|
|
3816
|
+
const dx2 = x3 - x2;
|
|
3817
|
+
const dy1 = y1 - y2;
|
|
3818
|
+
const dy2 = y3 - y2;
|
|
3819
|
+
const denominator = dx1 * dy2 - dx2 * dy1;
|
|
3820
|
+
const a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
|
|
3821
|
+
const a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
|
|
3822
|
+
|
|
3823
|
+
return new PerspectiveTransform(
|
|
3824
|
+
x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
|
|
3825
|
+
y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
|
|
3826
|
+
a13, a23, 1
|
|
3827
|
+
);
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
/**
|
|
3831
|
+
* Map an arbitrary quad onto the unit square — the inverse of
|
|
3832
|
+
* {@link squareToQuad}, via the adjugate.
|
|
3833
|
+
*
|
|
3834
|
+
* @returns {PerspectiveTransform}
|
|
3835
|
+
*/
|
|
3836
|
+
/* eslint-disable-next-line max-params */
|
|
3837
|
+
static quadToSquare(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
3838
|
+
return PerspectiveTransform.squareToQuad(x0, y0, x1, y1, x2, y2, x3, y3).inverse();
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
/**
|
|
3842
|
+
* Map one quad onto another, corner for corner.
|
|
3843
|
+
*
|
|
3844
|
+
* This is what turns four detected finder corners into a sampling grid:
|
|
3845
|
+
* compose "detected quad -> unit square" with "unit square -> ideal grid".
|
|
3846
|
+
*
|
|
3847
|
+
* @returns {PerspectiveTransform}
|
|
3848
|
+
*/
|
|
3849
|
+
/* eslint-disable-next-line max-params */
|
|
3850
|
+
static quadToQuad(
|
|
3851
|
+
sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3,
|
|
3852
|
+
dx0, dy0, dx1, dy1, dx2, dy2, dx3, dy3
|
|
3853
|
+
) {
|
|
3854
|
+
const toSquare = PerspectiveTransform.quadToSquare(sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
|
|
3855
|
+
const toQuad = PerspectiveTransform.squareToQuad(dx0, dy0, dx1, dy1, dx2, dy2, dx3, dy3);
|
|
3856
|
+
return toSquare.times(toQuad);
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
/**
|
|
3860
|
+
* Adjugate — the inverse up to a scale factor, which is irrelevant in
|
|
3861
|
+
* homogeneous coordinates because the division by w cancels it.
|
|
3862
|
+
*
|
|
3863
|
+
* @returns {PerspectiveTransform}
|
|
3864
|
+
*/
|
|
3865
|
+
inverse() {
|
|
3866
|
+
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
3867
|
+
return new PerspectiveTransform(
|
|
3868
|
+
a22 * a33 - a23 * a32,
|
|
3869
|
+
a23 * a31 - a21 * a33,
|
|
3870
|
+
a21 * a32 - a22 * a31,
|
|
3871
|
+
a13 * a32 - a12 * a33,
|
|
3872
|
+
a11 * a33 - a13 * a31,
|
|
3873
|
+
a12 * a31 - a11 * a32,
|
|
3874
|
+
a12 * a23 - a13 * a22,
|
|
3875
|
+
a13 * a21 - a11 * a23,
|
|
3876
|
+
a11 * a22 - a12 * a21
|
|
3877
|
+
);
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
/**
|
|
3881
|
+
* Matrix product: apply `this` first, then `other`.
|
|
3882
|
+
*
|
|
3883
|
+
* @param {PerspectiveTransform} other
|
|
3884
|
+
* @returns {PerspectiveTransform}
|
|
3885
|
+
*/
|
|
3886
|
+
times(other) {
|
|
3887
|
+
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
3888
|
+
const o = other;
|
|
3889
|
+
return new PerspectiveTransform(
|
|
3890
|
+
o.a11 * a11 + o.a21 * a12 + o.a31 * a13,
|
|
3891
|
+
o.a11 * a21 + o.a21 * a22 + o.a31 * a23,
|
|
3892
|
+
o.a11 * a31 + o.a21 * a32 + o.a31 * a33,
|
|
3893
|
+
o.a12 * a11 + o.a22 * a12 + o.a32 * a13,
|
|
3894
|
+
o.a12 * a21 + o.a22 * a22 + o.a32 * a23,
|
|
3895
|
+
o.a12 * a31 + o.a22 * a32 + o.a32 * a33,
|
|
3896
|
+
o.a13 * a11 + o.a23 * a12 + o.a33 * a13,
|
|
3897
|
+
o.a13 * a21 + o.a23 * a22 + o.a33 * a23,
|
|
3898
|
+
o.a13 * a31 + o.a23 * a32 + o.a33 * a33
|
|
3899
|
+
);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
__exports.PerspectiveTransform = PerspectiveTransform;
|
|
3904
|
+
};
|
|
3905
|
+
|
|
3906
|
+
__modules["image/grid-sampler.js"] = function (__require, __exports) {
|
|
3907
|
+
/**
|
|
3908
|
+
* Resample a distorted symbol in the image into an upright module grid.
|
|
3909
|
+
*
|
|
3910
|
+
* Given a transform that maps grid coordinates to image coordinates, this
|
|
3911
|
+
* samples the centre of every module. Sampling centres rather than averaging
|
|
3912
|
+
* whole cells is deliberate: module edges are where blur and bleed live, and
|
|
3913
|
+
* including them turns a marginal symbol into an unreadable one.
|
|
3914
|
+
*
|
|
3915
|
+
* @module image/grid-sampler
|
|
3916
|
+
*/
|
|
3917
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
3918
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
3919
|
+
const { PerspectiveTransform } = __require("image/perspective.js");
|
|
3920
|
+
|
|
3921
|
+
/**
|
|
3922
|
+
* Sample a `dimension` x `dimension` grid (or `width` x `height`).
|
|
3923
|
+
*
|
|
3924
|
+
* @param {BitMatrix} image Binarized source image.
|
|
3925
|
+
* @param {number} width Modules across.
|
|
3926
|
+
* @param {number} height Modules down.
|
|
3927
|
+
* @param {PerspectiveTransform} transform Grid space -> image space.
|
|
3928
|
+
* @returns {BitMatrix}
|
|
3929
|
+
* @throws {NotFoundError} If the grid falls outside the image.
|
|
3930
|
+
*/
|
|
3931
|
+
function sampleGrid(image, width, height, transform) {
|
|
3932
|
+
const out = new BitMatrix(width, height);
|
|
3933
|
+
const points = new Float32Array(width * 2);
|
|
3934
|
+
|
|
3935
|
+
for (let y = 0; y < height; y++) {
|
|
3936
|
+
// Module centres: offset by half a module in both axes.
|
|
3937
|
+
const gridY = y + 0.5;
|
|
3938
|
+
for (let x = 0; x < width; x++) {
|
|
3939
|
+
points[x * 2] = x + 0.5;
|
|
3940
|
+
points[x * 2 + 1] = gridY;
|
|
3941
|
+
}
|
|
3942
|
+
transform.transform(points);
|
|
3943
|
+
|
|
3944
|
+
for (let x = 0; x < width; x++) {
|
|
3945
|
+
const px = points[x * 2] | 0;
|
|
3946
|
+
const py = points[x * 2 + 1] | 0;
|
|
3947
|
+
if (px < 0 || py < 0 || px >= image.width || py >= image.height) {
|
|
3948
|
+
throw new NotFoundError(
|
|
3949
|
+
`Sampling grid escapes the image at module (${x}, ${y})`
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
if (image.get(px, py)) out.set(x, y);
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
return out;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
/**
|
|
3960
|
+
* Sample with a 3x3 majority vote per module.
|
|
3961
|
+
*
|
|
3962
|
+
* Slower, and worth it when a single-point sample lands on a speck of noise or
|
|
3963
|
+
* a JPEG artefact. Readers fall back to this after a clean sample fails to
|
|
3964
|
+
* decode, rather than paying for it on every attempt.
|
|
3965
|
+
*
|
|
3966
|
+
* @param {BitMatrix} image
|
|
3967
|
+
* @param {number} width
|
|
3968
|
+
* @param {number} height
|
|
3969
|
+
* @param {PerspectiveTransform} transform
|
|
3970
|
+
* @returns {BitMatrix}
|
|
3971
|
+
*/
|
|
3972
|
+
function sampleGridVoting(image, width, height, transform) {
|
|
3973
|
+
const out = new BitMatrix(width, height);
|
|
3974
|
+
|
|
3975
|
+
// Spacing between module centres, measured in image pixels, so the vote
|
|
3976
|
+
// spreads across the module rather than a fixed pixel radius that would be
|
|
3977
|
+
// meaningless at a different scale.
|
|
3978
|
+
const p0 = transform.transformPoint(0.5, 0.5);
|
|
3979
|
+
const p1 = transform.transformPoint(1.5, 0.5);
|
|
3980
|
+
const p2 = transform.transformPoint(0.5, 1.5);
|
|
3981
|
+
const stepX = Math.hypot(p1.x - p0.x, p1.y - p0.y);
|
|
3982
|
+
const stepY = Math.hypot(p2.x - p0.x, p2.y - p0.y);
|
|
3983
|
+
const rx = Math.max(1, Math.round(stepX / 4));
|
|
3984
|
+
const ry = Math.max(1, Math.round(stepY / 4));
|
|
3985
|
+
|
|
3986
|
+
for (let y = 0; y < height; y++) {
|
|
3987
|
+
for (let x = 0; x < width; x++) {
|
|
3988
|
+
const c = transform.transformPoint(x + 0.5, y + 0.5);
|
|
3989
|
+
const cx = c.x | 0;
|
|
3990
|
+
const cy = c.y | 0;
|
|
3991
|
+
if (cx < 0 || cy < 0 || cx >= image.width || cy >= image.height) {
|
|
3992
|
+
throw new NotFoundError(
|
|
3993
|
+
`Sampling grid escapes the image at module (${x}, ${y})`
|
|
3994
|
+
);
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
let dark = 0;
|
|
3998
|
+
let total = 0;
|
|
3999
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
4000
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
4001
|
+
const sx = cx + dx * rx;
|
|
4002
|
+
const sy = cy + dy * ry;
|
|
4003
|
+
if (sx < 0 || sy < 0 || sx >= image.width || sy >= image.height) continue;
|
|
4004
|
+
total++;
|
|
4005
|
+
if (image.get(sx, sy)) dark++;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
if (total > 0 && dark * 2 > total) out.set(x, y);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
return out;
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
/**
|
|
4016
|
+
* Build the transform for a symbol whose four corners are known, and sample it.
|
|
4017
|
+
*
|
|
4018
|
+
* Corners are in reading order: top-left, top-right, bottom-right, bottom-left.
|
|
4019
|
+
*
|
|
4020
|
+
* @param {BitMatrix} image
|
|
4021
|
+
* @param {number} dimension Modules per side.
|
|
4022
|
+
* @param {Array<{x: number, y: number}>} corners
|
|
4023
|
+
* @param {boolean} [voting]
|
|
4024
|
+
* @returns {BitMatrix}
|
|
4025
|
+
*/
|
|
4026
|
+
function sampleQuad(image, dimension, corners, voting = false) {
|
|
4027
|
+
if (corners.length !== 4) throw new NotFoundError('sampleQuad needs exactly 4 corners');
|
|
4028
|
+
const [tl, tr, br, bl] = corners;
|
|
4029
|
+
const d = dimension;
|
|
4030
|
+
|
|
4031
|
+
const transform = PerspectiveTransform.quadToQuad(
|
|
4032
|
+
0, 0, d, 0, d, d, 0, d,
|
|
4033
|
+
tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y
|
|
4034
|
+
);
|
|
4035
|
+
|
|
4036
|
+
return voting
|
|
4037
|
+
? sampleGridVoting(image, d, d, transform)
|
|
4038
|
+
: sampleGrid(image, d, d, transform);
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
__exports.sampleGrid = sampleGrid;
|
|
4042
|
+
__exports.sampleGridVoting = sampleGridVoting;
|
|
4043
|
+
__exports.sampleQuad = sampleQuad;
|
|
4044
|
+
};
|
|
4045
|
+
|
|
4046
|
+
__modules["datamatrix/detector.js"] = function (__require, __exports) {
|
|
4047
|
+
/**
|
|
4048
|
+
* Data Matrix ECC 200 detection in a binarized image.
|
|
4049
|
+
*
|
|
4050
|
+
* An ECC 200 symbol is distinguished by two neighbouring solid finder borders
|
|
4051
|
+
* (the L) and two alternating clock borders. The detector first finds dark
|
|
4052
|
+
* connected components, then scores every legal ECC 200 size and every
|
|
4053
|
+
* quarter-turn of the component's bounding quadrilateral against those four
|
|
4054
|
+
* borders. This deliberately verifies the complete border rather than merely
|
|
4055
|
+
* looking for an L: ordinary text and table rules produce L shapes often.
|
|
4056
|
+
*
|
|
4057
|
+
* The resulting quadrilateral is sampled back into the canonical orientation:
|
|
4058
|
+
* solid borders at left and bottom. It is intentionally independent of the
|
|
4059
|
+
* payload decoder, so geometry can be used by callers that need the matrix.
|
|
4060
|
+
*
|
|
4061
|
+
* @module datamatrix/detector
|
|
4062
|
+
*/
|
|
4063
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
4064
|
+
const { sampleGrid, sampleQuad } = __require("image/grid-sampler.js");
|
|
4065
|
+
const { PerspectiveTransform } = __require("image/perspective.js");
|
|
4066
|
+
const { decodeDataMatrix } = __require("datamatrix/decoder.js");
|
|
4067
|
+
|
|
4068
|
+
// ECC 200 dimensions. DMRE is deliberately not included: it uses a separate
|
|
4069
|
+
// size table and is not part of the original ECC 200 family implemented here.
|
|
4070
|
+
const SIZES = [
|
|
4071
|
+
[10, 10], [12, 12], [14, 14], [16, 16], [18, 18], [20, 20], [22, 22], [24, 24], [26, 26],
|
|
4072
|
+
[32, 32], [36, 36], [40, 40], [44, 44], [48, 48], [52, 52], [64, 64], [72, 72], [80, 80],
|
|
4073
|
+
[88, 88], [96, 96], [104, 104], [120, 120], [132, 132], [144, 144],
|
|
4074
|
+
[18, 8], [32, 8], [26, 12], [36, 12], [36, 16], [48, 16],
|
|
4075
|
+
];
|
|
4076
|
+
|
|
4077
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
4078
|
+
/** @typedef {{corners: Point[], dimension: number, width: number, height: number, moduleSize: number, matrix: import('../core/bit-matrix.js').BitMatrix}} Detection */
|
|
4079
|
+
|
|
4080
|
+
function dark(image, x, y) {
|
|
4081
|
+
return image.get(Math.max(0, Math.min(image.width - 1, Math.round(x))),
|
|
4082
|
+
Math.max(0, Math.min(image.height - 1, Math.round(y))));
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
/** Return components which are large enough to plausibly contain a symbol. */
|
|
4086
|
+
function components(image) {
|
|
4087
|
+
const seen = new Uint8Array(image.width * image.height);
|
|
4088
|
+
const out = [];
|
|
4089
|
+
const push = (x, y, xs, ys) => { xs.push(x); ys.push(y); };
|
|
4090
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
4091
|
+
const start = y * image.width + x;
|
|
4092
|
+
if (seen[start] || !image.get(x, y)) continue;
|
|
4093
|
+
const xs = [x], ys = [y]; seen[start] = 1;
|
|
4094
|
+
let head = 0, minX = x, maxX = x, minY = y, maxY = y;
|
|
4095
|
+
while (head < xs.length) {
|
|
4096
|
+
const px = xs[head], py = ys[head++];
|
|
4097
|
+
if (px < minX) minX = px; if (px > maxX) maxX = px;
|
|
4098
|
+
if (py < minY) minY = py; if (py > maxY) maxY = py;
|
|
4099
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
4100
|
+
if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height) continue;
|
|
4101
|
+
const at = ny * image.width + nx;
|
|
4102
|
+
if (!seen[at] && image.get(nx, ny)) { seen[at] = 1; push(nx, ny, xs, ys); }
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
if (maxX - minX >= 7 && maxY - minY >= 7) out.push({ minX, minY, maxX, maxY, pixels: xs.length });
|
|
4106
|
+
}
|
|
4107
|
+
return out.sort((a, b) => b.pixels - a.pixels).slice(0, 40);
|
|
4108
|
+
}
|
|
4109
|
+
|
|
4110
|
+
/** Sample a physical edge at module centres. */
|
|
4111
|
+
function edge(image, a, b, count) {
|
|
4112
|
+
const values = [];
|
|
4113
|
+
for (let i = 0; i < count; i++) {
|
|
4114
|
+
const t = (i + 0.5) / count;
|
|
4115
|
+
values.push(dark(image, a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t));
|
|
4116
|
+
}
|
|
4117
|
+
return values;
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
function solidScore(values) {
|
|
4121
|
+
let n = 0; for (const value of values) if (value) n++;
|
|
4122
|
+
return n / values.length;
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
function clockScore(values, startsDark) {
|
|
4126
|
+
let n = 0;
|
|
4127
|
+
for (let i = 0; i < values.length; i++) if (values[i] === ((i & 1) === 0 ? startsDark : !startsDark)) n++;
|
|
4128
|
+
return n / values.length;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
/** Count light/dark changes along a physical edge at approximately one-pixel intervals. */
|
|
4132
|
+
function edgeTransitions(image, a, b) {
|
|
4133
|
+
const steps = Math.max(1, Math.ceil(Math.hypot(b.x - a.x, b.y - a.y)));
|
|
4134
|
+
let previous = dark(image, a.x, a.y);
|
|
4135
|
+
let changes = 0;
|
|
4136
|
+
for (let i = 1; i <= steps; i++) {
|
|
4137
|
+
const t = i / steps;
|
|
4138
|
+
const value = dark(image, a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
|
4139
|
+
if (value !== previous) changes++;
|
|
4140
|
+
previous = value;
|
|
4141
|
+
}
|
|
4142
|
+
return changes;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
/** Reject a smaller harmonic whose module-centre samples happen to alternate. */
|
|
4146
|
+
function transitionCountFits(observed, modules) {
|
|
4147
|
+
const expected = modules - 1;
|
|
4148
|
+
const tolerance = Math.max(2, Math.floor(expected * 0.08));
|
|
4149
|
+
return Math.abs(observed - expected) <= tolerance;
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
function sample(image, width, height, corners, voting) {
|
|
4153
|
+
if (width === height) return sampleQuad(image, width, corners, voting);
|
|
4154
|
+
const [tl, tr, br, bl] = corners;
|
|
4155
|
+
const transform = PerspectiveTransform.quadToQuad(0, 0, width, 0, width, height, 0, height,
|
|
4156
|
+
tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y);
|
|
4157
|
+
return sampleGrid(image, width, height, transform);
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
/**
|
|
4161
|
+
* Find Data Matrix symbols in a binarized image.
|
|
4162
|
+
*
|
|
4163
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
|
|
4164
|
+
* @returns {Detection | null} The strongest candidate, or null when absent.
|
|
4165
|
+
*/
|
|
4166
|
+
function detectDataMatrix(binaryImage) {
|
|
4167
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
4168
|
+
throw new NotFoundError('detectDataMatrix: no image supplied');
|
|
4169
|
+
}
|
|
4170
|
+
const detections = [];
|
|
4171
|
+
const used = new Set();
|
|
4172
|
+
for (const box of components(binaryImage)) {
|
|
4173
|
+
const base = [
|
|
4174
|
+
{ x: box.minX, y: box.minY }, { x: box.maxX + 1, y: box.minY },
|
|
4175
|
+
{ x: box.maxX + 1, y: box.maxY + 1 }, { x: box.minX, y: box.maxY + 1 },
|
|
4176
|
+
];
|
|
4177
|
+
// Profile the actual ink, rather than the outer sampling quadrilateral:
|
|
4178
|
+
// its far x/y boundary lies one pixel beyond the last dark pixel.
|
|
4179
|
+
const ink = [
|
|
4180
|
+
{ x: box.minX, y: box.minY }, { x: box.maxX, y: box.minY },
|
|
4181
|
+
{ x: box.maxX, y: box.maxY }, { x: box.minX, y: box.maxY },
|
|
4182
|
+
];
|
|
4183
|
+
for (const [w, h] of SIZES) for (let turn = 0; turn < 4; turn++) {
|
|
4184
|
+
// A 90 degree turn swaps physical width and height.
|
|
4185
|
+
const physicalW = (turn & 1) ? h : w, physicalH = (turn & 1) ? w : h;
|
|
4186
|
+
const pitchX = (box.maxX - box.minX + 1) / physicalW;
|
|
4187
|
+
const pitchY = (box.maxY - box.minY + 1) / physicalH;
|
|
4188
|
+
if (Math.min(pitchX, pitchY) < 1 || Math.abs(pitchX - pitchY) > Math.max(pitchX, pitchY) * 0.22) continue;
|
|
4189
|
+
const corners = base.slice(turn).concat(base.slice(0, turn));
|
|
4190
|
+
const profile = ink.slice(turn).concat(ink.slice(0, turn));
|
|
4191
|
+
// Canonical edge order: top clock, right clock, bottom solid, left solid.
|
|
4192
|
+
const top = edge(binaryImage, profile[0], profile[1], w);
|
|
4193
|
+
const right = edge(binaryImage, profile[1], profile[2], h);
|
|
4194
|
+
const bottom = edge(binaryImage, profile[2], profile[3], w);
|
|
4195
|
+
const left = edge(binaryImage, profile[3], profile[0], h);
|
|
4196
|
+
// Sampling only the proposed module centres aliases exact harmonics: an
|
|
4197
|
+
// 80-module clock border, for example, can look like a perfect 16-module
|
|
4198
|
+
// border. Count transitions at image-pixel resolution as an independent
|
|
4199
|
+
// dimension measurement before accepting the candidate.
|
|
4200
|
+
if (!transitionCountFits(edgeTransitions(binaryImage, profile[0], profile[1]), w) ||
|
|
4201
|
+
!transitionCountFits(edgeTransitions(binaryImage, profile[1], profile[2]), h)) continue;
|
|
4202
|
+
// The top clock starts dark at the solid left border. The right clock is
|
|
4203
|
+
// anchored dark at the solid bottom border instead, so its top phase
|
|
4204
|
+
// depends on the symbol height (all ECC 200 heights are even and
|
|
4205
|
+
// therefore start light).
|
|
4206
|
+
const score = (clockScore(top, true) + clockScore(right, (h & 1) === 1) +
|
|
4207
|
+
solidScore(bottom) + solidScore(left)) / 4;
|
|
4208
|
+
if (score < 0.88) continue;
|
|
4209
|
+
const key = `${box.minX},${box.minY},${box.maxX},${box.maxY}`;
|
|
4210
|
+
if (used.has(key)) continue;
|
|
4211
|
+
let matrix;
|
|
4212
|
+
try { matrix = sample(binaryImage, w, h, corners, false); } catch (e) { continue; }
|
|
4213
|
+
used.add(key);
|
|
4214
|
+
detections.push({ corners, dimension: w === h ? w : 0, width: w, height: h,
|
|
4215
|
+
moduleSize: (pitchX + pitchY) / 2, matrix, score });
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
detections.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
|
|
4219
|
+
return detections[0] ?? null;
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
/**
|
|
4223
|
+
* Detect and decode Data Matrix symbols. Detection failure is normal for an
|
|
4224
|
+
* image without a symbol, so candidates that cannot decode are skipped.
|
|
4225
|
+
*
|
|
4226
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
4227
|
+
* @returns {(import('./decoder.js').DecodeResult & {corners: Point[]}) | null}
|
|
4228
|
+
*/
|
|
4229
|
+
function detectAndDecodeDataMatrix(binaryImage) {
|
|
4230
|
+
let detection;
|
|
4231
|
+
try { detection = detectDataMatrix(binaryImage); } catch (e) { return null; }
|
|
4232
|
+
if (!detection) return null;
|
|
4233
|
+
for (const voting of [false, true]) {
|
|
4234
|
+
let matrix = detection.matrix;
|
|
4235
|
+
try { if (voting) matrix = sample(binaryImage, detection.width, detection.height, detection.corners, true); } catch (e) { continue; }
|
|
4236
|
+
try { return Object.assign({ corners: detection.corners }, decodeDataMatrix(matrix)); }
|
|
4237
|
+
catch (e) { /* A geometric candidate is not necessarily a symbol. */ }
|
|
4238
|
+
}
|
|
4239
|
+
return null;
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
__exports.detectDataMatrix = detectDataMatrix;
|
|
4243
|
+
__exports.detectAndDecodeDataMatrix = detectAndDecodeDataMatrix;
|
|
4244
|
+
};
|
|
4245
|
+
|
|
4246
|
+
__modules["datamatrix/index.js"] = function (__require, __exports) {
|
|
4247
|
+
/** Data Matrix ECC 200 entry points. @module datamatrix */
|
|
4248
|
+
const __reexport0 = __require("datamatrix/encoder.js"); __exports.encodeDataMatrix = __reexport0.encodeDataMatrix; __exports.encodeDataMatrixCodewords = __reexport0.encodeDataMatrixCodewords;
|
|
4249
|
+
const __reexport1 = __require("datamatrix/decoder.js"); __exports.decodeDataMatrix = __reexport1.decodeDataMatrix;
|
|
4250
|
+
const __reexport2 = __require("datamatrix/detector.js"); __exports.detectDataMatrix = __reexport2.detectDataMatrix; __exports.detectAndDecodeDataMatrix = __reexport2.detectAndDecodeDataMatrix;
|
|
4251
|
+
const __reexport3 = __require("datamatrix/tables.js"); __exports.DATAMATRIX_SYMBOLS = __reexport3.DATAMATRIX_SYMBOLS; __exports.SYMBOLS = __reexport3.SYMBOLS; __exports.symbolForDataCodewords = __reexport3.symbolForDataCodewords; __exports.validateDataMatrixTables = __reexport3.validateDataMatrixTables; __exports.validateTables = __reexport3.validateTables;
|
|
4252
|
+
|
|
4253
|
+
|
|
4254
|
+
};
|
|
4255
|
+
|
|
4256
|
+
__modules["core/bit-buffer.js"] = function (__require, __exports) {
|
|
4257
|
+
/**
|
|
4258
|
+
* Bit-level writing and reading, MSB-first.
|
|
4259
|
+
*
|
|
4260
|
+
* Every 2D symbology serialises its payload as a bitstream that does not
|
|
4261
|
+
* respect byte boundaries — QR alone mixes 4-bit mode indicators, 10-bit
|
|
4262
|
+
* character-count fields and 11-bit alphanumeric pairs. These two classes are
|
|
4263
|
+
* the write and read halves of that.
|
|
4264
|
+
*
|
|
4265
|
+
* @module core/bit-buffer
|
|
4266
|
+
*/
|
|
4267
|
+
const { FormatError } = __require("core/errors.js");
|
|
4268
|
+
|
|
4269
|
+
/** Growable MSB-first bit writer. */
|
|
4270
|
+
class BitWriter {
|
|
4271
|
+
constructor() {
|
|
4272
|
+
/** @type {number[]} Packed bytes; the last one may be partially filled. */
|
|
4273
|
+
this.bytes = [];
|
|
4274
|
+
this.bitLength = 0;
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
/** @returns {number} Bits written so far. */
|
|
4278
|
+
get length() {
|
|
4279
|
+
return this.bitLength;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
/**
|
|
4283
|
+
* Append the low `count` bits of `value`, most significant first.
|
|
4284
|
+
*
|
|
4285
|
+
* @param {number} value
|
|
4286
|
+
* @param {number} count
|
|
4287
|
+
*/
|
|
4288
|
+
put(value, count) {
|
|
4289
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
4290
|
+
this.putBit(((value >>> i) & 1) === 1);
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
/** @param {boolean} bit */
|
|
4295
|
+
putBit(bit) {
|
|
4296
|
+
const idx = this.bitLength >>> 3;
|
|
4297
|
+
if (this.bytes.length <= idx) this.bytes.push(0);
|
|
4298
|
+
if (bit) this.bytes[idx] |= 0x80 >>> (this.bitLength & 7);
|
|
4299
|
+
this.bitLength++;
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
/** @param {ArrayLike<number>} data */
|
|
4303
|
+
putBytes(data) {
|
|
4304
|
+
for (let i = 0; i < data.length; i++) this.put(data[i], 8);
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
/** Pad with zero bits until the length is a multiple of 8. */
|
|
4308
|
+
padToByte() {
|
|
4309
|
+
while (this.bitLength & 7) this.putBit(false);
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
/**
|
|
4313
|
+
* @returns {Uint8Array} Byte view; trailing bits of the final byte are zero.
|
|
4314
|
+
*/
|
|
4315
|
+
toBytes() {
|
|
4316
|
+
return Uint8Array.from(this.bytes);
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
/** @returns {string} Debug view, e.g. "0100 0011 0101". */
|
|
4320
|
+
toString() {
|
|
4321
|
+
let s = '';
|
|
4322
|
+
for (let i = 0; i < this.bitLength; i++) {
|
|
4323
|
+
if (i && i % 4 === 0) s += ' ';
|
|
4324
|
+
s += (this.bytes[i >>> 3] >>> (7 - (i & 7))) & 1;
|
|
4325
|
+
}
|
|
4326
|
+
return s;
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
/** MSB-first bit reader over a byte array. */
|
|
4331
|
+
class BitReader {
|
|
4332
|
+
/** @param {ArrayLike<number>} bytes */
|
|
4333
|
+
constructor(bytes) {
|
|
4334
|
+
this.bytes = bytes;
|
|
4335
|
+
this.byteOffset = 0;
|
|
4336
|
+
this.bitOffset = 0;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
/** @returns {number} Bits not yet consumed. */
|
|
4340
|
+
available() {
|
|
4341
|
+
return 8 * (this.bytes.length - this.byteOffset) - this.bitOffset;
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
/**
|
|
4345
|
+
* Read `count` bits (1..32) as an unsigned integer, most significant first.
|
|
4346
|
+
*
|
|
4347
|
+
* @param {number} count
|
|
4348
|
+
* @returns {number}
|
|
4349
|
+
* @throws {FormatError} If the stream is exhausted.
|
|
4350
|
+
*/
|
|
4351
|
+
read(count) {
|
|
4352
|
+
if (count < 1 || count > 32) {
|
|
4353
|
+
throw new FormatError(`BitReader: cannot read ${count} bits`);
|
|
4354
|
+
}
|
|
4355
|
+
if (count > this.available()) {
|
|
4356
|
+
throw new FormatError(
|
|
4357
|
+
`BitReader: needed ${count} bits, ${this.available()} remain`
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
let result = 0;
|
|
4362
|
+
let remaining = count;
|
|
4363
|
+
|
|
4364
|
+
// Finish the partially consumed byte first, then take whole bytes.
|
|
4365
|
+
if (this.bitOffset > 0) {
|
|
4366
|
+
const inCurrent = 8 - this.bitOffset;
|
|
4367
|
+
const take = Math.min(remaining, inCurrent);
|
|
4368
|
+
const shift = inCurrent - take;
|
|
4369
|
+
const mask = (0xff >> this.bitOffset) & ~((1 << shift) - 1);
|
|
4370
|
+
result = (this.bytes[this.byteOffset] & mask) >> shift;
|
|
4371
|
+
remaining -= take;
|
|
4372
|
+
this.bitOffset += take;
|
|
4373
|
+
if (this.bitOffset === 8) {
|
|
4374
|
+
this.bitOffset = 0;
|
|
4375
|
+
this.byteOffset++;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
while (remaining >= 8) {
|
|
4380
|
+
result = (result << 8) | (this.bytes[this.byteOffset] & 0xff);
|
|
4381
|
+
this.byteOffset++;
|
|
4382
|
+
remaining -= 8;
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
if (remaining > 0) {
|
|
4386
|
+
const shift = 8 - remaining;
|
|
4387
|
+
const mask = ~((1 << shift) - 1) & 0xff;
|
|
4388
|
+
result = (result << remaining) | ((this.bytes[this.byteOffset] & mask) >> shift);
|
|
4389
|
+
this.bitOffset += remaining;
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
return result >>> 0;
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
|
+
/** @returns {boolean} */
|
|
4396
|
+
readBit() {
|
|
4397
|
+
return this.read(1) === 1;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
__exports.BitWriter = BitWriter;
|
|
4402
|
+
__exports.BitReader = BitReader;
|
|
4403
|
+
};
|
|
4404
|
+
|
|
4405
|
+
__modules["qr/tables.js"] = function (__require, __exports) {
|
|
4406
|
+
/**
|
|
4407
|
+
* QR Code structural tables.
|
|
4408
|
+
*
|
|
4409
|
+
* The design principle here is that as little as possible is *recalled* and as
|
|
4410
|
+
* much as possible is *derived*, because a barcode table is the one place where
|
|
4411
|
+
* a single mistyped digit produces a symbol that looks perfect and scans as
|
|
4412
|
+
* garbage — or, worse, scans correctly for the payload you tested and fails for
|
|
4413
|
+
* the payload your user sends.
|
|
4414
|
+
*
|
|
4415
|
+
* So:
|
|
4416
|
+
*
|
|
4417
|
+
* - Symbol size, function-pattern layout and total codeword capacity are
|
|
4418
|
+
* computed from geometry. Nothing is tabulated that the module grid already
|
|
4419
|
+
* knows.
|
|
4420
|
+
* - Alignment centres come from the spec's spacing rule, not a 40-row table.
|
|
4421
|
+
* - The group-1 / group-2 block split is arithmetic, not data.
|
|
4422
|
+
*
|
|
4423
|
+
* That leaves exactly three recalled numbers per (version, level): the error
|
|
4424
|
+
* correction codewords per block, the block count, and the total data codeword
|
|
4425
|
+
* count. Those three are deliberately redundant — they must satisfy
|
|
4426
|
+
*
|
|
4427
|
+
* blocks * eccPerBlock + totalDataCodewords === geometricTotalCodewords(v)
|
|
4428
|
+
*
|
|
4429
|
+
* for all 160 combinations, where the right-hand side is counted off the module
|
|
4430
|
+
* grid. Any single typo on either side breaks the identity. {@link validateTables}
|
|
4431
|
+
* enforces it, and the test suite asserts it returns no problems.
|
|
4432
|
+
*
|
|
4433
|
+
* @module qr/tables
|
|
4434
|
+
*/
|
|
4435
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
4436
|
+
|
|
4437
|
+
/** Error correction levels, weakest to strongest. */
|
|
4438
|
+
const ECC_LEVELS = ['L', 'M', 'Q', 'H'];
|
|
4439
|
+
|
|
4440
|
+
/**
|
|
4441
|
+
* Two-bit level indicator used in the format information.
|
|
4442
|
+
* Note this is *not* the L/M/Q/H ordering — the spec assigns them out of order.
|
|
4443
|
+
*/
|
|
4444
|
+
const ECC_LEVEL_BITS = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 };
|
|
4445
|
+
|
|
4446
|
+
/** Inverse of {@link ECC_LEVEL_BITS}, indexed by the 2-bit value. */
|
|
4447
|
+
const ECC_LEVEL_BY_BITS = ['M', 'L', 'H', 'Q'];
|
|
4448
|
+
const MIN_VERSION = 1;
|
|
4449
|
+
const MAX_VERSION = 40;
|
|
4450
|
+
|
|
4451
|
+
/** Version at and above which an 18-bit version information block is carried. */
|
|
4452
|
+
const VERSION_INFO_MIN = 7;
|
|
4453
|
+
|
|
4454
|
+
/** Mode indicator nibbles. */
|
|
4455
|
+
const MODE = {
|
|
4456
|
+
TERMINATOR: 0x0,
|
|
4457
|
+
NUMERIC: 0x1,
|
|
4458
|
+
ALPHANUMERIC: 0x2,
|
|
4459
|
+
STRUCTURED_APPEND: 0x3,
|
|
4460
|
+
BYTE: 0x4,
|
|
4461
|
+
FNC1_FIRST: 0x5,
|
|
4462
|
+
ECI: 0x7,
|
|
4463
|
+
KANJI: 0x8,
|
|
4464
|
+
FNC1_SECOND: 0x9,
|
|
4465
|
+
};
|
|
4466
|
+
|
|
4467
|
+
/**
|
|
4468
|
+
* Character count indicator width, in bits, by mode and version band.
|
|
4469
|
+
*
|
|
4470
|
+
* The bands are versions 1-9, 10-26 and 27-40. They are the reason segment
|
|
4471
|
+
* selection and version selection are mutually dependent: widening the count
|
|
4472
|
+
* field can push a payload over a version boundary, which widens it again.
|
|
4473
|
+
*/
|
|
4474
|
+
const COUNT_BITS = {
|
|
4475
|
+
[MODE.NUMERIC]: [10, 12, 14],
|
|
4476
|
+
[MODE.ALPHANUMERIC]: [9, 11, 13],
|
|
4477
|
+
[MODE.BYTE]: [8, 16, 16],
|
|
4478
|
+
[MODE.KANJI]: [8, 10, 12],
|
|
4479
|
+
};
|
|
4480
|
+
|
|
4481
|
+
/**
|
|
4482
|
+
* @param {number} version 1-40
|
|
4483
|
+
* @returns {number} Modules per side.
|
|
4484
|
+
*/
|
|
4485
|
+
function versionSize(version) {
|
|
4486
|
+
return 17 + 4 * version;
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
/**
|
|
4490
|
+
* Bits in the character count indicator.
|
|
4491
|
+
*
|
|
4492
|
+
* @param {number} mode One of {@link MODE}.
|
|
4493
|
+
* @param {number} version
|
|
4494
|
+
* @returns {number}
|
|
4495
|
+
*/
|
|
4496
|
+
function countBits(mode, version) {
|
|
4497
|
+
const widths = COUNT_BITS[mode];
|
|
4498
|
+
if (!widths) return 0;
|
|
4499
|
+
if (version <= 9) return widths[0];
|
|
4500
|
+
if (version <= 26) return widths[1];
|
|
4501
|
+
return widths[2];
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
/**
|
|
4505
|
+
* Centre coordinates of the alignment patterns for a version.
|
|
4506
|
+
*
|
|
4507
|
+
* The spec's rule: the first centre is always 6 and the last is always
|
|
4508
|
+
* `size - 7`; the count grows by one every seven versions; and the centres are
|
|
4509
|
+
* evenly spaced with the *first* gap absorbing the rounding slack. Expressing
|
|
4510
|
+
* that as arithmetic rather than a 40-row table means there is no table to
|
|
4511
|
+
* mistype, and {@link validateTables} can then assert the shape of the result.
|
|
4512
|
+
*
|
|
4513
|
+
* @param {number} version
|
|
4514
|
+
* @returns {number[]} Ascending centres. Empty for version 1.
|
|
4515
|
+
*/
|
|
4516
|
+
function alignmentCoordinates(version) {
|
|
4517
|
+
if (version < 2) return [];
|
|
4518
|
+
|
|
4519
|
+
const size = versionSize(version);
|
|
4520
|
+
const count = Math.floor(version / 7) + 2;
|
|
4521
|
+
const last = size - 7;
|
|
4522
|
+
|
|
4523
|
+
// Spacing is rounded up to an even number of modules so every centre lands on
|
|
4524
|
+
// the same parity as the timing pattern, which is what keeps the patterns
|
|
4525
|
+
// aligned with the module grid rather than straddling it.
|
|
4526
|
+
const step = Math.ceil((size - 13) / (2 * count - 2)) * 2;
|
|
4527
|
+
|
|
4528
|
+
const coords = [6];
|
|
4529
|
+
// Walk backwards from the final centre so the slack lands in the first gap.
|
|
4530
|
+
for (let i = count - 1; i >= 1; i--) coords.push(last - (count - 1 - i) * step);
|
|
4531
|
+
coords.sort((a, b) => a - b);
|
|
4532
|
+
return coords;
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4535
|
+
/**
|
|
4536
|
+
* Centres of the alignment patterns actually drawn, as [x, y] pairs.
|
|
4537
|
+
*
|
|
4538
|
+
* The three combinations that would sit on top of a finder pattern are omitted.
|
|
4539
|
+
*
|
|
4540
|
+
* @param {number} version
|
|
4541
|
+
* @returns {Array<[number, number]>}
|
|
4542
|
+
*/
|
|
4543
|
+
function alignmentCentres(version) {
|
|
4544
|
+
const coords = alignmentCoordinates(version);
|
|
4545
|
+
if (coords.length === 0) return [];
|
|
4546
|
+
|
|
4547
|
+
const size = versionSize(version);
|
|
4548
|
+
const lo = 6;
|
|
4549
|
+
const hi = size - 7;
|
|
4550
|
+
const out = [];
|
|
4551
|
+
for (let i = 0; i < coords.length; i++) {
|
|
4552
|
+
for (let j = 0; j < coords.length; j++) {
|
|
4553
|
+
const x = coords[j];
|
|
4554
|
+
const y = coords[i];
|
|
4555
|
+
// Skip the three finder corners.
|
|
4556
|
+
if (x === lo && y === lo) continue;
|
|
4557
|
+
if (x === lo && y === hi) continue;
|
|
4558
|
+
if (x === hi && y === lo) continue;
|
|
4559
|
+
out.push([x, y]);
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
return out;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
const reservedCache = new Map();
|
|
4566
|
+
|
|
4567
|
+
/**
|
|
4568
|
+
* Map of modules that carry function patterns rather than payload.
|
|
4569
|
+
*
|
|
4570
|
+
* A set bit means "reserved": finder, separator, timing, alignment, format
|
|
4571
|
+
* information, the dark module, and the version information blocks. This is the
|
|
4572
|
+
* single source of truth used by the encoder to skip modules while laying out
|
|
4573
|
+
* the bitstream, by the decoder to read them back in the same order, and by
|
|
4574
|
+
* {@link geometricTotalCodewords} to count what is left.
|
|
4575
|
+
*
|
|
4576
|
+
* Deriving capacity this way rather than by hand arithmetic is what makes the
|
|
4577
|
+
* awkward cases free: an alignment pattern that overlaps the timing pattern is
|
|
4578
|
+
* counted once because it is the same set of modules, not because anyone
|
|
4579
|
+
* remembered to subtract five.
|
|
4580
|
+
*
|
|
4581
|
+
* @param {number} version
|
|
4582
|
+
* @returns {BitMatrix} Shared, cached — treat as immutable.
|
|
4583
|
+
*/
|
|
4584
|
+
function reservedModules(version) {
|
|
4585
|
+
const cached = reservedCache.get(version);
|
|
4586
|
+
if (cached) return cached;
|
|
4587
|
+
|
|
4588
|
+
const size = versionSize(version);
|
|
4589
|
+
const m = new BitMatrix(size, size);
|
|
4590
|
+
|
|
4591
|
+
// Finder patterns with their separators: an 8x8 reserved block at each of
|
|
4592
|
+
// three corners (7x7 pattern plus a one-module light border on the inner
|
|
4593
|
+
// sides, which the corner blocks absorb).
|
|
4594
|
+
m.setRegion(0, 0, 8, 8);
|
|
4595
|
+
m.setRegion(size - 8, 0, 8, 8);
|
|
4596
|
+
m.setRegion(0, size - 8, 8, 8);
|
|
4597
|
+
|
|
4598
|
+
// Timing patterns, spanning the gap between the separators.
|
|
4599
|
+
for (let i = 8; i < size - 8; i++) {
|
|
4600
|
+
m.set(i, 6);
|
|
4601
|
+
m.set(6, i);
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
// Alignment patterns, 5x5 each.
|
|
4605
|
+
const centres = alignmentCentres(version);
|
|
4606
|
+
for (let i = 0; i < centres.length; i++) {
|
|
4607
|
+
m.setRegion(centres[i][0] - 2, centres[i][1] - 2, 5, 5);
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
// Format information: two copies plus the dark module. The copies partly
|
|
4611
|
+
// fall inside the 8x8 finder blocks already reserved; setting them again is
|
|
4612
|
+
// harmless and keeps the intent explicit.
|
|
4613
|
+
const [copyA, copyB] = formatInfoPositions(size);
|
|
4614
|
+
for (let i = 0; i < 15; i++) {
|
|
4615
|
+
m.set(copyA[i][0], copyA[i][1]);
|
|
4616
|
+
m.set(copyB[i][0], copyB[i][1]);
|
|
4617
|
+
}
|
|
4618
|
+
m.set(8, size - 8); // dark module
|
|
4619
|
+
|
|
4620
|
+
// Version information, two 6x3 blocks.
|
|
4621
|
+
if (version >= VERSION_INFO_MIN) {
|
|
4622
|
+
m.setRegion(size - 11, 0, 3, 6);
|
|
4623
|
+
m.setRegion(0, size - 11, 6, 3);
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
reservedCache.set(version, m);
|
|
4627
|
+
return m;
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
/**
|
|
4631
|
+
* Module positions of the two format information copies.
|
|
4632
|
+
*
|
|
4633
|
+
* Index `i` in each array is bit `i` of the 15-bit format value, bit 0 being
|
|
4634
|
+
* the least significant.
|
|
4635
|
+
*
|
|
4636
|
+
* CAVEAT WORTH READING: the *direction* of this numbering is the one thing in
|
|
4637
|
+
* this file that a round-trip test cannot falsify. Encoder and decoder share
|
|
4638
|
+
* these tables, so a mirrored layout would pass every test in the suite and
|
|
4639
|
+
* fail only against a real scanner. The layout below is the standard one; both
|
|
4640
|
+
* sides deliberately consume this single definition so there is no second place
|
|
4641
|
+
* for the convention to drift.
|
|
4642
|
+
*
|
|
4643
|
+
* @param {number} size Modules per side.
|
|
4644
|
+
* @returns {[Array<[number, number]>, Array<[number, number]>]} [copyA, copyB]
|
|
4645
|
+
*/
|
|
4646
|
+
function formatInfoPositions(size) {
|
|
4647
|
+
/** @type {Array<[number, number]>} */
|
|
4648
|
+
const a = [];
|
|
4649
|
+
/** @type {Array<[number, number]>} */
|
|
4650
|
+
const b = [];
|
|
4651
|
+
|
|
4652
|
+
for (let i = 0; i < 15; i++) {
|
|
4653
|
+
// Copy A wraps the top-left finder: down column 8, then left along row 8,
|
|
4654
|
+
// stepping over the two timing modules.
|
|
4655
|
+
if (i < 6) a.push([8, i]);
|
|
4656
|
+
else if (i === 6) a.push([8, 7]);
|
|
4657
|
+
else if (i === 7) a.push([8, 8]);
|
|
4658
|
+
else if (i === 8) a.push([7, 8]);
|
|
4659
|
+
else a.push([14 - i, 8]);
|
|
4660
|
+
|
|
4661
|
+
// Copy B is split: the low bits run right-to-left along row 8 beside the
|
|
4662
|
+
// top-right finder, the high bits run bottom-up beside the bottom-left one.
|
|
4663
|
+
if (i < 8) b.push([size - 1 - i, 8]);
|
|
4664
|
+
else b.push([8, size - 15 + i]);
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
return [a, b];
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
/**
|
|
4671
|
+
* Modules available to data and error correction, counted off the grid.
|
|
4672
|
+
*
|
|
4673
|
+
* @param {number} version
|
|
4674
|
+
* @returns {number}
|
|
4675
|
+
*/
|
|
4676
|
+
function freeModuleCount(version) {
|
|
4677
|
+
const reserved = reservedModules(version);
|
|
4678
|
+
const size = versionSize(version);
|
|
4679
|
+
let free = 0;
|
|
4680
|
+
for (let y = 0; y < size; y++) {
|
|
4681
|
+
for (let x = 0; x < size; x++) {
|
|
4682
|
+
if (!reserved.get(x, y)) free++;
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
return free;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
/**
|
|
4689
|
+
* Total codewords (data + error correction) a version holds.
|
|
4690
|
+
*
|
|
4691
|
+
* Geometric, not tabulated — this is the reference the ECC table is checked
|
|
4692
|
+
* against.
|
|
4693
|
+
*
|
|
4694
|
+
* @param {number} version
|
|
4695
|
+
* @returns {number}
|
|
4696
|
+
*/
|
|
4697
|
+
function geometricTotalCodewords(version) {
|
|
4698
|
+
return Math.floor(freeModuleCount(version) / 8);
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
/**
|
|
4702
|
+
* Bits left over after the last whole codeword, written as zeroes.
|
|
4703
|
+
*
|
|
4704
|
+
* @param {number} version
|
|
4705
|
+
* @returns {number} 0, 3, 4 or 7.
|
|
4706
|
+
*/
|
|
4707
|
+
function remainderBits(version) {
|
|
4708
|
+
return freeModuleCount(version) % 8;
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
const orderCache = new Map();
|
|
4712
|
+
|
|
4713
|
+
/**
|
|
4714
|
+
* Module positions in bitstream order, as interleaved x, y pairs.
|
|
4715
|
+
*
|
|
4716
|
+
* The layout walks two-module-wide columns from the bottom-right corner
|
|
4717
|
+
* leftward, alternating upward and downward, right module of the pair before
|
|
4718
|
+
* the left, skipping the vertical timing column and every reserved module.
|
|
4719
|
+
*
|
|
4720
|
+
* Encoder and decoder both consume this one function. That is not tidiness: a
|
|
4721
|
+
* placement order that disagrees between the two would still round-trip
|
|
4722
|
+
* perfectly within this library while producing symbols no scanner can read.
|
|
4723
|
+
* There is only one order because there is only one implementation of it.
|
|
4724
|
+
*
|
|
4725
|
+
* @param {number} version
|
|
4726
|
+
* @returns {Int32Array} Shared, cached — treat as immutable. Length is
|
|
4727
|
+
* `2 * freeModuleCount(version)`.
|
|
4728
|
+
*/
|
|
4729
|
+
function dataModuleOrder(version) {
|
|
4730
|
+
const cached = orderCache.get(version);
|
|
4731
|
+
if (cached) return cached;
|
|
4732
|
+
|
|
4733
|
+
const size = versionSize(version);
|
|
4734
|
+
const reserved = reservedModules(version);
|
|
4735
|
+
const out = new Int32Array(freeModuleCount(version) * 2);
|
|
4736
|
+
let n = 0;
|
|
4737
|
+
|
|
4738
|
+
let upward = true;
|
|
4739
|
+
for (let col = size - 1; col > 0; col -= 2) {
|
|
4740
|
+
// Column 6 is the vertical timing pattern. Stepping over it shifts the
|
|
4741
|
+
// whole remaining schedule left by one, so the loop variable itself has to
|
|
4742
|
+
// move — adjusting only the current pair would visit column 4 twice and
|
|
4743
|
+
// column 0 never, which is self-consistent between encoder and decoder and
|
|
4744
|
+
// therefore invisible to a round-trip test.
|
|
4745
|
+
if (col === 6) col--;
|
|
4746
|
+
for (let i = 0; i < size; i++) {
|
|
4747
|
+
const y = upward ? size - 1 - i : i;
|
|
3721
4748
|
for (let c = 0; c < 2; c++) {
|
|
3722
4749
|
const x = col - c;
|
|
3723
4750
|
if (reserved.get(x, y)) continue;
|
|
@@ -5565,934 +6592,1741 @@ __exports.ChecksumError = ChecksumError;
|
|
|
5565
6592
|
__exports.decodeQR = decodeQR;
|
|
5566
6593
|
};
|
|
5567
6594
|
|
|
5568
|
-
__modules["
|
|
6595
|
+
__modules["qr/detector.js"] = function (__require, __exports) {
|
|
5569
6596
|
/**
|
|
5570
|
-
*
|
|
5571
|
-
*
|
|
5572
|
-
* A 2D symbol photographed off-axis is not a rotated square — it is a
|
|
5573
|
-
* quadrilateral with converging edges. Correcting that needs a full projective
|
|
5574
|
-
* map, not an affine one; an affine approximation reads the near edge of a
|
|
5575
|
-
* tilted symbol correctly and drifts a module or more by the far edge.
|
|
6597
|
+
* QR Code detection — finding symbols in a binarized image.
|
|
5576
6598
|
*
|
|
5577
|
-
* The
|
|
5578
|
-
*
|
|
6599
|
+
* The whole thing hangs off one property of the finder pattern: along any line
|
|
6600
|
+
* through its centre, in any direction, the dark and light runs are in the
|
|
6601
|
+
* ratio 1:1:3:1:1. That is true horizontally, vertically and diagonally, it is
|
|
6602
|
+
* scale-independent, and no ordinary printed matter reproduces it by accident.
|
|
6603
|
+
* So the search is: scan rows for that ratio, confirm each hit by scanning the
|
|
6604
|
+
* column through it, cluster what survives, and look for three clusters
|
|
6605
|
+
* arranged in a right isoceles triangle.
|
|
5579
6606
|
*
|
|
5580
|
-
*
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
*/
|
|
5596
|
-
transform(points) {
|
|
5597
|
-
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
5598
|
-
for (let i = 0; i < points.length; i += 2) {
|
|
5599
|
-
const x = points[i];
|
|
5600
|
-
const y = points[i + 1];
|
|
5601
|
-
const w = a13 * x + a23 * y + a33;
|
|
5602
|
-
points[i] = (a11 * x + a21 * y + a31) / w;
|
|
5603
|
-
points[i + 1] = (a12 * x + a22 * y + a32) / w;
|
|
5604
|
-
}
|
|
5605
|
-
return points;
|
|
5606
|
-
}
|
|
5607
|
-
|
|
5608
|
-
/**
|
|
5609
|
-
* Transform a single point.
|
|
5610
|
-
*
|
|
5611
|
-
* @param {number} x @param {number} y
|
|
5612
|
-
* @returns {{x: number, y: number}}
|
|
5613
|
-
*/
|
|
5614
|
-
transformPoint(x, y) {
|
|
5615
|
-
const w = this.a13 * x + this.a23 * y + this.a33;
|
|
5616
|
-
return {
|
|
5617
|
-
x: (this.a11 * x + this.a21 * y + this.a31) / w,
|
|
5618
|
-
y: (this.a12 * x + this.a22 * y + this.a32) / w,
|
|
5619
|
-
};
|
|
5620
|
-
}
|
|
5621
|
-
|
|
5622
|
-
/**
|
|
5623
|
-
* Map the unit square — (0,0), (1,0), (1,1), (0,1) — onto an arbitrary quad.
|
|
5624
|
-
*
|
|
5625
|
-
* Corners are given in that same order, i.e. going around the quad, not
|
|
5626
|
-
* as opposite pairs.
|
|
5627
|
-
*
|
|
5628
|
-
* @returns {PerspectiveTransform}
|
|
5629
|
-
*/
|
|
5630
|
-
/* eslint-disable-next-line max-params */
|
|
5631
|
-
static squareToQuad(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
5632
|
-
const dx3 = x0 - x1 + x2 - x3;
|
|
5633
|
-
const dy3 = y0 - y1 + y2 - y3;
|
|
5634
|
-
|
|
5635
|
-
if (dx3 === 0 && dy3 === 0) {
|
|
5636
|
-
// The quad is a parallelogram, so the map is affine and the projective
|
|
5637
|
-
// terms vanish. Worth special-casing: it is the common case for flat
|
|
5638
|
-
// scans, and the general solution divides by zero here.
|
|
5639
|
-
return new PerspectiveTransform(
|
|
5640
|
-
x1 - x0, x2 - x1, x0,
|
|
5641
|
-
y1 - y0, y2 - y1, y0,
|
|
5642
|
-
0, 0, 1
|
|
5643
|
-
);
|
|
5644
|
-
}
|
|
5645
|
-
|
|
5646
|
-
const dx1 = x1 - x2;
|
|
5647
|
-
const dx2 = x3 - x2;
|
|
5648
|
-
const dy1 = y1 - y2;
|
|
5649
|
-
const dy2 = y3 - y2;
|
|
5650
|
-
const denominator = dx1 * dy2 - dx2 * dy1;
|
|
5651
|
-
const a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
|
|
5652
|
-
const a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
|
|
5653
|
-
|
|
5654
|
-
return new PerspectiveTransform(
|
|
5655
|
-
x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
|
|
5656
|
-
y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
|
|
5657
|
-
a13, a23, 1
|
|
5658
|
-
);
|
|
5659
|
-
}
|
|
5660
|
-
|
|
5661
|
-
/**
|
|
5662
|
-
* Map an arbitrary quad onto the unit square — the inverse of
|
|
5663
|
-
* {@link squareToQuad}, via the adjugate.
|
|
5664
|
-
*
|
|
5665
|
-
* @returns {PerspectiveTransform}
|
|
5666
|
-
*/
|
|
5667
|
-
/* eslint-disable-next-line max-params */
|
|
5668
|
-
static quadToSquare(x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
5669
|
-
return PerspectiveTransform.squareToQuad(x0, y0, x1, y1, x2, y2, x3, y3).inverse();
|
|
5670
|
-
}
|
|
5671
|
-
|
|
5672
|
-
/**
|
|
5673
|
-
* Map one quad onto another, corner for corner.
|
|
5674
|
-
*
|
|
5675
|
-
* This is what turns four detected finder corners into a sampling grid:
|
|
5676
|
-
* compose "detected quad -> unit square" with "unit square -> ideal grid".
|
|
5677
|
-
*
|
|
5678
|
-
* @returns {PerspectiveTransform}
|
|
5679
|
-
*/
|
|
5680
|
-
/* eslint-disable-next-line max-params */
|
|
5681
|
-
static quadToQuad(
|
|
5682
|
-
sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3,
|
|
5683
|
-
dx0, dy0, dx1, dy1, dx2, dy2, dx3, dy3
|
|
5684
|
-
) {
|
|
5685
|
-
const toSquare = PerspectiveTransform.quadToSquare(sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3);
|
|
5686
|
-
const toQuad = PerspectiveTransform.squareToQuad(dx0, dy0, dx1, dy1, dx2, dy2, dx3, dy3);
|
|
5687
|
-
return toSquare.times(toQuad);
|
|
5688
|
-
}
|
|
5689
|
-
|
|
5690
|
-
/**
|
|
5691
|
-
* Adjugate — the inverse up to a scale factor, which is irrelevant in
|
|
5692
|
-
* homogeneous coordinates because the division by w cancels it.
|
|
5693
|
-
*
|
|
5694
|
-
* @returns {PerspectiveTransform}
|
|
5695
|
-
*/
|
|
5696
|
-
inverse() {
|
|
5697
|
-
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
5698
|
-
return new PerspectiveTransform(
|
|
5699
|
-
a22 * a33 - a23 * a32,
|
|
5700
|
-
a23 * a31 - a21 * a33,
|
|
5701
|
-
a21 * a32 - a22 * a31,
|
|
5702
|
-
a13 * a32 - a12 * a33,
|
|
5703
|
-
a11 * a33 - a13 * a31,
|
|
5704
|
-
a12 * a31 - a11 * a32,
|
|
5705
|
-
a12 * a23 - a13 * a22,
|
|
5706
|
-
a13 * a21 - a11 * a23,
|
|
5707
|
-
a11 * a22 - a12 * a21
|
|
5708
|
-
);
|
|
5709
|
-
}
|
|
5710
|
-
|
|
5711
|
-
/**
|
|
5712
|
-
* Matrix product: apply `this` first, then `other`.
|
|
5713
|
-
*
|
|
5714
|
-
* @param {PerspectiveTransform} other
|
|
5715
|
-
* @returns {PerspectiveTransform}
|
|
5716
|
-
*/
|
|
5717
|
-
times(other) {
|
|
5718
|
-
const { a11, a21, a31, a12, a22, a32, a13, a23, a33 } = this;
|
|
5719
|
-
const o = other;
|
|
5720
|
-
return new PerspectiveTransform(
|
|
5721
|
-
o.a11 * a11 + o.a21 * a12 + o.a31 * a13,
|
|
5722
|
-
o.a11 * a21 + o.a21 * a22 + o.a31 * a23,
|
|
5723
|
-
o.a11 * a31 + o.a21 * a32 + o.a31 * a33,
|
|
5724
|
-
o.a12 * a11 + o.a22 * a12 + o.a32 * a13,
|
|
5725
|
-
o.a12 * a21 + o.a22 * a22 + o.a32 * a23,
|
|
5726
|
-
o.a12 * a31 + o.a22 * a32 + o.a32 * a33,
|
|
5727
|
-
o.a13 * a11 + o.a23 * a12 + o.a33 * a13,
|
|
5728
|
-
o.a13 * a21 + o.a23 * a22 + o.a33 * a23,
|
|
5729
|
-
o.a13 * a31 + o.a23 * a32 + o.a33 * a33
|
|
5730
|
-
);
|
|
5731
|
-
}
|
|
5732
|
-
}
|
|
6607
|
+
* Three finders give three corners. The fourth is the problem: extrapolating
|
|
6608
|
+
* `topRight + bottomLeft - topLeft` assumes the symbol is a parallelogram,
|
|
6609
|
+
* which is only true if it was photographed square-on. For version 2 and up the
|
|
6610
|
+
* bottom-right alignment pattern pins that corner down properly, which is what
|
|
6611
|
+
* makes a tilted symbol readable. When the alignment pattern cannot be found,
|
|
6612
|
+
* detection degrades to the parallelogram estimate rather than failing — a
|
|
6613
|
+
* slightly wrong corner still decodes on a flat image, and Reed-Solomon absorbs
|
|
6614
|
+
* the rest.
|
|
6615
|
+
*
|
|
6616
|
+
* @module qr/detector
|
|
6617
|
+
*/
|
|
6618
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
6619
|
+
const { PerspectiveTransform } = __require("image/perspective.js");
|
|
6620
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
6621
|
+
const { decodeQR } = __require("qr/decoder.js");
|
|
5733
6622
|
|
|
5734
|
-
|
|
5735
|
-
|
|
6623
|
+
/** Finder pattern run ratios, centre run first in the array's own order. */
|
|
6624
|
+
const FINDER_RATIOS = [1, 1, 3, 1, 1];
|
|
6625
|
+
|
|
6626
|
+
/** Alignment pattern run ratios. */
|
|
6627
|
+
const ALIGNMENT_RATIOS = [1, 1, 1, 1, 1];
|
|
6628
|
+
|
|
6629
|
+
/** Smallest and largest legal symbol dimensions, in modules. */
|
|
6630
|
+
const MIN_DIMENSION = 21;
|
|
6631
|
+
const MAX_DIMENSION = 177;
|
|
5736
6632
|
|
|
5737
|
-
__modules["image/grid-sampler.js"] = function (__require, __exports) {
|
|
5738
6633
|
/**
|
|
5739
|
-
*
|
|
6634
|
+
* Do five alternating runs match the expected ratios?
|
|
5740
6635
|
*
|
|
5741
|
-
*
|
|
5742
|
-
*
|
|
5743
|
-
*
|
|
5744
|
-
* including them turns a marginal symbol into an unreadable one.
|
|
6636
|
+
* Tolerance is half a module, scaled by the ratio, which is the widest band
|
|
6637
|
+
* that still rejects ordinary text and rules while accepting the blur and
|
|
6638
|
+
* rounding of a real scan.
|
|
5745
6639
|
*
|
|
5746
|
-
* @
|
|
6640
|
+
* @param {number[]} counts Five run lengths, dark first.
|
|
6641
|
+
* @param {number[]} ratios
|
|
6642
|
+
* @returns {number} The implied module size, or 0 if the ratios do not match.
|
|
5747
6643
|
*/
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
6644
|
+
function matchRatios(counts, ratios) {
|
|
6645
|
+
let total = 0;
|
|
6646
|
+
let units = 0;
|
|
6647
|
+
for (let i = 0; i < 5; i++) {
|
|
6648
|
+
if (counts[i] === 0) return 0;
|
|
6649
|
+
total += counts[i];
|
|
6650
|
+
units += ratios[i];
|
|
6651
|
+
}
|
|
6652
|
+
if (total < units) return 0;
|
|
6653
|
+
|
|
6654
|
+
const moduleSize = total / units;
|
|
6655
|
+
const tolerance = moduleSize / 2;
|
|
6656
|
+
for (let i = 0; i < 5; i++) {
|
|
6657
|
+
if (Math.abs(counts[i] - moduleSize * ratios[i]) > tolerance * ratios[i]) return 0;
|
|
6658
|
+
}
|
|
6659
|
+
return moduleSize;
|
|
6660
|
+
}
|
|
5751
6661
|
|
|
5752
6662
|
/**
|
|
5753
|
-
*
|
|
6663
|
+
* Scan one row for the finder run ratio.
|
|
5754
6664
|
*
|
|
5755
|
-
* @param {BitMatrix} image
|
|
5756
|
-
* @param {number}
|
|
5757
|
-
* @param {number}
|
|
5758
|
-
* @param {
|
|
5759
|
-
* @returns {BitMatrix}
|
|
5760
|
-
* @throws {NotFoundError} If the grid falls outside the image.
|
|
6665
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6666
|
+
* @param {number} y
|
|
6667
|
+
* @param {number[]} ratios
|
|
6668
|
+
* @param {(centreX: number, moduleSize: number) => void} onHit
|
|
5761
6669
|
*/
|
|
5762
|
-
function
|
|
5763
|
-
const
|
|
5764
|
-
const
|
|
6670
|
+
function scanRow(image, y, ratios, onHit) {
|
|
6671
|
+
const width = image.width;
|
|
6672
|
+
const counts = [0, 0, 0, 0, 0];
|
|
6673
|
+
let state = 0;
|
|
5765
6674
|
|
|
5766
|
-
for (let
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
6675
|
+
for (let x = 0; x < width; x++) {
|
|
6676
|
+
const dark = image.get(x, y);
|
|
6677
|
+
|
|
6678
|
+
// Even states are dark runs, odd states light.
|
|
6679
|
+
if (dark === ((state & 1) === 0)) {
|
|
6680
|
+
counts[state]++;
|
|
6681
|
+
continue;
|
|
5772
6682
|
}
|
|
5773
|
-
transform.transform(points);
|
|
5774
6683
|
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
6684
|
+
// A leading light margin is not part of any pattern.
|
|
6685
|
+
if (state === 0 && counts[0] === 0) continue;
|
|
6686
|
+
|
|
6687
|
+
if (state < 4) {
|
|
6688
|
+
state++;
|
|
6689
|
+
counts[state] = 1;
|
|
6690
|
+
continue;
|
|
6691
|
+
}
|
|
6692
|
+
|
|
6693
|
+
// Five runs complete, and the sixth has begun.
|
|
6694
|
+
const moduleSize = matchRatios(counts, ratios);
|
|
6695
|
+
if (moduleSize > 0) {
|
|
6696
|
+
onHit(x - counts[4] - counts[3] - counts[2] / 2, moduleSize);
|
|
5784
6697
|
}
|
|
6698
|
+
|
|
6699
|
+
// Slide the window on by two runs: the trailing dark run of a rejected
|
|
6700
|
+
// candidate is often the leading dark run of the real one.
|
|
6701
|
+
counts[0] = counts[2];
|
|
6702
|
+
counts[1] = counts[3];
|
|
6703
|
+
counts[2] = counts[4];
|
|
6704
|
+
counts[3] = 1;
|
|
6705
|
+
counts[4] = 0;
|
|
6706
|
+
state = 3;
|
|
5785
6707
|
}
|
|
5786
6708
|
|
|
5787
|
-
|
|
6709
|
+
if (state === 4) {
|
|
6710
|
+
const moduleSize = matchRatios(counts, ratios);
|
|
6711
|
+
if (moduleSize > 0) {
|
|
6712
|
+
onHit(width - counts[4] - counts[3] - counts[2] / 2, moduleSize);
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
5788
6715
|
}
|
|
5789
6716
|
|
|
5790
6717
|
/**
|
|
5791
|
-
*
|
|
6718
|
+
* Walk a line through a candidate centre and confirm the ratio holds there too.
|
|
5792
6719
|
*
|
|
5793
|
-
*
|
|
5794
|
-
*
|
|
5795
|
-
*
|
|
6720
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6721
|
+
* @param {number} x @param {number} y
|
|
6722
|
+
* @param {number} dx @param {number} dy Unit step defining the line.
|
|
6723
|
+
* @param {number[]} ratios
|
|
6724
|
+
* @param {number} maxRun Guard against running the length of a dark image.
|
|
6725
|
+
* @returns {number} Refined centre offset along the line, or NaN.
|
|
6726
|
+
*/
|
|
6727
|
+
function crossCheck(image, x, y, dx, dy, ratios, maxRun) {
|
|
6728
|
+
const width = image.width;
|
|
6729
|
+
const height = image.height;
|
|
6730
|
+
|
|
6731
|
+
/** @returns {boolean | null} Null when the sample falls outside the image. */
|
|
6732
|
+
const at = (i) => {
|
|
6733
|
+
const px = x + dx * i;
|
|
6734
|
+
const py = y + dy * i;
|
|
6735
|
+
if (px < 0 || py < 0 || px >= width || py >= height) return null;
|
|
6736
|
+
return image.get(px, py);
|
|
6737
|
+
};
|
|
6738
|
+
|
|
6739
|
+
if (at(0) !== true) return NaN;
|
|
6740
|
+
|
|
6741
|
+
const counts = [0, 0, 0, 0, 0];
|
|
6742
|
+
let i = 0;
|
|
6743
|
+
|
|
6744
|
+
// Forward from the centre: rest of the centre run, then light, then dark.
|
|
6745
|
+
while (at(i) === true && counts[2] < maxRun) { counts[2]++; i++; }
|
|
6746
|
+
if (at(i) === null) return NaN;
|
|
6747
|
+
const centreForward = counts[2];
|
|
6748
|
+
|
|
6749
|
+
while (at(i) === false && counts[3] < maxRun) { counts[3]++; i++; }
|
|
6750
|
+
if (at(i) === null || counts[3] === 0) return NaN;
|
|
6751
|
+
|
|
6752
|
+
while (at(i) === true && counts[4] < maxRun) { counts[4]++; i++; }
|
|
6753
|
+
if (counts[4] === 0) return NaN;
|
|
6754
|
+
|
|
6755
|
+
// Backward from the centre.
|
|
6756
|
+
i = -1;
|
|
6757
|
+
while (at(i) === true && counts[2] < maxRun * 2) { counts[2]++; i--; }
|
|
6758
|
+
if (at(i) === null) return NaN;
|
|
6759
|
+
const centreBackward = counts[2] - centreForward;
|
|
6760
|
+
|
|
6761
|
+
while (at(i) === false && counts[1] < maxRun) { counts[1]++; i--; }
|
|
6762
|
+
if (at(i) === null || counts[1] === 0) return NaN;
|
|
6763
|
+
|
|
6764
|
+
while (at(i) === true && counts[0] < maxRun) { counts[0]++; i--; }
|
|
6765
|
+
if (counts[0] === 0) return NaN;
|
|
6766
|
+
|
|
6767
|
+
if (matchRatios(counts, ratios) === 0) return NaN;
|
|
6768
|
+
|
|
6769
|
+
// The centre run spans offsets [-centreBackward, centreForward - 1].
|
|
6770
|
+
return (centreForward - 1 - centreBackward) / 2;
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
/**
|
|
6774
|
+
* @typedef {object} Candidate
|
|
6775
|
+
* @property {number} x
|
|
6776
|
+
* @property {number} y
|
|
6777
|
+
* @property {number} moduleSize
|
|
6778
|
+
* @property {number} hits
|
|
6779
|
+
*/
|
|
6780
|
+
|
|
6781
|
+
/**
|
|
6782
|
+
* Locate pattern centres of a given run ratio.
|
|
5796
6783
|
*
|
|
5797
|
-
* @param {BitMatrix} image
|
|
5798
|
-
* @param {number}
|
|
5799
|
-
* @param {number}
|
|
5800
|
-
* @param {
|
|
5801
|
-
* @returns {
|
|
6784
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6785
|
+
* @param {number[]} ratios
|
|
6786
|
+
* @param {number} unitsWide Modules the pattern spans (7 or 5).
|
|
6787
|
+
* @param {{x0: number, y0: number, x1: number, y1: number}} [region]
|
|
6788
|
+
* @returns {Candidate[]}
|
|
5802
6789
|
*/
|
|
5803
|
-
function
|
|
5804
|
-
|
|
6790
|
+
function findPatterns(image, ratios, unitsWide, region) {
|
|
6791
|
+
/** @type {Candidate[]} */
|
|
6792
|
+
const found = [];
|
|
5805
6793
|
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
// meaningless at a different scale.
|
|
5809
|
-
const p0 = transform.transformPoint(0.5, 0.5);
|
|
5810
|
-
const p1 = transform.transformPoint(1.5, 0.5);
|
|
5811
|
-
const p2 = transform.transformPoint(0.5, 1.5);
|
|
5812
|
-
const stepX = Math.hypot(p1.x - p0.x, p1.y - p0.y);
|
|
5813
|
-
const stepY = Math.hypot(p2.x - p0.x, p2.y - p0.y);
|
|
5814
|
-
const rx = Math.max(1, Math.round(stepX / 4));
|
|
5815
|
-
const ry = Math.max(1, Math.round(stepY / 4));
|
|
6794
|
+
const y0 = region ? Math.max(0, region.y0) : 0;
|
|
6795
|
+
const y1 = region ? Math.min(image.height, region.y1) : image.height;
|
|
5816
6796
|
|
|
5817
|
-
for (let y =
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
const cx = c.x | 0;
|
|
5821
|
-
const cy = c.y | 0;
|
|
5822
|
-
if (cx < 0 || cy < 0 || cx >= image.width || cy >= image.height) {
|
|
5823
|
-
throw new NotFoundError(
|
|
5824
|
-
`Sampling grid escapes the image at module (${x}, ${y})`
|
|
5825
|
-
);
|
|
5826
|
-
}
|
|
6797
|
+
for (let y = y0; y < y1; y++) {
|
|
6798
|
+
scanRow(image, y, ratios, (centreX, moduleSize) => {
|
|
6799
|
+
if (region && (centreX < region.x0 || centreX > region.x1)) return;
|
|
5827
6800
|
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
6801
|
+
const px = Math.floor(centreX);
|
|
6802
|
+
const maxRun = Math.ceil(moduleSize * unitsWide);
|
|
6803
|
+
const offsetY = crossCheck(image, px, y, 0, 1, ratios, maxRun);
|
|
6804
|
+
if (Number.isNaN(offsetY)) return;
|
|
6805
|
+
|
|
6806
|
+
const cy = y + offsetY;
|
|
6807
|
+
// Re-check horizontally at the refined row, which both confirms the hit
|
|
6808
|
+
// and gives a better x than the original scan line did.
|
|
6809
|
+
const offsetX = crossCheck(image, px, Math.round(cy), 1, 0, ratios, maxRun);
|
|
6810
|
+
if (Number.isNaN(offsetX)) return;
|
|
6811
|
+
|
|
6812
|
+
const cx = px + offsetX;
|
|
6813
|
+
|
|
6814
|
+
// Merge with an existing centre when they describe the same pattern.
|
|
6815
|
+
for (let i = 0; i < found.length; i++) {
|
|
6816
|
+
const c = found[i];
|
|
6817
|
+
if (
|
|
6818
|
+
Math.abs(c.x - cx) <= c.moduleSize &&
|
|
6819
|
+
Math.abs(c.y - cy) <= c.moduleSize &&
|
|
6820
|
+
Math.abs(c.moduleSize - moduleSize) <= Math.max(1, c.moduleSize / 2)
|
|
6821
|
+
) {
|
|
6822
|
+
const n = c.hits + 1;
|
|
6823
|
+
c.x = (c.x * c.hits + cx) / n;
|
|
6824
|
+
c.y = (c.y * c.hits + cy) / n;
|
|
6825
|
+
c.moduleSize = (c.moduleSize * c.hits + moduleSize) / n;
|
|
6826
|
+
c.hits = n;
|
|
6827
|
+
return;
|
|
5837
6828
|
}
|
|
5838
6829
|
}
|
|
5839
|
-
|
|
5840
|
-
|
|
6830
|
+
|
|
6831
|
+
found.push({ x: cx, y: cy, moduleSize, hits: 1 });
|
|
6832
|
+
});
|
|
5841
6833
|
}
|
|
5842
6834
|
|
|
5843
|
-
return
|
|
6835
|
+
return found;
|
|
5844
6836
|
}
|
|
5845
6837
|
|
|
5846
6838
|
/**
|
|
5847
|
-
*
|
|
6839
|
+
* @param {{x: number, y: number}} a @param {{x: number, y: number}} b
|
|
6840
|
+
* @returns {number}
|
|
6841
|
+
*/
|
|
6842
|
+
function distance(a, b) {
|
|
6843
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6846
|
+
/**
|
|
6847
|
+
* Order three finder centres as top-left, top-right, bottom-left.
|
|
5848
6848
|
*
|
|
5849
|
-
*
|
|
6849
|
+
* The corner is the centre opposite the longest side. Which of the remaining
|
|
6850
|
+
* two is "top right" follows from the sign of the cross product: in image
|
|
6851
|
+
* coordinates, with y increasing downward, a symbol the right way round has
|
|
6852
|
+
* (topRight - topLeft) x (bottomLeft - topLeft) positive.
|
|
5850
6853
|
*
|
|
5851
|
-
* @param {
|
|
5852
|
-
* @
|
|
5853
|
-
* @param {Array<{x: number, y: number}>} corners
|
|
5854
|
-
* @param {boolean} [voting]
|
|
5855
|
-
* @returns {BitMatrix}
|
|
6854
|
+
* @param {Candidate[]} three
|
|
6855
|
+
* @returns {{tl: Candidate, tr: Candidate, bl: Candidate} | null}
|
|
5856
6856
|
*/
|
|
5857
|
-
function
|
|
5858
|
-
|
|
5859
|
-
const
|
|
5860
|
-
const
|
|
6857
|
+
function orientFinders(three) {
|
|
6858
|
+
const [a, b, c] = three;
|
|
6859
|
+
const ab = distance(a, b);
|
|
6860
|
+
const bc = distance(b, c);
|
|
6861
|
+
const ca = distance(c, a);
|
|
5861
6862
|
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
tl
|
|
5865
|
-
)
|
|
6863
|
+
let tl, p, q, hypotenuse, leg1, leg2;
|
|
6864
|
+
if (ab >= bc && ab >= ca) {
|
|
6865
|
+
tl = c; p = a; q = b; hypotenuse = ab; leg1 = ca; leg2 = bc;
|
|
6866
|
+
} else if (bc >= ab && bc >= ca) {
|
|
6867
|
+
tl = a; p = b; q = c; hypotenuse = bc; leg1 = ab; leg2 = ca;
|
|
6868
|
+
} else {
|
|
6869
|
+
tl = b; p = c; q = a; hypotenuse = ca; leg1 = bc; leg2 = ab;
|
|
6870
|
+
}
|
|
5866
6871
|
|
|
5867
|
-
return
|
|
5868
|
-
|
|
5869
|
-
|
|
6872
|
+
if (leg1 === 0 || leg2 === 0) return null;
|
|
6873
|
+
|
|
6874
|
+
// The two legs must be near enough equal, and Pythagoras must hold: this is
|
|
6875
|
+
// what rejects three unrelated finder-lookalikes that happen to co-occur.
|
|
6876
|
+
const ratio = leg1 / leg2;
|
|
6877
|
+
if (ratio < 0.7 || ratio > 1.4) return null;
|
|
6878
|
+
const expected = Math.hypot(leg1, leg2);
|
|
6879
|
+
if (Math.abs(hypotenuse - expected) > expected * 0.25) return null;
|
|
6880
|
+
|
|
6881
|
+
const cross = (p.x - tl.x) * (q.y - tl.y) - (p.y - tl.y) * (q.x - tl.x);
|
|
6882
|
+
return cross >= 0 ? { tl, tr: p, bl: q } : { tl, tr: q, bl: p };
|
|
5870
6883
|
}
|
|
5871
6884
|
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
6885
|
+
/**
|
|
6886
|
+
* Snap a measured dimension to a legal symbol size.
|
|
6887
|
+
*
|
|
6888
|
+
* Every QR dimension is 17 + 4v, so `dimension % 4 === 1`. A measurement one
|
|
6889
|
+
* off is rounding; two off means the module size estimate is wrong and the
|
|
6890
|
+
* candidate is not worth pursuing.
|
|
6891
|
+
*
|
|
6892
|
+
* @param {number} raw
|
|
6893
|
+
* @returns {number} 0 if it cannot be reconciled.
|
|
6894
|
+
*/
|
|
6895
|
+
function snapDimension(raw) {
|
|
6896
|
+
let d = Math.round(raw);
|
|
6897
|
+
switch (d & 3) {
|
|
6898
|
+
case 0: d--; break;
|
|
6899
|
+
case 2: d++; break;
|
|
6900
|
+
case 3: return 0;
|
|
6901
|
+
default: break;
|
|
6902
|
+
}
|
|
6903
|
+
if (d < MIN_DIMENSION || d > MAX_DIMENSION) return 0;
|
|
6904
|
+
return d;
|
|
6905
|
+
}
|
|
6906
|
+
|
|
6907
|
+
/** The alignment pattern, as modules. 1 is dark. */
|
|
6908
|
+
const ALIGNMENT_MODULES = [
|
|
6909
|
+
[1, 1, 1, 1, 1],
|
|
6910
|
+
[1, 0, 0, 0, 1],
|
|
6911
|
+
[1, 0, 1, 0, 1],
|
|
6912
|
+
[1, 0, 0, 0, 1],
|
|
6913
|
+
[1, 1, 1, 1, 1],
|
|
6914
|
+
];
|
|
5876
6915
|
|
|
5877
|
-
__modules["qr/detector.js"] = function (__require, __exports) {
|
|
5878
6916
|
/**
|
|
5879
|
-
*
|
|
6917
|
+
* Confirm a candidate by reading the 5x5 module block it claims to be.
|
|
5880
6918
|
*
|
|
5881
|
-
* The
|
|
5882
|
-
*
|
|
5883
|
-
*
|
|
5884
|
-
*
|
|
5885
|
-
*
|
|
5886
|
-
*
|
|
5887
|
-
* arranged in a right isoceles triangle.
|
|
6919
|
+
* The run-ratio scan alone is not enough here. A finder pattern's 1:1:3:1:1 is
|
|
6920
|
+
* rare enough to stand on its own, but an alignment pattern's 1:1:1:1:1 occurs
|
|
6921
|
+
* constantly in ordinary data modules, so an unverified match near the expected
|
|
6922
|
+
* position is more likely to be payload than pattern — and a false match drags
|
|
6923
|
+
* the fourth corner off by several modules, which is worse than having no
|
|
6924
|
+
* alignment pattern at all.
|
|
5888
6925
|
*
|
|
5889
|
-
*
|
|
5890
|
-
*
|
|
5891
|
-
*
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
6926
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6927
|
+
* @param {number} cx @param {number} cy @param {number} moduleSize
|
|
6928
|
+
* @returns {boolean}
|
|
6929
|
+
*/
|
|
6930
|
+
function verifyAlignment(image, cx, cy, moduleSize) {
|
|
6931
|
+
let good = 0;
|
|
6932
|
+
for (let j = 0; j < 5; j++) {
|
|
6933
|
+
for (let i = 0; i < 5; i++) {
|
|
6934
|
+
const px = Math.round(cx + (i - 2) * moduleSize);
|
|
6935
|
+
const py = Math.round(cy + (j - 2) * moduleSize);
|
|
6936
|
+
if (px < 0 || py < 0 || px >= image.width || py >= image.height) return false;
|
|
6937
|
+
if (image.get(px, py) === (ALIGNMENT_MODULES[j][i] === 1)) good++;
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
// Allow two modules of slop for blur and sampling, but no more.
|
|
6941
|
+
return good >= 23;
|
|
6942
|
+
}
|
|
6943
|
+
|
|
6944
|
+
/**
|
|
6945
|
+
* Look for the bottom-right alignment pattern near where the geometry predicts.
|
|
5897
6946
|
*
|
|
5898
|
-
* @
|
|
6947
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6948
|
+
* @param {{x: number, y: number}} expected
|
|
6949
|
+
* @param {number} moduleSize
|
|
6950
|
+
* @returns {{x: number, y: number} | null}
|
|
5899
6951
|
*/
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
6952
|
+
function findAlignment(image, expected, moduleSize) {
|
|
6953
|
+
// Three modules of slack. The parallelogram estimate is good to well under
|
|
6954
|
+
// that on any symbol flat enough to decode, and a wider net only admits more
|
|
6955
|
+
// data modules as candidates.
|
|
6956
|
+
const radius = Math.max(3, Math.ceil(moduleSize * 3));
|
|
6957
|
+
const region = {
|
|
6958
|
+
x0: expected.x - radius,
|
|
6959
|
+
x1: expected.x + radius,
|
|
6960
|
+
y0: Math.floor(expected.y - radius),
|
|
6961
|
+
y1: Math.ceil(expected.y + radius),
|
|
6962
|
+
};
|
|
5904
6963
|
|
|
5905
|
-
|
|
5906
|
-
const FINDER_RATIOS = [1, 1, 3, 1, 1];
|
|
6964
|
+
const found = findPatterns(image, ALIGNMENT_RATIOS, 5, region);
|
|
5907
6965
|
|
|
5908
|
-
|
|
5909
|
-
|
|
6966
|
+
let best = null;
|
|
6967
|
+
let bestDistance = Infinity;
|
|
6968
|
+
for (let i = 0; i < found.length; i++) {
|
|
6969
|
+
// The alignment pattern is 5 modules across, so its implied module size
|
|
6970
|
+
// should agree with the one the finders reported.
|
|
6971
|
+
if (found[i].moduleSize > moduleSize * 1.5 || found[i].moduleSize < moduleSize / 1.5) continue;
|
|
6972
|
+
if (!verifyAlignment(image, found[i].x, found[i].y, moduleSize)) continue;
|
|
6973
|
+
const d = distance(found[i], expected);
|
|
6974
|
+
if (d < bestDistance) {
|
|
6975
|
+
bestDistance = d;
|
|
6976
|
+
best = found[i];
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
5910
6979
|
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
6980
|
+
// Last resort: the pattern is exactly where predicted but its runs were
|
|
6981
|
+
// mangled by blur. Reading the modules directly still confirms it.
|
|
6982
|
+
if (!best && verifyAlignment(image, expected.x, expected.y, moduleSize)) {
|
|
6983
|
+
best = { x: expected.x, y: expected.y };
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
return best;
|
|
6987
|
+
}
|
|
5914
6988
|
|
|
5915
6989
|
/**
|
|
5916
|
-
*
|
|
5917
|
-
*
|
|
5918
|
-
*
|
|
5919
|
-
*
|
|
5920
|
-
*
|
|
6990
|
+
* @typedef {object} Detection
|
|
6991
|
+
* @property {Array<{x: number, y: number}>} corners Outer corners of the
|
|
6992
|
+
* symbol, ordered top-left, top-right, bottom-right, bottom-left.
|
|
6993
|
+
* @property {number} dimension Modules per side.
|
|
6994
|
+
* @property {number} version
|
|
6995
|
+
* @property {number} moduleSize Estimated pixels per module.
|
|
6996
|
+
* @property {boolean} alignmentFound
|
|
6997
|
+
*/
|
|
6998
|
+
|
|
6999
|
+
/**
|
|
7000
|
+
* Find QR Code symbols in a binarized image.
|
|
5921
7001
|
*
|
|
5922
|
-
* @param {
|
|
5923
|
-
* @
|
|
5924
|
-
*
|
|
7002
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
|
|
7003
|
+
* @returns {Detection[]} Possibly empty; ordered by descending module size, so
|
|
7004
|
+
* the most prominent symbol comes first.
|
|
5925
7005
|
*/
|
|
5926
|
-
function
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
for (let i = 0; i < 5; i++) {
|
|
5930
|
-
if (counts[i] === 0) return 0;
|
|
5931
|
-
total += counts[i];
|
|
5932
|
-
units += ratios[i];
|
|
7006
|
+
function detectQR(binaryImage) {
|
|
7007
|
+
if (!binaryImage || !binaryImage.width) {
|
|
7008
|
+
throw new NotFoundError('detectQR: no image supplied');
|
|
5933
7009
|
}
|
|
5934
|
-
if (total < units) return 0;
|
|
5935
7010
|
|
|
5936
|
-
const
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
7011
|
+
const finders = findPatterns(binaryImage, FINDER_RATIOS, 7);
|
|
7012
|
+
// A single stray row hit is noise; a real finder is crossed many times.
|
|
7013
|
+
const solid = finders.filter((f) => f.hits >= 2);
|
|
7014
|
+
const pool = solid.length >= 3 ? solid : finders;
|
|
7015
|
+
if (pool.length < 3) return [];
|
|
7016
|
+
|
|
7017
|
+
// Prefer the largest patterns, and cap the combinatorics on noisy images.
|
|
7018
|
+
pool.sort((a, b) => b.moduleSize - a.moduleSize || b.hits - a.hits);
|
|
7019
|
+
const limit = Math.min(pool.length, 12);
|
|
5943
7020
|
|
|
5944
|
-
/**
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
5948
|
-
* @param {number} y
|
|
5949
|
-
* @param {number[]} ratios
|
|
5950
|
-
* @param {(centreX: number, moduleSize: number) => void} onHit
|
|
5951
|
-
*/
|
|
5952
|
-
function scanRow(image, y, ratios, onHit) {
|
|
5953
|
-
const width = image.width;
|
|
5954
|
-
const counts = [0, 0, 0, 0, 0];
|
|
5955
|
-
let state = 0;
|
|
7021
|
+
/** @type {Detection[]} */
|
|
7022
|
+
const detections = [];
|
|
7023
|
+
const used = new Set();
|
|
5956
7024
|
|
|
5957
|
-
for (let
|
|
5958
|
-
|
|
7025
|
+
for (let i = 0; i < limit; i++) {
|
|
7026
|
+
for (let j = i + 1; j < limit; j++) {
|
|
7027
|
+
for (let k = j + 1; k < limit; k++) {
|
|
7028
|
+
const three = [pool[i], pool[j], pool[k]];
|
|
5959
7029
|
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
continue;
|
|
5964
|
-
}
|
|
7030
|
+
// All three finders belong to one symbol, so they share a module size.
|
|
7031
|
+
const sizes = three.map((f) => f.moduleSize);
|
|
7032
|
+
if (Math.max(...sizes) > Math.min(...sizes) * 1.6) continue;
|
|
5965
7033
|
|
|
5966
|
-
|
|
5967
|
-
|
|
7034
|
+
const oriented = orientFinders(three);
|
|
7035
|
+
if (!oriented) continue;
|
|
5968
7036
|
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
continue;
|
|
5973
|
-
}
|
|
7037
|
+
const { tl, tr, bl } = oriented;
|
|
7038
|
+
const moduleSize = (tl.moduleSize + tr.moduleSize + bl.moduleSize) / 3;
|
|
7039
|
+
if (moduleSize <= 0) continue;
|
|
5974
7040
|
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
}
|
|
7041
|
+
// Centre-to-centre spans dimension - 7 modules.
|
|
7042
|
+
const across = (distance(tl, tr) + distance(tl, bl)) / 2;
|
|
7043
|
+
const dimension = snapDimension(across / moduleSize + 7);
|
|
7044
|
+
if (dimension === 0) continue;
|
|
5980
7045
|
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
counts[0] = counts[2];
|
|
5984
|
-
counts[1] = counts[3];
|
|
5985
|
-
counts[2] = counts[4];
|
|
5986
|
-
counts[3] = 1;
|
|
5987
|
-
counts[4] = 0;
|
|
5988
|
-
state = 3;
|
|
5989
|
-
}
|
|
7046
|
+
const version = (dimension - 17) / 4;
|
|
7047
|
+
if (version < 1 || version > 40) continue;
|
|
5990
7048
|
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
7049
|
+
const key = `${Math.round(tl.x)},${Math.round(tl.y)},${dimension}`;
|
|
7050
|
+
if (used.has(key)) continue;
|
|
7051
|
+
used.add(key);
|
|
7052
|
+
|
|
7053
|
+
detections.push(
|
|
7054
|
+
buildDetection(binaryImage, tl, tr, bl, dimension, version, moduleSize)
|
|
7055
|
+
);
|
|
7056
|
+
}
|
|
5995
7057
|
}
|
|
5996
7058
|
}
|
|
7059
|
+
|
|
7060
|
+
detections.sort((a, b) => b.moduleSize - a.moduleSize);
|
|
7061
|
+
return detections;
|
|
5997
7062
|
}
|
|
5998
7063
|
|
|
5999
7064
|
/**
|
|
6000
|
-
*
|
|
7065
|
+
* Turn three finder centres into four symbol corners.
|
|
6001
7066
|
*
|
|
6002
7067
|
* @param {import('../core/bit-matrix.js').BitMatrix} image
|
|
6003
|
-
* @param {
|
|
6004
|
-
* @param {number}
|
|
6005
|
-
* @
|
|
6006
|
-
* @param {number} maxRun Guard against running the length of a dark image.
|
|
6007
|
-
* @returns {number} Refined centre offset along the line, or NaN.
|
|
7068
|
+
* @param {Candidate} tl @param {Candidate} tr @param {Candidate} bl
|
|
7069
|
+
* @param {number} dimension @param {number} version @param {number} moduleSize
|
|
7070
|
+
* @returns {Detection}
|
|
6008
7071
|
*/
|
|
6009
|
-
function
|
|
6010
|
-
const
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
const
|
|
6015
|
-
const px = x + dx * i;
|
|
6016
|
-
const py = y + dy * i;
|
|
6017
|
-
if (px < 0 || py < 0 || px >= width || py >= height) return null;
|
|
6018
|
-
return image.get(px, py);
|
|
6019
|
-
};
|
|
6020
|
-
|
|
6021
|
-
if (at(0) !== true) return NaN;
|
|
6022
|
-
|
|
6023
|
-
const counts = [0, 0, 0, 0, 0];
|
|
6024
|
-
let i = 0;
|
|
6025
|
-
|
|
6026
|
-
// Forward from the centre: rest of the centre run, then light, then dark.
|
|
6027
|
-
while (at(i) === true && counts[2] < maxRun) { counts[2]++; i++; }
|
|
6028
|
-
if (at(i) === null) return NaN;
|
|
6029
|
-
const centreForward = counts[2];
|
|
7072
|
+
function buildDetection(image, tl, tr, bl, dimension, version, moduleSize) {
|
|
7073
|
+
const d = dimension;
|
|
7074
|
+
// Finder centres sit on module (3, 3) and friends, so at grid coordinate 3.5.
|
|
7075
|
+
const gridTl = [3.5, 3.5];
|
|
7076
|
+
const gridTr = [d - 3.5, 3.5];
|
|
7077
|
+
const gridBl = [3.5, d - 3.5];
|
|
6030
7078
|
|
|
6031
|
-
|
|
6032
|
-
|
|
7079
|
+
// Parallelogram estimate of the far corner, used both as the search seed for
|
|
7080
|
+
// the alignment pattern and as the fallback when it is not there.
|
|
7081
|
+
const guess = { x: tr.x + bl.x - tl.x, y: tr.y + bl.y - tl.y };
|
|
6033
7082
|
|
|
6034
|
-
|
|
6035
|
-
|
|
7083
|
+
/** @type {PerspectiveTransform | null} */
|
|
7084
|
+
let transform = null;
|
|
7085
|
+
let alignmentFound = false;
|
|
6036
7086
|
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
7087
|
+
if (version >= 2) {
|
|
7088
|
+
// The bottom-right alignment pattern is centred on module (d - 7, d - 7).
|
|
7089
|
+
const gridAlign = [d - 6.5, d - 6.5];
|
|
7090
|
+
// Where that module lands under the parallelogram assumption.
|
|
7091
|
+
const seed = {
|
|
7092
|
+
x: tl.x + ((gridAlign[0] - 3.5) / (d - 7)) * (tr.x - tl.x) +
|
|
7093
|
+
((gridAlign[1] - 3.5) / (d - 7)) * (bl.x - tl.x),
|
|
7094
|
+
y: tl.y + ((gridAlign[0] - 3.5) / (d - 7)) * (tr.y - tl.y) +
|
|
7095
|
+
((gridAlign[1] - 3.5) / (d - 7)) * (bl.y - tl.y),
|
|
7096
|
+
};
|
|
6042
7097
|
|
|
6043
|
-
|
|
6044
|
-
|
|
7098
|
+
const align = findAlignment(image, seed, moduleSize);
|
|
7099
|
+
if (align) {
|
|
7100
|
+
alignmentFound = true;
|
|
7101
|
+
transform = PerspectiveTransform.quadToQuad(
|
|
7102
|
+
gridTl[0], gridTl[1], gridTr[0], gridTr[1], gridAlign[0], gridAlign[1], gridBl[0], gridBl[1],
|
|
7103
|
+
tl.x, tl.y, tr.x, tr.y, align.x, align.y, bl.x, bl.y
|
|
7104
|
+
);
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
6045
7107
|
|
|
6046
|
-
|
|
6047
|
-
|
|
7108
|
+
const plain = PerspectiveTransform.quadToQuad(
|
|
7109
|
+
gridTl[0], gridTl[1], gridTr[0], gridTr[1], d - 3.5, d - 3.5, gridBl[0], gridBl[1],
|
|
7110
|
+
tl.x, tl.y, tr.x, tr.y, guess.x, guess.y, bl.x, bl.y
|
|
7111
|
+
);
|
|
6048
7112
|
|
|
6049
|
-
|
|
7113
|
+
const corners = cornersOf(transform ?? plain, d);
|
|
7114
|
+
// Keep the parallelogram corners as a second opinion whenever an alignment
|
|
7115
|
+
// pattern steered the first set. Verification makes a false match unlikely,
|
|
7116
|
+
// not impossible, and one extra sampling attempt is far cheaper than losing
|
|
7117
|
+
// a symbol to it.
|
|
7118
|
+
const altCorners = transform ? cornersOf(plain, d) : null;
|
|
6050
7119
|
|
|
6051
|
-
|
|
6052
|
-
return (centreForward - 1 - centreBackward) / 2;
|
|
7120
|
+
return { corners, altCorners, dimension, version, moduleSize, alignmentFound };
|
|
6053
7121
|
}
|
|
6054
7122
|
|
|
6055
7123
|
/**
|
|
6056
|
-
*
|
|
6057
|
-
*
|
|
6058
|
-
* @
|
|
6059
|
-
* @
|
|
6060
|
-
* @property {number} hits
|
|
7124
|
+
* The four outer corners of the symbol under a grid-to-image transform.
|
|
7125
|
+
*
|
|
7126
|
+
* @param {PerspectiveTransform} transform @param {number} d
|
|
7127
|
+
* @returns {Array<{x: number, y: number}>}
|
|
6061
7128
|
*/
|
|
7129
|
+
function cornersOf(transform, d) {
|
|
7130
|
+
return [
|
|
7131
|
+
transform.transformPoint(0, 0),
|
|
7132
|
+
transform.transformPoint(d, 0),
|
|
7133
|
+
transform.transformPoint(d, d),
|
|
7134
|
+
transform.transformPoint(0, d),
|
|
7135
|
+
];
|
|
7136
|
+
}
|
|
6062
7137
|
|
|
6063
7138
|
/**
|
|
6064
|
-
*
|
|
7139
|
+
* Find and decode every QR Code in a binarized image.
|
|
6065
7140
|
*
|
|
6066
|
-
*
|
|
6067
|
-
*
|
|
6068
|
-
*
|
|
6069
|
-
*
|
|
6070
|
-
*
|
|
7141
|
+
* Each candidate gets up to four attempts: a plain centre sample, a 3x3
|
|
7142
|
+
* majority vote for noisy input, and both of those rotated 180 degrees. The
|
|
7143
|
+
* rotation retry matters because three finders in a right isoceles triangle
|
|
7144
|
+
* look identical to the same three rotated half a turn — the orientation is
|
|
7145
|
+
* only settled once the format information reads cleanly, which is to say once
|
|
7146
|
+
* the decode succeeds.
|
|
7147
|
+
*
|
|
7148
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
7149
|
+
* @returns {Array<import('./decoder.js').DecodeResult & {corners: Array<{x: number, y: number}>}>}
|
|
7150
|
+
* Empty when nothing decodes; never throws for "no symbol here".
|
|
6071
7151
|
*/
|
|
6072
|
-
function
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
7152
|
+
function detectAndDecodeQR(binaryImage) {
|
|
7153
|
+
let detections;
|
|
7154
|
+
try {
|
|
7155
|
+
detections = detectQR(binaryImage);
|
|
7156
|
+
} catch (e) {
|
|
7157
|
+
return [];
|
|
7158
|
+
}
|
|
6078
7159
|
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
if (region && (centreX < region.x0 || centreX > region.x1)) return;
|
|
7160
|
+
const results = [];
|
|
7161
|
+
const seen = new Set();
|
|
6082
7162
|
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
const offsetY = crossCheck(image, px, y, 0, 1, ratios, maxRun);
|
|
6086
|
-
if (Number.isNaN(offsetY)) return;
|
|
7163
|
+
for (let i = 0; i < detections.length; i++) {
|
|
7164
|
+
const det = detections[i];
|
|
6087
7165
|
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
const offsetX = crossCheck(image, px, Math.round(cy), 1, 0, ratios, maxRun);
|
|
6092
|
-
if (Number.isNaN(offsetX)) return;
|
|
7166
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
7167
|
+
const voting = attempt === 1 || attempt === 3;
|
|
7168
|
+
const rotated = attempt >= 2;
|
|
6093
7169
|
|
|
6094
|
-
|
|
7170
|
+
let matrix;
|
|
7171
|
+
try {
|
|
7172
|
+
matrix = sampleQuad(binaryImage, det.dimension, det.corners, voting);
|
|
7173
|
+
} catch (e) {
|
|
7174
|
+
continue;
|
|
7175
|
+
}
|
|
7176
|
+
if (rotated) matrix.rotate180();
|
|
6095
7177
|
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
) {
|
|
6104
|
-
const n = c.hits + 1;
|
|
6105
|
-
c.x = (c.x * c.hits + cx) / n;
|
|
6106
|
-
c.y = (c.y * c.hits + cy) / n;
|
|
6107
|
-
c.moduleSize = (c.moduleSize * c.hits + moduleSize) / n;
|
|
6108
|
-
c.hits = n;
|
|
6109
|
-
return;
|
|
7178
|
+
try {
|
|
7179
|
+
const result = decodeQR(matrix);
|
|
7180
|
+
// The same symbol can be detected through more than one finder triple.
|
|
7181
|
+
const key = `${result.version}|${result.text}`;
|
|
7182
|
+
if (!seen.has(key)) {
|
|
7183
|
+
seen.add(key);
|
|
7184
|
+
results.push(Object.assign({ corners: det.corners }, result));
|
|
6110
7185
|
}
|
|
7186
|
+
break;
|
|
7187
|
+
} catch (e) {
|
|
7188
|
+
/* Try the next sampling strategy. */
|
|
6111
7189
|
}
|
|
6112
|
-
|
|
6113
|
-
found.push({ x: cx, y: cy, moduleSize, hits: 1 });
|
|
6114
|
-
});
|
|
7190
|
+
}
|
|
6115
7191
|
}
|
|
6116
7192
|
|
|
6117
|
-
return
|
|
7193
|
+
return results;
|
|
6118
7194
|
}
|
|
6119
7195
|
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
*/
|
|
6124
|
-
function distance(a, b) {
|
|
6125
|
-
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
6126
|
-
}
|
|
7196
|
+
__exports.detectQR = detectQR;
|
|
7197
|
+
__exports.detectAndDecodeQR = detectAndDecodeQR;
|
|
7198
|
+
};
|
|
6127
7199
|
|
|
7200
|
+
__modules["qr/index.js"] = function (__require, __exports) {
|
|
6128
7201
|
/**
|
|
6129
|
-
*
|
|
7202
|
+
* QR Code, re-exported.
|
|
6130
7203
|
*
|
|
6131
|
-
*
|
|
6132
|
-
*
|
|
6133
|
-
*
|
|
6134
|
-
* (topRight - topLeft) x (bottomLeft - topLeft) positive.
|
|
7204
|
+
* `QR_PLACEHOLDER` is deliberately absent: `src/index.js` probes for it to
|
|
7205
|
+
* decide whether this build can read and write QR, and its absence is what
|
|
7206
|
+
* reports the format as available.
|
|
6135
7207
|
*
|
|
6136
|
-
* @
|
|
6137
|
-
* @returns {{tl: Candidate, tr: Candidate, bl: Candidate} | null}
|
|
7208
|
+
* @module qr
|
|
6138
7209
|
*/
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
const ca = distance(c, a);
|
|
7210
|
+
const __reexport0 = __require("qr/encoder.js"); __exports.encodeQR = __reexport0.encodeQR;
|
|
7211
|
+
const __reexport1 = __require("qr/decoder.js"); __exports.decodeQR = __reexport1.decodeQR;
|
|
7212
|
+
const __reexport2 = __require("qr/detector.js"); __exports.detectQR = __reexport2.detectQR; __exports.detectAndDecodeQR = __reexport2.detectAndDecodeQR;
|
|
7213
|
+
const __reexport3 = __require("qr/tables.js"); __exports.validateTables = __reexport3.validateTables;
|
|
6144
7214
|
|
|
6145
|
-
let tl, p, q, hypotenuse, leg1, leg2;
|
|
6146
|
-
if (ab >= bc && ab >= ca) {
|
|
6147
|
-
tl = c; p = a; q = b; hypotenuse = ab; leg1 = ca; leg2 = bc;
|
|
6148
|
-
} else if (bc >= ab && bc >= ca) {
|
|
6149
|
-
tl = a; p = b; q = c; hypotenuse = bc; leg1 = ab; leg2 = ca;
|
|
6150
|
-
} else {
|
|
6151
|
-
tl = b; p = c; q = a; hypotenuse = ca; leg1 = bc; leg2 = ab;
|
|
6152
|
-
}
|
|
6153
7215
|
|
|
6154
|
-
|
|
7216
|
+
};
|
|
6155
7217
|
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
7218
|
+
__modules["aztec/high-level.js"] = function (__require, __exports) {
|
|
7219
|
+
/**
|
|
7220
|
+
* Aztec high-level stream writer.
|
|
7221
|
+
*
|
|
7222
|
+
* The output is deliberately a `BitWriter`, rather than a byte array: Aztec's
|
|
7223
|
+
* text controls and binary-shift lengths are not byte aligned. This module is
|
|
7224
|
+
* also the boundary where JavaScript strings become UTF-8. Passing a byte
|
|
7225
|
+
* view bypasses that conversion and preserves every octet unchanged.
|
|
7226
|
+
*
|
|
7227
|
+
* The initial state mandated by the symbology is UPPER. The greedy text pass
|
|
7228
|
+
* uses UPPER, LOWER, DIGIT and PUNCT tables, selecting the shortest available
|
|
7229
|
+
* latch at each byte. Bytes without a text-table representation are emitted
|
|
7230
|
+
* through the standard B/S (binary shift) escape. B/S is available from
|
|
7231
|
+
* UPPER and makes this a complete, lossless representation of UTF-8 payloads.
|
|
7232
|
+
*
|
|
7233
|
+
* @module aztec/high-level
|
|
7234
|
+
*/
|
|
7235
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
7236
|
+
const { EncodeError } = __require("core/errors.js");
|
|
6162
7237
|
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
7238
|
+
/** Aztec high-level table identifiers, exposed for decoder/API symmetry. */
|
|
7239
|
+
const HIGH_LEVEL_MODE = Object.freeze({
|
|
7240
|
+
UPPER: 0,
|
|
7241
|
+
LOWER: 1,
|
|
7242
|
+
DIGIT: 2,
|
|
7243
|
+
MIXED: 3,
|
|
7244
|
+
PUNCT: 4,
|
|
7245
|
+
});
|
|
7246
|
+
|
|
7247
|
+
/** Maximum number of bytes represented by one B/S escape. */
|
|
7248
|
+
const MAX_BINARY_SHIFT = 2078;
|
|
6166
7249
|
|
|
6167
7250
|
/**
|
|
6168
|
-
*
|
|
6169
|
-
*
|
|
6170
|
-
* Every QR dimension is 17 + 4v, so `dimension % 4 === 1`. A measurement one
|
|
6171
|
-
* off is rounding; two off means the module size estimate is wrong and the
|
|
6172
|
-
* candidate is not worth pursuing.
|
|
7251
|
+
* Convert accepted public input to its encoded octets.
|
|
6173
7252
|
*
|
|
6174
|
-
* @param {
|
|
6175
|
-
* @
|
|
7253
|
+
* @param {string|ArrayBuffer|ArrayBufferView} value
|
|
7254
|
+
* @param {'utf-8'} [charset]
|
|
7255
|
+
* @returns {Uint8Array}
|
|
6176
7256
|
*/
|
|
6177
|
-
function
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
7257
|
+
function aztecBytes(value, charset = 'utf-8') {
|
|
7258
|
+
if (charset !== 'utf-8') throw new EncodeError(`Aztec: unsupported charset "${charset}"`);
|
|
7259
|
+
if (typeof value === 'string') return new TextEncoder().encode(value);
|
|
7260
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
7261
|
+
if (ArrayBuffer.isView(value)) {
|
|
7262
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
7263
|
+
}
|
|
7264
|
+
throw new EncodeError('Aztec: value must be a string, ArrayBuffer, or byte view');
|
|
7265
|
+
}
|
|
7266
|
+
|
|
7267
|
+
/** @param {number} byte @returns {number} UPPER-table value, or -1. */
|
|
7268
|
+
function upperValue(byte) {
|
|
7269
|
+
if (byte === 0x20) return 1;
|
|
7270
|
+
if (byte >= 0x41 && byte <= 0x5a) return byte - 0x41 + 2;
|
|
7271
|
+
return -1;
|
|
7272
|
+
}
|
|
7273
|
+
|
|
7274
|
+
/** Aztec's latch table, packed as `(bitCount << 16) | bits`. */
|
|
7275
|
+
const LATCH = Object.freeze([
|
|
7276
|
+
[0, 327708, 327710, 327709, 656318],
|
|
7277
|
+
[590318, 0, 327710, 327709, 656318],
|
|
7278
|
+
[262158, 590300, 0, 590301, 932798],
|
|
7279
|
+
[327709, 327708, 656322, 0, 327710],
|
|
7280
|
+
[327711, 656380, 656382, 656381, 0],
|
|
7281
|
+
]);
|
|
7282
|
+
|
|
7283
|
+
/** @param {number} byte @returns {number} */
|
|
7284
|
+
function lowerValue(byte) {
|
|
7285
|
+
if (byte === 0x20) return 1;
|
|
7286
|
+
if (byte >= 0x61 && byte <= 0x7a) return byte - 0x61 + 2;
|
|
7287
|
+
return -1;
|
|
7288
|
+
}
|
|
7289
|
+
|
|
7290
|
+
/** @param {number} byte @returns {number} */
|
|
7291
|
+
function digitValue(byte) {
|
|
7292
|
+
if (byte === 0x20) return 1;
|
|
7293
|
+
if (byte >= 0x30 && byte <= 0x39) return byte - 0x30 + 2;
|
|
7294
|
+
if (byte === 0x2c) return 12;
|
|
7295
|
+
if (byte === 0x2e) return 13;
|
|
7296
|
+
return -1;
|
|
7297
|
+
}
|
|
7298
|
+
|
|
7299
|
+
const PUNCT = new Map([
|
|
7300
|
+
[0x0d, 1], [0x21, 6], [0x22, 7], [0x23, 8], [0x24, 9], [0x25, 10],
|
|
7301
|
+
[0x26, 11], [0x27, 12], [0x28, 13], [0x29, 14], [0x2a, 15], [0x2b, 16],
|
|
7302
|
+
[0x2c, 17], [0x2d, 18], [0x2e, 19], [0x2f, 20], [0x3a, 21], [0x3b, 22],
|
|
7303
|
+
[0x3c, 23], [0x3d, 24], [0x3e, 25], [0x3f, 26], [0x5b, 27], [0x5d, 28],
|
|
7304
|
+
[0x7b, 29], [0x7d, 30],
|
|
7305
|
+
]);
|
|
7306
|
+
|
|
7307
|
+
/** @param {number} byte @param {number} mode @returns {number} */
|
|
7308
|
+
function textValue(byte, mode) {
|
|
7309
|
+
switch (mode) {
|
|
7310
|
+
case HIGH_LEVEL_MODE.UPPER: return upperValue(byte);
|
|
7311
|
+
case HIGH_LEVEL_MODE.LOWER: return lowerValue(byte);
|
|
7312
|
+
case HIGH_LEVEL_MODE.DIGIT: return digitValue(byte);
|
|
7313
|
+
case HIGH_LEVEL_MODE.PUNCT: return PUNCT.get(byte) ?? -1;
|
|
7314
|
+
default: return -1;
|
|
6184
7315
|
}
|
|
6185
|
-
if (d < MIN_DIMENSION || d > MAX_DIMENSION) return 0;
|
|
6186
|
-
return d;
|
|
6187
7316
|
}
|
|
6188
7317
|
|
|
6189
|
-
/**
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
7318
|
+
/** @param {BitWriter} writer @param {number} from @param {number} to */
|
|
7319
|
+
function latch(writer, from, to) {
|
|
7320
|
+
if (from === to) return;
|
|
7321
|
+
const packed = LATCH[from][to];
|
|
7322
|
+
writer.put(packed & 0xffff, packed >>> 16);
|
|
7323
|
+
}
|
|
7324
|
+
|
|
7325
|
+
/** @param {number} mode @returns {number} */
|
|
7326
|
+
function characterWidth(mode) {
|
|
7327
|
+
return mode === HIGH_LEVEL_MODE.DIGIT ? 4 : 5;
|
|
7328
|
+
}
|
|
6197
7329
|
|
|
6198
7330
|
/**
|
|
6199
|
-
*
|
|
7331
|
+
* Write an Aztec binary-shift segment while in UPPER mode.
|
|
6200
7332
|
*
|
|
6201
|
-
*
|
|
6202
|
-
*
|
|
6203
|
-
*
|
|
6204
|
-
*
|
|
6205
|
-
* the fourth corner off by several modules, which is worse than having no
|
|
6206
|
-
* alignment pattern at all.
|
|
7333
|
+
* B/S is `11111`; its five-bit length directly covers 1..31 bytes. A zero
|
|
7334
|
+
* length selects the extended eleven-bit form, whose stored value is n - 31.
|
|
7335
|
+
* Splitting at 2078 keeps each control representable and makes arbitrarily
|
|
7336
|
+
* long byte input well-defined.
|
|
6207
7337
|
*
|
|
6208
|
-
* @param {
|
|
6209
|
-
* @param {
|
|
6210
|
-
* @
|
|
7338
|
+
* @param {BitWriter} writer
|
|
7339
|
+
* @param {Uint8Array} bytes
|
|
7340
|
+
* @param {number} start
|
|
7341
|
+
* @param {number} length
|
|
6211
7342
|
*/
|
|
6212
|
-
function
|
|
6213
|
-
let
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
7343
|
+
function writeBinaryShift(writer, bytes, start, length) {
|
|
7344
|
+
let at = start;
|
|
7345
|
+
let left = length;
|
|
7346
|
+
while (left > 0) {
|
|
7347
|
+
const count = Math.min(left, MAX_BINARY_SHIFT);
|
|
7348
|
+
writer.put(31, 5); // UPPER B/S
|
|
7349
|
+
if (count <= 31) writer.put(count, 5);
|
|
7350
|
+
else {
|
|
7351
|
+
writer.put(0, 5);
|
|
7352
|
+
writer.put(count - 31, 11);
|
|
6220
7353
|
}
|
|
7354
|
+
for (let i = 0; i < count; i++) writer.put(bytes[at + i], 8);
|
|
7355
|
+
at += count;
|
|
7356
|
+
left -= count;
|
|
6221
7357
|
}
|
|
6222
|
-
// Allow two modules of slop for blur and sampling, but no more.
|
|
6223
|
-
return good >= 23;
|
|
6224
7358
|
}
|
|
6225
7359
|
|
|
6226
7360
|
/**
|
|
6227
|
-
*
|
|
7361
|
+
* Build a valid Aztec high-level bitstream.
|
|
6228
7362
|
*
|
|
6229
|
-
* @param {
|
|
6230
|
-
* @param {{
|
|
6231
|
-
* @
|
|
6232
|
-
* @returns {{x: number, y: number} | null}
|
|
7363
|
+
* @param {string|ArrayBuffer|ArrayBufferView} value
|
|
7364
|
+
* @param {{charset?: 'utf-8'}} [options]
|
|
7365
|
+
* @returns {BitWriter}
|
|
6233
7366
|
*/
|
|
6234
|
-
function
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
7367
|
+
function encodeHighLevel(value, options = {}) {
|
|
7368
|
+
const bytes = aztecBytes(value, options.charset ?? 'utf-8');
|
|
7369
|
+
const writer = new BitWriter();
|
|
7370
|
+
let mode = HIGH_LEVEL_MODE.UPPER;
|
|
7371
|
+
|
|
7372
|
+
for (let at = 0; at < bytes.length;) {
|
|
7373
|
+
let bestMode = -1;
|
|
7374
|
+
let bestValue = -1;
|
|
7375
|
+
let bestCost = Number.POSITIVE_INFINITY;
|
|
7376
|
+
for (const candidate of [HIGH_LEVEL_MODE.UPPER, HIGH_LEVEL_MODE.LOWER, HIGH_LEVEL_MODE.DIGIT, HIGH_LEVEL_MODE.PUNCT]) {
|
|
7377
|
+
const value = textValue(bytes[at], candidate);
|
|
7378
|
+
if (value < 0) continue;
|
|
7379
|
+
const latchCost = candidate === mode ? 0 : LATCH[mode][candidate] >>> 16;
|
|
7380
|
+
const cost = latchCost + characterWidth(candidate);
|
|
7381
|
+
if (cost < bestCost) { bestCost = cost; bestMode = candidate; bestValue = value; }
|
|
7382
|
+
}
|
|
7383
|
+
if (bestMode >= 0) {
|
|
7384
|
+
latch(writer, mode, bestMode);
|
|
7385
|
+
writer.put(bestValue, characterWidth(bestMode));
|
|
7386
|
+
mode = bestMode;
|
|
7387
|
+
at++;
|
|
7388
|
+
} else {
|
|
7389
|
+
// B/S is defined from UPPER; the latch is retained after the shift.
|
|
7390
|
+
latch(writer, mode, HIGH_LEVEL_MODE.UPPER);
|
|
7391
|
+
mode = HIGH_LEVEL_MODE.UPPER;
|
|
7392
|
+
const start = at;
|
|
7393
|
+
while (at < bytes.length && ![HIGH_LEVEL_MODE.UPPER, HIGH_LEVEL_MODE.LOWER, HIGH_LEVEL_MODE.DIGIT, HIGH_LEVEL_MODE.PUNCT].some((m) => textValue(bytes[at], m) >= 0)) at++;
|
|
7394
|
+
writeBinaryShift(writer, bytes, start, at - start);
|
|
6259
7395
|
}
|
|
6260
7396
|
}
|
|
7397
|
+
return writer;
|
|
7398
|
+
}
|
|
6261
7399
|
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
7400
|
+
__exports.HIGH_LEVEL_MODE = HIGH_LEVEL_MODE;
|
|
7401
|
+
__exports.MAX_BINARY_SHIFT = MAX_BINARY_SHIFT;
|
|
7402
|
+
__exports.aztecBytes = aztecBytes;
|
|
7403
|
+
__exports.writeBinaryShift = writeBinaryShift;
|
|
7404
|
+
__exports.encodeHighLevel = encodeHighLevel;
|
|
7405
|
+
};
|
|
6267
7406
|
|
|
6268
|
-
|
|
7407
|
+
__modules["aztec/tables.js"] = function (__require, __exports) {
|
|
7408
|
+
/**
|
|
7409
|
+
* Aztec Code layer geometry and Reed-Solomon parameters.
|
|
7410
|
+
*
|
|
7411
|
+
* `totalBits` counts the payload ring before its leading pad bits are added;
|
|
7412
|
+
* consequently only `usableBits` can be partitioned into codewords. Compact
|
|
7413
|
+
* symbols have no reference grid. Full symbols insert alternating reference
|
|
7414
|
+
* rows and columns every 16 modules around the centre.
|
|
7415
|
+
*
|
|
7416
|
+
* The five data fields use generator base 1. GF(256)/DataMatrix is also the
|
|
7417
|
+
* Aztec 8-bit field: both use primitive polynomial 0x12d.
|
|
7418
|
+
*
|
|
7419
|
+
* @module aztec/tables
|
|
7420
|
+
*/
|
|
7421
|
+
const { GF16, GF64, GF256_AZTEC, GF1024, GF4096 } = __require("core/galois-field.js");
|
|
7422
|
+
|
|
7423
|
+
/** Reed-Solomon generator base defined for Aztec parameter and data fields. */
|
|
7424
|
+
const AZTEC_RS_GENERATOR_BASE = 1;
|
|
7425
|
+
|
|
7426
|
+
/** Minimum recommended error correction: 23 percent plus three codewords. */
|
|
7427
|
+
const AZTEC_DEFAULT_ECC_PERCENT = 23;
|
|
7428
|
+
const AZTEC_MIN_ECC_WORDS = 3;
|
|
7429
|
+
|
|
7430
|
+
/** Word size selected solely by the number of layers. */
|
|
7431
|
+
function wordSizeForLayers(layers) {
|
|
7432
|
+
if (!Number.isInteger(layers) || layers < 1 || layers > 32) {
|
|
7433
|
+
throw new RangeError(`Aztec: layers must be an integer from 1 to 32 (got ${layers})`);
|
|
7434
|
+
}
|
|
7435
|
+
if (layers <= 2) return 6;
|
|
7436
|
+
if (layers <= 8) return 8;
|
|
7437
|
+
if (layers <= 22) return 10;
|
|
7438
|
+
return 12;
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
/** Return the field used by Aztec codewords of `wordSize` bits. */
|
|
7442
|
+
function fieldForWordSize(wordSize) {
|
|
7443
|
+
switch (wordSize) {
|
|
7444
|
+
case 4: return GF16; // Mode message only.
|
|
7445
|
+
case 6: return GF64;
|
|
7446
|
+
case 8: return GF256_AZTEC;
|
|
7447
|
+
case 10: return GF1024;
|
|
7448
|
+
case 12: return GF4096;
|
|
7449
|
+
default: throw new RangeError(`Aztec: unsupported codeword size ${wordSize}`);
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
|
|
7453
|
+
/** Return the data field selected for a symbol with `layers` layers. */
|
|
7454
|
+
function fieldForLayers(layers) {
|
|
7455
|
+
return fieldForWordSize(wordSizeForLayers(layers));
|
|
7456
|
+
}
|
|
7457
|
+
|
|
7458
|
+
/** Matrix side length, including Full-mode reference grid lines. */
|
|
7459
|
+
function aztecSymbolSize(layers, compact = false) {
|
|
7460
|
+
if (!Number.isInteger(layers) || layers < 1 || layers > (compact ? 4 : 32)) {
|
|
7461
|
+
throw new RangeError(`Aztec: ${compact ? 'Compact' : 'Full'} layers out of range: ${layers}`);
|
|
7462
|
+
}
|
|
7463
|
+
if (compact) return 11 + 4 * layers;
|
|
7464
|
+
const baseMatrixSize = 14 + 4 * layers;
|
|
7465
|
+
return baseMatrixSize + 1 + 2 * Math.floor((baseMatrixSize / 2 - 1) / 15);
|
|
7466
|
+
}
|
|
7467
|
+
|
|
7468
|
+
function layer(layers, compact) {
|
|
7469
|
+
const wordSize = wordSizeForLayers(layers);
|
|
7470
|
+
const totalBits = ((compact ? 88 : 112) + 16 * layers) * layers;
|
|
7471
|
+
const usableBits = totalBits - totalBits % wordSize;
|
|
7472
|
+
const totalCodewords = usableBits / wordSize;
|
|
7473
|
+
const baseMatrixSize = (compact ? 11 : 14) + 4 * layers;
|
|
7474
|
+
return Object.freeze({
|
|
7475
|
+
compact,
|
|
7476
|
+
layers,
|
|
7477
|
+
wordSize,
|
|
7478
|
+
totalBits,
|
|
7479
|
+
usableBits,
|
|
7480
|
+
totalCodewords,
|
|
7481
|
+
// Compact mode encodes the count in six bits and can therefore hold no
|
|
7482
|
+
// more than 64 data codewords even where the ring itself is larger.
|
|
7483
|
+
maxDataCodewords: compact ? Math.min(totalCodewords, 64) : totalCodewords,
|
|
7484
|
+
baseMatrixSize,
|
|
7485
|
+
symbolSize: aztecSymbolSize(layers, compact),
|
|
7486
|
+
modeMessageDataWords: compact ? 2 : 4,
|
|
7487
|
+
modeMessageWords: compact ? 7 : 10,
|
|
7488
|
+
modeMessageBits: compact ? 28 : 40,
|
|
7489
|
+
rsGeneratorBase: AZTEC_RS_GENERATOR_BASE,
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
/** Compact Aztec layers 1 through 4, in encoding preference order. */
|
|
7494
|
+
const AZTEC_COMPACT_LAYERS = Object.freeze(
|
|
7495
|
+
Array.from({ length: 4 }, (_, i) => layer(i + 1, true)),
|
|
7496
|
+
);
|
|
7497
|
+
|
|
7498
|
+
/** Full Aztec layers 1 through 32, in ascending layer order. */
|
|
7499
|
+
const AZTEC_FULL_LAYERS = Object.freeze(
|
|
7500
|
+
Array.from({ length: 32 }, (_, i) => layer(i + 1, false)),
|
|
7501
|
+
);
|
|
7502
|
+
|
|
7503
|
+
/** All allowed symbols. Compact entries precede Full entries for automatic selection. */
|
|
7504
|
+
const AZTEC_LAYERS = Object.freeze([
|
|
7505
|
+
...AZTEC_COMPACT_LAYERS,
|
|
7506
|
+
...AZTEC_FULL_LAYERS,
|
|
7507
|
+
]);
|
|
7508
|
+
|
|
7509
|
+
/** Return one immutable layer record. */
|
|
7510
|
+
function aztecLayer(layers, compact = false) {
|
|
7511
|
+
if (!Number.isInteger(layers) || layers < 1 || layers > (compact ? 4 : 32)) {
|
|
7512
|
+
throw new RangeError(`Aztec: ${compact ? 'Compact' : 'Full'} layers out of range: ${layers}`);
|
|
7513
|
+
}
|
|
7514
|
+
return (compact ? AZTEC_COMPACT_LAYERS : AZTEC_FULL_LAYERS)[layers - 1];
|
|
6269
7515
|
}
|
|
6270
7516
|
|
|
6271
7517
|
/**
|
|
6272
|
-
*
|
|
6273
|
-
*
|
|
6274
|
-
*
|
|
6275
|
-
*
|
|
6276
|
-
*
|
|
6277
|
-
* @property {number} moduleSize Estimated pixels per module.
|
|
6278
|
-
* @property {boolean} alignmentFound
|
|
7518
|
+
* Calculate the minimum parity count for a data word count.
|
|
7519
|
+
*
|
|
7520
|
+
* The percentage is rounded up because a fractional codeword cannot be
|
|
7521
|
+
* emitted. The mandatory three words protect short payloads, where a bare
|
|
7522
|
+
* percentage would otherwise round to zero.
|
|
6279
7523
|
*/
|
|
7524
|
+
function eccCodewordsFor(dataCodewords, eccPercent = AZTEC_DEFAULT_ECC_PERCENT) {
|
|
7525
|
+
if (!Number.isInteger(dataCodewords) || dataCodewords < 0) {
|
|
7526
|
+
throw new RangeError(`Aztec: data codewords must be a non-negative integer (got ${dataCodewords})`);
|
|
7527
|
+
}
|
|
7528
|
+
if (!Number.isFinite(eccPercent) || eccPercent < 0 || eccPercent > 100) {
|
|
7529
|
+
throw new RangeError(`Aztec: ECC percent must be between 0 and 100 (got ${eccPercent})`);
|
|
7530
|
+
}
|
|
7531
|
+
return Math.ceil(dataCodewords * eccPercent / 100) + AZTEC_MIN_ECC_WORDS;
|
|
7532
|
+
}
|
|
6280
7533
|
|
|
6281
7534
|
/**
|
|
6282
|
-
*
|
|
7535
|
+
* Choose the first symbol which holds an already stuffed payload.
|
|
6283
7536
|
*
|
|
6284
|
-
*
|
|
6285
|
-
*
|
|
6286
|
-
* the most prominent symbol comes first.
|
|
7537
|
+
* `dataBits` must be a multiple of the candidate word size; callers which
|
|
7538
|
+
* start from high-level bits must stuff separately per candidate word size.
|
|
6287
7539
|
*/
|
|
6288
|
-
function
|
|
6289
|
-
|
|
6290
|
-
|
|
7540
|
+
function selectAztecLayer(dataBits, {
|
|
7541
|
+
eccPercent = AZTEC_DEFAULT_ECC_PERCENT,
|
|
7542
|
+
layers = null,
|
|
7543
|
+
compact = null,
|
|
7544
|
+
} = {}) {
|
|
7545
|
+
if (!Number.isInteger(dataBits) || dataBits < 0) {
|
|
7546
|
+
throw new RangeError(`Aztec: data bits must be a non-negative integer (got ${dataBits})`);
|
|
7547
|
+
}
|
|
7548
|
+
if (compact !== null && typeof compact !== 'boolean') {
|
|
7549
|
+
throw new TypeError('Aztec: compact must be true, false or null');
|
|
6291
7550
|
}
|
|
6292
7551
|
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
if (
|
|
7552
|
+
let candidates;
|
|
7553
|
+
if (layers !== null) {
|
|
7554
|
+
if (compact === null) throw new TypeError('Aztec: compact must be specified when layers is specified');
|
|
7555
|
+
candidates = [aztecLayer(layers, compact)];
|
|
7556
|
+
} else if (compact === null) {
|
|
7557
|
+
candidates = AZTEC_LAYERS;
|
|
7558
|
+
} else {
|
|
7559
|
+
candidates = compact ? AZTEC_COMPACT_LAYERS : AZTEC_FULL_LAYERS;
|
|
7560
|
+
}
|
|
6298
7561
|
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
7562
|
+
for (const candidate of candidates) {
|
|
7563
|
+
if (dataBits % candidate.wordSize !== 0) continue;
|
|
7564
|
+
const dataCodewords = dataBits / candidate.wordSize;
|
|
7565
|
+
const eccCodewords = eccCodewordsFor(dataCodewords, eccPercent);
|
|
7566
|
+
if (dataCodewords <= candidate.maxDataCodewords &&
|
|
7567
|
+
dataCodewords + eccCodewords <= candidate.totalCodewords) {
|
|
7568
|
+
return Object.freeze({ ...candidate, dataCodewords, eccCodewords });
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
6302
7571
|
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
const used = new Set();
|
|
7572
|
+
throw new RangeError('Aztec: payload and requested error correction do not fit an available symbol');
|
|
7573
|
+
}
|
|
6306
7574
|
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
7575
|
+
/** Check static identities so table corruption fails explicitly in tests. */
|
|
7576
|
+
function validateAztecTables() {
|
|
7577
|
+
const issues = [];
|
|
7578
|
+
for (const entry of AZTEC_LAYERS) {
|
|
7579
|
+
if (entry.usableBits % entry.wordSize !== 0) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: unaligned usable bits`);
|
|
7580
|
+
if (entry.totalCodewords !== entry.usableBits / entry.wordSize) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: codeword mismatch`);
|
|
7581
|
+
if (entry.symbolSize !== aztecSymbolSize(entry.layers, entry.compact)) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: matrix size mismatch`);
|
|
7582
|
+
if (entry.rsGeneratorBase !== AZTEC_RS_GENERATOR_BASE) issues.push(`${entry.compact ? 'C' : 'F'}${entry.layers}: generator base mismatch`);
|
|
7583
|
+
if (entry.compact && entry.maxDataCodewords > 64) issues.push(`C${entry.layers}: Compact data-word limit exceeded`);
|
|
7584
|
+
}
|
|
7585
|
+
return issues;
|
|
7586
|
+
}
|
|
6311
7587
|
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
7588
|
+
__exports.AZTEC_RS_GENERATOR_BASE = AZTEC_RS_GENERATOR_BASE;
|
|
7589
|
+
__exports.AZTEC_DEFAULT_ECC_PERCENT = AZTEC_DEFAULT_ECC_PERCENT;
|
|
7590
|
+
__exports.AZTEC_MIN_ECC_WORDS = AZTEC_MIN_ECC_WORDS;
|
|
7591
|
+
__exports.wordSizeForLayers = wordSizeForLayers;
|
|
7592
|
+
__exports.fieldForWordSize = fieldForWordSize;
|
|
7593
|
+
__exports.fieldForLayers = fieldForLayers;
|
|
7594
|
+
__exports.aztecSymbolSize = aztecSymbolSize;
|
|
7595
|
+
__exports.AZTEC_COMPACT_LAYERS = AZTEC_COMPACT_LAYERS;
|
|
7596
|
+
__exports.AZTEC_FULL_LAYERS = AZTEC_FULL_LAYERS;
|
|
7597
|
+
__exports.AZTEC_LAYERS = AZTEC_LAYERS;
|
|
7598
|
+
__exports.aztecLayer = aztecLayer;
|
|
7599
|
+
__exports.eccCodewordsFor = eccCodewordsFor;
|
|
7600
|
+
__exports.selectAztecLayer = selectAztecLayer;
|
|
7601
|
+
__exports.validateAztecTables = validateAztecTables;
|
|
7602
|
+
};
|
|
6315
7603
|
|
|
6316
|
-
|
|
6317
|
-
|
|
7604
|
+
__modules["aztec/encoder.js"] = function (__require, __exports) {
|
|
7605
|
+
/**
|
|
7606
|
+
* Aztec encoder: high-level bits, bit stuffing, Reed-Solomon and matrix layout.
|
|
7607
|
+
*
|
|
7608
|
+
* `tables.js` is intentionally the source of geometry and field selection.
|
|
7609
|
+
* Its `aztecLayer(layers, compact)` entries must expose `totalBits`,
|
|
7610
|
+
* `totalCodewords`, `baseMatrixSize` and `symbolSize`; `fieldForLayers()` must
|
|
7611
|
+
* return the matching binary field. All Aztec Reed-Solomon generators start
|
|
7612
|
+
* at alpha^1, hence the explicit base `1` in both data and mode messages.
|
|
7613
|
+
*
|
|
7614
|
+
* @module aztec/encoder
|
|
7615
|
+
*/
|
|
7616
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
7617
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
7618
|
+
const { EncodeError } = __require("core/errors.js");
|
|
7619
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
7620
|
+
const { encodeHighLevel } = __require("aztec/high-level.js");
|
|
7621
|
+
const { AZTEC_COMPACT_LAYERS, AZTEC_FULL_LAYERS, aztecLayer, eccCodewordsFor, fieldForLayers, fieldForWordSize, wordSizeForLayers } = __require("aztec/tables.js");
|
|
6318
7622
|
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
7623
|
+
/** @param {BitWriter} bits @param {number} at @returns {boolean} */
|
|
7624
|
+
function bitAt(bits, at) {
|
|
7625
|
+
return at >= 0 && at < bits.length && ((bits.bytes[at >>> 3] >>> (7 - (at & 7))) & 1) !== 0;
|
|
7626
|
+
}
|
|
6322
7627
|
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
7628
|
+
/** @param {BitWriter} bits @param {number} from @param {number} count @returns {number} */
|
|
7629
|
+
function readBits(bits, from, count) {
|
|
7630
|
+
let value = 0;
|
|
7631
|
+
for (let i = 0; i < count; i++) value = (value << 1) | (bitAt(bits, from + i) ? 1 : 0);
|
|
7632
|
+
return value;
|
|
7633
|
+
}
|
|
7634
|
+
|
|
7635
|
+
/**
|
|
7636
|
+
* Prevent all-zero and all-one codewords except their final bit. The final
|
|
7637
|
+
* bit is intentionally re-consumed after a stuffed word; it is the mechanism
|
|
7638
|
+
* that makes the transform injective and reversible.
|
|
7639
|
+
*
|
|
7640
|
+
* @param {BitWriter} bits @param {number} wordSize @returns {BitWriter}
|
|
7641
|
+
*/
|
|
7642
|
+
function stuffBits(bits, wordSize) {
|
|
7643
|
+
const out = new BitWriter();
|
|
7644
|
+
const reserved = (1 << wordSize) - 2;
|
|
7645
|
+
for (let at = 0; at < bits.length; at += wordSize) {
|
|
7646
|
+
const word = readBits(bits, at, wordSize);
|
|
7647
|
+
if ((word & reserved) === reserved) {
|
|
7648
|
+
out.put(word & reserved, wordSize);
|
|
7649
|
+
at--;
|
|
7650
|
+
} else if ((word & reserved) === 0) {
|
|
7651
|
+
out.put(word | 1, wordSize);
|
|
7652
|
+
at--;
|
|
7653
|
+
} else {
|
|
7654
|
+
out.put(word, wordSize);
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
return out;
|
|
7658
|
+
}
|
|
7659
|
+
|
|
7660
|
+
/**
|
|
7661
|
+
* Add systematic Aztec Reed-Solomon parity and the leading alignment bits.
|
|
7662
|
+
* @param {BitWriter} data @param {number} totalBits @param {number} wordSize
|
|
7663
|
+
* @param {import('../core/galois-field.js').GaloisField} field
|
|
7664
|
+
* @returns {{bits: BitWriter, dataWords: number, eccWords: number}}
|
|
7665
|
+
*/
|
|
7666
|
+
function addCheckWords(data, totalBits, wordSize, field) {
|
|
7667
|
+
const totalWords = Math.floor(totalBits / wordSize);
|
|
7668
|
+
const dataWords = Math.ceil(data.length / wordSize);
|
|
7669
|
+
if (dataWords > totalWords) throw new EncodeError('Aztec: data codewords exceed layer capacity');
|
|
7670
|
+
const eccWords = totalWords - dataWords;
|
|
7671
|
+
const words = new Array(dataWords);
|
|
7672
|
+
for (let i = 0; i < dataWords; i++) words[i] = readBits(data, i * wordSize, wordSize);
|
|
7673
|
+
const ecc = rsEncode(words, eccWords, field, 1);
|
|
7674
|
+
const out = new BitWriter();
|
|
7675
|
+
out.put(0, totalBits % wordSize);
|
|
7676
|
+
for (const word of words) out.put(word, wordSize);
|
|
7677
|
+
for (const word of ecc) out.put(word, wordSize);
|
|
7678
|
+
return { bits: out, dataWords, eccWords };
|
|
7679
|
+
}
|
|
7680
|
+
|
|
7681
|
+
/** @param {number} layers @param {number} dataWords @param {boolean} compact @returns {BitWriter} */
|
|
7682
|
+
function modeMessage(layers, dataWords, compact) {
|
|
7683
|
+
const raw = new BitWriter();
|
|
7684
|
+
if (compact) {
|
|
7685
|
+
raw.put(layers - 1, 2);
|
|
7686
|
+
raw.put(dataWords - 1, 6);
|
|
7687
|
+
return addCheckWords(raw, 28, 4, fieldForWordSize(4)).bits;
|
|
7688
|
+
}
|
|
7689
|
+
raw.put(layers - 1, 5);
|
|
7690
|
+
raw.put(dataWords - 1, 11);
|
|
7691
|
+
return addCheckWords(raw, 40, 4, fieldForWordSize(4)).bits;
|
|
7692
|
+
}
|
|
7693
|
+
|
|
7694
|
+
/** @param {BitMatrix} matrix @param {number} center @param {number} size */
|
|
7695
|
+
function drawBullsEye(matrix, center, size) {
|
|
7696
|
+
for (let ring = 0; ring < size; ring += 2) {
|
|
7697
|
+
for (let p = center - ring; p <= center + ring; p++) {
|
|
7698
|
+
matrix.set(p, center - ring); matrix.set(p, center + ring);
|
|
7699
|
+
matrix.set(center - ring, p); matrix.set(center + ring, p);
|
|
7700
|
+
}
|
|
7701
|
+
}
|
|
7702
|
+
matrix.set(center - size, center - size);
|
|
7703
|
+
matrix.set(center - size + 1, center - size);
|
|
7704
|
+
matrix.set(center - size, center - size + 1);
|
|
7705
|
+
matrix.set(center + size, center - size);
|
|
7706
|
+
matrix.set(center + size, center - size + 1);
|
|
7707
|
+
matrix.set(center + size, center + size - 1);
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
/** @param {BitMatrix} matrix @param {BitWriter} message @param {boolean} compact @param {number} center */
|
|
7711
|
+
function drawModeMessage(matrix, message, compact, center) {
|
|
7712
|
+
if (compact) {
|
|
7713
|
+
for (let i = 0; i < 7; i++) {
|
|
7714
|
+
const offset = center - 3 + i;
|
|
7715
|
+
if (bitAt(message, i)) matrix.set(offset, center - 5);
|
|
7716
|
+
if (bitAt(message, i + 7)) matrix.set(center + 5, offset);
|
|
7717
|
+
if (bitAt(message, 20 - i)) matrix.set(offset, center + 5);
|
|
7718
|
+
if (bitAt(message, 27 - i)) matrix.set(center - 5, offset);
|
|
7719
|
+
}
|
|
7720
|
+
} else {
|
|
7721
|
+
for (let i = 0; i < 10; i++) {
|
|
7722
|
+
const offset = center - 5 + i + Math.floor(i / 5);
|
|
7723
|
+
if (bitAt(message, i)) matrix.set(offset, center - 7);
|
|
7724
|
+
if (bitAt(message, i + 10)) matrix.set(center + 7, offset);
|
|
7725
|
+
if (bitAt(message, 29 - i)) matrix.set(offset, center + 7);
|
|
7726
|
+
if (bitAt(message, 39 - i)) matrix.set(center - 7, offset);
|
|
7727
|
+
}
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
6327
7730
|
|
|
6328
|
-
|
|
6329
|
-
|
|
7731
|
+
/**
|
|
7732
|
+
* Lay low-level bits in the four-sided, inward Aztec spiral.
|
|
7733
|
+
* @param {BitWriter} bits @param {{layers:number,compact:boolean,baseMatrixSize:number,symbolSize:number}} symbol
|
|
7734
|
+
* @returns {BitMatrix}
|
|
7735
|
+
*/
|
|
7736
|
+
function buildAztecMatrix(bits, symbol) {
|
|
7737
|
+
const { layers, compact, baseMatrixSize, symbolSize } = symbol;
|
|
7738
|
+
const matrix = new BitMatrix(symbolSize);
|
|
7739
|
+
const alignment = new Int32Array(baseMatrixSize);
|
|
7740
|
+
const center = Math.floor(symbolSize / 2);
|
|
6330
7741
|
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
7742
|
+
if (compact) {
|
|
7743
|
+
for (let i = 0; i < baseMatrixSize; i++) alignment[i] = i;
|
|
7744
|
+
} else {
|
|
7745
|
+
const originalCenter = Math.floor(baseMatrixSize / 2);
|
|
7746
|
+
for (let i = 0; i < originalCenter; i++) {
|
|
7747
|
+
const offset = i + Math.floor(i / 15);
|
|
7748
|
+
alignment[originalCenter - i - 1] = center - offset - 1;
|
|
7749
|
+
alignment[originalCenter + i] = center + offset + 1;
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
|
|
7753
|
+
let bit = 0;
|
|
7754
|
+
for (let layer = 0; layer < layers; layer++) {
|
|
7755
|
+
const rowSize = (layers - layer) * 4 + (compact ? 9 : 12);
|
|
7756
|
+
const low = layer * 2;
|
|
7757
|
+
const high = baseMatrixSize - 1 - low;
|
|
7758
|
+
for (let j = 0; j < rowSize; j++) {
|
|
7759
|
+
const offset = j * 2;
|
|
7760
|
+
for (let k = 0; k < 2; k++) {
|
|
7761
|
+
if (bitAt(bits, bit + offset + k)) matrix.set(alignment[low + k], alignment[low + j]);
|
|
7762
|
+
if (bitAt(bits, bit + rowSize * 2 + offset + k)) matrix.set(alignment[low + j], alignment[high - k]);
|
|
7763
|
+
if (bitAt(bits, bit + rowSize * 4 + offset + k)) matrix.set(alignment[high - k], alignment[high - j]);
|
|
7764
|
+
if (bitAt(bits, bit + rowSize * 6 + offset + k)) matrix.set(alignment[high - j], alignment[low + k]);
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
bit += rowSize * 8;
|
|
7768
|
+
}
|
|
7769
|
+
if (bit !== bits.length) throw new EncodeError(`Aztec: layout consumed ${bit} of ${bits.length} bits`);
|
|
6334
7770
|
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
7771
|
+
const mode = modeMessage(layers, symbol.dataWords, compact);
|
|
7772
|
+
drawModeMessage(matrix, mode, compact, center);
|
|
7773
|
+
drawBullsEye(matrix, center, compact ? 5 : 7);
|
|
7774
|
+
|
|
7775
|
+
if (!compact) {
|
|
7776
|
+
for (let i = 0, offset = 0; i < Math.floor(baseMatrixSize / 2) - 1; i += 15, offset += 16) {
|
|
7777
|
+
for (let p = center & 1; p < symbolSize; p += 2) {
|
|
7778
|
+
matrix.set(center - offset, p); matrix.set(center + offset, p);
|
|
7779
|
+
matrix.set(p, center - offset); matrix.set(p, center + offset);
|
|
6338
7780
|
}
|
|
6339
7781
|
}
|
|
6340
7782
|
}
|
|
7783
|
+
return matrix;
|
|
7784
|
+
}
|
|
6341
7785
|
|
|
6342
|
-
|
|
6343
|
-
|
|
7786
|
+
/** @param {number | undefined} layers @param {boolean | undefined} compact */
|
|
7787
|
+
function candidates(layers, compact) {
|
|
7788
|
+
if (layers !== undefined) {
|
|
7789
|
+
if (!Number.isInteger(layers) || layers < 1 || layers > 32) throw new EncodeError('Aztec: layers must be an integer 1..32');
|
|
7790
|
+
if (compact === true && layers > 4) throw new EncodeError('Aztec: compact symbols support layers 1..4');
|
|
7791
|
+
return [aztecLayer(layers, compact === true)];
|
|
7792
|
+
}
|
|
7793
|
+
if (compact === true) return AZTEC_COMPACT_LAYERS;
|
|
7794
|
+
if (compact === false) return AZTEC_FULL_LAYERS;
|
|
7795
|
+
return [...AZTEC_COMPACT_LAYERS, ...AZTEC_FULL_LAYERS];
|
|
6344
7796
|
}
|
|
6345
7797
|
|
|
6346
7798
|
/**
|
|
6347
|
-
*
|
|
7799
|
+
* Encode a UTF-8 string or bytes into an Aztec Code matrix.
|
|
6348
7800
|
*
|
|
6349
|
-
* @param {
|
|
6350
|
-
* @param {
|
|
6351
|
-
* @
|
|
6352
|
-
* @returns {Detection}
|
|
7801
|
+
* @param {string|ArrayBuffer|ArrayBufferView} value
|
|
7802
|
+
* @param {{layers?:number,compact?:boolean,eccPercent?:number,charset?:'utf-8'}} [options]
|
|
7803
|
+
* @returns {BitMatrix & {format?:string,layers?:number,compact?:boolean,eccPercent?:number,dataCodewords?:number}}
|
|
6353
7804
|
*/
|
|
6354
|
-
function
|
|
6355
|
-
const
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
const
|
|
7805
|
+
function encodeAztec(value, options = {}) {
|
|
7806
|
+
const eccPercent = options.eccPercent ?? 23;
|
|
7807
|
+
if (!Number.isFinite(eccPercent) || eccPercent < 5 || eccPercent > 95) {
|
|
7808
|
+
throw new EncodeError('Aztec: eccPercent must be between 5 and 95');
|
|
7809
|
+
}
|
|
7810
|
+
const high = encodeHighLevel(value, { charset: options.charset ?? 'utf-8' });
|
|
7811
|
+
for (const candidate of candidates(options.layers, options.compact)) {
|
|
7812
|
+
if (!candidate) continue;
|
|
7813
|
+
const wordSize = wordSizeForLayers(candidate.layers);
|
|
7814
|
+
const stuffed = stuffBits(high, wordSize);
|
|
7815
|
+
const dataWords = Math.ceil(stuffed.length / wordSize);
|
|
7816
|
+
const eccWords = eccCodewordsFor(dataWords, eccPercent);
|
|
7817
|
+
if (dataWords > candidate.maxDataCodewords || dataWords + eccWords > candidate.totalCodewords) continue;
|
|
7818
|
+
const checked = addCheckWords(stuffed, candidate.totalBits, wordSize, fieldForLayers(candidate.layers));
|
|
7819
|
+
// `addCheckWords` uses every remaining word as parity. This is stronger
|
|
7820
|
+
// than the requested percentage, never weaker, and canonical for a chosen
|
|
7821
|
+
// layer/data-word combination.
|
|
7822
|
+
const symbol = { ...candidate, dataWords: checked.dataWords };
|
|
7823
|
+
const matrix = buildAztecMatrix(checked.bits, symbol);
|
|
7824
|
+
matrix.format = 'aztec'; matrix.layers = candidate.layers; matrix.compact = candidate.compact;
|
|
7825
|
+
matrix.eccPercent = Math.round(checked.eccWords * wordSize * 100 / Math.max(1, stuffed.length));
|
|
7826
|
+
matrix.dataCodewords = checked.dataWords;
|
|
7827
|
+
return matrix;
|
|
7828
|
+
}
|
|
7829
|
+
throw new EncodeError('Aztec: payload does not fit the requested layers and error correction');
|
|
7830
|
+
}
|
|
6360
7831
|
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
7832
|
+
__exports.stuffBits = stuffBits;
|
|
7833
|
+
__exports.addCheckWords = addCheckWords;
|
|
7834
|
+
__exports.modeMessage = modeMessage;
|
|
7835
|
+
__exports.buildAztecMatrix = buildAztecMatrix;
|
|
7836
|
+
__exports.encodeAztec = encodeAztec;
|
|
7837
|
+
};
|
|
6364
7838
|
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
7839
|
+
__modules["aztec/decoder.js"] = function (__require, __exports) {
|
|
7840
|
+
/**
|
|
7841
|
+
* Decoder for a sampled Aztec symbol.
|
|
7842
|
+
*
|
|
7843
|
+
* This module deliberately accepts only a square, module-aligned BitMatrix.
|
|
7844
|
+
* Locating a bull's-eye in a photograph and perspective sampling are detector
|
|
7845
|
+
* concerns. Keeping the two stages apart makes all bit order and ECC rules
|
|
7846
|
+
* testable without image-processing noise.
|
|
7847
|
+
*
|
|
7848
|
+
* Contract with tables.js:
|
|
7849
|
+
* - aztecSymbolForLayers(compact, layers) returns the nominal symbol data;
|
|
7850
|
+
* - aztecWordSizeForLayers(layers) returns 6, 8, 10 or 12;
|
|
7851
|
+
* - aztecFieldForLayers(layers) returns the matching binary Galois field;
|
|
7852
|
+
* - aztecMatrixSize(compact, layers) returns the rendered square size.
|
|
7853
|
+
*
|
|
7854
|
+
* @module aztec/decoder
|
|
7855
|
+
*/
|
|
7856
|
+
const { FormatError } = __require("core/errors.js");
|
|
7857
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
7858
|
+
const { aztecLayer: aztecSymbolForLayers, wordSizeForLayers: aztecWordSizeForLayers, fieldForLayers: aztecFieldForLayers, fieldForWordSize, aztecSymbolSize: aztecMatrixSize } = __require("aztec/tables.js");
|
|
6368
7859
|
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
};
|
|
7860
|
+
const UPPER = ['CTRL_PS', ' ', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'CTRL_LL', 'CTRL_ML', 'CTRL_DL', 'CTRL_BS'];
|
|
7861
|
+
const LOWER = ['CTRL_PS', ' ', ...'abcdefghijklmnopqrstuvwxyz', 'CTRL_US', 'CTRL_ML', 'CTRL_DL', 'CTRL_BS'];
|
|
7862
|
+
const MIXED = [
|
|
7863
|
+
'CTRL_PS', ' ', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\b', '\t', '\n', '\x0b', '\f', '\r', '\x1b',
|
|
7864
|
+
'\x1c', '\x1d', '\x1e', '\x1f', '@', '\\', '^', '_', '`', '|', '~', '\x7f', 'CTRL_LL', 'CTRL_UL', 'CTRL_PL', 'CTRL_BS',
|
|
7865
|
+
];
|
|
7866
|
+
const PUNCT = ['FLG(n)', '\r', '\r\n', '. ', ', ', ': ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', ']', '{', '}', 'CTRL_UL'];
|
|
7867
|
+
const DIGIT = ['CTRL_PS', ' ', ...'0123456789', ',', '.', 'CTRL_UL'];
|
|
7868
|
+
const TABLES = { UPPER, LOWER, MIXED, PUNCT, DIGIT };
|
|
6379
7869
|
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
7870
|
+
/** @param {boolean[]} bits @param {number} offset @param {number} count */
|
|
7871
|
+
function readBits(bits, offset, count) {
|
|
7872
|
+
if (offset + count > bits.length) throw new FormatError('Aztec: truncated high-level stream');
|
|
7873
|
+
let value = 0;
|
|
7874
|
+
for (let i = 0; i < count; i++) value = (value << 1) | (bits[offset + i] ? 1 : 0);
|
|
7875
|
+
return value;
|
|
7876
|
+
}
|
|
7877
|
+
|
|
7878
|
+
/** @param {number} value @param {number} count @param {boolean[]} out */
|
|
7879
|
+
function appendBits(value, count, out) {
|
|
7880
|
+
for (let i = count - 1; i >= 0; i--) out.push(((value >>> i) & 1) !== 0);
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
/**
|
|
7884
|
+
* Decode an Aztec high-level bit stream to its exact byte payload.
|
|
7885
|
+
*
|
|
7886
|
+
* Text tables contribute their ISO-8859-1 byte values; Binary Shift appends
|
|
7887
|
+
* raw bytes. ECI markers are consumed but intentionally not emitted: callers
|
|
7888
|
+
* receive the transported byte payload and may select their own charset.
|
|
7889
|
+
*
|
|
7890
|
+
* @param {boolean[]} bits
|
|
7891
|
+
* @returns {Uint8Array}
|
|
7892
|
+
*/
|
|
7893
|
+
function decodeHighLevelBits(bits) {
|
|
7894
|
+
const output = [];
|
|
7895
|
+
let latch = 'UPPER';
|
|
7896
|
+
let shift = 'UPPER';
|
|
7897
|
+
let offset = 0;
|
|
7898
|
+
|
|
7899
|
+
while (offset < bits.length) {
|
|
7900
|
+
if (shift === 'BINARY') {
|
|
7901
|
+
if (offset + 5 > bits.length) break; // legal trailing pad
|
|
7902
|
+
let length = readBits(bits, offset, 5);
|
|
7903
|
+
offset += 5;
|
|
7904
|
+
if (length === 0) {
|
|
7905
|
+
if (offset + 11 > bits.length) throw new FormatError('Aztec: truncated Binary Shift length');
|
|
7906
|
+
length = readBits(bits, offset, 11) + 31;
|
|
7907
|
+
offset += 11;
|
|
7908
|
+
}
|
|
7909
|
+
if (offset + length * 8 > bits.length) throw new FormatError('Aztec: truncated Binary Shift data');
|
|
7910
|
+
for (let i = 0; i < length; i++) {
|
|
7911
|
+
output.push(readBits(bits, offset, 8));
|
|
7912
|
+
offset += 8;
|
|
7913
|
+
}
|
|
7914
|
+
shift = latch;
|
|
7915
|
+
continue;
|
|
7916
|
+
}
|
|
7917
|
+
|
|
7918
|
+
const size = shift === 'DIGIT' ? 4 : 5;
|
|
7919
|
+
if (offset + size > bits.length) break; // trailing pad after unstuffing
|
|
7920
|
+
const code = readBits(bits, offset, size);
|
|
7921
|
+
offset += size;
|
|
7922
|
+
const table = TABLES[shift];
|
|
7923
|
+
const token = table[code];
|
|
7924
|
+
if (token === undefined) throw new FormatError(`Aztec: invalid ${shift} code ${code}`);
|
|
7925
|
+
|
|
7926
|
+
if (token === 'FLG(n)') {
|
|
7927
|
+
if (offset + 3 > bits.length) throw new FormatError('Aztec: truncated FLG(n)');
|
|
7928
|
+
const count = readBits(bits, offset, 3);
|
|
7929
|
+
offset += 3;
|
|
7930
|
+
if (count === 0) output.push(0x1d); // FNC1 / GS
|
|
7931
|
+
else if (count <= 6) {
|
|
7932
|
+
// ECI assignment number, encoded as count decimal digits. It changes
|
|
7933
|
+
// interpretation, not the wire bytes, so consume it without output.
|
|
7934
|
+
for (let i = 0; i < count; i++) {
|
|
7935
|
+
if (offset + 4 > bits.length) throw new FormatError('Aztec: truncated ECI');
|
|
7936
|
+
const digit = readBits(bits, offset, 4);
|
|
7937
|
+
offset += 4;
|
|
7938
|
+
if (digit < 2 || digit > 11) throw new FormatError('Aztec: invalid ECI digit');
|
|
7939
|
+
}
|
|
7940
|
+
} else {
|
|
7941
|
+
throw new FormatError(`Aztec: unsupported FLG(${count})`);
|
|
7942
|
+
}
|
|
7943
|
+
shift = latch;
|
|
7944
|
+
continue;
|
|
7945
|
+
}
|
|
7946
|
+
|
|
7947
|
+
if (token.startsWith('CTRL_')) {
|
|
7948
|
+
const targetCode = token.slice(5, -1);
|
|
7949
|
+
const latchMode = token.endsWith('L');
|
|
7950
|
+
const target = ({ P: 'PUNCT', L: 'LOWER', M: 'MIXED', D: 'DIGIT', U: 'UPPER', B: 'BINARY' })[targetCode];
|
|
7951
|
+
if (!target) throw new FormatError(`Aztec: invalid control ${token}`);
|
|
7952
|
+
shift = target;
|
|
7953
|
+
if (latchMode) latch = shift;
|
|
7954
|
+
continue;
|
|
6387
7955
|
}
|
|
7956
|
+
|
|
7957
|
+
for (let i = 0; i < token.length; i++) output.push(token.charCodeAt(i));
|
|
7958
|
+
shift = latch;
|
|
6388
7959
|
}
|
|
7960
|
+
return Uint8Array.from(output);
|
|
7961
|
+
}
|
|
6389
7962
|
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
7963
|
+
/** @param {boolean} compact @param {number} layers */
|
|
7964
|
+
function alignmentMap(compact, layers) {
|
|
7965
|
+
const baseSize = (compact ? 11 : 14) + layers * 4;
|
|
7966
|
+
if (compact) return Array.from({ length: baseSize }, (_, i) => i);
|
|
7967
|
+
const size = aztecMatrixSize(layers, false);
|
|
7968
|
+
const map = new Array(baseSize);
|
|
7969
|
+
const baseCenter = baseSize >> 1;
|
|
7970
|
+
const center = size >> 1;
|
|
7971
|
+
for (let i = 0; i < baseCenter; i++) {
|
|
7972
|
+
const offset = i + Math.floor(i / 15);
|
|
7973
|
+
map[baseCenter - i - 1] = center - offset - 1;
|
|
7974
|
+
map[baseCenter + i] = center + offset + 1;
|
|
7975
|
+
}
|
|
7976
|
+
return map;
|
|
7977
|
+
}
|
|
7978
|
+
|
|
7979
|
+
/**
|
|
7980
|
+
* Read the four sides of the parameter message. The order mirrors the
|
|
7981
|
+
* clockwise write order and is independent of the data spiral.
|
|
7982
|
+
*
|
|
7983
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
7984
|
+
* @param {boolean} compact
|
|
7985
|
+
* @returns {boolean[]}
|
|
7986
|
+
*/
|
|
7987
|
+
function readModeBits(matrix, compact) {
|
|
7988
|
+
const center = matrix.width >> 1;
|
|
7989
|
+
const side = compact ? 7 : 10;
|
|
7990
|
+
const offset = compact ? 5 : 7;
|
|
7991
|
+
// Full symbols skip the reference grid line through the bull's-eye. This
|
|
7992
|
+
// exact sequence is also used by drawModeMessage() in encoder.js.
|
|
7993
|
+
const positions = Array.from(
|
|
7994
|
+
{ length: side },
|
|
7995
|
+
(_, i) => compact ? center - 3 + i : center - 5 + i + Math.floor(i / 5),
|
|
6393
7996
|
);
|
|
7997
|
+
const bits = [];
|
|
7998
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(positions[i], center - offset));
|
|
7999
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(center + offset, positions[i]));
|
|
8000
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(positions[side - 1 - i], center + offset));
|
|
8001
|
+
for (let i = 0; i < side; i++) bits.push(matrix.get(center - offset, positions[side - 1 - i]));
|
|
8002
|
+
return bits;
|
|
8003
|
+
}
|
|
6394
8004
|
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
8005
|
+
/** @param {boolean[]} bits @param {boolean} compact */
|
|
8006
|
+
function decodeModeMessage(bits, compact) {
|
|
8007
|
+
const total = compact ? 7 : 10;
|
|
8008
|
+
const dataWords = compact ? 2 : 4;
|
|
8009
|
+
const words = new Array(total);
|
|
8010
|
+
for (let i = 0; i < total; i++) words[i] = readBits(bits, i * 4, 4);
|
|
8011
|
+
const corrections = rsDecode(words, total - dataWords, fieldForWordSize(4), 1);
|
|
8012
|
+
let data = 0;
|
|
8013
|
+
for (let i = 0; i < dataWords; i++) data = (data << 4) | words[i];
|
|
8014
|
+
const layers = compact ? (data >>> 6) + 1 : (data >>> 11) + 1;
|
|
8015
|
+
const dataCodewords = compact ? (data & 0x3f) + 1 : (data & 0x7ff) + 1;
|
|
8016
|
+
return { layers, dataCodewords, corrections };
|
|
8017
|
+
}
|
|
6401
8018
|
|
|
6402
|
-
|
|
8019
|
+
/** @param {boolean} compact @param {number} layers */
|
|
8020
|
+
function totalBitsInLayers(compact, layers) {
|
|
8021
|
+
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
|
6403
8022
|
}
|
|
6404
8023
|
|
|
6405
8024
|
/**
|
|
6406
|
-
*
|
|
8025
|
+
* Extract raw, stuffed codeword bits in logical ring order.
|
|
8026
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
8027
|
+
* @param {boolean} compact @param {number} layers
|
|
8028
|
+
* @returns {boolean[]}
|
|
8029
|
+
*/
|
|
8030
|
+
function extractBits(matrix, compact, layers) {
|
|
8031
|
+
const baseSize = (compact ? 11 : 14) + layers * 4;
|
|
8032
|
+
const map = alignmentMap(compact, layers);
|
|
8033
|
+
const raw = new Array(totalBitsInLayers(compact, layers));
|
|
8034
|
+
let offset = 0;
|
|
8035
|
+
for (let layer = 0; layer < layers; layer++) {
|
|
8036
|
+
const rowSize = (layers - layer) * 4 + (compact ? 9 : 12);
|
|
8037
|
+
for (let j = 0; j < rowSize; j++) {
|
|
8038
|
+
const col = j * 2;
|
|
8039
|
+
for (let k = 0; k < 2; k++) {
|
|
8040
|
+
raw[offset + col + k] = matrix.get(map[layer * 2 + k], map[layer * 2 + j]);
|
|
8041
|
+
raw[offset + rowSize * 2 + col + k] = matrix.get(map[layer * 2 + j], map[baseSize - 1 - layer * 2 - k]);
|
|
8042
|
+
raw[offset + rowSize * 4 + col + k] = matrix.get(map[baseSize - 1 - layer * 2 - k], map[baseSize - 1 - layer * 2 - j]);
|
|
8043
|
+
raw[offset + rowSize * 6 + col + k] = matrix.get(map[baseSize - 1 - layer * 2 - j], map[layer * 2 + k]);
|
|
8044
|
+
}
|
|
8045
|
+
}
|
|
8046
|
+
offset += rowSize * 8;
|
|
8047
|
+
}
|
|
8048
|
+
return raw;
|
|
8049
|
+
}
|
|
8050
|
+
|
|
8051
|
+
/** @param {boolean[]} raw @param {number} layers @param {number} dataCodewords */
|
|
8052
|
+
function correctAndUnstuff(raw, layers, dataCodewords) {
|
|
8053
|
+
const wordSize = aztecWordSizeForLayers(layers);
|
|
8054
|
+
const totalWords = Math.floor(raw.length / wordSize);
|
|
8055
|
+
if (dataCodewords <= 0 || dataCodewords > totalWords) throw new FormatError('Aztec: invalid data word count');
|
|
8056
|
+
const start = raw.length % wordSize;
|
|
8057
|
+
const words = new Array(totalWords);
|
|
8058
|
+
for (let i = 0; i < totalWords; i++) words[i] = readBits(raw, start + i * wordSize, wordSize);
|
|
8059
|
+
const corrections = rsDecode(words, totalWords - dataCodewords, aztecFieldForLayers(layers), 1);
|
|
8060
|
+
const mask = (1 << wordSize) - 1;
|
|
8061
|
+
const corrected = [];
|
|
8062
|
+
for (let i = 0; i < dataCodewords; i++) {
|
|
8063
|
+
const word = words[i];
|
|
8064
|
+
if (word === 0 || word === mask) throw new FormatError('Aztec: invalid stuffed codeword');
|
|
8065
|
+
if (word === 1 || word === mask - 1) {
|
|
8066
|
+
for (let j = 0; j < wordSize - 1; j++) corrected.push(word === mask - 1);
|
|
8067
|
+
} else {
|
|
8068
|
+
appendBits(word, wordSize, corrected);
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
return { bits: corrected, corrections };
|
|
8072
|
+
}
|
|
8073
|
+
|
|
8074
|
+
/** @param {Uint8Array} bytes */
|
|
8075
|
+
function bytesToText(bytes) {
|
|
8076
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
|
|
8077
|
+
catch { return new TextDecoder('latin1').decode(bytes); }
|
|
8078
|
+
}
|
|
8079
|
+
|
|
8080
|
+
/**
|
|
8081
|
+
* Decode a square Aztec symbol with one bit per module and no quiet zone.
|
|
8082
|
+
* The matrix must already be oriented with the mode message at the top.
|
|
6407
8083
|
*
|
|
6408
|
-
* @param {
|
|
6409
|
-
* @returns {
|
|
8084
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
8085
|
+
* @returns {{text: string, bytes: Uint8Array, compact: boolean, layers: number, corrections: number, eccPercent: number}}
|
|
6410
8086
|
*/
|
|
6411
|
-
function
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
]
|
|
8087
|
+
function decodeAztec(matrix) {
|
|
8088
|
+
if (!matrix || matrix.width !== matrix.height) throw new FormatError('Aztec: expected a square BitMatrix');
|
|
8089
|
+
let compact;
|
|
8090
|
+
let mode;
|
|
8091
|
+
// Compact and full dimensions are disjoint; trying both also makes malformed
|
|
8092
|
+
// candidate handling deterministic for the future image detector.
|
|
8093
|
+
for (const candidate of [true, false]) {
|
|
8094
|
+
try {
|
|
8095
|
+
const value = decodeModeMessage(readModeBits(matrix, candidate), candidate);
|
|
8096
|
+
if (value.layers < 1 || value.layers > (candidate ? 4 : 32)) continue;
|
|
8097
|
+
if (aztecMatrixSize(value.layers, candidate) !== matrix.width) continue;
|
|
8098
|
+
compact = candidate;
|
|
8099
|
+
mode = value;
|
|
8100
|
+
break;
|
|
8101
|
+
} catch { /* Try the other family. */ }
|
|
8102
|
+
}
|
|
8103
|
+
if (compact === undefined || !mode) throw new FormatError('Aztec: invalid mode message or dimensions');
|
|
8104
|
+
// Ensure the declared layer data agrees with the table module, so a future
|
|
8105
|
+
// tables refactor cannot silently make decoder capacity calculations stale.
|
|
8106
|
+
aztecSymbolForLayers(mode.layers, compact);
|
|
8107
|
+
const raw = extractBits(matrix, compact, mode.layers);
|
|
8108
|
+
const payload = correctAndUnstuff(raw, mode.layers, mode.dataCodewords);
|
|
8109
|
+
const bytes = decodeHighLevelBits(payload.bits);
|
|
8110
|
+
const totalWords = Math.floor(raw.length / aztecWordSizeForLayers(mode.layers));
|
|
8111
|
+
return {
|
|
8112
|
+
text: bytesToText(bytes),
|
|
8113
|
+
bytes,
|
|
8114
|
+
compact,
|
|
8115
|
+
layers: mode.layers,
|
|
8116
|
+
corrections: mode.corrections + payload.corrections,
|
|
8117
|
+
eccPercent: Math.round(((totalWords - mode.dataCodewords) * 100) / totalWords),
|
|
8118
|
+
};
|
|
6418
8119
|
}
|
|
6419
8120
|
|
|
8121
|
+
__exports.decodeHighLevelBits = decodeHighLevelBits;
|
|
8122
|
+
__exports.decodeAztec = decodeAztec;
|
|
8123
|
+
};
|
|
8124
|
+
|
|
8125
|
+
__modules["aztec/detector.js"] = function (__require, __exports) {
|
|
6420
8126
|
/**
|
|
6421
|
-
*
|
|
8127
|
+
* Aztec image detection.
|
|
6422
8128
|
*
|
|
6423
|
-
*
|
|
6424
|
-
*
|
|
6425
|
-
*
|
|
6426
|
-
*
|
|
6427
|
-
*
|
|
6428
|
-
*
|
|
8129
|
+
* Aztec has no finder pattern at its outer border. Its reliable geometric
|
|
8130
|
+
* anchor is instead the alternating square bull's-eye in the centre: five
|
|
8131
|
+
* rings in Compact symbols, seven rings in Full symbols. The detector finds
|
|
8132
|
+
* isolated central modules, verifies those rings at module centres, then
|
|
8133
|
+
* samples each legal symbol dimension. The decoder is deliberately the final
|
|
8134
|
+
* arbiter: its mode-message Reed--Solomon check rejects accidental concentric
|
|
8135
|
+
* artwork and tells us which of the compact/full dimensions is real.
|
|
6429
8136
|
*
|
|
6430
|
-
*
|
|
6431
|
-
*
|
|
6432
|
-
*
|
|
8137
|
+
* Sampling uses a quadrilateral, not a cropped bitmap, so the detected
|
|
8138
|
+
* rotation is corrected before decoding. The ring search covers arbitrary
|
|
8139
|
+
* in-plane rotations (four-degree coarse search; at normal camera scales its
|
|
8140
|
+
* positional error remains well inside a module). The optional inverse pass
|
|
8141
|
+
* supports light modules on a dark field.
|
|
8142
|
+
*
|
|
8143
|
+
* @module aztec/detector
|
|
6433
8144
|
*/
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
8145
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
8146
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
8147
|
+
const { decodeAztec } = __require("aztec/decoder.js");
|
|
8148
|
+
|
|
8149
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
8150
|
+
/** @typedef {{corners: Point[], dimension: number, compact: boolean, moduleSize: number, matrix: import('../core/bit-matrix.js').BitMatrix}} Detection */
|
|
8151
|
+
|
|
8152
|
+
// Compact: 11 + 4 layers. Full symbols add reference-grid rows/columns every
|
|
8153
|
+
// 15 modules measured from their central 14-module base, not every 15 layers.
|
|
8154
|
+
const DIMENSIONS = [
|
|
8155
|
+
...[1, 2, 3, 4].map((layers) => ({ compact: true, dimension: 11 + 4 * layers })),
|
|
8156
|
+
...Array.from({ length: 32 }, (_, index) => {
|
|
8157
|
+
const layers = index + 1;
|
|
8158
|
+
return { compact: false, dimension: 15 + 4 * layers + 2 * Math.floor((2 * layers + 6) / 15) };
|
|
8159
|
+
}),
|
|
8160
|
+
];
|
|
8161
|
+
|
|
8162
|
+
function pixel(image, x, y) {
|
|
8163
|
+
const ix = Math.round(x);
|
|
8164
|
+
const iy = Math.round(y);
|
|
8165
|
+
return ix >= 0 && iy >= 0 && ix < image.width && iy < image.height && image.get(ix, iy);
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
/** Connected components of either polarity, retaining only plausible modules. */
|
|
8169
|
+
function components(image, value) {
|
|
8170
|
+
const seen = new Uint8Array(image.width * image.height);
|
|
8171
|
+
const out = [];
|
|
8172
|
+
const maximumArea = Math.max(4, Math.floor(image.width * image.height * 0.08));
|
|
8173
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
8174
|
+
const start = y * image.width + x;
|
|
8175
|
+
if (seen[start] || image.get(x, y) !== value) continue;
|
|
8176
|
+
const xs = [x];
|
|
8177
|
+
const ys = [y];
|
|
8178
|
+
seen[start] = 1;
|
|
8179
|
+
let head = 0;
|
|
8180
|
+
let minX = x; let maxX = x; let minY = y; let maxY = y;
|
|
8181
|
+
while (head < xs.length) {
|
|
8182
|
+
const px = xs[head]; const py = ys[head++];
|
|
8183
|
+
if (px < minX) minX = px; if (px > maxX) maxX = px;
|
|
8184
|
+
if (py < minY) minY = py; if (py > maxY) maxY = py;
|
|
8185
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
8186
|
+
if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height) continue;
|
|
8187
|
+
const at = ny * image.width + nx;
|
|
8188
|
+
if (!seen[at] && image.get(nx, ny) === value) {
|
|
8189
|
+
seen[at] = 1; xs.push(nx); ys.push(ny);
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
}
|
|
8193
|
+
const width = maxX - minX + 1;
|
|
8194
|
+
const height = maxY - minY + 1;
|
|
8195
|
+
const area = width * height;
|
|
8196
|
+
// The central module is solid and approximately square. This filter is
|
|
8197
|
+
// intentionally permissive because a rotated raster module is diamond-ish.
|
|
8198
|
+
if (xs.length <= maximumArea && Math.abs(width - height) <= Math.max(1, Math.ceil(Math.max(width, height) * 0.35)) &&
|
|
8199
|
+
xs.length >= area * 0.45) {
|
|
8200
|
+
out.push({ x: (minX + maxX) / 2, y: (minY + maxY) / 2, width, height, pixels: xs.length });
|
|
8201
|
+
}
|
|
6440
8202
|
}
|
|
8203
|
+
return out.sort((a, b) => b.pixels - a.pixels).slice(0, 2000);
|
|
8204
|
+
}
|
|
6441
8205
|
|
|
6442
|
-
|
|
6443
|
-
|
|
8206
|
+
function expectedDark(ring, inverted) {
|
|
8207
|
+
return inverted ? (ring & 1) === 1 : (ring & 1) === 0;
|
|
8208
|
+
}
|
|
6444
8209
|
|
|
6445
|
-
|
|
6446
|
-
|
|
8210
|
+
/** Score one square bull's-eye at an angle and a candidate module pitch. */
|
|
8211
|
+
function ringScore(image, centre, pitch, angle, inverted, rings) {
|
|
8212
|
+
const cos = Math.cos(angle);
|
|
8213
|
+
const sin = Math.sin(angle);
|
|
8214
|
+
let correct = 0;
|
|
8215
|
+
let total = 0;
|
|
8216
|
+
for (let ring = 0; ring < rings; ring++) {
|
|
8217
|
+
const wanted = expectedDark(ring, inverted);
|
|
8218
|
+
for (let j = -ring; j <= ring; j++) for (let i = -ring; i <= ring; i++) {
|
|
8219
|
+
if (ring && Math.abs(i) !== ring && Math.abs(j) !== ring) continue;
|
|
8220
|
+
const x = centre.x + (i * cos - j * sin) * pitch;
|
|
8221
|
+
const y = centre.y + (i * sin + j * cos) * pitch;
|
|
8222
|
+
if (pixel(image, x, y) === wanted) correct++;
|
|
8223
|
+
total++;
|
|
8224
|
+
}
|
|
8225
|
+
}
|
|
8226
|
+
return correct / total;
|
|
8227
|
+
}
|
|
6447
8228
|
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
8229
|
+
function rotateCorners(corners, turn) {
|
|
8230
|
+
return corners.slice(turn).concat(corners.slice(0, turn));
|
|
8231
|
+
}
|
|
6451
8232
|
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
}
|
|
6458
|
-
if (rotated) matrix.rotate180();
|
|
8233
|
+
function invert(matrix) {
|
|
8234
|
+
const out = matrix.clone();
|
|
8235
|
+
for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
|
|
8236
|
+
return out;
|
|
8237
|
+
}
|
|
6459
8238
|
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
8239
|
+
function cornersFor(centre, pitch, angle, dimension) {
|
|
8240
|
+
const half = dimension * pitch / 2;
|
|
8241
|
+
const cos = Math.cos(angle);
|
|
8242
|
+
const sin = Math.sin(angle);
|
|
8243
|
+
const point = (x, y) => ({ x: centre.x + x * cos - y * sin, y: centre.y + x * sin + y * cos });
|
|
8244
|
+
return [point(-half, -half), point(half, -half), point(half, half), point(-half, half)];
|
|
8245
|
+
}
|
|
8246
|
+
|
|
8247
|
+
/**
|
|
8248
|
+
* Find an Aztec symbol in a binarized image.
|
|
8249
|
+
*
|
|
8250
|
+
* The returned matrix is in the orientation accepted by the Aztec decoder.
|
|
8251
|
+
* A valid mode message is required before a geometric candidate is returned,
|
|
8252
|
+
* making false positives from decorative concentric squares very unlikely.
|
|
8253
|
+
*
|
|
8254
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
|
|
8255
|
+
* @returns {Detection | null}
|
|
8256
|
+
*/
|
|
8257
|
+
function detectAztec(binaryImage) {
|
|
8258
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
8259
|
+
throw new NotFoundError('detectAztec: no image supplied');
|
|
8260
|
+
}
|
|
8261
|
+
const candidates = [];
|
|
8262
|
+
for (const inverted of [false, true]) {
|
|
8263
|
+
for (const core of components(binaryImage, !inverted)) {
|
|
8264
|
+
// A non-rotated one-module component directly gives its pitch. For
|
|
8265
|
+
// rotated modules its bounding box grows by |sin| + |cos|, compensated
|
|
8266
|
+
// below for every tested angle.
|
|
8267
|
+
for (let degrees = 0; degrees < 180; degrees += 4) {
|
|
8268
|
+
const angle = degrees * Math.PI / 180;
|
|
8269
|
+
const scale = Math.abs(Math.cos(angle)) + Math.abs(Math.sin(angle));
|
|
8270
|
+
const pitch = ((core.width + core.height) / 2) / scale;
|
|
8271
|
+
if (pitch < 0.8) continue;
|
|
8272
|
+
// Test Full first: its seven rings also exclude Compact candidates.
|
|
8273
|
+
const fullScore = ringScore(binaryImage, core, pitch, angle, inverted, 7);
|
|
8274
|
+
const rings = fullScore >= 0.88 ? 7 : 5;
|
|
8275
|
+
const score = rings === 7 ? fullScore : ringScore(binaryImage, core, pitch, angle, inverted, 5);
|
|
8276
|
+
if (score < 0.91) continue;
|
|
8277
|
+
const symbolKinds = rings === 7 ? DIMENSIONS.filter((item) => !item.compact) : DIMENSIONS.filter((item) => item.compact);
|
|
8278
|
+
for (const kind of symbolKinds) {
|
|
8279
|
+
const baseCorners = cornersFor(core, pitch, angle, kind.dimension);
|
|
8280
|
+
for (let turn = 0; turn < 4; turn++) {
|
|
8281
|
+
const corners = rotateCorners(baseCorners, turn);
|
|
8282
|
+
let matrix;
|
|
8283
|
+
try { matrix = sampleQuad(binaryImage, kind.dimension, corners); } catch (e) { continue; }
|
|
8284
|
+
if (inverted) matrix = invert(matrix);
|
|
8285
|
+
try {
|
|
8286
|
+
// The decoder verifies the mode-message ECC and exact geometry.
|
|
8287
|
+
// We do not expose its result here so callers can use pure
|
|
8288
|
+
// detection without treating payload decoding as an API contract.
|
|
8289
|
+
decodeAztec(matrix);
|
|
8290
|
+
candidates.push({ corners, dimension: kind.dimension, compact: kind.compact,
|
|
8291
|
+
moduleSize: pitch, matrix, score });
|
|
8292
|
+
} catch (e) { /* Not an Aztec mode message at this dimension. */ }
|
|
8293
|
+
}
|
|
6467
8294
|
}
|
|
6468
|
-
break;
|
|
6469
|
-
} catch (e) {
|
|
6470
|
-
/* Try the next sampling strategy. */
|
|
6471
8295
|
}
|
|
6472
8296
|
}
|
|
6473
8297
|
}
|
|
6474
|
-
|
|
6475
|
-
|
|
8298
|
+
candidates.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
|
|
8299
|
+
const best = candidates[0];
|
|
8300
|
+
if (!best) return null;
|
|
8301
|
+
delete best.score;
|
|
8302
|
+
return best;
|
|
6476
8303
|
}
|
|
6477
8304
|
|
|
6478
|
-
__exports.detectQR = detectQR;
|
|
6479
|
-
__exports.detectAndDecodeQR = detectAndDecodeQR;
|
|
6480
|
-
};
|
|
6481
|
-
|
|
6482
|
-
__modules["qr/index.js"] = function (__require, __exports) {
|
|
6483
8305
|
/**
|
|
6484
|
-
*
|
|
6485
|
-
*
|
|
6486
|
-
* `QR_PLACEHOLDER` is deliberately absent: `src/index.js` probes for it to
|
|
6487
|
-
* decide whether this build can read and write QR, and its absence is what
|
|
6488
|
-
* reports the format as available.
|
|
8306
|
+
* Detect then decode an Aztec symbol. Detection failure is a normal result for
|
|
8307
|
+
* images without an Aztec code, therefore invalid candidates return null.
|
|
6489
8308
|
*
|
|
6490
|
-
* @
|
|
8309
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
8310
|
+
* @returns {(import('./decoder.js').DecodeResult & {corners: Point[]}) | null}
|
|
6491
8311
|
*/
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
8312
|
+
function detectAndDecodeAztec(binaryImage) {
|
|
8313
|
+
let detection;
|
|
8314
|
+
try { detection = detectAztec(binaryImage); } catch (e) { return null; }
|
|
8315
|
+
if (!detection) return null;
|
|
8316
|
+
try { return Object.assign({ corners: detection.corners }, decodeAztec(detection.matrix)); }
|
|
8317
|
+
catch (e) { return null; }
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
__exports.detectAztec = detectAztec;
|
|
8321
|
+
__exports.detectAndDecodeAztec = detectAndDecodeAztec;
|
|
8322
|
+
};
|
|
8323
|
+
|
|
8324
|
+
__modules["aztec/index.js"] = function (__require, __exports) {
|
|
8325
|
+
/** Aztec Code entry points. @module aztec */
|
|
8326
|
+
const __reexport0 = __require("aztec/encoder.js"); __exports.encodeAztec = __reexport0.encodeAztec;
|
|
8327
|
+
const __reexport1 = __require("aztec/decoder.js"); __exports.decodeAztec = __reexport1.decodeAztec;
|
|
8328
|
+
const __reexport2 = __require("aztec/detector.js"); __exports.detectAztec = __reexport2.detectAztec; __exports.detectAndDecodeAztec = __reexport2.detectAndDecodeAztec;
|
|
8329
|
+
const __reexport3 = __require("aztec/tables.js"); __exports.AZTEC_COMPACT_LAYERS = __reexport3.AZTEC_COMPACT_LAYERS; __exports.AZTEC_FULL_LAYERS = __reexport3.AZTEC_FULL_LAYERS; __exports.AZTEC_LAYERS = __reexport3.AZTEC_LAYERS; __exports.AZTEC_DEFAULT_ECC_PERCENT = __reexport3.AZTEC_DEFAULT_ECC_PERCENT; __exports.AZTEC_RS_GENERATOR_BASE = __reexport3.AZTEC_RS_GENERATOR_BASE; __exports.aztecLayer = __reexport3.aztecLayer; __exports.aztecSymbolSize = __reexport3.aztecSymbolSize; __exports.validateAztecTables = __reexport3.validateAztecTables;
|
|
6496
8330
|
|
|
6497
8331
|
|
|
6498
8332
|
};
|
|
@@ -7750,7 +9584,9 @@ const { LuminanceSource } = __require("image/luminance.js");
|
|
|
7750
9584
|
const { binarize } = __require("image/binarizer.js");
|
|
7751
9585
|
const { ONED_FORMATS } = __require("oned/index.js");
|
|
7752
9586
|
const { decodeOneD } = __require("oned/reader.js");
|
|
9587
|
+
const datamatrix = __require("datamatrix/index.js");
|
|
7753
9588
|
const qr = __require("qr/index.js");
|
|
9589
|
+
const aztec = __require("aztec/index.js");
|
|
7754
9590
|
__exports.BitMatrix = BitMatrix;
|
|
7755
9591
|
const __reexport0 = __require("core/errors.js"); __exports.BarcodeError = __reexport0.BarcodeError; __exports.EncodeError = __reexport0.EncodeError; __exports.NotFoundError = __reexport0.NotFoundError; __exports.FormatError = __reexport0.FormatError; __exports.ChecksumError = __reexport0.ChecksumError;
|
|
7756
9592
|
const __reexport1 = __require("image/luminance.js"); __exports.LuminanceSource = __reexport1.LuminanceSource;
|
|
@@ -7762,6 +9598,8 @@ const __reexport5 = __require("render/png.js"); __exports.toPNG = __reexport5.to
|
|
|
7762
9598
|
const __reexport6 = __require("render/index.js"); __exports.renderToCanvasAuto = __reexport6.renderToCanvasAuto; __exports.isWebGL2Available = __reexport6.isWebGL2Available;
|
|
7763
9599
|
const __reexport7 = __require("render/index.js"); __exports.renderToCanvasAutoAsync = __reexport7.renderToCanvasAutoAsync; __exports.isWebGPUAvailable = __reexport7.isWebGPUAvailable;
|
|
7764
9600
|
const __reexport8 = __require("qr/index.js"); __exports.encodeQR = __reexport8.encodeQR; __exports.decodeQR = __reexport8.decodeQR; __exports.detectQR = __reexport8.detectQR; __exports.detectAndDecodeQR = __reexport8.detectAndDecodeQR;
|
|
9601
|
+
const __reexport9 = __require("datamatrix/index.js"); __exports.encodeDataMatrix = __reexport9.encodeDataMatrix; __exports.decodeDataMatrix = __reexport9.decodeDataMatrix; __exports.detectDataMatrix = __reexport9.detectDataMatrix; __exports.detectAndDecodeDataMatrix = __reexport9.detectAndDecodeDataMatrix;
|
|
9602
|
+
const __reexport10 = __require("aztec/index.js"); __exports.encodeAztec = __reexport10.encodeAztec; __exports.decodeAztec = __reexport10.decodeAztec; __exports.detectAztec = __reexport10.detectAztec; __exports.detectAndDecodeAztec = __reexport10.detectAndDecodeAztec;
|
|
7765
9603
|
|
|
7766
9604
|
/**
|
|
7767
9605
|
* @typedef {object} FormatInfo
|
|
@@ -7785,6 +9623,10 @@ const qrCanEncode = qrPresent &&
|
|
|
7785
9623
|
typeof qr.encodeQR === 'function' && qr.QR_CAN_ENCODE !== false;
|
|
7786
9624
|
const qrCanDecode = qrPresent &&
|
|
7787
9625
|
typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
|
|
9626
|
+
const dataMatrixCanEncode = typeof datamatrix.encodeDataMatrix === 'function';
|
|
9627
|
+
const dataMatrixCanDecode = typeof datamatrix.detectAndDecodeDataMatrix === 'function';
|
|
9628
|
+
const aztecCanEncode = typeof aztec.encodeAztec === 'function';
|
|
9629
|
+
const aztecCanDecode = typeof aztec.detectAndDecodeAztec === 'function';
|
|
7788
9630
|
|
|
7789
9631
|
/**
|
|
7790
9632
|
* Every format this build supports.
|
|
@@ -7812,6 +9654,20 @@ function listFormats() {
|
|
|
7812
9654
|
canRead: qrCanDecode,
|
|
7813
9655
|
kind: /** @type {'2D'} */ ('2D'),
|
|
7814
9656
|
});
|
|
9657
|
+
formats.push({
|
|
9658
|
+
id: 'datamatrix',
|
|
9659
|
+
label: 'Data Matrix ECC 200',
|
|
9660
|
+
canWrite: dataMatrixCanEncode,
|
|
9661
|
+
canRead: dataMatrixCanDecode,
|
|
9662
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
9663
|
+
});
|
|
9664
|
+
formats.push({
|
|
9665
|
+
id: 'aztec',
|
|
9666
|
+
label: 'Aztec Code',
|
|
9667
|
+
canWrite: aztecCanEncode,
|
|
9668
|
+
canRead: aztecCanDecode,
|
|
9669
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
9670
|
+
});
|
|
7815
9671
|
|
|
7816
9672
|
return formats;
|
|
7817
9673
|
}
|
|
@@ -7832,6 +9688,9 @@ function listFormats() {
|
|
|
7832
9688
|
* @param {boolean} [options.checkDigit] Append a check digit, where optional.
|
|
7833
9689
|
* @param {boolean} [options.fullAscii] Code 39 extended encoding.
|
|
7834
9690
|
* @param {boolean} [options.gs1] Emit a leading FNC1.
|
|
9691
|
+
* @param {number} [options.layers] Aztec layer count; automatic if omitted.
|
|
9692
|
+
* @param {boolean} [options.compact] Force an Aztec Compact or Full symbol.
|
|
9693
|
+
* @param {number} [options.eccPercent] Requested Aztec error-correction percentage.
|
|
7835
9694
|
* @returns {BitMatrix}
|
|
7836
9695
|
*/
|
|
7837
9696
|
function encode(text, options = {}) {
|
|
@@ -7841,10 +9700,16 @@ function encode(text, options = {}) {
|
|
|
7841
9700
|
if (format === 'qr' || format === 'qrcode') {
|
|
7842
9701
|
return qr.encodeQR(value, options);
|
|
7843
9702
|
}
|
|
9703
|
+
if (format === 'datamatrix' || format === 'data-matrix') {
|
|
9704
|
+
return datamatrix.encodeDataMatrix(value, options);
|
|
9705
|
+
}
|
|
9706
|
+
if (format === 'aztec' || format === 'aztec-code') {
|
|
9707
|
+
return aztec.encodeAztec(value, options);
|
|
9708
|
+
}
|
|
7844
9709
|
|
|
7845
9710
|
const entry = ONED_FORMATS[format];
|
|
7846
9711
|
if (!entry) {
|
|
7847
|
-
const known = [...Object.keys(ONED_FORMATS), 'qr'].join(', ');
|
|
9712
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec'].join(', ');
|
|
7848
9713
|
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
7849
9714
|
}
|
|
7850
9715
|
return entry.encode(value, options);
|
|
@@ -7857,6 +9722,9 @@ function encode(text, options = {}) {
|
|
|
7857
9722
|
* @property {Uint8Array} [bytes] Raw payload, before text decoding.
|
|
7858
9723
|
* @property {number} [version] QR version.
|
|
7859
9724
|
* @property {string} [ecc] QR error-correction level.
|
|
9725
|
+
* @property {number} [layers] Aztec layer count.
|
|
9726
|
+
* @property {boolean} [compact] Whether an Aztec symbol is Compact.
|
|
9727
|
+
* @property {number} [corrections] Reed–Solomon corrections applied by an Aztec decode.
|
|
7860
9728
|
*/
|
|
7861
9729
|
|
|
7862
9730
|
/**
|
|
@@ -7877,6 +9745,8 @@ function decode(image, options = {}) {
|
|
|
7877
9745
|
const { formats = null, tryHarder = true, binarizer = 'auto' } = options;
|
|
7878
9746
|
const want = formats ? new Set(formats.map((f) => f.toLowerCase())) : null;
|
|
7879
9747
|
const wantQR = !want || want.has('qr') || want.has('qrcode');
|
|
9748
|
+
const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
|
|
9749
|
+
const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
|
|
7880
9750
|
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
7881
9751
|
|
|
7882
9752
|
const source = LuminanceSource.fromImageData(image);
|
|
@@ -7899,6 +9769,36 @@ function decode(image, options = {}) {
|
|
|
7899
9769
|
}
|
|
7900
9770
|
}
|
|
7901
9771
|
|
|
9772
|
+
if (wantDataMatrix && dataMatrixCanDecode) {
|
|
9773
|
+
// Hybrid thresholding can erase the interior of very large, perfectly
|
|
9774
|
+
// uniform modules. In auto mode keep the local-threshold attempt, then
|
|
9775
|
+
// retry Data Matrix once with the global threshold before giving up.
|
|
9776
|
+
const dataMatrixBits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
|
|
9777
|
+
for (const candidateBits of dataMatrixBits) {
|
|
9778
|
+
try {
|
|
9779
|
+
const found = datamatrix.detectAndDecodeDataMatrix(candidateBits);
|
|
9780
|
+
if (found) { results.push({ ...found, format: 'datamatrix' }); break; }
|
|
9781
|
+
} catch {
|
|
9782
|
+
/* no Data Matrix with this threshold */
|
|
9783
|
+
}
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
9786
|
+
|
|
9787
|
+
if (wantAztec && aztecCanDecode) {
|
|
9788
|
+
// The central bull's-eye is a small, high-contrast target. Hybrid
|
|
9789
|
+
// thresholding can flatten it on clean rendered symbols, so mirror the
|
|
9790
|
+
// Data Matrix global fallback in auto mode.
|
|
9791
|
+
const aztecBits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
|
|
9792
|
+
for (const candidateBits of aztecBits) {
|
|
9793
|
+
try {
|
|
9794
|
+
const found = aztec.detectAndDecodeAztec(candidateBits);
|
|
9795
|
+
if (found) { results.push({ ...found, format: 'aztec' }); break; }
|
|
9796
|
+
} catch {
|
|
9797
|
+
/* no Aztec code with this threshold */
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
}
|
|
9801
|
+
|
|
7902
9802
|
if (wantOneD) {
|
|
7903
9803
|
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
7904
9804
|
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
@@ -7933,7 +9833,7 @@ function decodeStrict(image, options) {
|
|
|
7933
9833
|
}
|
|
7934
9834
|
|
|
7935
9835
|
/** Library version, matching package.json. */
|
|
7936
|
-
const VERSION = '
|
|
9836
|
+
const VERSION = '1.1.0';
|
|
7937
9837
|
|
|
7938
9838
|
__exports.listFormats = listFormats;
|
|
7939
9839
|
__exports.encode = encode;
|