From f455246fde086dda0cb61b92c84a5146a831a2bf Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 06:56:58 +0200 Subject: test(codes): compare QR with segno, decode QR dumps and the QR screenshot Co-Authored-By: Claude Opus 5.5 --- tools/gen_qr_tables.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tools/gen_qr_tables.py (limited to 'tools/gen_qr_tables.py') diff --git a/tools/gen_qr_tables.py b/tools/gen_qr_tables.py new file mode 100644 index 0000000..68e6355 --- /dev/null +++ b/tools/gen_qr_tables.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Author: cirakg & claude ai +# Summary: Generate source/codes/QrTables.mc (QR error-correction block structure) from segno's published constants. +"""QR block structure for versions 1-40 and levels L, M, Q, H (ISO/IEC 18004 +table 9), taken from segno so it isn't typed by hand. Each entry is +[ec codewords per block, blocks in group 1, data codewords per group-1 +block, blocks in group 2, data codewords per group-2 block]. + + gen_qr_tables.py [--out source/codes/QrTables.mc] +""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from typing import Any, cast + +import segno.consts as c + +LEVELS = [c.ERROR_LEVEL_L, c.ERROR_LEVEL_M, c.ERROR_LEVEL_Q, c.ERROR_LEVEL_H] +# segno.consts.ECC: version -> level -> tuple of EC(num_blocks, num_total, num_data); untyped in segno. +ECC = cast(dict[int, dict[int, tuple[Any, ...]]], c.ECC) + + +def table() -> list[list[int]]: + rows = [] + for version in range(1, 41): + for level in LEVELS: + groups = ECC[version][level] + ec = groups[0].num_total - groups[0].num_data + g1 = groups[0] + g2 = groups[1] if len(groups) > 1 else None + assert all(g.num_total - g.num_data == ec for g in groups) + rows.append([ec, g1.num_blocks, g1.num_data, g2.num_blocks if g2 else 0, g2.num_data if g2 else 0]) + return rows + + +def alignment() -> list[list[int]]: + """Alignment pattern centre coordinates per version (ISO/IEC 18004 annex E).""" + # segno starts at version 2; version 1 has no alignment pattern. + return [[]] + [list(c.ALIGNMENT_POS[v - 2]) for v in range(2, 41)] + + +def render(rows: list[list[int]]) -> str: + lines = [] + for version in range(1, 41): + cells = ", ".join(str(n) for level in range(4) for n in rows[(version - 1) * 4 + level]) + lines.append(f" {cells}{'' if version == 40 else ','} // version {version}: L, M, Q, H") + return ( + "// Author: cirakg & claude ai\n" + "// Summary: QR error-correction block structure per version and level (generated from segno; do not edit).\n" + "//\n" + "// Generated by tools/gen_qr_tables.py. For version v (1-40) and level e (0 L, 1 M, 2 Q, 3 H), the five\n" + "// numbers at index ((v - 1) * 4 + e) * 5 are: EC codewords per block, group-1 blocks, data codewords\n" + "// per group-1 block, group-2 blocks, data codewords per group-2 block (ISO/IEC 18004 table 9).\n\n" + "import Toybox.Lang;\n\nmodule QrTables {\n const BLOCKS = [\n" + + "\n".join(lines) + + "\n ];\n\n // Alignment pattern centres (row and column coordinates) per version, from version 1.\n" + + " const ALIGNMENT = [\n" + + ",\n".join(f" {pos}" for pos in alignment()) + + "\n ];\n}\n" + ) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--out", type=Path, default=Path("source/codes/QrTables.mc")) + args = parser.parse_args(argv) + args.out.write_text(render(table()), encoding="utf-8") + print(f"wrote the QR block table to {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) -- cgit v1.2.3