hexatess-code 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. hexatess_code-0.1.0/LICENSE +30 -0
  2. hexatess_code-0.1.0/PKG-INFO +192 -0
  3. hexatess_code-0.1.0/README.md +132 -0
  4. hexatess_code-0.1.0/pyproject.toml +53 -0
  5. hexatess_code-0.1.0/setup.cfg +4 -0
  6. hexatess_code-0.1.0/src/hexatess/__init__.py +83 -0
  7. hexatess_code-0.1.0/src/hexatess/cli.py +65 -0
  8. hexatess_code-0.1.0/src/hexatess/decoder.py +75 -0
  9. hexatess_code-0.1.0/src/hexatess/encoder.py +122 -0
  10. hexatess_code-0.1.0/src/hexatess/galois.py +82 -0
  11. hexatess_code-0.1.0/src/hexatess/geometry.py +69 -0
  12. hexatess_code-0.1.0/src/hexatess/header.py +120 -0
  13. hexatess_code-0.1.0/src/hexatess/masks.py +48 -0
  14. hexatess_code-0.1.0/src/hexatess/reedsolomon.py +161 -0
  15. hexatess_code-0.1.0/src/hexatess/render.py +75 -0
  16. hexatess_code-0.1.0/src/hexatess/resilience.py +95 -0
  17. hexatess_code-0.1.0/src/hexatess_code.egg-info/PKG-INFO +192 -0
  18. hexatess_code-0.1.0/src/hexatess_code.egg-info/SOURCES.txt +29 -0
  19. hexatess_code-0.1.0/src/hexatess_code.egg-info/dependency_links.txt +1 -0
  20. hexatess_code-0.1.0/src/hexatess_code.egg-info/entry_points.txt +2 -0
  21. hexatess_code-0.1.0/src/hexatess_code.egg-info/requires.txt +4 -0
  22. hexatess_code-0.1.0/src/hexatess_code.egg-info/top_level.txt +1 -0
  23. hexatess_code-0.1.0/tests/test_codec.py +147 -0
  24. hexatess_code-0.1.0/tests/test_conformance.py +77 -0
  25. hexatess_code-0.1.0/tests/test_galois.py +111 -0
  26. hexatess_code-0.1.0/tests/test_geometry.py +103 -0
  27. hexatess_code-0.1.0/tests/test_header.py +87 -0
  28. hexatess_code-0.1.0/tests/test_masks.py +57 -0
  29. hexatess_code-0.1.0/tests/test_reedsolomon.py +109 -0
  30. hexatess_code-0.1.0/tests/test_render.py +73 -0
  31. hexatess_code-0.1.0/tests/test_resilience.py +72 -0
