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