From fa9c695256de1c7fe007ba5d6bb9b28b1deb5a5a Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 02:43:08 +0200 Subject: test(codes): compare with zxing-cpp reference rows and decode encoder output and screenshots Co-Authored-By: Claude Opus 5.5 --- tools/gen_code_vectors.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tools/gen_code_vectors.py (limited to 'tools/gen_code_vectors.py') diff --git a/tools/gen_code_vectors.py b/tools/gen_code_vectors.py new file mode 100644 index 0000000..970ca06 --- /dev/null +++ b/tools/gen_code_vectors.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# Author: cirakg & claude ai +# Summary: Generate test/data/CodeVectors.mc: zxing-cpp reference module rows for the linear barcode encoders. +"""Reference module rows for README 12.4 check 1 (all fake payloads). + +For every linear symbology: fixed edge cases plus seeded random payloads, +each with the options the watch uses and zxing-cpp's module row. Where the +watch adds something zxing doesn't (a Code 39 or ITF check character, Codabar +start/stop characters), the reference is made from the completed text. + + gen_code_vectors.py [--seed 1212] [--count 25] [--out test/data/CodeVectors.mc] +""" + +from __future__ import annotations + +import argparse +import random +import string +from pathlib import Path + +import barcode_ref as ref + +CHECK_ADD, CHECK_FIX, CHECK_OFF = 0, 1, 2 +TEXT_ON = 0x10 + + +def options(check: int) -> int: + return TEXT_ON | (check << 2) | 1 + + +def digits(rng: random.Random, n: int) -> str: + return "".join(rng.choice(string.digits) for _ in range(n)) + + +def valid_upce(rng: random.Random) -> str: + """Six UPC-E digits that are a canonical zero suppression.""" + while True: + d = digits(rng, 6) + last = d[5] + if (last == "3" and d[2] in "012") or (last == "4" and d[3] == "0") or (last in "56789" and d[4] == "0"): + continue + return d + + +def cases(rng: random.Random, count: int) -> list[tuple[int, str, int, str]]: + """(symbology, payload, options, zxing text) tuples.""" + out: list[tuple[int, str, int, str]] = [] + fixed = [ + (ref.EAN13, "590123412345", options(CHECK_ADD), "590123412345"), + (ref.EAN13, "5901234123457", options(CHECK_ADD), "5901234123457"), + (ref.EAN8, "1234567", options(CHECK_ADD), "1234567"), + (ref.UPCA, "01234567890", options(CHECK_ADD), "01234567890"), + (ref.UPCE, "0425261", options(CHECK_ADD), "0425261"), + (ref.UPCE, "1234565", options(CHECK_ADD), "1234565"), + (ref.CODE128, "1234", options(CHECK_ADD), "1234"), + (ref.CODE128, "123456789", options(CHECK_ADD), "123456789"), + (ref.CODE128, "AB12345678", options(CHECK_ADD), "AB12345678"), + (ref.CODE128, "A1234B12345", options(CHECK_ADD), "A1234B12345"), + (ref.CODE128, " ~", options(CHECK_ADD), " ~"), + (ref.CODE39, "CODE 39", options(CHECK_OFF), "CODE 39"), + (ref.CODE39, "CODE-39", options(CHECK_ADD), "CODE-39" + ref.code39_check("CODE-39")), + (ref.ITF, "1234", options(CHECK_OFF), "1234"), + (ref.ITF, "123", options(CHECK_ADD), "123" + str(ref.gs1_check("123"))), + (ref.CODABAR, "A40156B", options(CHECK_ADD), "A40156B"), + (ref.CODABAR, "31117013206375", options(CHECK_ADD), "A31117013206375A"), + ] + out.extend(fixed) + printable = string.ascii_letters + string.digits + " !#$%&()*+,-./:;<=>?@[]^_{|}~" + for _ in range(count): + out.append((ref.EAN13, digits(rng, 12), options(CHECK_ADD), "")) + out.append((ref.EAN8, digits(rng, 7), options(CHECK_ADD), "")) + out.append((ref.UPCA, digits(rng, 11), options(CHECK_ADD), "")) + out.append((ref.UPCE, rng.choice("01") + valid_upce(rng), options(CHECK_ADD), "")) + n = rng.randint(1, 20) + mixed = "".join(rng.choice(string.digits) if rng.random() < 0.5 else rng.choice(printable) for _ in range(n)) + out.append((ref.CODE128, mixed, options(CHECK_ADD), mixed)) + c39 = "".join(rng.choice(ref.CODE39_ALPHABET) for _ in range(rng.randint(1, 12))) + with_check = rng.random() < 0.5 + out.append( + ( + ref.CODE39, + c39, + options(CHECK_ADD if with_check else CHECK_OFF), + c39 + ref.code39_check(c39) if with_check else c39, + ) + ) + itf = digits(rng, 2 * rng.randint(1, 8)) + out.append((ref.ITF, itf, options(CHECK_OFF), itf)) + inner = "".join(rng.choice("0123456789-$:/.+") for _ in range(rng.randint(1, 12))) + guard = rng.choice("ABCD") + inner + rng.choice("ABCD") + out.append((ref.CODABAR, guard, options(CHECK_ADD), guard)) + return out + + +def render(rows: list[tuple[int, str, int, str]], seed: int) -> str: + lines = [] + for symbology, payload, opts, text in rows: + modules = ref.reference_modules(symbology, text or payload) + escaped = payload.replace("\\", "\\\\").replace('"', '\\"') + lines.append(f' [{symbology}, "{escaped}", {opts}, "{modules}"]') + body = ",\n".join(lines) + return ( + "// Author: cirakg & claude ai\n" + "// Summary: zxing-cpp reference module rows for the linear barcode encoders (generated; do not edit).\n" + f"//\n// Generated by tools/gen_code_vectors.py, seed {seed}. Rows: [symbology, payload, options,\n" + "// expected modules without quiet zones]. Fake payloads only.\n\n" + "import Toybox.Lang;\n\n(:test)\nmodule CodeVectors {\n const LINEAR = [\n" + body + "\n ];\n}\n" + ) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--seed", type=int, default=1212) + parser.add_argument("--count", type=int, default=25) + parser.add_argument("--out", type=Path, default=Path("test/data/CodeVectors.mc")) + args = parser.parse_args(argv) + rows = cases(random.Random(args.seed), args.count) + args.out.write_text(render(rows, args.seed), encoding="utf-8") + print(f"wrote {len(rows)} reference rows to {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) -- cgit v1.2.3