@@ -0,0 +1,30 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The Hexatess Code Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ----------------------------------------------------------------------
24
+ Note on the specification:
25
+
26
+ The format specification (SPECIFICATION.md) is additionally released under
27
+ the Creative Commons Attribution 4.0 International license (CC-BY-4.0), so
28
+ that anyone may implement compatible encoders/decoders in any language,
29
+ under any license, without restriction. Provide attribution as:
30
+ "Hexatess Code Specification v0.1, https://github.com/<you>/hexatess-code".
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexatess-code
3
+ Version: 0.1.0
4
+ Summary: Hexatess Code - an experimental 2D barcode on a hexagonal grid with Reed-Solomon error correction
5
+ Author: The Hexatess Code Authors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 The Hexatess Code Authors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ ----------------------------------------------------------------------
29
+ Note on the specification:
30
+
31
+ The format specification (SPECIFICATION.md) is additionally released under
32
+ the Creative Commons Attribution 4.0 International license (CC-BY-4.0), so
33
+ that anyone may implement compatible encoders/decoders in any language,
34
+ under any license, without restriction. Provide attribution as:
35
+ "Hexatess Code Specification v0.1, https://github.com/<you>/hexatess-code".
36
+
37
+ Project-URL: Homepage, https://github.com/lovro-abram/hexatess-code
38
+ Project-URL: Specification, https://github.com/lovro-abram/hexatess-code/blob/main/SPECIFICATION.md
39
+ Project-URL: Issues, https://github.com/lovro-abram/hexatess-code/issues
40
+ Keywords: barcode,2d-barcode,qr-code,hexagonal,hexatess,reed-solomon,error-correction,aztec
41
+ Classifier: Development Status :: 3 - Alpha
42
+ Classifier: Intended Audience :: Developers
43
+ Classifier: License :: OSI Approved :: MIT License
44
+ Classifier: Operating System :: OS Independent
45
+ Classifier: Programming Language :: Python :: 3
46
+ Classifier: Programming Language :: Python :: 3.8
47
+ Classifier: Programming Language :: Python :: 3.9
48
+ Classifier: Programming Language :: Python :: 3.10
49
+ Classifier: Programming Language :: Python :: 3.11
50
+ Classifier: Programming Language :: Python :: 3.12
51
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
52
+ Classifier: Topic :: Multimedia :: Graphics
53
+ Requires-Python: >=3.8
54
+ Description-Content-Type: text/markdown
55
+ License-File: LICENSE
56
+ Requires-Dist: pillow>=9.0
57
+ Provides-Extra: dev
58
+ Requires-Dist: pytest>=7.0; extra == "dev"
59
+ Dynamic: license-file
60
+
61
+ # Hexatess Code 🐝
62
+
63
+ **An experimental 2D barcode on a hexagonal grid** — with a hexagonal
64
+ bullseye finder, spiral serialization and a continuously selectable
65
+ Reed-Solomon error-correction budget of 5–90 %.
66
+
67
+ ![Hexatess Code example](docs/img/hexatess_primer.png)
68
+
69
+ ```python
70
+ from hexatess import encode, decode, render
71
+
72
+ grid, params = encode("Hello, Hexatess!", ec_pct=30)
73
+ render(grid, "hello.png")
74
+ text, stats = decode(grid) # ('Hello, Hexatess!', {...})
75
+ ```
76
+
77
+ ## Symbol anatomy
78
+
79
+ ![Symbol anatomy](docs/img/hexatess_anatomy.png)
80
+
81
+ * **A** — a real encoded symbol: hexagonal bullseye finder (rings 0–4),
82
+ orientation key (ring 5: two dark cells), data region (rings 6…)
83
+ filled in spiral order, and a quiet zone of at least 1 module;
84
+ * **B** — finder close-up: light centre (v0.1 rule `bit = ring mod 2`),
85
+ alternating dark/light rings, and the key — the first two canonical
86
+ ring-5 cells set dark, breaking the 60-fold symmetry and marking the
87
+ spiral start direction;
88
+ * **C** — spiral bit order across rings 6–7 (bit 0 at cell `(−6, +6)`),
89
+ rendered from the actual reference encoder output.
90
+
91
+ ## Why hexagons?
92
+
93
+ * **+15.5 % packing density** over the square grid — hexagons tile the
94
+ plane with ~15.5 % more modules per area at equal module size, which
95
+ directly translates into more data per printed area.
96
+ * **Rotational isotropy** — three axes of symmetry instead of two;
97
+ damage from any direction is statistically equivalent.
98
+ * **Proven heritage** — MaxiCode (UPS, ISO/IEC 16023) already proved a
99
+ hexagonal 2D code works in the field; Hexatess Code generalizes the
100
+ idea to variable-size, high-capacity, Aztec-style symbols.
101
+ * **Modern error control** — continuous EC budget from 5 % to 90 %
102
+ (not 7 discrete levels), independent RS blocks of ≤ 50 data bytes,
103
+ and a double-protected header.
104
+
105
+ > **Status: experimental.** This is a young format: the symbol
106
+ > specification and reference implementation are solid and heavily
107
+ > tested (2,500+ tests, conformance vectors), but there is **no camera
108
+ > decoder yet** — reading images assumes ideal upright sampling. See
109
+ > the roadmap below. Adopting a young format is a deliberate bet; the
110
+ > [full format specification](SPECIFICATION.md) is the insurance.
111
+
112
+ ## Installation
113
+
114
+ ```bash
115
+ pip install hexatess-code # from PyPI (once published)
116
+ # or from a source checkout:
117
+ pip install -e .
118
+ ```
119
+
120
+ Requires Python ≥ 3.8 and Pillow (for rendering only).
121
+
122
+ ## Command line
123
+
124
+ ```bash
125
+ hexatess "Hello world" -o koda.png --ec 30
126
+ hexatess "Important URL https://example.org" -o url.png --ec 55
127
+ hexatess-code --demo # demo symbol + robustness statistics
128
+ ```
129
+
130
+ ## API
131
+
132
+ | Function | Description |
133
+ |---|---|
134
+ | `encode(text, ec_pct=30, mask_id="auto", min_rings=None)` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
135
+ | `decode(grid)` | grid → `(text, stats)`; RS-corrects transparently |
136
+ | `render(grid, path, size_px=18, ...)` | grid → PNG (pointy-top hexagons, quiet zone, supersampling) |
137
+ | `sample_grid_from_image(path, rmax, ...)` | ideal re-sampling of a rendered PNG (self-test helper) |
138
+ | `run_tests(...)` | noise/blob robustness statistics |
139
+
140
+ `params` / `stats` contain `rmax` (radius in rings), `mask`, `ec`,
141
+ `blocks` (list of `(data_bytes, ecc_bytes)`) and `data_len`.
142
+
143
+ ## Error-correction budget
144
+
145
+ Choose any multiple of 5 between 5 and 90:
146
+
147
+ | EC | Character |
148
+ |---|---|
149
+ | 5–15 | maximum capacity, clean environments |
150
+ | 25–40 | general use (default 30) |
151
+ | 50–70 | industrial / outdoor |
152
+ | 80–90 | extreme damage tolerance |
153
+
154
+ Physical behaviour (measured on the reference implementation): one
155
+ flipped module is one RS *symbol* error, so uniform-noise tolerance is
156
+ roughly `EC / 16` percent of modules, while clustered (smudge/blob)
157
+ damage survives several times higher area fractions because flips
158
+ concentrate inside whole bytes.
159
+
160
+ ## Implement it in your own language
161
+
162
+ The format is deliberately **specification-first**: everything needed
163
+ for an independent implementation is in
164
+ [`SPECIFICATION.md`](SPECIFICATION.md), and
165
+ [`test_vectors/vectors_v0.1.json`](test_vectors/vectors_v0.1.json)
166
+ contains fixed inputs/outputs (grids, headers, damaged symbols, expected
167
+ results) to verify conformance. If your Rust/Go/JS decoder passes the
168
+ vectors, it speaks Hexatess Code.
169
+
170
+ ## Roadmap
171
+
172
+ 1. **v0.2 — camera decoding:** bullseye detection + perspective
173
+ correction (the critical ecosystem step).
174
+ 2. **v0.2 — erasure decoding:** declare blob-occluded modules as
175
+ erasures → doubles correctable symbol counts.
176
+ 3. **JavaScript/TypeScript SDK** + online playground (generate a code
177
+ in the browser in 10 seconds).
178
+ 4. Larger radii / capacity beyond 329 bytes (breaking header change).
179
+
180
+ Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
181
+
182
+ ## License
183
+
184
+ * Code: [MIT](LICENSE)
185
+ * Specification: CC-BY-4.0 — implement it anywhere, commercially, under
186
+ any license, no royalties, forever.
187
+
188
+ ---
189
+
190
+ *Hexatess Code stands on the shoulders of giants: Aztec Code (bullseye +
191
+ spiral), MaxiCode (hexagonal lattice), QR Code and Data Matrix
192
+ (Reed-Solomon practice).*
@@ -0,0 +1,132 @@
1
+ # Hexatess Code 🐝
2
+
3
+ **An experimental 2D barcode on a hexagonal grid** — with a hexagonal
4
+ bullseye finder, spiral serialization and a continuously selectable
5
+ Reed-Solomon error-correction budget of 5–90 %.
6
+
7
+ ![Hexatess Code example](docs/img/hexatess_primer.png)
8
+
9
+ ```python
10
+ from hexatess import encode, decode, render
11
+
12
+ grid, params = encode("Hello, Hexatess!", ec_pct=30)
13
+ render(grid, "hello.png")
14
+ text, stats = decode(grid) # ('Hello, Hexatess!', {...})
15
+ ```
16
+
17
+ ## Symbol anatomy
18
+
19
+ ![Symbol anatomy](docs/img/hexatess_anatomy.png)
20
+
21
+ * **A** — a real encoded symbol: hexagonal bullseye finder (rings 0–4),
22
+ orientation key (ring 5: two dark cells), data region (rings 6…)
23
+ filled in spiral order, and a quiet zone of at least 1 module;
24
+ * **B** — finder close-up: light centre (v0.1 rule `bit = ring mod 2`),
25
+ alternating dark/light rings, and the key — the first two canonical
26
+ ring-5 cells set dark, breaking the 60-fold symmetry and marking the
27
+ spiral start direction;
28
+ * **C** — spiral bit order across rings 6–7 (bit 0 at cell `(−6, +6)`),
29
+ rendered from the actual reference encoder output.
30
+
31
+ ## Why hexagons?
32
+
33
+ * **+15.5 % packing density** over the square grid — hexagons tile the
34
+ plane with ~15.5 % more modules per area at equal module size, which
35
+ directly translates into more data per printed area.
36
+ * **Rotational isotropy** — three axes of symmetry instead of two;
37
+ damage from any direction is statistically equivalent.
38
+ * **Proven heritage** — MaxiCode (UPS, ISO/IEC 16023) already proved a
39
+ hexagonal 2D code works in the field; Hexatess Code generalizes the
40
+ idea to variable-size, high-capacity, Aztec-style symbols.
41
+ * **Modern error control** — continuous EC budget from 5 % to 90 %
42
+ (not 7 discrete levels), independent RS blocks of ≤ 50 data bytes,
43
+ and a double-protected header.
44
+
45
+ > **Status: experimental.** This is a young format: the symbol
46
+ > specification and reference implementation are solid and heavily
47
+ > tested (2,500+ tests, conformance vectors), but there is **no camera
48
+ > decoder yet** — reading images assumes ideal upright sampling. See
49
+ > the roadmap below. Adopting a young format is a deliberate bet; the
50
+ > [full format specification](SPECIFICATION.md) is the insurance.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install hexatess-code # from PyPI (once published)
56
+ # or from a source checkout:
57
+ pip install -e .
58
+ ```
59
+
60
+ Requires Python ≥ 3.8 and Pillow (for rendering only).
61
+
62
+ ## Command line
63
+
64
+ ```bash
65
+ hexatess "Hello world" -o koda.png --ec 30
66
+ hexatess "Important URL https://example.org" -o url.png --ec 55
67
+ hexatess-code --demo # demo symbol + robustness statistics
68
+ ```
69
+
70
+ ## API
71
+
72
+ | Function | Description |
73
+ |---|---|
74
+ | `encode(text, ec_pct=30, mask_id="auto", min_rings=None)` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
75
+ | `decode(grid)` | grid → `(text, stats)`; RS-corrects transparently |
76
+ | `render(grid, path, size_px=18, ...)` | grid → PNG (pointy-top hexagons, quiet zone, supersampling) |
77
+ | `sample_grid_from_image(path, rmax, ...)` | ideal re-sampling of a rendered PNG (self-test helper) |
78
+ | `run_tests(...)` | noise/blob robustness statistics |
79
+
80
+ `params` / `stats` contain `rmax` (radius in rings), `mask`, `ec`,
81
+ `blocks` (list of `(data_bytes, ecc_bytes)`) and `data_len`.
82
+
83
+ ## Error-correction budget
84
+
85
+ Choose any multiple of 5 between 5 and 90:
86
+
87
+ | EC | Character |
88
+ |---|---|
89
+ | 5–15 | maximum capacity, clean environments |
90
+ | 25–40 | general use (default 30) |
91
+ | 50–70 | industrial / outdoor |
92
+ | 80–90 | extreme damage tolerance |
93
+
94
+ Physical behaviour (measured on the reference implementation): one
95
+ flipped module is one RS *symbol* error, so uniform-noise tolerance is
96
+ roughly `EC / 16` percent of modules, while clustered (smudge/blob)
97
+ damage survives several times higher area fractions because flips
98
+ concentrate inside whole bytes.
99
+
100
+ ## Implement it in your own language
101
+
102
+ The format is deliberately **specification-first**: everything needed
103
+ for an independent implementation is in
104
+ [`SPECIFICATION.md`](SPECIFICATION.md), and
105
+ [`test_vectors/vectors_v0.1.json`](test_vectors/vectors_v0.1.json)
106
+ contains fixed inputs/outputs (grids, headers, damaged symbols, expected
107
+ results) to verify conformance. If your Rust/Go/JS decoder passes the
108
+ vectors, it speaks Hexatess Code.
109
+
110
+ ## Roadmap
111
+
112
+ 1. **v0.2 — camera decoding:** bullseye detection + perspective
113
+ correction (the critical ecosystem step).
114
+ 2. **v0.2 — erasure decoding:** declare blob-occluded modules as
115
+ erasures → doubles correctable symbol counts.
116
+ 3. **JavaScript/TypeScript SDK** + online playground (generate a code
117
+ in the browser in 10 seconds).
118
+ 4. Larger radii / capacity beyond 329 bytes (breaking header change).
119
+
120
+ Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
121
+
122
+ ## License
123
+
124
+ * Code: [MIT](LICENSE)
125
+ * Specification: CC-BY-4.0 — implement it anywhere, commercially, under
126
+ any license, no royalties, forever.
127
+
128
+ ---
129
+
130
+ *Hexatess Code stands on the shoulders of giants: Aztec Code (bullseye +
131
+ spiral), MaxiCode (hexagonal lattice), QR Code and Data Matrix
132
+ (Reed-Solomon practice).*
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hexatess-code"
7
+ version = "0.1.0"
8
+ description = "Hexatess Code - an experimental 2D barcode on a hexagonal grid with Reed-Solomon error correction"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "The Hexatess Code Authors" }]
13
+ keywords = [
14
+ "barcode", "2d-barcode", "qr-code", "hexagonal", "hexatess",
15
+ "reed-solomon", "error-correction", "aztec",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Topic :: Multimedia :: Graphics",
30
+ ]
31
+ dependencies = [
32
+ "pillow>=9.0",
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ dev = [
37
+ "pytest>=7.0",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/lovro-abram/hexatess-code"
42
+ Specification = "https://github.com/lovro-abram/hexatess-code/blob/main/SPECIFICATION.md"
43
+ Issues = "https://github.com/lovro-abram/hexatess-code/issues"
44
+
45
+ [project.scripts]
46
+ hexatess = "hexatess.cli:main"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,83 @@
1
+ """Hexatess Code - an experimental 2D barcode on a hexagonal grid.
2
+
3
+ Reference implementation of specification v0.1. The symbol is a
4
+ hexagonal lattice with a hexagonal bullseye finder, an
5
+ orientation key ring, spiral serialization from the centre outwards,
6
+ a Reed-Solomon protected header and a continuously selectable
7
+ error-correction budget of 5-90 percent (Aztec-style).
8
+
9
+ Quick start
10
+ -----------
11
+ >>> from hexatess import encode, decode, render
12
+ >>> grid, params = encode("Hello, Hexatess!", ec_pct=30)
13
+ >>> params["rmax"], params["mask"], params["data_len"]
14
+ (11, 6, 16)
15
+ >>> render(grid, "hello.png")
16
+ 'hello.png'
17
+ >>> decode(grid)[0]
18
+ 'Hello, Hexatess!'
19
+
20
+ See SPECIFICATION.md in the repository for the full format
21
+ specification, and test_vectors/vectors_v0.1.json for conformance
22
+ data usable by independent implementations.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from .decoder import decode
28
+ from .encoder import encode
29
+ from .geometry import (
30
+ DIRS,
31
+ hex_corner,
32
+ hex_distance,
33
+ hex_ring,
34
+ hex_to_pixel,
35
+ ring_capacity,
36
+ )
37
+ from .header import (
38
+ BULLSEYE_RINGS,
39
+ BLOCK_DATA_MAX,
40
+ DATA_RING0,
41
+ KEY_RING,
42
+ MAX_DATA_BYTES,
43
+ MAX_EC_PCT,
44
+ MAX_RINGS,
45
+ MIN_EC_PCT,
46
+ MODE_BITS,
47
+ MODE_BYTES,
48
+ MODE_ECC,
49
+ bits_to_bytes,
50
+ bytes_to_bits,
51
+ pack_mode,
52
+ plan_blocks,
53
+ unpack_mode,
54
+ )
55
+ from .masks import evaluate_mask, mask_bit, mask_payload, select_mask
56
+ from .reedsolomon import rs_correct_msg, rs_encode_msg
57
+ from .render import render, sample_grid_from_image
58
+ from .resilience import add_blob_damage, add_random_noise, run_tests
59
+
60
+ __version__ = "0.1.0"
61
+ SPEC_VERSION = "0.1"
62
+
63
+ __all__ = [
64
+ # high-level API
65
+ "encode", "decode", "render", "sample_grid_from_image",
66
+ "run_tests", "add_random_noise", "add_blob_damage",
67
+ # geometry
68
+ "DIRS", "hex_ring", "hex_distance", "hex_to_pixel", "hex_corner",
69
+ "ring_capacity",
70
+ # framing
71
+ "bytes_to_bits", "bits_to_bytes", "pack_mode", "unpack_mode",
72
+ "plan_blocks",
73
+ # masks
74
+ "mask_bit", "mask_payload", "evaluate_mask", "select_mask",
75
+ # error correction
76
+ "rs_encode_msg", "rs_correct_msg",
77
+ # constants
78
+ "BULLSEYE_RINGS", "KEY_RING", "DATA_RING0", "MAX_RINGS",
79
+ "BLOCK_DATA_MAX", "MODE_BYTES", "MODE_ECC", "MODE_BITS",
80
+ "MIN_EC_PCT", "MAX_EC_PCT", "MAX_DATA_BYTES",
81
+ # meta
82
+ "__version__", "SPEC_VERSION",
83
+ ]
@@ -0,0 +1,65 @@
1
+ """Command-line interface for Hexatess Code.
2
+
3
+ Examples
4
+ --------
5
+ Encode text to PNG::
6
+
7
+ hexatess-code "Hello world" -o koda.png --ec 30
8
+
9
+ Render a demo symbol and run robustness tests::
10
+
11
+ hexatess-code --demo
12
+ hexatess-code --test
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+
19
+ from .decoder import decode
20
+ from .encoder import encode
21
+ from .render import render, sample_grid_from_image
22
+
23
+
24
+ def main(argv=None):
25
+ ap = argparse.ArgumentParser(
26
+ prog="hexatess",
27
+ description="Hexatess Code - hexagonal-grid 2D barcode "
28
+ "(reference implementation, spec v0.1)")
29
+ ap.add_argument("text", nargs="?", help="text to encode (UTF-8)")
30
+ ap.add_argument("-o", "--output", default="hexatess.png",
31
+ help="output PNG path (default: %(default)s)")
32
+ ap.add_argument("--ec", type=int, default=30,
33
+ help="error-correction %% (5-90, step 5; default 30)")
34
+ ap.add_argument("--mask", type=int, default=None, metavar="0-7",
35
+ help="force a mask instead of automatic selection")
36
+ ap.add_argument("--size", type=int, default=18,
37
+ help="hex radius in pixels (default %(default)s)")
38
+ ap.add_argument("--test", action="store_true",
39
+ help="run robustness tests")
40
+ ap.add_argument("--demo", action="store_true",
41
+ help="render a demo symbol and run robustness tests")
42
+ args = ap.parse_args(argv)
43
+
44
+ if args.test or args.demo:
45
+ from .resilience import run_tests
46
+ run_tests()
47
+
48
+ if args.demo or args.text:
49
+ text = args.text or "Hexatess Code v0.1 - " * 6
50
+ grid, params = encode(text, ec_pct=args.ec, mask_id="auto"
51
+ if args.mask is None else args.mask)
52
+ render(grid, args.output, size_px=args.size)
53
+ print("\nRendered: %s" % args.output)
54
+ print(" version (rings): %d, EC: %d%%, mask: %d, bytes: %d"
55
+ % (params["rmax"], params["ec"], params["mask"],
56
+ params["data_len"]))
57
+ grid2 = sample_grid_from_image(args.output, params["rmax"],
58
+ size_px=args.size)
59
+ t, _st = decode(grid2)
60
+ print(" self-decode from image: %s"
61
+ % ("OK" if t == text else "FAILED"))
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,75 @@
1
+ """Hexatess Code decoder (ideal-sampling reference).
2
+
3
+ Accepts a module grid ``{(q, r): 0|1}`` (as produced by the encoder or
4
+ sampled from an image) and returns the decoded UTF-8 text together with
5
+ the header parameters. Any symbol error correctable by Reed-Solomon is
6
+ absorbed transparently.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .geometry import hex_distance, hex_ring
12
+ from .header import (
13
+ DATA_RING0,
14
+ MODE_BITS,
15
+ bits_to_bytes,
16
+ plan_blocks,
17
+ unpack_mode,
18
+ )
19
+ from .masks import mask_bit
20
+ from .reedsolomon import rs_correct_msg
21
+
22
+
23
+ def decode(grid, params=None):
24
+ """Decode a module grid into text.
25
+
26
+ Parameters
27
+ ----------
28
+ grid : dict
29
+ Mapping ``{(q, r): 0|1}``.
30
+ params : dict, optional
31
+ Ignored; kept for API symmetry with v0.1 of the prototype.
32
+
33
+ Returns
34
+ -------
35
+ (text, stats)
36
+ ``text`` is the decoded UTF-8 string; ``stats`` reports the
37
+ parameters read from the header (``rmax``, ``mask``, ``ec``,
38
+ ``blocks``, ``data_len``).
39
+ """
40
+ cells = sorted(grid.keys(), key=lambda c: hex_distance(*c))
41
+ rmax = max(hex_distance(*c) for c in cells)
42
+ data_cells = [c for k in range(DATA_RING0, rmax + 1)
43
+ for c in hex_ring(k)]
44
+ bits = [grid[c] for c in data_cells]
45
+
46
+ # --- protected header
47
+ mode_bits = bits[:MODE_BITS]
48
+ mode = unpack_mode(bits_to_bytes(mode_bits))
49
+ rmax_m, mask_id, ec_pct, block_count, data_len = mode
50
+
51
+ # --- masked payload
52
+ payload_bits = bits[MODE_BITS:MODE_BITS + (data_len + sum(
53
+ e for _, e in plan_blocks(data_len, ec_pct))) * 8]
54
+ payload_bits = [b ^ mask_bit(i, mask_id)
55
+ for i, b in enumerate(payload_bits)]
56
+ stream = bits_to_bytes(payload_bits)
57
+
58
+ # --- per-block Reed-Solomon correction
59
+ blocks = plan_blocks(data_len, ec_pct)
60
+ out = bytearray()
61
+ pos_d = 0
62
+ pos_e = 0
63
+ ecc_total = sum(e for _, e in blocks)
64
+ data_bytes = stream[:data_len]
65
+ ecc_bytes = stream[data_len:data_len + ecc_total]
66
+ for size, ecc in blocks:
67
+ cw = list(data_bytes[pos_d:pos_d + size]) + \
68
+ list(ecc_bytes[pos_e:pos_e + ecc])
69
+ out += bytes(rs_correct_msg(cw, ecc))
70
+ pos_d += size
71
+ pos_e += ecc
72
+ text = bytes(out).decode("utf-8")
73
+ stats = {"rmax": rmax_m, "mask": mask_id, "ec": ec_pct,
74
+ "blocks": block_count, "data_len": data_len}
75
+ return text, stats