From 1ebc828f30698c1661055e17483e8d50a139ed45 Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 12:16:59 +0200 Subject: test(codes): compare Aztec with aztec_code_generator, decode it and its screenshot Co-Authored-By: Claude Opus 5.5 --- tools/gen_code_vectors.py | 45 +++++++++++++++++++++++++++++++++++++++ tools/tests/test_barcode_tools.py | 8 +++++++ tools/ui_flow.py | 9 ++++---- 3 files changed, 58 insertions(+), 4 deletions(-) (limited to 'tools') diff --git a/tools/gen_code_vectors.py b/tools/gen_code_vectors.py index 5ce2611..1472bb7 100644 --- a/tools/gen_code_vectors.py +++ b/tools/gen_code_vectors.py @@ -23,6 +23,7 @@ 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 aztec_code_generator import AztecCode from pdf417gen import encode as pdf417_encode from ppf.datamatrix import DataMatrix @@ -287,6 +288,42 @@ def pdf417_reference(payload: str, columns: int, level: int) -> tuple[int, int, return len(rows), len(rows[0]), packed.hex() +AZTEC_EC = [10, 23, 36, 50] + + +def aztec_cases(rng: random.Random, count: int) -> list[tuple[str, int]]: + """(payload, EC option 0-3) with one character class each (capitals, small + letters or digits, plus spaces): for these the reference's optimal mode + sequence and the watch's encodation give the same bits, so the symbols + must match exactly. Sizes reach full range with reference grids.""" + fixed = [ + ("A", 1), + ("HELLO WORLD", 1), + ("hello world", 0), + ("0042 7731", 2), + ("FAKE TICKET " * 10, 1), + ("abc def " * 40, 3), + ("1234567890" * 60, 1), + ("Z" * 900, 0), + ] + out = list(fixed) + for _ in range(count): + alphabet = rng.choice([string.ascii_uppercase + " ", string.ascii_lowercase + " ", string.digits + " "]) + n = rng.randint(1, 200) + payload = "".join(rng.choice(alphabet) for _ in range(n)).strip() or "X" + out.append((payload, rng.randrange(4))) + return out + + +def aztec_reference(payload: str, option: int) -> tuple[int, str]: + """aztec_code_generator's (size, matrix as hex of row-major bits).""" + code = AztecCode(payload, ec_percent=AZTEC_EC[option]) + bits = "".join(str(b) for row in code.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 code.size, packed.hex() + + def mc_string(text: str) -> str: """A Monkey C string literal body for text (quotes, backslashes, controls escaped).""" out = [] @@ -342,6 +379,11 @@ def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: n_rows, width, hexbits = reference pdf_lines.append(f' ["{mc_string(payload)}", {columns}, {level}, {n_rows}, {width}, "{hexbits}"]') pdf_body = ",\n".join(pdf_lines) + aztec_lines = [] + for payload, option in aztec_cases(random.Random(seed + 4), 16): + size, hexbits = aztec_reference(payload, option) + aztec_lines.append(f' ["{payload}", {option}, {size}, "{hexbits}"]') + aztec_body = ",\n".join(aztec_lines) return ( "// Author: cirakg & claude ai\n" "// Summary: zxing-cpp reference module rows for the linear barcode encoders (generated; do not edit).\n" @@ -358,6 +400,9 @@ def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: + "\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 // [payload, EC option, aztec_code_generator size, its matrix as hex of row-major bits]\n" + + " const AZTEC = [\n" + + aztec_body + "\n ];\n}\n" ) diff --git a/tools/tests/test_barcode_tools.py b/tools/tests/test_barcode_tools.py index 5a2e999..f63b3fe 100644 --- a/tools/tests/test_barcode_tools.py +++ b/tools/tests/test_barcode_tools.py @@ -109,3 +109,11 @@ 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 + + +def test_aztec_reference_is_compact_for_short_text() -> None: + size, hexbits = gen_code_vectors.aztec_reference("HELLO WORLD", 1) + assert size == 15 # compact, one layer + assert len(bytes.fromhex(hexbits)) == (15 * 15 + 7) // 8 + image = ref.render_matrix(bytes.fromhex(hexbits), (15, 15), 3, quiet=2) + assert ref.decode(image, ref.AZTEC) == ["HELLO WORLD"] diff --git a/tools/ui_flow.py b/tools/ui_flow.py index eea3e17..e54b530 100644 --- a/tools/ui_flow.py +++ b/tools/ui_flow.py @@ -65,6 +65,7 @@ MEMBER_QR = ( # source/app/Fixtures.mc: a version 6 QR ) 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 +RAIL_AZTEC = "RAIL-FAKE-0042 Zone 1-3" # source/app/Fixtures.mc: Aztec QR_TIMEOUT_S = 30.0 # the QR is encoded in timer steps the first time @@ -432,8 +433,8 @@ def flow_lockout(f: Flow) -> None: def flow_code(f: Flow) -> None: - """The EAN-13 gym card (index 6), the QR (7), Data Matrix (8) and PDF417 (9) - cards: their screenshots must decode.""" + """The EAN-13 gym card (index 6), the QR (7), Data Matrix (8), PDF417 (9) + and Aztec (10) cards: their screenshots must decode.""" f.act() f.back() # leave the waiting PIN screen f.expect("list") @@ -455,8 +456,8 @@ def flow_code(f: Flow) -> None: if encoded is not None: 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)): + # Data Matrix (8), PDF417 (9) and Aztec (10), also encoded as jobs. + for name, expected in (("code_datamatrix", PARCEL_DM), ("code_pdf417", TICKET_PDF417), ("code_aztec", RAIL_AZTEC)): f.act() f.back() f.expect("list") -- cgit v1.2.3