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