summaryrefslogtreecommitdiff
path: root/tools/gen_code_vectors.py
blob: 970ca06e8c40625f506ef27a77fedae54cdb01f1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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())