diff options
31 files changed, 544 insertions, 17 deletions
@@ -139,8 +139,9 @@ vectors: dirs need-image ## Regenerate test/data/Vectors.mc (cross-checked RFC/N corpus: dirs need-image ## Regenerate test/data/FuzzCorpus.mc (seeded fuzz inputs for the import parsers) $(DEV_OFFLINE) python3 tools/gen_fuzz_corpus.py -codes: dirs need-image ## Regenerate the QR tables (from segno) and test/data/CodeVectors.mc (zxing-cpp and segno references) +codes: dirs need-image ## Regenerate the QR and PDF417 tables and test/data/CodeVectors.mc (reference barcodes) $(DEV_OFFLINE) python3 tools/gen_qr_tables.py + $(DEV_OFFLINE) python3 tools/gen_pdf417_tables.py $(DEV_OFFLINE) python3 tools/gen_code_vectors.py spike: dirs need-image ## Build the M1 settings spike (debug .prg + beta .iq) into build/spike diff --git a/test/codes/CodeCacheTest.mc b/test/codes/CodeCacheTest.mc index e09d902..812acc9 100644 --- a/test/codes/CodeCacheTest.mc +++ b/test/codes/CodeCacheTest.mc @@ -39,7 +39,7 @@ function codeCacheOfLockedEntriesNeedsTheSession(logger as Test.Logger) as Boole e.type = Codes.QR; e.locked = true; var id = vault.add(e); - vault.saveCodeCache(id, [3, 1, 4, 0xE0]b); + vault.saveCodeCache(id, [3, 1, 4, 1, 0xE0]b); vault.lock(); var code = 0; try { @@ -49,6 +49,10 @@ function codeCacheOfLockedEntriesNeedsTheSession(logger as Test.Logger) as Boole } StorageFixtures.unlock(vault, StorageFixtures.PIN); var back = vault.loadCodeCache(id); - return code == VaultError.LOCKED && back != null && Bytes.equal(back, [3, 1, 4, 0xE0]b) - && Codes.Matrix.fromBytes([3, 1, 4, 0xE0]b) != null && Codes.Matrix.fromBytes([3, 1, 4]b) == null; + var pdf = new Codes.Matrix(3, 2, 2, [1, 0, 1, 0, 1, 1]b); + pdf.rowHeight = 3; + var round = Codes.Matrix.fromBytes(pdf.toBytes()); + return code == VaultError.LOCKED && back != null && Bytes.equal(back, [3, 1, 4, 1, 0xE0]b) + && Codes.Matrix.fromBytes([3, 1, 4, 1, 0xE0]b) != null && Codes.Matrix.fromBytes([3, 1, 4]b) == null + && round != null && round.rowHeight == 3 && Bytes.equal(round.modules, [1, 0, 1, 0, 1, 1]b); } diff --git a/test/codes/DataMatrixTest.mc b/test/codes/DataMatrixTest.mc new file mode 100644 index 0000000..ff3cbbe --- /dev/null +++ b/test/codes/DataMatrixTest.mc @@ -0,0 +1,110 @@ +// Author: cirakg & claude ai +// Summary: Tests for the Data Matrix encoder: ppf.datamatrix references (ASCII), Base256 and upper-shift decoding dumps, limits. + +import Toybox.Lang; +import Toybox.Test; + +(:test) +module MatrixFixtures { + + function packedHex(m as Codes.Matrix) as String { + var bits = m.modules; + var out = new [(bits.size() + 7) / 8]b; + for (var i = 0; i < bits.size(); i++) { + if (bits[i] == 1) { + out[i / 8] = out[i / 8] | (0x80 >> (i % 8)); + } + } + return Bytes.toHex(out); + } + + // A line tools/check_codes.py decodes with zxing-cpp. + function dump(logger as Test.Logger, symbology as Number, text as String, m as Codes.Matrix) as Void { + logger.debug("MATRIXDUMP " + symbology + " " + m.width + " " + m.height + " " + m.rowHeight + " " + m.quiet + " " + + Bytes.toHex(Bytes.fromUtf8(text)) + " " + packedHex(m)); + } +} + +// README 12.4 check 1: identical to ppf.datamatrix in ASCII mode, from +// 10 x 10 to 144 x 144 (multi-region and multi-block sizes). +(:test) +function dataMatrixMatchesReference(logger as Test.Logger) as Boolean { + var rows = CodeVectors.DATAMATRIX; + for (var i = 0; i < rows.size(); i++) { + var row = rows[i] as Array; + var payload = row[0] as String; + var result = DataMatrix.encode(payload); + if (result instanceof Codes.CodeError) { + logger.error("row " + i + ": " + result.message); + return false; + } + var expectedSize = row[1] as Number; + var expected = row[2] as String; + var got = MatrixFixtures.packedHex(result); + if (result.width != expectedSize || !got.equals(expected)) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "': size " + result.width + ", reference " + expectedSize); + return false; + } + } + logger.debug("CODES datamatrix reference rows=" + rows.size()); + return true; +} + +// README 12.4 check 2: Base256 (UTF-8), Upper Shift and plain payloads, +// decoded by tools/check_codes.py. +(:test) +function dataMatrixDumpForDecoding(logger as Test.Logger) as Boolean { + var payloads = ["Zürich", "Ünïcödé 😀 fake card 0042", "é", "1234567890", "Member: fake-0042, valid 2027-12-31", + "ÄÖÜäöüß" + "ÄÖÜäöüß" + "ÄÖÜäöüß", "abc"]; + var long = ""; + for (var i = 0; i < 30; i++) { + long += "é0123456789"; + } + payloads.add(long); + var xs = ""; + for (var i = 0; i < 50; i++) { + xs += "xxxxxxxxxx"; + } + payloads.add(xs); // 88 x 88: 4 x 4 regions, 4 interleaved blocks + var digits = ""; + for (var i = 0; i < 110; i++) { + digits += "0123456789"; + } + payloads.add(digits); // 88 x 88 in digit pairs, comparable with zint + for (var i = 0; i < payloads.size(); i++) { + var text = payloads[i] as String; + var result = DataMatrix.encode(text); + if (result instanceof Codes.CodeError) { + logger.error("'" + text + "': " + result.message); + return false; + } + MatrixFixtures.dump(logger, Codes.DATAMATRIX, text, result); + } + return true; +} + +(:test) +function dataMatrixLimits(logger as Test.Logger) as Boolean { + var big = ""; + for (var i = 0; i < 160; i++) { + big += "abcdefghij"; + } + // (Separate checks: one long && chain of instanceof tests made the + // compiler run out of memory.) + if (!(DataMatrix.encode(big) instanceof Codes.CodeError)) { + logger.error("too long text was accepted"); + return false; + } + if (!(DataMatrix.encode("") instanceof Codes.CodeError)) { + logger.error("empty text was accepted"); + return false; + } + // Base256 is chosen when cheaper than Upper Shift for every byte. + var b256 = DataMatrix.codewords(Bytes.fromUtf8("ÄÖÜ")); + if (b256[0] != DataMatrix.BASE256_LATCH) { + logger.error("Base256 not chosen"); + return false; + } + var ascii = DataMatrix.codewords(Bytes.fromUtf8("A1234")); + return ascii.size() == 3 && ascii[0] == 66 && ascii[1] == 142; +} diff --git a/test/codes/Pdf417Test.mc b/test/codes/Pdf417Test.mc new file mode 100644 index 0000000..8939e03 --- /dev/null +++ b/test/codes/Pdf417Test.mc @@ -0,0 +1,70 @@ +// Author: cirakg & claude ai +// Summary: Tests for the PDF417 encoder: pdf417gen reference rows, decoding dumps of screen-fitted symbols, limits. + +import Toybox.Lang; +import Toybox.Test; + +// README 12.4 check 1: the same rows as pdf417gen for fixed columns and +// levels (compaction, padding, EC, row indicators and patterns). +(:test) +function pdf417MatchesReference(logger as Test.Logger) as Boolean { + var rows = CodeVectors.PDF417; + for (var i = 0; i < rows.size(); i++) { + var row = rows[i] as Array; + var payload = row[0] as String; + var result = Pdf417.encode(payload, row[1] as Number, row[2] as Number); + if (result instanceof Codes.CodeError) { + logger.error("row " + i + ": " + result.message); + return false; + } + var expectedRows = row[3] as Number; + var expectedWidth = row[4] as Number; + if (result.height != expectedRows || result.rowHeight != Pdf417.ROW_HEIGHT || result.width != expectedWidth) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "': " + result.width + " x " + result.height + + ", reference " + expectedWidth + " x " + expectedRows); + return false; + } + var got = MatrixFixtures.packedHex(result); + if (!got.equals(row[5] as String)) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "' differs"); + return false; + } + } + logger.debug("CODES pdf417 reference rows=" + rows.size()); + return true; +} + +// README 12.4 check 2: symbols laid out for real screens (columns chosen to +// fit), decoded by tools/check_codes.py. +(:test) +function pdf417DumpForDecoding(logger as Test.Logger) as Boolean { + var payloads = ["Boarding pass FAKE 0042 seat 12A", "Zürich ✓ Ünïcödé fake", "12345678901234567890123456789", + "https://example.com/member/fake?id=0042&level=gold", "a"]; + var screens = [[218, 218, true], [390, 390, true], [320, 360, false]]; + for (var i = 0; i < payloads.size(); i++) { + var s = screens[i % screens.size()] as Array; + var job = new Pdf417.Pdf417Job(payloads[i] as String, 1, s[0] as Number, s[1] as Number, s[2] as Boolean); + Jobs.runToCompletion(job, 1); + var m = job.matrix(); + if (job.error != null || m == null) { + logger.error("'" + payloads[i] + "': " + job.error); + return false; + } + MatrixFixtures.dump(logger, Codes.PDF417, payloads[i] as String, m); + } + return true; +} + +(:test) +function pdf417Limits(logger as Test.Logger) as Boolean { + if (!(Pdf417.encode("", 3, 2) instanceof Codes.CodeError)) { + logger.error("empty text accepted"); + return false; + } + // Two codewords in 30 columns is a single row: PDF417 needs 3. + if (!(Pdf417.encode("ab", 30, 0) instanceof Codes.CodeError)) { + logger.error("a one-row symbol was accepted"); + return false; + } + return Pdf417.autoLevel(40) == 2 && Pdf417.autoLevel(41) == 3 && Pdf417.autoLevel(320) == 4 && Pdf417.autoLevel(321) == 5; +} diff --git a/test/data/CodeVectors.mc b/test/data/CodeVectors.mc index bb5f7bd..578a177 100644 --- a/test/data/CodeVectors.mc +++ b/test/data/CodeVectors.mc @@ -263,4 +263,67 @@ module CodeVectors { ["rlcekuvw?gmdkyn!tuemukewpdsxhwdc!nnnnnnnnnnnnn", 2, 0, -1, 4, 2, "fed2fb3fc107a5106e92cb6bb74c5875dbac6792ec14cb2507faaaaafe00193e007f655b98ba173cb266c612b6d5851580cb76ab2d86095dc93678970b2c28d5facde685918c708e3cb1d09a02815d00958cf2fbf638944030e91e7aee93d509e53efdaacb13fd805e6d47ffa8aaebd05ebdb1ebac962f95d4f4ea46ea191e89052bf81cfe4d5bd900"], ["9413149993340443910325583", 1, 0, -1, 1, 3, "fedbfc16906e8ebb75d5dba52ec10d07faafe01300b77a5ae2f39f84955268f8a0a080693bfb75905c79ba6a7dd536aea3f104e33febcd00"] ]; + + // [payload, ppf.datamatrix size, its matrix as hex of row-major bits] (ASCII mode) + const DATAMATRIX = [ + ["a", 10, "aab6f82a4b8f2ab8e337c0bff0"], + ["123456", 10, "aab2dc131dc220fec3d99d3ff0"], + ["Hello, World!", 18, "aaaaada15c6cfbb2abc12a3ad01ad2e23587b99f2ba6fe38e22aabfdcca3ad1b07d3b0d395413ffff0"], + ["00000000000000000000000000000000000000000000", 20, "aaaaac848589090a1203a4246848219095c921f7c259cc8785895d0a0383a568e8675590d1081b6bcef12c4c3d8b8aafffff"], + ["ABC-0042/fake member", 18, "aaaaacaeb8b572c1a3a5162a993a47533b63977a2cc27c5f8284db92473968f941b3be37a0d13ffff0"], + ["999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 48, "aaaaaaaaaaaaf676779d9d99cececeb3b3b2d9d9d9f67675bb3b3acececee76767d9d9d9ecececbb3b3a9d9d9de76767b3b3b2ecececf676779d9d9dcececeb3b3bed9d9d9f6762bbb3b3acecec6e76767d9d9c3ecececbb3a5a9d9d9de7616fb3b3b2eceee4f676779d97e5cececeb3b266d9d9d9f60367bb3b3ace91aee76767d8a62becececbba1deffffffffffffaaaaaaaaaaaa9d9d9de6bc17b3b3b2eb76dcf676778bf7ddcececea3c07ad9d9d9d6445fbb3b3ad90c5ae76761e816b9ececaac5c0ac9d9d11a246efb3b0228799c4f67461d6be05cecbd8b4dd36d9d699aaab17bb0ab8fb032ae774cfe2cab7ece776ff26e29d2495be3d7fb152d4b904ecf65b05b09c15cc9c7efb20feddf5b9f7ffe3ff3f3ccd4c4effffffffffff"], + ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 88, "aaaaaaaaaaaaaaaaaaaaaaa7a7a7e9e9fa7a7a7e9e9ff4f4f33d3d2f4f4f33d3d29e9e9fa7a7b9e9e9fa7a7bd3d3d2f4f4ed3d3d2f4f4efa7a7e9e9e9fa7a7e9e9e9cf4f4bd3d3ccf4f4bd3d3ce9e9ee7a7a7e9e9ee7a7a7bd3d3b4f4f4bd3d3b4f4f4a7a7a7e9e9fa7a7a7e9e9ff4f4f33d3d2f4f4f33d3d29e9e9fa7a7b9e9e9fa7a7bd3d3d2f4f4ed3d3d2f4f4efa7a7e9e9e9fa7a7e9e9e1cf4f4bd3d3ccf4f4bd3d34e9e9ee7a7a7e9e9ee7a7d7bd3d3b4f4f4bd3d3b4f4aca7a7a7e9e9fa7a7a7e9f61f4f4f33d3d2f4f4f33d2169e9e9fa7a7b9e9e9fa7a5bd3d3d2f4f4ed3d3d2f4fd6ffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaafa7a7e9e9e9fa7a7e9e9c1cf4f4bd3d3ccf4f4bd3e14e9e9ee7a7a7e9e9ee7a4d7bd3d3b4f4f4bd3d3b4f034a7a7a7e9e9fa7a7a7e96f5f4f4f33d3d2f4f4f33eed89e9e9fa7a7b9e9e9fa748bd3d3d2f4f4ed3d3d2f6e06fa7a7e9e9e9fa7a7e92041cf4f4bd3d3ccf4f4bf9ad0e9e9ee7a7a7e9e9ee62063bd3d3b4f4f4bd3d3b518dca7a7a7e9e9fa7a7a7f569ff4f4f33d3d2f4f4f22aa569e9e9fa7a7b9e9e9739093d3d3d2f4f4ed3d3e2b5deefa7a7e9e9e9fa7a679c651cf4f4bd3d3ccf4f3be58bce9e9ee7a7a7e9e9e6c0323bd3d3b4f4f4bd3fda221f8ffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaa7a7a7e9e9fa7a54ef8e21f4f4f33d3d2f4f6126a7c69e9e9fa7a7b9e9bfea997bd3d3d2f4f4ed3c61215d46fa7a7e9e9e9fa454772309cf4f4bd3d3ccf9a6a56664e9e9ee7a7a7e96026b3897bd3d3b4f4f4bc3772487a4a7a7a7e9e9fa722e618675f4f4f33d3d2fc28c2a58d09e9e9fa7a7b91c50e17e3bd3d3d2f4f4edc1f7abf236fa7a7e9e9e9dc9b1771a01cf4f4bd3d3eee1622532fce9e9ee7a7a7dde81f811f3bd3d3b4f4f8f6e4928de48a7a7a7e9e93829766f385ff4f4f33d3c6eec5eba43389e9e9fa7a67dc927ff7853d3d3d2f4f96f3082b0e4feffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaafa7a7e9e9e7b734a711d79cf4f4bd3d089cbcf3cfd2ce9e9ee7a4d9903bee7d073bd3d3b4fff8e6b643800eca7a7a7e9837fa553e7b3f3f4f4f33db10a3b04be79c89e9e9fa7d61bbe906dfc33d3d3d2f218c870b3bb7b16fa7a7e95eaff52befc08c9cf4f4be9b5892f652bec7ce9e9ee6bbd38709e7f1affbd3d3b54d3cd63d8a5e4bca7a7a718de386f9b758331f4f4fb27152b45e03095749e9e97074d7ec7a56956abd3d3dbe80b4a2e4b256e9efa7a6f34f41a9af87149c1cf4f32c65fcc7bb0b6a068e9e90f2a2bfcdacee8242bbd3ddb475f6bd3d1b67474ffffffffffffffffffffff"], + ["77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777", 144, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaafcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3f3b9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f3f3f3fcfcfdbf3f3fcfcfcff3f3f3fcfcfffe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ecfcfcff3f3f3fcfcfdbf3f3fcfcfcff3f3f3f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe7e7ebf3f3fcfcfcff3f3f3fcfcfdbf3f3fcfcfcfe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3f3f9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f3f3f3fcfcfdbf3f3fcfcfcff3f3f3fcfcfdfe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ecfcfcff3f3f3fcfcfdbf3f3fcfcfcff3f3f3f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe7e7ebf3f3fcfcfcff3f3f3fcfcfdbf3f3fcfcfcfe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3f3f9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f3f3f3fcfcfdbf3f3fcfcfcff3f3f3fcfcfdfe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ecfcfcff3f3f3fcfcfdbf3f3fcfcfcff3f3f3f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe7e7effffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabf3f3fcfcfcff3f3f3fcfcfdbf3f3fcfcfcfe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3f3f9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f3f3f3fcfcfdbf3f3fcfcfcff3f3f3fcfcfdfe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ecfcfcff3f3f3fcfcfdbf3f3fcfcfcff3f3f3f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe7e7ebf3f3fcfcfcff3f3f3fcfcfdbf3f3fcfcfcfe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3f3f9f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e7eef3f3f3fcfcfdbf3f3fcfcfcff3f3f3fcfce9fe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f9fbecfcfcff3f3f3fcfcfdbf3f3fcfcfcff3f227f9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe7e74bf3f3fcfcfcff3f3f3fcfcfdbf3f3fcfc91fe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f9f9d0fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbf3c679f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee7e356f3f3f3fcfcfdbf3f3fcfcfcff3f3f3fced8dfe7e7e9f9f9ee7e7e6f9f9f8fe7e7e9f8d52ffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacfcfcff3f3f3fcfcfdbf3f3fcfcfcff3b63bf9f9f8fe7e7e9f9f9ee7e7e6f9f9f8fe3546bf3f3fcfcfcff3f3f3fcfcfdbf3f3fced8efe7e7e6f9f9f8fe7e7e9f9f9ee7e7e6f89518fcfcfdbf3f3fcfcfcff3f3f3fcfcfdbb43a79f9f9ee7e7e6f9f9f8fe7e7e9f9f9ee29572f3f3f3fcfcfdbf3f3fcfcfcff3f3f3ed0f09fe7e7e9f9f9ee7e7e6f9f9f8fe7e7e8a454acfcfcff3f3f3fcfcfdbf3f3fcfcfcfbe3daff9f9f8fe7e7e9f9f9ee7e7e6f9f9f8af3464bf3f3fcfcfcff3f3f3fcfcfdbf3f3bfaff3fe7e7e6f9f9f8fe7e7e9f9f9ee7e7e2bef990fcfcfdbf3f3fcfcfcff3f3f3fcfc8fe9fce79f9f9ee7e7e6f9f9f8fe7e7e9f9f9afbe64ef3f3f3fcfcfdbf3f3fcfcfcff3f21fa7f395fe7e7e9f9f9ee7e7e6f9f9f8fe7e6eef9932cfcfcff3f3f3fcfcfdbf3f3fcfca7b9fce57f9f9f8fe7e7e9f9f9ee7e7e6f9f8bebe64eabf3f3fcfcfcff3f3f3fcfcfdbf28e9ff3967e7e7e6f9f9f8fe7e7e9f9f9ee7e2faf993a0fcfcfdbf3f3fcfcfcff3f3f3fca3a7fce5979f9f9ee7e7e6f9f9f8fe7e7e9f8beee64d82ffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf3f3f3fcfcfdbf3f3fcfcfcff28e9ff39441fe7e7e9f9f9ee7e7e6f9f9f8fe1fbe95360acfcfcff3f3f3fcfcfdbf3f3fca327dc65101f9f9f8fe7e7e9f9f9ee7e7e6f856fc9ad820bf3f3fcfcfcff3f3f3fcfcfda8d113d94407e7e7e6f9f9f8fe7e7e9f9f9ee05f52eb60c0fcfcfdbf3f3fcfcfcff3f3f3a2c40d8010079f9f9ee7e7e6f9f9f8fe7e7e81717ead83b2f3f3f3fcfcfdbf3f3fcfcfcb8a9041c0400dfe7e7e9f9f9ee7e7e6f9f9f8840dfa860ac2cfcfcff3f3f3fcfcfdbf3f21abc105a18c39f9f9f8fe7e7e9f9f9ee7e7e89037e899ad36bf3f3fcfcfcff3f3f3fcfc8baf04138630dfe7e7e6f9f9f8fe7e7e9f9fa0c0dfa0e6b4d8fcfcfdbf3f3fcfcfcff3f2abbc104988c3679f9f9ee7e7e6f9f9f8fe7ec4837e8282d26af3f3f3fcfcfdbf3f3fcfcb2bf04121a70d89fe7e7e9f9f9ee7e7e6f9fb108dfa008b4b2ecfcfcff3f3f3fcfcfdbf2cafc104839c3729f9f9f8fe7e7e9f9f9ee7ec40b7e840ad2cb0bf3f3fcfcfcff3f3f3fcb2bd841289f2dd67e7e7e6f9f9f8fe7e7e9fb902dfa102b1b2c0ffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaafcfcfdbf3f3fcfcfcff2c6f1b0ca27c9758f9f9f9ee7e7e6f9f9f8feec2c8f840ac6cb1ef3f3f3fcfcfdbf3f3fcb1dcbb3a89dc5d635fe7e7e9f9f9ee7e7e6fb34989fd00c8b2c76cfcfcff3f3f3fcfcfdac152bd7a26d8758dff9f9f8fe7e7e9f9f9eec1268d74038acb1dabf3f3fcfcfcff3f3f3b075b1d689b19d637fe7e7e6f9f9f8fe7e7eb06ba4c503e2b2c768fcfcfdbf3f3fcfcfcfc1f6c5d266c1f58de79f9f9ee7e7e6f9f9f8c1ae94944e8acb1daaf3f3f3fcfcfdbf3f3fc7db15c9fb0fd63715fe7e7e9f9f9ee7e7e486ba50d13a22a457a2cfcfcff3f3f3fcfcfd9f6c55a7ec3dd49d55f9f9f8fe7e7e9f9f909ae946c0e808974e4abf3f3fcfcfcff3f3f1fdb1539fb0f7967497e7e7e6f9f9f8fe7e40eba51ca3a0629b0928fcfcfdbf3f3fcfcfc7f6c549fec3dbdbca5f9f9f9ee7e7e6f9f906ae94628e81f8cc2ca2f3f3f3fcfcfdbf3f1fdb1507fa8f5def391dfe7e7e9f9f9ee7e51aba9180ba47e4b0b2cecfcfcff3f3f3fcfe7dfc741feabd77bce461f9f9f8fe7e7e9f9566fac602e91f92c2cb3effffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabf3f3fcfcfcff3f871f1907fbaf5dbf39787e7e7e6f9f9f8fe641aef0802a07e4c8bedf8fcfcfdbf3f3fcfe901c271f3ffd76fcebc179f9f9ee7e7e6f9902eb4200e81f930afd7e2f3f3f3fcfcfdbfbc838dc7cfff5dbdbaf051fe7e7e9f9f9ee654bad780b887e4c2bf5f8acfcfcff3f3f3fef209b607bffd76f3ebc0c1f9f9f8fe7e7e9952ecde1a80df93cafb7e28bf3f3fcfcfcffbc823d80efd95d3cfaf0307e7e7e6f9f9f8e54bb4f86a04de4fceedf8a0fcfcfdbf3f3fef208de1bbf1f75f3bbc0c1f9f9f9ee7e7e6952ed6c0a82499b70eb6ea8af3f3f3fcfcffbc82379fefe7fcf28bf0346dfe7e7e9f9f98d4bb5c8aa090d6163ad82a2ecfcfcff3f3fbf208d9ffbf97c38a2fc0d1b5f9f9f8fe7e64d2ed7caa8250f059ecd0a8b6bf3f3fcfcfefc82365fe9e498e2bad9346d7e7e7e6f9f9d4cb85feaa7946c167a0eae2d8fcfcfdbf3fbda48d97fa0921b8aeb1cddb4f9f9f9ee7e796ad27faa9e51c859e86abc972f3f3f3fcfef3bd3e5fee2687e3bbc5f76e61ee7e7e9f9e1ea787e6e1d1f0f67a7a9f1c1effffffffffffffffffffffffffffffffffff"], + ["lZ3X, T,f.60q.7lzy", 18, "aaaaac60bb0d6aebfbcb97392f5cf9331851b630af7ddcc11bec81f9a93fb05df273000db91dbffff0"], + ["oe)YQbXh8bEse18ix-,1hZ5NKsOyqCBFcdwzywrDxUnzq6P.Z-EjLbMxSlK0IBrtR:lHO/Lb4LFW6xRH 3ZB9)hAmU+WRD e.Gp9;(.6(A)hdR/:9GHQE:Hy;+fwpbVA3UYMphkYmXJx6m(A1:M3rC,e0E1IMKcA6;5OEgzplt4qyFx;kNiZu8QiFV;fkhS7r-Hfj 1mAR+LwxWC/H6oE:ZOMM(w9rd8s8wTTM55p-+DzTG-ZgE6BV6JkLoaMTKGvtZ4jE5,khbr..(Z,Acu2j4W+DFA5sf1l(gVz3KtHEJzTan)gulsOYMSuaxGvFcFO0V/1RLa.lNqaB3g04r9uO0Y6VERoqQC1TbGR6-OXdm(vo49( ghMZjGBh1 ", 80, "aaaaaaaaaaaaaaaaaaaaa9b2bde207aafa5cdcf1c545491970fb0d29505ee4eedba725a9ccfb222395820d958495f1095766a32bfeccddba9f1b2911d8304d5a82b371e9d1f4eeabfb52fbaacafd2901b0a10a1b14d29c8ee0208f333ce0ed83cbdaecaff565ed749490486893fe9aa09eb2a7cf833b1693d3da4ea0e0d5f9093c6cdabb9aea9faabf3ecbb19a4c4b4e56a187ac0488c84b5bb389a8ecbb5477d9c4083b36c35a8d6a72b2b73aaac9f21a1c66db97d44d2998d31e4f0538ffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaceb21c1b3ba3cc3c37c3d61b4965b6d5b5a8ca50f1a0bac0b1bab619007f951f4c095495e149ff3ccecabaaa9380ea1dcafbeda12a691a8d5faf4bde8cae3f8fadf02abc3151d6b6c8d59cb56da84e56f034bf10afaa293b5b039f878cf686b5e08eb2b2d93258ce5bbf2e7dd44184072dd158c519ad9e6cc9e8da792ba88ef8ddbfe471484d4a95526da312a79cbf6e8dc2d439e629a5556d197c9bc64aabb4b242b8472fcb81fb05e3d1dd8a719685cc0ddd86ffffffffffffffffffffaaaaaaaaaaaaaaaaaaaacb08bc9abbbbcd3b21e1cf66484e20d2804d709cca8d3bab8395ccb8e05dc5982a3974eb422dbfdc84373da9c1ab01b82f87f157a93870da5d6de0c6bcdcddb78596fb1c39438f91c9955cdd322b43a0ba403a4d179faf5fb097a7376b9d90db41699138cbe91b12ed9eb99ec66fd5794f063aaf680adbc2a1923bc81baef3da48f385f6293afc834d4bf81ae49cfd9daba43fbcd9f3910d4952c4df826a33128b703c9dc1db7a782a09f65d6f787af926484f38ffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaa3a95b897dfb12dba97ddc75ec56c4c3ede9c74aab143d97f5c2b53bd701f6e72f30b293c42caf6886a49f0005c18539a1b1c116ab9a46cb364a21fcebf01aafa7f0495e3f51e6452fa94ac570ee118484c69ea28bb0e31f8885b95acbada0e0802b1e0eb93fbb57b9be6cdf47458526092a14be5c6990a4a0b55fb53388df9d09c388774c0fd48eb46db210f229bb3e67c28a9d96d7d48b894facdb3f0fdde8c3105a9a4384debcb835b5096c697c845f4f1766ffffffffffffffffffff"], + ["h", 10, "aab6b8ba73f5389f9b2dc33ff0"], + ["FvofCc(uB", 16, "aaaaa277e7e4f1479898e6e3a760b347a040a5c38748b815a0d0eb85ee52ffff"], + [";Fj", 10, "aaab386be1edab5a4323b63ff0"], + ["ujvvb DcBeZPYa1UQljH1V4LjmNWTQ)NO6UPpeAsLEwq3AvJUJYaHSDNUey1V9rSYclKZjpHmSV:,l)dhiNVZ//Qbj0Hv8 cdbGIPpz VE;QELEKQIle231XPvhye6o;NyBD;c1Mo3xm", 44, "aaaaaaaaaaabf2b26ca419d7b513fcfd8b8a4772ba63869d5b4566ea25296ecf87cb524a9657c8baacf904b1b1a92366406bab63f692d985b70355408b280de2d2f382549a44f54b3f32ebca3dd5616a925dcce2926b9f09f5b19a1f03aac29868cc319735e339836deaa8703c099056dbe48a6fffffffffffaaaaaaaaaaaf2f2c6fa74d924a332c1e0ce2fef8469785259b896faaebd2e24a4be755529e682f4a09e2f12b995da3ef780924bff0170bbc6a4bfe2a4e50957ec9ebf5cd1b0373ca884af8d66fd66bb267fd2d52bde81b91d4c57a2768cca4e6e07925d47293db3c0ae6caff1f1fb9302a2e7c6fffffffffff"], + ["vCQ/4xnw3PRoTC7i0-2o5ypxQuAVfOmk0", 24, "aaaaaaab33b1802452ccdacd9b8562fa3383ca0a0eaba877e8d2f4873f13e5a69accf4f5c03512b2af1bf66096ee5be9c87204a905fff673bc9dbd79939e1ac24265d2200affffff"], + ["5m;fKPHfjXiGVlU:;ffOpuSyY)U1GPGZ0rbVs59mZ;UfdW1L/-MRL1yC8mxHu zSptyuYEKOXLyzaNmAxGeAls0;1,)twPSc2t2aheJcqv0Q9GuBXIlA-m:CCQmDOl", 44, "aaaaaaaaaaab23527ce8cdd7a33365296e2febe27b31984653f5a2e923a16949c7cd627b59d7caa89c7b225dade69b4e55ca603a7c5a05d7e683b529ab4aacf8a2ab909dd357724f198b653a07cd1e7397288caea66a2dd3cd855b6110aa93416d5dcdf727634321ca8b29f243fb9e598bd7bd2fffffffffffaaaaaaaaaaaa19336d60dded454abd17ce66d4e34d49992042a0cf8a0abb72b12d96c55295070dee2ee4f0f99450a3cbcfeba32bf2ecebdf704ac1e6c890c5f31d43c1611beaed8a992fe72b43d4f7bbbf142e2b4c75a453d5655bb17d8e988ee96967edc283ba78486ade6020a5dd55ba5e2c0fffffffffff"], + ["BaZ", 10, "aaab1ca2ab9c35bfd3a3cebff0"], + ["BR.i8TrGi)v84-", 18, "aaaaafa15a15faffab92aa34e0bee2b31da3f6922a10bd32fa9023e42c23f91a133a015795813ffff0"], + ["hXJaL58:g1gZ/MLtV9-4);Bl/wm1GJOjDrmH", 24, "aaaaaaae372db516869264ddeb8f56fa3aeba31274eba839f4c638832c63e298fcaa005dc59428ba8413e5e1e48e6001fd88808372cdeeb29086af7db00a6af32be5d1516affffff"], + ["iKjSUUDkTSUNpiyW1ntzBFN.Sx31pyhHwa;9Y8G2VEq.g+kM3CJa+FRmvFFUxtoaNW,J /4wM(+0N ee0pr,XbsQGhfp9jU/;MQ/QjYyYavaLZ", 40, "aaaaaaaaaaad2bbae9c996656c89a8c4969bbb39dc404d045293f25ef83df2550c15c08ea9fbcabba9fd2e7e64b328bf6923a6e04c11d4df1cfabd4dd7168ed13e8252da9c6bdf780e50d689079a16b3e9d42a527493333aab25d140697944ffffffffffaaaaaaaaaafaacd9d26dde5b0d02faeafafffe5b80434df964a4217ea83398d5486750b2b61cdfa790e52ad160eabd3ba3e7c99f2c4b66b061787b21ce51ccda88a92f59b863f1b9e8db54b9ff7b0895c9c54b2d04e080dd223593574cc3f6ffffffffff"], + ["SFuqbQkDgZdPh X34:JstZXLNTpZa)DDtR5OJug17HMBQw+A3Io08hvDZU5)DiSFBkBU-1HubI+012Cy-6zH809UbArf(.FLI(MPF)-Kafl:bJougPWCUniGak7P+-cPAZU+rrDOr03dp(1a3SR,UGLbO+BkFdYaLrF9WyZgpmAgG0E85nf75eVwU;Fmsz:)FcyzjnD1TI8vqgmX1iVxKUghfDu:9ilCwR;lwrwq;EIH", 64, "aaaaaaaaaaaaaaaabb25ebcd8241acbb8558b9d4f93e99def899b52986a392c396888404f454e72a927bd25ba6a5cac5d45897589760f144ad25f083a29bb1238d74f658d91a86eaaf2bae4de551f387d64888c0d074c4cc8ca5ba9f8d23ba739ad6b356f91cc8e8dacbbc7bacafd33dde48954aa6f6f570ffffffffffffffffaaaaaaaaaaaaaaaae92dca7b88bd9259a9d8cc589794ad2cb43fcf8dd32beead95d6d9788546e002f519acafaebf925bd45685e681d08010fa8bac319dbf97dd86149156f0aaea18a8abf32bcd1fdbcb8688f26295dae30e881bae0fa895b383e1f48dfcd4e4cdb4cda5ae21d4abb443969286a6e654a570ffffffffffffffffaaaaaaaaaaaaaaaa829ba6fff463f88be1429512e126b7d0e909e78dd61feccbdd90d160a3ccb7288d11ad49ece5a23fe756b1c4fc4c9078fa6dbea58471bc31df56d41ec43cca14ab07faa7baa1b9f7d23a9218ea7ad2b4a1efe1b7e8effef19d40ff1ad54ad420a1998c2fd2259e75d466ba66baceaca0ffffffffffffffffaaaaaaaaaaaaaaaace9daa45d131f21b944cf0ecad4ebc5c9a3b8e299e19bdc3dd1eb0ee939ac63ca8ebcd29cf859fcf815cf0149e62a1e0aaa39dbbaae79e1dd55eb79ac42afd64ea5fb4e9f31fd05b9be6ad8c8486cccec317f229b7038d49e21884da8ad6b598eba1aa83a0a1b2379950ca229c9ec154ffffffffffffffff"], + ["EG3i5(I RnBn1xa35w2SqNp4;aa2vaf+pye7D;", 26, "aaaaaaaca18a9916963232a07b8d5b502afec07c8042b2af3fb1d572972f4a057a664b72f1fcb19582a932b4711fc888332a527bb0e2ec22e3c37ff516fb126b31992573bb5e5259842dfb843235b91d7dbffffff0"], + ["Pi:TEJL7ORGX(X0undnKsYQ3;1EgGnFc4zF:Xms", 26, "aaaaaaab41cdfd6d4c5a8273039cdc0eb29aff1d97d5f2a23da1a9a52baba8bbda7d70f20a3439d8282dac601d9e6888f38a4251dcf10b24f3db7ef3c3c314939398bc1aaae869398c5212923b91ddacd9bffffff0"], + ["g SNIWi", 14, "aaaa421c7f24e1d50a72dc1c2f3bcf92751ba521a3cc93fff0"], + ["ozqosb7JXpDng3xgQ.uggy.PzmVZe/0lxKClM", 26, "aaaaaaaacce93f454d62f22943995199a8f41a198166b3b6a0cb99f2c429ce193b734da26afa43d083f63af5c6bc58ca73ba4e7bdcfa0e2bec345af33b9a53ec49d898bf3bba04fdbf111b2c123df9197d3ffffff0"], + ["O6VOU)X9v8zuaO45K;A22x-6uMDx2NntT C-P,NRBxBds)tJvc(Ey4a()(.665;jWV 4wq0XqjWJHV+2Qy1.lG2SVlLXQ;4hhJ2bgHLw ROdu/N6UC uXqqZ4KJnpCqT-/68w/v3y7Vf5oy-Dn2 k/PBv-2POuURtdf:yJgp", 48, "aaaaaaaaaaaa9d2293a4abc5e447c6d97578f4a295f9a63b8d88828455c4abbb69c41afdd7666ccf9090e30d27e149fbbe55f48f4f12a5bd07cc28b9e415d6a9bddcfe702fab8829c39750b066c8aafa7398cbcfb74c10d38e52c902cdd1f171e17934de1eeeb635b5c8978b96f4a49d3208ecacf9e2819b88149a82114ea9a931eca033d55e369192e4ffffffffffffaaaaaaaaaaaac96bc196ab93b10514cb944eb0a0b98b0b67e5d3a4e08f1cf25cddd699b7d35f8e8b8fead1d2ab90895dbe70369bcd46aa0cc9c3ae2b9c997692e038aa25238a3ae3a4d492b6a6b0acf4b7aa474f9c51f2dc7dc6daf0a5dba3f9c9777c8b7c44c44bf1ca4781d9d076ae02d6b53f2dc0bca3b5cdeec38414bcdec5a197b7ba9ca483f410ffffffffffff"], + ["hNhOl/X 27m,Wjq, RaU4", 20, "aaaaaa52ab9452cfab91cad28f0125c55a4ed823b542cd9429b32bcdc6b9aa6ecc4a37ca2dec94f9c8482aba25d6e2afffff"], + ["ss2ja.QxzgynQ;y.ShHHvG67qqqq", 22, "aaaaaabacf1e5a12b22ab5855f4aaf41bff654b2ad43d52bda8368df480c2ca7e9d3a8f3ca291fe2ff2aa1d398c5baa0089e2c22289a1def9fd3fffff0"] + ]; + + // [payload, columns, level, pdf417gen rows, width, one module row per PDF417 row as hex] + const PDF417 = [ + ["Hello, World!", 3, 2, 7, 120, "ff547d5f3ade1ec9cd0467d5f3fa29ff547d462c9f93d78c1747a903fa29ff5454f03be99105ec49f6a3e3fa29ff546bcfa3b91b08486326bcfbfa29ff546b842fd61e412f9677ae73fa29ff547d7b24c7d7ee48eb07af0bfa29ff5474efa318530c8d3df74efbfa29"], + ["abcABC123;:@#", 4, 1, 4, 137, "ff547abc35b810504d79f47713a9c1fd14ffaa3ea61fab8e9827ae729f31fab8fe8a7fd51a9f0fd647d0eafce98f92d4f87f453fea8af3c6e3429ce161d8e7b457df3fa290"], + ["Member 0042 - fake card", 2, 2, 11, 103, "ff5475383d6f9f35ef5787f453fea8fd477d1bbae31f530fe8a7fd51d5f8bd984f98ba8fdfd14ffaa2bef9ea78e8de579e3fa29ff5475c33cda105e2d7107f453fea8f5e8541e3f2c5d7d0fe8a7fd514e70a7706e6434ef1fd14ffaa3e961be5ea1e86bf7bfa29ff5469bf33d61bd30a61f7f453fea8a3067d5f378e5a31cfe8a7fd51d38ca7ee726434e41fd148"], + ["12345678901234567890123456789012345678901234567890", 3, 3, 12, 120, "ff5475382b0c13de888c37d5f3fa29ff547d41ba641d184e40d7d4c3fa29ff5454f03f1694c3eacf87eb43fa29ff5457df25e7933868ee86bcfbfa29ff546bd83e14d8b90be5c6b883fa29ff547d7b2b7c1719ceafc75f13fa29ff54539c21b8d4630f73774efbfa29ff547e93ac7d179bee2d06bf7bfa29ff547e9931f49cf96be3b7d393fa29ff54518324d818642c34451863fa29ff5469c0baf7161f2bc5e69c83fa29ff54688fbf5857940f9bd7d1d3fa29"], + ["Zürich ✓ fake data", 4, 2, 7, 137, "ff547d5f2b0c1bce28211724e3a9c1fd14ffaa3ea31e828bf5870dcb2e11ea40fe8a7fd51a9f0b37867ecb64f1f2f6d47c7f453fea8d79f729c218c98298e51c57df3fa29ff546b8432741ce7aeb107d77bd739fd14ffaa3d791e5e89bbe489e37b39ebc2fe8a7fd51d3bec37746d03110db8d0a7387f452"], + ["tab\there\nnew line\r end", 5, 4, 10, 154, "ff5475382b1810d90f57870c9b0d41ea78fe8a7fd51ea04e8c251e837a7d3e8eb17e7d4c3fa29ff54753f37d21361edcb86fcca7da15be0fe8a7fd515f7cd0436863a18c946c0c6417d7ebfa29ff5475ee21789a720f1115f4726fd9ae20fe8a7fd51f5e6d27c4fb23a8fd1b3c834775f0bfa29ff54539c30d8d7390dcc85ef131cc5a73cfe8a7fd51e910c87b79b22fb3dcef2e0596bf7bfa29ff54699fa7099633ccf945e772cc794e7efe8a7fd51460cc3216ef3a18edc750f78951dc3fa290"], + ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 8, 5, 21, 205, "ff546a30342610d84ef6877b43bda1ded0ef6877b43a8e1fd14ffaa3d66184e4c2726139309c984e4c2726139309c9f518fe8a7fd51d47ebee65f732fb997dccbee65f732fb997dccfd627f453fea8f4bc77b43bda1ded0ef6877b43bda1ded0ef687d2fbfa29ff545788309c984e4c2726139309c984e4c272613935c21fd14ffaa3afa17dccbee65f732fb997dccbee65f732fb99afc4fe8a7fd51a71eef6877b43bda1ded0ef6877b43bda1ded0a7bc7f453fea8f4816139309c984e4c2726139309c984e4c2727a503fa29ff54530fafb997dccbee65f732fb997dccbee65f733d3c5fd14ffaa28e71ded0ef6877b43bda1ded0ef6877b43bda1a39efe8a7fd51d3cec2726139309c984e4c2726139309c984e4d3887f453fea8d1bf5f732fb997dccbee65f732fb997dccbee6747cbfa29ff5474173bda1ded0ef6877b43bda1ce428632431928241fd14ffaa3a309b3d0c1395d9fbe625397cafdc705dbc2a1e888fe8a7fd5140bcb0274e33b4cfd0a1ef5e85823ae61d97e4943c7f453fea8e5bc771438d61b042ec5842823ee5d8a608dc6796fbfa29ff547a10bc2d943d0d0726b81221e5bf5c84f446fa28f05fd14ffaa3cbc99c2eb90c4c9c2e4c1b33eb41c7a1eb8afdb5e0fe8a7fd51da18d1b84c6139d1190dcb874673233b21d2e0f6877f453fea8e83b7998b7a7db788c4f67888b13a17e6e9e8f50fb3fa29ff545b3c299f1e17ac5bf44bc2d03997e2e87d67a6326fdfd1480"], + ["0000000000000abc111111111111", 3, 2, 9, 120, "ff547d5f358e13de8d6707d5f3fa29ff547a843e51991ecfa837a903fa29ff5454f028c7d8f929f64543c3fa29ff546bcfb5e7d04c6af3c6bcfbfa29ff546b823ae61d730eb987ae73fa29ff547d7b3d7a1fa68c7c97afb3fa29ff5474efa18c9bce8986174efbfa29ff547e973ec85c468de5f57ee3fa29ff547e993e875f5c8efc97d3a3fa29"], + ["https://example.com/member/fake?id=0042&level=gold", 6, 6, 28, 171, "ff5454103d27986a0b8745dd038ecd70e8fa9f7f453fea8face41793e351b3c493e6472fa0bd1f50cfe8a7fd5151e0b8dc67c2bdfad29e0faf36723ab1f1fd14ffaa3a4e1c28ee51c58c423b91bce2c34874b83fa29ff546bc629f0df1a8fd4e7d263c369e8eeeb837f453fea8f5e272bf2fc751e6ccc2f7d0eb1f254bc0fe8a7fd514f3c86324319218c90c64a09045c729c71fd14ffaa3a681cb86fb0a7ec5bf25df9d29f2e7a483fa29ff547e98b3d9dcf5c87a16fc6afdc5be42e9f47f453fea8a3de5871b1c69f2bed1cf6320b6481a3bcfe8a7fd514f10f4c378a83a70d3bf2e4c171683a70dfd14ffaa3f4698fa2a31f5b0f3f2653930a20f68fd3fa29ff547413bc8bdc7289630774130d41b982d0587f453fea8e8c17ca33ec511d7ce2e37a413c491e882fe8a7fd51411eb81663d1ae40d3d20e4bf4f423287dfd14ffaa32ce13ba082c64332304ddc84e973865b83fa29ff5474343a065e0a4c27b5c2fa3cbde092a3e67f453fea8f2f46c4f28479340ef47a60f5a3d11daf8fe8a7fd51da0c979e5e78a5e793b90d821477236841fd14ffaa341c9bcbefa4c587d389d999e4fdca7d05bfa29ff54493c27339afd0bf1d72fa2731d1e24fdba7f453fea8b70c53cf39c450cc2eb3c45022c609b738fe8a7fd51cb10ee3d7219330f578bc85e4483d3e50dfd14ffaa3dbe531bcc25f6e5c3ba3d241edf44591c3fa29ff54644c3b231138eef31609630c25c750c8b07f453fea8cbee7b733ddc5f2dcfbb472193c9092f20fe8a7fd51237ce7ae4ddf3eb917910cfda4e33b62f1fd14ffaa2471d73a0f4bc795e31581d678b3106477bfa290"], + ["$Oe2ü!]yeßrVgßßL'n+ßAZ>€mpa}Ré€ßü!$@é.yR|W]üUB/ü-W<0KoQ,a", 3, 2, 35, 120, "ff5454083473d0450e3b37d5f3fa29ff547ea3a0bc9e19adfae7d41bfa29ff5454f03f269b67cc7c9751fbfa29ff54691823b91908686346bcfbfa29ff5475c3213edbf5ce8316bd83fa29ff547d7b397d18f92dfb475f43fa29ff5468b023b89f3aee4de74efbfa29ff547d2c37eb9d062a3e67e93bfa29ff547e993f269cbe8c7c9530fbfa29ff54742e3c5799c62863451863fa29ff5474e3363c5cc74dfae69c0bfa29ff54688fbf269cbe8c7c968dfbfa29ff54682335cf11dc8e8cf6877bfa29ff547d11b7eb9e598e8d07a363fa29ff54505e3b4f98f92f7eb502f3fa29ff544b183ddb91dc8c3774a103fa29ff5451f626f91bf5cb7c451fbbfa29ff547cb933ed97ef69e2879793fa29ff54725c319219670e49c6d103fa29ff5450f9bd0517eb09e5e7a08bfa29ff5464be335e1f43acfd95b3c3fa29ff5464ef39271973c863264ce3fa29ff547ca32397d04f2becf7940bfa29ff54793d3391d875c8fb176fd3fa29ff54599023b91c9bc821176433fa29ff5465ec3a0c5e828bf5872f3bfa29ff544887af18d9f0acfd959df3fa29ff5459f7b9271b410863248c33fa29ff547c9627a3dbe9e9e5e7edbbfa29ff5458c7bf4d13e62cf89584e3fa29ff546437209411dc4e62c6c663fa29ff546de8321c5f5debf5876f33fa29ff547e2d31f25a23efc59458fbfa29ff5448213a4715c70ef6464163fa29ff545bf1aebe1e50282fb597e3fa29"], + ["kQzBzRNßQb>h<\"oßü[?v}|,ü[ß`", 4, 2, 14, 137, "ff547a9e2902186888ee266283a9c1fd14ffaa3f51de31af045437cafb3dfa9cfe8a7fd51a9f0de8c67ecbf269cbe8ea3f7f453fea8fafd4319331414c308c7657df3fa29ff5475c327d1dcb98c67a731d3ae31fd14ffaa3d791dfa2cfd97e4d397d1d7d0fe8a7fd51a73ce49c6d04218c91dc8a7387f453fea8fa587471a7d1d987af08557e73fa29ff54533e23a199fb2fc9a6d9f2987dfd14ffaa28ee10c648ee4651820845460cfe8a7fd51d38ce83151f32fd613cbce9cc7f453fea8a21e484f31f258f92c7c968dfbfa29ff54505039271c84eef1946f72871dfd14ffaa3e88de2cce4317e72bc149e890fe8a4"], + ["v€_A\\€h€+&ß=}^Q!:éiéXX*E9`évW`JCMé8$I[i~#", 1, 2, 80, 86, "ff5475cfba2e1d5c0fe8a7fd51fa8ec37975813fa29ff546af833f65d47efe8a7fd51a638dbcf6bdf3fa29ff5475c33a341d7defe8a7fd51f5c2ed3e75f43fa29ff54746f218c94ee0fe8a7fd51f4b09e5e74c23fa29ff54537c2ccf14c3efe8a7fd514330d71e518c3fa29ff5474e333be5a7cefe8a7fd51a27ccfd968dfbfa29ff54653036f3d430cfe8a7fd51f446e8d068e13fa29ff547a1ebb4f940bcfe8a7fd51dac08632728e3fa29ff5451f63e1a9a1c8fe8a7fd51f976cfd979793fa29ff545b0436f3ded38fe8a7fd5143e6e8d0740d3fa29ff545a073b4f96cf0fe8a7fd51d93086325bb03fa29ff547ca337d79e5dcfe8a7fd516f7e897876fd3fa29ff5448cc20845ec8efe8a7fd5197b0e8317ed63fa29ff546ccfb97d1677cfe8a7fd51c85c86326463bfa29ff547c962fa39db90fe8a7fd51633c876e584e3fa29ff54761330c6dc84efe8a7fd51b7a0e33964f1bfa29ff54450f2321d163efe8a7fd518ac0fbcb6c20bfa29ff545bf1bd835e442fe8a7fd51897ccfd94487bfa29ff5479ae39271c538fe8a7fd51d9c2f43648f0bfa29ff547747b1f259978fe8a7fd51c4dec34879a73fa29ff547d88b7eb9b1e4fe8a7fd5111befc9a6e4e3fa29ff54732c32e793730fe8a7fd5163f6bf58587cbfa29ff545ce7afb99b88efe8a7fd5177108ee87bb73fa29ff5478a438235c5ccfe8a7fd517058df9a5c09bfa29ff547720af7cd1102fe8a7fd518bc6dfae79b43fa29ff54612fbf2691bbefe8a7fd511082cb9e6e683fa29ff5471322fd6137d0fe8a7fd5108bcdfb177ae3fa29ff54620b23b8931d8fe8a7fd5137e685f65d9fbfa29ff544e4327829189cfe8a7fd518518e3945c743fa29ff547bb8bd22199e2fe8a7fd5138b0cfd95e633fa29ff544250392710a08fe8a7fd5177e2f436798cbfa29ff544e11b1f257848fe8a7fd51e758e3a142483fa29ff546e7d3c451dc7afe8a7fd510d1cb1bc63ae3fa29ff544ee4273d9ce88fe8a7fd51f0a69f4742f13fa29ff5473d3ac6f11cb0fe8a7fd51dec8f79446613fa29ff547c25a3cbd8de2fe8a7fd51be8484bc67933fa29ff544210a73a1dec2fe8a7fd513af8fa4c708cbfa29ff546f943f2ed9e2cfe8a7fd518c2284d85ef13fa29ff54467d3afbdf7bafe8a7fd517c64df446f84bfa29ff544148211210828fe8a7fd519cfae8c270593fa29"], + [")7Qß{ß5CTJ{A!aNb;u+éS€}KI70߀€MééeüKGr€B8€H3éß=Iv", 4, 3, 27, 137, "ff546a1828c311dc88c6171d13a9c1fd14ffaa3ea0d3d1edfae7418a8f99ea10fe8a7fd51a9f0c7c940d3b1e4d9fb2fd687f453fea8d260724e32ce10c64e3a857df3fa29ff546bd83c335e2369e5e705db5c11fd14ffaa3d791f5c49e0973f4beb89d7c4fe8a7fd51a7bec764416c23b915ef0a7387f453fea8fd27609cb7d79bf5ce8317e973fa29ff54533e3dafd8f92bc8167ecbe9c9fd14ffaa28e39b79ea0884bde218c9460cfe8a7fd51a7029e5e7c32b2721823ad3847f453fea8a21e571fb3f65341cbe327d1d3fa29ff5468262bb81c44e86ec50442871dfd14ffaa3d1b1d0c4bf5859fb37eb9e884fe8a7fd51a0befc9a7b5fbf269ed7ee57e7f453fea89660431920b0d0422e49c650c3fa29ff5451fbbd0517eb0f27770dcb47ddfd14ffaa25cfd9fb2d93c657c3b4f9cbe4fe8a7fd51b4108632530c367390422ed187f453fea8f41179663a341d0c4bf587a0a3fa29ff54725fb227df43acfd97e4d24879fd14ffaa24c31973ce49c659c218c9c99efe8a7fd51e502be8e619eb0de57eb0f2847f453fea8f6fa7d742c471df16c3ea64fcbfa29ff546cd82e7b16c10c560426c36411fd14ffaa3979db78891f67e2bbae31cbdcfe8a7fd51661e8a78612faf8d1bf1afb3d7f4520"], + ["5€C0ß0GHQSßm4éézKü€z€kwebbCzY€$WYVéxZlKébxMFaü$jT!V&%T)bJüfGV:2LAwE JljF6>AyC!€z?;", 5, 5, 39, 154, "ff546b70341193de8d6704108b6f3dea78fe8a7fd51eb30e8d074312fd61d304faef7a813fa29ff54753f33f65f934e5f463e4a7cc5fac4fe8a7fd51d238e3a46de7b104d0422e49c7d7ebfa29ff54578828f997eb0f0497046b7eb9d7b8fe8a7fd51f5e6fc9a7b5fbf269ed7ec7c96bf13fa29ff54745c21b051dc4d8424108b9271a73cfe8a7fd51e902f41479663a341d0c4bf587a443fa29ff54699fa3c499fb2d93c657c3b4f9e9e2fe8a7fd51e85e863261a2319415e788ee251dc3fa29ff5474f3bd30de31abe876fd73cb314f40fe8a7fd51441ecaf8769f31f25df8a9f0d747cbfa29ff545042231910422e49c65cf218c94140fe8a7fd51d18486fa5f67bc215c8d0823d7d1bbfa29ff54504f33f65f934f6bf63e4a3c892878fe8a7fd519638ef6270c9bdae15ef082114a083fa29ff547a10ba0c5e828bf584f6fbe1a5e850fe8a7fd51f2e283a64e13278250f22cf896d783fa29ff54792f39d210a10de74431a29861ed1cfe8a7fd51d076c77d6fd73a0c5e828bf58741a3fa29ff545b7821c59074c933e4784ba2fd937efe8a7fd516f70d84161b1a3b8994c0b1a05b983fa29ff547cb3beca17d9ed0e27a04ba605cb40fe8a7fd51e4f2ae7e5f1a21c2d034e8978764fbfa29ff54488820845b79ea0884bde218c9b204fe8a7fd512fb086f94f2f308e97eb0bf584bc43fa29ff547e46b1f25a7e2de58506fa3b7162e0fe8a7fd51b140a78f7423b9a0d20a0f3d148ee3fa29ff547b61309d1d3b0c9d049fbbcc35ed90fe8a7fd5163beb0874385a1e290e58de1358273fa29ff5458ec2c79d9098e45c6320aee116310fe8a7fd51b782f8c566f43eb39e772f2287271bfa29ff5462dfa382d6678982752782f31935c0fe8a7fd516184cbbc7cebaf3ed882ccb1c58683fa29ff547d90bc41567c2d8797d21b67a1f648fe8a7fd513678e9f14f3bb7899d3e4f4f1789ebfa29ff5462ce218c5adc0c58e6863a5f7d1420fe8a7fd51b3c2f3615f4739681f6a09fb77239bfa29ff546e83bf2c53a1898cf539faee79761cfe8a4"], + ["/a[iV+1ßXpL>R2KX&\"ye\" é", 4, 3, 12, 137, "ff54753835e7d0cd0828247723a9c1fd14ffaa3ea0da3d886bf5f67b30f5f530fe8a7fd51a9f0bc6367ecbf269cbe8fd687f453fea8afbe43193decd9986e3b657df3fa29ff546bd827979d302c4f374c135c41fd14ffaa3d7913470cf8943ac39fa5d7c4fe8a7fd514e70c62447722368104d8a7387f453fea8fd276fd73a0c5e86cbf586bf7bfa29ff54533e3e5ed6ef890df6197be9c9fd14ffaa28c19d09c8cc27093a6e61460cfe8a7fd51a702d03a7da1323852f86d3907f453fea8a21e5dde353e17c34da0f7d1d3fa290"], + ["é€5éV?[ßüQ|aNrßw]/Ff;`R-ü/ü XuNV", 7, 5, 17, 188, "ff547d4fa98c10422e49c65cf36f3d411097bc6a303fa29ff5475902fd31fadedfae7418bd0d97eb0cc3d7a883fa29ff546a3e21d0df2e8cfd97e4d397d1f934db3e6b3f3fa29ff5474b8218c9cc58e30b431a35c798ec8c36c7a5e3fa29ff5475e737eb9d062a3e65fac209e93cbcd0717ae3bfa29ff547af0b0f4d0e2c81a7543c30f2d3e32cfd975f9bfa29ff54538e39271b4108632433420845c938da0869c7bfa29ff547a412fd61e0a2c1395f67bb1c57eb093e657e3bfa29ff54531f226f91c4c9a07459f35fa1620ecc4f7a793fa29ff5468ef2239d679cee246612210857710f7b351ce3fa29ff5469e638dc5e904d07b7828be315e842f4847a73bfa29ff5451be23899b8b8df114f442fb8d07a2fc4d747d3fa29ff54682c20a417be6e4cf59e3b2dc104669df774173fa29ff547a31afc59f530c09d71b8b4389f0d4bcdf7d133fa29ff54506fbf165209ebf7247912e3bd913e9cb0651f3fa29ff5465b838a714618c8166361b1b311bce887772de3fa29ff547a1134e41e290e17362f1a65f9ea40c47151e13fa290"], + ["N|oéh#%éZZé~Sw3ß&ü:ßü5a€]i€aoé]z_/s^ßD$l7é€wS€€YJ߀-.üR`é/;kßü%.éZ߀'5ßüx€E:Nü&t€#5 u(jW?\\aMßü.", 5, 2, 40, 154, "ff5475bc3c9791330e30b431a33241ea78fe8a7fd51f518dfae7418bd0d97eb0f86a7ad83fa29ff54753f2fce99e98cfd97e4d3dafda8f8fe8a7fd5148408632671920845c938cb9e7d7ebfa29ff546b842fd613cbcedc47dd8a17d9ebdefe8a7fd51f5e6fa1d67ecbf269cbe8c7c97af0bfa29ff547a2f31da10422e49c6d04218c9a73cfe8a7fd51e940cf5f6fd73a0c547cce8317d233fa29ff54699fb6cf98f92bf7b4fb233f654c7cfe8a7fd514240dbcf504425ef10c648ee451dc3fa29ff5469c4340e50d7edfae79663a341a7b0fe8a7fd51441eed3e63e4a7d9170cecfd951be3fa29ff546821b9271973c86324772393794140fe8a7fd51e88886f94f2f3cb193e8ef11474643fa29ff54504f278499fb2fc9a72fa31f2541befe8a7fd512c30a18c4c343c579d91882114a083fa29ff5451e0ba0c5e86cf2cc74683a189f42cfe8a7fd51f2e2c7c9478a20e99790283ca7cbdbfa29ff546db0317791084e6237abc21199ed1cfe8a7fd5143ecbf587e74b7eb9d062a3e67a0dbfa29ff545b78364f195f0ed3e63e4a7c65927cfe8a7fd512738c4374108b9271b41086325b983fa29ff547944313cd08f2dfae7418bd0d9e5b0fe8a7fd51e4f2c7c967c2af8c9cfd28978727d3fa29ff54766720845c938cb38724e36821b204fe8a7fd512f40bf585f4e26f91bf5ce83165e33fa29ff547e46bdafd8f92876767ecbf269fb3afe8a7fd51d8b0cb386de7a82212f78863248ee3fa29ff547928279797cb8e19d6fd73a0c5f6ccfe8a7fd5163bee5f47e4d36cf98f92c78b6c2f3fa29ff54486620845b79ea0884bde218c96310fe8a7fd51e4eea3d07902a43d1bf5ce8316de23fa29ff5462dfb6cf98f92cf896fd933f65167cfe8a7fd512082dbcf504425ef10c64c76258683fa29ff5449f1b439182729e5e46fa3e1a5fb26fe8a7fd51367883a66fa221d0db41e9f644d8f3fa29ff547167a3b89cd308211724e32ce11420fe8a7fd51c8c2e8317a0a2fd611cbebf586cf23fa29ff546e83b1f258f9286b84705bbf2532e0fe8a7fd51c49cc24358d021bb14082e91c66843fa29"], + ["ré1üx€eW2Qb&FkxZüü?xVHLl€iCZ%[np]cggéRPSz?ré&j<S'GBhkI.€'62&éßT|H2FR[RSRn:>éßpuO3üZüW^üPOWOLRüCß ux0i€jß*IIé^Qu€8ß", 7, 0, 26, 188, "ff546a183da7186d88211724e32e793de8a8106a303fa29ff547eae37eb9d062f4145fac21be9bf5cf2cc7a843fa29ff546a3e32be1da7cc7c94fb0a0e99be88e8bf757e3fa29ff546930331414c30ec237b5c3b0b11dc4de717a5e3fa29ff5475cc37eb9d062f4147418bd0517eb09e5e6b823fa29ff547af0a1d9d8f1683a663ae3ba79ba38cfd97af43fa29ff5469efb6f3d411097bc431930d211dc4d1b869c7bfa29ff5457c32fa718cf29f47721a27979a07282f47e973fa29ff54531f324f99fb2fc9a7b5fb1f257a10de4369bf3fa29ff5451c7338c59c648634621ba0845c938cb9e51ce3fa29ff5474ec2fd61047af8694f2f2be31e228f18d69c23fa29ff5451be21e89a43ec9fa41d32c6f17b18cfd9511e3fa29ff54682636f3d411097bc431923b910cc2e3a474173fa29ff547a283e931bf5ce8317a1b3a0c547ccbf587a213fa29ff54506fa7099875c87a14385afded9c8e9e1b743ebfa29ff544b3031cd1b8d0c34171d9a31d90422e49c72de3fa29ff5451e43d0d9d062a3e65fac386e9c434cf5f68fbbfa29ff547cbda3c6d9fb2fc9a6d9f31f250ececfd94bbf3fa29ff546d0439271b41086324221231850422e49c76863fa29ff54687b3d0517eb0f16c70b13ec85bf5ce8317a0a3fa29ff54649f36cf98f92a27867ecbf269cbe8c7c949783fa29ff544986305318d0886e661a420845b79ea08876e7bfa29ff547cac3a1897eb0f8696fd73a0c547ccbf5879423fa29ff54727d27c359f14909e67ecbf269ed7ec7c97dbcbfa29ff546cd8208898642c3664108b6f3d411097bc722e3fa29ff5472e32fd31f59cdfae7418a8f99e236e5a072f73fa29"], + ["€K-\\f?[:ß%+muüE}Nk:V~=ß<:Adi€b4)€ßlße@!~5ü7n€\\r13Eß`T'W%G/[S4vIoHü!^`9_üß)U}U<ß@Gy3W 7zé,üV[qt-ß=!S3tVüI0ü$I:J]bßqu", 9, 5, 28, 222, "ff545410368210422dbcf504425ef10c64ec2377213b4c1a860fe8a7fd51fade82f14f2f319e5ec1adfae7418a8f997eb0be9c7d433fa29ff54543c30d791178cfd97e4d36cf98f92961f43b738fad59f0fe8a7fd51d270c34471d9b044d1dc8c84c433220845c938cb3869303fa29ff546bcc2fd613cbcaf206f97b05edfb4cdfae79663a341d706fe8a7fd51ebeced3e63e4a3c893e76874667ecb64f195f0ed3e6bf23fa29ff5453cf392719670863270d1a0845c938cb38431920b0da7befe8a7fd51f4869e5e69c13b711c33adfae7418bd0517eb0c11d7a483fa29ff547d3a3bf499fb2d93c657c3b4f98f9281a76d07b7ed1f4f6fe8a7fd5147bc826350c6328c10422e49c659c218c91dc8d9ce51c73fa29ff5453c8223d1e31a8f5e5f4e38ef57d70c67960deb8235d386fe8a7fd51fa32dfb141d33217d304eefc967ecbf269b67cc7c97a3cbfa29ff547413a3b91b08482225ef9b0c6d90b0ec8c4108b9271a098fe8a7fd51d188f4147418a8f997eb09e5e73bcb8869e10a837d7a20bfa29ff545027a069dfa68cfd97e4d397d18f9281a77d0ea4efd28f0fe8a7fd519670def350c627721c74486c14108b9271973c86324b303fa29ff547d09a7af1bf5ce8317a0a2fd61987ac6794fa3bf34d47ccfe8a7fd51e5e29f1967ecbf269cbe8c7c95f8ea78251f688f1b65f93fa29ff54768321b211dc4f3a64108b9271b410863259cc2bde1b410fe8a7fd51d062dfae7418bd0517eb09edf619eb3d7d8efa9e5e7d05bfa29ff546d9f3e4e51e44cfd97e4d397d18f92e7cb61a7b1d71f934fe8a7fd516e18e84e78af24ee1c57098c17b173342119ccc88649863fa29ff5472c83e50d8de2f4ee7eae21d7df30ae5907986ac8fdf286fe8a7fd51c9f284bc5cc3b7c91463efc4d43cdb7c4993f2b81659383fa29ff54644c3ef59e7a2e4cf440a3b19d79e4e938486c3473db360fe8a7fd519786e5884c3eba3dd21e8f5ce7a08ae2f9e884d7834bc83fa29ff5448cfad83d2178b0de47b3357c11e509cb0634f21c8dd9fafe8a7fd51238ee938486c3d0bd08ccdd9050e3b71911220c43759e73fa29"], + ["ß\"ßz(dP€,:<é\"ß_uzü:Dk!&<7+ma\\a[#8bY'zl€aZSßFP;ßßéL7Ko!Oé\"€^OlC`€>hObD8és", 6, 0, 22, 171, "ff54751c282210422e49c659c218c91dc8fa9f7f453fea8fab0731d37eb9d062a3e65fac21be5fa8efe8a7fd5151e081a75f7ba3c85074c84bc67ecb57c1fd14ffaa3e97db79ea0884bde218c98ec8cca074b83fa29ff546b902be31bf5ce8317a1b2fd613cbceb867f453fea8f5e261a7b3f65f934e5f463e4a069df5c2fe8a7fd514f78ec8c61b3379c50422e49c6d0429c71fd14ffaa35f1d7eb0cf5f78eebc1293cbcb7c27d2c3fa29ff547e98b3e253c22c7ca590e3dfadf5c4a6f87f453fea8d1cf4639a18d1ae3c9cf6618db7611a3bcfe8a7fd51a740c1715f67b8ce5e31a9f474efcba719fd14ffaa3f4699fb2d93c657c3b4f98f929f64689f3fa29ff54504823b89ce228211724e32ce10c64d0587f453fea8a7e77d09a7979f5de9fa35be239de5f446fe8a7fd51411e905e4f20bb7e98f92ba30644fbd0f5fd14ffaa3cb7db088dda07de5b9ec50422e49c65b83fa29ff54747bbd0d97eb09e5e731d37eb9e598a3ec7f453fea8f2f4657c3b4f98f9281ae4784bbf51f976fe8a7fd51ed0e8ee268ce2f7cd0422dbcf504436841fd14ffaa287a1d0c4bf584f2f3a605f0d4becf50f9bfa29ff54493c2e1b9483c8f1b67c533f65f934b40e7f453fea8b71865cf218c9824c863258312f3c9b738fe8a40"], + ["+,>üQr]=RßM}éS/$/€üd2!Vcß7]éüYéß^/jk€nv€cWüPTU6Fé!r8ü|ßj=ßéJ:^^sL{vüf#u*g]_2üc>ß'!k*ß_ERc\\!TQ", 2, 3, 88, 103, "ff5457bc396790448f5787f453fea8fa86763dba605f5defe8a7fd51d5f8cfd97e4d350f9fd14ffaa34c39b4108632579e3fa29ff5475c1bc49189e6af8c7f453fea8f5e86f8531f29d7c8fe8a7fd51d19eef62621bb4ef1fd14ffaa3d241bf5ce83174c0bfa29ff5469bf397d18f92fd3b7f453fea8d0c76690379c9a31cfe8a7fd51d386dfae7418a9fb9fd14ffaa288f1ed7ec7c9519f3fa29ff546518220913068a1837f453fea8f441623db7eb9a3d8fe8a7fd51d0fad93c657c25781fd14ffaa3b4c12f78e49c7947bfa29ff5451f33d0517eb0d0e17f453fea8977e4790a1c2dcbe8fe8a7fd51c9bcfbcb6188bed3dfd14ffaa3e82d05e8dfae681cbfa29ff5449783f269cbe8c91f7f453fea8ec8c431938e892660fe8a7fd51f286847a6fd73cb9dfd14ffaa3ede5f934f6bf76fcbfa29ff546ce739271b410d9087f453fea897905fac39de5fb4cfe8a7fd51663ccfd97e4d2ce7dfd14ffaa32131973ce49c59c63fa29ff547b6828f997eb0edc47f453fea8908f40d72f1b1611cfe8a7fd51b084b3c74108ac641fd14ffaa39399e598e8d05be13fa29ff544507bb4f98f928bbf7f453fea8c53061a0bce9920a0fe8a7fd51f650dfae79663c8d9fd14ffaa26de195f0ed3e7e26bfa29ff54734c218c905988a207f453fea8e4765f67a17cd67ecfe8a7fd517430cfd97e4d2edc1fd14ffaa39bbdb410863266883fa29ff547c85bc58dc43490f47f453fea899df7dbd3683d7230fe8a7fd5199208211724e33739fd14ffaa2c2fde86cbf587b06bfa29ff545c582069d12f0b8837f453fea8bb8461b638e85e64efe8a7fd51e288dfae7418b1709fd14ffaa3705db67cc7c9615f3fa29ff54798ba3b91e74c88d87f453fea88bc16fd73a0c5e6c8fe8a7fd511b78e5f463e4b3ae1fd14ffaa310ddc34c866466243fa29ff54737237eb9d0629be47f453fea88cde72fa3f2697a40fe8a7fd518826cb9e431922199fd14ffaa2ebe1e4c6b0fa7c45bfa29ff545ec333c2d79049cce7f453fea8c28671db23b9130c8fe8a7fd51e236e0bb61bcb11edfd14ffaa338b99fb2fc9a5e61bfa29ff5446d0368210c64c2837f453fea899fb4178a7a79cc74fe8a7fd5178c6cf914f86af089fd14ffaa3ce9905ce8ee471b73fa29ff546e7ca87cd1e5edc7d7f453fea88687513c33f651d30fe8a7fd513b88e49c6d043ce8dfd14ffaa3c2857eb082f442f9bfa29ff544f442069d44f09ed87f453fea8e7214108b92710882fe8a7fd51e124a3e65fac237d1fd14ffaa37d05034ecf896fb33fa29ff544630a18d1d846c6287f453fea8fce942f9b7eb9cef2fe8a7fd51df2cfc9a72fa27891fd14ffaa379c50c648ee4421b3fa29ff544efd3cb9dd184f39d7f453fea89e114fb12069d7c34fe8a7fd510508e59e67ba3082dfd14ffaa210f5e6c8bf58705dbfa29ff5463d637a3160b8872c7f453fea8c37762ef3ba2105b0fe8a7fd51f0d2af984f8bbe195fd14ffaa23c89deb8872c4799bfa29ff544330b2431105082637f453fea8c77d7d0b3e8dd7cb8fe8a7fd517ee2eafc4c23b7e69fd14ffaa239d118629831433b3fa29"], + ["R:Qé€HEYNs2hßG4*a{{Jßü\" ^f+pf=ß{é>!~qsHü€j:d(vjvgekNGéQWe€O>S|RüQAe8\\5", 11, 0, 10, 256, "ff5475383a11d10d8dda0798ba0845c938cb9e6de7a82212f7886326a0c3fa29ff547d583e531197ec13a7866be1a9bf5ce83151f32fd61a074d1c87d4c3fa29ff54541e27d91034ec79643db33f65f934e5f47e4d36cf98f9281a76af83fa29ff5457df2368104d882224177208918258fb5e7390a0845c938cb387a4f3fa29ff546b902fd613cbce0bb6fd73a0c5e86cbf584f2f3e931db88e0996b883fa29ff547af9af209df92cfd97e4d36cf9b278caf8769f31f251e668f097d70bfa29ff54539c20b191dc8e72143623b10ded1cec2363b2393790422e49c53c7bfa29ff546be3bd0d97eb09a3f417937eb9e598e8d074312fd61e40ac2396bf7bfa29ff5453bf23ed1034ecf4c5ec633f65f934db3e63e4b097d3ec2cf8a537c3fa29ff545183231cd8636ed304319218c90c6486324319218c9dc22fbd368583fa29"], + ["pk\";GV1€7pR/ >\\$aFS€8退\":4)XE4é#üeLßL+?E(qnYßéXüo7{ljJ7j€o)ßEQ-NéZ'QkßpWéh8l)Gn.ü^]}0", 3, 2, 50, 120, "ff54759e281058258ec237d5f3fa29ff547ea3bf7b5f5ded07b75903fa29ff5454f02fdc59fb2d93c751fbfa29ff545208282212f7886326bcfbfa29ff5475c330475c374becf75e73fa29ff547d7b3391d9c2ebe3175f43fa29ff547a27a5ce10c68d71e74efbfa29ff547d2c38675f116dfae7a413fa29ff547e99364f195f0ed3e530fbfa29ff54508827bd158c0821151863fa29ff5474e32df1182e8e19d69e63fa29ff54688fa78a17d90caf868dfbfa29ff54506625ef10c648ee46877bfa29ff547d11ae4f91e5ee8e37a31bfa29ff54505e27849cf2e9f3b502f3fa29ff5465de20845c938cb9e4a103fa29ff5451f62fd613d3cdfae7a113fa29ff547cb93f269b67cc7c979793fa29ff547927a0b0d1dc4c5306d103fa29ff5450f9b7eb9d062a3e6683a3fa29ff5464be31f25746082df5b3c3fa29ff546de7b79c5e53cc61264ce3fa29ff547ca327d1d17c6becf79663fa29ff54793d278499fb2fc9a76fd3fa29ff54488432ce1c938cb9e76433fa29ff5465ec2fd611be8dfae4bc13fa29ff544887bf269b67cc7c959df3fa29ff54762620941c744c61148c33fa29ff547c9627d1df650becf79213fa29ff5458c7a28f12278c51f584e3fa29ff546c73a0845b79ea0886c663fa29ff546de83a1897eb0e09a64e0bfa29ff547e2d2069d3c24cfd9458fbfa29ff545873392719670863264163fa29ff545bf1bd18d3d1e9ebc591fbfa29ff54449e33f65f934f6bf4487bfa29ff544586218c99c62ce31628c3fa29ff547670b8375e094dfae6470bfa29ff546e873f269cbe8c7c9665e3fa29ff547367304b11dc49dc473463fa29ff547d88b7eb9d062f436790dbfa29ff545defb1f250e26cf8a6e4e3fa29ff547dd73868d1dc88c616263bfa29ff5458fdba065b1e29be4761d3fa29ff546e2e33f65f934db3e6e23bfa29ff546226218c90444bdf66ec83fa29ff5478a43e195f1dabf5871643fa29ff545c1331f25cbf6a09e5c09bfa29ff545df72b1818c3684c362373fa29ff5462f1bd09186f48d3f7e6b3fa29"], + ["hzH#éeo!e€(h~lyXü1Iüßn)[(o)ti\"ß`@oSé0R752TNTgß?}lp€scü,", 4, 0, 23, 137, "ff54751c28c6186a0ce3171d13a9c1fd14ffaa3f5713d3cdfae7418bd0d9fa8efe8a7fd51a9f0c7c94fb0a609d3c12eafc7f453fea8fa5f431a328c10422dbcf57df3fa29ff5475cc3a341d0c4bf584f2f3ae19fd14ffaa3d7917dee871340d3b147debd0fe8a7fd514f78e1a36f79a19910422a7387f453fea8af867418bd0517eb0f0457d2c3fa29ff54533e33e299fb2fc9a6d9f34df9fd14ffaa3473dc938cb38431930d05460cfe8a7fd51d3b09e5e427da37d1c134e9c67f453fea8a21e40d3a7849bf64b1bc511e3fa29ff545048318510422e49c659c2871dfd14ffaa3d1417eb09e5e72e1a7d1df446fe8a7fd51a0be982777e2b3f65f934e87d7f453fea8f2df65cf218c90dccc761650c3fa29ff5451e430475a3ccbeb8763928fb1fd14ffaa25cfd1e6ccfd97e4d397d12efcfe8a7fd51ed0e86324772379c90c68ed187f453fea8d0f67d9137eb9e598e8d050f9bfa29ff54725fbb4f98f92cf92513c24bc1fd14ffaa2dc610422e49c6d04218c9c99efe8a7fd51f2b09ebc5fac308e9f1daf9467f452"], + ["=t]€:9jüqsT€Xx4H|c,@LL#Qé'", 11, 5, 10, 256, "ff547538290410cc886c84772393790422dbcf504425ef10c64e3b36a0c3fa29ff547eb7bdb99db88dfae7418bd0517eb0e099788a2fae1bf5cf2cc7d4c3fa29ff54541e32be1da7cc7c9471ba79dd2c3ec89f61d739fa5d17e885e567c3fa29ff5457df3431d2084c7626190a0845c938cb9e431923b910cc286327a4f3fa29ff546bcc2fd617eb0c8397a482e5f1fa2ed9e159fb3b1c5f226a7e76b883fa29ff547af9bf2c9331ece2e478da4c7d0c4ebcc65e5026439302ecf136bf23fa29ff54539c227713718970e671a34c39e72c8631459833dc585deee4853c7bfa29ff547d21bd04917ee8df47a2132f6133e4fa4350f9b09ed43e6bf676bf7bfa29ff5453bf22c7d7c34fa7b46b8325f1a9f0f4f260d7b7139fa32e7e97d3dbfa29ff54518330a610bdea3b845ce32431410888427b4e2de3905b0918368583fa29"], + ["é\\I,p|z€dNV4€rQu€0ZS%Jü€\\é>5?Y2Wo(%Bf7MCg", 8, 3, 11, 205, "ff5475383a2e10422e49c65cf218c91dc8ed3059cc3a8e1fd14ffaa3d41190e2e2e35edf27d1de73adfae79663a341f530fe8a7fd51d47eed3e63e4a3c85074cdccf4f9db3f65b278a83c7f453fea8afbe504425ef10c64c36c4771331411b4082117d2fbfa29ff546b813cb31d1a0e8625fac3e3b53cbcf114661eb5c41fd14ffaa3afa18a3ecfd97e4d36cf9b278caf8769f31f25ebe6fe8a7fd514e708ee4769820845c938cb9e431923b91d19ea7bc7f453fea8fa4c70cebe7353eb8f0cd6fa7b82693cbc8df46bf7bfa29ff54530fa3c157ee2871677e4a3c29a2f8f93963e4a9df9fd14ffaa28c19da0cf7944ce1b8b79c7428881677337321a39efe8a7fd51a704d0e278ccbe44de8c6fa8c621cb08e5fb26d3907f452"], + ["&IO$u(WFü{ß(TAWéß)|8a>\"JLH2KJ(üaéü0eLq\"H,eüR414/P9ü>€ßeTDL1:üü{)üCßZß", 12, 2, 10, 273, "ff5475383420d8ed0e3a16ed0261a1c25cc61267b93b4c10422e49c6d042a041fd14ffaa3ea317eb09e5e705db7eb9d062a3e65fac279791be8f33442f9b7eb9f530fe8a7fd51fad0fc9a7b5fbf269cbe8c7c940d3af9cd8f94c85f7d712f8c5869ed47c7f453fea8afbe59f7b6441a338b182477239c850422e49c6d04218c90504821169183fa29ff546b843a0c5e86ce8317a0a2fd61f1da82f25f67bd985f126fded5f0bb5c41fd14ffaa3af893e349f6167ecbf269b67cc7c96723ba1f5283cbf3a6fcd33f65ebc2fe8a7fd514e70e49c6d04218c91dc8e8cf4108b6f3d411097bc724e32ce10c64d1607f453fea8f4a041792fb3df776e468579037eb9d062f4147418bd0517eb09e5e6bf7bfa29ff547d3933e859fb2fc9a6d9f31f2544f0cfd97e4d397d18f92876767eca98f9fd14ffaa28c19c938cb384319218c9d4e082ee6c5028211d11cb0614110acfbdd0b8fe8a40"] + ]; } diff --git a/test/golden/fenix7/about.png b/test/golden/fenix7/about.png Binary files differindex 1e4799d..eb34e4e 100644 --- a/test/golden/fenix7/about.png +++ b/test/golden/fenix7/about.png diff --git a/test/golden/fenix7/code_datamatrix.png b/test/golden/fenix7/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..f2d699a --- /dev/null +++ b/test/golden/fenix7/code_datamatrix.png diff --git a/test/golden/fenix7/code_pdf417.png b/test/golden/fenix7/code_pdf417.png Binary files differnew file mode 100644 index 0000000..768243e --- /dev/null +++ b/test/golden/fenix7/code_pdf417.png diff --git a/test/golden/fr255s/about.png b/test/golden/fr255s/about.png Binary files differindex 70134e0..b4aebeb 100644 --- a/test/golden/fr255s/about.png +++ b/test/golden/fr255s/about.png diff --git a/test/golden/fr255s/code_datamatrix.png b/test/golden/fr255s/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..f1116cb --- /dev/null +++ b/test/golden/fr255s/code_datamatrix.png diff --git a/test/golden/fr255s/code_pdf417.png b/test/golden/fr255s/code_pdf417.png Binary files differnew file mode 100644 index 0000000..5f20e05 --- /dev/null +++ b/test/golden/fr255s/code_pdf417.png diff --git a/test/golden/fr70/about.png b/test/golden/fr70/about.png Binary files differindex 8980df8..758268c 100644 --- a/test/golden/fr70/about.png +++ b/test/golden/fr70/about.png diff --git a/test/golden/fr70/code_datamatrix.png b/test/golden/fr70/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..605d1f5 --- /dev/null +++ b/test/golden/fr70/code_datamatrix.png diff --git a/test/golden/fr70/code_pdf417.png b/test/golden/fr70/code_pdf417.png Binary files differnew file mode 100644 index 0000000..f3ca5ee --- /dev/null +++ b/test/golden/fr70/code_pdf417.png diff --git a/test/golden/fr965/about.png b/test/golden/fr965/about.png Binary files differindex 8e7d950..fe21a67 100644 --- a/test/golden/fr965/about.png +++ b/test/golden/fr965/about.png diff --git a/test/golden/fr965/code_datamatrix.png b/test/golden/fr965/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..bff9127 --- /dev/null +++ b/test/golden/fr965/code_datamatrix.png diff --git a/test/golden/fr965/code_pdf417.png b/test/golden/fr965/code_pdf417.png Binary files differnew file mode 100644 index 0000000..05ef3d9 --- /dev/null +++ b/test/golden/fr965/code_pdf417.png diff --git a/test/golden/instinct3amoled45mm/about.png b/test/golden/instinct3amoled45mm/about.png Binary files differindex 89f3826..b896bc8 100644 --- a/test/golden/instinct3amoled45mm/about.png +++ b/test/golden/instinct3amoled45mm/about.png diff --git a/test/golden/instinct3amoled45mm/code_datamatrix.png b/test/golden/instinct3amoled45mm/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..4c47654 --- /dev/null +++ b/test/golden/instinct3amoled45mm/code_datamatrix.png diff --git a/test/golden/instinct3amoled45mm/code_pdf417.png b/test/golden/instinct3amoled45mm/code_pdf417.png Binary files differnew file mode 100644 index 0000000..2c9361e --- /dev/null +++ b/test/golden/instinct3amoled45mm/code_pdf417.png diff --git a/test/golden/venusq2/code_datamatrix.png b/test/golden/venusq2/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..01ec315 --- /dev/null +++ b/test/golden/venusq2/code_datamatrix.png diff --git a/test/golden/venusq2/code_pdf417.png b/test/golden/venusq2/code_pdf417.png Binary files differnew file mode 100644 index 0000000..2f1c136 --- /dev/null +++ b/test/golden/venusq2/code_pdf417.png diff --git a/test/golden/venux1/code_datamatrix.png b/test/golden/venux1/code_datamatrix.png Binary files differnew file mode 100644 index 0000000..627ae8d --- /dev/null +++ b/test/golden/venux1/code_datamatrix.png diff --git a/test/golden/venux1/code_pdf417.png b/test/golden/venux1/code_pdf417.png Binary files differnew file mode 100644 index 0000000..869cc03 --- /dev/null +++ b/test/golden/venux1/code_pdf417.png diff --git a/test/golden/venux1/code_qr.png b/test/golden/venux1/code_qr.png Binary files differindex 8d34f4c..90c15dd 100644 --- a/test/golden/venux1/code_qr.png +++ b/test/golden/venux1/code_qr.png diff --git a/tools/barcode_ref.py b/tools/barcode_ref.py index f56a7c3..9c4c6d5 100644 --- a/tools/barcode_ref.py +++ b/tools/barcode_ref.py @@ -14,9 +14,12 @@ from typing import Any, cast import zxingcpp from PIL import Image -QR, CODE128, CODE39, EAN13, EAN8, UPCA, UPCE, ITF, CODABAR = range(9) +QR, CODE128, CODE39, EAN13, EAN8, UPCA, UPCE, ITF, CODABAR, DATAMATRIX, PDF417, AZTEC = range(12) FORMATS = { QR: zxingcpp.BarcodeFormat.QRCode, + DATAMATRIX: zxingcpp.BarcodeFormat.DataMatrix, + PDF417: zxingcpp.BarcodeFormat.PDF417, + AZTEC: zxingcpp.BarcodeFormat.Aztec, CODE128: zxingcpp.BarcodeFormat.Code128, CODE39: zxingcpp.BarcodeFormat.Code39, EAN13: zxingcpp.BarcodeFormat.EAN13, @@ -55,15 +58,17 @@ def render_linear(modules: str, scale: int, height: int = 40) -> Image.Image: READ_FORMATS = {**FORMATS, CODE39: zxingcpp.BarcodeFormat.Code39Std, UPCA: zxingcpp.BarcodeFormat.EAN13} -def render_matrix(packed: bytes, size: int, scale: int, quiet: int) -> Image.Image: - """A square matrix (row-major bits, 8 per byte) as black modules on white with a quiet zone.""" - side = (size + 2 * quiet) * scale - image = Image.new("L", (side, side), 255) - for i in range(size * size): +def render_matrix(packed: bytes, shape: tuple[int, int], scale: int, *, quiet: int, row_height: int = 1) -> Image.Image: + """A matrix (row-major bits, 8 per byte; shape = width and stored rows, + each row row_height modules high) as black modules on white with a + quiet zone.""" + size, rows = shape + image = Image.new("L", ((size + 2 * quiet) * scale, (rows * row_height + 2 * quiet) * scale), 255) + for i in range(size * rows): if packed[i // 8] & (0x80 >> (i % 8)): x, y = i % size, i // size - left, top = (quiet + x) * scale, (quiet + y) * scale - image.paste(0, (left, top, left + scale, top + scale)) + left, top = (quiet + x) * scale, (quiet + y * row_height) * scale + image.paste(0, (left, top, left + scale, top + scale * row_height)) return image diff --git a/tools/check_codes.py b/tools/check_codes.py index 53e30f0..7ec47b4 100644 --- a/tools/check_codes.py +++ b/tools/check_codes.py @@ -20,6 +20,8 @@ import barcode_ref as ref DUMP = re.compile(r"CODEDUMP (\d+) ([0-9a-f]*) ([01]+)") QR_DUMP = re.compile(r"QRDUMP (\d+) ([0-9a-f]*) ([0-9a-f]+)") +# Any 2D code: symbology, width, stored rows, row height, quiet zone, text as hex, modules as hex. +MATRIX_DUMP = re.compile(r"MATRIXDUMP (\d+) (\d+) (\d+) (\d+) (\d+) ([0-9a-f]*) ([0-9a-f]+)") SCALES = (1, 2, 3) QR_SCALES = (2, 3) # README 12.2: 2D codes use at least 2 px per module @@ -43,10 +45,22 @@ def check_log(path: Path) -> tuple[int, list[str]]: text = bytes.fromhex(match.group(2)).decode("utf-8") packed = bytes.fromhex(match.group(3)) for scale in QR_SCALES: - got = ref.decode(ref.render_matrix(packed, size, scale, quiet=4), ref.QR) + got = ref.decode(ref.render_matrix(packed, (size, size), scale, quiet=4), ref.QR) if text not in got: failures.append(f"QR size {size} '{text[:30]}' at {scale} px/module: read {got}") checked += 1 + for match in MATRIX_DUMP.finditer(path.read_text(encoding="utf-8", errors="replace")): + symbology, width, height, row_height, quiet = (int(match.group(k)) for k in range(1, 6)) + text = bytes.fromhex(match.group(6)).decode("utf-8") + packed = bytes.fromhex(match.group(7)) + for scale in QR_SCALES: + image = ref.render_matrix(packed, (width, height), scale, quiet=max(quiet, 2), row_height=row_height) + got = ref.decode(image, symbology) + if text not in got: + failures.append( + f"symbology {symbology} {width}x{height} '{text[:30]}' at {scale} px/module: read {got}" + ) + checked += 1 return checked, failures diff --git a/tools/gen_code_vectors.py b/tools/gen_code_vectors.py index a87b0c9..5ce2611 100644 --- a/tools/gen_code_vectors.py +++ b/tools/gen_code_vectors.py @@ -21,7 +21,10 @@ from pathlib import Path from typing import Any, cast import barcode_ref as ref +import ppf.datamatrix.codec_ascii # noqa: F401 (registers the datamatrix.ascii codec) import segno +from pdf417gen import encode as pdf417_encode +from ppf.datamatrix import DataMatrix CHECK_ADD, CHECK_FIX, CHECK_OFF = 0, 1, 2 TEXT_ON = 0x10 @@ -183,6 +186,126 @@ def qr_reference(payload: str, level: int, version: int, mask: int) -> tuple[int return int(qr.designator.split("-")[0]), qr.mask, packed.hex() +def dm_cases(rng: random.Random, count: int) -> list[str]: + """ASCII payloads for Data Matrix (ppf.datamatrix has no Base256 or + extended ASCII; those are covered by decoding). Lengths cover small, + multi-region and the largest (144 x 144, multi-block) sizes.""" + fixed = ["a", "123456", "Hello, World!", "0" * 44, "ABC-0042/fake member", "9" * 300, "x" * 500, "7" * 3116] + printable = string.ascii_letters + string.digits + " .,:;-/+()" + out = list(fixed) + for _ in range(count): + n = rng.choice([rng.randint(1, 40), rng.randint(40, 400)]) + out.append("".join(rng.choice(printable) for _ in range(n))) + return out + + +# Data codewords per square Data Matrix size (ISO/IEC 16022 table 7). +DM_CAPACITY = { + 10: 3, + 12: 5, + 14: 8, + 16: 12, + 18: 18, + 20: 22, + 22: 30, + 24: 36, + 26: 44, + 32: 62, + 36: 86, + 40: 114, + 44: 144, + 48: 174, + 52: 204, + 64: 280, + 72: 368, + 80: 456, + 88: 576, + 96: 696, + 104: 816, + 120: 1050, + 132: 1304, + 144: 1558, +} + + +def ppf_pad_differs(payload: str, size: int) -> bool: + """True when ppf.datamatrix would write a pad codeword of 0 instead of 254. + + ppf computes the 253-state pad as (R + 130) % 254, which turns 254 into 0; + ISO/IEC 16022 5.2.3 only subtracts 254 above 254. The watch follows the + standard, so such payloads make no exact reference. + """ + used = len(payload.encode("datamatrix.ascii")) + capacity = DM_CAPACITY[size] + return any((149 * pos) % 253 == 124 for pos in range(used + 2, capacity + 1)) + + +def dm_reference(payload: str) -> tuple[int, str]: + """ppf.datamatrix's (size, matrix as hex of row-major bits), ASCII only.""" + matrix = DataMatrix(payload, codecs=["ascii"]).matrix + bits = "".join(str(b) for row in matrix for b in row) + bits += "0" * (-len(bits) % 8) + packed = bytes(int(bits[i : i + 8], 2) for i in range(0, len(bits), 8)) + return len(matrix), packed.hex() + + +def pdf417_cases(rng: random.Random, count: int) -> list[tuple[str, int, int]]: + """(payload, columns, level): text in all submodes, digit runs below and + above 13, UTF-8 bytes, exact 6-byte groups, levels 0-6, 1-12 columns.""" + fixed = [ + ("Hello, World!", 3, 2), + ("abcABC123;:@#", 4, 1), + ("Member 0042 - fake card", 2, 2), + ("12345678901234567890123456789012345678901234567890", 3, 3), + ("Zürich ✓ fake data", 4, 2), + ("\x01\x02\x03\x04\x05\x06", 2, 0), + ("tab\there\nnew line\r end", 5, 4), + ("x" * 200, 8, 5), + ("0" * 13 + "abc" + "1" * 12, 3, 2), + ("https://example.com/member/fake?id=0042&level=gold", 6, 6), + ] + out = list(fixed) + printable = string.ascii_letters + string.digits + " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + for _ in range(count): + n = rng.randint(1, 120) + payload = "".join(rng.choice(printable) if rng.random() < 0.8 else rng.choice("éßü€") for _ in range(n)) + out.append((payload, rng.randint(1, 12), rng.randint(0, 5))) + return out + + +def pdf417_reference(payload: str, columns: int, level: int) -> tuple[int, int, str] | None: + """pdf417gen's (rows, width, rows of modules as hex), or None when the + combination is outside PDF417's limits (fewer than 3 or more than 90 rows).""" + try: + codes = pdf417_encode(payload, columns=columns, security_level=level) + except ValueError: + return None + rows = ["".join(format(value, "b") for value in row) for row in codes] + bits = "".join(rows) + bits += "0" * (-len(bits) % 8) + packed = bytes(int(bits[i : i + 8], 2) for i in range(0, len(bits), 8)) + return len(rows), len(rows[0]), packed.hex() + + +def mc_string(text: str) -> str: + """A Monkey C string literal body for text (quotes, backslashes, controls escaped).""" + out = [] + for ch in text: + if ch in '\\"': + out.append("\\" + ch) + elif ch == "\n": + out.append("\\n") + elif ch == "\t": + out.append("\\t") + elif ch == "\r": + out.append("\\r") + elif ord(ch) < 32: + out.append(f"\\u{ord(ch):04x}") + else: + out.append(ch) + return "".join(out) + + def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: lines = [] for symbology, payload, opts, text in rows: @@ -202,6 +325,23 @@ def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: escaped = payload.replace("\\", "\\\\").replace('"', '\\"') qr_lines.append(f' ["{escaped}", {level}, {version}, {mask}, {ref_version}, {ref_mask}, "{hexbits}"]') qr_body = ",\n".join(qr_lines) + dm_lines = [] + for dm_case in dm_cases(random.Random(seed + 2), 20): + payload = dm_case + size, hexbits = dm_reference(payload) + while ppf_pad_differs(payload, size): + payload += payload[-1] + size, hexbits = dm_reference(payload) + dm_lines.append(f' ["{payload}", {size}, "{hexbits}"]') + dm_body = ",\n".join(dm_lines) + pdf_lines = [] + for payload, columns, level in pdf417_cases(random.Random(seed + 3), 20): + reference = pdf417_reference(payload, columns, level) + if reference is None or any(ord(ch) < 32 and ch not in "\t\n\r" for ch in payload): + continue + n_rows, width, hexbits = reference + pdf_lines.append(f' ["{mc_string(payload)}", {columns}, {level}, {n_rows}, {width}, "{hexbits}"]') + pdf_body = ",\n".join(pdf_lines) return ( "// Author: cirakg & claude ai\n" "// Summary: zxing-cpp reference module rows for the linear barcode encoders (generated; do not edit).\n" @@ -212,6 +352,12 @@ def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: + "\n ];\n\n // [payload, level, version (0 auto), mask (-1 auto), segno version, segno mask,\n" + " // segno matrix as hex of row-major bits]\n const QR = [\n" + qr_body + + "\n ];\n\n // [payload, ppf.datamatrix size, its matrix as hex of row-major bits] (ASCII mode)\n" + + " const DATAMATRIX = [\n" + + dm_body + + "\n ];\n\n // [payload, columns, level, pdf417gen rows, width, one module row per PDF417 row as hex]\n" + + " const PDF417 = [\n" + + pdf_body + "\n ];\n}\n" ) diff --git a/tools/gen_pdf417_tables.py b/tools/gen_pdf417_tables.py new file mode 100644 index 0000000..7a66677 --- /dev/null +++ b/tools/gen_pdf417_tables.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# Author: cirakg & claude ai +# Summary: Generate the PDF417 table resources (patterns, EC factors, text table) from pdf417gen's published tables. +"""PDF417 tables for the watch (ISO/IEC 15438), taken from pdf417gen so the +2,787 bar patterns aren't typed by hand. Five JSON resources (the compiler +warns about resources over 8 KB): + +- codes0, codes1, codes2: the 17-module pattern of every codeword (0-928) + in clusters 0, 3 and 6 +- ec: the Reed-Solomon factors (mod 929) for levels 0-8 +- text: per text-compaction submode (upper, lower, mixed, punctuation), the + value of ASCII 0-126 in that submode, or -1 + +The app loads them only while it encodes a PDF417 code. + + gen_pdf417_tables.py [--out-dir resources/pdf417] +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from pdf417gen import codes, data + +SUBMODES = [data.UPPER, data.LOWER, data.MIXED, data.PUNCT] + + +HEADER = {"_author": "cirakg & claude ai"} + + +def tables() -> dict[str, object]: + """Resource name -> JSON content.""" + text = [[data.CHARACTERS_LOOKUP.get(c, {}).get(mode, -1) for c in range(127)] for mode in SUBMODES] + out: dict[str, object] = {} + for k in range(3): + out[f"codes{k}"] = { + **HEADER, + "_summary": f"PDF417 cluster {3 * k} patterns (generated; do not edit).", + "v": list(codes.CODES[k]), + } + out["ec"] = { + **HEADER, + "_summary": "PDF417 EC factors per level (generated; do not edit).", + "v": [list(f) for f in data.ERROR_CORRECTION_FACTORS], + } + out["text"] = {**HEADER, "_summary": "PDF417 text submode values (generated; do not edit).", "v": text} + return out + + +def resources_xml(names: list[str]) -> str: + rows = "\n".join(f' <jsonData id="Pdf417{name.capitalize()}" filename="{name}.json" />' for name in names) + return ( + "<!--\n Author: cirakg & claude ai\n" + " Summary: The PDF417 tables as JSON resources, loaded only while a PDF417 code is encoded (generated).\n-->\n" + f"<resources>\n{rows}\n</resources>\n" + ) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--out-dir", type=Path, default=Path("resources/pdf417")) + args = parser.parse_args(argv) + args.out_dir.mkdir(parents=True, exist_ok=True) + for old in args.out_dir.glob("*.json"): + old.unlink() + content = tables() + for name, value in content.items(): + (args.out_dir / f"{name}.json").write_text(json.dumps(value, separators=(",", ":")) + "\n", encoding="utf-8") + (args.out_dir / "pdf417.xml").write_text(resources_xml(list(content)), encoding="utf-8") + print(f"wrote {len(content)} PDF417 table resources to {args.out_dir}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/tests/test_barcode_tools.py b/tools/tests/test_barcode_tools.py index 0ee3cc4..5a2e999 100644 --- a/tools/tests/test_barcode_tools.py +++ b/tools/tests/test_barcode_tools.py @@ -8,6 +8,7 @@ from pathlib import Path import barcode_ref as ref import check_codes import gen_code_vectors +import gen_pdf417_tables import gen_qr_tables ROOT = Path(__file__).resolve().parents[2] @@ -83,5 +84,28 @@ def test_segno_padding_quirk_detection() -> None: def test_rendered_qr_matrix_decodes() -> None: _, _, hexbits = gen_code_vectors.qr_reference("HELLO WORLD", 1, 1, 2) - image = ref.render_matrix(bytes.fromhex(hexbits), 21, 2, quiet=4) + image = ref.render_matrix(bytes.fromhex(hexbits), (21, 21), 2, quiet=4) assert ref.decode(image, ref.QR) == ["HELLO WORLD"] + + +def test_committed_pdf417_tables_match_pdf417gen(tmp_path: Path) -> None: + gen_pdf417_tables.main(["--out-dir", str(tmp_path)]) + for path in tmp_path.iterdir(): + assert path.read_text(encoding="utf-8") == (ROOT / "resources" / "pdf417" / path.name).read_text( + encoding="utf-8" + ) + codes = gen_pdf417_tables.tables() + assert len(codes["codes0"]["v"]) == 929 # type: ignore[index] + + +def test_ppf_padding_quirk_detection() -> None: + # (149 * pos) % 253 == 124 at pos 124 * 149^-1 mod 253; a 10x10 symbol + # (3 data codewords) has no pad at such a position. + assert not gen_code_vectors.ppf_pad_differs("a", 10) + assert any(gen_code_vectors.ppf_pad_differs("x" * n, 88) for n in (480, 500, 520)) + + +def test_pdf417_reference_rows() -> None: + reference = gen_code_vectors.pdf417_reference("Hello, World!", 3, 2) + assert reference is not None and reference[0] == 7 and reference[1] == 17 * (3 + 4) + 1 + assert gen_code_vectors.pdf417_reference("ab", 30, 0) is None # one row: below the minimum of 3 diff --git a/tools/ui_flow.py b/tools/ui_flow.py index 5428509..eea3e17 100644 --- a/tools/ui_flow.py +++ b/tools/ui_flow.py @@ -63,6 +63,8 @@ GYM_CARD = "5901234123457" # source/app/Fixtures.mc: EAN-13 with its check digi MEMBER_QR = ( # source/app/Fixtures.mc: a version 6 QR "https://example.com/member/fake-0042?name=Alice%20Example&card=0000111122223333&valid=2027-12-31&level=gold" ) +PARCEL_DM = "PARCEL-FAKE-0042-7731" # source/app/Fixtures.mc: Data Matrix +TICKET_PDF417 = "Boarding pass FAKE 0042 seat 12A" # source/app/Fixtures.mc: PDF417 QR_TIMEOUT_S = 30.0 # the QR is encoded in timer steps the first time @@ -430,7 +432,8 @@ def flow_lockout(f: Flow) -> None: def flow_code(f: Flow) -> None: - """The EAN-13 gym card (index 6) and the QR card (7): their screenshots must decode.""" + """The EAN-13 gym card (index 6), the QR (7), Data Matrix (8) and PDF417 (9) + cards: their screenshots must decode.""" f.act() f.back() # leave the waiting PIN screen f.expect("list") @@ -448,10 +451,20 @@ def flow_code(f: Flow) -> None: f.act() f.open_focused_row() f.expect("code", QR_TIMEOUT_S) - encoded = f.log.latest(r"DIAG qr_encoded version=(\d+) ms=(\d+)") + encoded = f.log.latest(r"DIAG matrix_encoded type=\d+ size=(\S+) ms=(\d+)") if encoded is not None: - print(f"[{f.sim.device_dir.name}] QR version {encoded.group(1)} encoded in {encoded.group(2)} ms") + print(f"[{f.sim.device_dir.name}] QR {encoded.group(1)} encoded in {encoded.group(2)} ms") f.shot("code_qr", expect_code=MEMBER_QR) + # Data Matrix (8) and PDF417 (9), also encoded as jobs the first time. + for name, expected in (("code_datamatrix", PARCEL_DM), ("code_pdf417", TICKET_PDF417)): + f.act() + f.back() + f.expect("list") + f.next() + f.act() + f.open_focused_row() + f.expect("code", QR_TIMEOUT_S) + f.shot(name, expect_code=expected) def run_flow(f: Flow) -> None: |
