aboutsummaryrefslogtreecommitdiff
path: root/tools/gen_qr_tables.py
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 06:56:58 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 06:56:58 +0200
commitf455246fde086dda0cb61b92c84a5146a831a2bf (patch)
tree09b5e97ee5f84e495bbd0646e1ad0e3e2fbfb9af /tools/gen_qr_tables.py
parent2df98d1ba129f95a10c6b81665671d3b12560c79 (diff)
test(codes): compare QR with segno, decode QR dumps and the QR screenshot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/gen_qr_tables.py')
-rw-r--r--tools/gen_qr_tables.py75
1 files changed, 75 insertions, 0 deletions
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())