diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..91abb11 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index c00e7dd..472aafc 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -24,15 +24,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - pip install . + pip install .[dev] if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 + - name: Lint run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + make lint - name: Test with pytest run: | python -m pytest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..40429cb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing to rmqrcode-python +Thank you for interesting in contributing to rmqrcode-python! Any suggestions are welcome. + +## Style Guides + +### Docstrings +This project uses [Google-Style format](https://google.github.io/styleguide/pyguide.html#381-docstrings) docstrings. + +### Git Commit Message +Consider starting commit message with one of the following prefixes. +- `feat:` : New feature +- `fix:` : Bug fix +- `refactor:` : Refactoring +- `chore:` : Little things +- `ci`: CI +- `doc:` : Documentation + +## Pull Requests +Before make a pull request, please do the following. +1. `make format` +2. `make lint` +3. `python -m pytest` and make sure all tests are passed. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..64421c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: lint +lint: + flake8 src + isort --check --diff src + black --check src + +.PHONY: format +format: + isort src + black src diff --git a/README.md b/README.md index 6c054d1..6602109 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,21 @@ ![reop-url](https://user-images.githubusercontent.com/14174940/172978619-accbf9d0-9dd8-4b19-b47e-ad139a68dcc9.png) -This is an rMQR Code image generator implemented in Python. This is implemented based on [ISO/IEC 23941:2022](https://www.iso.org/standard/77404.html). +The rMQR Code is a rectangular two-dimensional barcode. This package can generate an rMQR Code image. This is implemented based on [ISO/IEC 23941: Rectangular Micro QR Code (rMQR) bar code symbology specification](https://www.iso.org/standard/77404.html). +[![pytest](https://github.com/OUDON/rmqrcode-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/OUDON/rmqrcode-python/actions/workflows/python-app.yml) +![PyPI](https://img.shields.io/pypi/v/rmqrcode?color=blue) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/rmqrcode) +![PyPI - Downloads](https://img.shields.io/pypi/dm/rmqrcode?color=orange) -## 📌 Important Notice +## 🎮 Online Demo Site +You can try this online: https://rmqr.oudon.xyz . + +## 📌 Notice - Please verify an image generated by this software whether it can decode correctly before use. - Because this is in early stage, QR Code readers may have not been supported rMQR Code yet. + ## 🚀 Installation ``` pip install rmqrcode @@ -64,7 +72,7 @@ The `fit_strategy` parameter is enum value of `rmqrcode.FitStrategy` to specify - **`FitStrategy.BALANCED`**: Try to keep balance of width and height. Here is an example of images genereated by each fit strategies for data `Test test test`: -![Example of fit strategies](https://user-images.githubusercontent.com/14174940/172822478-4f2b5fb8-49bd-464f-b6b2-c7347f71cbf5.png) +![Example of fit strategies](https://user-images.githubusercontent.com/14174940/175759120-7fb5ec71-c258-4646-9b91-6865b3eeac3f.png) ### Save as image ```py @@ -76,7 +84,7 @@ image.save("my_qr.png") ``` -## 📚 Advanced Usage +## 📙 Advanced Usage ### Select rMQR Code size manually To select rMQR Code size manually, use `rMQR()` constructor. ```py @@ -108,6 +116,17 @@ The rMQR Code has the four encoding modes Numeric, Alphanumeric, Byte and Kanji |Byte|✅| |Kanji|| + +## 🤝 Contiributing +Any suggestions are welcome! If you are interesting in contiributing, please read [CONTRIBUTING](https://github.com/OUDON/rmqrcode-python/blob/develop/CONTRIBUTING.md). + + +## 📚 References +- [Rectangular Micro QR Code (rMQR) bar code symbology specification: ISO/IEC 23941](https://www.iso.org/standard/77404.html) +- [rMQR Code | QRcode.com | DENSO WAVE](https://www.qrcode.com/en/codes/rmqr.html) +- [Creating a QR Code step by step](https://www.nayuki.io/page/creating-a-qr-code-step-by-step) + + ---- The word "QR Code" is registered trademark of DENSO WAVE Incorporated.
http://www.denso-wave.com/qrcode/faqpatent-e.html diff --git a/example.py b/example.py index 7e11b6c..16f7eb2 100644 --- a/example.py +++ b/example.py @@ -6,6 +6,14 @@ import logging +try: + import numpy + import cv2 + USE_NUMPY = True +except ImportError: + USE_NUMPY = False + + def main(): data = "https://oudon.xyz" error_correction_level = ErrorCorrectionLevel.M @@ -26,6 +34,13 @@ def main(): image.show() image.save("my_qr.png") + # Convert to numpy array + if USE_NUMPY: + img = image.get_ndarray() + cv2.imshow("img", img) + cv2.waitKey(0) + cv2.destroyAllWindows() + def _init_logger(): logger = logging.getLogger() diff --git a/pyproject.toml b/pyproject.toml index fa7093a..8969d2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ [build-system] requires = ["setuptools>=42"] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 119 +exclude = "generator_polynomials.py" diff --git a/setup.cfg b/setup.cfg index ada13c1..cf930d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = rmqrcode -version = 0.1.3 +version = 0.2.0 author = Takahiro Tomita author_email = ttp8101@gmail.com description = An rMQR Code Generetor @@ -23,10 +23,21 @@ install_requires = [options.extras_require] dev = pytest + flake8 + isort + black [options.packages.find] where = src [options.entry_points] console_scripts = - rmqr = rmqrcode.console:main \ No newline at end of file + rmqr = rmqrcode.console:main + +[flake8] +max-line-length = 119 +extend-ignore = E203 +exclude = src/rmqrcode/format/generator_polynomials.py + +[isort] +profile=black diff --git a/src/rmqrcode/__init__.py b/src/rmqrcode/__init__.py index 2b40cee..aca9a2f 100644 --- a/src/rmqrcode/__init__.py +++ b/src/rmqrcode/__init__.py @@ -1,6 +1,5 @@ -from .rmqrcode import rMQR -from .rmqrcode import FitStrategy -from .rmqrcode import DataTooLongError -from .rmqrcode import IllegalVersionError +from .format.error_correction_level import ErrorCorrectionLevel from .qr_image import QRImage -from .format.error_correction_level import ErrorCorrectionLevel \ No newline at end of file +from .rmqrcode import DataTooLongError, FitStrategy, IllegalVersionError, rMQR + +__all__ = ("rMQR", "DataTooLongError", "FitStrategy", "IllegalVersionError", "QRImage", "ErrorCorrectionLevel") diff --git a/src/rmqrcode/console.py b/src/rmqrcode/console.py index aa1117e..5220c64 100644 --- a/src/rmqrcode/console.py +++ b/src/rmqrcode/console.py @@ -1,15 +1,16 @@ #!/usr/bin/env python -import rmqrcode -from rmqrcode import rMQR -from rmqrcode import QRImage -from rmqrcode import ErrorCorrectionLevel -from rmqrcode import FitStrategy -from rmqrcode import DataTooLongError -from rmqrcode import IllegalVersionError - import argparse import sys +from rmqrcode import ( + DataTooLongError, + ErrorCorrectionLevel, + FitStrategy, + IllegalVersionError, + QRImage, + rMQR, +) + def _show_error_and_exit(msg): print(msg, file=sys.stderr) @@ -17,7 +18,7 @@ def _show_error_and_exit(msg): def _make_qr(data, ecc, version, fit_strategy): - if version == None: + if version is None: qr = rMQR.fit(data, ecc=ecc, fit_strategy=fit_strategy) else: try: @@ -29,7 +30,6 @@ def _make_qr(data, ecc, version, fit_strategy): return qr - def _save_image(qr, output): image = QRImage(qr) try: @@ -42,24 +42,19 @@ def main(): parser = _init_argparser() args = parser.parse_args() - if args.ecc == 'M': + if args.ecc == "M": ecc = ErrorCorrectionLevel.M - elif args.ecc == 'H': + elif args.ecc == "H": ecc = ErrorCorrectionLevel.H fit_strategy = FitStrategy.BALANCED - if args.fit_strategy == 'min_width': + if args.fit_strategy == "min_width": fit_strategy = FitStrategy.MINIMIZE_WIDTH - elif args.fit_strategy == 'min_height': + elif args.fit_strategy == "min_height": fit_strategy = FitStrategy.MINIMIZE_HEIGHT try: - qr = _make_qr( - args.DATA, - ecc=ecc, - version=args.version, - fit_strategy=fit_strategy - ) + qr = _make_qr(args.DATA, ecc=ecc, version=args.version, fit_strategy=fit_strategy) except DataTooLongError: _show_error_and_exit("Error: The data is too long.") @@ -68,13 +63,20 @@ def main(): def _init_argparser(): parser = argparse.ArgumentParser() - parser.add_argument('DATA', type=str, help="Data to encode.") - parser.add_argument('OUTPUT', type=str, help="Output file path") - parser.add_argument('--ecc', help="Error correction level. (default: M)", type=str, choices=["M", "H"], default='M') - parser.add_argument('--version', help="rMQR Code version like 'R11x139'.") - parser.add_argument('--fit-strategy', choices=["min_width", "min_height", "balanced"], help="Strategy how to determine rMQR Code size.", dest="fit_strategy") + parser.add_argument("DATA", type=str, help="Data to encode.") + parser.add_argument("OUTPUT", type=str, help="Output file path") + parser.add_argument( + "--ecc", help="Error correction level. (default: M)", type=str, choices=["M", "H"], default="M" + ) + parser.add_argument("--version", help="rMQR Code version like 'R11x139'.") + parser.add_argument( + "--fit-strategy", + choices=["min_width", "min_height", "balanced"], + help="Strategy how to determine rMQR Code size.", + dest="fit_strategy", + ) return parser if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/rmqrcode/encoder/byte_encoder.py b/src/rmqrcode/encoder/byte_encoder.py index 38b7c22..ed6bbdf 100644 --- a/src/rmqrcode/encoder/byte_encoder.py +++ b/src/rmqrcode/encoder/byte_encoder.py @@ -4,12 +4,11 @@ class ByteEncoder: @staticmethod def _encoded_bits(s): res = "" - encoded = s.encode('utf-8') + encoded = s.encode("utf-8") for byte in encoded: res += bin(byte)[2:].zfill(8) return res - @staticmethod def encode(data, character_count_length): res = ByteEncoder.MODE_INDICATOR @@ -17,7 +16,6 @@ def encode(data, character_count_length): res += ByteEncoder._encoded_bits(data) return res - @staticmethod def length(data): - return len(data.encode('utf-8')) + return len(data.encode("utf-8")) diff --git a/src/rmqrcode/enums/color.py b/src/rmqrcode/enums/color.py index 7aeb18d..324839b 100644 --- a/src/rmqrcode/enums/color.py +++ b/src/rmqrcode/enums/color.py @@ -1,6 +1,7 @@ from enum import Enum + class Color(Enum): UNDEFINED = -1 WHITE = 0 - BLACK = 1 \ No newline at end of file + BLACK = 1 diff --git a/src/rmqrcode/enums/fit_strategy.py b/src/rmqrcode/enums/fit_strategy.py index bd006e9..80588b3 100644 --- a/src/rmqrcode/enums/fit_strategy.py +++ b/src/rmqrcode/enums/fit_strategy.py @@ -1,6 +1,7 @@ from enum import Enum + class FitStrategy(Enum): MINIMIZE_WIDTH = 0 MINIMIZE_HEIGHT = 1 - BALANCED = 2 \ No newline at end of file + BALANCED = 2 diff --git a/src/rmqrcode/format/alignment_pattern_coordinates.py b/src/rmqrcode/format/alignment_pattern_coordinates.py index 249698b..7b95c3f 100644 --- a/src/rmqrcode/format/alignment_pattern_coordinates.py +++ b/src/rmqrcode/format/alignment_pattern_coordinates.py @@ -5,4 +5,4 @@ 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111], -} \ No newline at end of file +} diff --git a/src/rmqrcode/format/data_capacities.py b/src/rmqrcode/format/data_capacities.py index 58b3739..304f067 100644 --- a/src/rmqrcode/format/data_capacities.py +++ b/src/rmqrcode/format/data_capacities.py @@ -1,326 +1,325 @@ from .error_correction_level import ErrorCorrectionLevel - # ISO/IEC 23941:2022 Table 6 DataCapacities = { - 'R7x43': { - 'height': 7, - 'width': 43, - 'capacity': { - 'Byte': { + "R7x43": { + "height": 7, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 5, ErrorCorrectionLevel.H: 2, }, }, }, - 'R7x59': { - 'height': 7, - 'width': 59, - 'capacity': { - 'Byte': { + "R7x59": { + "height": 7, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 11, ErrorCorrectionLevel.H: 6, }, }, }, - 'R7x77': { - 'height': 7, - 'width': 77, - 'capacity': { - 'Byte': { + "R7x77": { + "height": 7, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 19, ErrorCorrectionLevel.H: 9, }, }, }, - 'R7x99': { - 'height': 7, - 'width': 99, - 'capacity': { - 'Byte': { + "R7x99": { + "height": 7, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 27, ErrorCorrectionLevel.H: 13, }, }, }, - 'R7x139': { - 'height': 7, - 'width': 139, - 'capacity': { - 'Byte': { + "R7x139": { + "height": 7, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 42, ErrorCorrectionLevel.H: 22, }, }, }, - 'R9x43': { - 'height': 9, - 'width': 43, - 'capacity': { - 'Byte': { + "R9x43": { + "height": 9, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 11, ErrorCorrectionLevel.H: 6, }, }, }, - 'R9x59': { - 'height': 9, - 'width': 59, - 'capacity': { - 'Byte': { + "R9x59": { + "height": 9, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 20, ErrorCorrectionLevel.H: 10, }, }, }, - 'R9x77': { - 'height': 9, - 'width': 77, - 'capacity': { - 'Byte': { + "R9x77": { + "height": 9, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 30, ErrorCorrectionLevel.H: 16, }, }, }, - 'R9x99': { - 'height': 9, - 'width': 99, - 'capacity': { - 'Byte': { + "R9x99": { + "height": 9, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 40, ErrorCorrectionLevel.H: 20, }, }, }, - 'R9x139': { - 'height': 9, - 'width': 139, - 'capacity': { - 'Byte': { + "R9x139": { + "height": 9, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 61, ErrorCorrectionLevel.H: 31, }, }, }, - 'R11x27': { - 'height': 11, - 'width': 27, - 'capacity': { - 'Byte': { + "R11x27": { + "height": 11, + "width": 27, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 6, ErrorCorrectionLevel.H: 4, }, }, }, - 'R11x43': { - 'height': 11, - 'width': 43, - 'capacity': { - 'Byte': { + "R11x43": { + "height": 11, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 18, ErrorCorrectionLevel.H: 10, }, }, }, - 'R11x59': { - 'height': 11, - 'width': 59, - 'capacity': { - 'Byte': { + "R11x59": { + "height": 11, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 30, ErrorCorrectionLevel.H: 14, }, }, }, - 'R11x77': { - 'height': 11, - 'width': 77, - 'capacity': { - 'Byte': { + "R11x77": { + "height": 11, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 41, ErrorCorrectionLevel.H: 21, }, }, }, - 'R11x99': { - 'height': 11, - 'width': 99, - 'capacity': { - 'Byte': { + "R11x99": { + "height": 11, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 55, ErrorCorrectionLevel.H: 27, }, }, }, - 'R11x139': { - 'height': 11, - 'width': 139, - 'capacity': { - 'Byte': { + "R11x139": { + "height": 11, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 82, ErrorCorrectionLevel.H: 40, }, }, }, - 'R13x27': { - 'height': 13, - 'width': 27, - 'capacity': { - 'Byte': { + "R13x27": { + "height": 13, + "width": 27, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 11, ErrorCorrectionLevel.H: 6, }, }, }, - 'R13x43': { - 'height': 13, - 'width': 43, - 'capacity': { - 'Byte': { + "R13x43": { + "height": 13, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 26, ErrorCorrectionLevel.H: 12, }, }, }, - 'R13x59': { - 'height': 13, - 'width': 59, - 'capacity': { - 'Byte': { + "R13x59": { + "height": 13, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 36, ErrorCorrectionLevel.H: 18, }, }, }, - 'R13x77': { - 'height': 13, - 'width': 77, - 'capacity': { - 'Byte': { + "R13x77": { + "height": 13, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 51, ErrorCorrectionLevel.H: 27, }, }, }, - 'R13x99': { - 'height': 13, - 'width': 99, - 'capacity': { - 'Byte': { + "R13x99": { + "height": 13, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 71, ErrorCorrectionLevel.H: 33, }, }, }, - 'R13x139': { - 'height': 13, - 'width': 139, - 'capacity': { - 'Byte': { + "R13x139": { + "height": 13, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 104, ErrorCorrectionLevel.H: 52, }, }, }, - 'R15x43': { - 'height': 15, - 'width': 43, - 'capacity': { - 'Byte': { + "R15x43": { + "height": 15, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 31, ErrorCorrectionLevel.H: 13, }, }, }, - 'R15x59': { - 'height': 15, - 'width': 59, - 'capacity': { - 'Byte': { + "R15x59": { + "height": 15, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 46, ErrorCorrectionLevel.H: 24, }, }, }, - 'R15x77': { - 'height': 15, - 'width': 77, - 'capacity': { - 'Byte': { + "R15x77": { + "height": 15, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 65, ErrorCorrectionLevel.H: 29, }, }, }, - 'R15x99': { - 'height': 15, - 'width': 99, - 'capacity': { - 'Byte': { + "R15x99": { + "height": 15, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 86, ErrorCorrectionLevel.H: 46, }, }, }, - 'R15x139': { - 'height': 15, - 'width': 139, - 'capacity': { - 'Byte': { + "R15x139": { + "height": 15, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 125, ErrorCorrectionLevel.H: 67, }, }, }, - 'R17x43': { - 'height': 17, - 'width': 43, - 'capacity': { - 'Byte': { + "R17x43": { + "height": 17, + "width": 43, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 37, ErrorCorrectionLevel.H: 19, }, }, }, - 'R17x59': { - 'height': 17, - 'width': 59, - 'capacity': { - 'Byte': { + "R17x59": { + "height": 17, + "width": 59, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 54, ErrorCorrectionLevel.H: 26, }, }, }, - 'R17x77': { - 'height': 17, - 'width': 77, - 'capacity': { - 'Byte': { + "R17x77": { + "height": 17, + "width": 77, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 76, ErrorCorrectionLevel.H: 36, }, }, }, - 'R17x99': { - 'height': 17, - 'width': 99, - 'capacity': { - 'Byte': { + "R17x99": { + "height": 17, + "width": 99, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 98, ErrorCorrectionLevel.H: 54, }, }, }, - 'R17x139': { - 'height': 17, - 'width': 139, - 'capacity': { - 'Byte': { + "R17x139": { + "height": 17, + "width": 139, + "capacity": { + "Byte": { ErrorCorrectionLevel.M: 150, ErrorCorrectionLevel.H: 74, }, }, }, -} \ No newline at end of file +} diff --git a/src/rmqrcode/format/error_correction_level.py b/src/rmqrcode/format/error_correction_level.py index 7483698..84e18e3 100644 --- a/src/rmqrcode/format/error_correction_level.py +++ b/src/rmqrcode/format/error_correction_level.py @@ -1,5 +1,6 @@ from enum import Enum + class ErrorCorrectionLevel(Enum): M = 0 - H = 1 \ No newline at end of file + H = 1 diff --git a/src/rmqrcode/format/mask.py b/src/rmqrcode/format/mask.py index 4822236..4ce92b7 100644 --- a/src/rmqrcode/format/mask.py +++ b/src/rmqrcode/format/mask.py @@ -1,2 +1,2 @@ def mask(x, y): - return (y//2 + x//3) % 2 == 0 + return (y // 2 + x // 3) % 2 == 0 diff --git a/src/rmqrcode/format/rmqr_versions.py b/src/rmqrcode/format/rmqr_versions.py index 2bc9232..6861772 100644 --- a/src/rmqrcode/format/rmqr_versions.py +++ b/src/rmqrcode/format/rmqr_versions.py @@ -1,878 +1,877 @@ from .error_correction_level import ErrorCorrectionLevel - rMQRVersions = { - 'R7x43': { - 'version_indicator': 0b00000, - 'height': 7, - 'width': 43, - 'remainder_bits': 0, - 'character_count_length': 3, - 'codewords_total': 13, - 'blocks': { + "R7x43": { + "version_indicator": 0b00000, + "height": 7, + "width": 43, + "remainder_bits": 0, + "character_count_length": 3, + "codewords_total": 13, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 13, - 'k': 6, + "num": 1, + "c": 13, + "k": 6, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 13, - 'k': 3, + "num": 1, + "c": 13, + "k": 3, }, - ] - } + ], + }, }, - 'R7x59': { - 'version_indicator': 0b00001, - 'height': 7, - 'width': 59, - 'remainder_bits': 3, - 'character_count_length': 4, - 'codewords_total': 21, - 'blocks': { + "R7x59": { + "version_indicator": 0b00001, + "height": 7, + "width": 59, + "remainder_bits": 3, + "character_count_length": 4, + "codewords_total": 21, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 21, - 'k': 12, + "num": 1, + "c": 21, + "k": 12, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 21, - 'k': 7, + "num": 1, + "c": 21, + "k": 7, }, - ] - } + ], + }, }, - 'R7x77': { - 'version_indicator': 0b00010, - 'height': 7, - 'width': 77, - 'remainder_bits': 5, - 'character_count_length': 5, - 'codewords_total': 32, - 'blocks': { + "R7x77": { + "version_indicator": 0b00010, + "height": 7, + "width": 77, + "remainder_bits": 5, + "character_count_length": 5, + "codewords_total": 32, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 32, - 'k': 20, + "num": 1, + "c": 32, + "k": 20, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 32, - 'k': 10, + "num": 1, + "c": 32, + "k": 10, }, - ] - } + ], + }, }, - 'R7x99': { - 'version_indicator': 0b00011, - 'height': 7, - 'width': 99, - 'remainder_bits': 6, - 'character_count_length': 5, - 'codewords_total': 44, - 'blocks': { + "R7x99": { + "version_indicator": 0b00011, + "height": 7, + "width": 99, + "remainder_bits": 6, + "character_count_length": 5, + "codewords_total": 44, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 44, - 'k': 28, + "num": 1, + "c": 44, + "k": 28, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 44, - 'k': 14, + "num": 1, + "c": 44, + "k": 14, }, - ] - } + ], + }, }, - 'R7x139': { - 'version_indicator': 0b00100, - 'height': 7, - 'width': 139, - 'remainder_bits': 1, - 'character_count_length': 6, - 'codewords_total': 68, - 'blocks': { + "R7x139": { + "version_indicator": 0b00100, + "height": 7, + "width": 139, + "remainder_bits": 1, + "character_count_length": 6, + "codewords_total": 68, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 68, - 'k': 44, + "num": 1, + "c": 68, + "k": 44, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 34, - 'k': 12, + "num": 2, + "c": 34, + "k": 12, }, - ] - } + ], + }, }, - 'R9x43': { - 'version_indicator': 0b00101, - 'height': 9, - 'width': 43, - 'remainder_bits': 2, - 'character_count_length': 4, - 'codewords_total': 21, - 'blocks': { + "R9x43": { + "version_indicator": 0b00101, + "height": 9, + "width": 43, + "remainder_bits": 2, + "character_count_length": 4, + "codewords_total": 21, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 21, - 'k': 12, + "num": 1, + "c": 21, + "k": 12, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 21, - 'k': 7, + "num": 1, + "c": 21, + "k": 7, }, - ] - } + ], + }, }, - 'R9x59': { - 'version_indicator': 0b00110, - 'height': 9, - 'width': 59, - 'remainder_bits': 3, - 'character_count_length': 5, - 'codewords_total': 33, - 'blocks': { + "R9x59": { + "version_indicator": 0b00110, + "height": 9, + "width": 59, + "remainder_bits": 3, + "character_count_length": 5, + "codewords_total": 33, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 33, - 'k': 21, + "num": 1, + "c": 33, + "k": 21, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 33, - 'k': 11, + "num": 1, + "c": 33, + "k": 11, }, - ] - } + ], + }, }, - 'R9x77': { - 'version_indicator': 0b00111, - 'height': 9, - 'width': 77, - 'remainder_bits': 1, - 'character_count_length': 5, - 'codewords_total': 49, - 'blocks': { + "R9x77": { + "version_indicator": 0b00111, + "height": 9, + "width": 77, + "remainder_bits": 1, + "character_count_length": 5, + "codewords_total": 49, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 49, - 'k': 31, + "num": 1, + "c": 49, + "k": 31, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 24, - 'k': 8, + "num": 1, + "c": 24, + "k": 8, }, { - 'num': 1, - 'c': 25, - 'k': 9, + "num": 1, + "c": 25, + "k": 9, }, - ] - } + ], + }, }, - 'R9x99': { - 'version_indicator': 0b01000, - 'height': 9, - 'width': 99, - 'remainder_bits': 4, - 'character_count_length': 6, - 'codewords_total': 66, - 'blocks': { + "R9x99": { + "version_indicator": 0b01000, + "height": 9, + "width": 99, + "remainder_bits": 4, + "character_count_length": 6, + "codewords_total": 66, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 66, - 'k': 42, + "num": 1, + "c": 66, + "k": 42, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 33, - 'k': 11, + "num": 2, + "c": 33, + "k": 11, }, - ] - } + ], + }, }, - 'R9x139': { - 'version_indicator': 0b01001, - 'height': 9, - 'width': 139, - 'remainder_bits': 5, - 'character_count_length': 6, - 'codewords_total': 99, - 'blocks': { + "R9x139": { + "version_indicator": 0b01001, + "height": 9, + "width": 139, + "remainder_bits": 5, + "character_count_length": 6, + "codewords_total": 99, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 49, - 'k': 31, + "num": 1, + "c": 49, + "k": 31, }, { - 'num': 1, - 'c': 50, - 'k': 32, + "num": 1, + "c": 50, + "k": 32, }, ], ErrorCorrectionLevel.H: [ { - 'num': 3, - 'c': 33, - 'k': 11, + "num": 3, + "c": 33, + "k": 11, } - ] - } + ], + }, }, - 'R11x27': { - 'version_indicator': 0b01010, - 'height': 11, - 'width': 27, - 'remainder_bits': 2, - 'character_count_length': 3, - 'codewords_total': 15, - 'blocks': { + "R11x27": { + "version_indicator": 0b01010, + "height": 11, + "width": 27, + "remainder_bits": 2, + "character_count_length": 3, + "codewords_total": 15, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 15, - 'k': 7, + "num": 1, + "c": 15, + "k": 7, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 15, - 'k': 5, + "num": 1, + "c": 15, + "k": 5, } - ] - } + ], + }, }, - 'R11x43': { - 'version_indicator': 0b01011, - 'height': 11, - 'width': 43, - 'remainder_bits': 1, - 'character_count_length': 5, - 'codewords_total': 31, - 'blocks': { + "R11x43": { + "version_indicator": 0b01011, + "height": 11, + "width": 43, + "remainder_bits": 1, + "character_count_length": 5, + "codewords_total": 31, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 31, - 'k': 19, + "num": 1, + "c": 31, + "k": 19, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 31, - 'k': 11, + "num": 1, + "c": 31, + "k": 11, } - ] - } + ], + }, }, - 'R11x59': { - 'version_indicator': 0b01100, - 'height': 11, - 'width': 59, - 'remainder_bits': 0, - 'character_count_length': 5, - 'codewords_total': 47, - 'blocks': { + "R11x59": { + "version_indicator": 0b01100, + "height": 11, + "width": 59, + "remainder_bits": 0, + "character_count_length": 5, + "codewords_total": 47, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 47, - 'k': 31, + "num": 1, + "c": 47, + "k": 31, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 23, - 'k': 7, + "num": 1, + "c": 23, + "k": 7, }, { - 'num': 1, - 'c': 24, - 'k': 8, + "num": 1, + "c": 24, + "k": 8, }, - ] - } + ], + }, }, - 'R11x77': { - 'version_indicator': 0b01101, - 'height': 11, - 'width': 77, - 'remainder_bits': 2, - 'character_count_length': 6, - 'codewords_total': 67, - 'blocks': { + "R11x77": { + "version_indicator": 0b01101, + "height": 11, + "width": 77, + "remainder_bits": 2, + "character_count_length": 6, + "codewords_total": 67, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 67, - 'k': 43, + "num": 1, + "c": 67, + "k": 43, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 33, - 'k': 11, + "num": 1, + "c": 33, + "k": 11, }, { - 'num': 1, - 'c': 34, - 'k': 12, + "num": 1, + "c": 34, + "k": 12, }, - ] - } + ], + }, }, - 'R11x99': { - 'version_indicator': 0b01110, - 'height': 11, - 'width': 99, - 'remainder_bits': 7, - 'character_count_length': 6, - 'codewords_total': 89, - 'blocks': { + "R11x99": { + "version_indicator": 0b01110, + "height": 11, + "width": 99, + "remainder_bits": 7, + "character_count_length": 6, + "codewords_total": 89, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 44, - 'k': 28, + "num": 1, + "c": 44, + "k": 28, }, { - 'num': 1, - 'c': 45, - 'k': 29, + "num": 1, + "c": 45, + "k": 29, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 44, - 'k': 14, + "num": 1, + "c": 44, + "k": 14, }, { - 'num': 1, - 'c': 45, - 'k': 15, + "num": 1, + "c": 45, + "k": 15, }, - ] - } + ], + }, }, - 'R11x139': { - 'version_indicator': 0b01111, - 'height': 11, - 'width': 139, - 'remainder_bits': 6, - 'character_count_length': 7, - 'codewords_total': 132, - 'blocks': { + "R11x139": { + "version_indicator": 0b01111, + "height": 11, + "width": 139, + "remainder_bits": 6, + "character_count_length": 7, + "codewords_total": 132, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 66, - 'k': 42, + "num": 2, + "c": 66, + "k": 42, }, ], ErrorCorrectionLevel.H: [ { - 'num': 3, - 'c': 44, - 'k': 14, + "num": 3, + "c": 44, + "k": 14, } - ] - } + ], + }, }, - 'R13x27': { - 'version_indicator': 0b10000, - 'height': 13, - 'width': 27, - 'character_count_length': 4, - 'remainder_bits': 4, - 'codewords_total': 21, - 'blocks': { + "R13x27": { + "version_indicator": 0b10000, + "height": 13, + "width": 27, + "character_count_length": 4, + "remainder_bits": 4, + "codewords_total": 21, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 21, - 'k': 14, + "num": 1, + "c": 21, + "k": 14, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 21, - 'k': 7, + "num": 1, + "c": 21, + "k": 7, } - ] - } + ], + }, }, - 'R13x43': { - 'version_indicator': 0b10001, - 'height': 13, - 'width': 43, - 'remainder_bits': 1, - 'character_count_length': 5, - 'codewords_total': 41, - 'blocks': { + "R13x43": { + "version_indicator": 0b10001, + "height": 13, + "width": 43, + "remainder_bits": 1, + "character_count_length": 5, + "codewords_total": 41, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 41, - 'k': 27, + "num": 1, + "c": 41, + "k": 27, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 41, - 'k': 13, + "num": 1, + "c": 41, + "k": 13, } - ] - } + ], + }, }, - 'R13x59': { - 'version_indicator': 0b10010, - 'height': 13, - 'width': 59, - 'remainder_bits': 6, - 'character_count_length': 6, - 'codewords_total': 60, - 'blocks': { + "R13x59": { + "version_indicator": 0b10010, + "height": 13, + "width": 59, + "remainder_bits": 6, + "character_count_length": 6, + "codewords_total": 60, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 60, - 'k': 38, + "num": 1, + "c": 60, + "k": 38, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 30, - 'k': 10, + "num": 2, + "c": 30, + "k": 10, } - ] - } + ], + }, }, - 'R13x77': { - 'version_indicator': 0b10011, - 'height': 13, - 'width': 77, - 'remainder_bits': 4, - 'character_count_length': 6, - 'codewords_total': 85, - 'blocks': { + "R13x77": { + "version_indicator": 0b10011, + "height": 13, + "width": 77, + "remainder_bits": 4, + "character_count_length": 6, + "codewords_total": 85, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 42, - 'k': 26, + "num": 1, + "c": 42, + "k": 26, }, { - 'num': 1, - 'c': 43, - 'k': 27, + "num": 1, + "c": 43, + "k": 27, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 42, - 'k': 14, + "num": 1, + "c": 42, + "k": 14, }, { - 'num': 1, - 'c': 43, - 'k': 15, + "num": 1, + "c": 43, + "k": 15, }, - ] - } + ], + }, }, - 'R13x99': { - 'version_indicator': 0b10100, - 'height': 13, - 'width': 99, - 'remainder_bits': 3, - 'character_count_length': 7, - 'codewords_total': 113, - 'blocks': { + "R13x99": { + "version_indicator": 0b10100, + "height": 13, + "width": 99, + "remainder_bits": 3, + "character_count_length": 7, + "codewords_total": 113, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 56, - 'k': 36, + "num": 1, + "c": 56, + "k": 36, }, { - 'num': 1, - 'c': 57, - 'k': 37, + "num": 1, + "c": 57, + "k": 37, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 37, - 'k': 11, + "num": 1, + "c": 37, + "k": 11, }, { - 'num': 2, - 'c': 38, - 'k': 12, + "num": 2, + "c": 38, + "k": 12, }, - ] - } + ], + }, }, - 'R13x139': { - 'version_indicator': 0b10101, - 'height': 13, - 'width': 139, - 'remainder_bits': 0, - 'character_count_length': 7, - 'codewords_total': 166, - 'blocks': { + "R13x139": { + "version_indicator": 0b10101, + "height": 13, + "width": 139, + "remainder_bits": 0, + "character_count_length": 7, + "codewords_total": 166, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 55, - 'k': 35, + "num": 2, + "c": 55, + "k": 35, }, { - 'num': 1, - 'c': 56, - 'k': 36, + "num": 1, + "c": 56, + "k": 36, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 41, - 'k': 13, + "num": 2, + "c": 41, + "k": 13, }, { - 'num': 2, - 'c': 42, - 'k': 14, + "num": 2, + "c": 42, + "k": 14, }, - ] - } + ], + }, }, - 'R15x43': { - 'version_indicator': 0b10110, - 'height': 15, - 'width': 43, - 'remainder_bits': 1, - 'character_count_length': 6, - 'codewords_total': 51, - 'blocks': { + "R15x43": { + "version_indicator": 0b10110, + "height": 15, + "width": 43, + "remainder_bits": 1, + "character_count_length": 6, + "codewords_total": 51, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 51, - 'k': 33, + "num": 1, + "c": 51, + "k": 33, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 25, - 'k': 7, + "num": 1, + "c": 25, + "k": 7, }, { - 'num': 1, - 'c': 26, - 'k': 8, + "num": 1, + "c": 26, + "k": 8, }, - ] - } + ], + }, }, - 'R15x59': { - 'version_indicator': 0b10111, - 'height': 15, - 'width': 59, - 'remainder_bits': 4, - 'character_count_length': 6, - 'codewords_total': 74, - 'blocks': { + "R15x59": { + "version_indicator": 0b10111, + "height": 15, + "width": 59, + "remainder_bits": 4, + "character_count_length": 6, + "codewords_total": 74, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 74, - 'k': 48, + "num": 1, + "c": 74, + "k": 48, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 37, - 'k': 13, + "num": 2, + "c": 37, + "k": 13, } - ] - } + ], + }, }, - 'R15x77': { - 'version_indicator': 0b11000, - 'height': 15, - 'width': 77, - 'remainder_bits': 6, - 'character_count_length': 7, - 'codewords_total': 103, - 'blocks': { + "R15x77": { + "version_indicator": 0b11000, + "height": 15, + "width": 77, + "remainder_bits": 6, + "character_count_length": 7, + "codewords_total": 103, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 51, - 'k': 33, + "num": 1, + "c": 51, + "k": 33, }, { - 'num': 1, - 'c': 52, - 'k': 34, + "num": 1, + "c": 52, + "k": 34, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 34, - 'k': 10, + "num": 2, + "c": 34, + "k": 10, }, { - 'num': 1, - 'c': 35, - 'k': 11, + "num": 1, + "c": 35, + "k": 11, }, - ] - } + ], + }, }, - 'R15x99': { - 'version_indicator': 0b11001, - 'height': 15, - 'width': 99, - 'remainder_bits': 7, - 'character_count_length': 7, - 'codewords_total': 136, - 'blocks': { + "R15x99": { + "version_indicator": 0b11001, + "height": 15, + "width": 99, + "remainder_bits": 7, + "character_count_length": 7, + "codewords_total": 136, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 68, - 'k': 44, + "num": 2, + "c": 68, + "k": 44, }, ], ErrorCorrectionLevel.H: [ { - 'num': 4, - 'c': 34, - 'k': 12, + "num": 4, + "c": 34, + "k": 12, }, ], - } + }, }, - 'R15x139': { - 'version_indicator': 0b11010, - 'height': 15, - 'width': 139, - 'remainder_bits': 2, - 'character_count_length': 7, - 'codewords_total': 199, - 'blocks': { + "R15x139": { + "version_indicator": 0b11010, + "height": 15, + "width": 139, + "remainder_bits": 2, + "character_count_length": 7, + "codewords_total": 199, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 66, - 'k': 42, + "num": 2, + "c": 66, + "k": 42, }, { - 'num': 1, - 'c': 67, - 'k': 43, + "num": 1, + "c": 67, + "k": 43, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 39, - 'k': 13, + "num": 1, + "c": 39, + "k": 13, }, { - 'num': 4, - 'c': 40, - 'k': 14, + "num": 4, + "c": 40, + "k": 14, }, ], - } + }, }, - 'R17x43': { - 'version_indicator': 0b11011, - 'height': 17, - 'width': 43, - 'remainder_bits': 1, - 'character_count_length': 6, - 'codewords_total': 61, - 'blocks': { + "R17x43": { + "version_indicator": 0b11011, + "height": 17, + "width": 43, + "remainder_bits": 1, + "character_count_length": 6, + "codewords_total": 61, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 1, - 'c': 60, - 'k': 39, + "num": 1, + "c": 60, + "k": 39, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 30, - 'k': 10, + "num": 1, + "c": 30, + "k": 10, }, { - 'num': 1, - 'c': 31, - 'k': 11, + "num": 1, + "c": 31, + "k": 11, }, - ] - } + ], + }, }, - 'R17x59': { - 'version_indicator': 0b11100, - 'height': 17, - 'width': 59, - 'remainder_bits': 2, - 'character_count_length': 6, - 'codewords_total': 88, - 'blocks': { + "R17x59": { + "version_indicator": 0b11100, + "height": 17, + "width": 59, + "remainder_bits": 2, + "character_count_length": 6, + "codewords_total": 88, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 44, - 'k': 28, + "num": 2, + "c": 44, + "k": 28, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 44, - 'k': 14, + "num": 2, + "c": 44, + "k": 14, } - ] - } + ], + }, }, - 'R17x77': { - 'version_indicator': 0b11101, - 'height': 17, - 'width': 77, - 'remainder_bits': 0, - 'character_count_length': 7, - 'codewords_total': 122, - 'blocks': { + "R17x77": { + "version_indicator": 0b11101, + "height": 17, + "width": 77, + "remainder_bits": 0, + "character_count_length": 7, + "codewords_total": 122, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 61, - 'k': 39, + "num": 2, + "c": 61, + "k": 39, }, ], ErrorCorrectionLevel.H: [ { - 'num': 1, - 'c': 40, - 'k': 12, + "num": 1, + "c": 40, + "k": 12, }, { - 'num': 2, - 'c': 41, - 'k': 13, + "num": 2, + "c": 41, + "k": 13, }, ], - } + }, }, - 'R17x99': { - 'version_indicator': 0b11110, - 'height': 17, - 'width': 99, - 'remainder_bits': 3, - 'character_count_length': 7, - 'codewords_total': 160, - 'blocks': { + "R17x99": { + "version_indicator": 0b11110, + "height": 17, + "width": 99, + "remainder_bits": 3, + "character_count_length": 7, + "codewords_total": 160, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 2, - 'c': 53, - 'k': 33, + "num": 2, + "c": 53, + "k": 33, }, { - 'num': 1, - 'c': 54, - 'k': 34, - } + "num": 1, + "c": 54, + "k": 34, + }, ], ErrorCorrectionLevel.H: [ { - 'num': 4, - 'c': 40, - 'k': 14, + "num": 4, + "c": 40, + "k": 14, }, ], - } + }, }, - 'R17x139': { - 'version_indicator': 0b11111, - 'height': 17, - 'width': 139, - 'remainder_bits': 4, - 'character_count_length': 8, - 'codewords_total': 232, - 'blocks': { + "R17x139": { + "version_indicator": 0b11111, + "height": 17, + "width": 139, + "remainder_bits": 4, + "character_count_length": 8, + "codewords_total": 232, + "blocks": { ErrorCorrectionLevel.M: [ { - 'num': 4, - 'c': 58, - 'k': 38, + "num": 4, + "c": 58, + "k": 38, }, ], ErrorCorrectionLevel.H: [ { - 'num': 2, - 'c': 38, - 'k': 12, + "num": 2, + "c": 38, + "k": 12, }, { - 'num': 4, - 'c': 39, - 'k': 13, + "num": 4, + "c": 39, + "k": 13, }, ], - } + }, }, } diff --git a/src/rmqrcode/qr_image.py b/src/rmqrcode/qr_image.py index a94050f..a2ee637 100644 --- a/src/rmqrcode/qr_image.py +++ b/src/rmqrcode/qr_image.py @@ -1,39 +1,39 @@ -from .enums.color import Color - -from PIL import Image -from PIL import ImageDraw +from PIL import Image, ImageDraw class QRImage: def __init__(self, qr, module_size=10): self._module_size = module_size - self._img = Image.new( - 'RGB', - ((qr.width() + 4) * module_size, (qr.height() + 4) * module_size), - (255, 255, 255) - ) - self._make_image(qr) - + qr_list = qr.to_list() + self._img = Image.new("RGB", (len(qr_list[0]) * module_size, len(qr_list) * module_size), (255, 255, 255)) + self._make_image(qr_list) def show(self): self._img.show() pass + def get_ndarray(self): + try: + import numpy as np + except ImportError: + raise ImportError("numpy is not installed") + + return np.array(self._img) def save(self, name): self._img.save(name) - - def _make_image(self, qr): + def _make_image(self, qr_list): draw = ImageDraw.Draw(self._img) - for y in range(qr.height()): - for x in range(qr.width()): - r, g, b = 125, 125, 125 - if qr.value_at(x, y) == Color.BLACK: - r, g, b = 0, 0, 0 - elif qr.value_at(x, y) == Color.WHITE: - r, g, b, = 255, 255, 255 + for y in range(len(qr_list)): + for x in range(len(qr_list[0])): + r, g, b = (0, 0, 0) if qr_list[y][x] else (255, 255, 255) draw.rectangle( - xy=((x + 2) * self._module_size, (y + 2) * self._module_size, (x + 1 + 2) * self._module_size, (y + 1 + 2) * self._module_size), - fill=(r, g, b) - ) \ No newline at end of file + xy=( + x * self._module_size, + y * self._module_size, + (x + 1) * self._module_size, + (y + 1) * self._module_size, + ), + fill=(r, g, b), + ) diff --git a/src/rmqrcode/rmqrcode.py b/src/rmqrcode/rmqrcode.py index 466934b..5117d8e 100644 --- a/src/rmqrcode/rmqrcode.py +++ b/src/rmqrcode/rmqrcode.py @@ -1,30 +1,76 @@ -from .format.error_correction_level import ErrorCorrectionLevel -from .format.rmqr_versions import rMQRVersions -from .format.data_capacities import DataCapacities +"""A module to make an rMQR Code. + +Example: + Use the rMQR.fit method to make an rMQR automatically with some options. + + qr = rMQR.fit( + "https://oudon.xyz", + ecc=ErrorCorrectionLevel.M, + fit_strategy=FitStrategy.MINIMIZE_WIDTH + ) + + The following example shows how to select the size of an rMQR Code. + + qr = rMQR("R11x139", ErrorCorrectionLevel.H) + qr.make("https://oudon.xyz") + +""" + +import logging + +from .encoder.byte_encoder import ByteEncoder +from .enums.color import Color +from .enums.fit_strategy import FitStrategy from .format.alignment_pattern_coordinates import AlignmentPatternCoordinates +from .format.data_capacities import DataCapacities +from .format.error_correction_level import ErrorCorrectionLevel from .format.generator_polynomials import GeneratorPolynomials from .format.mask import mask - -from .encoder.byte_encoder import ByteEncoder +from .format.rmqr_versions import rMQRVersions from .util.error_correction import compute_bch, compute_reed_solomon from .util.utilities import split_into_8bits -from .enums.color import Color -from .enums.fit_strategy import FitStrategy -import logging class rMQR: + """A class to make an rMQR Code. + + Attributes: + QUIET_ZONE_MODULES (int): The width of the quiet zone. + + """ + + QUIET_ZONE_MODULES = 2 + @staticmethod def _init_logger(): + """Initializes a logger and returns it. + + Returns: + logging.RootLogger: Logger + + """ logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) logger.setLevel(logging.DEBUG) logger.propagate = True return logger - @staticmethod - def fit(data,ecc=ErrorCorrectionLevel.M, fit_strategy=FitStrategy.BALANCED): + def fit(data, ecc=ErrorCorrectionLevel.M, fit_strategy=FitStrategy.BALANCED): + """Attempts to make an rMQR have optimized version for given data. + + Args: + data (str): Data string to encode. + ecc (rmqrcode.ErrorCorrectionLevel): Error correction level. + fit_strategy (rmqrcode.FitStrategy): Strategy how determine rMQR Code version. + + Returns: + rmqrcode.rMQR: Optimized rMQR Code. + + Raises: + rmqrcode.DataTooLongError: If the data is too long to encode. + + """ logger = rMQR._init_logger() data_length = ByteEncoder.length(data) @@ -34,36 +80,46 @@ def fit(data,ecc=ErrorCorrectionLevel.M, fit_strategy=FitStrategy.BALANCED): logger.debug("Select rMQR Code version") for version_name, qr_version in DataCapacities.items(): - if data_length <= qr_version['capacity']['Byte'][ecc]: - width, height = qr_version['width'], qr_version['height'] - if not width in determined_width and not height in determined_height: + if data_length <= qr_version["capacity"]["Byte"][ecc]: + width, height = qr_version["width"], qr_version["height"] + if width not in determined_width and height not in determined_height: determined_width.add(width) determined_height.add(height) - ok_versions.append({ - 'version': version_name, - 'width': width, - 'height': height, - }) + ok_versions.append( + { + "version": version_name, + "width": width, + "height": height, + } + ) logger.debug(f"ok: {version_name}") if len(ok_versions) == 0: raise DataTooLongError("The data is too long.") if fit_strategy == FitStrategy.MINIMIZE_WIDTH: - sort_key = lambda x: x['width'] + + def sort_key(x): + return x["width"] + elif fit_strategy == FitStrategy.MINIMIZE_HEIGHT: - sort_key = lambda x: x['height'] + + def sort_key(x): + return x["height"] + elif fit_strategy == FitStrategy.BALANCED: - sort_key = lambda x: x['height'] * 9 + x['width'] + + def sort_key(x): + return x["height"] * 9 + x["width"] + selected = sorted(ok_versions, key=sort_key)[0] logger.debug(f"selected: {selected}") - qr = rMQR(selected['version'], ecc) + qr = rMQR(selected["version"], ecc) qr.make(data) return qr - - def __init__(self, version, ecc, logger=None): + def __init__(self, version, ecc, with_quiet_zone=True, logger=None): self._logger = logger or rMQR._init_logger() if not rMQR.validate_version(version): @@ -71,47 +127,136 @@ def __init__(self, version, ecc, logger=None): qr_version = rMQRVersions[version] self._version = version - self._height = qr_version['height'] - self._width = qr_version['width'] + self._height = qr_version["height"] + self._width = qr_version["width"] self._error_correction_level = ecc self._qr = [[Color.UNDEFINED for x in range(self._width)] for y in range(self._height)] - def make(self, data): + """Makes an rMQR Code for given data + + Args: + data (str): Data string. + + Returns: + void + """ self._put_finder_pattern() self._put_corner_finder_pattern() self._put_alignment_pattern() self._put_timing_pattern() self._put_version_information() - mask_area = self._put_data(data); + mask_area = self._put_data(data) self._apply_mask(mask_area) - def version_name(self): - return f"R{self._height}x{self._width}" + """Returns the version name. + + Returns: + str: The version name. + Examples: + >>> qr.version_name() + "R13x77" + + """ + return f"R{self._height}x{self._width}" def size(self): - return (self.width(), self.height()) + """Returns the size. + Returns: + tuple: The rMQR Code size. + + Examples: + >>> qr.size() + (77, 13) + + Note: + This not includes the quiet zone. + + """ + return (self.width(), self.height()) def height(self): - return self._height + """Returns the height. + + Returns: + int: The height. + Note: + This not includes the quiet zone. + + """ + return self._height def width(self): - return self._width + """Returns the width. + Returns: + int: The width. + + Note: + This not includes the quiet zone. + + """ + return self._width def value_at(self, x, y): + """DEPRECATED: Returns the color at the point of (x, y). + + Returns: + rmqrcode.Color: The color of rMQRCode at the point of (x, y). + + Note: + This method is deprecated. Use to_list() alternatively. + This not includes the quiet zone. + + """ return self._qr[y][x] + def to_list(self, with_quiet_zone=True): + """Converts to two-dimensional list and returns it. - def to_list(self): - return [list(map(lambda x: 1 if x == Color.BLACK else 0, column)) for column in self._qr] + The value is 1 for the dark module and 0 for the light module. + + Args: + with_quiet_zone (bool): Flag to select whether include the quiet zone. + + Returns: + list: Converted list. + + """ + + res = [] + if with_quiet_zone: + for y in range(self.QUIET_ZONE_MODULES): + res.append([0] * (self.width() + self.QUIET_ZONE_MODULES * 2)) + for row in self._to_binary_list(): + res.append([0] * self.QUIET_ZONE_MODULES + row + [0] * self.QUIET_ZONE_MODULES) + for y in range(self.QUIET_ZONE_MODULES): + res.append([0] * (self.width() + self.QUIET_ZONE_MODULES * 2)) + else: + res = self._to_binary_list() + return res + def _to_binary_list(self): + """Converts to two-dimensional list and returns it. - def __str__(self): + The value is 1 for the dark module and 0 for the light module. + + Args: + with_quiet_zone (bool): Flag to select whether include the quiet zone. + + Returns: + list: Converted list. + + Note: + This not includes the quiet zone. + """ + return [list(map(lambda x: 1 if x == Color.BLACK else 0, column)) for column in self._qr] + + def __str__(self, with_quiet_zone=True): res = "" show = {} @@ -122,15 +267,26 @@ def __str__(self): show[False] = "_" res += f"rMQR Version R{self._height}x{self._width}:\n" - for i in range(self._height): - for j in range(self._width): + if with_quiet_zone: + res += (show[False] * (self.width() + self.QUIET_ZONE_MODULES * 2) + "\n") * self.QUIET_ZONE_MODULES + + for i in range(self.height()): + if with_quiet_zone: + res += show[False] * self.QUIET_ZONE_MODULES + + for j in range(self.width()): if self._qr[i][j] in show: res += show[self._qr[i][j]] else: res += self._qr[i][j] + + if with_quiet_zone: + res += show[False] * self.QUIET_ZONE_MODULES res += "\n" - return res + if with_quiet_zone: + res += (show[False] * (self.width() + self.QUIET_ZONE_MODULES * 2) + "\n") * self.QUIET_ZONE_MODULES + return res def _put_finder_pattern(self): # Finder pattern @@ -145,7 +301,7 @@ def _put_finder_pattern(self): # Inner square for i in range(3): for j in range(3): - self._qr[2+i][2+j] = Color.BLACK + self._qr[2 + i][2 + j] = Color.BLACK # Separator for n in range(8): @@ -160,29 +316,27 @@ def _put_finder_pattern(self): for i in range(5): for j in range(5): color = Color.BLACK if i == 0 or i == 4 or j == 0 or j == 4 else Color.WHITE - self._qr[self._height-i-1][self._width-j-1] = color + self._qr[self._height - i - 1][self._width - j - 1] = color # Inner square - self._qr[self._height-1-2][self._width-1-2] = Color.BLACK - + self._qr[self._height - 1 - 2][self._width - 1 - 2] = Color.BLACK def _put_corner_finder_pattern(self): # Corner finder pattern # Bottom left - self._qr[self._height-1][0] = Color.BLACK - self._qr[self._height-1][1] = Color.BLACK - self._qr[self._height-1][2] = Color.BLACK + self._qr[self._height - 1][0] = Color.BLACK + self._qr[self._height - 1][1] = Color.BLACK + self._qr[self._height - 1][2] = Color.BLACK if self._height >= 11: - self._qr[self._height-2][0] = Color.BLACK - self._qr[self._height-2][1] = Color.WHITE + self._qr[self._height - 2][0] = Color.BLACK + self._qr[self._height - 2][1] = Color.WHITE # Top right - self._qr[0][self._width-1] = Color.BLACK - self._qr[0][self._width-2] = Color.BLACK - self._qr[1][self._width-1] = Color.BLACK - self._qr[1][self._width-2] = Color.WHITE - + self._qr[0][self._width - 1] = Color.BLACK + self._qr[0][self._width - 2] = Color.BLACK + self._qr[1][self._width - 1] = Color.BLACK + self._qr[1][self._width - 2] = Color.WHITE def _put_alignment_pattern(self): # Alignment pattern @@ -194,8 +348,7 @@ def _put_alignment_pattern(self): # Top side self._qr[i][center_x + j - 1] = color # Bottom side - self._qr[self._height-1-i][center_x + j - 1] = color - + self._qr[self._height - 1 - i][center_x + j - 1] = color def _put_timing_pattern(self): # Timing pattern @@ -215,13 +368,11 @@ def _put_timing_pattern(self): if self._qr[i][j] == Color.UNDEFINED: self._qr[i][j] = color - def _put_version_information(self): version_information = self._compute_version_info() self._put_version_information_finder_pattern_side(version_information) self._put_version_information_finder_sub_pattern_side(version_information) - def _put_version_information_finder_pattern_side(self, version_information): mask = 0b011111101010110010 version_information ^= mask @@ -230,8 +381,7 @@ def _put_version_information_finder_pattern_side(self, version_information): for n in range(18): di = n % 5 dj = n // 5 - self._qr[si+di][sj+dj] = Color.BLACK if version_information>>n & 1 else Color.WHITE - + self._qr[si + di][sj + dj] = Color.BLACK if version_information >> n & 1 else Color.WHITE def _put_version_information_finder_sub_pattern_side(self, version_information): mask = 0b100000101001111011 @@ -241,27 +391,46 @@ def _put_version_information_finder_sub_pattern_side(self, version_information): for n in range(15): di = n % 5 dj = n // 5 - self._qr[si+di][sj+dj] = Color.BLACK if version_information>>n & 1 else Color.WHITE - self._qr[self._height-1-5][self._width-1-4] = Color.BLACK if version_information>>15 & 1 else Color.WHITE - self._qr[self._height-1-5][self._width-1-3] = Color.BLACK if version_information>>16 & 1 else Color.WHITE - self._qr[self._height-1-5][self._width-1-2] = Color.BLACK if version_information>>17 & 1 else Color.WHITE - + self._qr[si + di][sj + dj] = Color.BLACK if version_information >> n & 1 else Color.WHITE + self._qr[self._height - 1 - 5][self._width - 1 - 4] = ( + Color.BLACK if version_information >> 15 & 1 else Color.WHITE + ) + self._qr[self._height - 1 - 5][self._width - 1 - 3] = ( + Color.BLACK if version_information >> 16 & 1 else Color.WHITE + ) + self._qr[self._height - 1 - 5][self._width - 1 - 2] = ( + Color.BLACK if version_information >> 17 & 1 else Color.WHITE + ) def _compute_version_info(self): qr_version = rMQRVersions[self.version_name()] - version_information_data = qr_version['version_indicator'] + version_information_data = qr_version["version_indicator"] if self._error_correction_level == ErrorCorrectionLevel.H: - version_information_data |= 1<<6 + version_information_data |= 1 << 6 reminder_polynomial = compute_bch(version_information_data) - version_information_data = version_information_data<<12 | reminder_polynomial + version_information_data = version_information_data << 12 | reminder_polynomial return version_information_data - def _put_data(self, data): + """Symbol character placement. + + This method puts data into the encoding region of the rMQR Code. Also this + method computes a two-dimensional list shows where encoding region at the + same time. And returns the list. + + See: "7.7.3 Symbol character placement" in the ISO/IEC 23941. + + Args: + data (str): Data string. + + Returns: + list: A two-dimensional list shows where encoding region. + + """ qr_version = rMQRVersions[self.version_name()] - character_count_length = qr_version['character_count_length'] - codewords_total = qr_version['codewords_total'] + character_count_length = qr_version["character_count_length"] + codewords_total = qr_version["codewords_total"] encoded_data = self._convert_to_bites_data(data, character_count_length, codewords_total) codewords = split_into_8bits(encoded_data) @@ -278,8 +447,7 @@ def _put_data(self, data): codewords.append("00010001") data_codewords_per_block, rs_codewords_per_block = self._split_into_blocks( - codewords, - qr_version['blocks'][self._error_correction_level] + codewords, qr_version["blocks"][self._error_correction_level] ) # Construct the final message codeword sequence @@ -301,15 +469,15 @@ def _put_data(self, data): self._logger.debug(f"Put RS data codewords {i} : {rs_codewords[i]}") # Codeword placement - dy = -1 # Up + dy = -1 # Up current_codeword_idx = 0 current_bit_idx = 0 cx, cy = self._width - 2, self._height - 6 - remainder_bits = qr_version['remainder_bits'] + remainder_bits = qr_version["remainder_bits"] mask_area = [[False for i in range(self._width)] for j in range(self._height)] while True: - for x in [cx, cx-1]: + for x in [cx, cx - 1]: if self._qr[cy][x] == Color.UNDEFINED: # Process only empty cell if current_codeword_idx == len(final_codewords): @@ -319,7 +487,11 @@ def _put_data(self, data): remainder_bits -= 1 else: # Codewords - self._qr[cy][x] = Color.BLACK if final_codewords[current_codeword_idx][current_bit_idx] == '1' else Color.WHITE + self._qr[cy][x] = ( + Color.BLACK + if final_codewords[current_codeword_idx][current_bit_idx] == "1" + else Color.WHITE + ) mask_area[cy][x] = True current_bit_idx += 1 if current_bit_idx == 8: @@ -344,15 +516,14 @@ def _put_data(self, data): return mask_area - def _split_into_blocks(self, codewords, blocks_definition): data_idx, error_idx = 0, 0 data_codewords_per_block = [] rs_codewords_per_block = [] for block_definition in blocks_definition: - for i in range(block_definition['num']): - data_codewords_num = block_definition['k'] - rs_codewords_num = block_definition['c'] - block_definition['k'] + for i in range(block_definition["num"]): + data_codewords_num = block_definition["k"] + rs_codewords_num = block_definition["c"] - block_definition["k"] g = GeneratorPolynomials[rs_codewords_num] codewords_in_block = codewords[data_idx : data_idx + data_codewords_num] @@ -366,7 +537,6 @@ def _split_into_blocks(self, codewords, blocks_definition): return data_codewords_per_block, rs_codewords_per_block - def _convert_to_bites_data(self, data, character_count_length, codewords_total): encoded_data = ByteEncoder.encode(data, character_count_length) @@ -376,8 +546,19 @@ def _convert_to_bites_data(self, data, character_count_length, codewords_total): return encoded_data - def _apply_mask(self, mask_area): + """Data masking. + + This method applies the data mask. + + Args: + mask_area (list): A two-dimensional list shows where encoding region. + This is computed by self._put_data(). + + Returns: + void + + """ for y in range(self._height): for x in range(self._width): if not mask_area[y][x]: @@ -388,15 +569,35 @@ def _apply_mask(self, mask_area): elif self._qr[y][x] == Color.WHITE: self._qr[y][x] = Color.BLACK - @staticmethod def validate_version(version_name): + """Check if the given version_name is valid + + Args: + version_name (str): Version name. + + Returns: + bool: Validation result. + + Example: + >>> rMQR.validate_version("R13x77") + True + + >>> rMQR.validate_version("R14x55") + False + + >>> rMQR.validate_version("13, 77") + False + + """ return version_name in rMQRVersions class DataTooLongError(ValueError): + "A class represents an error raised when the given data is too long." pass class IllegalVersionError(ValueError): - pass \ No newline at end of file + "A class represents an error raised when the given version name is illegal." + pass diff --git a/src/rmqrcode/util/error_correction.py b/src/rmqrcode/util/error_correction.py index a3b47de..14943a5 100644 --- a/src/rmqrcode/util/error_correction.py +++ b/src/rmqrcode/util/error_correction.py @@ -1,12 +1,13 @@ -from .utilities import msb, to_binary from .galois_fields import GaloisFields +from .utilities import msb, to_binary + def compute_bch(data): data <<= 12 - g = 1<<12 | 1<<11 | 1<<10 | 1<<9 | 1<<8 | 1<<5 | 1<<2 | 1<<0 + g = 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0 tmp_data = data - while (msb(tmp_data) >= 13): + while msb(tmp_data) >= 13: multiple = msb(tmp_data) - 13 tmp_g = g << multiple tmp_data ^= tmp_g @@ -14,6 +15,8 @@ def compute_bch(data): gf = GaloisFields() + + def compute_reed_solomon(data, g, num_error_codewords): f = list(map(lambda x: int(x, 2), data)) @@ -21,13 +24,14 @@ def compute_reed_solomon(data, g, num_error_codewords): f.append(0) for i in range(len(data)): - if f[i] == 0: continue + if f[i] == 0: + continue mult = gf.i2e[f[i]] for j in range(len(g)): - f[i+j] ^= gf.e2i[(g[j]+mult)%255] + f[i + j] ^= gf.e2i[(g[j] + mult) % 255] rs_codewords = [] for i in range(num_error_codewords): rs_codewords.append(to_binary(f[-num_error_codewords + i], 8)) - return rs_codewords \ No newline at end of file + return rs_codewords diff --git a/src/rmqrcode/util/galois_fields.py b/src/rmqrcode/util/galois_fields.py index 6b3c076..4a0cdf7 100644 --- a/src/rmqrcode/util/galois_fields.py +++ b/src/rmqrcode/util/galois_fields.py @@ -5,7 +5,7 @@ class GaloisFields: def __init__(self): # Irreducible polynomial in GF(2^8) - p = (1<<8)|(1<<4)|(1<<3)|(1<<2)|1 + p = (1 << 8) | (1 << 4) | (1 << 3) | (1 << 2) | 1 self.e2i[0] = 1 self.e2i[255] = 1 @@ -15,7 +15,7 @@ def __init__(self): tmp = 1 for e in range(1, 255): tmp <<= 1 - if tmp & (1<<8): + if tmp & (1 << 8): tmp ^= p self.e2i[e] = tmp self.i2e[tmp] = e diff --git a/src/rmqrcode/util/utilities.py b/src/rmqrcode/util/utilities.py index 8de88ac..1f4f4c4 100644 --- a/src/rmqrcode/util/utilities.py +++ b/src/rmqrcode/util/utilities.py @@ -8,9 +8,9 @@ def to_binary(data, len): def split_into_8bits(data): codewords = [] - while (len(data) >= 8): + while len(data) >= 8: codewords.append(data[:8]) data = data[8:] if data != "": - codewords.append(data.ljust(8, '0')) - return codewords \ No newline at end of file + codewords.append(data.ljust(8, "0")) + return codewords diff --git a/tests/rmqrcode_test.py b/tests/rmqrcode_test.py index b857821..c075f77 100644 --- a/tests/rmqrcode_test.py +++ b/tests/rmqrcode_test.py @@ -14,6 +14,12 @@ def test_make(self): qr = rMQR('R13x99', ErrorCorrectionLevel.M) qr.make("abc") + assert len(qr.to_list(with_quiet_zone=True)) is 17 + assert len(qr.to_list(with_quiet_zone=True)[0]) is 103 + + assert len(qr.to_list(with_quiet_zone=False)) is 13 + assert len(qr.to_list(with_quiet_zone=False)[0]) is 99 + def test_raise_too_long_error(self): with pytest.raises(DataTooLongError) as e: s = "a".ljust(200, "a")