@sythos/js_barcode_universal 1.0.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 +69 -43
- package/bundle/sythos-barcode.esm.js +1164 -4
- package/bundle/sythos-barcode.js +1160 -4
- package/examples/create.html +2 -1
- package/examples/read.html +341 -341
- package/licenses/README.md +1 -0
- package/licenses/aztec-code.license +74 -0
- package/package.json +6 -3
- 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 -262
- package/src/datamatrix/detector.js +225 -225
- package/src/datamatrix/encoder.js +191 -191
- package/src/datamatrix/index.js +42 -42
- package/src/datamatrix/tables.js +123 -123
- package/src/index.js +38 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Sythos Barcode Suite v1.
|
|
2
|
+
* Sythos Barcode Suite v1.1.0
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
5
|
*
|
|
@@ -2917,6 +2917,9 @@ const GF256_QR = new GaloisField({ size: 256, primitive: 0x011d, name: 'GF(256)/
|
|
|
2917
2917
|
/** Data Matrix ECC200. x^8 + x^5 + x^3 + x^2 + 1 */
|
|
2918
2918
|
const GF256_DM = new GaloisField({ size: 256, primitive: 0x012d, name: 'GF(256)/DataMatrix' });
|
|
2919
2919
|
|
|
2920
|
+
/** Aztec's eight-bit data field is algebraically identical to Data Matrix's. */
|
|
2921
|
+
const GF256_AZTEC = GF256_DM;
|
|
2922
|
+
|
|
2920
2923
|
/** PDF417. Prime field; 3 is a primitive root modulo 929. */
|
|
2921
2924
|
const GF929 = new GaloisField({ size: 929, prime: true, generator: 3, name: 'GF(929)' });
|
|
2922
2925
|
|
|
@@ -2929,6 +2932,7 @@ const GF4096 = new GaloisField({ size: 4096, primitive: 0x1069, name: 'GF(4096)'
|
|
|
2929
2932
|
__exports.GaloisField = GaloisField;
|
|
2930
2933
|
__exports.GF256_QR = GF256_QR;
|
|
2931
2934
|
__exports.GF256_DM = GF256_DM;
|
|
2935
|
+
__exports.GF256_AZTEC = GF256_AZTEC;
|
|
2932
2936
|
__exports.GF929 = GF929;
|
|
2933
2937
|
__exports.GF16 = GF16;
|
|
2934
2938
|
__exports.GF64 = GF64;
|
|
@@ -2964,7 +2968,7 @@ const { ChecksumError } = __require("core/errors.js");
|
|
|
2964
2968
|
*
|
|
2965
2969
|
* g(x) = product over i of (x - a^(base + i)), i = 0 .. eccLen-1
|
|
2966
2970
|
*
|
|
2967
|
-
* `base` is 0 for QR
|
|
2971
|
+
* `base` is 0 for QR; 1 for Aztec, Data Matrix and PDF417.
|
|
2968
2972
|
*
|
|
2969
2973
|
* @param {number} eccLen
|
|
2970
2974
|
* @param {import('./galois-field.js').GaloisField} field
|
|
@@ -7208,6 +7212,1122 @@ const __reexport2 = __require("qr/detector.js"); __exports.detectQR = __reexport
|
|
|
7208
7212
|
const __reexport3 = __require("qr/tables.js"); __exports.validateTables = __reexport3.validateTables;
|
|
7209
7213
|
|
|
7210
7214
|
|
|
7215
|
+
};
|
|
7216
|
+
|
|
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");
|
|
7236
|
+
|
|
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;
|
|
7248
|
+
|
|
7249
|
+
/**
|
|
7250
|
+
* Convert accepted public input to its encoded octets.
|
|
7251
|
+
*
|
|
7252
|
+
* @param {string|ArrayBuffer|ArrayBufferView} value
|
|
7253
|
+
* @param {'utf-8'} [charset]
|
|
7254
|
+
* @returns {Uint8Array}
|
|
7255
|
+
*/
|
|
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;
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
|
|
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
|
+
}
|
|
7328
|
+
|
|
7329
|
+
/**
|
|
7330
|
+
* Write an Aztec binary-shift segment while in UPPER mode.
|
|
7331
|
+
*
|
|
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.
|
|
7336
|
+
*
|
|
7337
|
+
* @param {BitWriter} writer
|
|
7338
|
+
* @param {Uint8Array} bytes
|
|
7339
|
+
* @param {number} start
|
|
7340
|
+
* @param {number} length
|
|
7341
|
+
*/
|
|
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);
|
|
7352
|
+
}
|
|
7353
|
+
for (let i = 0; i < count; i++) writer.put(bytes[at + i], 8);
|
|
7354
|
+
at += count;
|
|
7355
|
+
left -= count;
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
/**
|
|
7360
|
+
* Build a valid Aztec high-level bitstream.
|
|
7361
|
+
*
|
|
7362
|
+
* @param {string|ArrayBuffer|ArrayBufferView} value
|
|
7363
|
+
* @param {{charset?: 'utf-8'}} [options]
|
|
7364
|
+
* @returns {BitWriter}
|
|
7365
|
+
*/
|
|
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);
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
return writer;
|
|
7397
|
+
}
|
|
7398
|
+
|
|
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
|
+
};
|
|
7405
|
+
|
|
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];
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
/**
|
|
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.
|
|
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
|
+
}
|
|
7532
|
+
|
|
7533
|
+
/**
|
|
7534
|
+
* Choose the first symbol which holds an already stuffed payload.
|
|
7535
|
+
*
|
|
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.
|
|
7538
|
+
*/
|
|
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');
|
|
7549
|
+
}
|
|
7550
|
+
|
|
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
|
+
}
|
|
7560
|
+
|
|
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
|
+
}
|
|
7570
|
+
|
|
7571
|
+
throw new RangeError('Aztec: payload and requested error correction do not fit an available symbol');
|
|
7572
|
+
}
|
|
7573
|
+
|
|
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
|
+
}
|
|
7586
|
+
|
|
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
|
+
};
|
|
7602
|
+
|
|
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");
|
|
7621
|
+
|
|
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
|
+
}
|
|
7626
|
+
|
|
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
|
+
}
|
|
7729
|
+
|
|
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);
|
|
7740
|
+
|
|
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`);
|
|
7769
|
+
|
|
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);
|
|
7779
|
+
}
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
return matrix;
|
|
7783
|
+
}
|
|
7784
|
+
|
|
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];
|
|
7795
|
+
}
|
|
7796
|
+
|
|
7797
|
+
/**
|
|
7798
|
+
* Encode a UTF-8 string or bytes into an Aztec Code matrix.
|
|
7799
|
+
*
|
|
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}}
|
|
7803
|
+
*/
|
|
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
|
+
}
|
|
7830
|
+
|
|
7831
|
+
__exports.stuffBits = stuffBits;
|
|
7832
|
+
__exports.addCheckWords = addCheckWords;
|
|
7833
|
+
__exports.modeMessage = modeMessage;
|
|
7834
|
+
__exports.buildAztecMatrix = buildAztecMatrix;
|
|
7835
|
+
__exports.encodeAztec = encodeAztec;
|
|
7836
|
+
};
|
|
7837
|
+
|
|
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");
|
|
7858
|
+
|
|
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 };
|
|
7868
|
+
|
|
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;
|
|
7954
|
+
}
|
|
7955
|
+
|
|
7956
|
+
for (let i = 0; i < token.length; i++) output.push(token.charCodeAt(i));
|
|
7957
|
+
shift = latch;
|
|
7958
|
+
}
|
|
7959
|
+
return Uint8Array.from(output);
|
|
7960
|
+
}
|
|
7961
|
+
|
|
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),
|
|
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
|
+
}
|
|
8003
|
+
|
|
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
|
+
}
|
|
8017
|
+
|
|
8018
|
+
/** @param {boolean} compact @param {number} layers */
|
|
8019
|
+
function totalBitsInLayers(compact, layers) {
|
|
8020
|
+
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
|
8021
|
+
}
|
|
8022
|
+
|
|
8023
|
+
/**
|
|
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.
|
|
8082
|
+
*
|
|
8083
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
8084
|
+
* @returns {{text: string, bytes: Uint8Array, compact: boolean, layers: number, corrections: number, eccPercent: number}}
|
|
8085
|
+
*/
|
|
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
|
+
};
|
|
8118
|
+
}
|
|
8119
|
+
|
|
8120
|
+
__exports.decodeHighLevelBits = decodeHighLevelBits;
|
|
8121
|
+
__exports.decodeAztec = decodeAztec;
|
|
8122
|
+
};
|
|
8123
|
+
|
|
8124
|
+
__modules["aztec/detector.js"] = function (__require, __exports) {
|
|
8125
|
+
/**
|
|
8126
|
+
* Aztec image detection.
|
|
8127
|
+
*
|
|
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.
|
|
8135
|
+
*
|
|
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
|
|
8143
|
+
*/
|
|
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
|
+
}
|
|
8201
|
+
}
|
|
8202
|
+
return out.sort((a, b) => b.pixels - a.pixels).slice(0, 2000);
|
|
8203
|
+
}
|
|
8204
|
+
|
|
8205
|
+
function expectedDark(ring, inverted) {
|
|
8206
|
+
return inverted ? (ring & 1) === 1 : (ring & 1) === 0;
|
|
8207
|
+
}
|
|
8208
|
+
|
|
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
|
+
}
|
|
8227
|
+
|
|
8228
|
+
function rotateCorners(corners, turn) {
|
|
8229
|
+
return corners.slice(turn).concat(corners.slice(0, turn));
|
|
8230
|
+
}
|
|
8231
|
+
|
|
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
|
+
}
|
|
8237
|
+
|
|
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
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
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;
|
|
8302
|
+
}
|
|
8303
|
+
|
|
8304
|
+
/**
|
|
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.
|
|
8307
|
+
*
|
|
8308
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
8309
|
+
* @returns {(import('./decoder.js').DecodeResult & {corners: Point[]}) | null}
|
|
8310
|
+
*/
|
|
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;
|
|
8329
|
+
|
|
8330
|
+
|
|
7211
8331
|
};
|
|
7212
8332
|
|
|
7213
8333
|
__modules["render/options.js"] = function (__require, __exports) {
|
|
@@ -8465,6 +9585,7 @@ const { ONED_FORMATS } = __require("oned/index.js");
|
|
|
8465
9585
|
const { decodeOneD } = __require("oned/reader.js");
|
|
8466
9586
|
const datamatrix = __require("datamatrix/index.js");
|
|
8467
9587
|
const qr = __require("qr/index.js");
|
|
9588
|
+
const aztec = __require("aztec/index.js");
|
|
8468
9589
|
__exports.BitMatrix = BitMatrix;
|
|
8469
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;
|
|
8470
9591
|
const __reexport1 = __require("image/luminance.js"); __exports.LuminanceSource = __reexport1.LuminanceSource;
|
|
@@ -8477,6 +9598,7 @@ const __reexport6 = __require("render/index.js"); __exports.renderToCanvasAuto =
|
|
|
8477
9598
|
const __reexport7 = __require("render/index.js"); __exports.renderToCanvasAutoAsync = __reexport7.renderToCanvasAutoAsync; __exports.isWebGPUAvailable = __reexport7.isWebGPUAvailable;
|
|
8478
9599
|
const __reexport8 = __require("qr/index.js"); __exports.encodeQR = __reexport8.encodeQR; __exports.decodeQR = __reexport8.decodeQR; __exports.detectQR = __reexport8.detectQR; __exports.detectAndDecodeQR = __reexport8.detectAndDecodeQR;
|
|
8479
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;
|
|
8480
9602
|
|
|
8481
9603
|
/**
|
|
8482
9604
|
* @typedef {object} FormatInfo
|
|
@@ -8502,6 +9624,8 @@ const qrCanDecode = qrPresent &&
|
|
|
8502
9624
|
typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
|
|
8503
9625
|
const dataMatrixCanEncode = typeof datamatrix.encodeDataMatrix === 'function';
|
|
8504
9626
|
const dataMatrixCanDecode = typeof datamatrix.detectAndDecodeDataMatrix === 'function';
|
|
9627
|
+
const aztecCanEncode = typeof aztec.encodeAztec === 'function';
|
|
9628
|
+
const aztecCanDecode = typeof aztec.detectAndDecodeAztec === 'function';
|
|
8505
9629
|
|
|
8506
9630
|
/**
|
|
8507
9631
|
* Every format this build supports.
|
|
@@ -8536,6 +9660,13 @@ function listFormats() {
|
|
|
8536
9660
|
canRead: dataMatrixCanDecode,
|
|
8537
9661
|
kind: /** @type {'2D'} */ ('2D'),
|
|
8538
9662
|
});
|
|
9663
|
+
formats.push({
|
|
9664
|
+
id: 'aztec',
|
|
9665
|
+
label: 'Aztec Code',
|
|
9666
|
+
canWrite: aztecCanEncode,
|
|
9667
|
+
canRead: aztecCanDecode,
|
|
9668
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
9669
|
+
});
|
|
8539
9670
|
|
|
8540
9671
|
return formats;
|
|
8541
9672
|
}
|
|
@@ -8556,6 +9687,9 @@ function listFormats() {
|
|
|
8556
9687
|
* @param {boolean} [options.checkDigit] Append a check digit, where optional.
|
|
8557
9688
|
* @param {boolean} [options.fullAscii] Code 39 extended encoding.
|
|
8558
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.
|
|
8559
9693
|
* @returns {BitMatrix}
|
|
8560
9694
|
*/
|
|
8561
9695
|
function encode(text, options = {}) {
|
|
@@ -8568,10 +9702,13 @@ function encode(text, options = {}) {
|
|
|
8568
9702
|
if (format === 'datamatrix' || format === 'data-matrix') {
|
|
8569
9703
|
return datamatrix.encodeDataMatrix(value, options);
|
|
8570
9704
|
}
|
|
9705
|
+
if (format === 'aztec' || format === 'aztec-code') {
|
|
9706
|
+
return aztec.encodeAztec(value, options);
|
|
9707
|
+
}
|
|
8571
9708
|
|
|
8572
9709
|
const entry = ONED_FORMATS[format];
|
|
8573
9710
|
if (!entry) {
|
|
8574
|
-
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix'].join(', ');
|
|
9711
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec'].join(', ');
|
|
8575
9712
|
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
8576
9713
|
}
|
|
8577
9714
|
return entry.encode(value, options);
|
|
@@ -8584,6 +9721,9 @@ function encode(text, options = {}) {
|
|
|
8584
9721
|
* @property {Uint8Array} [bytes] Raw payload, before text decoding.
|
|
8585
9722
|
* @property {number} [version] QR version.
|
|
8586
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.
|
|
8587
9727
|
*/
|
|
8588
9728
|
|
|
8589
9729
|
/**
|
|
@@ -8605,6 +9745,7 @@ function decode(image, options = {}) {
|
|
|
8605
9745
|
const want = formats ? new Set(formats.map((f) => f.toLowerCase())) : null;
|
|
8606
9746
|
const wantQR = !want || want.has('qr') || want.has('qrcode');
|
|
8607
9747
|
const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
|
|
9748
|
+
const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
|
|
8608
9749
|
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
8609
9750
|
|
|
8610
9751
|
const source = LuminanceSource.fromImageData(image);
|
|
@@ -8642,6 +9783,21 @@ function decode(image, options = {}) {
|
|
|
8642
9783
|
}
|
|
8643
9784
|
}
|
|
8644
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
|
+
|
|
8645
9801
|
if (wantOneD) {
|
|
8646
9802
|
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
8647
9803
|
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
@@ -8676,7 +9832,7 @@ function decodeStrict(image, options) {
|
|
|
8676
9832
|
}
|
|
8677
9833
|
|
|
8678
9834
|
/** Library version, matching package.json. */
|
|
8679
|
-
const VERSION = '1.
|
|
9835
|
+
const VERSION = '1.1.0';
|
|
8680
9836
|
|
|
8681
9837
|
__exports.listFormats = listFormats;
|
|
8682
9838
|
__exports.encode = encode;
|
|
@@ -8701,17 +9857,21 @@ export const {
|
|
|
8701
9857
|
binarizeGlobal,
|
|
8702
9858
|
binarizeHybrid,
|
|
8703
9859
|
decode,
|
|
9860
|
+
decodeAztec,
|
|
8704
9861
|
decodeDataMatrix,
|
|
8705
9862
|
decodeOneD,
|
|
8706
9863
|
decodeOneDStrict,
|
|
8707
9864
|
decodeQR,
|
|
8708
9865
|
decodeStrict,
|
|
9866
|
+
detectAndDecodeAztec,
|
|
8709
9867
|
detectAndDecodeDataMatrix,
|
|
8710
9868
|
detectAndDecodeQR,
|
|
9869
|
+
detectAztec,
|
|
8711
9870
|
detectDataMatrix,
|
|
8712
9871
|
detectQR,
|
|
8713
9872
|
ean13CheckDigit,
|
|
8714
9873
|
encode,
|
|
9874
|
+
encodeAztec,
|
|
8715
9875
|
encodeCodabar,
|
|
8716
9876
|
encodeCode11,
|
|
8717
9877
|
encodeCode128,
|