@sythos/js_barcode_universal 1.2.5 → 1.3.1
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/LICENSE +8 -1
- package/NOTICE.md +3 -0
- package/README.md +548 -463
- package/bundle/sythos-barcode.esm.js +2377 -3
- package/bundle/sythos-barcode.js +2365 -3
- package/examples/create.html +1009 -730
- package/licenses/README.md +35 -58
- package/licenses/aztec-code.license +15 -13
- package/licenses/codabar.license +18 -15
- package/licenses/code-11.license +11 -9
- package/licenses/code-128.license +10 -8
- package/licenses/code-39.license +10 -8
- package/licenses/code-93.license +16 -14
- package/licenses/data-matrix.license +15 -13
- package/licenses/ean-13.license +10 -8
- package/licenses/ean-8.license +10 -8
- package/licenses/frameqr.license +84 -0
- package/licenses/gs1-128.license +10 -8
- package/licenses/isbn.license +10 -8
- package/licenses/itf-14.license +10 -8
- package/licenses/itf.license +9 -7
- package/licenses/micro-qr.license +79 -0
- package/licenses/micropdf417.license +52 -67
- package/licenses/msi-plessey.license +13 -11
- package/licenses/pdf417.license +78 -37
- package/licenses/pharmacode.license +14 -12
- package/licenses/qr-code.license +9 -7
- package/licenses/rmqr.license +79 -0
- package/licenses/upc-a.license +12 -10
- package/licenses/upc-e.license +10 -8
- package/package.json +97 -88
- 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/frameqr/decoder.js +239 -0
- package/src/frameqr/detector.js +192 -0
- package/src/frameqr/encoder.js +156 -0
- package/src/frameqr/index.js +42 -0
- package/src/frameqr/tables.js +270 -0
- package/src/index.js +91 -2
- package/src/microqr/decoder.js +245 -0
- package/src/microqr/detector.js +355 -0
- package/src/microqr/encoder.js +269 -0
- package/src/microqr/index.js +36 -0
- package/src/microqr/tables.js +316 -0
- package/src/oned/index.js +59 -59
- package/src/rmqr/decoder.js +101 -0
- package/src/rmqr/detector.js +90 -0
- package/src/rmqr/encoder.js +172 -0
- package/src/rmqr/index.js +37 -0
- package/src/rmqr/tables.js +154 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Sythos Barcode Suite v1.
|
|
2
|
+
* Sythos Barcode Suite v1.3.1
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
5
|
*
|
|
@@ -10252,6 +10252,2281 @@ const __reexport4 = __require("micropdf417/decoder.js"); __exports.decodeMicroPD
|
|
|
10252
10252
|
const __reexport5 = __require("micropdf417/detector.js"); __exports.detectMicroPDF417 = __reexport5.detectMicroPDF417; __exports.detectAndDecodeMicroPDF417 = __reexport5.detectAndDecodeMicroPDF417;
|
|
10253
10253
|
|
|
10254
10254
|
|
|
10255
|
+
};
|
|
10256
|
+
|
|
10257
|
+
__modules["microqr/tables.js"] = function (__require, __exports) {
|
|
10258
|
+
/**
|
|
10259
|
+
* Micro QR Code structural facts and geometry.
|
|
10260
|
+
*
|
|
10261
|
+
* Only the irreducible public symbology values are tabulated. Grid capacity,
|
|
10262
|
+
* reserved areas and placement order are derived independently and checked by
|
|
10263
|
+
* {@link validateMicroQrTables}. M1 and M3 have a four-bit final data symbol
|
|
10264
|
+
* character; `dataBits` therefore is authoritative and must not be inferred as
|
|
10265
|
+
* `dataCodewords * 8` for those versions.
|
|
10266
|
+
*
|
|
10267
|
+
* @module microqr/tables
|
|
10268
|
+
*/
|
|
10269
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
10270
|
+
|
|
10271
|
+
/** Numeric version identifiers used by the encoder selection loop. */
|
|
10272
|
+
const MICROQR_VERSIONS = Object.freeze([1, 2, 3, 4]);
|
|
10273
|
+
const MICROQR_VERSION_NAMES = Object.freeze(['M1', 'M2', 'M3', 'M4']);
|
|
10274
|
+
const MICROQR_ECC_LEVELS = Object.freeze(['DETECT', 'L', 'M', 'Q']);
|
|
10275
|
+
const MICROQR_FORMAT_MASK = 0x4445;
|
|
10276
|
+
const MICROQR_FORMAT_GENERATOR = 0x537;
|
|
10277
|
+
|
|
10278
|
+
const symbol = (version, ecc, symbolNumber, totalCodewords, dataCodewords, dataBits, eccCodewords) => Object.freeze({
|
|
10279
|
+
version,
|
|
10280
|
+
ecc,
|
|
10281
|
+
symbolNumber,
|
|
10282
|
+
size: 9 + Number(version.slice(1)) * 2,
|
|
10283
|
+
totalCodewords,
|
|
10284
|
+
dataCodewords,
|
|
10285
|
+
dataBits,
|
|
10286
|
+
eccCodewords,
|
|
10287
|
+
blockCount: 1,
|
|
10288
|
+
shortDataCodewordBits: dataBits % 8 || 8,
|
|
10289
|
+
remainderBits: 0,
|
|
10290
|
+
});
|
|
10291
|
+
|
|
10292
|
+
/** The eight legal Micro QR version/error-correction combinations. */
|
|
10293
|
+
const MICROQR_SYMBOLS = Object.freeze([
|
|
10294
|
+
symbol('M1', 'DETECT', 0, 5, 3, 20, 2),
|
|
10295
|
+
symbol('M2', 'L', 1, 10, 5, 40, 5),
|
|
10296
|
+
symbol('M2', 'M', 2, 10, 4, 32, 6),
|
|
10297
|
+
symbol('M3', 'L', 3, 17, 11, 84, 6),
|
|
10298
|
+
symbol('M3', 'M', 4, 17, 9, 68, 8),
|
|
10299
|
+
symbol('M4', 'L', 5, 24, 16, 128, 8),
|
|
10300
|
+
symbol('M4', 'M', 6, 24, 14, 112, 10),
|
|
10301
|
+
symbol('M4', 'Q', 7, 24, 10, 80, 14),
|
|
10302
|
+
]);
|
|
10303
|
+
|
|
10304
|
+
const symbolByKey = new Map(MICROQR_SYMBOLS.map((entry) => [`${entry.version}:${entry.ecc}`, entry]));
|
|
10305
|
+
const symbolByNumber = new Map(MICROQR_SYMBOLS.map((entry) => [entry.symbolNumber, entry]));
|
|
10306
|
+
|
|
10307
|
+
function canonicalVersion(version) {
|
|
10308
|
+
const result = typeof version === 'number' ? `M${version}` : String(version).toUpperCase();
|
|
10309
|
+
if (!MICROQR_VERSION_NAMES.includes(result)) throw new RangeError(`Micro QR: version must be M1-M4, got ${version}`);
|
|
10310
|
+
return result;
|
|
10311
|
+
}
|
|
10312
|
+
|
|
10313
|
+
/** @param {string|number} version @returns {number} */
|
|
10314
|
+
function microQrVersionSize(version) {
|
|
10315
|
+
return 9 + Number(canonicalVersion(version).slice(1)) * 2;
|
|
10316
|
+
}
|
|
10317
|
+
|
|
10318
|
+
/** Resolve the format symbol number for a legal version/ECC pair. */
|
|
10319
|
+
function microQrSymbolNumber(version, ecc) {
|
|
10320
|
+
return microQrBlockLayout(version, ecc).symbolNumber;
|
|
10321
|
+
}
|
|
10322
|
+
|
|
10323
|
+
/** Resolve the immutable single-block layout for a legal version/ECC pair. */
|
|
10324
|
+
function microQrBlockLayout(version, ecc) {
|
|
10325
|
+
const canonical = canonicalVersion(version);
|
|
10326
|
+
const level = ecc == null && canonical === 'M1' ? 'DETECT' : String(ecc).toUpperCase();
|
|
10327
|
+
const entry = symbolByKey.get(`${canonical}:${level}`);
|
|
10328
|
+
if (!entry) throw new RangeError(`Micro QR: error correction level ${ecc} is not valid for ${canonical}`);
|
|
10329
|
+
return entry;
|
|
10330
|
+
}
|
|
10331
|
+
|
|
10332
|
+
/** @returns {number} Usable message bits, including mode/count overhead. */
|
|
10333
|
+
function microQrDataCapacityBits(version, ecc) {
|
|
10334
|
+
return microQrBlockLayout(version, ecc).dataBits;
|
|
10335
|
+
}
|
|
10336
|
+
|
|
10337
|
+
/**
|
|
10338
|
+
* Encode the five format data bits with BCH(15,5), then apply the Micro QR
|
|
10339
|
+
* format mask. `symbolNumber` occupies the high three data bits and `mask`
|
|
10340
|
+
* the low two.
|
|
10341
|
+
*/
|
|
10342
|
+
function microQrFormatInfo(symbolNumber, mask, maybeMask) {
|
|
10343
|
+
// Public convenience overload: (version, ecc, mask).
|
|
10344
|
+
if (arguments.length === 3) {
|
|
10345
|
+
symbolNumber = microQrSymbolNumber(symbolNumber, mask);
|
|
10346
|
+
mask = maybeMask;
|
|
10347
|
+
}
|
|
10348
|
+
if (!Number.isInteger(symbolNumber) || symbolNumber < 0 || symbolNumber > 7) {
|
|
10349
|
+
throw new RangeError('Micro QR: symbol number must be an integer in 0..7');
|
|
10350
|
+
}
|
|
10351
|
+
if (!Number.isInteger(mask) || mask < 0 || mask > 3) {
|
|
10352
|
+
throw new RangeError('Micro QR: mask must be an integer in 0..3');
|
|
10353
|
+
}
|
|
10354
|
+
const data = (symbolNumber << 2) | mask;
|
|
10355
|
+
let remainder = data << 10;
|
|
10356
|
+
for (let bit = 14; bit >= 10; bit--) {
|
|
10357
|
+
if ((remainder & (1 << bit)) !== 0) remainder ^= MICROQR_FORMAT_GENERATOR << (bit - 10);
|
|
10358
|
+
}
|
|
10359
|
+
return ((data << 10) | remainder) ^ MICROQR_FORMAT_MASK;
|
|
10360
|
+
}
|
|
10361
|
+
|
|
10362
|
+
function hammingDistance(a, b) {
|
|
10363
|
+
let bits = (a ^ b) & 0x7fff;
|
|
10364
|
+
let count = 0;
|
|
10365
|
+
while (bits) { bits &= bits - 1; count++; }
|
|
10366
|
+
return count;
|
|
10367
|
+
}
|
|
10368
|
+
|
|
10369
|
+
/** Decode/correct a 15-bit format word. Returns null beyond three errors. */
|
|
10370
|
+
function microQrDecodeFormatInfo(bits) {
|
|
10371
|
+
if (!Number.isInteger(bits) || bits < 0 || bits > 0x7fff) {
|
|
10372
|
+
throw new RangeError('Micro QR: format information must be a 15-bit integer');
|
|
10373
|
+
}
|
|
10374
|
+
let best = null;
|
|
10375
|
+
let bestDistance = 16;
|
|
10376
|
+
for (const entry of MICROQR_SYMBOLS) for (let mask = 0; mask < 4; mask++) {
|
|
10377
|
+
const expected = microQrFormatInfo(entry.symbolNumber, mask);
|
|
10378
|
+
const distance = hammingDistance(bits, expected);
|
|
10379
|
+
if (distance < bestDistance) {
|
|
10380
|
+
bestDistance = distance;
|
|
10381
|
+
best = { version: entry.version, ecc: entry.ecc, symbolNumber: entry.symbolNumber, mask, correctedBits: distance, bits: expected };
|
|
10382
|
+
}
|
|
10383
|
+
}
|
|
10384
|
+
return bestDistance <= 3 ? best : null;
|
|
10385
|
+
}
|
|
10386
|
+
|
|
10387
|
+
/**
|
|
10388
|
+
* Format modules in bit-number order, least significant bit first. Bits 0..7
|
|
10389
|
+
* run down column 8; bits 8..14 continue right-to-left along row 8.
|
|
10390
|
+
* Position (8,8) is shared by the two arms and appears once.
|
|
10391
|
+
*/
|
|
10392
|
+
function microQrFormatInfoPositions(sizeOrVersion) {
|
|
10393
|
+
const size = typeof sizeOrVersion === 'number' && sizeOrVersion >= 11
|
|
10394
|
+
? sizeOrVersion
|
|
10395
|
+
: microQrVersionSize(sizeOrVersion);
|
|
10396
|
+
if (![11, 13, 15, 17].includes(size)) throw new RangeError(`Micro QR: invalid symbol size ${size}`);
|
|
10397
|
+
const positions = [];
|
|
10398
|
+
for (let y = 1; y <= 8; y++) positions.push([8, y]);
|
|
10399
|
+
for (let x = 7; x >= 1; x--) positions.push([x, 8]);
|
|
10400
|
+
return positions;
|
|
10401
|
+
}
|
|
10402
|
+
|
|
10403
|
+
const reservedCache = new Map();
|
|
10404
|
+
const functionCache = new Map();
|
|
10405
|
+
|
|
10406
|
+
/**
|
|
10407
|
+
* Fixed dark function modules before format information is written. Light
|
|
10408
|
+
* separator and light timing modules remain unset; use
|
|
10409
|
+
* {@link microQrReservedModules} to distinguish them from payload modules.
|
|
10410
|
+
*/
|
|
10411
|
+
function microQrFunctionModules(version) {
|
|
10412
|
+
const canonical = canonicalVersion(version);
|
|
10413
|
+
const cached = functionCache.get(canonical);
|
|
10414
|
+
if (cached) return cached;
|
|
10415
|
+
const size = microQrVersionSize(canonical);
|
|
10416
|
+
const matrix = new BitMatrix(size, size);
|
|
10417
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
10418
|
+
const outer = x === 0 || x === 6 || y === 0 || y === 6;
|
|
10419
|
+
const centre = x >= 2 && x <= 4 && y >= 2 && y <= 4;
|
|
10420
|
+
if (outer || centre) matrix.set(x, y);
|
|
10421
|
+
}
|
|
10422
|
+
for (let coordinate = 8; coordinate < size; coordinate += 2) {
|
|
10423
|
+
matrix.set(coordinate, 0);
|
|
10424
|
+
matrix.set(0, coordinate);
|
|
10425
|
+
}
|
|
10426
|
+
functionCache.set(canonical, matrix);
|
|
10427
|
+
return matrix;
|
|
10428
|
+
}
|
|
10429
|
+
|
|
10430
|
+
/** Shared immutable-in-use map of finder, separator, timing and format modules. */
|
|
10431
|
+
function microQrReservedModules(version) {
|
|
10432
|
+
const canonical = canonicalVersion(version);
|
|
10433
|
+
const cached = reservedCache.get(canonical);
|
|
10434
|
+
if (cached) return cached;
|
|
10435
|
+
const size = microQrVersionSize(canonical);
|
|
10436
|
+
const matrix = new BitMatrix(size, size);
|
|
10437
|
+
matrix.setRegion(0, 0, 8, 8); // 7x7 finder plus inner separator
|
|
10438
|
+
for (let coordinate = 8; coordinate < size; coordinate++) {
|
|
10439
|
+
matrix.set(coordinate, 0); // horizontal timing
|
|
10440
|
+
matrix.set(0, coordinate); // vertical timing
|
|
10441
|
+
}
|
|
10442
|
+
for (const [x, y] of microQrFormatInfoPositions(size)) matrix.set(x, y);
|
|
10443
|
+
reservedCache.set(canonical, matrix);
|
|
10444
|
+
return matrix;
|
|
10445
|
+
}
|
|
10446
|
+
|
|
10447
|
+
/** Number of modules carrying data or error-correction bits. */
|
|
10448
|
+
function microQrFreeModuleCount(version) {
|
|
10449
|
+
const size = microQrVersionSize(version);
|
|
10450
|
+
const reserved = microQrReservedModules(version);
|
|
10451
|
+
let count = 0;
|
|
10452
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
10453
|
+
if (!reserved.get(x, y)) count++;
|
|
10454
|
+
}
|
|
10455
|
+
return count;
|
|
10456
|
+
}
|
|
10457
|
+
|
|
10458
|
+
const orderCache = new Map();
|
|
10459
|
+
|
|
10460
|
+
/** Payload module coordinates as interleaved x,y pairs, MSB-first stream order. */
|
|
10461
|
+
function microQrDataModuleOrder(version) {
|
|
10462
|
+
const canonical = canonicalVersion(version);
|
|
10463
|
+
const cached = orderCache.get(canonical);
|
|
10464
|
+
if (cached) return cached;
|
|
10465
|
+
const size = microQrVersionSize(canonical);
|
|
10466
|
+
const reserved = microQrReservedModules(canonical);
|
|
10467
|
+
const order = new Int32Array(microQrFreeModuleCount(canonical) * 2);
|
|
10468
|
+
let offset = 0;
|
|
10469
|
+
let upward = true;
|
|
10470
|
+
for (let column = size - 1; column > 0; column -= 2) {
|
|
10471
|
+
for (let rowOffset = 0; rowOffset < size; rowOffset++) {
|
|
10472
|
+
const y = upward ? size - 1 - rowOffset : rowOffset;
|
|
10473
|
+
for (let side = 0; side < 2; side++) {
|
|
10474
|
+
const x = column - side;
|
|
10475
|
+
if (reserved.get(x, y)) continue;
|
|
10476
|
+
order[offset++] = x;
|
|
10477
|
+
order[offset++] = y;
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
upward = !upward;
|
|
10481
|
+
}
|
|
10482
|
+
orderCache.set(canonical, order);
|
|
10483
|
+
return order;
|
|
10484
|
+
}
|
|
10485
|
+
|
|
10486
|
+
/** The four Micro QR data-mask predicates. */
|
|
10487
|
+
function microQrMaskBit(mask, x, y) {
|
|
10488
|
+
switch (mask) {
|
|
10489
|
+
case 0: return (y & 1) === 0;
|
|
10490
|
+
case 1: return (((y >> 1) + Math.floor(x / 3)) & 1) === 0;
|
|
10491
|
+
case 2: return ((((y * x) & 1) + ((y * x) % 3)) & 1) === 0;
|
|
10492
|
+
case 3: return ((((y + x) & 1) + ((y * x) % 3)) & 1) === 0;
|
|
10493
|
+
default: throw new RangeError(`Micro QR: mask must be an integer in 0..3, got ${mask}`);
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
|
|
10497
|
+
/** Return all internal table/geometry invariant failures. */
|
|
10498
|
+
function validateMicroQrTables() {
|
|
10499
|
+
const issues = [];
|
|
10500
|
+
if (MICROQR_SYMBOLS.length !== 8) issues.push('expected eight symbol/ECC combinations');
|
|
10501
|
+
const numbers = new Set();
|
|
10502
|
+
for (const entry of MICROQR_SYMBOLS) {
|
|
10503
|
+
const tag = `${entry.version}-${entry.ecc}`;
|
|
10504
|
+
if (numbers.has(entry.symbolNumber)) issues.push(`${tag}: duplicate symbol number`);
|
|
10505
|
+
numbers.add(entry.symbolNumber);
|
|
10506
|
+
if (entry.blockCount !== 1) issues.push(`${tag}: Micro QR must use one block`);
|
|
10507
|
+
if (entry.dataBits + entry.eccCodewords * 8 !== microQrFreeModuleCount(entry.version)) {
|
|
10508
|
+
issues.push(`${tag}: data/ECC bits do not fill the encoding region`);
|
|
10509
|
+
}
|
|
10510
|
+
if (entry.totalCodewords !== entry.dataCodewords + entry.eccCodewords) issues.push(`${tag}: codeword count mismatch`);
|
|
10511
|
+
if (entry.dataBits !== (entry.dataCodewords - 1) * 8 + entry.shortDataCodewordBits) issues.push(`${tag}: final data codeword mismatch`);
|
|
10512
|
+
if (entry.shortDataCodewordBits !== (entry.version === 'M1' || entry.version === 'M3' ? 4 : 8)) issues.push(`${tag}: wrong final data codeword width`);
|
|
10513
|
+
}
|
|
10514
|
+
for (const version of MICROQR_VERSIONS) {
|
|
10515
|
+
const size = microQrVersionSize(version);
|
|
10516
|
+
const positions = microQrFormatInfoPositions(size);
|
|
10517
|
+
const unique = new Set(positions.map(([x, y]) => `${x},${y}`));
|
|
10518
|
+
if (positions.length !== 15 || unique.size !== 15) issues.push(`${version}: format positions must be 15 unique modules`);
|
|
10519
|
+
const order = microQrDataModuleOrder(version);
|
|
10520
|
+
const orderUnique = new Set();
|
|
10521
|
+
for (let i = 0; i < order.length; i += 2) {
|
|
10522
|
+
const x = order[i], y = order[i + 1];
|
|
10523
|
+
if (microQrReservedModules(version).get(x, y)) issues.push(`${version}: placement enters reserved module ${x},${y}`);
|
|
10524
|
+
orderUnique.add(`${x},${y}`);
|
|
10525
|
+
}
|
|
10526
|
+
if (orderUnique.size * 2 !== order.length) issues.push(`${version}: placement repeats a module`);
|
|
10527
|
+
if (order.length !== microQrFreeModuleCount(version) * 2) issues.push(`${version}: placement does not cover encoding region`);
|
|
10528
|
+
const functions = microQrFunctionModules(version);
|
|
10529
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
10530
|
+
if (functions.get(x, y) && !microQrReservedModules(version).get(x, y)) {
|
|
10531
|
+
issues.push(`${version}: dark function module ${x},${y} is not reserved`);
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10535
|
+
for (const entry of MICROQR_SYMBOLS) for (let mask = 0; mask < 4; mask++) {
|
|
10536
|
+
const decoded = microQrDecodeFormatInfo(microQrFormatInfo(entry.symbolNumber, mask));
|
|
10537
|
+
if (!decoded || decoded.symbolNumber !== entry.symbolNumber || decoded.mask !== mask || decoded.correctedBits !== 0) {
|
|
10538
|
+
issues.push(`${entry.version}-${entry.ecc}: format round-trip failed for mask ${mask}`);
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10541
|
+
return issues;
|
|
10542
|
+
}
|
|
10543
|
+
|
|
10544
|
+
__exports.MICROQR_VERSIONS = MICROQR_VERSIONS;
|
|
10545
|
+
__exports.MICROQR_VERSION_NAMES = MICROQR_VERSION_NAMES;
|
|
10546
|
+
__exports.MICROQR_ECC_LEVELS = MICROQR_ECC_LEVELS;
|
|
10547
|
+
__exports.MICROQR_FORMAT_MASK = MICROQR_FORMAT_MASK;
|
|
10548
|
+
__exports.MICROQR_FORMAT_GENERATOR = MICROQR_FORMAT_GENERATOR;
|
|
10549
|
+
__exports.MICROQR_SYMBOLS = MICROQR_SYMBOLS;
|
|
10550
|
+
__exports.microQrVersionSize = microQrVersionSize;
|
|
10551
|
+
__exports.microQrSymbolNumber = microQrSymbolNumber;
|
|
10552
|
+
__exports.microQrBlockLayout = microQrBlockLayout;
|
|
10553
|
+
__exports.microQrDataCapacityBits = microQrDataCapacityBits;
|
|
10554
|
+
__exports.microQrFormatInfo = microQrFormatInfo;
|
|
10555
|
+
__exports.microQrDecodeFormatInfo = microQrDecodeFormatInfo;
|
|
10556
|
+
__exports.microQrFormatInfoPositions = microQrFormatInfoPositions;
|
|
10557
|
+
__exports.microQrFunctionModules = microQrFunctionModules;
|
|
10558
|
+
__exports.microQrReservedModules = microQrReservedModules;
|
|
10559
|
+
__exports.microQrFreeModuleCount = microQrFreeModuleCount;
|
|
10560
|
+
__exports.microQrDataModuleOrder = microQrDataModuleOrder;
|
|
10561
|
+
__exports.microQrMaskBit = microQrMaskBit;
|
|
10562
|
+
__exports.validateMicroQrTables = validateMicroQrTables;
|
|
10563
|
+
};
|
|
10564
|
+
|
|
10565
|
+
__modules["microqr/encoder.js"] = function (__require, __exports) {
|
|
10566
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
10567
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
10568
|
+
const { EncodeError } = __require("core/errors.js");
|
|
10569
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
10570
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
10571
|
+
const { MICROQR_VERSIONS, microQrBlockLayout, microQrDataModuleOrder, microQrFormatInfo, microQrFormatInfoPositions, microQrMaskBit, microQrSymbolNumber, microQrVersionSize } = __require("microqr/tables.js");
|
|
10572
|
+
const MICROQR_ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
10573
|
+
const MODE = { numeric: 0, alphanumeric: 1, byte: 2, kanji: 3 };
|
|
10574
|
+
const MODE_MIN_VERSION = { numeric: 1, alphanumeric: 2, byte: 3, kanji: 3 };
|
|
10575
|
+
const COUNT_BITS = {
|
|
10576
|
+
numeric: [0, 3, 4, 5, 6], alphanumeric: [0, 0, 3, 4, 5],
|
|
10577
|
+
byte: [0, 0, 0, 4, 5], kanji: [0, 0, 0, 3, 4],
|
|
10578
|
+
};
|
|
10579
|
+
|
|
10580
|
+
function parseVersion(value) {
|
|
10581
|
+
if (value == null) return null;
|
|
10582
|
+
const match = /^M?([1-4])$/i.exec(String(value));
|
|
10583
|
+
if (!match) throw new EncodeError(`Micro QR: version must be M1-M4, got ${value}`);
|
|
10584
|
+
return Number(match[1]);
|
|
10585
|
+
}
|
|
10586
|
+
|
|
10587
|
+
function versionNumber(version) {
|
|
10588
|
+
return typeof version === 'number' ? version : Number(String(version).slice(1));
|
|
10589
|
+
}
|
|
10590
|
+
|
|
10591
|
+
function latin1Bytes(text) {
|
|
10592
|
+
const bytes = new Uint8Array(text.length);
|
|
10593
|
+
for (let i = 0; i < text.length; i++) {
|
|
10594
|
+
const cp = text.charCodeAt(i);
|
|
10595
|
+
if (cp > 0xff) throw new EncodeError('Micro QR: byte mode supports ISO-8859-1 only (ECI is unavailable)');
|
|
10596
|
+
bytes[i] = cp;
|
|
10597
|
+
}
|
|
10598
|
+
return bytes;
|
|
10599
|
+
}
|
|
10600
|
+
|
|
10601
|
+
let sjisReverseMap;
|
|
10602
|
+
|
|
10603
|
+
function sjisToThirteenBits(sjis) {
|
|
10604
|
+
const trail = sjis & 0xff;
|
|
10605
|
+
if (trail < 0x40 || trail === 0x7f || trail > 0xfc) return -1;
|
|
10606
|
+
let adjusted;
|
|
10607
|
+
if (sjis >= 0x8140 && sjis <= 0x9ffc) adjusted = sjis - 0x8140;
|
|
10608
|
+
else if (sjis >= 0xe040 && sjis <= 0xebbf) adjusted = sjis - 0xc140;
|
|
10609
|
+
else return -1;
|
|
10610
|
+
const packed = (adjusted >>> 8) * 0xc0 + (adjusted & 0xff);
|
|
10611
|
+
return packed <= 0x1fff ? packed : -1;
|
|
10612
|
+
}
|
|
10613
|
+
|
|
10614
|
+
function getSjisReverseMap() {
|
|
10615
|
+
if (sjisReverseMap !== undefined) return sjisReverseMap;
|
|
10616
|
+
let decoder;
|
|
10617
|
+
try {
|
|
10618
|
+
decoder = new TextDecoder('shift_jis', { fatal: true });
|
|
10619
|
+
if (decoder.decode(new Uint8Array([0x82, 0xa0])) !== 'あ') return (sjisReverseMap = null);
|
|
10620
|
+
} catch {
|
|
10621
|
+
return (sjisReverseMap = null);
|
|
10622
|
+
}
|
|
10623
|
+
const result = new Map();
|
|
10624
|
+
const bytes = new Uint8Array(2);
|
|
10625
|
+
for (const [start, end] of [[0x8140, 0x9ffc], [0xe040, 0xebbf]]) {
|
|
10626
|
+
for (let value = start; value <= end; value++) {
|
|
10627
|
+
if (sjisToThirteenBits(value) < 0) continue;
|
|
10628
|
+
bytes[0] = value >>> 8;
|
|
10629
|
+
bytes[1] = value & 0xff;
|
|
10630
|
+
let character;
|
|
10631
|
+
try { character = decoder.decode(bytes); } catch { continue; }
|
|
10632
|
+
if (Array.from(character).length === 1 && !result.has(character)) result.set(character, value);
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
10635
|
+
sjisReverseMap = result;
|
|
10636
|
+
return result;
|
|
10637
|
+
}
|
|
10638
|
+
|
|
10639
|
+
function kanjiValues(text) {
|
|
10640
|
+
const reverse = getSjisReverseMap();
|
|
10641
|
+
if (!reverse) return null;
|
|
10642
|
+
const values = [];
|
|
10643
|
+
for (const character of text) {
|
|
10644
|
+
const sjis = reverse.get(character);
|
|
10645
|
+
if (sjis == null) return null;
|
|
10646
|
+
values.push(sjisToThirteenBits(sjis));
|
|
10647
|
+
}
|
|
10648
|
+
return values;
|
|
10649
|
+
}
|
|
10650
|
+
|
|
10651
|
+
function chooseMode(text, forced) {
|
|
10652
|
+
const mode = forced == null ? (/^\d+$/.test(text) ? 'numeric' :
|
|
10653
|
+
[...text].every((ch) => MICROQR_ALPHANUMERIC.includes(ch)) ? 'alphanumeric' :
|
|
10654
|
+
kanjiValues(text) ? 'kanji' : 'byte') :
|
|
10655
|
+
String(forced).toLowerCase();
|
|
10656
|
+
if (!(mode in MODE)) throw new EncodeError(`Micro QR: unsupported mode "${forced}"`);
|
|
10657
|
+
if (mode === 'numeric' && !/^\d+$/.test(text)) throw new EncodeError('Micro QR: numeric mode accepts digits only');
|
|
10658
|
+
if (mode === 'alphanumeric' && ![...text].every((ch) => MICROQR_ALPHANUMERIC.includes(ch))) {
|
|
10659
|
+
throw new EncodeError('Micro QR: alphanumeric mode contains an unsupported character');
|
|
10660
|
+
}
|
|
10661
|
+
if (mode === 'kanji' && !kanjiValues(text)) {
|
|
10662
|
+
throw new EncodeError('Micro QR: kanji mode requires characters representable in the QR Shift_JIS ranges');
|
|
10663
|
+
}
|
|
10664
|
+
return mode;
|
|
10665
|
+
}
|
|
10666
|
+
|
|
10667
|
+
function encodePayload(text, mode) {
|
|
10668
|
+
const w = new BitWriter();
|
|
10669
|
+
if (mode === 'numeric') {
|
|
10670
|
+
for (let i = 0; i < text.length; i += 3) {
|
|
10671
|
+
const n = Math.min(3, text.length - i);
|
|
10672
|
+
w.put(Number(text.slice(i, i + n)), n === 3 ? 10 : n === 2 ? 7 : 4);
|
|
10673
|
+
}
|
|
10674
|
+
} else if (mode === 'alphanumeric') {
|
|
10675
|
+
let i = 0;
|
|
10676
|
+
for (; i + 1 < text.length; i += 2) {
|
|
10677
|
+
w.put(MICROQR_ALPHANUMERIC.indexOf(text[i]) * 45 + MICROQR_ALPHANUMERIC.indexOf(text[i + 1]), 11);
|
|
10678
|
+
}
|
|
10679
|
+
if (i < text.length) w.put(MICROQR_ALPHANUMERIC.indexOf(text[i]), 6);
|
|
10680
|
+
} else if (mode === 'byte') {
|
|
10681
|
+
w.putBytes(latin1Bytes(text));
|
|
10682
|
+
} else {
|
|
10683
|
+
for (const value of kanjiValues(text)) w.put(value, 13);
|
|
10684
|
+
}
|
|
10685
|
+
return w;
|
|
10686
|
+
}
|
|
10687
|
+
|
|
10688
|
+
function getBit(writer, index) {
|
|
10689
|
+
return ((writer.bytes[index >>> 3] >>> (7 - (index & 7))) & 1) === 1;
|
|
10690
|
+
}
|
|
10691
|
+
|
|
10692
|
+
function writeData(text, mode, version, layout) {
|
|
10693
|
+
const numericVersion = versionNumber(version);
|
|
10694
|
+
const payload = encodePayload(text, mode);
|
|
10695
|
+
const writer = new BitWriter();
|
|
10696
|
+
if (numericVersion > 1) writer.put(MODE[mode], numericVersion - 1);
|
|
10697
|
+
const count = mode === 'byte' ? latin1Bytes(text).length : [...text].length;
|
|
10698
|
+
const countWidth = COUNT_BITS[mode][numericVersion];
|
|
10699
|
+
if (countWidth === 0 || count >= 2 ** countWidth) return null;
|
|
10700
|
+
writer.put(count, countWidth);
|
|
10701
|
+
for (let i = 0; i < payload.length; i++) writer.putBit(getBit(payload, i));
|
|
10702
|
+
if (writer.length > layout.dataBits) return null;
|
|
10703
|
+
for (let i = 0, n = Math.min(2 * numericVersion + 1, layout.dataBits - writer.length); i < n; i++) writer.putBit(false);
|
|
10704
|
+
if (numericVersion !== 1 && numericVersion !== 3) {
|
|
10705
|
+
while ((writer.length & 7) && writer.length < layout.dataBits) writer.putBit(false);
|
|
10706
|
+
}
|
|
10707
|
+
if (numericVersion === 1 || numericVersion === 3) {
|
|
10708
|
+
while (writer.length < layout.dataBits) writer.putBit(false);
|
|
10709
|
+
return writer;
|
|
10710
|
+
}
|
|
10711
|
+
let pad = 0;
|
|
10712
|
+
while (writer.length + 8 <= layout.dataBits) writer.put(pad++ & 1 ? 0x11 : 0xec, 8);
|
|
10713
|
+
while (writer.length < layout.dataBits) writer.putBit(false);
|
|
10714
|
+
return writer;
|
|
10715
|
+
}
|
|
10716
|
+
|
|
10717
|
+
function finalMessage(dataWriter, layout) {
|
|
10718
|
+
const bytes = Array.from(dataWriter.toBytes());
|
|
10719
|
+
if (layout.shortDataCodewordBits === 4) bytes[bytes.length - 1] &= 0xf0;
|
|
10720
|
+
const ecc = rsEncode(bytes, layout.eccCodewords, GF256_QR, 0);
|
|
10721
|
+
const out = [];
|
|
10722
|
+
const full = layout.shortDataCodewordBits === 4 ? bytes.length - 1 : bytes.length;
|
|
10723
|
+
for (let i = 0; i < full; i++) for (let b = 7; b >= 0; b--) out.push((bytes[i] >>> b) & 1);
|
|
10724
|
+
if (layout.shortDataCodewordBits === 4) for (let b = 7; b >= 4; b--) out.push((bytes[bytes.length - 1] >>> b) & 1);
|
|
10725
|
+
for (const value of ecc) for (let b = 7; b >= 0; b--) out.push((value >>> b) & 1);
|
|
10726
|
+
return out;
|
|
10727
|
+
}
|
|
10728
|
+
|
|
10729
|
+
function drawFunctions(matrix) {
|
|
10730
|
+
const size = matrix.width;
|
|
10731
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
10732
|
+
const ring = x === 0 || x === 6 || y === 0 || y === 6;
|
|
10733
|
+
const core = x >= 2 && x <= 4 && y >= 2 && y <= 4;
|
|
10734
|
+
matrix.setValue(x, y, ring || core);
|
|
10735
|
+
}
|
|
10736
|
+
for (let i = 0; i < 8; i++) { matrix.unset(7, i); matrix.unset(i, 7); }
|
|
10737
|
+
for (let i = 8; i < size; i++) if ((i & 1) === 0) { matrix.set(i, 0); matrix.set(0, i); }
|
|
10738
|
+
}
|
|
10739
|
+
|
|
10740
|
+
function microMaskScore(matrix) {
|
|
10741
|
+
let right = 0, bottom = 0;
|
|
10742
|
+
for (let i = 1; i < matrix.width; i++) {
|
|
10743
|
+
if (matrix.get(matrix.width - 1, i)) right++;
|
|
10744
|
+
if (matrix.get(i, matrix.height - 1)) bottom++;
|
|
10745
|
+
}
|
|
10746
|
+
return Math.min(right, bottom) * 16 + Math.max(right, bottom);
|
|
10747
|
+
}
|
|
10748
|
+
|
|
10749
|
+
function buildMatrix(version, ecc, mask, bits) {
|
|
10750
|
+
const matrix = new BitMatrix(microQrVersionSize(version));
|
|
10751
|
+
drawFunctions(matrix);
|
|
10752
|
+
const order = microQrDataModuleOrder(version);
|
|
10753
|
+
for (let i = 0; i < bits.length; i++) {
|
|
10754
|
+
const x = order[i * 2], y = order[i * 2 + 1];
|
|
10755
|
+
matrix.setValue(x, y, (bits[i] === 1) !== microQrMaskBit(mask, x, y));
|
|
10756
|
+
}
|
|
10757
|
+
const format = microQrFormatInfo(microQrSymbolNumber(version, ecc), mask);
|
|
10758
|
+
const positions = microQrFormatInfoPositions(matrix.width);
|
|
10759
|
+
for (let i = 0; i < 15; i++) matrix.setValue(positions[i][0], positions[i][1], ((format >>> i) & 1) === 1);
|
|
10760
|
+
return matrix;
|
|
10761
|
+
}
|
|
10762
|
+
function encodeMicroQR(text, options = {}) {
|
|
10763
|
+
text = String(text);
|
|
10764
|
+
if (!text) throw new EncodeError('Micro QR: payload must not be empty');
|
|
10765
|
+
if (options.eci != null || options.gs1 === true) throw new EncodeError('Micro QR: ECI and GS1/FNC1 are unavailable');
|
|
10766
|
+
const mode = chooseMode(text, options.mode);
|
|
10767
|
+
const wantedVersion = parseVersion(options.version);
|
|
10768
|
+
const wantedEcc = options.ecc == null ? null : String(options.ecc).toUpperCase();
|
|
10769
|
+
if (wantedEcc === 'H') throw new EncodeError('Micro QR: error correction level H is unavailable');
|
|
10770
|
+
if (options.mask != null && (!Number.isInteger(options.mask) || options.mask < 0 || options.mask > 3)) {
|
|
10771
|
+
throw new EncodeError(`Micro QR: mask must be an integer 0-3, got ${options.mask}`);
|
|
10772
|
+
}
|
|
10773
|
+
|
|
10774
|
+
let selected;
|
|
10775
|
+
for (const version of MICROQR_VERSIONS) {
|
|
10776
|
+
if (wantedVersion != null && version !== wantedVersion) continue;
|
|
10777
|
+
const numericVersion = versionNumber(version);
|
|
10778
|
+
if (numericVersion < MODE_MIN_VERSION[mode]) continue;
|
|
10779
|
+
const levels = numericVersion === 1 ? ['DETECT'] : numericVersion < 4 ? ['L', 'M'] : ['L', 'M', 'Q'];
|
|
10780
|
+
for (const ecc of levels) {
|
|
10781
|
+
if (wantedEcc != null && ecc !== wantedEcc) continue;
|
|
10782
|
+
const layout = microQrBlockLayout(version, ecc);
|
|
10783
|
+
const data = writeData(text, mode, version, layout);
|
|
10784
|
+
if (data) { selected = { version, ecc, layout, data }; break; }
|
|
10785
|
+
}
|
|
10786
|
+
if (selected) break;
|
|
10787
|
+
}
|
|
10788
|
+
if (!selected) throw new EncodeError('Micro QR: payload does not fit the requested version/error level');
|
|
10789
|
+
const bits = finalMessage(selected.data, selected.layout);
|
|
10790
|
+
if (options.mask != null) return buildMatrix(selected.version, selected.ecc, options.mask, bits);
|
|
10791
|
+
let best, score = -1;
|
|
10792
|
+
for (let mask = 0; mask < 4; mask++) {
|
|
10793
|
+
const candidate = buildMatrix(selected.version, selected.ecc, mask, bits);
|
|
10794
|
+
const candidateScore = microMaskScore(candidate);
|
|
10795
|
+
if (candidateScore > score) { score = candidateScore; best = candidate; }
|
|
10796
|
+
}
|
|
10797
|
+
return best;
|
|
10798
|
+
}
|
|
10799
|
+
|
|
10800
|
+
__exports.MICROQR_ALPHANUMERIC = MICROQR_ALPHANUMERIC;
|
|
10801
|
+
__exports.encodeMicroQR = encodeMicroQR;
|
|
10802
|
+
};
|
|
10803
|
+
|
|
10804
|
+
__modules["microqr/decoder.js"] = function (__require, __exports) {
|
|
10805
|
+
/**
|
|
10806
|
+
* Micro QR decoder for an already sampled M1-M4 module matrix.
|
|
10807
|
+
*
|
|
10808
|
+
* @module microqr/decoder
|
|
10809
|
+
*/
|
|
10810
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
10811
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
10812
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
10813
|
+
const { microQrBlockLayout, microQrDataModuleOrder, microQrDecodeFormatInfo, microQrFormatInfoPositions, microQrMaskBit } = __require("microqr/tables.js");
|
|
10814
|
+
|
|
10815
|
+
const ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
10816
|
+
const MODE_NAMES = ['numeric', 'alphanumeric', 'byte', 'kanji'];
|
|
10817
|
+
const COUNT_BITS = {
|
|
10818
|
+
numeric: [0, 3, 4, 5, 6],
|
|
10819
|
+
alphanumeric: [0, 0, 3, 4, 5],
|
|
10820
|
+
byte: [0, 0, 0, 4, 5],
|
|
10821
|
+
kanji: [0, 0, 0, 3, 4],
|
|
10822
|
+
};
|
|
10823
|
+
|
|
10824
|
+
class LimitedBitReader {
|
|
10825
|
+
constructor(bytes, limit) {
|
|
10826
|
+
this.bytes = bytes;
|
|
10827
|
+
this.limit = limit;
|
|
10828
|
+
this.offset = 0;
|
|
10829
|
+
}
|
|
10830
|
+
|
|
10831
|
+
available() { return this.limit - this.offset; }
|
|
10832
|
+
|
|
10833
|
+
read(count) {
|
|
10834
|
+
if (!Number.isInteger(count) || count < 1 || count > 32 || count > this.available()) {
|
|
10835
|
+
throw new FormatError(`Micro QR: needed ${count} bits, ${Math.max(0, this.available())} remain`);
|
|
10836
|
+
}
|
|
10837
|
+
let value = 0;
|
|
10838
|
+
for (let i = 0; i < count; i++, this.offset++) {
|
|
10839
|
+
value = (value << 1) | ((this.bytes[this.offset >>> 3] >>> (7 - (this.offset & 7))) & 1);
|
|
10840
|
+
}
|
|
10841
|
+
return value >>> 0;
|
|
10842
|
+
}
|
|
10843
|
+
}
|
|
10844
|
+
|
|
10845
|
+
function moduleAt(matrix, x, y, mirrored) {
|
|
10846
|
+
return mirrored ? matrix.get(y, x) : matrix.get(x, y);
|
|
10847
|
+
}
|
|
10848
|
+
|
|
10849
|
+
function readFormat(matrix, expectedVersion, mirrored) {
|
|
10850
|
+
let bits = 0;
|
|
10851
|
+
const positions = microQrFormatInfoPositions(matrix.width);
|
|
10852
|
+
for (let i = 0; i < positions.length; i++) {
|
|
10853
|
+
const [x, y] = positions[i];
|
|
10854
|
+
if (moduleAt(matrix, x, y, mirrored)) bits |= 1 << i;
|
|
10855
|
+
}
|
|
10856
|
+
const format = microQrDecodeFormatInfo(bits);
|
|
10857
|
+
if (!format) throw new FormatError('Micro QR: format information is unreadable');
|
|
10858
|
+
if (format.version !== expectedVersion) {
|
|
10859
|
+
throw new FormatError(
|
|
10860
|
+
`Micro QR: format identifies ${format.version}, but the matrix dimension identifies ${expectedVersion}`,
|
|
10861
|
+
);
|
|
10862
|
+
}
|
|
10863
|
+
return format;
|
|
10864
|
+
}
|
|
10865
|
+
|
|
10866
|
+
function readCodewords(matrix, layout, mask, mirrored) {
|
|
10867
|
+
const order = microQrDataModuleOrder(layout.version);
|
|
10868
|
+
const data = new Array(layout.dataCodewords).fill(0);
|
|
10869
|
+
const ecc = new Array(layout.eccCodewords).fill(0);
|
|
10870
|
+
let streamOffset = 0;
|
|
10871
|
+
|
|
10872
|
+
const readBit = () => {
|
|
10873
|
+
const x = order[streamOffset * 2];
|
|
10874
|
+
const y = order[streamOffset * 2 + 1];
|
|
10875
|
+
if (x === undefined || y === undefined) throw new FormatError('Micro QR: encoding region is truncated');
|
|
10876
|
+
streamOffset++;
|
|
10877
|
+
return moduleAt(matrix, x, y, mirrored) !== microQrMaskBit(mask, x, y) ? 1 : 0;
|
|
10878
|
+
};
|
|
10879
|
+
const readInto = (target, index, count, highBit = 7) => {
|
|
10880
|
+
for (let bit = highBit; bit > highBit - count; bit--) target[index] |= readBit() << bit;
|
|
10881
|
+
};
|
|
10882
|
+
|
|
10883
|
+
const fullData = layout.shortDataCodewordBits === 4 ? layout.dataCodewords - 1 : layout.dataCodewords;
|
|
10884
|
+
for (let i = 0; i < fullData; i++) readInto(data, i, 8);
|
|
10885
|
+
if (layout.shortDataCodewordBits === 4) readInto(data, data.length - 1, 4);
|
|
10886
|
+
for (let i = 0; i < ecc.length; i++) readInto(ecc, i, 8);
|
|
10887
|
+
|
|
10888
|
+
if (streamOffset !== order.length / 2) {
|
|
10889
|
+
throw new FormatError(`Micro QR: read ${streamOffset} of ${order.length / 2} encoding modules`);
|
|
10890
|
+
}
|
|
10891
|
+
return data.concat(ecc);
|
|
10892
|
+
}
|
|
10893
|
+
|
|
10894
|
+
function correctCodewords(received, layout) {
|
|
10895
|
+
const corrections = rsDecode(received, layout.eccCodewords, GF256_QR, 0);
|
|
10896
|
+
if (layout.version === 'M1' && corrections !== 0) {
|
|
10897
|
+
throw new ChecksumError('Micro QR: M1 provides error detection only');
|
|
10898
|
+
}
|
|
10899
|
+
return { data: Uint8Array.from(received.slice(0, layout.dataCodewords)), corrections };
|
|
10900
|
+
}
|
|
10901
|
+
|
|
10902
|
+
function decodeKanjiValue(value) {
|
|
10903
|
+
const combined = (Math.floor(value / 0xc0) << 8) | (value % 0xc0);
|
|
10904
|
+
const sjis = combined + (combined < 0x1f00 ? 0x8140 : 0xc140);
|
|
10905
|
+
const bytes = Uint8Array.of(sjis >>> 8, sjis & 0xff);
|
|
10906
|
+
try {
|
|
10907
|
+
return new TextDecoder('shift_jis', { fatal: true }).decode(bytes);
|
|
10908
|
+
} catch {
|
|
10909
|
+
throw new FormatError(`Micro QR: invalid Kanji value ${value}`);
|
|
10910
|
+
}
|
|
10911
|
+
}
|
|
10912
|
+
|
|
10913
|
+
function parsePayload(data, version, dataBits) {
|
|
10914
|
+
const reader = new LimitedBitReader(data, dataBits);
|
|
10915
|
+
const modeValue = version === 1 ? 0 : reader.read(version - 1);
|
|
10916
|
+
if (modeValue > 3 || (version === 2 && modeValue > 1)) {
|
|
10917
|
+
throw new FormatError(`Micro QR: mode indicator ${modeValue} is unavailable in M${version}`);
|
|
10918
|
+
}
|
|
10919
|
+
const mode = MODE_NAMES[modeValue];
|
|
10920
|
+
const countWidth = COUNT_BITS[mode][version];
|
|
10921
|
+
if (!countWidth) throw new FormatError(`Micro QR: ${mode} mode is unavailable in M${version}`);
|
|
10922
|
+
const count = reader.read(countWidth);
|
|
10923
|
+
if (count === 0) throw new FormatError('Micro QR: zero-length data segment');
|
|
10924
|
+
|
|
10925
|
+
let text = '';
|
|
10926
|
+
const rawBytes = [];
|
|
10927
|
+
if (mode === 'numeric') {
|
|
10928
|
+
let remaining = count;
|
|
10929
|
+
while (remaining >= 3) {
|
|
10930
|
+
const value = reader.read(10);
|
|
10931
|
+
if (value >= 1000) throw new FormatError(`Micro QR: invalid numeric triplet ${value}`);
|
|
10932
|
+
text += String(value).padStart(3, '0');
|
|
10933
|
+
remaining -= 3;
|
|
10934
|
+
}
|
|
10935
|
+
if (remaining === 2) {
|
|
10936
|
+
const value = reader.read(7);
|
|
10937
|
+
if (value >= 100) throw new FormatError(`Micro QR: invalid numeric pair ${value}`);
|
|
10938
|
+
text += String(value).padStart(2, '0');
|
|
10939
|
+
} else if (remaining === 1) {
|
|
10940
|
+
const value = reader.read(4);
|
|
10941
|
+
if (value >= 10) throw new FormatError(`Micro QR: invalid numeric digit ${value}`);
|
|
10942
|
+
text += String(value);
|
|
10943
|
+
}
|
|
10944
|
+
} else if (mode === 'alphanumeric') {
|
|
10945
|
+
let remaining = count;
|
|
10946
|
+
while (remaining >= 2) {
|
|
10947
|
+
const value = reader.read(11);
|
|
10948
|
+
if (value >= 45 * 45) throw new FormatError(`Micro QR: invalid alphanumeric pair ${value}`);
|
|
10949
|
+
text += ALPHANUMERIC[Math.floor(value / 45)] + ALPHANUMERIC[value % 45];
|
|
10950
|
+
remaining -= 2;
|
|
10951
|
+
}
|
|
10952
|
+
if (remaining === 1) {
|
|
10953
|
+
const value = reader.read(6);
|
|
10954
|
+
if (value >= 45) throw new FormatError(`Micro QR: invalid alphanumeric value ${value}`);
|
|
10955
|
+
text += ALPHANUMERIC[value];
|
|
10956
|
+
}
|
|
10957
|
+
} else if (mode === 'byte') {
|
|
10958
|
+
for (let i = 0; i < count; i++) {
|
|
10959
|
+
const value = reader.read(8);
|
|
10960
|
+
rawBytes.push(value);
|
|
10961
|
+
text += String.fromCharCode(value);
|
|
10962
|
+
}
|
|
10963
|
+
} else {
|
|
10964
|
+
for (let i = 0; i < count; i++) text += decodeKanjiValue(reader.read(13));
|
|
10965
|
+
}
|
|
10966
|
+
return { text, bytes: Uint8Array.from(rawBytes), mode };
|
|
10967
|
+
}
|
|
10968
|
+
|
|
10969
|
+
function decodeOrientation(matrix, expectedVersion, mirrored) {
|
|
10970
|
+
const format = readFormat(matrix, expectedVersion, mirrored);
|
|
10971
|
+
const layout = microQrBlockLayout(format.version, format.ecc);
|
|
10972
|
+
const received = readCodewords(matrix, layout, format.mask, mirrored);
|
|
10973
|
+
const { data, corrections } = correctCodewords(received, layout);
|
|
10974
|
+
const payload = parsePayload(data, Number(format.version.slice(1)), layout.dataBits);
|
|
10975
|
+
return {
|
|
10976
|
+
text: payload.text,
|
|
10977
|
+
bytes: payload.bytes,
|
|
10978
|
+
mode: payload.mode,
|
|
10979
|
+
version: format.version,
|
|
10980
|
+
ecc: format.ecc,
|
|
10981
|
+
mask: format.mask,
|
|
10982
|
+
corrections,
|
|
10983
|
+
formatCorrections: format.correctedBits,
|
|
10984
|
+
mirrored,
|
|
10985
|
+
};
|
|
10986
|
+
}
|
|
10987
|
+
|
|
10988
|
+
/** Decode a sampled Micro QR Code symbol without its quiet zone. */
|
|
10989
|
+
function decodeMicroQR(matrix) {
|
|
10990
|
+
if (!matrix || !Number.isInteger(matrix.width) || typeof matrix.get !== 'function') {
|
|
10991
|
+
throw new FormatError('Micro QR: no matrix supplied');
|
|
10992
|
+
}
|
|
10993
|
+
if (matrix.height !== matrix.width) {
|
|
10994
|
+
throw new FormatError(`Micro QR: symbol must be square, got ${matrix.width}x${matrix.height}`);
|
|
10995
|
+
}
|
|
10996
|
+
const version = (matrix.width - 9) / 2;
|
|
10997
|
+
if (!Number.isInteger(version) || version < 1 || version > 4) {
|
|
10998
|
+
throw new FormatError(`Micro QR: ${matrix.width} modules is not a valid M1-M4 symbol size`);
|
|
10999
|
+
}
|
|
11000
|
+
const expectedVersion = `M${version}`;
|
|
11001
|
+
try {
|
|
11002
|
+
return decodeOrientation(matrix, expectedVersion, false);
|
|
11003
|
+
} catch (primaryError) {
|
|
11004
|
+
try {
|
|
11005
|
+
return decodeOrientation(matrix, expectedVersion, true);
|
|
11006
|
+
} catch {
|
|
11007
|
+
throw primaryError;
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
11011
|
+
__exports.ChecksumError = ChecksumError; __exports.FormatError = FormatError;
|
|
11012
|
+
|
|
11013
|
+
__exports.decodeMicroQR = decodeMicroQR;
|
|
11014
|
+
};
|
|
11015
|
+
|
|
11016
|
+
__modules["microqr/detector.js"] = function (__require, __exports) {
|
|
11017
|
+
/**
|
|
11018
|
+
* Micro QR detection in binarized rasters.
|
|
11019
|
+
*
|
|
11020
|
+
* A Micro QR symbol has one 7x7 finder in its top-left corner. That alone is
|
|
11021
|
+
* not enough to distinguish it from one corner of a normal QR Code, so every
|
|
11022
|
+
* candidate is also required to have the Micro QR timing arms and a format
|
|
11023
|
+
* word which the decoder accepts. The decoder is deliberately the final
|
|
11024
|
+
* geometric arbiter; BCH and Reed--Solomon verification make accidental
|
|
11025
|
+
* acceptance of ordinary square artwork very unlikely.
|
|
11026
|
+
*
|
|
11027
|
+
* Finder geometry supplies two local axes. Timing arms refine their lengths,
|
|
11028
|
+
* while a small fourth-corner search lets projective sampling absorb mild
|
|
11029
|
+
* perspective despite the format having no remote alignment pattern.
|
|
11030
|
+
*
|
|
11031
|
+
* @module microqr/detector
|
|
11032
|
+
*/
|
|
11033
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11034
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
11035
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
11036
|
+
const { decodeMicroQR } = __require("microqr/decoder.js");
|
|
11037
|
+
|
|
11038
|
+
/** Legal Micro QR side lengths (M1 through M4). */
|
|
11039
|
+
const DIMENSIONS = [11, 13, 15, 17];
|
|
11040
|
+
|
|
11041
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
11042
|
+
|
|
11043
|
+
/**
|
|
11044
|
+
* @typedef {object} Detection
|
|
11045
|
+
* @property {Point[]} corners Outer corners in reading order.
|
|
11046
|
+
* @property {number} dimension Side length in modules.
|
|
11047
|
+
* @property {'M1'|'M2'|'M3'|'M4'} version
|
|
11048
|
+
* @property {number} moduleSize Estimated pixels per module at the finder.
|
|
11049
|
+
* @property {number} rotation Clockwise orientation of the source raster.
|
|
11050
|
+
* @property {boolean} inverted Whether the detected symbol used inverted polarity.
|
|
11051
|
+
* @property {BitMatrix} matrix Rectified, normally polarised module matrix.
|
|
11052
|
+
*/
|
|
11053
|
+
|
|
11054
|
+
function rotateVector(vector) {
|
|
11055
|
+
return { x: -vector.y, y: vector.x };
|
|
11056
|
+
}
|
|
11057
|
+
|
|
11058
|
+
function add(point, a, av, b, bv) {
|
|
11059
|
+
return { x: point.x + a.x * av + b.x * bv, y: point.y + a.y * av + b.y * bv };
|
|
11060
|
+
}
|
|
11061
|
+
|
|
11062
|
+
function sample(image, point) {
|
|
11063
|
+
const x = Math.round(point.x);
|
|
11064
|
+
const y = Math.round(point.y);
|
|
11065
|
+
if (x < 0 || y < 0 || x >= image.width || y >= image.height) return null;
|
|
11066
|
+
return image.get(x, y);
|
|
11067
|
+
}
|
|
11068
|
+
|
|
11069
|
+
function expectedFinder(x, y) {
|
|
11070
|
+
return x === 0 || y === 0 || x === 6 || y === 6 ||
|
|
11071
|
+
(x >= 2 && x <= 4 && y >= 2 && y <= 4);
|
|
11072
|
+
}
|
|
11073
|
+
|
|
11074
|
+
/** Connected components matching one polarity, capped to plausible centre blocks. */
|
|
11075
|
+
function components(image, value) {
|
|
11076
|
+
const seen = new Uint8Array(image.width * image.height);
|
|
11077
|
+
const result = [];
|
|
11078
|
+
const maximumArea = Math.max(16, Math.floor(image.width * image.height * 0.08));
|
|
11079
|
+
|
|
11080
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
11081
|
+
const start = y * image.width + x;
|
|
11082
|
+
if (seen[start] || image.get(x, y) !== value) continue;
|
|
11083
|
+
|
|
11084
|
+
const queueX = [x];
|
|
11085
|
+
const queueY = [y];
|
|
11086
|
+
seen[start] = 1;
|
|
11087
|
+
let head = 0;
|
|
11088
|
+
let minX = x; let maxX = x; let minY = y; let maxY = y;
|
|
11089
|
+
|
|
11090
|
+
while (head < queueX.length) {
|
|
11091
|
+
const px = queueX[head];
|
|
11092
|
+
const py = queueY[head++];
|
|
11093
|
+
if (px < minX) minX = px;
|
|
11094
|
+
if (px > maxX) maxX = px;
|
|
11095
|
+
if (py < minY) minY = py;
|
|
11096
|
+
if (py > maxY) maxY = py;
|
|
11097
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
11098
|
+
if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height) continue;
|
|
11099
|
+
const index = ny * image.width + nx;
|
|
11100
|
+
if (!seen[index] && image.get(nx, ny) === value) {
|
|
11101
|
+
seen[index] = 1;
|
|
11102
|
+
queueX.push(nx);
|
|
11103
|
+
queueY.push(ny);
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
const width = maxX - minX + 1;
|
|
11109
|
+
const height = maxY - minY + 1;
|
|
11110
|
+
const area = width * height;
|
|
11111
|
+
if (queueX.length > maximumArea || Math.min(width, height) < 2) continue;
|
|
11112
|
+
if (Math.max(width, height) > Math.min(width, height) * 1.7) continue;
|
|
11113
|
+
if (queueX.length < area * 0.42) continue;
|
|
11114
|
+
result.push({
|
|
11115
|
+
x: (minX + maxX) / 2,
|
|
11116
|
+
y: (minY + maxY) / 2,
|
|
11117
|
+
width,
|
|
11118
|
+
height,
|
|
11119
|
+
pixels: queueX.length,
|
|
11120
|
+
});
|
|
11121
|
+
}
|
|
11122
|
+
|
|
11123
|
+
return result.sort((a, b) => b.pixels - a.pixels).slice(0, 256);
|
|
11124
|
+
}
|
|
11125
|
+
|
|
11126
|
+
/** Score the complete 7x7 finder at module centres. */
|
|
11127
|
+
function finderScore(image, centre, u, v, pitch, inverted) {
|
|
11128
|
+
let correct = 0;
|
|
11129
|
+
let total = 0;
|
|
11130
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
11131
|
+
const actual = sample(image, add(centre, u, (x - 3) * pitch, v, (y - 3) * pitch));
|
|
11132
|
+
if (actual === null) continue;
|
|
11133
|
+
const wanted = inverted ? !expectedFinder(x, y) : expectedFinder(x, y);
|
|
11134
|
+
if (actual === wanted) correct++;
|
|
11135
|
+
total++;
|
|
11136
|
+
}
|
|
11137
|
+
return total === 49 ? correct / total : 0;
|
|
11138
|
+
}
|
|
11139
|
+
|
|
11140
|
+
/** Validate separator, timing arms and a sparse quiet-zone outline. */
|
|
11141
|
+
function structureScore(image, centre, u, v, pitch, dimension, sx, sy, inverted) {
|
|
11142
|
+
let correct = 0;
|
|
11143
|
+
let total = 0;
|
|
11144
|
+
const check = (x, y, dark) => {
|
|
11145
|
+
const point = add(centre, u, (x - 3) * pitch * sx, v, (y - 3) * pitch * sy);
|
|
11146
|
+
const actual = sample(image, point);
|
|
11147
|
+
if (actual !== null && actual === (inverted ? !dark : dark)) correct++;
|
|
11148
|
+
total++;
|
|
11149
|
+
};
|
|
11150
|
+
|
|
11151
|
+
// The light separator lies between the finder and encoding region.
|
|
11152
|
+
for (let i = 0; i <= 7; i++) {
|
|
11153
|
+
check(7, i, false);
|
|
11154
|
+
check(i, 7, false);
|
|
11155
|
+
}
|
|
11156
|
+
// Both timing arms start dark at coordinate 8 and alternate to the edge.
|
|
11157
|
+
for (let i = 8; i < dimension; i++) {
|
|
11158
|
+
check(i, 0, (i & 1) === 0);
|
|
11159
|
+
check(0, i, (i & 1) === 0);
|
|
11160
|
+
}
|
|
11161
|
+
// A quiet-zone sample just beyond each edge rejects an isolated normal-QR
|
|
11162
|
+
// finder and most decorative squares without requiring a perfect crop.
|
|
11163
|
+
for (let i = 0; i < dimension; i += 2) {
|
|
11164
|
+
check(i, -1.25, false);
|
|
11165
|
+
check(-1.25, i, false);
|
|
11166
|
+
check(i, dimension + 0.75, false);
|
|
11167
|
+
check(dimension + 0.75, i, false);
|
|
11168
|
+
}
|
|
11169
|
+
return correct / total;
|
|
11170
|
+
}
|
|
11171
|
+
|
|
11172
|
+
function invert(matrix) {
|
|
11173
|
+
const out = matrix.clone();
|
|
11174
|
+
for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
|
|
11175
|
+
return out;
|
|
11176
|
+
}
|
|
11177
|
+
|
|
11178
|
+
function orientationDegrees(u) {
|
|
11179
|
+
const degrees = Math.atan2(u.y, u.x) * 180 / Math.PI;
|
|
11180
|
+
return ((Math.round(degrees / 90) * 90) % 360 + 360) % 360;
|
|
11181
|
+
}
|
|
11182
|
+
|
|
11183
|
+
function cornersFor(centre, u, v, pitch, dimension, sx, sy, dx = 0, dy = 0) {
|
|
11184
|
+
const tl = add(centre, u, -3.5 * pitch, v, -3.5 * pitch);
|
|
11185
|
+
const tr = add(tl, u, dimension * pitch * sx, v, 0);
|
|
11186
|
+
const bl = add(tl, u, 0, v, dimension * pitch * sy);
|
|
11187
|
+
const br = add(add(tr, v, dimension * pitch * sy, u, 0), u, dx * pitch, v, dy * pitch);
|
|
11188
|
+
return [tl, tr, br, bl];
|
|
11189
|
+
}
|
|
11190
|
+
|
|
11191
|
+
function candidateKey(detection) {
|
|
11192
|
+
const centre = detection.finderCentre;
|
|
11193
|
+
return `${Math.round(centre.x)},${Math.round(centre.y)},${detection.dimension}`;
|
|
11194
|
+
}
|
|
11195
|
+
|
|
11196
|
+
function sameCandidate(left, right) {
|
|
11197
|
+
if (left.dimension !== right.dimension) return false;
|
|
11198
|
+
const centre = (detection) => detection.corners.reduce(
|
|
11199
|
+
(sum, point) => ({ x: sum.x + point.x / 4, y: sum.y + point.y / 4 }),
|
|
11200
|
+
{ x: 0, y: 0 },
|
|
11201
|
+
);
|
|
11202
|
+
const a = centre(left);
|
|
11203
|
+
const b = centre(right);
|
|
11204
|
+
const tolerance = Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
11205
|
+
return Math.hypot(a.x - b.x, a.y - b.y) < tolerance;
|
|
11206
|
+
}
|
|
11207
|
+
|
|
11208
|
+
/**
|
|
11209
|
+
* Find Micro QR symbols in a binarized raster.
|
|
11210
|
+
*
|
|
11211
|
+
* The search accepts arbitrary in-plane angles, including all quarter-turns.
|
|
11212
|
+
* Non-integer scale is supported through centre sampling. Mild projective
|
|
11213
|
+
* distortion is handled by searching the unconstrained fourth corner.
|
|
11214
|
+
*
|
|
11215
|
+
* @param {BitMatrix} binaryImage Set bit = dark.
|
|
11216
|
+
* @returns {Detection[]} Best candidate first; empty when no symbol is found.
|
|
11217
|
+
*/
|
|
11218
|
+
function detectMicroQR(binaryImage) {
|
|
11219
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
11220
|
+
throw new NotFoundError('detectMicroQR: no image supplied');
|
|
11221
|
+
}
|
|
11222
|
+
|
|
11223
|
+
const detections = [];
|
|
11224
|
+
const seen = new Set();
|
|
11225
|
+
|
|
11226
|
+
for (const inverted of [false, true]) {
|
|
11227
|
+
for (const centre of components(binaryImage, !inverted)) {
|
|
11228
|
+
for (let degrees = 0; degrees < 180; degrees += 3) {
|
|
11229
|
+
const angle = degrees * Math.PI / 180;
|
|
11230
|
+
const axis = { x: Math.cos(angle), y: Math.sin(angle) };
|
|
11231
|
+
const perpendicular = rotateVector(axis);
|
|
11232
|
+
const footprint = Math.abs(axis.x) + Math.abs(axis.y);
|
|
11233
|
+
const pitch = ((centre.width + centre.height) / 2) / (3 * footprint);
|
|
11234
|
+
if (pitch < 0.75) continue;
|
|
11235
|
+
|
|
11236
|
+
// The finder is rotationally symmetric; four turns decide which pair
|
|
11237
|
+
// of arms points into the encoding region.
|
|
11238
|
+
for (let turn = 0, u = axis, v = perpendicular; turn < 4; turn++) {
|
|
11239
|
+
if (turn > 0) { u = v; v = { x: -u.y, y: u.x }; }
|
|
11240
|
+
const fScore = finderScore(binaryImage, centre, u, v, pitch, inverted);
|
|
11241
|
+
if (fScore < 0.9) continue;
|
|
11242
|
+
|
|
11243
|
+
for (const dimension of DIMENSIONS) {
|
|
11244
|
+
const scales = [0.84, 0.92, 1, 1.08, 1.16];
|
|
11245
|
+
const rankedX = scales.map((scale) => ({
|
|
11246
|
+
scale,
|
|
11247
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, scale, 1, inverted),
|
|
11248
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
11249
|
+
const rankedY = scales.map((scale) => ({
|
|
11250
|
+
scale,
|
|
11251
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, 1, scale, inverted),
|
|
11252
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
11253
|
+
|
|
11254
|
+
for (const xs of rankedX) for (const ys of rankedY) {
|
|
11255
|
+
const score = structureScore(binaryImage, centre, u, v, pitch, dimension, xs.scale, ys.scale, inverted);
|
|
11256
|
+
if (score < 0.78) continue;
|
|
11257
|
+
|
|
11258
|
+
// With a single finder there is no direct bottom-right anchor.
|
|
11259
|
+
// A compact search around the affine estimate lets the projective
|
|
11260
|
+
// sampler account for convergence of the remote edges.
|
|
11261
|
+
for (const delta of [[0, 0], [-0.75, 0], [0.75, 0], [0, -0.75], [0, 0.75],
|
|
11262
|
+
[-0.75, -0.75], [0.75, -0.75], [-0.75, 0.75], [0.75, 0.75]]) {
|
|
11263
|
+
const corners = cornersFor(
|
|
11264
|
+
centre, u, v, pitch, dimension, xs.scale, ys.scale, delta[0], delta[1]
|
|
11265
|
+
);
|
|
11266
|
+
let matrix;
|
|
11267
|
+
try { matrix = sampleQuad(binaryImage, dimension, corners, score < 0.9); }
|
|
11268
|
+
catch (error) { continue; }
|
|
11269
|
+
if (inverted) matrix = invert(matrix);
|
|
11270
|
+
|
|
11271
|
+
try {
|
|
11272
|
+
const decoded = decodeMicroQR(matrix);
|
|
11273
|
+
const version = decoded.version ?? `M${(dimension - 9) / 2}`;
|
|
11274
|
+
const detection = {
|
|
11275
|
+
corners,
|
|
11276
|
+
dimension,
|
|
11277
|
+
version,
|
|
11278
|
+
moduleSize: pitch,
|
|
11279
|
+
rotation: orientationDegrees(u),
|
|
11280
|
+
inverted,
|
|
11281
|
+
matrix,
|
|
11282
|
+
finderCentre: { x: centre.x, y: centre.y },
|
|
11283
|
+
score: fScore + score,
|
|
11284
|
+
};
|
|
11285
|
+
const key = candidateKey(detection);
|
|
11286
|
+
if (!seen.has(key) && !detections.some((entry) => sameCandidate(entry, detection))) {
|
|
11287
|
+
seen.add(key);
|
|
11288
|
+
detections.push(detection);
|
|
11289
|
+
}
|
|
11290
|
+
// Decoder validation settled this dimension and orientation.
|
|
11291
|
+
break;
|
|
11292
|
+
} catch (error) {
|
|
11293
|
+
/* Try the next perspective hypothesis. */
|
|
11294
|
+
}
|
|
11295
|
+
}
|
|
11296
|
+
}
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
}
|
|
11302
|
+
|
|
11303
|
+
detections.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
|
|
11304
|
+
for (const detection of detections) {
|
|
11305
|
+
delete detection.finderCentre;
|
|
11306
|
+
delete detection.score;
|
|
11307
|
+
}
|
|
11308
|
+
return detections;
|
|
11309
|
+
}
|
|
11310
|
+
|
|
11311
|
+
/**
|
|
11312
|
+
* Detect and decode all Micro QR symbols in a binarized raster.
|
|
11313
|
+
*
|
|
11314
|
+
* @param {BitMatrix} binaryImage
|
|
11315
|
+
* @returns {Array<object>}
|
|
11316
|
+
*/
|
|
11317
|
+
function detectAndDecodeMicroQR(binaryImage) {
|
|
11318
|
+
let detections;
|
|
11319
|
+
try { detections = detectMicroQR(binaryImage); }
|
|
11320
|
+
catch (error) { return []; }
|
|
11321
|
+
|
|
11322
|
+
const results = [];
|
|
11323
|
+
const seen = new Set();
|
|
11324
|
+
for (const detection of detections) {
|
|
11325
|
+
try {
|
|
11326
|
+
const decoded = decodeMicroQR(detection.matrix);
|
|
11327
|
+
const key = `${decoded.version ?? detection.version}|${decoded.text ?? ''}`;
|
|
11328
|
+
if (seen.has(key)) continue;
|
|
11329
|
+
seen.add(key);
|
|
11330
|
+
results.push(Object.assign({
|
|
11331
|
+
corners: detection.corners,
|
|
11332
|
+
rotation: detection.rotation,
|
|
11333
|
+
inverted: detection.inverted,
|
|
11334
|
+
}, decoded));
|
|
11335
|
+
} catch (error) {
|
|
11336
|
+
/* A failed candidate is a normal no-symbol result. */
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11339
|
+
return results;
|
|
11340
|
+
}
|
|
11341
|
+
|
|
11342
|
+
__exports.detectMicroQR = detectMicroQR;
|
|
11343
|
+
__exports.detectAndDecodeMicroQR = detectAndDecodeMicroQR;
|
|
11344
|
+
};
|
|
11345
|
+
|
|
11346
|
+
__modules["microqr/index.js"] = function (__require, __exports) {
|
|
11347
|
+
/** Micro QR Code M1-M4 public module surface. @module microqr */
|
|
11348
|
+
const __reexport0 = __require("microqr/encoder.js"); __exports.encodeMicroQR = __reexport0.encodeMicroQR;
|
|
11349
|
+
const __reexport1 = __require("microqr/decoder.js"); __exports.decodeMicroQR = __reexport1.decodeMicroQR;
|
|
11350
|
+
const __reexport2 = __require("microqr/detector.js"); __exports.detectMicroQR = __reexport2.detectMicroQR; __exports.detectAndDecodeMicroQR = __reexport2.detectAndDecodeMicroQR;
|
|
11351
|
+
const __reexport3 = __require("microqr/tables.js"); __exports.validateMicroQrTables = __reexport3.validateMicroQrTables;
|
|
11352
|
+
|
|
11353
|
+
|
|
11354
|
+
};
|
|
11355
|
+
|
|
11356
|
+
__modules["rmqr/tables.js"] = function (__require, __exports) {
|
|
11357
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11358
|
+
|
|
11359
|
+
/** The 32 rMQR dimensions in ISO/IEC 23941 order (width, height). */
|
|
11360
|
+
const RMQR_SIZES = Object.freeze([
|
|
11361
|
+
[43, 7], [59, 7], [77, 7], [99, 7], [139, 7],
|
|
11362
|
+
[43, 9], [59, 9], [77, 9], [99, 9], [139, 9],
|
|
11363
|
+
[27, 11], [43, 11], [59, 11], [77, 11], [99, 11], [139, 11],
|
|
11364
|
+
[27, 13], [43, 13], [59, 13], [77, 13], [99, 13], [139, 13],
|
|
11365
|
+
[43, 15], [59, 15], [77, 15], [99, 15], [139, 15],
|
|
11366
|
+
[43, 17], [59, 17], [77, 17], [99, 17], [139, 17],
|
|
11367
|
+
]);
|
|
11368
|
+
|
|
11369
|
+
const REMAINDER_BITS = Object.freeze([0, 3, 5, 6, 1, 2, 3, 1, 4, 5, 2, 1, 0, 2, 7, 6, 4, 1, 6, 4, 3, 0, 1, 4, 6, 7, 2, 1, 2, 0, 3, 4]);
|
|
11370
|
+
const TOTAL_CODEWORDS = Object.freeze([13, 21, 32, 44, 68, 21, 33, 49, 66, 99, 15, 31, 47, 67, 89, 132, 21, 41, 60, 85, 113, 166, 51, 74, 103, 136, 199, 61, 88, 122, 160, 232]);
|
|
11371
|
+
|
|
11372
|
+
// Each block is [number of blocks, total codewords per block, data codewords per block].
|
|
11373
|
+
const M_BLOCKS = [
|
|
11374
|
+
[[1, 13, 6]], [[1, 21, 12]], [[1, 32, 20]], [[1, 44, 28]], [[1, 68, 44]],
|
|
11375
|
+
[[1, 21, 12]], [[1, 33, 21]], [[1, 49, 31]], [[1, 66, 42]], [[1, 49, 31], [1, 50, 32]],
|
|
11376
|
+
[[1, 15, 7]], [[1, 31, 19]], [[1, 47, 31]], [[1, 67, 43]], [[1, 44, 28], [1, 45, 29]], [[2, 66, 42]],
|
|
11377
|
+
[[1, 21, 14]], [[1, 41, 27]], [[1, 60, 38]], [[1, 42, 26], [1, 43, 27]], [[1, 56, 36], [1, 57, 37]], [[2, 55, 35], [1, 56, 36]],
|
|
11378
|
+
[[1, 51, 33]], [[1, 74, 48]], [[1, 51, 33], [1, 52, 34]], [[2, 68, 44]], [[2, 66, 42], [1, 67, 43]],
|
|
11379
|
+
[[1, 60, 39]], [[2, 44, 28]], [[2, 61, 39]], [[2, 53, 33], [1, 54, 34]], [[4, 58, 38]],
|
|
11380
|
+
];
|
|
11381
|
+
const H_BLOCKS = [
|
|
11382
|
+
[[1, 13, 3]], [[1, 21, 7]], [[1, 32, 10]], [[1, 44, 14]], [[2, 34, 12]],
|
|
11383
|
+
[[1, 21, 7]], [[1, 33, 11]], [[1, 24, 8], [1, 25, 9]], [[2, 33, 11]], [[3, 33, 11]],
|
|
11384
|
+
[[1, 15, 5]], [[1, 31, 11]], [[1, 23, 7], [1, 24, 8]], [[1, 33, 11], [1, 34, 12]], [[1, 44, 14], [1, 45, 15]], [[3, 44, 14]],
|
|
11385
|
+
[[1, 21, 7]], [[1, 41, 13]], [[2, 30, 10]], [[1, 42, 14], [1, 43, 15]], [[1, 37, 11], [2, 38, 12]], [[2, 41, 13], [2, 42, 14]],
|
|
11386
|
+
[[1, 25, 7], [1, 26, 8]], [[2, 37, 13]], [[2, 34, 10], [1, 35, 11]], [[4, 34, 12]], [[1, 39, 13], [4, 40, 14]],
|
|
11387
|
+
[[1, 30, 10], [1, 31, 11]], [[2, 44, 14]], [[1, 40, 12], [2, 41, 13]], [[4, 40, 14]], [[2, 38, 12], [4, 39, 13]],
|
|
11388
|
+
];
|
|
11389
|
+
|
|
11390
|
+
const COUNT_BITS = Object.freeze({
|
|
11391
|
+
numeric: [4, 5, 6, 7, 7, 5, 6, 7, 7, 8, 4, 6, 7, 7, 8, 8, 5, 6, 7, 7, 8, 8, 7, 7, 8, 8, 9, 7, 8, 8, 8, 9],
|
|
11392
|
+
alphanumeric: [3, 5, 5, 6, 6, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 5, 6, 6, 7, 7, 8, 6, 7, 7, 7, 8, 6, 7, 7, 8, 8],
|
|
11393
|
+
byte: [3, 4, 5, 5, 6, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 4, 6, 6, 7, 7, 7, 6, 6, 7, 7, 7, 6, 6, 7, 7, 8],
|
|
11394
|
+
kanji: [2, 3, 4, 5, 5, 3, 4, 5, 5, 6, 2, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 5, 5, 6, 6, 7, 5, 6, 6, 6, 7],
|
|
11395
|
+
});
|
|
11396
|
+
|
|
11397
|
+
const ALIGNMENT_BY_WIDTH = Object.freeze({ 27: [], 43: [21], 59: [19, 39], 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111] });
|
|
11398
|
+
|
|
11399
|
+
/** @param {number} version */
|
|
11400
|
+
function versionInfo(version) {
|
|
11401
|
+
if (!Number.isInteger(version) || version < 1 || version > 32) throw new RangeError(`rMQR: version must be 1-32, got ${version}`);
|
|
11402
|
+
const [width, height] = RMQR_SIZES[version - 1];
|
|
11403
|
+
const blockTable = (ecc) => ecc === 'M' ? M_BLOCKS[version - 1] : H_BLOCKS[version - 1];
|
|
11404
|
+
const blockLayout = (ecc) => {
|
|
11405
|
+
if (ecc !== 'M' && ecc !== 'H') throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
11406
|
+
const blocks = [];
|
|
11407
|
+
for (const [count, total, data] of blockTable(ecc)) for (let i = 0; i < count; i++) blocks.push({ total, data, ecc: total - data });
|
|
11408
|
+
return { blocks, totalCodewords: TOTAL_CODEWORDS[version - 1], totalDataCodewords: blocks.reduce((n, b) => n + b.data, 0), eccCodewords: blocks.reduce((n, b) => n + b.ecc, 0) };
|
|
11409
|
+
};
|
|
11410
|
+
return Object.freeze({ version, width, height, name: `R${height}x${width}`, indicator: version - 1, remainderBits: REMAINDER_BITS[version - 1], totalCodewords: TOTAL_CODEWORDS[version - 1], countBits(mode) { return COUNT_BITS[mode]?.[version - 1] ?? 0; }, blockLayout });
|
|
11411
|
+
}
|
|
11412
|
+
|
|
11413
|
+
/** @param {number} width @param {number} height */
|
|
11414
|
+
function versionForSize(width, height) {
|
|
11415
|
+
const i = RMQR_SIZES.findIndex(([w, h]) => w === width && h === height);
|
|
11416
|
+
return i < 0 ? null : versionInfo(i + 1);
|
|
11417
|
+
}
|
|
11418
|
+
|
|
11419
|
+
/** @param {number} version */
|
|
11420
|
+
function alignmentCoordinates(version) { return ALIGNMENT_BY_WIDTH[versionInfo(version).width] || []; }
|
|
11421
|
+
|
|
11422
|
+
function bchRemainder(value) {
|
|
11423
|
+
const generator = 0x1f25;
|
|
11424
|
+
let v = value << 12;
|
|
11425
|
+
while (v !== 0 && v.toString(2).length >= 13) v ^= generator << (v.toString(2).length - 13);
|
|
11426
|
+
return v;
|
|
11427
|
+
}
|
|
11428
|
+
|
|
11429
|
+
/** Unmasked 18-bit format sequence: 5-bit version indicator plus ECC bit. */
|
|
11430
|
+
function formatBits(version, ecc) {
|
|
11431
|
+
const v = versionInfo(version);
|
|
11432
|
+
if (ecc !== 'M' && ecc !== 'H') throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
11433
|
+
const data = v.indicator | (ecc === 'H' ? 1 << 5 : 0);
|
|
11434
|
+
return (data << 12) | bchRemainder(data);
|
|
11435
|
+
}
|
|
11436
|
+
const FORMAT_MASK_FINDER = 0b011111101010110010;
|
|
11437
|
+
const FORMAT_MASK_SUB = 0b100000101001111011;
|
|
11438
|
+
|
|
11439
|
+
/** rMQR has one fixed mask: floor(y/2)+floor(x/3) even. */
|
|
11440
|
+
function maskBit(x, y) { return (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0; }
|
|
11441
|
+
|
|
11442
|
+
/** Function modules; set bits are non-data cells. */
|
|
11443
|
+
function functionModules(version) {
|
|
11444
|
+
const v = versionInfo(version); const m = new BitMatrix(v.width, v.height); const { width: w, height: h } = v;
|
|
11445
|
+
m.setRegion(0, 0, w, 1); m.setRegion(0, h - 1, w, 1); m.setRegion(0, 1, 1, h - 2); m.setRegion(w - 1, 1, 1, h - 2);
|
|
11446
|
+
for (const cx of alignmentCoordinates(version)) { m.setRegion(cx - 1, 1, 3, 2); m.setRegion(cx - 1, h - 3, 3, 2); m.setRegion(cx, 3, 1, h - 6); }
|
|
11447
|
+
m.setRegion(1, 1, 7, h === 7 ? 5 : 7); m.setRegion(8, 1, 3, 5); m.setRegion(11, 1, 1, 3);
|
|
11448
|
+
m.setRegion(w - 5, h - 5, 4, 4); m.setRegion(w - 8, h - 6, 3, 5); m.setRegion(w - 5, h - 6, 3, 1);
|
|
11449
|
+
m.set(w - 2, 1); if (h > 9) m.set(1, h - 2);
|
|
11450
|
+
return m;
|
|
11451
|
+
}
|
|
11452
|
+
|
|
11453
|
+
/** Data coordinates in the standard right-to-left two-column traversal. */
|
|
11454
|
+
function dataModuleOrder(version) {
|
|
11455
|
+
const v = versionInfo(version); const fn = functionModules(version); const out = [];
|
|
11456
|
+
let cx = v.width - 2, cy = v.height - 6, dy = -1;
|
|
11457
|
+
// The data path starts beside the lower-right format area, then snakes
|
|
11458
|
+
// through each pair of columns between the one-module outer border.
|
|
11459
|
+
while (cx > 0) {
|
|
11460
|
+
for (const xx of [cx, cx - 1]) if (!fn.get(xx, cy)) out.push([xx, cy]);
|
|
11461
|
+
if (dy < 0 && cy === 1) { cx -= 2; dy = 1; }
|
|
11462
|
+
else if (dy > 0 && cy === v.height - 2) { cx -= 2; dy = -1; }
|
|
11463
|
+
else cy += dy;
|
|
11464
|
+
}
|
|
11465
|
+
return out;
|
|
11466
|
+
}
|
|
11467
|
+
function dataBitCapacity(version, ecc) { const b = versionInfo(version).blockLayout(ecc); return b.totalDataCodewords * 8; }
|
|
11468
|
+
function validateTables() {
|
|
11469
|
+
const problems = [];
|
|
11470
|
+
if (RMQR_SIZES.length !== 32) problems.push(`expected 32 sizes, got ${RMQR_SIZES.length}`);
|
|
11471
|
+
for (let i = 1; i <= 32; i++) {
|
|
11472
|
+
const v = versionInfo(i); const order = dataModuleOrder(i); const expected = v.totalCodewords * 8 + v.remainderBits;
|
|
11473
|
+
if (order.length !== expected) problems.push(`${v.name}: data modules ${order.length}, expected ${expected}`);
|
|
11474
|
+
for (const ecc of ['M', 'H']) { const b = v.blockLayout(ecc); if (b.totalCodewords !== v.totalCodewords) problems.push(`${v.name}-${ecc}: block total mismatch`); }
|
|
11475
|
+
}
|
|
11476
|
+
return problems;
|
|
11477
|
+
}
|
|
11478
|
+
|
|
11479
|
+
__exports.RMQR_SIZES = RMQR_SIZES;
|
|
11480
|
+
__exports.versionInfo = versionInfo;
|
|
11481
|
+
__exports.versionForSize = versionForSize;
|
|
11482
|
+
__exports.alignmentCoordinates = alignmentCoordinates;
|
|
11483
|
+
__exports.formatBits = formatBits;
|
|
11484
|
+
__exports.FORMAT_MASK_FINDER = FORMAT_MASK_FINDER;
|
|
11485
|
+
__exports.FORMAT_MASK_SUB = FORMAT_MASK_SUB;
|
|
11486
|
+
__exports.maskBit = maskBit;
|
|
11487
|
+
__exports.functionModules = functionModules;
|
|
11488
|
+
__exports.dataModuleOrder = dataModuleOrder;
|
|
11489
|
+
__exports.dataBitCapacity = dataBitCapacity;
|
|
11490
|
+
__exports.validateTables = validateTables;
|
|
11491
|
+
};
|
|
11492
|
+
|
|
11493
|
+
__modules["rmqr/encoder.js"] = function (__require, __exports) {
|
|
11494
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11495
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
11496
|
+
const { EncodeError } = __require("core/errors.js");
|
|
11497
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
11498
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
11499
|
+
const { FORMAT_MASK_FINDER, FORMAT_MASK_SUB, dataBitCapacity, dataModuleOrder, formatBits, functionModules, maskBit, versionForSize, versionInfo } = __require("rmqr/tables.js");
|
|
11500
|
+
const ALPHANUMERIC_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
11501
|
+
const MODE = Object.freeze({ TERMINATOR: 0, NUMERIC: 1, ALPHANUMERIC: 2, BYTE: 3, KANJI: 4, FNC1: 5, FNC1_SECOND: 6, ECI: 7 });
|
|
11502
|
+
|
|
11503
|
+
function utf8Bytes(text) { return new TextEncoder().encode(text); }
|
|
11504
|
+
function latin1Bytes(text) { const out = new Uint8Array(text.length); for (let i = 0; i < text.length; i++) { const c = text.charCodeAt(i); if (c > 255) throw new EncodeError('rMQR: text is not ISO-8859-1'); out[i] = c; } return out; }
|
|
11505
|
+
function isAlpha(text) { for (const ch of text) if (ALPHANUMERIC_CHARS.indexOf(ch) < 0) return false; return true; }
|
|
11506
|
+
function isNumeric(text) { return /^[0-9]*$/.test(text); }
|
|
11507
|
+
|
|
11508
|
+
let sjisMap;
|
|
11509
|
+
function getSjisMap() {
|
|
11510
|
+
if (sjisMap !== undefined) return sjisMap;
|
|
11511
|
+
try {
|
|
11512
|
+
const decoder = new TextDecoder('shift_jis', { fatal: true });
|
|
11513
|
+
if (decoder.decode(new Uint8Array([0x82, 0xa0])) !== 'あ') return (sjisMap = null);
|
|
11514
|
+
const map = new Map(); const buf = new Uint8Array(2);
|
|
11515
|
+
for (const [lo, hi] of [[0x8140, 0x9ffc], [0xe040, 0xebbf]]) for (let sjis = lo; sjis <= hi; sjis++) {
|
|
11516
|
+
const trail = sjis & 255; if (trail < 0x40 || trail === 0x7f || trail > 0xfc) continue;
|
|
11517
|
+
buf[0] = sjis >> 8; buf[1] = trail; let text; try { text = decoder.decode(buf); } catch { continue; }
|
|
11518
|
+
if (Array.from(text).length === 1 && !map.has(text)) map.set(text, sjis);
|
|
11519
|
+
}
|
|
11520
|
+
return (sjisMap = map);
|
|
11521
|
+
} catch { return (sjisMap = null); }
|
|
11522
|
+
}
|
|
11523
|
+
function kanjiValue(ch) {
|
|
11524
|
+
const sjis = getSjisMap()?.get(ch); if (sjis === undefined) return -1;
|
|
11525
|
+
let v; if (sjis >= 0x8140 && sjis <= 0x9ffc) v = sjis - 0x8140; else if (sjis >= 0xe040 && sjis <= 0xebbf) v = sjis - 0xc140; else return -1;
|
|
11526
|
+
return ((v >> 8) * 0xc0) + (v & 0xff);
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11529
|
+
function chooseMode(text, requested) {
|
|
11530
|
+
if (requested === 'numeric' || requested === 'alphanumeric' || requested === 'byte' || requested === 'kanji') return requested;
|
|
11531
|
+
if (isNumeric(text)) return 'numeric';
|
|
11532
|
+
if (isAlpha(text)) return 'alphanumeric';
|
|
11533
|
+
return 'byte';
|
|
11534
|
+
}
|
|
11535
|
+
|
|
11536
|
+
function modeBits(mode) { return MODE[mode.toUpperCase()] ?? MODE.BYTE; }
|
|
11537
|
+
function charCount(mode, text, bytes) { return mode === 'byte' ? bytes.length : mode === 'kanji' ? Array.from(text).length : text.length; }
|
|
11538
|
+
function payloadBits(mode, text, bytes) {
|
|
11539
|
+
if (mode === 'numeric') { let n = 0; for (let i = 0; i < text.length; i += 3) n += text.length - i >= 3 ? 10 : text.length - i === 2 ? 7 : 4; return n; }
|
|
11540
|
+
if (mode === 'alphanumeric') return Math.floor(text.length / 2) * 11 + (text.length & 1 ? 6 : 0);
|
|
11541
|
+
if (mode === 'kanji') return Array.from(text).length * 13;
|
|
11542
|
+
return bytes.length * 8;
|
|
11543
|
+
}
|
|
11544
|
+
|
|
11545
|
+
function putEci(writer, assignment) {
|
|
11546
|
+
writer.put(MODE.ECI, 3);
|
|
11547
|
+
if (assignment <= 127) writer.put(assignment, 8);
|
|
11548
|
+
else if (assignment <= 16383) writer.put(0x8000 | assignment, 16);
|
|
11549
|
+
else if (assignment <= 999999) writer.put(0xc00000 | assignment, 24);
|
|
11550
|
+
else throw new EncodeError(`rMQR: invalid ECI assignment ${assignment}`);
|
|
11551
|
+
}
|
|
11552
|
+
|
|
11553
|
+
function makeData(text, v, ecc, options) {
|
|
11554
|
+
const requested = options.mode;
|
|
11555
|
+
const mode = chooseMode(text, requested);
|
|
11556
|
+
const charset = options.charset || (mode === 'byte' && Array.from(text).every((ch) => ch.charCodeAt(0) <= 255) ? 'iso-8859-1' : 'utf-8');
|
|
11557
|
+
const bytes = mode === 'byte' ? (charset === 'iso-8859-1' ? latin1Bytes(text) : utf8Bytes(text)) : new Uint8Array();
|
|
11558
|
+
const countBits = v.countBits(mode);
|
|
11559
|
+
if (!countBits) throw new EncodeError(`rMQR: unsupported mode ${mode}`);
|
|
11560
|
+
const writer = new BitWriter();
|
|
11561
|
+
if (options.eci !== undefined) putEci(writer, options.eci);
|
|
11562
|
+
else if (mode === 'byte' && charset === 'utf-8') putEci(writer, 26);
|
|
11563
|
+
writer.put(modeBits(mode), 3); writer.put(charCount(mode, text, bytes), countBits); // header
|
|
11564
|
+
if (mode === 'numeric') for (let i = 0; i < text.length; i += 3) { const s = text.slice(i, i + 3); writer.put(Number(s), s.length === 3 ? 10 : s.length === 2 ? 7 : 4); }
|
|
11565
|
+
else if (mode === 'alphanumeric') for (let i = 0; i < text.length; i += 2) { const a = ALPHANUMERIC_CHARS.indexOf(text[i]); const b = i + 1 < text.length ? ALPHANUMERIC_CHARS.indexOf(text[i + 1]) : -1; if (a < 0 || (b < 0 && i + 1 < text.length)) throw new EncodeError('rMQR: invalid alphanumeric character'); writer.put(b < 0 ? a : a * 45 + b, b < 0 ? 6 : 11); }
|
|
11566
|
+
else if (mode === 'kanji') for (const ch of Array.from(text)) { const value = kanjiValue(ch); if (value < 0) throw new EncodeError(`rMQR: character ${ch} is not encodable in Kanji mode`); writer.put(value, 13); }
|
|
11567
|
+
else writer.putBytes(bytes);
|
|
11568
|
+
const capacity = dataBitCapacity(v.version, ecc);
|
|
11569
|
+
if (writer.length > capacity) throw new EncodeError(`rMQR: payload does not fit ${v.name}-${ecc}`);
|
|
11570
|
+
if (writer.length + 3 <= capacity) writer.put(0, 3);
|
|
11571
|
+
while (writer.length & 7) writer.putBit(false);
|
|
11572
|
+
const dataBytes = v.blockLayout(ecc).totalDataCodewords;
|
|
11573
|
+
let pad = 0xec; while (writer.toBytes().length < dataBytes) { writer.put(pad, 8); pad = pad === 0xec ? 0x11 : 0xec; }
|
|
11574
|
+
return writer.toBytes();
|
|
11575
|
+
}
|
|
11576
|
+
|
|
11577
|
+
function interleave(data, v, ecc) {
|
|
11578
|
+
const layout = v.blockLayout(ecc); const blocks = []; let offset = 0;
|
|
11579
|
+
for (const b of layout.blocks) { const d = Array.from(data.slice(offset, offset + b.data)); offset += b.data; blocks.push({ data: d, ecc: rsEncode(d, b.ecc, GF256_QR, 0) }); }
|
|
11580
|
+
const out = []; const maxData = Math.max(...blocks.map((b) => b.data.length)); const maxEcc = Math.max(...blocks.map((b) => b.ecc.length));
|
|
11581
|
+
for (let i = 0; i < maxData; i++) for (const b of blocks) if (i < b.data.length) out.push(b.data[i]);
|
|
11582
|
+
for (let i = 0; i < maxEcc; i++) for (const b of blocks) if (i < b.ecc.length) out.push(b.ecc[i]);
|
|
11583
|
+
return Uint8Array.from(out);
|
|
11584
|
+
}
|
|
11585
|
+
|
|
11586
|
+
function drawFunctions(m, version) {
|
|
11587
|
+
const v = versionInfo(version); const fn = functionModules(version); const { width: w, height: h } = v;
|
|
11588
|
+
const setIfData = (x, y, on) => { if (!fn.get(x, y)) m.setValue(x, y, on); };
|
|
11589
|
+
for (let x = 0; x < w; x++) { m.setValue(x, 0, (x & 1) === 0); m.setValue(x, h - 1, (x & 1) === 0); }
|
|
11590
|
+
for (const x of [0, w - 1, ...new Set([...(awaitableAlignment(version))])]) for (let y = 0; y < h; y++) setIfData(x, y, (y & 1) === 0);
|
|
11591
|
+
for (const cx of awaitableAlignment(version)) for (const y of [0, 1, h - 2, h - 1]) m.setValue(cx + (y === 0 || y === h - 1 ? 0 : 0), y, false);
|
|
11592
|
+
// Alignment patterns (top and bottom).
|
|
11593
|
+
for (const cx of awaitableAlignment(version)) for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) { const on = i === 0 || i === 2 || j === 0 || j === 2; m.setValue(cx + j - 1, i, on); m.setValue(cx + j - 1, h - 1 - i, on); }
|
|
11594
|
+
// Finder and separator.
|
|
11595
|
+
for (let i = 0; i < 7; i++) for (let j = 0; j < 7; j++) m.setValue(j, i, i === 0 || i === 6 || j === 0 || j === 6 || (i >= 2 && i <= 4 && j >= 2 && j <= 4));
|
|
11596
|
+
for (let n = 0; n < 8; n++) { if (n < h) m.setValue(7, n, false); if (h >= 9) m.setValue(n, 7, false); }
|
|
11597
|
+
for (let i = 0; i < 5; i++) for (let j = 0; j < 5; j++) m.setValue(w - j - 1, h - i - 1, i === 0 || i === 4 || j === 0 || j === 4 || (i === 2 && j === 2));
|
|
11598
|
+
m.set(w - 1, 0); m.set(w - 2, 0); m.set(w - 1, 1); if (h >= 11) { m.set(0, h - 1); m.set(1, h - 1); m.set(2, h - 1); m.set(0, h - 2); }
|
|
11599
|
+
}
|
|
11600
|
+
|
|
11601
|
+
// Kept as a local helper so drawFunctions stays independent of mutable tables.
|
|
11602
|
+
function awaitableAlignment(version) { return ({ 27: [], 43: [21], 59: [19, 39], 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111] })[versionInfo(version).width] || []; }
|
|
11603
|
+
|
|
11604
|
+
function drawFormat(m, version, ecc) {
|
|
11605
|
+
const v = versionInfo(version); let bits = formatBits(version, ecc) ^ FORMAT_MASK_FINDER;
|
|
11606
|
+
for (let n = 0; n < 18; n++) m.setValue(8 + Math.floor(n / 5), 1 + (n % 5), ((bits >>> n) & 1) !== 0);
|
|
11607
|
+
bits = formatBits(version, ecc) ^ FORMAT_MASK_SUB;
|
|
11608
|
+
for (let n = 0; n < 15; n++) m.setValue(v.width - 8 + Math.floor(n / 5), v.height - 6 + (n % 5), ((bits >>> n) & 1) !== 0);
|
|
11609
|
+
for (let n = 15; n < 18; n++) m.setValue(v.width - 5 + (n - 15), v.height - 6, ((bits >>> n) & 1) !== 0);
|
|
11610
|
+
}
|
|
11611
|
+
|
|
11612
|
+
function buildMatrix(version, ecc, codewords) {
|
|
11613
|
+
const v = versionInfo(version); const m = new BitMatrix(v.width, v.height); drawFunctions(m, version); drawFormat(m, version, ecc);
|
|
11614
|
+
const fn = functionModules(version); const order = dataModuleOrder(version); let bit = 0; for (const [x, y] of order) { let on = bit < codewords.length * 8 && ((codewords[bit >>> 3] >>> (7 - (bit & 7))) & 1) !== 0; if (maskBit(x, y)) on = !on; m.setValue(x, y, on); bit++; }
|
|
11615
|
+
return m;
|
|
11616
|
+
}
|
|
11617
|
+
|
|
11618
|
+
/** Encode text into a rMQR module matrix. */
|
|
11619
|
+
function encodeRMQR(text, options = {}) {
|
|
11620
|
+
if (typeof text !== 'string' || text.length === 0) throw new EncodeError('rMQR: text must be a non-empty string');
|
|
11621
|
+
const ecc = options.ecc || 'M'; if (ecc !== 'M' && ecc !== 'H') throw new EncodeError('rMQR: ECC must be M or H');
|
|
11622
|
+
let forced = options.version; if (typeof forced === 'string') { const match = /^R(\d+)x(\d+)$/i.exec(forced); if (!match) throw new EncodeError(`rMQR: invalid version ${forced}`); const info = versionForSize(Number(match[2]), Number(match[1])); if (!info) throw new EncodeError(`rMQR: unsupported version ${forced}`); forced = info.version; }
|
|
11623
|
+
if (forced !== undefined && (!Number.isInteger(forced) || forced < 1 || forced > 32)) throw new EncodeError('rMQR: version must be 1-32');
|
|
11624
|
+
const versions = forced ? [forced] : Array.from({ length: 32 }, (_, i) => i + 1);
|
|
11625
|
+
let selected = null;
|
|
11626
|
+
for (const n of versions) { const v = versionInfo(n); try { const data = makeData(text, v, ecc, options); selected = { v, data }; break; } catch (error) { if (forced) throw error; } }
|
|
11627
|
+
if (!selected) throw new EncodeError(`rMQR: text is too long for ECC ${ecc}`);
|
|
11628
|
+
const codewords = interleave(selected.data, selected.v, ecc); const matrix = buildMatrix(selected.v.version, ecc, codewords); matrix.rmqr = { version: selected.v.version, name: selected.v.name, ecc }; return matrix;
|
|
11629
|
+
}
|
|
11630
|
+
__exports.MODE = MODE;
|
|
11631
|
+
|
|
11632
|
+
__exports.ALPHANUMERIC_CHARS = ALPHANUMERIC_CHARS;
|
|
11633
|
+
__exports.encodeRMQR = encodeRMQR;
|
|
11634
|
+
};
|
|
11635
|
+
|
|
11636
|
+
__modules["rmqr/decoder.js"] = function (__require, __exports) {
|
|
11637
|
+
const { BitReader } = __require("core/bit-buffer.js");
|
|
11638
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
11639
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
11640
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
11641
|
+
const { FORMAT_MASK_FINDER, FORMAT_MASK_SUB, dataModuleOrder, functionModules, maskBit, versionForSize, versionInfo, formatBits } = __require("rmqr/tables.js");
|
|
11642
|
+
const { ALPHANUMERIC_CHARS, MODE } = __require("rmqr/encoder.js");
|
|
11643
|
+
|
|
11644
|
+
function hamming(a, b) { let v = a ^ b, n = 0; while (v) { v &= v - 1; n++; } return n; }
|
|
11645
|
+
function readFormat(matrix, v) {
|
|
11646
|
+
let a = 0; for (let n = 0; n < 18; n++) if (matrix.get(8 + Math.floor(n / 5), 1 + (n % 5))) a |= 1 << n;
|
|
11647
|
+
let b = 0; for (let n = 0; n < 15; n++) if (matrix.get(v.width - 8 + Math.floor(n / 5), v.height - 6 + (n % 5))) b |= 1 << n;
|
|
11648
|
+
for (let n = 15; n < 18; n++) if (matrix.get(v.width - 5 + (n - 15), v.height - 6)) b |= 1 << n;
|
|
11649
|
+
const candidates = [];
|
|
11650
|
+
for (let version = 1; version <= 32; version++) for (const ecc of ['M', 'H']) {
|
|
11651
|
+
candidates.push({ version, ecc, finder: formatBits(version, ecc) ^ FORMAT_MASK_FINDER, sub: formatBits(version, ecc) ^ FORMAT_MASK_SUB });
|
|
11652
|
+
}
|
|
11653
|
+
let best = null;
|
|
11654
|
+
for (const c of candidates) for (const [value, expected] of [[a, c.finder], [b, c.sub]]) { const distance = hamming(value, expected); if (!best || distance < best.distance) best = { ...c, distance }; }
|
|
11655
|
+
if (!best || best.distance > 3) throw new FormatError('rMQR: format information is unreadable');
|
|
11656
|
+
return best;
|
|
11657
|
+
}
|
|
11658
|
+
|
|
11659
|
+
function readCodewords(matrix, v, mask, total) {
|
|
11660
|
+
const order = dataModuleOrder(v.version); const out = new Uint8Array(total); let bit = 0;
|
|
11661
|
+
for (const [x, y] of order) { if (bit >= total * 8) break; let on = matrix.get(x, y); if (maskBit(x, y)) on = !on; if (on) out[bit >>> 3] |= 0x80 >>> (bit & 7); bit++; }
|
|
11662
|
+
return out;
|
|
11663
|
+
}
|
|
11664
|
+
|
|
11665
|
+
function deinterleave(codewords, v, ecc) {
|
|
11666
|
+
const blocks = v.blockLayout(ecc).blocks; const arrays = blocks.map((b) => new Array(b.total).fill(0)); let offset = 0;
|
|
11667
|
+
const maxData = Math.max(...blocks.map((b) => b.data)); for (let i = 0; i < maxData; i++) for (let b = 0; b < blocks.length; b++) if (i < blocks[b].data) arrays[b][i] = codewords[offset++];
|
|
11668
|
+
const maxEcc = Math.max(...blocks.map((b) => b.ecc)); for (let i = 0; i < maxEcc; i++) for (let b = 0; b < blocks.length; b++) if (i < blocks[b].ecc) arrays[b][blocks[b].data + i] = codewords[offset++];
|
|
11669
|
+
const data = []; let corrections = 0;
|
|
11670
|
+
for (let b = 0; b < arrays.length; b++) { corrections += rsDecode(arrays[b], blocks[b].ecc, GF256_QR, 0); data.push(...arrays[b].slice(0, blocks[b].data)); }
|
|
11671
|
+
return { data: Uint8Array.from(data), corrections };
|
|
11672
|
+
}
|
|
11673
|
+
|
|
11674
|
+
function decodeBytes(bytes, eci) {
|
|
11675
|
+
try { return new TextDecoder(eci === 26 ? 'utf-8' : 'iso-8859-1', { fatal: false }).decode(bytes); } catch { return String.fromCharCode(...bytes); }
|
|
11676
|
+
}
|
|
11677
|
+
function parseSegments(data, v) {
|
|
11678
|
+
const reader = new BitReader(data); let text = ''; const raw = []; let eci = 3; let mode;
|
|
11679
|
+
while (reader.available() >= 3) {
|
|
11680
|
+
const peek = (() => { const save = { byteOffset: reader.byteOffset, bitOffset: reader.bitOffset }; const n = reader.read(3); reader.byteOffset = save.byteOffset; reader.bitOffset = save.bitOffset; return n; })();
|
|
11681
|
+
if (peek === 0) break;
|
|
11682
|
+
mode = reader.read(3);
|
|
11683
|
+
if (mode === MODE.ECI) { const first = reader.read(8); let value; if (!(first & 0x80)) value = first; else if ((first & 0xc0) === 0x80) value = ((first & 0x3f) << 8) | reader.read(8); else if ((first & 0xe0) === 0xc0) value = ((first & 0x1f) << 16) | reader.read(16); else throw new FormatError('rMQR: invalid ECI'); eci = value; continue; }
|
|
11684
|
+
const kind = mode === MODE.NUMERIC ? 'numeric' : mode === MODE.ALPHANUMERIC ? 'alphanumeric' : mode === MODE.BYTE ? 'byte' : mode === MODE.KANJI ? 'kanji' : null;
|
|
11685
|
+
if (!kind) throw new FormatError(`rMQR: unsupported mode ${mode}`);
|
|
11686
|
+
const count = reader.read(v.countBits(kind));
|
|
11687
|
+
if (kind === 'numeric') { let remaining = count; while (remaining >= 3) { const n = reader.read(10).toString().padStart(3, '0'); text += n; remaining -= 3; } if (remaining === 2) text += reader.read(7).toString().padStart(2, '0'); else if (remaining === 1) text += reader.read(4).toString(); }
|
|
11688
|
+
else if (kind === 'alphanumeric') { let remaining = count; while (remaining >= 2) { const n = reader.read(11); text += ALPHANUMERIC_CHARS[Math.floor(n / 45)] + ALPHANUMERIC_CHARS[n % 45]; remaining -= 2; } if (remaining) text += ALPHANUMERIC_CHARS[reader.read(6)]; }
|
|
11689
|
+
else if (kind === 'byte') { const b = new Uint8Array(count); for (let i = 0; i < count; i++) { b[i] = reader.read(8); raw.push(b[i]); } text += decodeBytes(b, eci); }
|
|
11690
|
+
else { const bytes = new Uint8Array(count * 2); for (let i = 0; i < count; i++) { const n = reader.read(13); const v2 = n; const high = Math.floor(v2 / 0xc0); const low = v2 % 0xc0; const sjis = high < 0x1f ? 0x8140 + (high << 8) + low : 0xc140 + (high << 8) + low; bytes[i * 2] = sjis >> 8; bytes[i * 2 + 1] = sjis & 255; } try { text += new TextDecoder('shift_jis').decode(bytes); } catch { text += String.fromCharCode(...bytes); } }
|
|
11691
|
+
}
|
|
11692
|
+
return { text, bytes: Uint8Array.from(raw) };
|
|
11693
|
+
}
|
|
11694
|
+
|
|
11695
|
+
/** Decode an exact rMQR module matrix (without quiet zone). */
|
|
11696
|
+
function decodeRMQR(matrix) {
|
|
11697
|
+
if (!matrix || !matrix.width || !matrix.height) throw new FormatError('rMQR: no matrix supplied');
|
|
11698
|
+
const v = versionForSize(matrix.width, matrix.height); if (!v) throw new FormatError(`rMQR: unsupported symbol size ${matrix.width}x${matrix.height}`);
|
|
11699
|
+
const info = readFormat(matrix, v); if (info.version !== v.version) throw new FormatError('rMQR: format/version mismatch');
|
|
11700
|
+
const codewords = readCodewords(matrix, v, 4, v.totalCodewords); const corrected = deinterleave(codewords, v, info.ecc); const parsed = parseSegments(corrected.data, v);
|
|
11701
|
+
return { ...parsed, version: v.version, name: v.name, ecc: info.ecc, mask: 4, corrections: corrected.corrections };
|
|
11702
|
+
}
|
|
11703
|
+
__exports.ChecksumError = ChecksumError;
|
|
11704
|
+
|
|
11705
|
+
__exports.decodeRMQR = decodeRMQR;
|
|
11706
|
+
};
|
|
11707
|
+
|
|
11708
|
+
__modules["rmqr/detector.js"] = function (__require, __exports) {
|
|
11709
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11710
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
11711
|
+
const { decodeRMQR } = __require("rmqr/decoder.js");
|
|
11712
|
+
const { versionForSize } = __require("rmqr/tables.js");
|
|
11713
|
+
|
|
11714
|
+
function rotate90(source) {
|
|
11715
|
+
const out = new BitMatrix(source.height, source.width);
|
|
11716
|
+
for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) if (source.get(x, y)) out.set(source.height - 1 - y, x);
|
|
11717
|
+
return out;
|
|
11718
|
+
}
|
|
11719
|
+
function rotate180(source) { const out = new BitMatrix(source.width, source.height); for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) if (source.get(x, y)) out.set(source.width - 1 - x, source.height - 1 - y); return out; }
|
|
11720
|
+
function rotate270(source) { return rotate90(rotate180(source)); }
|
|
11721
|
+
function crop(source, box) {
|
|
11722
|
+
const out = new BitMatrix(box.width, box.height); for (let y = 0; y < box.height; y++) for (let x = 0; x < box.width; x++) if (source.get(box.x + x, box.y + y)) out.set(x, y); return out;
|
|
11723
|
+
}
|
|
11724
|
+
function sample(matrix, width, height, scale, offsetX, offsetY) {
|
|
11725
|
+
const out = new BitMatrix(width, height); const half = Math.max(0, Math.floor(scale / 2));
|
|
11726
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
11727
|
+
let dark = 0, count = 0; const x0 = offsetX + x * scale, y0 = offsetY + y * scale;
|
|
11728
|
+
for (let yy = 0; yy < scale; yy++) for (let xx = 0; xx < scale; xx++) { if (matrix.get(x0 + xx, y0 + yy)) dark++; count++; }
|
|
11729
|
+
if (dark * 2 >= count) out.set(x, y);
|
|
11730
|
+
}
|
|
11731
|
+
return out;
|
|
11732
|
+
}
|
|
11733
|
+
function bounds(matrix) { return matrix.getBounds(); }
|
|
11734
|
+
|
|
11735
|
+
/** Detect an axis-aligned, clean rMQR raster and return the exact module matrix. */
|
|
11736
|
+
function detectRMQR(image, options = {}) {
|
|
11737
|
+
if (!image || !image.width || !image.height) throw new NotFoundError('rMQR: no raster supplied');
|
|
11738
|
+
if (options.perspective) throw new NotFoundError('rMQR: perspective detection is not available for clean-raster mode');
|
|
11739
|
+
const orientations = [image, rotate90(image), rotate180(image), rotate270(image)];
|
|
11740
|
+
for (let rotation = 0; rotation < orientations.length; rotation++) {
|
|
11741
|
+
const source = orientations[rotation]; const box = bounds(source); if (!box) continue;
|
|
11742
|
+
for (let version = 1; version <= 32; version++) {
|
|
11743
|
+
const v = versionForSize(box.width, box.height); // exact modules, scale 1
|
|
11744
|
+
if (v && v.version === version) {
|
|
11745
|
+
try { const matrix = crop(source, box); const result = decodeRMQR(matrix); return { matrix, result, rotation, corners: { x: box.x, y: box.y, width: box.width, height: box.height } }; } catch { /* try next orientation/candidate */ }
|
|
11746
|
+
}
|
|
11747
|
+
// Integer nearest-neighbour scale. The dark bounding box must still be an
|
|
11748
|
+
// exact multiple of the standard geometry; this rejects arbitrary text.
|
|
11749
|
+
const candidate = versionForSize(box.width, box.height);
|
|
11750
|
+
if (candidate) continue;
|
|
11751
|
+
const canonical = (awaitableVersion(version));
|
|
11752
|
+
const wScale = box.width / canonical.width;
|
|
11753
|
+
const hScale = box.height / canonical.height;
|
|
11754
|
+
if (!Number.isInteger(wScale) || wScale < 1 || wScale !== hScale) continue;
|
|
11755
|
+
const info = canonical;
|
|
11756
|
+
try { const matrix = sample(source, info.width, info.height, wScale, box.x, box.y); const result = decodeRMQR(matrix); return { matrix, result, rotation, scale: wScale, corners: { x: box.x, y: box.y, width: box.width, height: box.height } }; } catch { /* continue */ }
|
|
11757
|
+
}
|
|
11758
|
+
}
|
|
11759
|
+
throw new NotFoundError('rMQR: no clean axis-aligned symbol found');
|
|
11760
|
+
}
|
|
11761
|
+
|
|
11762
|
+
function awaitableVersion(version) {
|
|
11763
|
+
const sizes = [[43, 7], [59, 7], [77, 7], [99, 7], [139, 7], [43, 9], [59, 9], [77, 9], [99, 9], [139, 9], [27, 11], [43, 11], [59, 11], [77, 11], [99, 11], [139, 11], [27, 13], [43, 13], [59, 13], [77, 13], [99, 13], [139, 13], [43, 15], [59, 15], [77, 15], [99, 15], [139, 15], [43, 17], [59, 17], [77, 17], [99, 17], [139, 17]];
|
|
11764
|
+
return { version, width: sizes[version - 1][0], height: sizes[version - 1][1] };
|
|
11765
|
+
}
|
|
11766
|
+
|
|
11767
|
+
/** Detect and decode a raster in one call. */
|
|
11768
|
+
function detectAndDecodeRMQR(image, options = {}) { return detectRMQR(image, options).result; }
|
|
11769
|
+
|
|
11770
|
+
__exports.detectRMQR = detectRMQR;
|
|
11771
|
+
__exports.detectAndDecodeRMQR = detectAndDecodeRMQR;
|
|
11772
|
+
};
|
|
11773
|
+
|
|
11774
|
+
__modules["rmqr/index.js"] = function (__require, __exports) {
|
|
11775
|
+
const __reexport0 = __require("rmqr/encoder.js"); __exports.encodeRMQR = __reexport0.encodeRMQR; __exports.ALPHANUMERIC_CHARS = __reexport0.ALPHANUMERIC_CHARS;
|
|
11776
|
+
const __reexport1 = __require("rmqr/decoder.js"); __exports.decodeRMQR = __reexport1.decodeRMQR;
|
|
11777
|
+
const __reexport2 = __require("rmqr/detector.js"); __exports.detectRMQR = __reexport2.detectRMQR; __exports.detectAndDecodeRMQR = __reexport2.detectAndDecodeRMQR;
|
|
11778
|
+
const __reexport3 = __require("rmqr/tables.js"); __exports.RMQR_SIZES = __reexport3.RMQR_SIZES; __exports.versionInfo = __reexport3.versionInfo; __exports.versionForSize = __reexport3.versionForSize; __exports.alignmentCoordinates = __reexport3.alignmentCoordinates; __exports.dataModuleOrder = __reexport3.dataModuleOrder; __exports.functionModules = __reexport3.functionModules; __exports.dataBitCapacity = __reexport3.dataBitCapacity; __exports.formatBits = __reexport3.formatBits; __exports.maskBit = __reexport3.maskBit; __exports.validateTables = __reexport3.validateTables;
|
|
11779
|
+
|
|
11780
|
+
|
|
11781
|
+
};
|
|
11782
|
+
|
|
11783
|
+
__modules["frameqr/tables.js"] = function (__require, __exports) {
|
|
11784
|
+
/**
|
|
11785
|
+
* Structural contract for the Sythos Canvas QR profile.
|
|
11786
|
+
*
|
|
11787
|
+
* DENSO WAVE's public material describes FrameQR(R) as a proprietary symbol
|
|
11788
|
+
* with a freely shaped canvas, dedicated generation/reading software and no
|
|
11789
|
+
* compatibility with ordinary QR readers. It does not publish the bitstream,
|
|
11790
|
+
* placement or error-correction rules required for an interoperable encoder.
|
|
11791
|
+
* Consequently this module does not claim to implement DENSO FrameQR.
|
|
11792
|
+
*
|
|
11793
|
+
* The implementable profile below starts with an ISO/IEC 18004 QR Model 2
|
|
11794
|
+
* symbol at level H and clears a bounded group of data modules. Its worst-case
|
|
11795
|
+
* damage is calculated per Reed-Solomon block and must remain within the
|
|
11796
|
+
* standard QR correction radius. Function modules are never canvas modules.
|
|
11797
|
+
* This provides a deterministic, independently testable canvas QR profile, but
|
|
11798
|
+
* it is explicitly non-certified and not a substitute for proprietary FrameQR
|
|
11799
|
+
* generation or validation software.
|
|
11800
|
+
*
|
|
11801
|
+
* @module frameqr/tables
|
|
11802
|
+
*/
|
|
11803
|
+
const { blockLayout, dataModuleOrder, reservedModules, versionSize } = __require("qr/tables.js");
|
|
11804
|
+
|
|
11805
|
+
/** Public identity and compatibility boundary of the implementable profile. */
|
|
11806
|
+
const FRAMEQR_PROFILE = Object.freeze({
|
|
11807
|
+
id: 'sythos-canvas-qr/1',
|
|
11808
|
+
name: 'Sythos Canvas QR profile',
|
|
11809
|
+
certified: false,
|
|
11810
|
+
densoFrameQrCompatible: false,
|
|
11811
|
+
baseSymbology: 'QR Code Model 2',
|
|
11812
|
+
requiredEcc: 'H',
|
|
11813
|
+
standard: 'ISO/IEC 18004 QR Code baseline',
|
|
11814
|
+
});
|
|
11815
|
+
|
|
11816
|
+
/** Canvas shapes whose module membership is fully deterministic. */
|
|
11817
|
+
const FRAMEQR_CANVAS_SHAPES = Object.freeze(['square', 'circle', 'diamond']);
|
|
11818
|
+
|
|
11819
|
+
function oddAtMost(value, maximum) {
|
|
11820
|
+
let n = Math.max(1, Math.min(maximum, Math.floor(value)));
|
|
11821
|
+
if ((n & 1) === 0) n--;
|
|
11822
|
+
return Math.max(1, n);
|
|
11823
|
+
}
|
|
11824
|
+
|
|
11825
|
+
function assertSymbolSize(symbolSize) {
|
|
11826
|
+
if (!Number.isInteger(symbolSize) || symbolSize < 21 || symbolSize > 177 || (symbolSize - 17) % 4 !== 0) {
|
|
11827
|
+
throw new RangeError(`Frame QR profile: ${symbolSize} is not a QR Model 2 symbol size`);
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
|
|
11831
|
+
/**
|
|
11832
|
+
* Canonicalise a canvas request.
|
|
11833
|
+
*
|
|
11834
|
+
* Coordinates and dimensions are module units. Odd dimensions make the centre
|
|
11835
|
+
* unambiguous. Only quarter turns are accepted because arbitrary-angle raster
|
|
11836
|
+
* membership would depend on renderer-specific sampling.
|
|
11837
|
+
*
|
|
11838
|
+
* @param {number} symbolSize QR module width/height (21..177).
|
|
11839
|
+
* @param {object} [canvas]
|
|
11840
|
+
* @returns {{shape:string,centerX:number,centerY:number,width:number,height:number,angle:number}}
|
|
11841
|
+
*/
|
|
11842
|
+
function normalizeCanvasSpec(symbolSize, canvas = {}) {
|
|
11843
|
+
assertSymbolSize(symbolSize);
|
|
11844
|
+
if (canvas === null || typeof canvas !== 'object' || Array.isArray(canvas)) {
|
|
11845
|
+
throw new TypeError('Frame QR profile: canvas must be an object');
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
const shape = String(canvas.shape ?? 'square').toLowerCase();
|
|
11849
|
+
if (!FRAMEQR_CANVAS_SHAPES.includes(shape)) {
|
|
11850
|
+
throw new RangeError(`Frame QR profile: unsupported canvas shape "${shape}"`);
|
|
11851
|
+
}
|
|
11852
|
+
|
|
11853
|
+
const defaultSize = oddAtMost(Math.max(3, Math.round(symbolSize * 0.17)), symbolSize - 16);
|
|
11854
|
+
const requestedSize = canvas.size;
|
|
11855
|
+
const requestedWidth = canvas.width ?? requestedSize ?? defaultSize;
|
|
11856
|
+
const requestedHeight = canvas.height ?? requestedSize ?? defaultSize;
|
|
11857
|
+
const maximumDimension = symbolSize - 16;
|
|
11858
|
+
for (const [name, value] of [['width', requestedWidth], ['height', requestedHeight]]) {
|
|
11859
|
+
if (!Number.isFinite(Number(value)) || Number(value) < 1 || Number(value) > maximumDimension) {
|
|
11860
|
+
throw new RangeError(
|
|
11861
|
+
`Frame QR profile: canvas ${name} must be between 1 and ${maximumDimension} modules`
|
|
11862
|
+
);
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
11865
|
+
const width = oddAtMost(Number(requestedWidth), maximumDimension);
|
|
11866
|
+
const height = oddAtMost(Number(requestedHeight), maximumDimension);
|
|
11867
|
+
const centerX = Math.round(canvas.centerX ?? (symbolSize - 1) / 2);
|
|
11868
|
+
const centerY = Math.round(canvas.centerY ?? (symbolSize - 1) / 2);
|
|
11869
|
+
const angle = ((Number(canvas.angle ?? 0) % 360) + 360) % 360;
|
|
11870
|
+
|
|
11871
|
+
if (![0, 90, 180, 270].includes(angle)) {
|
|
11872
|
+
throw new RangeError('Frame QR profile: angle must be 0, 90, 180 or 270 degrees');
|
|
11873
|
+
}
|
|
11874
|
+
if (centerX < 8 || centerY < 8 || centerX >= symbolSize - 8 || centerY >= symbolSize - 8) {
|
|
11875
|
+
throw new RangeError('Frame QR profile: canvas centre must remain inside the finder-pattern boundary');
|
|
11876
|
+
}
|
|
11877
|
+
|
|
11878
|
+
return { shape, centerX, centerY, width, height, angle };
|
|
11879
|
+
}
|
|
11880
|
+
|
|
11881
|
+
/**
|
|
11882
|
+
* Enumerate canvas modules, including any overlaps with QR function modules.
|
|
11883
|
+
* A conforming encoder rejects such overlaps rather than damaging function
|
|
11884
|
+
* patterns.
|
|
11885
|
+
*
|
|
11886
|
+
* @param {number} symbolSize
|
|
11887
|
+
* @param {object} [canvas]
|
|
11888
|
+
* @returns {Array<[number, number]>}
|
|
11889
|
+
*/
|
|
11890
|
+
function canvasModules(symbolSize, canvas = {}) {
|
|
11891
|
+
const spec = normalizeCanvasSpec(symbolSize, canvas);
|
|
11892
|
+
const rotate = spec.angle === 90 || spec.angle === 270;
|
|
11893
|
+
const width = rotate ? spec.height : spec.width;
|
|
11894
|
+
const height = rotate ? spec.width : spec.height;
|
|
11895
|
+
const halfWidth = (width - 1) / 2;
|
|
11896
|
+
const halfHeight = (height - 1) / 2;
|
|
11897
|
+
const modules = [];
|
|
11898
|
+
|
|
11899
|
+
for (let dy = -halfHeight; dy <= halfHeight; dy++) {
|
|
11900
|
+
for (let dx = -halfWidth; dx <= halfWidth; dx++) {
|
|
11901
|
+
const nx = halfWidth === 0 ? 0 : dx / halfWidth;
|
|
11902
|
+
const ny = halfHeight === 0 ? 0 : dy / halfHeight;
|
|
11903
|
+
let inside;
|
|
11904
|
+
if (spec.shape === 'circle') inside = nx * nx + ny * ny <= 1 + Number.EPSILON;
|
|
11905
|
+
else if (spec.shape === 'diamond') inside = Math.abs(nx) + Math.abs(ny) <= 1 + Number.EPSILON;
|
|
11906
|
+
else inside = true;
|
|
11907
|
+
if (inside) modules.push([spec.centerX + dx, spec.centerY + dy]);
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
return modules;
|
|
11911
|
+
}
|
|
11912
|
+
|
|
11913
|
+
/** Build the interleaved-codeword to RS-block map used by QR Model 2. */
|
|
11914
|
+
function codewordBlockMap(layout) {
|
|
11915
|
+
const dataCounts = new Array(layout.blockCount);
|
|
11916
|
+
for (let block = 0; block < layout.blockCount; block++) {
|
|
11917
|
+
dataCounts[block] = block < layout.group1Blocks
|
|
11918
|
+
? layout.group1DataCount
|
|
11919
|
+
: layout.group2DataCount;
|
|
11920
|
+
}
|
|
11921
|
+
|
|
11922
|
+
const map = [];
|
|
11923
|
+
const maxData = Math.max(...dataCounts);
|
|
11924
|
+
for (let i = 0; i < maxData; i++) {
|
|
11925
|
+
for (let block = 0; block < layout.blockCount; block++) {
|
|
11926
|
+
if (i < dataCounts[block]) map.push(block);
|
|
11927
|
+
}
|
|
11928
|
+
}
|
|
11929
|
+
for (let i = 0; i < layout.eccPerBlock; i++) {
|
|
11930
|
+
for (let block = 0; block < layout.blockCount; block++) map.push(block);
|
|
11931
|
+
}
|
|
11932
|
+
return map;
|
|
11933
|
+
}
|
|
11934
|
+
|
|
11935
|
+
/**
|
|
11936
|
+
* Calculate worst-case QR codeword damage caused by a canvas.
|
|
11937
|
+
* A codeword is counted if any of its modules is touched. This is conservative:
|
|
11938
|
+
* clearing an already-light module does no damage, but safety cannot depend on
|
|
11939
|
+
* one payload or mask.
|
|
11940
|
+
*
|
|
11941
|
+
* @param {number} version QR version 1..40.
|
|
11942
|
+
* @param {object} [canvas]
|
|
11943
|
+
*/
|
|
11944
|
+
function analyzeCanvasDamage(version, canvas = {}) {
|
|
11945
|
+
if (!Number.isInteger(version) || version < 1 || version > 40) {
|
|
11946
|
+
throw new RangeError(`Frame QR profile: version must be an integer 1-40, got ${version}`);
|
|
11947
|
+
}
|
|
11948
|
+
const symbolSize = versionSize(version);
|
|
11949
|
+
const spec = normalizeCanvasSpec(symbolSize, canvas);
|
|
11950
|
+
const modules = canvasModules(symbolSize, spec);
|
|
11951
|
+
const reserved = reservedModules(version);
|
|
11952
|
+
const reservedOverlaps = modules.filter(([x, y]) => reserved.get(x, y));
|
|
11953
|
+
|
|
11954
|
+
const moduleKeys = new Set(modules.map(([x, y]) => `${x},${y}`));
|
|
11955
|
+
const order = dataModuleOrder(version);
|
|
11956
|
+
const touchedCodewords = new Set();
|
|
11957
|
+
for (let bit = 0; bit < order.length / 2; bit++) {
|
|
11958
|
+
if (moduleKeys.has(`${order[bit * 2]},${order[bit * 2 + 1]}`)) touchedCodewords.add(bit >> 3);
|
|
11959
|
+
}
|
|
11960
|
+
|
|
11961
|
+
const layout = blockLayout(version, 'H');
|
|
11962
|
+
const blockMap = codewordBlockMap(layout);
|
|
11963
|
+
const touchedByBlockSets = Array.from({ length: layout.blockCount }, () => new Set());
|
|
11964
|
+
for (const codeword of touchedCodewords) {
|
|
11965
|
+
const block = blockMap[codeword];
|
|
11966
|
+
if (block !== undefined) touchedByBlockSets[block].add(codeword);
|
|
11967
|
+
}
|
|
11968
|
+
const touchedCodewordsByBlock = touchedByBlockSets.map((set) => set.size);
|
|
11969
|
+
const correctionBudgetPerBlock = Math.floor(layout.eccPerBlock / 2);
|
|
11970
|
+
const safe = reservedOverlaps.length === 0 &&
|
|
11971
|
+
touchedCodewordsByBlock.every((count) => count <= correctionBudgetPerBlock);
|
|
11972
|
+
|
|
11973
|
+
return {
|
|
11974
|
+
profile: FRAMEQR_PROFILE.id,
|
|
11975
|
+
certified: false,
|
|
11976
|
+
version,
|
|
11977
|
+
symbolSize,
|
|
11978
|
+
canvas: spec,
|
|
11979
|
+
canvasModuleCount: modules.length,
|
|
11980
|
+
reservedOverlaps,
|
|
11981
|
+
touchedCodewordCount: touchedCodewords.size,
|
|
11982
|
+
touchedCodewordsByBlock,
|
|
11983
|
+
correctionBudgetPerBlock,
|
|
11984
|
+
safe,
|
|
11985
|
+
};
|
|
11986
|
+
}
|
|
11987
|
+
|
|
11988
|
+
/** Validate a canvas and return the non-certifying structural analysis. */
|
|
11989
|
+
function validateCanvasSpec(version, canvas = {}) {
|
|
11990
|
+
return analyzeCanvasDamage(version, canvas);
|
|
11991
|
+
}
|
|
11992
|
+
|
|
11993
|
+
/** Self-check the fixed profile contract and representative QR geometries. */
|
|
11994
|
+
function validateFrameQrTables() {
|
|
11995
|
+
const problems = [];
|
|
11996
|
+
if (FRAMEQR_PROFILE.certified !== false || FRAMEQR_PROFILE.densoFrameQrCompatible !== false) {
|
|
11997
|
+
problems.push('profile compatibility boundary must remain explicitly non-certified');
|
|
11998
|
+
}
|
|
11999
|
+
if (new Set(FRAMEQR_CANVAS_SHAPES).size !== FRAMEQR_CANVAS_SHAPES.length) {
|
|
12000
|
+
problems.push('canvas shape identifiers must be unique');
|
|
12001
|
+
}
|
|
12002
|
+
for (const version of [1, 2, 3, 4, 7, 10, 20, 30, 40]) {
|
|
12003
|
+
const size = versionSize(version);
|
|
12004
|
+
const spec = normalizeCanvasSpec(size);
|
|
12005
|
+
const modules = canvasModules(size, spec);
|
|
12006
|
+
const unique = new Set(modules.map(([x, y]) => `${x},${y}`));
|
|
12007
|
+
if (unique.size !== modules.length) problems.push(`v${version}: duplicate canvas modules`);
|
|
12008
|
+
if (modules.some(([x, y]) => x < 0 || y < 0 || x >= size || y >= size)) {
|
|
12009
|
+
problems.push(`v${version}: canvas escapes the symbol`);
|
|
12010
|
+
}
|
|
12011
|
+
const analysis = analyzeCanvasDamage(version, spec);
|
|
12012
|
+
if (analysis.touchedCodewordsByBlock.length !== blockLayout(version, 'H').blockCount) {
|
|
12013
|
+
problems.push(`v${version}: damage analysis block count mismatch`);
|
|
12014
|
+
}
|
|
12015
|
+
}
|
|
12016
|
+
return problems;
|
|
12017
|
+
}
|
|
12018
|
+
|
|
12019
|
+
__exports.FRAMEQR_PROFILE = FRAMEQR_PROFILE;
|
|
12020
|
+
__exports.FRAMEQR_CANVAS_SHAPES = FRAMEQR_CANVAS_SHAPES;
|
|
12021
|
+
__exports.normalizeCanvasSpec = normalizeCanvasSpec;
|
|
12022
|
+
__exports.canvasModules = canvasModules;
|
|
12023
|
+
__exports.analyzeCanvasDamage = analyzeCanvasDamage;
|
|
12024
|
+
__exports.validateCanvasSpec = validateCanvasSpec;
|
|
12025
|
+
__exports.validateFrameQrTables = validateFrameQrTables;
|
|
12026
|
+
};
|
|
12027
|
+
|
|
12028
|
+
__modules["frameqr/encoder.js"] = function (__require, __exports) {
|
|
12029
|
+
/**
|
|
12030
|
+
* Sythos Canvas QR profile encoder.
|
|
12031
|
+
*
|
|
12032
|
+
* This encoder deliberately builds on this project's QR Code implementation
|
|
12033
|
+
* and then removes a conservatively bounded set of data modules for artwork.
|
|
12034
|
+
* It is not an implementation of DENSO FrameQR and makes no interoperability
|
|
12035
|
+
* claim for that proprietary format.
|
|
12036
|
+
*
|
|
12037
|
+
* @module frameqr/encoder
|
|
12038
|
+
*/
|
|
12039
|
+
const { EncodeError } = __require("core/errors.js");
|
|
12040
|
+
const { encodeQR } = __require("qr/encoder.js");
|
|
12041
|
+
const { FRAMEQR_PROFILE, canvasModules, normalizeCanvasSpec, validateCanvasSpec } = __require("frameqr/tables.js");
|
|
12042
|
+
|
|
12043
|
+
/**
|
|
12044
|
+
* @typedef {object} FrameQrEncodeOptions
|
|
12045
|
+
* @property {'H'} [ecc] The profile always uses QR error correction H.
|
|
12046
|
+
* @property {number} [version] Force a QR version 1-40.
|
|
12047
|
+
* @property {number} [mask] Force a QR mask 0-7.
|
|
12048
|
+
* @property {'auto'|'utf-8'|'iso-8859-1'} [charset] Byte mode interpretation.
|
|
12049
|
+
* @property {boolean} [kanji] Allow QR kanji mode.
|
|
12050
|
+
* @property {object} [canvas] Profile artwork reservation.
|
|
12051
|
+
*/
|
|
12052
|
+
|
|
12053
|
+
function versionFor(matrix) {
|
|
12054
|
+
return (matrix.width - 17) / 4;
|
|
12055
|
+
}
|
|
12056
|
+
|
|
12057
|
+
function clearCanvas(matrix, modules) {
|
|
12058
|
+
for (const [x, y] of modules) matrix.unset(x, y);
|
|
12059
|
+
}
|
|
12060
|
+
|
|
12061
|
+
/**
|
|
12062
|
+
* Encode a QR Code with a conservative artwork canvas according to the
|
|
12063
|
+
* non-certified Sythos Canvas QR profile.
|
|
12064
|
+
*
|
|
12065
|
+
* The profile forces QR H error correction and rejects a canvas whenever its
|
|
12066
|
+
* known codeword damage exceeds the per-block correction budget. When a
|
|
12067
|
+
* version is not forced, the smallest QR version that holds both payload and
|
|
12068
|
+
* safe canvas is selected. A decoder can reconstruct the reserved modules from
|
|
12069
|
+
* the returned profile metadata.
|
|
12070
|
+
*
|
|
12071
|
+
* @param {string} text
|
|
12072
|
+
* @param {FrameQrEncodeOptions} [options]
|
|
12073
|
+
* @returns {import('../core/bit-matrix.js').BitMatrix}
|
|
12074
|
+
* @throws {EncodeError} When the QR payload/options are invalid or the canvas
|
|
12075
|
+
* cannot safely fit the selected QR version.
|
|
12076
|
+
*/
|
|
12077
|
+
function encodeFrameQR(text, options = {}) {
|
|
12078
|
+
if (typeof text !== 'string') {
|
|
12079
|
+
throw new EncodeError('Sythos Canvas QR: text must be a string');
|
|
12080
|
+
}
|
|
12081
|
+
if (options.ecc !== undefined && options.ecc !== 'H') {
|
|
12082
|
+
throw new EncodeError('Sythos Canvas QR: ecc is fixed to H for this profile');
|
|
12083
|
+
}
|
|
12084
|
+
|
|
12085
|
+
const qrOptions = {
|
|
12086
|
+
mask: options.mask,
|
|
12087
|
+
charset: options.charset,
|
|
12088
|
+
kanji: options.kanji,
|
|
12089
|
+
ecc: 'H',
|
|
12090
|
+
};
|
|
12091
|
+
for (const key of Object.keys(qrOptions)) {
|
|
12092
|
+
if (qrOptions[key] === undefined) delete qrOptions[key];
|
|
12093
|
+
}
|
|
12094
|
+
|
|
12095
|
+
const versions = options.version === undefined
|
|
12096
|
+
? Array.from({ length: 40 }, (_, index) => index + 1)
|
|
12097
|
+
: [options.version];
|
|
12098
|
+
let capacityError = null;
|
|
12099
|
+
let unsafeAnalysis = null;
|
|
12100
|
+
let selected = null;
|
|
12101
|
+
|
|
12102
|
+
for (const version of versions) {
|
|
12103
|
+
let matrix;
|
|
12104
|
+
try {
|
|
12105
|
+
matrix = encodeQR(text, { ...qrOptions, version });
|
|
12106
|
+
} catch (error) {
|
|
12107
|
+
capacityError = error;
|
|
12108
|
+
continue;
|
|
12109
|
+
}
|
|
12110
|
+
|
|
12111
|
+
let canvas;
|
|
12112
|
+
let analysis;
|
|
12113
|
+
try {
|
|
12114
|
+
canvas = normalizeCanvasSpec(matrix.width, options.canvas);
|
|
12115
|
+
analysis = validateCanvasSpec(versionFor(matrix), canvas);
|
|
12116
|
+
} catch (error) {
|
|
12117
|
+
if (error instanceof EncodeError) throw error;
|
|
12118
|
+
throw new EncodeError(`Sythos Canvas QR: invalid canvas: ${error.message}`);
|
|
12119
|
+
}
|
|
12120
|
+
if (!analysis.safe) {
|
|
12121
|
+
unsafeAnalysis = analysis;
|
|
12122
|
+
continue;
|
|
12123
|
+
}
|
|
12124
|
+
selected = { matrix, canvas };
|
|
12125
|
+
break;
|
|
12126
|
+
}
|
|
12127
|
+
|
|
12128
|
+
if (!selected) {
|
|
12129
|
+
if (unsafeAnalysis) {
|
|
12130
|
+
throw new EncodeError(
|
|
12131
|
+
'Sythos Canvas QR: canvas is not safe for the selected QR version; ' +
|
|
12132
|
+
`it touches ${unsafeAnalysis.touchedCodewordCount} codewords and has ` +
|
|
12133
|
+
`a per-block correction budget of ${unsafeAnalysis.correctionBudgetPerBlock}`
|
|
12134
|
+
);
|
|
12135
|
+
}
|
|
12136
|
+
if (capacityError) throw capacityError;
|
|
12137
|
+
throw new EncodeError('Sythos Canvas QR: unable to select a QR version');
|
|
12138
|
+
}
|
|
12139
|
+
|
|
12140
|
+
const { matrix, canvas } = selected;
|
|
12141
|
+
clearCanvas(matrix, canvasModules(matrix.width, canvas));
|
|
12142
|
+
matrix.frameqr = {
|
|
12143
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12144
|
+
certified: false,
|
|
12145
|
+
canvas,
|
|
12146
|
+
};
|
|
12147
|
+
return matrix;
|
|
12148
|
+
}
|
|
12149
|
+
|
|
12150
|
+
__exports.encodeFrameQR = encodeFrameQR;
|
|
12151
|
+
};
|
|
12152
|
+
|
|
12153
|
+
__modules["frameqr/decoder.js"] = function (__require, __exports) {
|
|
12154
|
+
/**
|
|
12155
|
+
* Decoder for the Sythos Canvas QR profile.
|
|
12156
|
+
*
|
|
12157
|
+
* This is intentionally not a DENSO FrameQR decoder. The profile is a QR
|
|
12158
|
+
* symbol with a bounded artwork reservation and an explicit `frameqr` marker.
|
|
12159
|
+
* A normal QR matrix is rejected unless a detector (or another trusted
|
|
12160
|
+
* caller) supplies the profile and explicitly opts in to an unmarked matrix.
|
|
12161
|
+
*
|
|
12162
|
+
* The QR decoder is deliberately kept as the single payload decoder. It can
|
|
12163
|
+
* repair the bounded modules removed for the canvas because the encoder fixes
|
|
12164
|
+
* error correction to H and validates the reservation before clearing it.
|
|
12165
|
+
*
|
|
12166
|
+
* @module frameqr/decoder
|
|
12167
|
+
*/
|
|
12168
|
+
const { FormatError } = __require("core/errors.js");
|
|
12169
|
+
const { decodeQR } = __require("qr/decoder.js");
|
|
12170
|
+
const { FRAMEQR_PROFILE, normalizeCanvasSpec, validateCanvasSpec, analyzeCanvasDamage } = __require("frameqr/tables.js");
|
|
12171
|
+
|
|
12172
|
+
/** @param {unknown} value @returns {boolean} */
|
|
12173
|
+
function isObject(value) {
|
|
12174
|
+
return value !== null && typeof value === 'object';
|
|
12175
|
+
}
|
|
12176
|
+
|
|
12177
|
+
/**
|
|
12178
|
+
* Return the profile marker attached by the encoder. Older callers sometimes
|
|
12179
|
+
* use camel case, so accepting it here is harmless while the emitted marker
|
|
12180
|
+
* remains the canonical `frameqr` property.
|
|
12181
|
+
*
|
|
12182
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12183
|
+
* @returns {object|null}
|
|
12184
|
+
*/
|
|
12185
|
+
function markerOf(matrix) {
|
|
12186
|
+
const marker = matrix && (matrix.frameqr ?? matrix.frameQR);
|
|
12187
|
+
return isObject(marker) ? marker : null;
|
|
12188
|
+
}
|
|
12189
|
+
|
|
12190
|
+
/** @param {unknown} profile @returns {boolean} */
|
|
12191
|
+
function isExpectedProfile(profile) {
|
|
12192
|
+
if (profile === FRAMEQR_PROFILE.id) return true;
|
|
12193
|
+
return isObject(profile) && profile.id === FRAMEQR_PROFILE.id;
|
|
12194
|
+
}
|
|
12195
|
+
|
|
12196
|
+
/**
|
|
12197
|
+
* Normalize and validate the canvas metadata. Validation functions in the
|
|
12198
|
+
* table module throw on invalid input; a few consumers also return `false` or
|
|
12199
|
+
* a problem list, so those forms are handled defensively here.
|
|
12200
|
+
*
|
|
12201
|
+
* @param {number} symbolSize
|
|
12202
|
+
* @param {number} version
|
|
12203
|
+
* @param {object} canvas
|
|
12204
|
+
* @returns {{canvas: object, damage: object}}
|
|
12205
|
+
*/
|
|
12206
|
+
function validateCanvas(symbolSize, version, canvas) {
|
|
12207
|
+
if (!isObject(canvas)) throw new FormatError('Sythos Canvas QR: canvas metadata is missing');
|
|
12208
|
+
|
|
12209
|
+
let normalized;
|
|
12210
|
+
try {
|
|
12211
|
+
normalized = normalizeCanvasSpec(symbolSize, canvas);
|
|
12212
|
+
const validation = validateCanvasSpec(version, normalized);
|
|
12213
|
+
if (
|
|
12214
|
+
validation === false ||
|
|
12215
|
+
(Array.isArray(validation) && validation.length > 0) ||
|
|
12216
|
+
(isObject(validation) && validation.valid === false) ||
|
|
12217
|
+
(isObject(validation) && validation.safe === false)
|
|
12218
|
+
) {
|
|
12219
|
+
throw new Error('canvas geometry is outside the profile limits');
|
|
12220
|
+
}
|
|
12221
|
+
} catch (error) {
|
|
12222
|
+
if (error instanceof FormatError) throw error;
|
|
12223
|
+
throw new FormatError(`Sythos Canvas QR: invalid canvas metadata: ${error.message}`);
|
|
12224
|
+
}
|
|
12225
|
+
|
|
12226
|
+
let damage;
|
|
12227
|
+
try {
|
|
12228
|
+
damage = analyzeCanvasDamage(version, normalized);
|
|
12229
|
+
} catch (error) {
|
|
12230
|
+
throw new FormatError(`Sythos Canvas QR: cannot analyse canvas damage: ${error.message}`);
|
|
12231
|
+
}
|
|
12232
|
+
return { canvas: normalized, damage };
|
|
12233
|
+
}
|
|
12234
|
+
|
|
12235
|
+
/**
|
|
12236
|
+
* Resolve the profile marker and canvas from the matrix/options pair.
|
|
12237
|
+
*
|
|
12238
|
+
* `allowUnmarked` is deliberately opt-in. It exists for a detector that has
|
|
12239
|
+
* already verified the canvas signature after sampling a photograph; it is
|
|
12240
|
+
* not enabled by the public default and therefore cannot silently relabel an
|
|
12241
|
+
* ordinary QR Code as FrameQR.
|
|
12242
|
+
*
|
|
12243
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12244
|
+
* @param {object} options
|
|
12245
|
+
* @returns {{profile: string, canvas: object, damage: object, certified: false}}
|
|
12246
|
+
*/
|
|
12247
|
+
function resolveProfile(matrix, options) {
|
|
12248
|
+
const marker = markerOf(matrix);
|
|
12249
|
+
const suppliedProfile = options.profile;
|
|
12250
|
+
const markerProfile = marker ? marker.profile : undefined;
|
|
12251
|
+
if (markerProfile !== undefined && suppliedProfile !== undefined) {
|
|
12252
|
+
const markerId = isObject(markerProfile) ? markerProfile.id : markerProfile;
|
|
12253
|
+
const suppliedId = isObject(suppliedProfile) ? suppliedProfile.id : suppliedProfile;
|
|
12254
|
+
if (markerId !== suppliedId) {
|
|
12255
|
+
throw new FormatError('Sythos Canvas QR: matrix and requested profile markers disagree');
|
|
12256
|
+
}
|
|
12257
|
+
}
|
|
12258
|
+
const profile = markerProfile ?? suppliedProfile;
|
|
12259
|
+
|
|
12260
|
+
if (!isExpectedProfile(profile)) {
|
|
12261
|
+
throw new FormatError(
|
|
12262
|
+
'Sythos Canvas QR: profile marker is missing or is not the Sythos Canvas QR profile'
|
|
12263
|
+
);
|
|
12264
|
+
}
|
|
12265
|
+
if (marker && marker.certified === true) {
|
|
12266
|
+
throw new FormatError(
|
|
12267
|
+
'Sythos Canvas QR: certified FrameQR input is not supported by this profile decoder'
|
|
12268
|
+
);
|
|
12269
|
+
}
|
|
12270
|
+
if (!marker && !options.allowUnmarked) {
|
|
12271
|
+
throw new FormatError(
|
|
12272
|
+
'Sythos Canvas QR: unmarked QR matrix rejected; provide a verified profile marker'
|
|
12273
|
+
);
|
|
12274
|
+
}
|
|
12275
|
+
|
|
12276
|
+
const markerCanvas = marker ? marker.canvas : undefined;
|
|
12277
|
+
const canvas = options.canvas ?? markerCanvas;
|
|
12278
|
+
const version = (matrix.width - 17) / 4;
|
|
12279
|
+
if (!Number.isInteger(version) || version < 1 || version > 40) {
|
|
12280
|
+
throw new FormatError(`Sythos Canvas QR: invalid QR symbol size ${matrix.width}`);
|
|
12281
|
+
}
|
|
12282
|
+
const checked = validateCanvas(matrix.width, version, canvas);
|
|
12283
|
+
if (markerCanvas && options.canvas) {
|
|
12284
|
+
let marked;
|
|
12285
|
+
try {
|
|
12286
|
+
marked = normalizeCanvasSpec(matrix.width, markerCanvas);
|
|
12287
|
+
} catch (error) {
|
|
12288
|
+
throw new FormatError(`Sythos Canvas QR: invalid marker canvas: ${error.message}`);
|
|
12289
|
+
}
|
|
12290
|
+
const fields = ['shape', 'centerX', 'centerY', 'width', 'height', 'angle'];
|
|
12291
|
+
if (fields.some((field) => marked[field] !== checked.canvas[field])) {
|
|
12292
|
+
throw new FormatError('Sythos Canvas QR: matrix and requested canvas metadata disagree');
|
|
12293
|
+
}
|
|
12294
|
+
}
|
|
12295
|
+
return {
|
|
12296
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12297
|
+
canvas: checked.canvas,
|
|
12298
|
+
damage: checked.damage,
|
|
12299
|
+
certified: false,
|
|
12300
|
+
};
|
|
12301
|
+
}
|
|
12302
|
+
|
|
12303
|
+
/**
|
|
12304
|
+
* Decode a Sythos Canvas QR matrix.
|
|
12305
|
+
*
|
|
12306
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12307
|
+
* Square QR modules, normally returned by `encodeFrameQR` or a FrameQR
|
|
12308
|
+
* detector. The encoder's `frameqr` metadata is required by default.
|
|
12309
|
+
* @param {object} [options]
|
|
12310
|
+
* @param {object} [options.canvas] Explicit canvas metadata for a sampled
|
|
12311
|
+
* matrix when the source marker was not preserved.
|
|
12312
|
+
* @param {string|object} [options.profile] Expected profile identifier.
|
|
12313
|
+
* @param {boolean} [options.allowUnmarked=false] Explicit detector opt-in for
|
|
12314
|
+
* a matrix whose marker was lost during image sampling.
|
|
12315
|
+
* @returns {import('../qr/decoder.js').DecodeResult & {
|
|
12316
|
+
* format: 'frameqr', profile: string, certified: false,
|
|
12317
|
+
* frame: object, canvas: object, canvasDamage: object
|
|
12318
|
+
* }}
|
|
12319
|
+
* @throws {FormatError} If the profile marker/canvas is invalid or the input
|
|
12320
|
+
* is an ordinary QR Code.
|
|
12321
|
+
*/
|
|
12322
|
+
function decodeFrameQR(matrix, options = {}) {
|
|
12323
|
+
if (!matrix || !Number.isInteger(matrix.width) || !Number.isInteger(matrix.height)) {
|
|
12324
|
+
throw new FormatError('Sythos Canvas QR: no matrix supplied');
|
|
12325
|
+
}
|
|
12326
|
+
if (matrix.width !== matrix.height) {
|
|
12327
|
+
throw new FormatError(
|
|
12328
|
+
`Sythos Canvas QR: symbol must be square, got ${matrix.width}x${matrix.height}`
|
|
12329
|
+
);
|
|
12330
|
+
}
|
|
12331
|
+
|
|
12332
|
+
const profile = resolveProfile(matrix, options);
|
|
12333
|
+
let decoded;
|
|
12334
|
+
try {
|
|
12335
|
+
decoded = decodeQR(matrix);
|
|
12336
|
+
} catch (error) {
|
|
12337
|
+
// Preserve the original QR/RS error when possible, but give callers a
|
|
12338
|
+
// profile-specific context without exposing implementation details.
|
|
12339
|
+
if (error instanceof FormatError) {
|
|
12340
|
+
throw new FormatError(`Sythos Canvas QR: payload is not recoverable: ${error.message}`);
|
|
12341
|
+
}
|
|
12342
|
+
throw error;
|
|
12343
|
+
}
|
|
12344
|
+
|
|
12345
|
+
return {
|
|
12346
|
+
...decoded,
|
|
12347
|
+
format: 'frameqr',
|
|
12348
|
+
profile: profile.profile,
|
|
12349
|
+
certified: profile.certified,
|
|
12350
|
+
frame: profile.canvas,
|
|
12351
|
+
canvas: profile.canvas,
|
|
12352
|
+
canvasDamage: profile.damage,
|
|
12353
|
+
};
|
|
12354
|
+
}
|
|
12355
|
+
__exports.isExpectedProfile = isExpectedProfile;
|
|
12356
|
+
|
|
12357
|
+
__exports.decodeFrameQR = decodeFrameQR;
|
|
12358
|
+
};
|
|
12359
|
+
|
|
12360
|
+
__modules["frameqr/detector.js"] = function (__require, __exports) {
|
|
12361
|
+
/**
|
|
12362
|
+
* Detector for the non-certified Sythos Canvas QR profile.
|
|
12363
|
+
*
|
|
12364
|
+
* The profile deliberately reuses QR Model 2 geometry. Finder localisation and
|
|
12365
|
+
* projective sampling therefore use the QR detector; the additional profile
|
|
12366
|
+
* check is the light canvas signature. A sampled image cannot carry the
|
|
12367
|
+
* encoder's in-memory marker, so the detector reconstructs the expected
|
|
12368
|
+
* metadata, verifies that every reserved canvas module is light, and only then
|
|
12369
|
+
* opts into the profile decoder. This rejects ordinary QR symbols whose centre
|
|
12370
|
+
* merely happens to contain a plausible payload.
|
|
12371
|
+
*
|
|
12372
|
+
* Detection is verified for clean binarized rasters, integer scaling, quiet
|
|
12373
|
+
* zones, and in-plane quarter turns. Arbitrary photographic perspective is not
|
|
12374
|
+
* claimed by this module.
|
|
12375
|
+
*
|
|
12376
|
+
* @module frameqr/detector
|
|
12377
|
+
*/
|
|
12378
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
12379
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
12380
|
+
const { detectQR } = __require("qr/detector.js");
|
|
12381
|
+
const { decodeFrameQR } = __require("frameqr/decoder.js");
|
|
12382
|
+
const { FRAMEQR_PROFILE, canvasModules, normalizeCanvasSpec } = __require("frameqr/tables.js");
|
|
12383
|
+
|
|
12384
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
12385
|
+
|
|
12386
|
+
/** @typedef {object} FrameQRDetection
|
|
12387
|
+
* @property {Point[]} corners Outer corners in reading order.
|
|
12388
|
+
* @property {number} dimension QR modules per side.
|
|
12389
|
+
* @property {number} version QR Model 2 version.
|
|
12390
|
+
* @property {number} moduleSize Estimated pixels per module.
|
|
12391
|
+
* @property {number} rotation Clockwise in-plane orientation in degrees.
|
|
12392
|
+
* @property {BitMatrix} matrix Rectified profile matrix.
|
|
12393
|
+
* @property {object} canvas Normalized canvas specification.
|
|
12394
|
+
* @property {string} profile Profile identifier.
|
|
12395
|
+
* @property {false} certified Always false for this implementation.
|
|
12396
|
+
*/
|
|
12397
|
+
|
|
12398
|
+
function orientationDegrees(corners) {
|
|
12399
|
+
const [tl, tr] = corners;
|
|
12400
|
+
const angle = Math.atan2(tr.y - tl.y, tr.x - tl.x) * 180 / Math.PI;
|
|
12401
|
+
return ((Math.round(angle / 90) * 90) % 360 + 360) % 360;
|
|
12402
|
+
}
|
|
12403
|
+
/**
|
|
12404
|
+
* Return the number of canvas modules that are dark in a sampled matrix.
|
|
12405
|
+
*
|
|
12406
|
+
* Encoded profile symbols clear the whole reserved canvas. A non-zero count
|
|
12407
|
+
* means either a normal QR symbol or a raster whose canvas was overwritten by
|
|
12408
|
+
* artwork; both are rejected because decoding that image would be speculative.
|
|
12409
|
+
*/
|
|
12410
|
+
function canvasDarkCount(matrix, canvas) {
|
|
12411
|
+
let dark = 0;
|
|
12412
|
+
for (const [x, y] of canvasModules(matrix.width, canvas)) {
|
|
12413
|
+
if (matrix.get(x, y)) dark++;
|
|
12414
|
+
}
|
|
12415
|
+
return dark;
|
|
12416
|
+
}
|
|
12417
|
+
|
|
12418
|
+
function sameCandidate(left, right) {
|
|
12419
|
+
if (left.dimension !== right.dimension) return false;
|
|
12420
|
+
const a = left.corners[0];
|
|
12421
|
+
const b = right.corners[0];
|
|
12422
|
+
return Math.hypot(a.x - b.x, a.y - b.y) <= Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
12423
|
+
}
|
|
12424
|
+
|
|
12425
|
+
/**
|
|
12426
|
+
* Detect Sythos Canvas QR symbols in a binarized raster.
|
|
12427
|
+
*
|
|
12428
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
|
|
12429
|
+
* @param {object} [options]
|
|
12430
|
+
* @param {object} [options.canvas] Explicit canvas metadata for non-default
|
|
12431
|
+
* shapes/size. Without it, the profile's canonical centered square is used.
|
|
12432
|
+
* @param {boolean} [options.voting=false] Use majority sampling per module.
|
|
12433
|
+
* @returns {FrameQRDetection[]} Best candidate first; empty when no verified
|
|
12434
|
+
* profile signature is found.
|
|
12435
|
+
*/
|
|
12436
|
+
function detectFrameQR(binaryImage, options = {}) {
|
|
12437
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
12438
|
+
throw new NotFoundError('detectFrameQR: no image supplied');
|
|
12439
|
+
}
|
|
12440
|
+
|
|
12441
|
+
let candidates;
|
|
12442
|
+
try { candidates = detectQR(binaryImage); } catch { return []; }
|
|
12443
|
+
const detections = [];
|
|
12444
|
+
|
|
12445
|
+
for (const candidate of candidates) {
|
|
12446
|
+
// QR detector dimensions are already constrained to legal Model 2 sizes.
|
|
12447
|
+
let canvas;
|
|
12448
|
+
try { canvas = normalizeCanvasSpec(candidate.dimension, options.canvas); }
|
|
12449
|
+
catch { continue; }
|
|
12450
|
+
|
|
12451
|
+
for (const voting of [Boolean(options.voting), !Boolean(options.voting)]) {
|
|
12452
|
+
let matrix;
|
|
12453
|
+
try { matrix = sampleQuad(binaryImage, candidate.dimension, candidate.corners, voting); }
|
|
12454
|
+
catch { continue; }
|
|
12455
|
+
|
|
12456
|
+
// The signature check is intentionally strict. It prevents an ordinary
|
|
12457
|
+
// QR symbol from being relabelled as Canvas QR by the decoder's explicit
|
|
12458
|
+
// allowUnmarked escape hatch.
|
|
12459
|
+
if (canvasDarkCount(matrix, canvas) !== 0) continue;
|
|
12460
|
+
|
|
12461
|
+
const marked = matrix;
|
|
12462
|
+
marked.frameqr = {
|
|
12463
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12464
|
+
certified: false,
|
|
12465
|
+
canvas,
|
|
12466
|
+
};
|
|
12467
|
+
let decoded;
|
|
12468
|
+
try {
|
|
12469
|
+
decoded = decodeFrameQR(marked, {
|
|
12470
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12471
|
+
canvas,
|
|
12472
|
+
allowUnmarked: true,
|
|
12473
|
+
});
|
|
12474
|
+
} catch { continue; }
|
|
12475
|
+
|
|
12476
|
+
const detection = {
|
|
12477
|
+
corners: candidate.corners,
|
|
12478
|
+
dimension: candidate.dimension,
|
|
12479
|
+
version: candidate.version,
|
|
12480
|
+
moduleSize: candidate.moduleSize,
|
|
12481
|
+
rotation: orientationDegrees(candidate.corners),
|
|
12482
|
+
matrix: marked,
|
|
12483
|
+
canvas,
|
|
12484
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12485
|
+
certified: false,
|
|
12486
|
+
result: decoded,
|
|
12487
|
+
};
|
|
12488
|
+
if (!detections.some((entry) => sameCandidate(entry, detection))) detections.push(detection);
|
|
12489
|
+
break;
|
|
12490
|
+
}
|
|
12491
|
+
}
|
|
12492
|
+
|
|
12493
|
+
detections.sort((a, b) => b.moduleSize - a.moduleSize);
|
|
12494
|
+
return detections;
|
|
12495
|
+
}
|
|
12496
|
+
|
|
12497
|
+
/**
|
|
12498
|
+
* Detect and decode all verified Canvas QR symbols in one call.
|
|
12499
|
+
*
|
|
12500
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
12501
|
+
* @param {object} [options]
|
|
12502
|
+
* @returns {Array<object>}
|
|
12503
|
+
*/
|
|
12504
|
+
function detectAndDecodeFrameQR(binaryImage, options = {}) {
|
|
12505
|
+
let detections;
|
|
12506
|
+
try { detections = detectFrameQR(binaryImage, options); } catch { return []; }
|
|
12507
|
+
const results = [];
|
|
12508
|
+
const seen = new Set();
|
|
12509
|
+
for (const detection of detections) {
|
|
12510
|
+
const result = detection.result;
|
|
12511
|
+
const key = `${result.version}|${result.text}`;
|
|
12512
|
+
if (seen.has(key)) continue;
|
|
12513
|
+
seen.add(key);
|
|
12514
|
+
results.push({ ...result, corners: detection.corners, rotation: detection.rotation });
|
|
12515
|
+
}
|
|
12516
|
+
return results;
|
|
12517
|
+
}
|
|
12518
|
+
|
|
12519
|
+
__exports.detectFrameQR = detectFrameQR;
|
|
12520
|
+
__exports.detectAndDecodeFrameQR = detectAndDecodeFrameQR;
|
|
12521
|
+
};
|
|
12522
|
+
|
|
12523
|
+
__modules["frameqr/index.js"] = function (__require, __exports) {
|
|
12524
|
+
const __reexport0 = __require("frameqr/encoder.js"); __exports.encodeFrameQR = __reexport0.encodeFrameQR;
|
|
12525
|
+
const __reexport1 = __require("frameqr/decoder.js"); __exports.decodeFrameQR = __reexport1.decodeFrameQR;
|
|
12526
|
+
const __reexport2 = __require("frameqr/detector.js"); __exports.detectFrameQR = __reexport2.detectFrameQR; __exports.detectAndDecodeFrameQR = __reexport2.detectAndDecodeFrameQR;
|
|
12527
|
+
const __reexport3 = __require("frameqr/tables.js"); __exports.FRAMEQR_PROFILE = __reexport3.FRAMEQR_PROFILE; __exports.FRAMEQR_CANVAS_SHAPES = __reexport3.FRAMEQR_CANVAS_SHAPES; __exports.canvasModules = __reexport3.canvasModules; __exports.normalizeCanvasSpec = __reexport3.normalizeCanvasSpec; __exports.analyzeCanvasDamage = __reexport3.analyzeCanvasDamage; __exports.validateCanvasSpec = __reexport3.validateCanvasSpec; __exports.validateFrameQrTables = __reexport3.validateFrameQrTables;
|
|
12528
|
+
|
|
12529
|
+
|
|
10255
12530
|
};
|
|
10256
12531
|
|
|
10257
12532
|
__modules["render/options.js"] = function (__require, __exports) {
|
|
@@ -11512,6 +13787,9 @@ const qr = __require("qr/index.js");
|
|
|
11512
13787
|
const aztec = __require("aztec/index.js");
|
|
11513
13788
|
const pdf417 = __require("pdf417/index.js");
|
|
11514
13789
|
const micropdf417 = __require("micropdf417/index.js");
|
|
13790
|
+
const microqr = __require("microqr/index.js");
|
|
13791
|
+
const rmqr = __require("rmqr/index.js");
|
|
13792
|
+
const frameqr = __require("frameqr/index.js");
|
|
11515
13793
|
__exports.BitMatrix = BitMatrix;
|
|
11516
13794
|
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;
|
|
11517
13795
|
const __reexport1 = __require("image/luminance.js"); __exports.LuminanceSource = __reexport1.LuminanceSource;
|
|
@@ -11527,6 +13805,9 @@ const __reexport9 = __require("datamatrix/index.js"); __exports.encodeDataMatrix
|
|
|
11527
13805
|
const __reexport10 = __require("aztec/index.js"); __exports.encodeAztec = __reexport10.encodeAztec; __exports.decodeAztec = __reexport10.decodeAztec; __exports.detectAztec = __reexport10.detectAztec; __exports.detectAndDecodeAztec = __reexport10.detectAndDecodeAztec;
|
|
11528
13806
|
const __reexport11 = __require("pdf417/index.js"); __exports.encodePDF417 = __reexport11.encodePDF417; __exports.decodePDF417 = __reexport11.decodePDF417; __exports.detectPDF417 = __reexport11.detectPDF417; __exports.detectAndDecodePDF417 = __reexport11.detectAndDecodePDF417;
|
|
11529
13807
|
const __reexport12 = __require("micropdf417/index.js"); __exports.encodeMicroPDF417 = __reexport12.encodeMicroPDF417; __exports.decodeMicroPDF417 = __reexport12.decodeMicroPDF417; __exports.detectMicroPDF417 = __reexport12.detectMicroPDF417; __exports.detectAndDecodeMicroPDF417 = __reexport12.detectAndDecodeMicroPDF417;
|
|
13808
|
+
const __reexport13 = __require("microqr/index.js"); __exports.encodeMicroQR = __reexport13.encodeMicroQR; __exports.decodeMicroQR = __reexport13.decodeMicroQR; __exports.detectMicroQR = __reexport13.detectMicroQR; __exports.detectAndDecodeMicroQR = __reexport13.detectAndDecodeMicroQR;
|
|
13809
|
+
const __reexport14 = __require("rmqr/index.js"); __exports.encodeRMQR = __reexport14.encodeRMQR; __exports.decodeRMQR = __reexport14.decodeRMQR; __exports.detectRMQR = __reexport14.detectRMQR; __exports.detectAndDecodeRMQR = __reexport14.detectAndDecodeRMQR;
|
|
13810
|
+
const __reexport15 = __require("frameqr/index.js"); __exports.encodeFrameQR = __reexport15.encodeFrameQR; __exports.decodeFrameQR = __reexport15.decodeFrameQR; __exports.detectFrameQR = __reexport15.detectFrameQR; __exports.detectAndDecodeFrameQR = __reexport15.detectAndDecodeFrameQR;
|
|
11530
13811
|
|
|
11531
13812
|
/**
|
|
11532
13813
|
* @typedef {object} FormatInfo
|
|
@@ -11562,6 +13843,12 @@ const pdf417CanEncode = typeof pdf417.encodePDF417 === 'function';
|
|
|
11562
13843
|
const pdf417CanDecode = typeof pdf417.detectAndDecodePDF417 === 'function';
|
|
11563
13844
|
const microPdf417CanEncode = typeof micropdf417.encodeMicroPDF417 === 'function';
|
|
11564
13845
|
const microPdf417CanDecode = typeof micropdf417.detectAndDecodeMicroPDF417 === 'function';
|
|
13846
|
+
const microQrCanEncode = typeof microqr.encodeMicroQR === 'function';
|
|
13847
|
+
const microQrCanDecode = typeof microqr.detectAndDecodeMicroQR === 'function';
|
|
13848
|
+
const rmqrCanEncode = typeof rmqr.encodeRMQR === 'function';
|
|
13849
|
+
const rmqrCanDecode = typeof rmqr.detectAndDecodeRMQR === 'function';
|
|
13850
|
+
const frameQrCanEncode = typeof frameqr.encodeFrameQR === 'function';
|
|
13851
|
+
const frameQrCanDecode = typeof frameqr.detectAndDecodeFrameQR === 'function';
|
|
11565
13852
|
|
|
11566
13853
|
/**
|
|
11567
13854
|
* Every format this build supports.
|
|
@@ -11617,6 +13904,27 @@ function listFormats() {
|
|
|
11617
13904
|
canRead: microPdf417CanDecode,
|
|
11618
13905
|
kind: /** @type {'2D'} */ ('2D'),
|
|
11619
13906
|
});
|
|
13907
|
+
formats.push({
|
|
13908
|
+
id: 'microqr',
|
|
13909
|
+
label: 'Micro QR Code',
|
|
13910
|
+
canWrite: microQrCanEncode,
|
|
13911
|
+
canRead: microQrCanDecode,
|
|
13912
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
13913
|
+
});
|
|
13914
|
+
formats.push({
|
|
13915
|
+
id: 'rmqr',
|
|
13916
|
+
label: 'rMQR Code',
|
|
13917
|
+
canWrite: rmqrCanEncode,
|
|
13918
|
+
canRead: rmqrCanDecode,
|
|
13919
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
13920
|
+
});
|
|
13921
|
+
formats.push({
|
|
13922
|
+
id: 'frameqr',
|
|
13923
|
+
label: 'Sythos Canvas QR profile',
|
|
13924
|
+
canWrite: frameQrCanEncode,
|
|
13925
|
+
canRead: frameQrCanDecode,
|
|
13926
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
13927
|
+
});
|
|
11620
13928
|
|
|
11621
13929
|
return formats;
|
|
11622
13930
|
}
|
|
@@ -11647,6 +13955,14 @@ function listFormats() {
|
|
|
11647
13955
|
* @param {'auto'|'text'|'byte'|'numeric'} [options.compaction] PDF417 compaction mode.
|
|
11648
13956
|
* @param {number} [options.eci] MicroPDF417 byte-compaction ECI assignment (3 or 26).
|
|
11649
13957
|
* @param {number} [options.aspectRatio] Preferred MicroPDF417 symbol aspect ratio.
|
|
13958
|
+
* @param {object} [options.canvas] Sythos Canvas QR artwork reservation.
|
|
13959
|
+
* @param {'square'|'circle'|'diamond'} [options.canvas.shape] Canvas shape.
|
|
13960
|
+
* @param {number} [options.canvas.size] Odd canvas size in QR modules.
|
|
13961
|
+
* @param {number} [options.canvas.width] Canvas width in QR modules.
|
|
13962
|
+
* @param {number} [options.canvas.height] Canvas height in QR modules.
|
|
13963
|
+
* @param {number} [options.canvas.centerX] Canvas centre X in QR modules.
|
|
13964
|
+
* @param {number} [options.canvas.centerY] Canvas centre Y in QR modules.
|
|
13965
|
+
* @param {0|90|180|270} [options.canvas.angle] Canvas quarter-turn.
|
|
11650
13966
|
* @returns {BitMatrix}
|
|
11651
13967
|
*/
|
|
11652
13968
|
function encode(text, options = {}) {
|
|
@@ -11668,10 +13984,19 @@ function encode(text, options = {}) {
|
|
|
11668
13984
|
if (format === 'micropdf417' || format === 'micro-pdf417' || format === 'micro-pdf-417') {
|
|
11669
13985
|
return micropdf417.encodeMicroPDF417(value, options);
|
|
11670
13986
|
}
|
|
13987
|
+
if (format === 'microqr' || format === 'micro-qr') {
|
|
13988
|
+
return microqr.encodeMicroQR(value, options);
|
|
13989
|
+
}
|
|
13990
|
+
if (format === 'rmqr' || format === 'r-mqr' || format === 'rectangular-micro-qr') {
|
|
13991
|
+
return rmqr.encodeRMQR(value, options);
|
|
13992
|
+
}
|
|
13993
|
+
if (format === 'frameqr' || format === 'frame-qr' || format === 'canvas-qr') {
|
|
13994
|
+
return frameqr.encodeFrameQR(value, options);
|
|
13995
|
+
}
|
|
11671
13996
|
|
|
11672
13997
|
const entry = ONED_FORMATS[format];
|
|
11673
13998
|
if (!entry) {
|
|
11674
|
-
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'pdf417', 'micropdf417'].join(', ');
|
|
13999
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'pdf417', 'micropdf417', 'microqr', 'rmqr', 'frameqr'].join(', ');
|
|
11675
14000
|
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
11676
14001
|
}
|
|
11677
14002
|
return entry.encode(value, options);
|
|
@@ -11694,6 +14019,9 @@ function encode(text, options = {}) {
|
|
|
11694
14019
|
* @property {number} [rowHeight] PDF417 row height in modules.
|
|
11695
14020
|
* @property {number} [variant] MicroPDF417 predefined variant number.
|
|
11696
14021
|
* @property {number} [eccCodewords] MicroPDF417 fixed error-correction codewords.
|
|
14022
|
+
* @property {string} [profile] Sythos Canvas QR profile identifier.
|
|
14023
|
+
* @property {boolean} [certified] Whether the profile is certified by its originator.
|
|
14024
|
+
* @property {object} [canvas] Canvas reservation metadata for the Sythos profile.
|
|
11697
14025
|
*/
|
|
11698
14026
|
|
|
11699
14027
|
/**
|
|
@@ -11708,6 +14036,8 @@ function encode(text, options = {}) {
|
|
|
11708
14036
|
* @param {string[]} [options.formats] Restrict to these format ids.
|
|
11709
14037
|
* @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
|
|
11710
14038
|
* @param {'global'|'hybrid'|'auto'} [options.binarizer]
|
|
14039
|
+
* @param {object} [options.frameqr] Sythos Canvas QR detector options when
|
|
14040
|
+
* the profile marker is not preserved through image rendering.
|
|
11711
14041
|
* @returns {DecodeResult[]}
|
|
11712
14042
|
*/
|
|
11713
14043
|
function decode(image, options = {}) {
|
|
@@ -11718,6 +14048,9 @@ function decode(image, options = {}) {
|
|
|
11718
14048
|
const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
|
|
11719
14049
|
const wantPDF417 = !want || want.has('pdf417') || want.has('pdf-417');
|
|
11720
14050
|
const wantMicroPDF417 = !want || want.has('micropdf417') || want.has('micro-pdf417') || want.has('micro-pdf-417');
|
|
14051
|
+
const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
|
|
14052
|
+
const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
|
|
14053
|
+
const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
|
|
11721
14054
|
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
11722
14055
|
|
|
11723
14056
|
const source = LuminanceSource.fromImageData(image);
|
|
@@ -11794,6 +14127,35 @@ function decode(image, options = {}) {
|
|
|
11794
14127
|
}
|
|
11795
14128
|
}
|
|
11796
14129
|
|
|
14130
|
+
if (wantMicroQR && microQrCanDecode) {
|
|
14131
|
+
try {
|
|
14132
|
+
for (const found of microqr.detectAndDecodeMicroQR(bits)) {
|
|
14133
|
+
results.push({ ...found, format: 'microqr' });
|
|
14134
|
+
}
|
|
14135
|
+
} catch {
|
|
14136
|
+
/* no Micro QR in this pass */
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
|
|
14140
|
+
if (wantRMQR && rmqrCanDecode) {
|
|
14141
|
+
try {
|
|
14142
|
+
const found = rmqr.detectAndDecodeRMQR(bits);
|
|
14143
|
+
if (found) results.push({ ...found, format: 'rmqr' });
|
|
14144
|
+
} catch {
|
|
14145
|
+
/* no rMQR in this pass */
|
|
14146
|
+
}
|
|
14147
|
+
}
|
|
14148
|
+
|
|
14149
|
+
if (wantFrameQR && frameQrCanDecode) {
|
|
14150
|
+
try {
|
|
14151
|
+
for (const found of frameqr.detectAndDecodeFrameQR(bits, options.frameqr ?? {})) {
|
|
14152
|
+
results.push({ ...found, format: 'frameqr' });
|
|
14153
|
+
}
|
|
14154
|
+
} catch {
|
|
14155
|
+
/* no Sythos Canvas QR profile in this pass */
|
|
14156
|
+
}
|
|
14157
|
+
}
|
|
14158
|
+
|
|
11797
14159
|
if (wantOneD) {
|
|
11798
14160
|
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
11799
14161
|
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
@@ -11828,7 +14190,7 @@ function decodeStrict(image, options) {
|
|
|
11828
14190
|
}
|
|
11829
14191
|
|
|
11830
14192
|
/** Library version, matching package.json. */
|
|
11831
|
-
const VERSION = '1.
|
|
14193
|
+
const VERSION = '1.3.1';
|
|
11832
14194
|
|
|
11833
14195
|
__exports.listFormats = listFormats;
|
|
11834
14196
|
__exports.encode = encode;
|
|
@@ -11855,22 +14217,31 @@ export const {
|
|
|
11855
14217
|
decode,
|
|
11856
14218
|
decodeAztec,
|
|
11857
14219
|
decodeDataMatrix,
|
|
14220
|
+
decodeFrameQR,
|
|
11858
14221
|
decodeMicroPDF417,
|
|
14222
|
+
decodeMicroQR,
|
|
11859
14223
|
decodeOneD,
|
|
11860
14224
|
decodeOneDStrict,
|
|
11861
14225
|
decodePDF417,
|
|
11862
14226
|
decodeQR,
|
|
14227
|
+
decodeRMQR,
|
|
11863
14228
|
decodeStrict,
|
|
11864
14229
|
detectAndDecodeAztec,
|
|
11865
14230
|
detectAndDecodeDataMatrix,
|
|
14231
|
+
detectAndDecodeFrameQR,
|
|
11866
14232
|
detectAndDecodeMicroPDF417,
|
|
14233
|
+
detectAndDecodeMicroQR,
|
|
11867
14234
|
detectAndDecodePDF417,
|
|
11868
14235
|
detectAndDecodeQR,
|
|
14236
|
+
detectAndDecodeRMQR,
|
|
11869
14237
|
detectAztec,
|
|
11870
14238
|
detectDataMatrix,
|
|
14239
|
+
detectFrameQR,
|
|
11871
14240
|
detectMicroPDF417,
|
|
14241
|
+
detectMicroQR,
|
|
11872
14242
|
detectPDF417,
|
|
11873
14243
|
detectQR,
|
|
14244
|
+
detectRMQR,
|
|
11874
14245
|
ean13CheckDigit,
|
|
11875
14246
|
encode,
|
|
11876
14247
|
encodeAztec,
|
|
@@ -11882,14 +14253,17 @@ export const {
|
|
|
11882
14253
|
encodeDataMatrix,
|
|
11883
14254
|
encodeEAN13,
|
|
11884
14255
|
encodeEAN8,
|
|
14256
|
+
encodeFrameQR,
|
|
11885
14257
|
encodeISBN,
|
|
11886
14258
|
encodeITF,
|
|
11887
14259
|
encodeITF14,
|
|
11888
14260
|
encodeMSI,
|
|
11889
14261
|
encodeMicroPDF417,
|
|
14262
|
+
encodeMicroQR,
|
|
11890
14263
|
encodePDF417,
|
|
11891
14264
|
encodePharmacode,
|
|
11892
14265
|
encodeQR,
|
|
14266
|
+
encodeRMQR,
|
|
11893
14267
|
encodeUPCA,
|
|
11894
14268
|
encodeUPCE,
|
|
11895
14269
|
isWebGL2Available,
|