├── .gitignore ├── README.md ├── docs └── USS-PDF-417.pdf └── python ├── LICENSE ├── README.md ├── fuzz_testing └── test.py ├── images └── haiku.png ├── pyproject.toml ├── setup.py ├── src └── pdf417decoder │ ├── BarcodeArea.py │ ├── BarcodeInfo.py │ ├── BorderPattern.py │ ├── BorderSymbol.py │ ├── Decoder.py │ ├── ErrorCorrection.py │ ├── Modulus.py │ ├── Polynomial.py │ ├── StaticTables.py │ └── __init__.py └── tests ├── binary_data.png ├── blurred_error_correction.png ├── byte_mode.png ├── character_type_switches.txt ├── character_type_transitions.png ├── macropdf_part1and2.png ├── macropdf_part3.png ├── missing_data.png ├── multiple_barcodes.png ├── rotated.png ├── tests.py └── upside_down.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdf417decoder 2 | ![Image of a PDF417 barcode](https://raw.githubusercontent.com/sparkfish/pdf417decoder/dev/python/images/haiku.png?id=1) 3 | 4 | pdf417decoder is a pure Python library for decoding [PDF417 barcodes](https://en.wikipedia.org/wiki/PDF417). 5 | 6 | Reader is capable of Error Detection and Correction according to the standards for PDF417 which you can read about here [ISO/IEC 15438:2006](https://www.iso.org/standard/43816.html) or download an older version of the PDF [this website](https://www.expresscorp.com/public/uploads/specifications/44/USS-PDF-417.pdf). 7 | 8 | ## Installation 9 | 10 | Use the package manager [pip](https://pip.pypa.io/en/stable/) to install pdf417decoder. 11 | 12 | ```bash 13 | pip install pdf417decoder 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```python 19 | from PIL import Image as PIL 20 | from pdf417decoder import PDF417Decoder 21 | 22 | image = PIL.open("barcode.png") 23 | decoder = PDF417Decoder(image) 24 | 25 | if (decoder.decode() > 0): 26 | decoded = decoder.barcode_data_index_to_string(0) 27 | ``` 28 | 29 | ## Testing Results 30 | 31 | This library was tested using [pdf417gen](https://pypi.org/project/pdf417gen/) to create random barcodes and blurred with [OpenCV](https://pypi.org/project/opencv-python/) to test error correction. PyTest is used with several test images to show the libraries capability to decode barcodes in the following test cases. 32 | 33 | * Binary data 34 | * Multiple barcodes 35 | * Upside down barcode 36 | * Rotated barcode 37 | * Error Corrections: Corrupted data due to blurred barcode 38 | * Error Corrections: Missing data due marks concealing barcode 39 | * Character type transitions (Upper, Lower, Mixed and Punctuation) 40 | 41 | ## Roadmap 42 | 43 | - [x] Initial port of C# to Python 44 | - [x] Create tests for functionality 45 | - [x] Create fuzzy testing of decoder 46 | - [x] Decode from PIL.Image 47 | - [ ] Decode from Numpy Array 48 | - [ ] Convert THRESH_OTSU image processing to use original algorithm written in C# or alternative 49 | - [ ] Performance testing and optimizations 50 | 51 | ## Contributing 52 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 53 | 54 | ## License 55 | [CPOL](https://www.codeproject.com/info/cpol10.aspx) 56 | 57 | This project is a derivative of code licensed under the Code Project Open License (CPOL). The Code Project Open License (CPOL) is intended to provide developers who choose to share their code with a license that protects them and provides users of their code with a clear statement regarding how the code can be used. 58 | 59 | ## Credits 60 | 61 | Source code is a port of a C# Library created and maintained by [Uzi Granot](https://www.codeproject.com/script/Membership/View.aspx?mid=193217). [PDF417 Barcode Decoder .NET Class Library and Two Demo Apps](https://www.codeproject.com/Articles/4042463/PDF417-Barcode-Decoder-NET-Class-Library-and-Two-D) 62 | -------------------------------------------------------------------------------- /docs/USS-PDF-417.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/docs/USS-PDF-417.pdf -------------------------------------------------------------------------------- /python/LICENSE: -------------------------------------------------------------------------------- 1 | Preamble 2 | This License governs Your use of the Work. This License is intended to allow developers to use the Source Code and Executable Files provided as part of the Work in any application in any form. 3 | 4 | The main points subject to the terms of the License are: 5 | 6 | Source Code and Executable Files can be used in commercial applications; 7 | Source Code and Executable Files can be redistributed; and 8 | Source Code can be modified to create derivative works. 9 | No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is provided "as-is". 10 | The Article(s) accompanying the Work may not be distributed or republished without the Author's consent 11 | This License is entered between You, the individual or other entity reading or otherwise making use of the Work licensed pursuant to this License and the individual or other entity which offers the Work under the terms of this License ("Author"). 12 | 13 | License 14 | 15 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 16 | 17 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY USE OF THE WORK. 18 | 19 | Definitions. 20 | 21 | "Articles" means, collectively, all articles written by Author which describes how the Source Code and Executable Files for the Work may be used by a user. 22 | 23 | "Author" means the individual or entity that offers the Work under the terms of this License. 24 | 25 | "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works. 26 | 27 | "Executable Files" refer to the executables, binary files, configuration and any required data files included in the Work. 28 | 29 | "Publisher" means the provider of the website, magazine, CD-ROM, DVD or other medium from or by which the Work is obtained by You. 30 | 31 | "Source Code" refers to the collection of source code and configuration files used to create the Executable Files. 32 | 33 | "Standard Version" refers to such a Work if it has not been modified, or has been modified in accordance with the consent of the Author, such consent being in the full discretion of the Author. 34 | 35 | "Work" refers to the collection of files distributed by the Publisher, including the Source Code, Executable Files, binaries, data files, documentation, whitepapers and the Articles. 36 | 37 | "You" is you, an individual or entity wishing to use the Work and exercise your rights under this License. 38 | 39 | Fair Use/Fair Use Rights. Nothing in this License is intended to reduce, limit, or restrict any rights arising from fair use, fair dealing, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. 40 | License Grant. Subject to the terms and conditions of this License, the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 41 | You may use the standard version of the Source Code or Executable Files in Your own applications. 42 | 43 | You may apply bug fixes, portability fixes and other modifications obtained from the Public Domain or from the Author. A Work modified in such a way shall still be considered the standard version and will be subject to this License. 44 | 45 | You may otherwise modify Your copy of this Work (excluding the Articles) in any way to create a Derivative Work, provided that You insert a prominent notice in each changed file stating how, when and where You changed that file. 46 | 47 | You may distribute the standard version of the Executable Files and Source Code or Derivative Work in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution. 48 | 49 | The Articles discussing the Work published in any form by the author may not be distributed or republished without the Author's consent. The author retains copyright to any such Articles. You may use the Executable Files and Source Code pursuant to this License but you may not repost or republish or otherwise distribute or make available the Articles, without the prior written consent of the Author. 50 | 51 | Any subroutines or modules supplied by You and linked into the Source Code or Executable Files of this Work shall not be considered part of this Work and will not be subject to the terms of this License. 52 | Patent License. Subject to the terms and conditions of this License, each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, import, and otherwise transfer the Work. 53 | 54 | Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 55 | You agree not to remove any of the original copyright, patent, trademark, and attribution notices and associated disclaimers that may appear in the Source Code or Executable Files. 56 | 57 | You agree not to advertise or in any way imply that this Work is a product of Your own. 58 | The name of the Author may not be used to endorse or promote products derived from the Work without the prior written consent of the Author. 59 | 60 | You agree not to sell, lease, or rent any part of the Work. This does not restrict you from including the Work or any part of the Work inside a larger software distribution that itself is being sold. The Work by itself, though, cannot be sold, leased or rented. 61 | You may distribute the Executable Files and Source Code only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy of the Executable Files or Source Code You distribute and ensure that anyone receiving such Executable Files and Source Code agrees that the terms of this License apply to such Executable Files and/or Source Code. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute the Executable Files or Source Code with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License. 62 | 63 | You agree not to use the Work for illegal, immoral or improper purposes, or on pages containing illegal, immoral or improper material. The Work is subject to applicable export laws. You agree to comply with all such laws and regulations that may apply to the Work after Your receipt of the Work. 64 | 65 | Representations, Warranties and Disclaimer. THIS WORK IS PROVIDED "AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT, OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. 66 | 67 | Indemnity. You agree to defend, indemnify and hold harmless the Author and the Publisher from and against any claims, suits, losses, damages, liabilities, costs, and expenses (including reasonable legal or attorneys’ fees) resulting from or relating to any use of the Work by You. 68 | 69 | Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 70 | Termination. 71 | 72 | This License and the rights granted hereunder will terminate automatically upon any breach by You of any term of this License. Individuals or entities who have received Derivative Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination of this License. 73 | 74 | If You bring a copyright, trademark, patent or any other infringement claim against any contributor over infringements You claim are made by the Work, your License from such contributor to the Work ends automatically. 75 | 76 | Subject to the above terms and conditions, this License is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, the Author reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 77 | 78 | Publisher. The parties hereby confirm that the Publisher shall not, under any circumstances, be responsible for and shall not have any liability in respect of the subject matter of this License. The Publisher makes no warranty whatsoever in connection with the Work and shall not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. The Publisher reserves the right to cease making the Work available to You at any time without notice 79 | 80 | Miscellaneous 81 | 82 | This License shall be governed by the laws of the location of the head office of the Author or if the Author is an individual, the laws of location of the principal place of residence of the Author. 83 | 84 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this License, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 85 | 86 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 87 | 88 | This License constitutes the entire agreement between the parties with respect to the Work licensed herein. There are no understandings, agreements or representations with respect to the Work not specified herein. The Author shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Author and You. -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # pdf417decoder 2 | ![Image of a PDF417 barcode](https://raw.githubusercontent.com/sparkfish/pdf417decoder/dev/python/images/haiku.png?id=1) 3 | 4 | pdf417decoder is a pure Python library for decoding [PDF417 barcodes](https://en.wikipedia.org/wiki/PDF417). 5 | 6 | Reader is capable of Error Detection and Correction according to the standards for PDF417 which you can read about here [ISO/IEC 15438:2006](https://www.iso.org/standard/43816.html) or download an older version of the PDF [this website](https://www.expresscorp.com/public/uploads/specifications/44/USS-PDF-417.pdf). 7 | 8 | ## Installation 9 | 10 | Use the package manager [pip](https://pip.pypa.io/en/stable/) to install pdf417decoder. 11 | 12 | ```bash 13 | pip install pdf417decoder 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```python 19 | from PIL import Image as PIL 20 | from pdf417decoder import PDF417Decoder 21 | 22 | image = PIL.open("barcode.png") 23 | decoder = PDF417Decoder(image) 24 | 25 | if (decoder.decode() > 0): 26 | decoded = decoder.barcode_data_index_to_string(0) 27 | ``` 28 | 29 | ## Testing Results 30 | 31 | This library was tested using [pdf417gen](https://pypi.org/project/pdf417gen/) to create random barcodes and blurred with [OpenCV](https://pypi.org/project/opencv-python/) to test error correction. PyTest is used with several test images to show the libraries capability to decode barcodes in the following test cases. 32 | 33 | * Binary data 34 | * Multiple barcodes 35 | * Upside down barcode 36 | * Rotated barcode 37 | * Error Corrections: Corrupted data due to blurred barcode 38 | * Error Corrections: Missing data due marks concealing barcode 39 | * Character type transitions (Upper, Lower, Mixed and Punctuation) 40 | 41 | ## Contributing 42 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 43 | 44 | ## License 45 | [CPOL](https://www.codeproject.com/info/cpol10.aspx) 46 | 47 | This project is a derivative of code licensed under the Code Project Open License (CPOL). The Code Project Open License (CPOL) is intended to provide developers who choose to share their code with a license that protects them and provides users of their code with a clear statement regarding how the code can be used. 48 | 49 | ## Credits 50 | 51 | Source code is a port of a C# Library created and maintained by [Uzi Granot](https://www.codeproject.com/script/Membership/View.aspx?mid=193217). [PDF417 Barcode Decoder .NET Class Library and Two Demo Apps](https://www.codeproject.com/Articles/4042463/PDF417-Barcode-Decoder-NET-Class-Library-and-Two-D) 52 | -------------------------------------------------------------------------------- /python/fuzz_testing/test.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import numpy as np 4 | import cv2 5 | import time 6 | import csv 7 | import cProfile 8 | import zxing 9 | 10 | from PIL import Image 11 | from pdf417decoder import PDF417Decoder 12 | from pdf417gen import encode, render_image 13 | 14 | class BarcodeTest: 15 | ''' Class to generate and store details for a test barcode ''' 16 | def __init__(self, encodedData: string, columns: int, security_level: int, scale: int, ratio: int): 17 | codes = encode(encodedData, columns=columns, security_level=security_level) 18 | 19 | self.EncodedData = encodedData 20 | self.OriginalImage = render_image(codes, scale=scale, ratio=ratio) 21 | self.OriginalNumpy = np.array(self.OriginalImage) 22 | self.crappify() 23 | 24 | def crappify(self): 25 | crap_image = np.array(self.OriginalImage.rotate(random.uniform(-2,2))) 26 | self.CrappifiedNumpy = cv2.blur(crap_image,(3,3)) 27 | self.CrappifiedImage = Image.fromarray(self.CrappifiedNumpy, 'RGB') 28 | 29 | def test_image(filepath, expected_decode): 30 | barcode = Image.open(filepath) 31 | decoder = PDF417Decoder(barcode) 32 | 33 | try: 34 | barcode_count = decoder.decode() 35 | 36 | if (barcode_count > 0): 37 | decoded = decoder.barcode_data_index_to_string(0) 38 | 39 | if (decoded == expected_decode): 40 | print("Success") 41 | else: 42 | print("Failure: Mismatch") 43 | print("Expected: " + expected_decode) 44 | print("Decoded : " + decoded) 45 | else: 46 | print("Failure: No barcode detected") 47 | except: 48 | print("Failure: Exception occurred") 49 | 50 | def test_against_zxing(): 51 | test_number = 0 52 | success = 0 53 | failure = 0 54 | zxing_success = 0 55 | zxing_failure = 0 56 | while (True): 57 | test_number += 1 58 | 59 | # Randomize barcode settings 60 | columns = 10 #random.randint(5,15) 61 | security_level = 3 #random.randint(2,5) 62 | scale = 2 #random.randint(2,5) 63 | ratio = 2 #random.randint(2,4) 64 | 65 | # With random text valid for a PDF417 barcode 66 | text_length = 300 #random.randint(1, 5) * 50 67 | text = ''.join(random.choices(string.ascii_letters + string.digits + "&,:#-.$/+%* =^;<>@[\\]_'~!|()?{}", k=text_length)) 68 | 69 | # and Create the barcode. 70 | barcode = BarcodeTest(text, columns, security_level, scale, ratio) 71 | barcode.CrappifiedImage.save("barcode.png") 72 | 73 | # Start decoding 74 | try: 75 | reader = zxing.BarCodeReader() 76 | decodedZxing = reader.decode("barcode.png", possible_formats="PDF_417").parsed 77 | except: 78 | decodedZxing = '' 79 | 80 | try: 81 | decoder = PDF417Decoder(barcode.CrappifiedImage) 82 | barcode_count = decoder.decode() 83 | 84 | if (barcode_count == 0): 85 | decoded = '' 86 | else: 87 | decoded = decoder.barcode_data_index_to_string(0) 88 | except: 89 | decoded = '' 90 | 91 | if (decoded == barcode.EncodedData): 92 | success += 1 93 | else: 94 | failure += 1 95 | 96 | if (decodedZxing == barcode.EncodedData): 97 | zxing_success += 1 98 | else: 99 | zxing_failure += 1 100 | 101 | print(f"Success: {success} - Failure: {failure} - ZXing Success: {zxing_success} - ZXing: Failure {zxing_failure} - Total: {test_number}") 102 | 103 | def fuzz_testing(): 104 | # Collection of test result successes, failures and decode time for this test run. 105 | fields = ['Columns', 'Security Level', 'Scale', 'Ratio', 'Length', 'Successes', 'Failures', 'Average Decode Time'] 106 | test_results = list() 107 | test_number = 0 108 | 109 | while (True): 110 | test_number += 1 111 | 112 | # Randomize barcode settings 113 | columns = random.randint(5,15) 114 | security_level = random.randint(2,5) 115 | scale = random.randint(2,5) 116 | ratio = random.randint(2,4) 117 | 118 | # With random text valid for a PDF417 barcode 119 | text_length = 300 #random.randint(1, 5) * 50 120 | text = ''.join(random.choices(string.ascii_letters + string.digits + "&,:#-.$/+%* =^;<>@[\\]_'~!|()?{}", k=text_length)) 121 | 122 | # and Create the barcode. 123 | barcode = BarcodeTest(text, columns, security_level, scale, ratio) 124 | 125 | # Search for existing configuration of above settings in test results. 126 | test_result = (0,0,0,0,0,0,0,0.0) 127 | for result in test_results: 128 | if (result[0] == columns and result[1] == security_level and result[2] == scale and result[3] == ratio and result[4] == text_length): 129 | test_result = result 130 | break 131 | 132 | # Create a new one if not found or remove it from the list if it was found. 133 | if (test_result[0] == 0): 134 | test_result == (columns, security_level, scale, ratio, text_length, 0, 0, 0.0) 135 | else: 136 | test_results.remove(test_result) 137 | 138 | # Start decoding 139 | decode_start_time = time.perf_counter() 140 | try: 141 | decoder = PDF417Decoder(barcode.CrappifiedImage) 142 | barcode_count = decoder.decode() 143 | 144 | if (barcode_count == 0): 145 | decoded = '' 146 | else: 147 | decoded = decoder.barcode_data_index_to_string(0) 148 | except: 149 | decoded = '' 150 | decode_stop_time = time.perf_counter() 151 | decode_time = round(decode_stop_time - decode_start_time, 2) 152 | 153 | # Record Success or Failure of decoding 154 | if (decoded == barcode.EncodedData): 155 | successes = test_result[5] + 1 156 | failures = test_result[6] 157 | total = successes + failures 158 | total_decode = test_result[7] + decode_time 159 | avg_decode = round(total_decode / total, 2) 160 | 161 | print(f"SUCCESS #{successes} out of {total}: Columns: {columns} Security: {security_level} Scale: {scale} Ratio: {ratio} Length: {text_length} Average time to Decode: {avg_decode} seconds") 162 | 163 | # Add updated test result for this random barcode configuration back to the test results. 164 | test_results.append((columns, security_level, scale, ratio, text_length, successes, failures, total_decode)) 165 | else: 166 | successes = test_result[5] 167 | failures = test_result[6] + 1 168 | total = successes + failures 169 | total_decode = test_result[7] + decode_time 170 | avg_decode = round(total_decode / total, 2) 171 | 172 | print(f"FAILURE #{failures} out of {total}: Columns: {columns} Security: {security_level} Scale: {scale} Ratio: {ratio} Length: {text_length} Average time to Decode: {avg_decode} seconds") 173 | 174 | # Add updated test result for this random barcode configuration back to the test results. 175 | test_results.append((columns, security_level, scale, ratio, text_length, successes, failures, total_decode)) 176 | 177 | # Save out failure image if necessary for debugging. 178 | #barcode.CrappifiedImage.save("errors/failure-" + str(failures) + "_columns-" + str(columns) + "_security-" + str(security_level) + "_ratio-" + str(ratio) + ".png") 179 | 180 | # Export test run results every 20 tests. 181 | if (test_number % 20 == 0): 182 | with open('test_results.csv', 'w') as f: 183 | 184 | # using csv.writer method from CSV package 185 | write = csv.writer(f) 186 | 187 | write.writerow(fields) 188 | write.writerows(test_results) 189 | 190 | def save_barcode_image(filename: string, encodedData: string, columns: int, security_level: int, scale: int, ratio: int): 191 | barcode = BarcodeTest(encodedData, columns, security_level, scale, ratio) 192 | barcode.CrappifiedImage.save(filename) 193 | 194 | def main(): 195 | test_image("BarcodePackage.png", "3s&H{%NKxxc\Q<&Y$Nx-^lN>3Z k.[XEXYJe,/%r5LET&|@ou5|,Cd.n%S!k9f7l^QEWxke68Q+]lKCITQ?o!=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="pdf417decoder", 8 | version="1.0.8", 9 | author="Sparkfish LLC", 10 | author_email="packages@sparkfish.com", 11 | description="A PDF417 barcode decoder", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/sparkfish/pdf417decoder", 15 | project_urls={ 16 | "Bug Tracker": "https://github.com/sparkfish/pdf417decoder/issues", 17 | }, 18 | classifiers=[ 19 | "Programming Language :: Python :: 3", 20 | "License :: Other/Proprietary License", 21 | "Operating System :: OS Independent", 22 | ], 23 | package_dir={"": "src"}, 24 | packages=setuptools.find_packages(where="src"), 25 | python_requires=">=3.7", 26 | install_requires=[ 27 | "numpy >= 1.20.1", 28 | "opencv-python >= 4.5.1.48", 29 | ], 30 | ) -------------------------------------------------------------------------------- /python/src/pdf417decoder/BarcodeArea.py: -------------------------------------------------------------------------------- 1 | from pdf417decoder.BorderPattern import BorderPattern 2 | 3 | class BarcodeArea: 4 | MAX_SYMBOL_ERROR = 0.08 5 | 6 | @property 7 | def left_center_x(self) -> int: 8 | """ Left border line of PDF 417 barcode excluding start border """ 9 | return self._left_center_x 10 | 11 | @left_center_x.setter 12 | def left_center_x(self, value: int): 13 | self._left_center_x = value 14 | 15 | @property 16 | def left_center_y(self) -> int: 17 | """ Left border line of PDF 417 barcode excluding start border """ 18 | return self._left_center_y 19 | 20 | @left_center_y.setter 21 | def left_center_y(self, value: int): 22 | self._left_center_y = value 23 | 24 | @property 25 | def left_delta_x(self) -> int: 26 | """ Left border line of PDF 417 barcode excluding start border """ 27 | return self._left_delta_x 28 | 29 | @left_delta_x.setter 30 | def left_delta_x(self, value: int): 31 | self._left_delta_x = value 32 | 33 | @property 34 | def left_delta_y(self) -> int: 35 | """ Left border line of PDF 417 barcode excluding start border """ 36 | return self._left_delta_y 37 | 38 | @left_delta_y.setter 39 | def left_delta_y(self, value: int): 40 | self._left_delta_y = value 41 | 42 | @property 43 | def right_center_x(self) -> int: 44 | """ Right border line of PDF 417 barcode excluding stop border """ 45 | return self._right_center_x 46 | 47 | @right_center_x.setter 48 | def right_center_x(self, value: int): 49 | self._right_center_x = value 50 | 51 | @property 52 | def right_center_y(self) -> int: 53 | """ Right border line of PDF 417 barcode excluding stop border """ 54 | return self._right_center_y 55 | 56 | @right_center_y.setter 57 | def right_center_y(self, value: int): 58 | self._right_center_y = value 59 | 60 | @property 61 | def right_delta_x(self) -> int: 62 | """ Right border line of PDF 417 barcode excluding stop border """ 63 | return self._right_delta_x 64 | 65 | @right_delta_x.setter 66 | def right_delta_x(self, value: int): 67 | self._right_delta_x = value 68 | 69 | @property 70 | def right_delta_y(self) -> int: 71 | """ Right border line of PDF 417 barcode excluding stop border """ 72 | return self._right_delta_y 73 | 74 | @right_delta_y.setter 75 | def right_delta_y(self, value: int): 76 | self._right_delta_y = value 77 | 78 | @property 79 | def average_symbol_width(self) -> float: 80 | """ Average symbol width of start and stop borders """ 81 | return self._average_symbol_width 82 | 83 | @average_symbol_width.setter 84 | def average_symbol_width(self, value: float): 85 | self._average_symbol_width = value 86 | 87 | @property 88 | def max_symbol_error(self) -> float: 89 | """ Max symbol error """ 90 | return self._max_symbol_error 91 | 92 | @max_symbol_error.setter 93 | def max_symbol_error(self, value: float): 94 | self._max_symbol_error = value 95 | 96 | 97 | def __init__(self, startBorder: BorderPattern, stopBorder: BorderPattern): 98 | # left border line of PDF 417 barcode excluding start border 99 | self.left_center_x = startBorder.center_x 100 | self.left_center_y = startBorder.center_y 101 | self.left_delta_x = startBorder.delta_x 102 | self.left_delta_y = startBorder.delta_y 103 | 104 | # right border line of PDF 417 barcode excluding stop border 105 | self.right_center_x = stopBorder.center_x 106 | self.right_center_y = stopBorder.center_y 107 | self.right_delta_x = stopBorder.delta_x 108 | self.right_delta_y = stopBorder.delta_y 109 | 110 | # average symbol width of start and stop borders 111 | self.average_symbol_width = 0.5 * (startBorder.average_symbol_width + stopBorder.average_symbol_width) 112 | self.max_symbol_error = self.MAX_SYMBOL_ERROR * self.average_symbol_width 113 | 114 | def left_x_func_y(self, posY: int) -> int: 115 | return int(self.left_center_x + (self.left_delta_x * (posY - self.left_center_y)) / self.left_delta_y) 116 | 117 | def right_x_func_y(self, posY: int) -> int: 118 | return int(self.right_center_x + (self.right_delta_x * (posY - self.right_center_y)) / self.right_delta_y) 119 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/BarcodeInfo.py: -------------------------------------------------------------------------------- 1 | class BarcodeInfo: 2 | """Barcode results extra information""" 3 | 4 | @property 5 | def barcode_data(self) -> bytearray: 6 | """Barcode binary (byte array) data""" 7 | return self._barcode_data 8 | 9 | @barcode_data.setter 10 | def barcode_data(self, value: bytearray): 11 | self._barcode_data = value 12 | 13 | @property 14 | def character_set(self) -> bytearray: 15 | """ 16 | Global Label Identifier character sets (ISO-8859-n) 17 | The n in ISO-8859-n represents numbers 1 to 9, 13 and 15 18 | """ 19 | return self._character_set 20 | 21 | @character_set.setter 22 | def character_set(self, value: bytearray): 23 | self._character_set = value 24 | 25 | @property 26 | def gli_character_set_number(self) -> int: 27 | """ 28 | Global Label Identifier character set number 29 | This number is two more than the part number 30 | """ 31 | return self._gli_character_set_number 32 | 33 | @gli_character_set_number.setter 34 | def gli_character_set_number(self, value: int): 35 | self._gli_character_set_number = value 36 | 37 | @property 38 | def gli_general_purpose(self) -> int: 39 | """ 40 | Global label identifier general purpose number 41 | code word 926 value 900 to 810899 42 | """ 43 | return self._gli_general_purpose 44 | 45 | @gli_general_purpose.setter 46 | def gli_general_purpose(self, value: int): 47 | self._gli_general_purpose = value 48 | 49 | @property 50 | def gli_user_defined(self) -> int: 51 | """ 52 | Global label identifier user defined number 53 | code word 925 value 810,900 to 811,799 54 | """ 55 | return self._gli_user_defined 56 | 57 | @gli_user_defined.setter 58 | def gli_user_defined(self, value: int): 59 | self._gli_user_defined = value 60 | 61 | @property 62 | def data_columns(self) -> int: 63 | """ Data columns """ 64 | return self._data_columns 65 | 66 | @data_columns.setter 67 | def data_columns(self, value: int): 68 | self._data_columns = value 69 | 70 | @property 71 | def data_rows(self) -> int: 72 | """ Data rows """ 73 | return self._data_rows 74 | 75 | @data_rows.setter 76 | def data_rows(self, value: int): 77 | self._data_rows = value 78 | 79 | @property 80 | def error_correction_length(self) -> int: 81 | """ Error correction length """ 82 | return self._error_correction_length 83 | 84 | @error_correction_length.setter 85 | def error_correction_length(self, value: int): 86 | self._error_correction_length = value 87 | 88 | @property 89 | def error_correction_count(self) -> int: 90 | """ Error correction count """ 91 | return self._error_correction_count 92 | 93 | @error_correction_count.setter 94 | def error_correction_count(self, value: int): 95 | self._error_correction_count = value 96 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/BorderPattern.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from pdf417decoder.BorderSymbol import BorderSymbol 4 | 5 | class BorderPattern: 6 | @property 7 | def center_x(self) -> int: 8 | """ """ 9 | return self._center_x 10 | 11 | @center_x.setter 12 | def center_x(self, value: int): 13 | self._center_x = value 14 | 15 | @property 16 | def center_y(self) -> int: 17 | """ """ 18 | return self._center_y 19 | 20 | @center_y.setter 21 | def center_y(self, value: int): 22 | self._center_y = value 23 | 24 | @property 25 | def delta_x(self) -> int: 26 | """ """ 27 | return self._delta_x 28 | 29 | @delta_x.setter 30 | def delta_x(self, value: int): 31 | self._delta_x = value 32 | 33 | @property 34 | def delta_y(self) -> int: 35 | """ """ 36 | return self._delta_y 37 | 38 | @delta_y.setter 39 | def delta_y(self, value: int): 40 | self._delta_y = value 41 | 42 | @property 43 | def border_length(self) -> float: 44 | """ Border line length """ 45 | return self._border_length 46 | 47 | @border_length.setter 48 | def border_length(self, value: float): 49 | self._border_length = value 50 | 51 | @property 52 | def average_symbol_width(self) -> float: 53 | """ Barcode average pattern width """ 54 | return self._average_symbol_width 55 | 56 | @average_symbol_width.setter 57 | def average_symbol_width(self, value: float): 58 | self._average_symbol_width = value 59 | 60 | def round_away_from_zero(self, x) -> int: 61 | if x >= 0.0: 62 | return int(math.floor(x + 0.5)) 63 | else: 64 | return int(math.ceil(x - 0.5)) 65 | 66 | def __init__(self, stop_pattern: bool, symbol_list: list): 67 | self.center_x = 0 68 | self.center_y = 0 69 | self.delta_x = 0 70 | self.delta_y = 0 71 | self.border_length = 0.0 72 | self.average_symbol_width = 0.0 73 | 74 | symbol_count = len(symbol_list) 75 | total_width = 0 76 | float_delta_x = 0.0 77 | float_delta_y = 0.0 78 | 79 | if stop_pattern: 80 | for symbol in symbol_list: 81 | self.center_x += symbol.x1 82 | self.center_y += symbol.y1 83 | total_width += symbol.x2 - symbol.x1 84 | 85 | self.center_x = int(self.center_x / symbol_count) 86 | self.center_y = int(self.center_y / symbol_count) 87 | 88 | for symbol in symbol_list: 89 | float_delta_x += (symbol.x1 - self.center_x) * \ 90 | (symbol.y1 - self.center_y) 91 | float_delta_y += (symbol.y1 - self.center_y) * \ 92 | (symbol.y1 - self.center_y) 93 | else: 94 | for symbol in symbol_list: 95 | self.center_x += symbol.x2 96 | self.center_y += symbol.y1 97 | total_width += symbol.x2 - symbol.x1 98 | 99 | self.center_x = int(self.center_x / symbol_count) 100 | self.center_y = int(self.center_y / symbol_count) 101 | 102 | # slope of x as func of y 103 | for symbol in symbol_list: 104 | float_delta_x += (symbol.x2 - self.center_x) * \ 105 | (symbol.y1 - self.center_y) 106 | float_delta_y += (symbol.y1 - self.center_y) * \ 107 | (symbol.y1 - self.center_y) 108 | 109 | # border line length 110 | self.border_length = math.sqrt( 111 | (float_delta_x * float_delta_x) + (float_delta_y * float_delta_y)) 112 | 113 | # calculate barcode angle of rotation relative to the image 114 | cos_rot = float_delta_y / self.border_length 115 | sin_rot = float_delta_x / self.border_length 116 | 117 | # horizontal pattern width 118 | hor_width = float(total_width) / symbol_count 119 | 120 | # barcode average pattern width 121 | self.average_symbol_width = cos_rot * hor_width 122 | 123 | # the center position is either too high or too low 124 | # if the barcode is not parallel to the image coordinates 125 | center_adj = 0.5 * sin_rot * hor_width 126 | 127 | if (stop_pattern): 128 | self.center_x += self.round_away_from_zero(center_adj * sin_rot) 129 | self.center_y += self.round_away_from_zero(center_adj * cos_rot) 130 | else: 131 | self.center_x -= self.round_away_from_zero(center_adj * sin_rot) 132 | self.center_y -= self.round_away_from_zero(center_adj * cos_rot) 133 | 134 | self.delta_y = 1000 135 | self.delta_x = int((self.delta_y * float_delta_x) / float_delta_y) 136 | 137 | # convert to ints (became float during division operations above) 138 | self.center_x = self.round_away_from_zero(self.center_x) 139 | self.center_y = self.round_away_from_zero(self.center_y) 140 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/BorderSymbol.py: -------------------------------------------------------------------------------- 1 | class BorderSymbol: 2 | def __init__(self, x1, y1, x2): 3 | self._x1 = x1 4 | self._y1 = y1 5 | self._x2 = x2 6 | 7 | @property 8 | def x1(self) -> int: 9 | """ Top Left X """ 10 | return self._x1 11 | 12 | @x1.setter 13 | def x1(self, value: int): 14 | self._x1 = value 15 | 16 | @property 17 | def y1(self) -> int: 18 | """ Top Left Y """ 19 | return self._y1 20 | 21 | @y1.setter 22 | def y1(self, value: int): 23 | self._y1 = value 24 | 25 | @property 26 | def x2(self) -> int: 27 | """ Bottom Right X """ 28 | return self._x2 29 | 30 | @x2.setter 31 | def x2(self, value: int): 32 | self._x2 = value 33 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/Decoder.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | import math 3 | import numpy as np 4 | import cv2 5 | import time 6 | from enum import Enum, auto 7 | from PIL import Image as PIL 8 | from typing import Tuple 9 | 10 | import pdf417decoder.Modulus 11 | import pdf417decoder.Polynomial 12 | import pdf417decoder.StaticTables 13 | import pdf417decoder.ErrorCorrection 14 | from pdf417decoder.BarcodeInfo import BarcodeInfo 15 | from pdf417decoder.BarcodeArea import BarcodeArea 16 | from pdf417decoder.BorderPattern import BorderPattern 17 | from pdf417decoder.BorderSymbol import BorderSymbol 18 | 19 | class EncodingMode(Enum): 20 | BYTE = auto() 21 | TEXT = auto() 22 | NUMERIC = auto() 23 | 24 | class TextEncodingMode(Enum): 25 | UPPER = auto() 26 | LOWER = auto() 27 | MIXED = auto() 28 | PUNCT = auto() 29 | SHIFT_UPPER = auto() 30 | SHIFT_PUNCT = auto() 31 | 32 | class PDF417Decoder: 33 | # Width of Symbol in Bars 34 | MODULES_IN_CODEWORD = 17 35 | 36 | # Control codewords 37 | SWITCH_TO_TEXT_MODE = 900 38 | SWITCH_TO_BYTE_MODE = 901 39 | SWITCH_TO_NUMERIC_MODE = 902 40 | SHIFT_TO_BYTE_MODE = 913 41 | SWITCH_TO_BYTE_MODE_FOR_SIX = 924 42 | START_MACRO_PDF417_CONTROL_BLOCK = 928 43 | MACRO_PDF417_OPTION = 923 44 | MACRO_PDF417_TERMINATOR = 922 45 | 46 | # User-Defined GLis: 47 | # Codeword 925 followed by one codeword 48 | # The program allows for value of 0 to 899. 49 | # The documentation is not clear one codeword 50 | # cannot be 810,900 to 811,799. 51 | # (GLI values from 810,900 to 811,799). These GLis 52 | # should be used for closed-system applications 53 | GLI_USER_DEFINED = 925 54 | 55 | # General Purpose GLis: 56 | # Codeword 926 followed by two codewords 57 | # representing GLI values from 900 to 810,899 58 | GLI_GENERAL_PURPOSE = 926 59 | 60 | # international character set 61 | # Codeword 927 followed by a single codeword 62 | # with a value ranging from O to 899. The GLI 63 | # value of 0 is the default interpretation 64 | # This value is probably ISO 8859 part number 65 | GLI_CHARACTER_SET = 927 66 | 67 | START_SIG = [9, 2, 2, 2, 2, 2] 68 | STOP_SIG = [8, 2, 4, 4, 2, 2] 69 | 70 | Y_STEP = [1, -1, 2, -2, 3, -3] 71 | 72 | @property 73 | def barcodes_info(self) -> list: 74 | """ Returned array of barcodes binary data plus extra information """ 75 | return self._barcodes_info 76 | 77 | @barcodes_info.setter 78 | def barcodes_info(self, value: list): 79 | self._barcodes_info = value 80 | 81 | def __init__(self, input_image: PIL.Image): 82 | self.input_image = input_image 83 | self.global_label_id_character_set = None 84 | self.global_label_id_character_set_number = None 85 | self.global_label_id_general_purpose = None 86 | self.global_label_id_user_defined = None 87 | self.scan_x = np.zeros((9), dtype = int) 88 | self.scan_y = np.zeros((9), dtype = int) 89 | 90 | def decode(self) -> int: 91 | """Decode PDF417 barcode image into binary array 92 | 93 | Args: 94 | input_image (Image): Barcode image bitmap 95 | 96 | Returns: 97 | int: Count of decoded barcodes or zero 98 | """ 99 | 100 | if (not self.convert_image()): 101 | return 0 102 | 103 | if (not self.locate_barcodes()): 104 | return 0 105 | 106 | # reset results list 107 | self.barcodes_extra_info_list = list() 108 | 109 | # loop for all barcodes found 110 | for barcode_area in self.barcode_list: 111 | self.barcode_area = barcode_area 112 | 113 | # reset some variables 114 | self.ind_control = 0 115 | self.data_rows = 0 116 | self.data_columns = 0 117 | self.error_correction_length = 0 118 | self.error_correction_count = 0 119 | self.barcode_binary_data = None 120 | self.barcodes_data = None 121 | self.barcodes_info = None 122 | self.macro_segment = None 123 | self.macro_file_id = None 124 | self.macro_file_name = None 125 | self.macro_is_last = None 126 | self.macro_segment_count = None 127 | 128 | self.average_symbol_width = barcode_area.average_symbol_width 129 | self.max_symbol_error = barcode_area.max_symbol_error 130 | 131 | if (not self.left_indicators()): 132 | continue 133 | 134 | if (not self.right_indicators()): 135 | continue 136 | 137 | if (not self.set_trans_matrix()): 138 | continue 139 | 140 | if (not self.get_codewords()): 141 | continue 142 | 143 | if (not self.codewords_to_data()): # convert codewords to bytes and text 144 | continue 145 | 146 | result = BarcodeInfo() 147 | result.barcode_data = self.barcode_binary_data 148 | result.character_set = self.global_label_id_character_set 149 | result.gli_character_set_number = self.global_label_id_character_set_number 150 | result.gli_general_purpose = self.global_label_id_general_purpose 151 | result.gli_user_defined = self.global_label_id_user_defined 152 | result.data_columns = self.data_columns 153 | result.data_rows = self.data_rows 154 | result.error_correction_length = self.error_correction_length 155 | result.error_correction_count = self.error_correction_count 156 | result.macro_segment = self.macro_segment 157 | result.macro_file_id = self.macro_file_id 158 | result.macro_file_name = self.macro_file_name 159 | result.macro_is_last = self.macro_is_last 160 | result.macro_segment_count = self.macro_segment_count 161 | self.barcodes_extra_info_list.append(result) 162 | 163 | barcodes_count = len(self.barcodes_extra_info_list) 164 | 165 | if (barcodes_count == 0): 166 | return 0 167 | 168 | self.barcodes_info = self.barcodes_extra_info_list 169 | 170 | self.barcodes_data = list() 171 | 172 | for i in range(barcodes_count): 173 | self.barcodes_data.append(self.barcodes_info[i].barcode_data) 174 | 175 | return barcodes_count 176 | 177 | @staticmethod 178 | def assemble_data(info_list): 179 | """ 180 | Assembles data from a list of barcode information objects. 181 | This assumes the info_list represents a single file, though the 182 | segments may be in any order. 183 | 184 | Args: 185 | info_list (list): A list of barcode information objects. 186 | 187 | Returns: 188 | bytes: The assembled data as a bytearray. 189 | 190 | Raises: 191 | ValueError: If there are inconsistencies in file ID, segment count, or duplicate segments. 192 | """ 193 | data = bytearray() 194 | file_id = None 195 | file_name = None 196 | segment_count = None 197 | if not info_list: 198 | return data # Return empty bytearray if info_list is empty 199 | segments = [None] * len(info_list) 200 | 201 | for info in info_list: 202 | if info.macro_file_id: 203 | if file_id is None: 204 | file_id = info.macro_file_id 205 | elif file_id != info.macro_file_id: 206 | raise ValueError(f"File ID mismatch: {file_id} != {info.macro_file_id}") 207 | if info.macro_file_name: 208 | if file_name is None: 209 | file_name = info.macro_file_name 210 | 211 | if info.macro_segment_count: 212 | if segment_count is None: 213 | segment_count = info.macro_segment_count 214 | if segment_count != len(segments): 215 | raise ValueError(f"Segment count mismatch: {segment_count} != {len(segments)}") 216 | elif segment_count != info.macro_segment_count: 217 | raise ValueError(f"Segment count mismatch: {segment_count} != {info.macro_segment_count}") 218 | if info.macro_segment is None: 219 | index = 0 220 | else: 221 | index = info.macro_segment 222 | if index == len(segments) -1 and not info.macro_is_last: 223 | # Not actually needed for decoding, but for now this helps validate encoders 224 | raise ValueError("Macro PDF417 missing terminator") 225 | 226 | 227 | if segments[index] is not None: 228 | raise ValueError(f"Duplicate segment: {index}") 229 | 230 | segments[index] = info.barcode_data 231 | 232 | 233 | for segment in segments: 234 | if segment is None: 235 | raise ValueError("Missing segment in barcode data.") 236 | data.extend(segment) 237 | return data 238 | 239 | 240 | def barcode_data_index_to_string(self, index: int) -> str: 241 | """Convert binary data to string for one result""" 242 | 243 | if (self.barcodes_info[index].character_set != None): 244 | return self.binary_data_to_string(self.barcodes_info[index].barcode_data, self.barcodes_info[index].character_set) 245 | 246 | return self.binary_data_to_string(self.barcodes_info[index].barcode_data) 247 | 248 | def binary_data_to_string(self, barcode_binary_data: bytearray, iso_standard: str = "ISO-8859-1") -> str: 249 | """Convert binary data array to text string 250 | 251 | Args: 252 | barcode_binary_data (bytearray): Binary byte array 253 | iso_standard (str, optional): ISO standard "ISO-8859-part". Defaults to "ISO-8859-1". 254 | 255 | Returns: 256 | str: Text string of binary data 257 | """ 258 | # convert byte array to string 259 | binary = barcode_binary_data 260 | decoded = barcode_binary_data.decode(iso_standard) 261 | return decoded 262 | 263 | def locate_barcodes(self) -> bool: 264 | self.barcode_list = list() 265 | 266 | start_symbols = list() 267 | stop_symbols = list() 268 | 269 | scan = 0 270 | 271 | while (True): 272 | self.bar_pos = list([0] * self.image_width) 273 | for row in range(self.image_height): 274 | # scan the line for array of bars 275 | if (not self.scan_line(row)): 276 | continue 277 | 278 | #look for start signature 279 | self.border_signature(start_symbols, self.START_SIG, row) 280 | self.border_signature(stop_symbols, self.STOP_SIG, row) 281 | 282 | remove_symbols = list() 283 | # remove all lists with less than 18 symbols 284 | for index in range(len(start_symbols)): 285 | if (len(start_symbols[index]) < 18): 286 | remove_symbols.append(start_symbols[index]) 287 | 288 | for remove in remove_symbols: 289 | start_symbols.remove(remove) 290 | 291 | remove_symbols = list() 292 | # remove all lists with less than 18 symbols 293 | for index in range(len(stop_symbols)): 294 | if (len(stop_symbols[index]) < 18): 295 | remove_symbols.append(stop_symbols[index]) 296 | 297 | for remove in remove_symbols: 298 | stop_symbols.remove(remove) 299 | 300 | # match start and stop patterns 301 | if (len(start_symbols) != 0 and len(stop_symbols) != 0): 302 | for start_list in start_symbols: 303 | for stop_list in stop_symbols: 304 | self.match_start_and_stop(start_list, stop_list) 305 | 306 | if (len(self.barcode_list) > 0 or scan == 4): 307 | break 308 | 309 | # rotate image by 90 degrees and try again 310 | self.image_matrix = np.rot90(self.image_matrix) 311 | self.image_width, self.image_height = self.image_height, self.image_width 312 | start_symbols.clear() 313 | stop_symbols.clear() 314 | self.barcode_list.clear() 315 | 316 | scan += 1 317 | 318 | return len(self.barcode_list) > 0 319 | 320 | def scan_line(self, row: int) -> bool: 321 | """Convert image line to black and white bars""" 322 | 323 | row_data = self.image_matrix[row] 324 | d = np.diff(row_data) != 0 325 | flatnonzero_d = np.flatnonzero(d) + 1 326 | idx = np.concatenate(([0], flatnonzero_d)) 327 | c = np.diff(np.concatenate((idx, [len(row_data)]))) 328 | bars = list(zip(idx, row_data[idx], c)) 329 | 330 | self.bar_end = 0 331 | for bar in bars: 332 | self.bar_pos[self.bar_end] = bar[0] + bar[2] 333 | self.bar_end += 1 334 | 335 | return self.bar_end > 8 336 | 337 | def border_signature(self, border_symbols: list, signature: list, row: int): 338 | # search for start or stop signature 339 | bar_ptr_end = self.bar_end - 8 340 | 341 | for bar_ptr in range(0, bar_ptr_end, 2): 342 | # width of 8 bars 343 | width = self.bar_pos[bar_ptr + 8] - self.bar_pos[bar_ptr] 344 | 345 | # test for signature 346 | index = 0 347 | for i in range(6): 348 | index = i 349 | calc = (34 * (self.bar_pos[bar_ptr + index + 2] - self.bar_pos[bar_ptr + index]) + width) / (2 * width) 350 | calc_int = int(calc) 351 | if (calc_int != signature[index]): 352 | break 353 | index += 1 354 | 355 | # no start or stop signature 356 | if (index < 6): 357 | continue 358 | 359 | new_symbol = BorderSymbol(self.bar_pos[bar_ptr], row, self.bar_pos[bar_ptr + 8]) 360 | 361 | if (len(border_symbols) == 0): 362 | new_symbol_list = list([new_symbol]) 363 | border_symbols.append(new_symbol_list) 364 | else: 365 | # try to match it to one of the existing lists 366 | for symbols in border_symbols: 367 | # compare to last symbol 368 | last_symbol = symbols[len(symbols) - 1] 369 | 370 | # not part of current list 371 | if (row - last_symbol.y1 >= 18 or abs(new_symbol.x1 - last_symbol.x1) >= 5 or abs(new_symbol.x2 - last_symbol.x2) >= 5): 372 | continue 373 | 374 | # add to current list 375 | symbols.append(new_symbol) 376 | new_symbol = None 377 | break 378 | 379 | # start a new list 380 | if (new_symbol is not None): 381 | new_symbol_list = list([new_symbol]) 382 | border_symbols.append(new_symbol_list) 383 | 384 | # continue search after start signature 385 | bar_ptr += 6; 386 | 387 | def match_start_and_stop(self, start_list: list, stop_list: list) -> bool: 388 | # calculate start and stop patterns relative to image coordinates 389 | start_border = BorderPattern(False, start_list) 390 | stop_border = BorderPattern(True, stop_list) 391 | 392 | # borders slopes must be less than 45 deg 393 | if (start_border.delta_y <= abs(start_border.delta_x) or stop_border.delta_y <= abs(stop_border.delta_x)): 394 | return False 395 | 396 | # stop must be to the right of start 397 | if (stop_border.center_x <= start_border.center_x): 398 | return False 399 | 400 | # center line 401 | center_delta_x = stop_border.center_x - start_border.center_x 402 | center_delta_y = stop_border.center_y - start_border.center_y 403 | center_length = sqrt(center_delta_x * center_delta_x + center_delta_y * center_delta_y) 404 | 405 | # angle bewteen start line and center line must be about 84 to 96 406 | cos = (start_border.delta_x * center_delta_x + start_border.delta_y * center_delta_y) / (center_length * start_border.border_length) 407 | if (abs(cos) > 0.1): 408 | return False 409 | 410 | # angle bewteen start line and center line must be about 85 to 95 411 | cos = (stop_border.delta_x * center_delta_x + stop_border.delta_y * center_delta_y) / (center_length * stop_border.border_length) 412 | if (abs(cos) > 0.1): 413 | return False 414 | 415 | # add to the list 416 | self.barcode_list.append(BarcodeArea(start_border, stop_border)); 417 | return True 418 | 419 | def left_indicators(self) -> bool: 420 | # get mid column codeword 421 | pos_x = self.barcode_area.left_center_x 422 | pos_y = self.barcode_area.left_center_y 423 | mid_codeword = self.get_codeword(pos_x, pos_y, self.barcode_area.left_delta_y, -self.barcode_area.left_delta_x) 424 | last_codeword = mid_codeword 425 | top_codeword = -1 426 | bottom_codeword = -1 427 | 428 | # move up from center 429 | error_count = 0 430 | pos_y -= 1 431 | for pos_y in range(pos_y, 0, -1): 432 | pos_x = self.barcode_area.left_x_func_y(pos_y) 433 | # get cluster plus codeword 434 | codeword = self.get_codeword(pos_x, pos_y, self.barcode_area.left_delta_y, -self.barcode_area.left_delta_x) 435 | 436 | # valid codeword 437 | if (codeword >= 0): 438 | if (codeword == last_codeword): 439 | if (self.ind_control != 7): 440 | self.set_info(codeword) 441 | 442 | #save position 443 | self.top_left_x = self.scan_x[0] 444 | self.top_left_y = self.scan_y[0] 445 | top_codeword = codeword 446 | else: 447 | last_codeword = codeword 448 | 449 | error_count = 0 450 | continue 451 | 452 | # error 453 | error_count += 1 454 | if (error_count > 20): 455 | break 456 | 457 | # move down from center 458 | pos_x = self.barcode_area.left_center_x 459 | pos_y = self.barcode_area.left_center_y 460 | last_codeword = mid_codeword 461 | error_count = 0 462 | 463 | pos_y += 1 464 | for pos_y in range(pos_y, self.image_height): 465 | # get cluster plus codeword 466 | pos_x = self.barcode_area.left_x_func_y(pos_y) 467 | codeword = self.get_codeword(pos_x, pos_y, self.barcode_area.left_delta_y, -self.barcode_area.left_delta_x) 468 | 469 | # valid codeword 470 | if (codeword >= 0): 471 | if (codeword == last_codeword): 472 | if (self.ind_control != 7): 473 | self.set_info(codeword) 474 | 475 | #save position 476 | self.bottom_left_x = self.scan_x[0] 477 | self.bottom_left_y = self.scan_y[0] 478 | bottom_codeword = codeword 479 | else: 480 | last_codeword = codeword 481 | 482 | error_count = 0 483 | continue 484 | 485 | # error 486 | error_count += 1 487 | if (error_count > 20): 488 | break 489 | 490 | if (top_codeword < 0 or bottom_codeword < 0): 491 | return False 492 | 493 | cluster = top_codeword >> 10 494 | self.top_left_row = 3 * int((top_codeword & 0x3ff) / 30) + cluster 495 | self.top_left_col = -1 496 | 497 | cluster = bottom_codeword >> 10 498 | self.bottom_left_row = 3 * int((bottom_codeword & 0x3ff) / 30) + cluster 499 | self.bottom_left_col = -1 500 | 501 | return True 502 | 503 | def right_indicators(self) -> bool: 504 | # get mid column codeword 505 | pos_x = self.barcode_area.right_center_x 506 | pos_y = self.barcode_area.right_center_y 507 | mid_codeword = self.rev_get_codeword(pos_x, pos_y, self.barcode_area.right_delta_y, -self.barcode_area.right_delta_x) 508 | last_codeword = mid_codeword 509 | top_codeword = -1 510 | bottom_codeword = -1 511 | 512 | # move up from center 513 | error_count = 0 514 | for pos_y in range(pos_y, 0, -1): 515 | pos_x = self.barcode_area.right_x_func_y(pos_y) 516 | # get cluster plus codeword 517 | codeword = self.rev_get_codeword(pos_x, pos_y, self.barcode_area.right_delta_y, -self.barcode_area.right_delta_x) 518 | 519 | # valid codeword 520 | if (codeword >= 0): 521 | if (codeword == last_codeword): 522 | if (self.ind_control != 7): 523 | self.set_info(codeword) 524 | 525 | #save position 526 | self.top_right_x = self.scan_x[0] 527 | self.top_right_y = self.scan_y[0] 528 | top_codeword = codeword 529 | else: 530 | last_codeword = codeword 531 | 532 | error_count = 0 533 | continue 534 | 535 | # error 536 | error_count += 1 537 | if (error_count > 20): 538 | break 539 | 540 | # move down from center 541 | pos_x = self.barcode_area.right_center_x 542 | pos_y = self.barcode_area.right_center_y 543 | last_codeword = mid_codeword 544 | error_count = 0 545 | 546 | pos_y += 1 547 | for pos_y in range(pos_y, self.image_height): 548 | # get cluster plus codeword 549 | pos_x = self.barcode_area.right_x_func_y(pos_y) 550 | codeword = self.rev_get_codeword(pos_x, pos_y, self.barcode_area.right_delta_y, -self.barcode_area.right_delta_x) 551 | 552 | # valid codeword 553 | if (codeword >= 0): 554 | if (codeword == last_codeword): 555 | if (self.ind_control != 7): 556 | self.set_info(codeword) 557 | 558 | #save position 559 | self.bottom_right_x = self.scan_x[0] 560 | self.bottom_right_y = self.scan_y[0] 561 | bottom_codeword = codeword 562 | else: 563 | last_codeword = codeword 564 | 565 | error_count = 0 566 | continue 567 | 568 | # error 569 | error_count += 1 570 | if (error_count > 20): 571 | break 572 | 573 | if (self.ind_control != 7 or top_codeword < 0 or bottom_codeword < 0): 574 | return False 575 | 576 | cluster = top_codeword >> 10 577 | self.top_right_row = 3 * int((top_codeword & 0x3ff) / 30) + cluster 578 | self.top_right_col = self.data_columns 579 | 580 | cluster = bottom_codeword >> 10 581 | self.bottom_right_row = 3 * int((bottom_codeword & 0x3ff) / 30) + cluster 582 | self.bottom_right_col = self.data_columns 583 | 584 | return True 585 | 586 | def set_info(self, codeword: int): 587 | cluster = codeword >> 10 588 | info = (codeword & 0x3ff) % 30 589 | 590 | if (cluster == 0): 591 | if ((self.ind_control & 1) == 0): 592 | self.data_rows += info * 3 + 1 593 | self.ind_control |= 1 594 | 595 | elif (cluster == 1): 596 | if ((self.ind_control & 2) == 0): 597 | data_rows_extra = info % 3 598 | self.error_correction_length = 1 << int((info / 3 + 1)) 599 | self.data_rows += data_rows_extra 600 | self.ind_control |= 2 601 | elif (cluster == 2): 602 | if ((self.ind_control & 4) == 0): 603 | self.data_columns = info + 1 604 | self.ind_control |= 4 605 | 606 | def set_trans_matrix(self) -> bool: 607 | matrix = np.zeros((8, 9), dtype = float) 608 | 609 | matrix[0, 0] = self.top_left_col 610 | matrix[0, 1] = self.top_left_row 611 | matrix[0, 2] = 1.0 612 | matrix[0, 6] = -self.top_left_col * self.top_left_x 613 | matrix[0, 7] = -self.top_left_row * self.top_left_x 614 | matrix[0, 8] = self.top_left_x 615 | 616 | matrix[1, 0] = self.top_right_col 617 | matrix[1, 1] = self.top_right_row 618 | matrix[1, 2] = 1.0 619 | matrix[1, 6] = -self.top_right_col * self.top_right_x 620 | matrix[1, 7] = -self.top_right_row * self.top_right_x 621 | matrix[1, 8] = self.top_right_x 622 | 623 | matrix[2, 0] = self.bottom_left_col 624 | matrix[2, 1] = self.bottom_left_row 625 | matrix[2, 2] = 1.0 626 | matrix[2, 6] = -self.bottom_left_col * self.bottom_left_x 627 | matrix[2, 7] = -self.bottom_left_row * self.bottom_left_x 628 | matrix[2, 8] = self.bottom_left_x 629 | 630 | matrix[3, 0] = self.bottom_right_col 631 | matrix[3, 1] = self.bottom_right_row 632 | matrix[3, 2] = 1.0 633 | matrix[3, 6] = -self.bottom_right_col * self.bottom_right_x 634 | matrix[3, 7] = -self.bottom_right_row * self.bottom_right_x 635 | matrix[3, 8] = self.bottom_right_x 636 | 637 | matrix[4, 3] = self.top_left_col 638 | matrix[4, 4] = self.top_left_row 639 | matrix[4, 5] = 1.0 640 | matrix[4, 6] = -self.top_left_col * self.top_left_y 641 | matrix[4, 7] = -self.top_left_row * self.top_left_y 642 | matrix[4, 8] = self.top_left_y 643 | 644 | matrix[5, 3] = self.top_right_col 645 | matrix[5, 4] = self.top_right_row 646 | matrix[5, 5] = 1.0 647 | matrix[5, 6] = -self.top_right_col * self.top_right_y 648 | matrix[5, 7] = -self.top_right_row * self.top_right_y 649 | matrix[5, 8] = self.top_right_y 650 | 651 | matrix[6, 3] = self.bottom_left_col 652 | matrix[6, 4] = self.bottom_left_row 653 | matrix[6, 5] = 1.0 654 | matrix[6, 6] = -self.bottom_left_col * self.bottom_left_y 655 | matrix[6, 7] = -self.bottom_left_row * self.bottom_left_y 656 | matrix[6, 8] = self.bottom_left_y 657 | 658 | matrix[7, 3] = self.bottom_right_col 659 | matrix[7, 4] = self.bottom_right_row 660 | matrix[7, 5] = 1.0 661 | matrix[7, 6] = -self.bottom_right_col * self.bottom_right_y 662 | matrix[7, 7] = -self.bottom_right_row * self.bottom_right_y 663 | matrix[7, 8] = self.bottom_right_y 664 | 665 | for row in range(8): 666 | row 667 | # If the element is zero, make it non zero by adding another row 668 | if (matrix[row, row] == 0): 669 | for row1 in range(row + 1, 8): 670 | if (matrix[row1, row] != 0): 671 | break 672 | 673 | if (row1 == 8): 674 | return False 675 | 676 | for col in range(row, 9): 677 | matrix[row, col] += matrix[row1, col] 678 | 679 | #make the diagonal element 1.0 680 | for col in range(8, row, -1): 681 | m1 = matrix[row, col] 682 | m2 = matrix[row, row] 683 | m3 = m1 / m2 684 | matrix[row, col] = m3 685 | 686 | # subtract current row from next rows to eliminate one value 687 | for row1 in range(row + 1, 8): 688 | for col in range(8, row, -1): 689 | m1 = matrix[row, col] 690 | m2 = matrix[row1, row] 691 | m3 = m1 * m2 692 | matrix[row1, col] -= m3 693 | 694 | # go up from last row and eliminate all solved values 695 | for col in range(7, 0, -1): 696 | for row in range(col - 1, -1, -1): 697 | m1 = matrix[row, col] 698 | m2 = matrix[col, 8] 699 | m3 = m1 * m2 700 | matrix[row, 8] -= m3 701 | 702 | # save transformation matrix coefficients 703 | self.trans4a = matrix[0, 8]; 704 | self.trans4b = matrix[1, 8]; 705 | self.trans4c = matrix[2, 8]; 706 | self.trans4d = matrix[3, 8]; 707 | self.trans4e = matrix[4, 8]; 708 | self.trans4f = matrix[5, 8]; 709 | self.trans4g = matrix[6, 8]; 710 | self.trans4h = matrix[7, 8]; 711 | 712 | return True 713 | 714 | def get_codewords(self) -> bool: 715 | try: 716 | # codewords array 717 | self.codewords = list([0] * (self.data_columns * self.data_rows)) 718 | cwptr = 0 719 | 720 | erasures_count = 0 721 | 722 | for barcode_y in range(self.data_rows): 723 | for barcode_x in range(self.data_columns): 724 | codeword = self.data_codeword(barcode_x, barcode_y) 725 | 726 | if (codeword < 0): 727 | self.codewords[cwptr] = 0 728 | cwptr += 1 729 | erasures_count += 1 730 | if (erasures_count > self.error_correction_length / 2): 731 | return False 732 | else: 733 | self.codewords[cwptr] = codeword 734 | cwptr += 1 735 | 736 | test_result = pdf417decoder.ErrorCorrection.test_codewords(self.codewords, self.error_correction_length) 737 | error_correction_count = test_result[0] 738 | 739 | # Too many errors decode failed 740 | if (error_correction_count < 0): 741 | return False 742 | 743 | self.codewords = test_result[1] 744 | 745 | return True 746 | except: 747 | return False 748 | 749 | def round_away_from_zero(self, x) -> int: 750 | if x >= 0.0: 751 | return int(math.floor(x + 0.5)) 752 | else: 753 | return int(math.ceil(x - 0.5)) 754 | 755 | def data_codeword(self, data_matrix_x: int, data_matrix_y: int) -> int: 756 | w = self.trans4g * data_matrix_x + self.trans4h * data_matrix_y + 1.0 757 | orig_x = self.round_away_from_zero((self.trans4a * data_matrix_x + self.trans4b * data_matrix_y + self.trans4c) / w) 758 | orig_y = self.round_away_from_zero((self.trans4d * data_matrix_x + self.trans4e * data_matrix_y + self.trans4f) / w) 759 | 760 | data_matrix_x += 1 761 | w = self.trans4g * data_matrix_x + self.trans4h * data_matrix_y + 1.0 762 | delta_x = self.round_away_from_zero((self.trans4a * data_matrix_x + self.trans4b * data_matrix_y + self.trans4c) / w) - orig_x 763 | delta_y = self.round_away_from_zero((self.trans4d * data_matrix_x + self.trans4e * data_matrix_y + self.trans4f) / w) - orig_y 764 | 765 | codeword = self.get_codeword(orig_x, orig_y, delta_x, delta_y) 766 | 767 | if (codeword >= 0 and codeword >> 10 == data_matrix_y % 3): 768 | return codeword & 0x3ff 769 | 770 | # try to fix the problem 771 | for index in range(len(self.Y_STEP)): 772 | y = orig_y + self.Y_STEP[index] 773 | x = orig_x - int((y - orig_y) * delta_y / delta_x) 774 | codeword = self.get_codeword(x, y, delta_x, delta_y) 775 | 776 | if (codeword >= 0 and codeword >> 10 == data_matrix_y % 3): 777 | return codeword & 0x3ff 778 | 779 | # error return 780 | return -1; 781 | 782 | def codewords_to_text(self, binary_data: bytearray, seg_len: int): 783 | """Convert codewords to text""" 784 | text_len = 2 * seg_len 785 | code = 0 786 | next = 0 787 | save_mode = TextEncodingMode.UPPER 788 | ascii_char = 0 789 | 790 | for i in range(text_len): 791 | if ((i & 1) == 0): 792 | codeword = self.codewords[self.codewords_ptr] 793 | self.codewords_ptr += 1 794 | code = int(codeword / 30) 795 | next = codeword % 30 796 | else: 797 | code = next 798 | if (code == 29 and i == text_len - 1): 799 | break 800 | 801 | #switch 802 | 803 | loop = True 804 | 805 | # While loop is a hack to allow breaking out of the if. 806 | while (loop): 807 | loop = False 808 | if (self._text_encoding_mode == TextEncodingMode.UPPER): 809 | ascii_char = pdf417decoder.StaticTables.UPPER_TO_TEXT[code] 810 | if (ascii_char != 0): 811 | binary_data += ascii_char.to_bytes(1, "little") 812 | break 813 | 814 | if (code == 27): 815 | self._text_encoding_mode = TextEncodingMode.LOWER 816 | elif (code == 28): 817 | self._text_encoding_mode = TextEncodingMode.MIXED 818 | else: 819 | save_mode = self._text_encoding_mode 820 | self._text_encoding_mode = TextEncodingMode.SHIFT_PUNCT 821 | elif (self._text_encoding_mode == TextEncodingMode.LOWER): 822 | ascii_char = pdf417decoder.StaticTables.LOWER_TO_TEXT[code] 823 | if (ascii_char != 0): 824 | binary_data += ascii_char.to_bytes(1, "little") 825 | break 826 | 827 | if (code == 27): 828 | self._text_encoding_mode = TextEncodingMode.SHIFT_UPPER 829 | elif (code == 28): 830 | self._text_encoding_mode = TextEncodingMode.MIXED 831 | else: 832 | save_mode = self._text_encoding_mode 833 | self._text_encoding_mode = TextEncodingMode.SHIFT_PUNCT 834 | elif (self._text_encoding_mode == TextEncodingMode.MIXED): 835 | ascii_char = pdf417decoder.StaticTables.MIXED_TO_TEXT[code] 836 | if (ascii_char != 0): 837 | binary_data += ascii_char.to_bytes(1, "little") 838 | break 839 | 840 | if (code == 25): 841 | self._text_encoding_mode = TextEncodingMode.PUNCT 842 | elif (code == 27): 843 | self._text_encoding_mode = TextEncodingMode.LOWER 844 | elif (code == 28): 845 | self._text_encoding_mode = TextEncodingMode.UPPER 846 | else: 847 | save_mode = self._text_encoding_mode 848 | self._text_encoding_mode = TextEncodingMode.SHIFT_PUNCT 849 | elif (self._text_encoding_mode == TextEncodingMode.PUNCT): 850 | ascii_char = pdf417decoder.StaticTables.PUNCT_TO_TEXT[code] 851 | if (ascii_char != 0): 852 | binary_data += ascii_char.to_bytes(1, "little") 853 | break 854 | 855 | self._text_encoding_mode = TextEncodingMode.UPPER 856 | elif (self._text_encoding_mode == TextEncodingMode.SHIFT_UPPER): 857 | self._text_encoding_mode = TextEncodingMode.LOWER 858 | ascii_char = pdf417decoder.StaticTables.UPPER_TO_TEXT[code] 859 | if (ascii_char != 0): 860 | binary_data += ascii_char.to_bytes(1, "little") 861 | break 862 | elif (self._text_encoding_mode == TextEncodingMode.SHIFT_PUNCT): 863 | self._text_encoding_mode = save_mode 864 | ascii_char = pdf417decoder.StaticTables.PUNCT_TO_TEXT[code] 865 | if (ascii_char != 0): 866 | binary_data += ascii_char.to_bytes(1, "little") 867 | break 868 | 869 | def get_codeword(self, left_x: int, left_y: int, delta_x: int, delta_y: int): 870 | # make sure we are on a white to black transition 871 | result = self.white_to_black_transition(left_x, left_y, delta_x, delta_y) 872 | left_x = result[0] 873 | left_y = result[1] 874 | 875 | if (left_x == -1 and left_y == -1): 876 | return -2 877 | 878 | # go right looking for color transition 879 | self.scan_x[0] = left_x 880 | self.scan_y[0] = left_y 881 | 882 | dot_color = True 883 | t = 1 884 | x = left_x + 1 885 | 886 | while (True): 887 | if (t >= 9): 888 | break 889 | 890 | y = left_y + int((x - left_x) * delta_y / delta_x) 891 | 892 | if (y >= len(self.image_matrix) or x >= len(self.image_matrix[0])): 893 | return -2 894 | 895 | if (self.image_matrix[y, x] == dot_color): 896 | x += 1 897 | continue 898 | 899 | dot_color = not dot_color 900 | self.scan_x[t] = x 901 | self.scan_y[t] = y 902 | 903 | t += 1 904 | x += 1 905 | 906 | return self.scan_to_codeword() 907 | 908 | def rev_get_codeword(self, right_x: int, right_y: int, delta_x: int, delta_y: int) -> int: 909 | # make sure we are on a white to black transition 910 | result = self.white_to_black_transition(right_x, right_y, delta_x, delta_y) 911 | right_x = result[0] 912 | right_y = result[1] 913 | 914 | if (right_x == -1 and right_y == -1): 915 | return -1 916 | 917 | # go left looking for color transition 918 | self.scan_x[8] = right_x 919 | self.scan_y[8] = right_y 920 | 921 | dot_color = False 922 | t = 7 923 | x = right_x - 1 924 | 925 | while (True): 926 | y = right_y + int((x - right_x) * delta_y / delta_x) 927 | 928 | if (abs(y) >= len(self.image_matrix) or abs(x) >= len(self.image_matrix[0])): 929 | return -2 930 | 931 | if (self.image_matrix[y, x] == dot_color): 932 | x -= 1 933 | continue 934 | 935 | dot_color = not dot_color 936 | self.scan_x[t] = x 937 | self.scan_y[t] = y 938 | 939 | t -= 1 940 | x -= 1 941 | 942 | if (t < 0): 943 | break 944 | 945 | return self.scan_to_codeword() 946 | 947 | def white_to_black_transition(self, pos_x: int, pos_y: int, delta_x: int, delta_y: int) -> Tuple[int, int]: 948 | try: 949 | # current pixel is black 950 | if (self.image_matrix[pos_y, pos_x]): 951 | # pixel on the left is white 952 | if (not self.image_matrix[pos_y, pos_x - 1]): 953 | return (pos_x, pos_y) 954 | 955 | # go left to find first white pixel 956 | x = pos_x - 1 957 | while (True): 958 | # matching y coordinate 959 | y = pos_y + int((x - pos_x) * delta_y / delta_x) 960 | 961 | if (abs(y) >= len(self.image_matrix) or abs(x) >= len(self.image_matrix[0])): 962 | return (-1, -1) 963 | 964 | # pixel is white 965 | if (not self.image_matrix[y, x]): 966 | return (pos_x, pos_y) 967 | 968 | # move current pixel one to the left 969 | pos_x = x 970 | pos_y = y 971 | x -= 1 972 | 973 | # current pixel is white 974 | # go right to the next transition from white to black 975 | x = pos_x + 1 976 | while (True): 977 | # matching y coordinate 978 | y = pos_y + int((x - pos_x) * delta_y / delta_x) 979 | 980 | 981 | if (abs(y) >= len(self.image_matrix) or abs(x) >= len(self.image_matrix[0])): 982 | return (-1, -1) 983 | 984 | # pixel is white 985 | if (self.image_matrix.shape[0] <= y or self.image_matrix.shape[1] <= x): 986 | return (-1, -1) 987 | 988 | if (not self.image_matrix[y, x]): 989 | x += 1 990 | continue 991 | 992 | # return black point 993 | pos_x = x 994 | pos_y = y 995 | return (pos_x, pos_y) 996 | except: 997 | return (-1, -1) 998 | 999 | def scan_to_codeword(self) -> int: 1000 | # line slope 1001 | scan_delta_x = self.scan_x[8] - self.scan_x[0] 1002 | scan_delta_y = self.scan_y[8] - self.scan_y[0] 1003 | 1004 | # line length 1005 | length = sqrt(scan_delta_x * scan_delta_x + scan_delta_y * scan_delta_y) 1006 | 1007 | if (abs(length - self.average_symbol_width) > self.max_symbol_error): 1008 | return -1 1009 | 1010 | # one over one bar width 1011 | inv_width = self.MODULES_IN_CODEWORD / length 1012 | 1013 | symbol = 0 1014 | mode = 9 1015 | 1016 | # loop for two bars 1017 | for bar_index in range(6): 1018 | bdx = self.scan_x[bar_index + 2] - self.scan_x[bar_index] 1019 | bdy = self.scan_y[bar_index + 2] - self.scan_y[bar_index] 1020 | 1021 | # two bars width must be 2 to 9 1022 | two_bars = self.round_away_from_zero(inv_width * sqrt(bdx * bdx + bdy * bdy)) 1023 | 1024 | if (two_bars < 2 or two_bars > 9): 1025 | return -1 1026 | 1027 | # accumulate symbol 1028 | # symbol is made of 6 two bars width 1029 | # we subtract 2 to make the range of 0 to 7 (3 bits) 1030 | # we pack 6 two bar width into 18 bits 1031 | symbol |= (two_bars - 2) << 3 * (5 - bar_index) 1032 | 1033 | if (bar_index == 0 or bar_index == 4): 1034 | mode += two_bars 1035 | elif (bar_index == 1 or bar_index == 5): 1036 | mode -= two_bars 1037 | 1038 | # test mode 1039 | mode = mode % 9 1040 | 1041 | if (mode != 0 and mode != 3 and mode != 6): 1042 | return -1 1043 | 1044 | # translate symbol to cluster plus codeword 1045 | symbol_table = pdf417decoder.StaticTables.SYMBOL_TABLE 1046 | symbol_found = self.find_symbol(symbol_table, symbol << 12); 1047 | 1048 | # symbol not found 1049 | if (symbol_found < 0): 1050 | return -1; 1051 | 1052 | # symbol found 1053 | return symbol_found & 0xfff 1054 | 1055 | def find_symbol(self, array, element): 1056 | for symbol in array: 1057 | if ((symbol & 0x7ffff000) == element): 1058 | return symbol 1059 | 1060 | return -1 1061 | 1062 | def codewords_to_data(self) -> bool: 1063 | """Convert codewords to data""" 1064 | # data codewords pointer and end 1065 | self.codewords_ptr = 1; 1066 | codewords_end = self.codewords[0] 1067 | 1068 | # make sure data length make sense 1069 | if (codewords_end + self.error_correction_length != self.data_columns * self.data_rows): 1070 | return False 1071 | 1072 | # initialize encoding modes 1073 | self._encoding_mode = EncodingMode.TEXT; 1074 | self._text_encoding_mode = TextEncodingMode.UPPER; 1075 | 1076 | # binary data result 1077 | binary_data = bytearray() 1078 | 1079 | while (self.codewords_ptr < codewords_end): 1080 | # load codeword at current pointer 1081 | command = self.codewords[self.codewords_ptr] 1082 | self.codewords_ptr += 1 1083 | 1084 | 1085 | # for the first time this codeword can be data 1086 | if (command < 900): 1087 | command = self.SWITCH_TO_TEXT_MODE 1088 | self.codewords_ptr -= 1 1089 | 1090 | # count codewords data 1091 | seg_end = self.codewords_ptr 1092 | while (seg_end < codewords_end and self.codewords[seg_end] < 900): 1093 | seg_end += 1 1094 | 1095 | seg_len = seg_end - self.codewords_ptr 1096 | 1097 | if (seg_len == 0): 1098 | if (command == self.MACRO_PDF417_TERMINATOR): 1099 | self.macro_is_last = True 1100 | continue 1101 | 1102 | if (command == self.SWITCH_TO_BYTE_MODE): 1103 | self._text_encoding_mode = TextEncodingMode.UPPER 1104 | self.codewords_to_bytes(binary_data, seg_len, False) 1105 | elif (command == self.SWITCH_TO_BYTE_MODE_FOR_SIX): 1106 | self._text_encoding_mode = TextEncodingMode.UPPER 1107 | self.codewords_to_bytes(binary_data, seg_len, True) 1108 | elif (command == self.SHIFT_TO_BYTE_MODE): 1109 | shift_byte = self.codewords[self.codewords_ptr] 1110 | self.codewords_ptr += 1 1111 | if (shift_byte >= 900): 1112 | return False 1113 | binary_data.append(shift_byte) 1114 | elif (command == self.SWITCH_TO_TEXT_MODE): 1115 | self.codewords_to_text(binary_data, seg_len) 1116 | elif (command == self.SWITCH_TO_NUMERIC_MODE): 1117 | self._text_encoding_mode = TextEncodingMode.UPPER 1118 | self.codewords_to_numeric(binary_data, seg_len) 1119 | elif (command == self.GLI_CHARACTER_SET): 1120 | if (len(binary_data) > 0): 1121 | return False 1122 | 1123 | g1 = self.codewords[self.codewords_ptr] 1124 | self.codewords_ptr += 1 1125 | 1126 | if (g1 >= 900): 1127 | return False 1128 | 1129 | self.global_label_id_character_set_number = g1 1130 | part = g1 - 2 1131 | 1132 | if (part < 1 or part > 9 and part != 13 and part != 15): 1133 | part = 1 1134 | 1135 | self.global_label_id_character_set = "ISO-8859-" + str(part) 1136 | elif (command == self.GLI_GENERAL_PURPOSE): 1137 | if (len(binary_data) > 0): 1138 | return False 1139 | 1140 | g2 = self.codewords[self.codewords_ptr] 1141 | self.codewords_ptr += 1 1142 | g3 = self.codewords[self.codewords_ptr] 1143 | self.codewords_ptr += 1 1144 | 1145 | if (g2 >= 900 or g3 >= 900): 1146 | return False 1147 | 1148 | self.global_label_id_general_purpose = 900 * (g2 + 1) + g3 1149 | elif (command == self.GLI_USER_DEFINED): 1150 | if (len(binary_data) > 0): 1151 | return False 1152 | 1153 | g4 = self.codewords[self.codewords_ptr] 1154 | self.codewords_ptr += 1 1155 | 1156 | if (g4 >= 900): 1157 | return False 1158 | 1159 | self.global_label_id_user_defined = 810900 + g4 1160 | elif (command == self.START_MACRO_PDF417_CONTROL_BLOCK): 1161 | segment_data = bytearray() 1162 | if (not seg_len > 2): 1163 | print("Macro PDF417 Control Block segment length error") 1164 | return False 1165 | self.codewords_to_numeric(segment_data, 2) 1166 | self.macro_segment = int(segment_data) 1167 | file_id = bytearray() 1168 | self.codewords_to_bytes(file_id, seg_len - 2, False) 1169 | self.macro_file_id = file_id 1170 | elif (command == self.MACRO_PDF417_OPTION): 1171 | g1 = self.codewords[self.codewords_ptr] 1172 | self.codewords_ptr += 1 1173 | if (g1 == 0): 1174 | name_data = bytearray() 1175 | self.codewords_to_text(name_data, seg_len - 1) 1176 | self.macro_file_name = self.binary_data_to_string(name_data) 1177 | elif (g1 == 1): 1178 | segment_data = bytearray() 1179 | self.codewords_to_numeric(segment_data, seg_len - 1) 1180 | self.macro_segment_count = int(segment_data) 1181 | else: 1182 | print("Unknown Macro PDF417 Option %d" % g1) 1183 | else: 1184 | print("Unknown command %d" % command) 1185 | return False 1186 | 1187 | self.barcode_binary_data = binary_data 1188 | 1189 | # return binary bytes array 1190 | return True 1191 | 1192 | def codewords_to_bytes(self, binary_data: bytearray, seg_len: int, six_flag: bool): 1193 | """Convert codewords to bytes""" 1194 | # Number of whole 5 codewords blocks 1195 | blocks = int(seg_len / 5) 1196 | 1197 | # if number of blocks is one or more and SixFlag is false, the last block is not converted 5 to 6 1198 | if ((seg_len % 5) == 0 and blocks >= 1 and not six_flag): 1199 | blocks -= 1 1200 | 1201 | # loop for blocks 1202 | for block in range(blocks): 1203 | temp = (900 ** 4) * self.codewords[self.codewords_ptr] 1204 | self.codewords_ptr += 1 1205 | temp += (900 ** 3) * self.codewords[self.codewords_ptr] 1206 | self.codewords_ptr += 1 1207 | temp += (900 ** 2) * self.codewords[self.codewords_ptr] 1208 | self.codewords_ptr += 1 1209 | temp += (900 ** 1) * self.codewords[self.codewords_ptr] 1210 | self.codewords_ptr += 1 1211 | temp += self.codewords[self.codewords_ptr] 1212 | self.codewords_ptr += 1 1213 | 1214 | # convert to bytes 1215 | for index in range(6): 1216 | val = temp >> (40 - 8 * index) 1217 | val_byte = val % 256 1218 | binary_data.append(val_byte); 1219 | 1220 | # left over 1221 | seg_len -= 5 * blocks 1222 | 1223 | while (seg_len > 0): 1224 | binary_data.append(self.codewords[self.codewords_ptr] % 256) 1225 | self.codewords_ptr += 1 1226 | seg_len -= 1 1227 | 1228 | def codewords_to_numeric(self, binary_data: bytearray, seg_len: int): 1229 | """Convert codewords to numeric characters""" 1230 | # loop for blocks of 15 or less codewords 1231 | block_len = 0 1232 | 1233 | while (seg_len > 0): 1234 | block_len = min(seg_len, 15) 1235 | 1236 | temp = 0 1237 | 1238 | for index in range(block_len - 1, -1, -1): 1239 | temp += (900 ** index) * self.codewords[self.codewords_ptr] 1240 | self.codewords_ptr += 1 1241 | 1242 | # convert number to a string 1243 | num_str = str(temp)[1:] # skip first digit, it is 1 1244 | 1245 | for num in num_str: 1246 | binary_data.append(ord(num)) 1247 | 1248 | seg_len -= block_len 1249 | 1250 | def convert_image(self) -> bool: 1251 | """ Convert image to black and white boolean matrix """ 1252 | self.image_width = self.input_image.width 1253 | self.image_height = self.input_image.height 1254 | 1255 | np_image = np.array(self.input_image) 1256 | if (len(np_image.shape) > 2): 1257 | gray = cv2.cvtColor(np_image, cv2.COLOR_BGR2GRAY) 1258 | else: 1259 | gray = np_image 1260 | black_white = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] 1261 | 1262 | # padding with single white line at the leftmost of the image 1263 | if (not (black_white[:, 0] == 255).all()): 1264 | black_white = np.concatenate([np.full((black_white.shape[0],1), fill_value=255, dtype=black_white.dtype), black_white], axis=1) 1265 | 1266 | self.image_height, self.image_width = black_white.shape[:2] 1267 | 1268 | # Save the final cleaned up black and white image. 1269 | #PIL.fromarray(black_white).save("black_and_white.png") 1270 | 1271 | #Load a Black and White created from C# version. 1272 | # bwimage = PIL.open("BlackWhiteImage.png").convert('RGB') 1273 | # black_white_color = np.asarray(bwimage) 1274 | # threshold_result = cv2.threshold(black_white_color, 0, 255, cv2.THRESH_BINARY) 1275 | # threshold_result_0 = threshold_result[0] 1276 | # threshold_result_1 = threshold_result[1] 1277 | 1278 | self.image_matrix = np.zeros((self.image_height, self.image_width), dtype=bool) 1279 | 1280 | mask = np.where(black_white[:,:] != 255) 1281 | self.image_matrix[mask] = True 1282 | 1283 | return True 1284 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/ErrorCorrection.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from pdf417decoder import Modulus 3 | from pdf417decoder.Polynomial import ONE, Polynomial, ZERO 4 | 5 | def test_codewords(codewords: list, error_correction_length: int) -> tuple: 6 | """ Decode the received codewords """ 7 | poly_codewords = Polynomial(0, 0, codewords) 8 | 9 | # create syndrom coefficients array 10 | syndrome = list([0] * error_correction_length) 11 | 12 | # assume new errors 13 | error = False 14 | 15 | # test for errors 16 | # if the syndrom array is all zeros, there is no error 17 | for i in range(error_correction_length, 0, -1): 18 | # TODO: This may have been translated incorrectly! Confirm it's not broken. 19 | # Original Code: if((Syndrome[ErrorCorrectionLength - Index] = PolyCodewords.EvaluateAt(Modulus.ExpTable[Index])) != 0) Error = true; 20 | mod_exp_table_result = Modulus.exp_table[i] 21 | evaluate_result = poly_codewords.evaluate_at(mod_exp_table_result) 22 | syndrome_index = error_correction_length - i 23 | syndrome[syndrome_index] = evaluate_result 24 | if (syndrome[syndrome_index] != 0): 25 | error = True 26 | 27 | if (not error): 28 | return (0, codewords) 29 | 30 | # convert syndrom array to polynomial 31 | poly_syndrome = Polynomial(0, 0, syndrome) 32 | 33 | # Greatest Common Divisor (return -1 if error cannot be corrected) 34 | result = euclidean_algorithm(error_correction_length, poly_syndrome) 35 | 36 | if (not result[0]): 37 | return (-1, codewords) 38 | 39 | error_locator = result[1] 40 | error_evaluator = result[2] 41 | 42 | error_locations = find_error_locations(error_locator) 43 | 44 | if (error_locations is None): 45 | return (-1, codewords) 46 | 47 | formal_derivative = find_formal_derivatives(error_locator) 48 | 49 | errors = len(error_locations) 50 | 51 | # This is directly applying Forney's Formula 52 | for i in range(errors): 53 | error_location = error_locations[i] 54 | error_position = len(codewords) - 1 - Modulus.log_table[Modulus.invert(error_location)] 55 | 56 | if (error_position < 0): 57 | return (-1, codewords) 58 | 59 | error_magnitude = Modulus.divide(Modulus.negate(error_evaluator.evaluate_at(error_location)), formal_derivative.evaluate_at(error_location)) 60 | corrected_codeword = Modulus.subtract(codewords[error_position], error_magnitude) 61 | codewords[error_position] = corrected_codeword 62 | error_locations[i] = error_position 63 | 64 | return (errors, codewords) 65 | 66 | def euclidean_algorithm(error_correction_length: int, poly_r: Polynomial) -> Tuple[bool,Polynomial,Polynomial]: 67 | """ Runs the euclidean algorithm (Greatest Common Divisor) until r's degree is less than R/2 """ 68 | poly_r_last = Polynomial(error_correction_length, 1) 69 | poly_t_last = ZERO 70 | poly_t = ONE 71 | 72 | # Run Euclidean algorithm until r's degree is less than R/2 73 | while (poly_r.degree >= (error_correction_length / 2)): 74 | poly_r_last2 = poly_r_last 75 | poly_t_last2 = poly_t_last 76 | poly_r_last = poly_r 77 | poly_t_last = poly_t 78 | 79 | if (poly_r_last.is_zero): 80 | return (False, None, None) 81 | 82 | # Divide rLastLast by PolyRLast, with quotient in q and remainder in r 83 | poly_r = poly_r_last2 84 | 85 | # initial quotient polynomial 86 | quotient = ZERO 87 | 88 | dlt_inverse = Modulus.invert(poly_r_last.leading_coefficient()) 89 | 90 | while (poly_r.degree >= poly_r_last.degree and not poly_r.is_zero): 91 | # divide polyR and polyRLast leading coefficients 92 | scale = Modulus.multiply(poly_r.leading_coefficient(), dlt_inverse) 93 | 94 | # degree difference between polyR and polyRLast 95 | degree_diff = poly_r.degree - poly_r_last.degree 96 | quotient = quotient.add(Polynomial(degree_diff, scale)) 97 | poly_r = poly_r.subtract(poly_r_last.multiply_by_monomial(degree_diff, scale)) 98 | 99 | poly_t = quotient.multiply(poly_t_last).subtract(poly_t_last2).make_negative() 100 | 101 | sigma_tilde_at_zero = poly_t.last_coefficient() 102 | 103 | if (sigma_tilde_at_zero == 0): 104 | return (False, None, None) 105 | 106 | inverse = Modulus.invert(sigma_tilde_at_zero) 107 | error_locator = poly_t.multiply_by_constant(inverse) 108 | error_evaluator = poly_r.multiply_by_constant(inverse) 109 | 110 | return (True, error_locator, error_evaluator) 111 | 112 | def find_error_locations(error_locator: Polynomial) -> list: 113 | """ 114 | Finds the error locations as a direct application of Chien's search 115 | error locations are not error positions within codewords array 116 | """ 117 | 118 | # This is a direct application of Chien's search 119 | locator_degree = error_locator.degree; 120 | error_locations = list([0] * locator_degree) 121 | error_count = 0 122 | 123 | for i in range(1, Modulus.MOD): 124 | if (error_count >= locator_degree): 125 | break 126 | 127 | if (error_locator.evaluate_at(i) == 0): 128 | error_locations[error_count] = i 129 | error_count += 1 130 | 131 | if error_count == locator_degree: 132 | return error_locations 133 | else: 134 | return None 135 | 136 | 137 | def find_formal_derivatives(error_locator: Polynomial) -> Polynomial: 138 | """ Finds the error magnitudes by directly applying Forney's Formula """ 139 | locator_degree = error_locator.degree 140 | derivative_coefficients = list([0] * locator_degree) 141 | 142 | for i in range(1, locator_degree + 1): 143 | derivative_coefficients[locator_degree - i] = Modulus.multiply(i, error_locator.get_coefficient(i)) 144 | 145 | return Polynomial(0, 0, derivative_coefficients) 146 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/Modulus.py: -------------------------------------------------------------------------------- 1 | # PDF 417 uses a Base 929 encoding 2 | MOD = 929 3 | 4 | exp_table = list([0] * MOD) 5 | log_table = list([0] * MOD) 6 | 7 | # Populate exponent and log tables 8 | current_value = 1 9 | for i in range(MOD): 10 | exp_table[i] = current_value 11 | log_table[current_value] = i 12 | current_value = (3 * current_value) % MOD 13 | 14 | def add(a, b) -> int: 15 | result = (a + b) % MOD 16 | return result 17 | 18 | def subtract(a, b) -> int: 19 | result = (MOD + a - b) % MOD 20 | return result 21 | 22 | def negate(a) -> int: 23 | result = (MOD - a) % MOD 24 | return result 25 | 26 | def invert(a) -> int: 27 | result = exp_table[MOD - log_table[a] - 1] 28 | return result 29 | 30 | def multiply(a, b) -> int: 31 | if (a == 0 or b == 0): 32 | return 0 33 | 34 | result = exp_table[(log_table[a] + log_table[b]) % (MOD - 1)] 35 | return result 36 | 37 | def divide(a, b) -> int: 38 | result = multiply(a, invert(b)) 39 | return result 40 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/Polynomial.py: -------------------------------------------------------------------------------- 1 | import math 2 | from pdf417decoder import Modulus 3 | 4 | class Polynomial: 5 | 6 | @property 7 | def coefficients(self) -> list: 8 | """ Polynomial coefficients """ 9 | return self._coefficients 10 | 11 | @coefficients.setter 12 | def coefficients(self, value: list): 13 | self._coefficients = value 14 | 15 | @property 16 | def length(self) -> int: 17 | """ Polynomial length (Typically Coefficients.Length) """ 18 | return self._length 19 | 20 | @length.setter 21 | def length(self, value: int): 22 | self._length = value 23 | 24 | @property 25 | def degree(self) -> int: 26 | """ Polynomial degree (Typically Coefficients.Length - 1) """ 27 | return self._degree 28 | 29 | @degree.setter 30 | def degree(self, value: int): 31 | self._degree = value 32 | 33 | def __init__(self, degree: int, coefficient: int, coefficients: list = None): 34 | if (coefficients is None): 35 | """ Create a polynomial with one leading non zero value """ 36 | self.degree = degree 37 | self.length = degree + 1 38 | self.coefficients = list([0] * self.length) 39 | self.coefficients[0] = coefficient 40 | return 41 | 42 | self.length = len(coefficients) 43 | 44 | if (self.length > 1 and coefficients[0] == 0): 45 | first_non_zero = 0 46 | 47 | # count leading zeros 48 | for i in range(self.length): 49 | first_non_zero = i 50 | if (coefficients[i] != 0): 51 | break 52 | 53 | if (first_non_zero == self.length): 54 | # all coefficients are zeros 55 | self.coefficients = list([0]) 56 | self.length = 1 57 | else: 58 | # new length 59 | self.length -= first_non_zero 60 | 61 | # create shorter coefficients array 62 | self.coefficients = list([0] * self.length) 63 | 64 | # copy non zero part to new array 65 | # Array.Copy(Coefficients, FirstNonZero, this.Coefficients, 0, PolyLength); 66 | for i in range(first_non_zero, len(coefficients)): 67 | self.coefficients[i - first_non_zero] = coefficients[i] 68 | else: 69 | # save coefficient array argument unchanged 70 | self.coefficients = coefficients 71 | 72 | # set polynomial degree 73 | self.degree = self.length - 1; 74 | 75 | @property 76 | def is_zero(self) -> bool: 77 | """ Test for zero polynomial """ 78 | return self.coefficients[0] == 0 79 | 80 | def get_coefficient(self, degree: int) -> int: 81 | """ Coefficient value of degree term in this polynomial """ 82 | return self.coefficients[self.degree - degree] 83 | 84 | def last_coefficient(self) -> int: 85 | """ Coefficient value of zero degree term in this polynomial """ 86 | return self.coefficients[self.degree] 87 | 88 | def leading_coefficient(self) -> int: 89 | """ Leading coefficient """ 90 | return self.coefficients[0] 91 | 92 | def evaluate_at(self, x) -> int: 93 | """ Evaluation of this polynomial at a given point """ 94 | if (x == 0): return self.coefficients[0] 95 | 96 | result = 0 97 | 98 | # Return the x^1 coefficient 99 | if (x == 1): 100 | # Return the sum of the coefficients 101 | for coefficient in self.coefficients: 102 | result = Modulus.add(result, coefficient) 103 | else: 104 | result = self.coefficients[0] 105 | for i in range (1, self.length): 106 | multiply_result = Modulus.multiply(x, result) 107 | add_result = Modulus.add(multiply_result, self.coefficients[i]) 108 | result = add_result 109 | 110 | return result 111 | 112 | def make_negative(self) -> 'Polynomial': 113 | """ Returns a Negative version of this instance """ 114 | result = list([0] * self.length) 115 | 116 | for i in range(self.length): 117 | result[i] = Modulus.negate(self.coefficients[i]) 118 | 119 | return Polynomial(0, 0, result) 120 | 121 | def add(self, other: 'Polynomial') -> 'Polynomial': 122 | if (self.is_zero): 123 | return other 124 | 125 | if (other.is_zero): 126 | return self 127 | 128 | # Assume this polynomial is smaller than the other one 129 | smaller = self.coefficients 130 | larger = other.coefficients 131 | 132 | # Assumption is wrong. exchange the two arrays 133 | if (len(smaller) > len(larger)): 134 | smaller = other.coefficients 135 | larger = self.coefficients 136 | 137 | result = list([0] * len(larger)) 138 | delta = len(larger) - len(smaller) 139 | 140 | # Copy high-order terms only found in higher-degree polynomial's coefficients 141 | # Array.Copy(Larger, 0, Result, 0, Delta); 142 | for i in range(len(larger)): 143 | result[i] = larger[i] 144 | 145 | # Add the coefficients of the two polynomials 146 | # for(int Index = Delta; Index < Larger.Length; Index++) 147 | for i in range(delta, len(larger)): 148 | # Result[Index] = Modulus.Add(Smaller[Index - Delta], Larger[Index]); 149 | result[i] = Modulus.add(smaller[i - delta], larger[i]) 150 | 151 | return Polynomial(0, 0, result) 152 | 153 | def subtract(self, other: 'Polynomial') -> 'Polynomial': 154 | """ Subtract two polynomials """ 155 | if (other.is_zero): return self 156 | 157 | return self.add(other.make_negative()) 158 | 159 | def multiply(self, other: 'Polynomial') -> 'Polynomial': 160 | """ Multiply two polynomials """ 161 | if (self.is_zero or other.is_zero): return ZERO 162 | 163 | result = list([0] * (self.length + other.length - 1)) 164 | 165 | for i in range(self.length): 166 | coeff = self.coefficients[i] 167 | for j in range(other.length): 168 | result[i+j] = Modulus.add(result[i+j], Modulus.multiply(coeff, other.coefficients[j])) 169 | 170 | return Polynomial(0, 0, result) 171 | 172 | def multiply_by_constant(self, constant: int) -> 'Polynomial': 173 | """ Multiply by an integer constant """ 174 | if (constant == 0): return ZERO 175 | if (constant == 1): return self 176 | 177 | result = list([0] * self.length) 178 | 179 | for i in range(self.length): 180 | result[i] = Modulus.multiply(self.coefficients[i], constant) 181 | 182 | return Polynomial(0, 0, result) 183 | 184 | def multiply_by_monomial(self, degree: int, constant: int) -> 'Polynomial': 185 | """ Multipies by a Monomial """ 186 | if (constant == 0): return ZERO 187 | 188 | result = list([0] * (self.length + degree)) 189 | 190 | for i in range(self.length): 191 | result[i] = Modulus.multiply(self.coefficients[i], constant) 192 | 193 | return Polynomial(0, 0, result) 194 | 195 | def __str__(self): 196 | coefficients = '\n'.join([str(num) for num in self.coefficients]) 197 | return 'Degree: {degree}, Length: {length}\r\n{coefficients}'.format(degree=self.degree, length=self.length, coefficients=coefficients) 198 | 199 | def export(self, filename): 200 | text_file = open(filename, "w") 201 | n = text_file.write(str(self)) 202 | text_file.close() 203 | 204 | 205 | 206 | ZERO = Polynomial(0, 0, list([0])) 207 | ONE = Polynomial(0, 0, list([1])) 208 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/StaticTables.py: -------------------------------------------------------------------------------- 1 | # upper case to text table 2 | UPPER_TO_TEXT = bytearray([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, 32, 0, 0, 0]) 3 | 4 | # lower case to text table 5 | LOWER_TO_TEXT = bytearray([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, 32, 0, 0, 0]) 6 | 7 | # table for text mixed sub-mode 8 | MIXED_TO_TEXT = bytearray([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58, 35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0]) 9 | 10 | # punctuaion to text table 11 | PUNCT_TO_TEXT = bytearray([59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58, 10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0]) 12 | 13 | # Symbol to cluster and codeword translation table. 14 | # Each entry is made of: 15 | # 18 bits symbol (bit 29 to bit 12) 16 | # 2 bits cluster (bit 11 to bit 10) 17 | # 10 bits codeword (bit 9 to bit 0) 18 | SYMBOL_TABLE = list([ \ 19 | 0x0000c802, 0x00015805, 0x0001e808, 0x00024009, 0x0002780a, 0x0002d00b, 0x0004c80c, 0x0005580f, \ 20 | 0x0005b012, 0x0005e812, 0x00064014, 0x00067815, 0x00092015, 0x00095818, 0x0009b018, 0x0009e81b, \ 21 | 0x000a401b, 0x000a781d, 0x000db01d, 0x000e142c, 0x000e401f, 0x000ea42f, 0x000f3432, 0x000fc434, \ 22 | 0x00121436, 0x00124021, 0x0012a439, 0x0013343b, 0x0013c43d, 0x0016a43e, 0x00173440, 0x0017c441, \ 23 | 0x00243830, 0x0024c833, 0x00255836, 0x0025b02b, 0x0025e838, 0x0026402e, 0x0026783a, 0x0026d030, \ 24 | 0x0028c83c, 0x00292034, 0x0029583f, 0x0029b037, 0x0029e842, 0x002a403a, 0x002a7843, 0x002d203c, \ 25 | 0x002d5846, 0x002db03f, 0x002de848, 0x002e4041, 0x00318466, 0x0031b043, 0x00321469, 0x00324045, \ 26 | 0x0032a46c, 0x0032d046, 0x0033346e, 0x0033c470, 0x00361471, 0x0036a473, 0x00373475, 0x0037c476, \ 27 | 0x003aa477, 0x003b3478, 0x00483856, 0x0048c859, 0x0049204d, 0x0049585b, 0x0049b050, 0x0049e85d, \ 28 | 0x004a4052, 0x004a785e, 0x004ad053, 0x004c9054, 0x004cc860, 0x004d2057, 0x004d5863, 0x004db05a, \ 29 | 0x004de864, 0x004e405c, 0x004ed05d, 0x0051205e, 0x00515866, 0x0051b060, 0x00524062, 0x00558494, \ 30 | 0x0055b063, 0x00561497, 0x0056a499, 0x0057349b, 0x0057c49c, 0x005a149d, 0x005aa49f, 0x005b34a0, \ 31 | 0x005ea4a1, 0x006c386e, 0x006c9067, 0x006cc870, 0x006d206a, 0x006d5872, 0x006db06d, 0x006de873, \ 32 | 0x006e406f, 0x00709071, 0x0070c875, 0x00712074, 0x00715876, 0x0071b076, 0x00724078, 0x0072d079, \ 33 | 0x0076407b, 0x007984b4, 0x007a14b6, 0x007aa4b8, 0x007b34b9, 0x007e14ba, 0x007ea4bb, 0x0090007c, \ 34 | 0x0090387a, 0x0090907f, 0x0090c87c, 0x00912082, 0x0091587d, 0x0091b084, 0x00924086, 0x00949087, \ 35 | 0x0094c87e, 0x00952088, 0x0095b089, 0x009d84c8, 0x009e14ca, 0x009ea4cb, 0x00b4008a, 0x00b4387f, \ 36 | 0x00b4c880, 0x00b6408e, 0x0120550b, 0x0120b8aa, 0x0120e50e, 0x012148ad, 0x01217511, 0x0121d8b0, \ 37 | 0x012268b2, 0x0122f8b4, 0x0124b8b8, 0x012548bb, 0x0125a0c8, 0x0125d8be, 0x012630cc, 0x012668c0, \ 38 | 0x0126c0ce, 0x0126f8c2, 0x012910d2, 0x012948c8, 0x0129a0d6, 0x0129d8cb, 0x012a30d9, 0x012a68cd, \ 39 | 0x012ac0db, 0x012da0de, 0x012dd8d2, 0x012e30e1, 0x012ec0e3, 0x01320541, 0x013230e5, 0x01329544, \ 40 | 0x0132c0e7, 0x01332547, 0x0133b549, 0x0136954d, 0x0137254f, 0x0137b551, 0x014428de, 0x01445555, \ 41 | 0x0144b8e1, 0x0144e558, 0x014548e4, 0x0145755a, 0x0145d8e6, 0x014668e8, 0x0146f8e9, 0x0148b8ec, \ 42 | 0x014910fb, 0x014948ef, 0x0149a0ff, 0x0149d8f1, 0x014a3102, 0x014a68f3, 0x014d1109, 0x014d48f7, \ 43 | 0x014da10c, 0x014dd8f9, 0x014e310f, 0x0151a114, 0x01523116, 0x0152c118, 0x0156057c, 0x01563119, \ 44 | 0x0156957f, 0x01572581, 0x0157b583, 0x015a9585, 0x015b2587, 0x01682901, 0x0168558a, 0x0168b904, \ 45 | 0x0168e58c, 0x01694906, 0x0169d908, 0x016a6909, 0x016c8129, 0x016cb90c, 0x016d112d, 0x016d490e, \ 46 | 0x016da130, 0x016dd910, 0x016e3133, 0x016ec135, 0x01711138, 0x01714912, 0x0171a13b, 0x0172313d, \ 47 | 0x0172c13f, 0x0175a140, 0x01763141, 0x017a05a5, 0x017a95a7, 0x017b25a9, 0x017e95ab, 0x018c2913, \ 48 | 0x018c55ad, 0x018cb915, 0x018d4917, 0x018dd918, 0x0190814e, 0x0190b91a, 0x01911151, 0x0191491c, \ 49 | 0x0191a154, 0x01923156, 0x01951159, 0x0195a15a, 0x0196315b, 0x019e05bc, 0x019e95be, 0x01b0291d, \ 50 | 0x01b0b91f, 0x01b14920, 0x01b4b922, 0x01b51161, 0x01b5a162, 0x01b63163, 0x01d42923, 0x01d4b924, \ 51 | 0x02404625, 0x0240a963, 0x0240d628, 0x02413966, 0x0241662b, 0x0241c969, 0x0241f62c, 0x0242596b, \ 52 | 0x0242e96d, 0x0244a971, 0x0244d632, 0x02453974, 0x02456636, 0x0245c977, 0x02465979, 0x0246e97b, \ 53 | 0x024901d4, 0x02493980, 0x024991d8, 0x0249c983, 0x024a21dc, 0x024a5985, 0x024ab1df, 0x024d91e6, \ 54 | 0x024dc988, 0x024e21e9, 0x025221f0, 0x0256865a, 0x0257165d, 0x0257a65f, 0x02641989, 0x02644663, \ 55 | 0x0264a98c, 0x0264d666, 0x0265398f, 0x02656667, 0x0265c991, 0x02665993, 0x0266e994, 0x0268a997, \ 56 | 0x0268d66c, 0x0269399a, 0x0269c99c, 0x026a599e, 0x026d020f, 0x026d39a2, 0x026d9213, 0x026dc9a4, \ 57 | 0x026e2216, 0x0271921e, 0x02722221, 0x02762224, 0x027a8683, 0x027b1685, 0x028819a5, 0x02884687, \ 58 | 0x0288a9a8, 0x0288d688, 0x028939aa, 0x0289c9ac, 0x028a59ad, 0x028ca9b0, 0x028d39b2, 0x028dc9b4, \ 59 | 0x0291023a, 0x029139b7, 0x0291923d, 0x02922240, 0x0292b242, 0x02959244, 0x02962245, 0x029e8696, \ 60 | 0x02ac19b8, 0x02ac4697, 0x02aca9ba, 0x02ad39bc, 0x02adc9bd, 0x02b0a9bf, 0x02b139c1, 0x02b5024d, \ 61 | 0x02d019c2, 0x02d0a9c4, 0x02d139c5, 0x02d4a9c7, 0x02f419c8, 0x02f4a9c9, 0x036036fa, 0x03609a07, \ 62 | 0x0360c6fc, 0x03612a0a, 0x036156fe, 0x0361ba0d, 0x0361e6ff, 0x03624a0f, 0x0362da11, 0x03649a15, \ 63 | 0x0364c703, 0x03652a18, 0x03655705, 0x0365ba1b, 0x03664a1d, 0x0366da1f, 0x03692a24, 0x0369ba27, \ 64 | 0x036a4a29, 0x036d82e7, 0x036dba2d, 0x036e12eb, 0x036ea2ee, 0x037212f4, 0x0372a2f6, 0x03840a2e, \ 65 | 0x03843718, 0x03849a31, 0x0384c71a, 0x03852a34, 0x0385571b, 0x0385ba36, 0x03864a38, 0x0386da39, \ 66 | 0x03889a3c, 0x0388c71d, 0x03892a3f, 0x0389ba41, 0x038a4a43, 0x038d2a47, 0x038dba49, 0x03918313, \ 67 | 0x03921316, 0x0392a318, 0x0396131a, 0x03a80a4a, 0x03a8371e, 0x03a89a4d, 0x03a8c71f, 0x03a92a4f, \ 68 | 0x03a9ba51, 0x03aa4a52, 0x03ac9a55, 0x03ad2a57, 0x03adba59, 0x03b12a5c, 0x03b58323, 0x03b61324, \ 69 | 0x03cc0a5d, 0x03cc3720, 0x03cc9a5f, 0x03cd2a61, 0x03cdba62, 0x03d09a64, 0x03d12a66, 0x03f00a67, \ 70 | 0x03f09a69, 0x03f12a6a, 0x03f49a6c, 0x0480275b, 0x0480b75d, 0x0481475f, 0x0481d760, 0x04848ab7, \ 71 | 0x0484b762, 0x04851aba, 0x04854764, 0x0485aabd, 0x04863abf, 0x0486cac1, 0x04891ac6, 0x0489aac9, \ 72 | 0x048a3acb, 0x048daacf, 0x04a42765, 0x04a4b767, 0x04a54768, 0x04a88ad8, 0x04a8b76a, 0x04a91adb, \ 73 | 0x04a9aadd, 0x04aa3adf, 0x04ad1ae3, 0x04adaae5, 0x04c8276b, 0x04c8b76c, 0x04cc8aec, 0x04cd1aee, \ 74 | 0x04cdaaf0, 0x04d11af3, 0x04ec276d, 0x04f08af7, 0x04f11af9, 0x05a01784, 0x05a0a786, 0x05a13788, \ 75 | 0x05a1c789, 0x05a4a78b, 0x05a5378d, 0x05a90b44, 0x05a99b47, 0x05aa2b49, 0x05ad9b4d, 0x05c4178e, \ 76 | 0x05c4a790, 0x05c53791, 0x05c8a793, 0x05cd0b57, 0x05cd9b59, 0x05e81794, 0x05e8a795, 0x05f10b5f, \ 77 | 0x08004800, 0x0800d803, 0x08013006, 0x08016806, 0x0801c008, 0x0801f809, 0x0802500a, 0x0804a00c, \ 78 | 0x0804d80d, 0x0805300f, 0x08056810, 0x0805c013, 0x0805f813, 0x08093016, 0x0809941e, 0x0809c019, \ 79 | 0x080a2421, 0x080a501c, 0x080ab424, 0x080b4426, 0x080bd428, 0x080d9429, 0x080dc01e, 0x080e242d, \ 80 | 0x080e5020, 0x080eb430, 0x080f4433, 0x080fd435, 0x08122437, 0x0812b43a, 0x0813443c, 0x0816882b, \ 81 | 0x0816b43f, 0x0817182d, 0x0817a82f, 0x08244831, 0x0824a026, 0x0824d834, 0x08253029, 0x08256837, \ 82 | 0x0825c02c, 0x0825f839, 0x0826502f, 0x0828a031, 0x0828d83d, 0x08293035, 0x08296840, 0x0829c038, \ 83 | 0x082a503b, 0x082d045a, 0x082d303d, 0x082d945d, 0x082dc040, 0x082e2460, 0x082e5042, 0x082eb462, \ 84 | 0x082f4464, 0x082fd465, 0x08319467, 0x0831c044, 0x0832246a, 0x0832b46d, 0x0833446f, 0x08362472, \ 85 | 0x0836b474, 0x083a8852, 0x083b1854, 0x083ba855, 0x08481047, 0x08484857, 0x0848a04a, 0x0848d85a, \ 86 | 0x0849304e, 0x0849685c, 0x0849c051, 0x084ca055, 0x084cd861, 0x084d3058, 0x084dc05b, 0x0851048b, \ 87 | 0x0851305f, 0x0851948e, 0x0851c061, 0x08522490, 0x0852b492, 0x08534493, 0x08559495, 0x08562498, \ 88 | 0x0856b49a, 0x085a249e, 0x085e886c, 0x085f186d, 0x086c1064, 0x086c486f, 0x086ca068, 0x086cd871, \ 89 | 0x086d306b, 0x086dc06e, 0x086e5070, 0x0870a072, 0x08713075, 0x0871c077, 0x087504ae, 0x0875307a, \ 90 | 0x087594b0, 0x087624b2, 0x0876b4b3, 0x087994b5, 0x087a24b7, 0x0890107d, 0x0890487b, 0x0890a080, \ 91 | 0x08913083, 0x0891c085, 0x089904c4, 0x089994c6, 0x089a24c7, 0x089d94c9, 0x08b4a08c, 0x08b5308d, \ 92 | 0x08bd04d0, 0x08bd94d1, 0x09003881, 0x0900c884, 0x09015887, 0x0901b098, 0x0901e889, 0x0902409a, \ 93 | 0x0902788b, 0x0904c88d, 0x0905209e, 0x09055890, 0x0905b0a1, 0x0905e893, 0x090640a3, 0x09067894, \ 94 | 0x0906d0a4, 0x090920a5, 0x09095897, 0x0909b0a8, 0x0909e899, 0x090a40aa, 0x090ad0ab, 0x090d84f6, \ 95 | 0x090db0ac, 0x090e14f9, 0x090e40ae, 0x090ea4fc, 0x090f34fe, 0x090fc500, 0x09121501, 0x091240af, \ 96 | 0x0912a503, 0x09133505, 0x0913c506, 0x0916a507, 0x09173508, 0x092038a7, 0x0920c8ab, 0x092120b6, \ 97 | 0x092158ae, 0x0921b0b9, 0x0921e8b1, 0x092240bc, 0x092278b3, 0x092438b5, 0x092490c1, 0x0924c8b9, \ 98 | 0x092520c5, 0x092558bc, 0x0925b0c9, 0x0925e8bf, 0x092640cd, 0x092678c1, 0x092890cf, 0x0928c8c4, \ 99 | 0x092920d3, 0x092958c9, 0x0929b0d7, 0x0929e8cc, 0x092a40da, 0x092d20dc, 0x092d58cf, 0x092d8533, \ 100 | 0x092db0df, 0x092e1536, 0x092e40e2, 0x092ea539, 0x092f353b, 0x092fc53d, 0x0931853e, 0x0931b0e4, \ 101 | 0x09321542, 0x093240e6, 0x0932a545, 0x09333548, 0x0933c54a, 0x0936154b, 0x0936a54e, 0x09373550, \ 102 | 0x093aa552, 0x094438df, 0x094490eb, 0x0944c8e2, 0x094520ee, 0x094558e5, 0x0945b0f1, 0x0945e8e7, \ 103 | 0x094640f3, 0x0946d0f4, 0x094838ea, 0x094890f8, 0x0948c8ed, 0x094920fc, 0x094958f0, 0x0949b100, \ 104 | 0x0949e8f2, 0x094a4103, 0x094ad105, 0x094c9106, 0x094cc8f5, 0x094d210a, 0x094d58f8, 0x094db10d, \ 105 | 0x094e4110, 0x094ed111, 0x09512112, 0x09518572, 0x0951b115, 0x09521575, 0x09524117, 0x0952a577, \ 106 | 0x09533579, 0x0955857a, 0x0956157d, 0x0956a580, 0x09573582, 0x095a1584, 0x095aa586, 0x0968011a, \ 107 | 0x09683902, 0x0968911d, 0x0968c905, 0x09692120, 0x09695907, 0x0969b122, 0x096a4124, 0x096ad125, \ 108 | 0x096c0126, 0x096c390a, 0x096c912a, 0x096cc90d, 0x096d212e, 0x096d590f, 0x096db131, 0x096e4134, \ 109 | 0x09709136, 0x0970c911, 0x09712139, 0x0971b13c, 0x0972413e, 0x0975859e, 0x097615a0, 0x0976a5a2, \ 110 | 0x097985a3, 0x097a15a6, 0x097aa5a8, 0x097e15aa, 0x098c0142, 0x098c3914, 0x098c9145, 0x098cc916, \ 111 | 0x098d2147, 0x098db149, 0x098e414a, 0x0990014b, 0x09903919, 0x0990914f, 0x0990c91b, 0x09912152, \ 112 | 0x0991b155, 0x09924157, 0x09949158, 0x099985b8, 0x099a15ba, 0x099d85bb, 0x099e15bd, 0x09b0391e, \ 113 | 0x09b1b15e, 0x09b43921, 0x09b49160, 0x09bd85c5, 0x0a20293d, 0x0a2055f1, 0x0a20b940, 0x0a20e5f4, \ 114 | 0x0a214943, 0x0a2175f6, 0x0a21d945, 0x0a226947, 0x0a22f948, 0x0a24b94b, 0x0a251194, 0x0a25494e, \ 115 | 0x0a25a198, 0x0a25d950, 0x0a26319b, 0x0a266952, 0x0a26c19d, 0x0a2911a1, 0x0a294956, 0x0a29a1a4, \ 116 | 0x0a29d958, 0x0a2a31a7, 0x0a2ac1a9, 0x0a2da1ac, 0x0a2e31ae, 0x0a320618, 0x0a3231b1, 0x0a32961b, \ 117 | 0x0a33261d, 0x0a33b61f, 0x0a369621, 0x0a372623, 0x0a402960, 0x0a405626, 0x0a40b964, 0x0a40e629, \ 118 | 0x0a414967, 0x0a41d96a, 0x0a42696c, 0x0a44296e, 0x0a44562f, 0x0a4481c0, 0x0a44b972, 0x0a44e633, \ 119 | 0x0a4511c4, 0x0a454975, 0x0a45a1c7, 0x0a45d978, 0x0a4631ca, 0x0a46697a, 0x0a4881d0, 0x0a48b97e, \ 120 | 0x0a4911d5, 0x0a494981, 0x0a49a1d9, 0x0a49d984, 0x0a4a31dd, 0x0a4d11e3, 0x0a4d4987, 0x0a4da1e7, \ 121 | 0x0a4e31ea, 0x0a4ec1ec, 0x0a51a1ee, 0x0a520650, 0x0a5231f1, 0x0a529653, 0x0a532655, 0x0a560658, \ 122 | 0x0a56965b, 0x0a57265e, 0x0a5a9661, 0x0a64298a, 0x0a645664, 0x0a64b98d, 0x0a654990, 0x0a65d992, \ 123 | 0x0a682995, 0x0a685669, 0x0a6881fe, 0x0a68b998, 0x0a691201, 0x0a69499b, 0x0a69a204, 0x0a69d99d, \ 124 | 0x0a6a3206, 0x0a6ac208, 0x0a6c820c, 0x0a6cb9a0, 0x0a6d1210, 0x0a6d49a3, 0x0a6da214, 0x0a6e3217, \ 125 | 0x0a6ec219, 0x0a71121c, 0x0a71a21f, 0x0a723222, 0x0a76067d, 0x0a76967f, 0x0a7a0681, 0x0a7a9684, \ 126 | 0x0a8829a6, 0x0a88b9a9, 0x0a8949ab, 0x0a8c29ae, 0x0a8c822d, 0x0a8cb9b1, 0x0a8d1230, 0x0a8d49b3, \ 127 | 0x0a8da232, 0x0a8e3234, 0x0a908237, 0x0a90b9b6, 0x0a91123b, 0x0a91a23e, 0x0a923241, 0x0a951243, \ 128 | 0x0a9a0693, 0x0a9e0695, 0x0aac29b9, 0x0aacb9bb, 0x0ab029be, 0x0ab0b9c0, 0x0ab4824c, 0x0ab5a24f, \ 129 | 0x0ad029c3, 0x0ad429c6, 0x0b4019e8, 0x0b4046d7, 0x0b40a9eb, 0x0b40d6da, 0x0b4139ee, 0x0b4166db, \ 130 | 0x0b41c9f0, 0x0b4259f2, 0x0b42e9f3, 0x0b44a9f6, 0x0b44d6e0, 0x0b4539f9, 0x0b45c9fb, 0x0b4659fd, \ 131 | 0x0b4902aa, 0x0b493a01, 0x0b4992ae, 0x0b49ca03, 0x0b4a22b1, 0x0b4d92b9, 0x0b4e22bc, 0x0b5222c0, \ 132 | 0x0b5686f7, 0x0b5716f9, 0x0b601a04, 0x0b6046fb, 0x0b60aa08, 0x0b60d6fd, 0x0b613a0b, 0x0b61ca0e, \ 133 | 0x0b625a10, 0x0b641a12, 0x0b644701, 0x0b64aa16, 0x0b64d704, 0x0b653a19, 0x0b65ca1c, 0x0b665a1e, \ 134 | 0x0b68aa22, 0x0b6902d6, 0x0b693a25, 0x0b6992d9, 0x0b69ca28, 0x0b6a22dc, 0x0b6ab2de, 0x0b6d02e4, \ 135 | 0x0b6d3a2c, 0x0b6d92e8, 0x0b6e22ec, 0x0b7192f2, 0x0b7222f5, 0x0b768714, 0x0b7a8717, 0x0b841a2f, \ 136 | 0x0b844719, 0x0b84aa32, 0x0b853a35, 0x0b85ca37, 0x0b881a3a, 0x0b88471c, 0x0b88aa3d, 0x0b893a40, \ 137 | 0x0b89ca42, 0x0b8caa45, 0x0b8d0306, 0x0b8d3a48, 0x0b8d9309, 0x0b8e230b, 0x0b910310, 0x0b919314, \ 138 | 0x0b922317, 0x0ba81a4b, 0x0ba8aa4e, 0x0ba93a50, 0x0bac1a53, 0x0bacaa56, 0x0bad3a58, 0x0bb0aa5b, \ 139 | 0x0bb1031f, 0x0bb19320, 0x0bb50322, 0x0bcc1a5e, 0x0bccaa60, 0x0bd01a63, 0x0bd0aa65, 0x0bf01a68, \ 140 | 0x0bf41a6b, 0x0c600a8f, 0x0c603755, 0x0c609a92, 0x0c60c757, 0x0c612a95, 0x0c615758, 0x0c61ba97, \ 141 | 0x0c624a99, 0x0c62da9a, 0x0c649a9d, 0x0c64c75a, 0x0c652aa0, 0x0c65baa2, 0x0c664aa4, 0x0c692aa8, \ 142 | 0x0c69baaa, 0x0c6d8386, 0x0c6e1389, 0x0c72138e, 0x0c800aab, 0x0c80375c, 0x0c809aae, 0x0c80c75e, \ 143 | 0x0c812ab0, 0x0c81bab2, 0x0c824ab3, 0x0c840ab4, 0x0c843761, 0x0c849ab8, 0x0c84c763, 0x0c852abb, \ 144 | 0x0c85babe, 0x0c864ac0, 0x0c889ac4, 0x0c892ac7, 0x0c89baca, 0x0c8d2ace, 0x0c8d83a0, 0x0ca40ad0, \ 145 | 0x0ca43766, 0x0ca49ad2, 0x0ca52ad4, 0x0ca5bad5, 0x0ca80ad6, 0x0ca83769, 0x0ca89ad9, 0x0ca92adc, \ 146 | 0x0ca9bade, 0x0cac9ae1, 0x0cad2ae4, 0x0cc80ae6, 0x0cc89ae8, 0x0cc92ae9, 0x0ccc0aea, 0x0ccc9aed, \ 147 | 0x0ccd2aef, 0x0cd09af2, 0x0cec0af4, 0x0cec9af5, 0x0cf00af6, 0x0cf09af8, 0x0d80277e, 0x0d80b780, \ 148 | 0x0d814781, 0x0d848b27, 0x0d84b783, 0x0d851b2a, 0x0d85ab2c, 0x0d863b2e, 0x0d891b32, 0x0d89ab34, \ 149 | 0x0da02785, 0x0da0b787, 0x0da4278a, 0x0da48b3b, 0x0da4b78c, 0x0da51b3d, 0x0da5ab3f, 0x0da88b42, \ 150 | 0x0da91b45, 0x0da9ab48, 0x0dad1b4c, 0x0dc4278f, 0x0dc82792, 0x0dc88b51, 0x0dc91b53, 0x0dcc8b55, \ 151 | 0x0dcd1b58, 0x0dec8b5c, 0x0df08b5e, 0x0ea01799, 0x0ea0a79b, 0x0ea1379c, 0x0ea4a79e, 0x0ea90b89, \ 152 | 0x0ea99b8b, 0x0ec4179f, 0x0ec4a7a0, 0x0ecd0b91, 0x10002000, 0x10005801, 0x1000b003, 0x1000e804, \ 153 | 0x10014007, 0x10017807, 0x1004b00d, 0x10051410, 0x10054010, 0x1005a413, 0x10063416, 0x1006c418, \ 154 | 0x1007541a, 0x1009141b, 0x10094017, 0x1009a41f, 0x1009d01a, 0x100a3422, 0x100ac425, 0x100b5427, \ 155 | 0x100da42a, 0x100e342e, 0x100ec431, 0x10120825, 0x10123438, 0x10129827, 0x10132829, 0x1013b82a, \ 156 | 0x1016982c, 0x1017282e, 0x10242023, 0x10245832, 0x1024b027, 0x1024e835, 0x1025402a, 0x1025d02d, \ 157 | 0x1028844e, 0x1028b032, 0x10291451, 0x10294036, 0x1029a454, 0x1029d039, 0x102a3456, 0x102ac458, \ 158 | 0x102b5459, 0x102d145b, 0x102d403e, 0x102da45e, 0x102e3461, 0x102ec463, 0x1031a468, 0x1032346b, \ 159 | 0x1036084e, 0x10369850, 0x10372851, 0x103a9853, 0x10482048, 0x10485858, 0x1048b04b, 0x1049404f, \ 160 | 0x104c8482, 0x104cb056, 0x104d1485, 0x104d4059, 0x104da487, 0x104e3489, 0x104ec48a, 0x1051148c, \ 161 | 0x1051a48f, 0x10523491, 0x1055a496, 0x105a086a, 0x105a986b, 0x106c2065, 0x106cb069, 0x106d406c, \ 162 | 0x107084a8, 0x1070b073, 0x107114aa, 0x1071a4ac, 0x107234ad, 0x107514af, 0x1075a4b1, 0x107e0879, \ 163 | 0x1090207e, 0x1090b081, 0x109484c0, 0x109514c2, 0x1095a4c3, 0x109914c5, 0x10b4208b, 0x10b884ce, \ 164 | 0x10b914cf, 0x11004882, 0x1100a092, 0x1100d885, 0x11013095, 0x11016888, 0x1101c099, 0x1101f88a, \ 165 | 0x1104a09b, 0x1104d88e, 0x1105309f, 0x11056891, 0x1105c0a2, 0x110904ea, 0x110930a6, 0x110994ed, \ 166 | 0x1109c0a9, 0x110a24f0, 0x110ab4f2, 0x110b44f4, 0x110bd4f5, 0x110d94f7, 0x110dc0ad, 0x110e24fa, \ 167 | 0x110eb4fd, 0x110f44ff, 0x11122502, 0x1112b504, 0x111688a3, 0x111718a5, 0x1117a8a6, 0x112010b0, \ 168 | 0x112048a8, 0x1120a0b3, 0x1120d8ac, 0x112130b7, 0x112168af, 0x1121c0ba, 0x112250bd, 0x112410be, \ 169 | 0x112448b6, 0x1124a0c2, 0x1124d8ba, 0x112530c6, 0x112568bd, 0x1125c0ca, 0x1128a0d0, 0x1128d8c5, \ 170 | 0x11290525, 0x112930d4, 0x11299528, 0x1129c0d8, 0x112a252b, 0x112ab52d, 0x112b452f, 0x112d0530, \ 171 | 0x112d30dd, 0x112d9534, 0x112dc0e0, 0x112e2537, 0x112eb53a, 0x112f453c, 0x1131953f, 0x11322543, \ 172 | 0x1132b546, 0x1136254c, 0x113688d9, 0x113718db, 0x113a88dc, 0x113b18dd, 0x114410e8, 0x114448e0, \ 173 | 0x1144a0ec, 0x1144d8e3, 0x114530ef, 0x1145c0f2, 0x114810f5, 0x114848eb, 0x1148a0f9, 0x1148d8ee, \ 174 | 0x114930fd, 0x1149c101, 0x114a5104, 0x114ca107, 0x114d0568, 0x114d310b, 0x114d956b, 0x114dc10e, \ 175 | 0x114e256d, 0x114eb56f, 0x11510570, 0x11513113, 0x11519573, 0x11522576, 0x1152b578, 0x1155957b, \ 176 | 0x1156257e, 0x115a88ff, 0x115e8900, 0x1168111b, 0x11684903, 0x1168a11e, 0x11693121, 0x1169c123, \ 177 | 0x116c1127, 0x116c490b, 0x116ca12b, 0x116d312f, 0x116dc132, 0x1170a137, 0x11710597, 0x1171313a, \ 178 | 0x11719599, 0x1172259b, 0x1175059c, 0x1175959f, 0x117625a1, 0x117995a4, 0x118c1143, 0x118ca146, \ 179 | 0x118d3148, 0x1190114c, 0x1190a150, 0x11913153, 0x119505b4, 0x119595b6, 0x119905b7, 0x119995b9, \ 180 | 0x11b0115c, 0x11b0a15d, 0x11b4115f, 0x11b905c3, 0x11bd05c4, 0x12003925, 0x1200c928, 0x12012169, \ 181 | 0x1201592a, 0x1201b16c, 0x1201e92c, 0x1202416e, 0x1202792d, 0x1202d16f, 0x12049170, 0x1204c92f, \ 182 | 0x12052173, 0x12055932, 0x1205b176, 0x1205e933, 0x12064178, 0x1206d179, 0x1209217a, 0x12095935, \ 183 | 0x1209b17c, 0x120a417e, 0x120d85e1, 0x120db17f, 0x120e15e4, 0x120e4180, 0x120ea5e6, 0x120f35e8, \ 184 | 0x120fc5e9, 0x121215ea, 0x1212a5ec, 0x121335ed, 0x1216a5ee, 0x1220393e, 0x12209184, 0x1220c941, \ 185 | 0x12212187, 0x12215944, 0x1221b18a, 0x1221e946, 0x1222418c, 0x12243949, 0x12249191, 0x1224c94c, \ 186 | 0x12252195, 0x1225594f, 0x1225b199, 0x1225e951, 0x1226419c, 0x1228919e, 0x1228c954, 0x122921a2, \ 187 | 0x12295957, 0x1229b1a5, 0x122a41a8, 0x122d21aa, 0x122d860e, 0x122db1ad, 0x122e1611, 0x122e41af, \ 188 | 0x122ea613, 0x122f3615, 0x12318616, 0x1231b1b0, 0x12321619, 0x1232a61c, 0x1233361e, 0x12361620, \ 189 | 0x1236a622, 0x124001b2, 0x12403961, 0x124091b5, 0x1240c965, 0x124121b8, 0x12415968, 0x1241b1ba, \ 190 | 0x124241bc, 0x124401bd, 0x1244396f, 0x124491c1, 0x1244c973, 0x124521c5, 0x12455976, 0x1245b1c8, \ 191 | 0x124641cb, 0x1246d1cc, 0x124801cd, 0x1248397c, 0x124891d1, 0x1248c97f, 0x124921d6, 0x12495982, \ 192 | 0x1249b1da, 0x124a41de, 0x124ad1e0, 0x124c91e1, 0x124cc986, 0x124d21e4, 0x124d8646, 0x124db1e8, \ 193 | 0x124e1649, 0x124e41eb, 0x124ea64b, 0x125121ed, 0x1251864e, 0x1251b1ef, 0x12521651, 0x1252a654, \ 194 | 0x12558656, 0x12561659, 0x1256a65c, 0x125a1660, 0x126401f2, 0x1264398b, 0x126491f5, 0x1264c98e, \ 195 | 0x126521f7, 0x1265b1f9, 0x126641fa, 0x126801fb, 0x12683996, 0x126891ff, 0x1268c999, 0x12692202, \ 196 | 0x1269b205, 0x126a4207, 0x126c0209, 0x126c399f, 0x126c920d, 0x126cc9a1, 0x126d2211, 0x126db215, \ 197 | 0x126e4218, 0x1270921a, 0x1271221d, 0x12718677, 0x1271b220, 0x12721679, 0x12752223, 0x1275867b, \ 198 | 0x1276167e, 0x12798680, 0x127a1682, 0x12880225, 0x128839a7, 0x12889227, 0x12892229, 0x1289b22a, \ 199 | 0x128c022b, 0x128c39af, 0x128c922e, 0x128d2231, 0x128db233, 0x12900235, 0x129039b5, 0x12909238, \ 200 | 0x1291223c, 0x1291b23f, 0x12958690, 0x12998692, 0x129d8694, 0x12ad2247, 0x12b09249, 0x12b1224a, \ 201 | 0x12b4024b, 0x12b5224e, 0x132029d6, 0x132056b4, 0x1320b9d9, 0x1320e6b6, 0x132149db, 0x1321d9dd, \ 202 | 0x132269de, 0x13248275, 0x1324b9e1, 0x13251279, 0x132549e3, 0x1325a27c, 0x1325d9e5, 0x1326327f, \ 203 | 0x13291284, 0x132949e7, 0x1329a287, 0x132a3289, 0x132da28c, 0x132e328e, 0x133206cf, 0x133296d1, \ 204 | 0x133326d3, 0x133696d5, 0x134029e9, 0x134056d8, 0x1340b9ec, 0x134149ef, 0x1341d9f1, 0x134429f4, \ 205 | 0x134456dd, 0x1344829a, 0x1344b9f7, 0x1345129d, 0x134549fa, 0x1345a2a0, 0x1345d9fc, 0x134632a2, \ 206 | 0x1346c2a4, 0x134882a7, 0x1348b9ff, 0x134912ab, 0x13494a02, 0x1349a2af, 0x134a32b2, 0x134ac2b4, \ 207 | 0x134d12b7, 0x134da2ba, 0x134e32bd, 0x1351a2bf, 0x135206f1, 0x135296f3, 0x135606f5, 0x135696f8, \ 208 | 0x13602a05, 0x1360ba09, 0x13614a0c, 0x13642a13, 0x136482c9, 0x1364ba17, 0x136512cc, 0x13654a1a, \ 209 | 0x1365a2ce, 0x136632d0, 0x13682a20, 0x136882d3, 0x1368ba23, 0x136912d7, 0x13694a26, 0x1369a2da, \ 210 | 0x136a32dd, 0x136c82e1, 0x136cba2b, 0x136d12e5, 0x136da2e9, 0x136e32ed, 0x137112f0, 0x1371a2f3, \ 211 | 0x13720710, 0x13760713, 0x137a0716, 0x13842a30, 0x1384ba33, 0x13882a3b, 0x138882fd, 0x1388ba3e, \ 212 | 0x138912ff, 0x1389a301, 0x138c2a44, 0x138c8304, 0x138cba46, 0x138d1307, 0x138da30a, 0x1390830e, \ 213 | 0x13911311, 0x1391a315, 0x13951319, 0x13a82a4c, 0x13ac2a54, 0x13ac831c, 0x13ad131d, 0x13b02a5a, \ 214 | 0x13b0831e, 0x13b48321, 0x14401a7c, 0x14404745, 0x1440aa7f, 0x1440d746, 0x14413a81, 0x1441ca83, \ 215 | 0x14425a84, 0x1444aa87, 0x14453a89, 0x1445ca8b, 0x1449035e, 0x14493a8e, 0x14499361, 0x144a2364, \ 216 | 0x144d9369, 0x144e236b, 0x14568754, 0x14601a90, 0x14604756, 0x1460aa93, 0x14613a96, 0x1461ca98, \ 217 | 0x14641a9b, 0x14644759, 0x1464aa9e, 0x14653aa1, 0x1465caa3, 0x1468aaa6, 0x14690379, 0x14693aa9, \ 218 | 0x1469937c, 0x146a237e, 0x146d0383, 0x146d9387, 0x146e238a, 0x1471938d, 0x14801aac, 0x1480aaaf, \ 219 | 0x14813ab1, 0x14841ab5, 0x1484aab9, 0x14853abc, 0x14881ac2, 0x1488aac5, 0x14890398, 0x14893ac8, \ 220 | 0x1489939a, 0x148caacd, 0x148d039e, 0x14a41ad1, 0x14a4aad3, 0x14a81ad7, 0x14a8aada, 0x14ac1ae0, \ 221 | 0x14acaae2, 0x14c81ae7, 0x14cc1aeb, 0x14d01af1, 0x15600b0c, 0x1560377c, 0x15609b0f, 0x1560c77d, \ 222 | 0x15612b11, 0x1561bb13, 0x15624b14, 0x15649b17, 0x15652b19, 0x1565bb1b, 0x15692b1e, 0x15800b1f, \ 223 | 0x1580377f, 0x15809b21, 0x15812b23, 0x1581bb24, 0x15840b25, 0x15843782, 0x15849b28, 0x15852b2b, \ 224 | 0x1585bb2d, 0x15889b30, 0x15892b33, 0x15a00b35, 0x15a09b37, 0x15a12b38, 0x15a40b39, 0x15a49b3c, \ 225 | 0x15a52b3e, 0x15a80b40, 0x15a89b43, 0x15a92b46, 0x15ac9b4b, 0x15c40b4e, 0x15c49b4f, 0x15c80b50, \ 226 | 0x15c89b52, 0x15cc0b54, 0x15cc9b56, 0x15e80b5a, 0x15ec0b5b, 0x15f00b5d, 0x16802797, 0x1680b798, \ 227 | 0x16848b78, 0x16851b7a, 0x1685ab7c, 0x16891b7f, 0x16a0279a, 0x16a4279d, 0x16a48b83, 0x16a51b85, \ 228 | 0x16a88b87, 0x16a91b8a, 0x16c88b8e, 0x16cc8b90, 0x18003001, 0x18009402, 0x1800c004, 0x18012405, \ 229 | 0x1801b408, 0x1802440a, 0x1802d40c, 0x1804940d, 0x1804c00e, 0x18052411, 0x18055011, 0x1805b414, \ 230 | 0x18064417, 0x1806d419, 0x1809241c, 0x1809b420, 0x180a4423, 0x180d881f, 0x180db42b, 0x180e1821, \ 231 | 0x180ea823, 0x180f3824, 0x18121826, 0x1812a828, 0x18240442, 0x18243024, 0x18249445, 0x1824c028, \ 232 | 0x18252448, 0x1825b44a, 0x1826444c, 0x1826d44d, 0x1828944f, 0x1828c033, 0x18292452, 0x1829b455, \ 233 | 0x182a4457, 0x182d245c, 0x182db45f, 0x1831884a, 0x1832184c, 0x1832a84d, 0x1836184f, 0x18480479, \ 234 | 0x18483049, 0x1848947c, 0x1848c04c, 0x1849247e, 0x1849b480, 0x184a4481, 0x184c9483, 0x184d2486, \ 235 | 0x184db488, 0x1851248d, 0x18558868, 0x18561869, 0x186c04a2, 0x186c3066, 0x186c94a4, 0x186d24a6, \ 236 | 0x186db4a7, 0x187094a9, 0x187124ab, 0x18798878, 0x189004bc, 0x189094be, 0x189124bf, 0x189494c1, \ 237 | 0x18b404cc, 0x18b494cd, 0x1900208f, 0x19005883, 0x1900b093, 0x1900e886, 0x19014096, 0x190484de, \ 238 | 0x1904b09c, 0x190514e1, 0x190540a0, 0x1905a4e4, 0x190634e6, 0x1906c4e8, 0x190754e9, 0x190914eb, \ 239 | 0x190940a7, 0x1909a4ee, 0x190a34f1, 0x190ac4f3, 0x190da4f8, 0x190e34fb, 0x1912089f, 0x191298a1, \ 240 | 0x191328a2, 0x191698a4, 0x192020b1, 0x192058a9, 0x1920b0b4, 0x192140b8, 0x1921d0bb, 0x192420bf, \ 241 | 0x192458b7, 0x19248517, 0x1924b0c3, 0x1925151a, 0x192540c7, 0x1925a51d, 0x1925d0cb, 0x1926351f, \ 242 | 0x1926c521, 0x19288522, 0x1928b0d1, 0x19291526, 0x192940d5, 0x1929a529, 0x192a352c, 0x192ac52e, \ 243 | 0x192d1531, 0x192da535, 0x192e3538, 0x1931a540, 0x193208d5, 0x193298d7, 0x193608d8, 0x193698da, \ 244 | 0x194420e9, 0x1944b0ed, 0x194540f0, 0x194820f6, 0x1948855e, 0x1948b0fa, 0x19491561, 0x194940fe, \ 245 | 0x1949a563, 0x194a3565, 0x194c8566, 0x194cb108, 0x194d1569, 0x194da56c, 0x194e356e, 0x19511571, \ 246 | 0x1951a574, 0x195608fd, 0x195a08fe, 0x1968211c, 0x1968b11f, 0x196c2128, 0x196c8590, 0x196cb12c, \ 247 | 0x196d1592, 0x196da594, 0x19708595, 0x19711598, 0x1971a59a, 0x1975159d, 0x198c2144, 0x1990214d, \ 248 | 0x199085b0, 0x199115b2, 0x199485b3, 0x199515b5, 0x19b485c1, 0x19b885c2, 0x1a001164, 0x1a004926, \ 249 | 0x1a00a167, 0x1a00d929, 0x1a01316a, 0x1a01692b, 0x1a01c16d, 0x1a04a171, 0x1a04d930, 0x1a053174, \ 250 | 0x1a05c177, 0x1a0905d8, 0x1a09317b, 0x1a0995db, 0x1a09c17d, 0x1a0a25dd, 0x1a0ab5df, 0x1a0b45e0, \ 251 | 0x1a0d95e2, 0x1a0e25e5, 0x1a0eb5e7, 0x1a1225eb, 0x1a16893b, 0x1a17193c, 0x1a201181, 0x1a20493f, \ 252 | 0x1a20a185, 0x1a20d942, 0x1a213188, 0x1a21c18b, 0x1a22518d, 0x1a24118e, 0x1a24494a, 0x1a24a192, \ 253 | 0x1a24d94d, 0x1a253196, 0x1a25c19a, 0x1a28a19f, 0x1a290604, 0x1a2931a3, 0x1a299607, 0x1a29c1a6, \ 254 | 0x1a2a2609, 0x1a2ab60b, 0x1a2d060c, 0x1a2d31ab, 0x1a2d960f, 0x1a2e2612, 0x1a2eb614, 0x1a319617, \ 255 | 0x1a32261a, 0x1a36895e, 0x1a3a895f, 0x1a4011b3, 0x1a404962, 0x1a40a1b6, 0x1a4131b9, 0x1a41c1bb, \ 256 | 0x1a4411be, 0x1a444970, 0x1a44a1c2, 0x1a4531c6, 0x1a45c1c9, 0x1a4811ce, 0x1a48497d, 0x1a48a1d2, \ 257 | 0x1a49063c, 0x1a4931d7, 0x1a49963f, 0x1a49c1db, 0x1a4a2641, 0x1a4ca1e2, 0x1a4d0644, 0x1a4d31e5, \ 258 | 0x1a4d9647, 0x1a4e264a, 0x1a51064c, 0x1a51964f, 0x1a522652, 0x1a559657, 0x1a6411f3, 0x1a64a1f6, \ 259 | 0x1a6531f8, 0x1a6811fc, 0x1a68a200, 0x1a693203, 0x1a6c120a, 0x1a6ca20e, 0x1a6d0671, 0x1a6d3212, \ 260 | 0x1a6d9673, 0x1a70a21b, 0x1a710675, 0x1a719678, 0x1a75067a, 0x1a75967c, 0x1a881226, 0x1a88a228, \ 261 | 0x1a8c122c, 0x1a8ca22f, 0x1a901236, 0x1a90a239, 0x1a91068d, 0x1a95068f, 0x1a990691, 0x1aac1246, \ 262 | 0x1ab01248, 0x1b0039ca, 0x1b009251, 0x1b00c9cc, 0x1b012253, 0x1b0159ce, 0x1b01b256, 0x1b01e9cf, \ 263 | 0x1b024258, 0x1b04925a, 0x1b04c9d0, 0x1b05225d, 0x1b0559d1, 0x1b05b25f, 0x1b064261, 0x1b06d262, \ 264 | 0x1b092263, 0x1b09b265, 0x1b0a4266, 0x1b0d86aa, 0x1b0db267, 0x1b0e16ac, 0x1b0ea6ae, 0x1b0f36af, \ 265 | 0x1b1216b0, 0x1b12a6b1, 0x1b200268, 0x1b2039d7, 0x1b20926b, 0x1b20c9da, 0x1b21226d, 0x1b2159dc, \ 266 | 0x1b21b26f, 0x1b224271, 0x1b240272, 0x1b2439df, 0x1b249276, 0x1b24c9e2, 0x1b25227a, 0x1b2559e4, \ 267 | 0x1b25b27d, 0x1b264280, 0x1b26d281, 0x1b289282, 0x1b28c9e6, 0x1b292285, 0x1b29b288, 0x1b2a428a, \ 268 | 0x1b2d228b, 0x1b2d86c8, 0x1b2db28d, 0x1b2e16ca, 0x1b2ea6cc, 0x1b3186cd, 0x1b3216d0, 0x1b32a6d2, \ 269 | 0x1b3616d4, 0x1b40028f, 0x1b4039ea, 0x1b409292, 0x1b40c9ed, 0x1b412294, 0x1b41b296, 0x1b424297, \ 270 | 0x1b440298, 0x1b4439f5, 0x1b44929b, 0x1b44c9f8, 0x1b45229e, 0x1b45b2a1, 0x1b4642a3, 0x1b4802a5, \ 271 | 0x1b4839fe, 0x1b4892a8, 0x1b48ca00, 0x1b4922ac, 0x1b49b2b0, 0x1b4a42b3, 0x1b4c92b5, 0x1b4d22b8, \ 272 | 0x1b4d86eb, 0x1b4db2bb, 0x1b4e16ed, 0x1b5122be, 0x1b5186ef, 0x1b5216f2, 0x1b5586f4, 0x1b5616f6, \ 273 | 0x1b6002c1, 0x1b603a06, 0x1b6092c3, 0x1b6122c5, 0x1b61b2c6, 0x1b6402c7, 0x1b643a14, 0x1b6492ca, \ 274 | 0x1b6522cd, 0x1b65b2cf, 0x1b6802d1, 0x1b683a21, 0x1b6892d4, 0x1b6922d8, 0x1b69b2db, 0x1b6c02df, \ 275 | 0x1b6c3a2a, 0x1b6c92e2, 0x1b6d22e6, 0x1b6d870c, 0x1b6db2ea, 0x1b7092ef, 0x1b7122f1, 0x1b71870f, \ 276 | 0x1b758712, 0x1b798715, 0x1b8402f7, 0x1b8492f9, 0x1b8522fa, 0x1b8802fb, 0x1b8892fe, 0x1b892300, \ 277 | 0x1b8c0302, 0x1b8c9305, 0x1b8d2308, 0x1b90030c, 0x1b90930f, 0x1b912312, 0x1ba8931b, 0x1c202a72, \ 278 | 0x1c205732, 0x1c20ba74, 0x1c214a76, 0x1c21da77, 0x1c24833a, 0x1c24ba79, 0x1c25133d, 0x1c254a7b, \ 279 | 0x1c25a33f, 0x1c263341, 0x1c26c343, 0x1c291346, 0x1c29a348, 0x1c2a334a, 0x1c2da34c, 0x1c320741, \ 280 | 0x1c329743, 0x1c402a7d, 0x1c40ba80, 0x1c414a82, 0x1c442a85, 0x1c448352, 0x1c44ba88, 0x1c451355, \ 281 | 0x1c454a8a, 0x1c45a357, 0x1c463359, 0x1c48835b, 0x1c48ba8d, 0x1c49135f, 0x1c49a362, 0x1c4a3365, \ 282 | 0x1c4d1367, 0x1c4da36a, 0x1c520751, 0x1c560753, 0x1c602a91, 0x1c60ba94, 0x1c642a9c, 0x1c648370, \ 283 | 0x1c64ba9f, 0x1c651372, 0x1c65a374, 0x1c682aa5, 0x1c688377, 0x1c68baa7, 0x1c69137a, 0x1c69a37d, \ 284 | 0x1c6c8381, 0x1c6d1384, 0x1c6da388, 0x1c71138c, 0x1c802aad, 0x1c842ab6, 0x1c848392, 0x1c851394, \ 285 | 0x1c882ac3, 0x1c888396, 0x1c891399, 0x1c8c2acc, 0x1c8c839c, 0x1c8d139f, 0x1d401b02, 0x1d40477b, \ 286 | 0x1d40ab04, 0x1d413b06, 0x1d41cb07, 0x1d44ab09, 0x1d453b0b, 0x1d601b0d, 0x1d60ab10, 0x1d613b12, \ 287 | 0x1d641b15, 0x1d64ab18, 0x1d653b1a, 0x1d68ab1d, 0x1d801b20, 0x1d80ab22, 0x1d841b26, 0x1d84ab29, \ 288 | 0x1d881b2f, 0x1d88ab31, 0x1da01b36, 0x1da41b3a, 0x1da81b41, 0x1dac1b4a, 0x1e600b68, 0x1e603796, \ 289 | 0x1e609b6a, 0x1e612b6c, 0x1e61bb6d, 0x1e649b6f, 0x1e652b71, 0x1e800b72, 0x1e809b74, 0x1e812b75, \ 290 | 0x1e840b76, 0x1e849b79, 0x1e852b7b, 0x1e889b7e, 0x1ea00b80, 0x1ea09b81, 0x1ea40b82, 0x1ea49b84, \ 291 | 0x1ea80b86, 0x1ea89b88, 0x1ec40b8c, 0x1ec80b8d, 0x1ecc0b8f, 0x1f848b9d, 0x1f851b9f, 0x20001400, \ 292 | 0x20004002, 0x2000a403, 0x2000d005, 0x20013406, 0x2001c409, 0x2002540b, 0x2004a40e, 0x20053412, \ 293 | 0x2005c415, 0x20090816, 0x2009341d, 0x20099819, 0x200a281c, 0x200ab81e, 0x200d9820, 0x200e2822, \ 294 | 0x20168022, 0x20241443, 0x20244025, 0x2024a446, 0x20253449, 0x2025c44b, 0x2028a450, 0x20293453, \ 295 | 0x202d0844, 0x202d9847, 0x202e2849, 0x2031984b, 0x2048147a, 0x2048a47d, 0x2049347f, 0x204ca484, \ 296 | 0x20510865, 0x20519867, 0x206c14a3, 0x206ca4a5, 0x20750877, 0x209014bd, 0x210004d2, 0x21003090, \ 297 | 0x210094d5, 0x2100c094, 0x210124d8, 0x21015097, 0x2101b4da, 0x210244dc, 0x2102d4dd, 0x210494df, \ 298 | 0x2104c09d, 0x210524e2, 0x2105b4e5, 0x210644e7, 0x210924ec, 0x2109b4ef, 0x210d889b, 0x210e189d, \ 299 | 0x210ea89e, 0x211218a0, 0x21200509, 0x212030b2, 0x2120950c, 0x2120c0b5, 0x2121250f, 0x2121b512, \ 300 | 0x21224513, 0x21240514, 0x212430c0, 0x21249518, 0x2124c0c4, 0x2125251b, 0x2125b51e, 0x21264520, \ 301 | 0x21289523, 0x21292527, 0x2129b52a, 0x212d2532, 0x212d88d0, 0x212e18d3, 0x213188d4, 0x213218d6, \ 302 | 0x21440553, 0x214430ea, 0x21449556, 0x21452559, 0x2145b55b, 0x2148055c, 0x214830f7, 0x2148955f, \ 303 | 0x21492562, 0x2149b564, 0x214c9567, 0x214d256a, 0x215188fb, 0x215588fc, 0x21680588, 0x2168958b, \ 304 | 0x2169258d, 0x216c058e, 0x216c9591, 0x216d2593, 0x21709596, 0x218c05ac, 0x218c95ae, 0x219005af, \ 305 | 0x219095b1, 0x21b005bf, 0x21b405c0, 0x22002165, 0x22005927, 0x2200b168, 0x2201416b, 0x220485cf, \ 306 | 0x2204b172, 0x220515d2, 0x22054175, 0x2205a5d4, 0x220635d6, 0x2206c5d7, 0x220915d9, 0x2209a5dc, \ 307 | 0x220a35de, 0x220da5e3, 0x22120939, 0x2212993a, 0x22202182, 0x2220b186, 0x22214189, 0x2224218f, \ 308 | 0x222485fa, 0x2224b193, 0x222515fd, 0x22254197, 0x2225a5ff, 0x22263601, 0x22288602, 0x2228b1a0, \ 309 | 0x22291605, 0x2229a608, 0x222a360a, 0x222d160d, 0x222da610, 0x2232095c, 0x2236095d, 0x224021b4, \ 310 | 0x2240b1b7, 0x224421bf, 0x22448630, 0x2244b1c3, 0x22451634, 0x2245a637, 0x224821cf, 0x2248863a, \ 311 | 0x2248b1d3, 0x2249163d, 0x2249a640, 0x224c8642, 0x224d1645, 0x224da648, 0x2251164d, 0x226421f4, \ 312 | 0x226821fd, 0x2268866a, 0x2269166d, 0x226c220b, 0x226c866f, 0x226d1672, 0x22708674, 0x22711676, \ 313 | 0x228c868a, 0x2290868c, 0x2294868e, 0x23001250, 0x230049cb, 0x2300a252, 0x2300d9cd, 0x23013254, \ 314 | 0x2301c257, 0x23025259, 0x2304a25b, 0x2305325e, 0x2305c260, 0x230906a4, 0x23093264, 0x230996a6, \ 315 | 0x230a26a8, 0x230ab6a9, 0x230d96ab, 0x230e26ad, 0x231689d5, 0x23201269, 0x232049d8, 0x2320a26c, \ 316 | 0x2321326e, 0x2321c270, 0x23241273, 0x232449e0, 0x2324a277, 0x2325327b, 0x2325c27e, 0x2328a283, \ 317 | 0x232906c1, 0x23293286, 0x232996c3, 0x232a26c5, 0x232d06c6, 0x232d96c9, 0x232e26cb, 0x233196ce, \ 318 | 0x23401290, 0x2340a293, 0x23413295, 0x23441299, 0x2344a29c, 0x2345329f, 0x234812a6, 0x2348a2a9, \ 319 | 0x234906e5, 0x234932ad, 0x234996e7, 0x234ca2b6, 0x234d06e9, 0x234d96ec, 0x235106ee, 0x235196f0, \ 320 | 0x236012c2, 0x2360a2c4, 0x236412c8, 0x2364a2cb, 0x236812d2, 0x2368a2d5, 0x23690708, 0x236c12e0, \ 321 | 0x236ca2e3, 0x236d070b, 0x2371070e, 0x23750711, 0x238412f8, 0x238812fc, 0x238c1303, 0x2390130d, \ 322 | 0x24000325, 0x24003a6d, 0x24009326, 0x2400ca6f, 0x24012328, 0x24015a70, 0x2401b329, 0x2402432a, \ 323 | 0x2404932b, 0x2404ca71, 0x2405232c, 0x2405b32d, 0x2406432e, 0x2409232f, 0x2409b330, 0x240d872c, \ 324 | 0x240e172e, 0x240ea72f, 0x24121730, 0x24200331, 0x24203a73, 0x24209332, 0x2420ca75, 0x24212334, \ 325 | 0x2421b336, 0x24224337, 0x24240338, 0x24243a78, 0x2424933b, 0x2424ca7a, 0x2425233e, 0x2425b340, \ 326 | 0x24264342, 0x24289344, 0x24292347, 0x2429b349, 0x242d234b, 0x242d873d, 0x242e173f, 0x24318740, \ 327 | 0x24321742, 0x2440034d, 0x24403a7e, 0x2440934e, 0x2441234f, 0x2441b350, 0x24440351, 0x24443a86, \ 328 | 0x24449353, 0x24452356, 0x2445b358, 0x2448035a, 0x24483a8c, 0x2448935c, 0x24492360, 0x2449b363, \ 329 | 0x244c9366, 0x244d2368, 0x244d874e, 0x24518750, 0x24558752, 0x2460036c, 0x2460936d, 0x2461236e, \ 330 | 0x2464036f, 0x24649371, 0x24652373, 0x24680375, 0x24689378, 0x2469237b, 0x246c037f, 0x246c9382, \ 331 | 0x246d2385, 0x2470938b, 0x2480038f, 0x24809390, 0x24840391, 0x24849393, 0x24880395, 0x24889397, \ 332 | 0x248c039b, 0x248c939d, 0x25202afc, 0x2520bafe, 0x25214aff, 0x2524bb01, 0x2532077a, 0x25402b03, \ 333 | 0x2540bb05, 0x25442b08, 0x2544bb0a, 0x25602b0e, 0x25642b16, 0x25682b1c, 0x26401b62, 0x2640ab64, \ 334 | 0x26413b65, 0x2644ab67, 0x26601b69, 0x2660ab6b, 0x26641b6e, 0x2664ab70, 0x26801b73, 0x26841b77, \ 335 | 0x26881b7d, 0x27600b94, 0x27609b96, 0x27612b97, 0x27649b99, 0x27800b9a, 0x27809b9b, 0x27840b9c, \ 336 | 0x27849b9e, 0x27a00ba0, 0x28002401, 0x2800b404, 0x28014407, 0x2804880b, 0x2804b40f, 0x2805180e, \ 337 | 0x2805a811, 0x28063814, 0x28091817, 0x2809a81a, 0x28242444, 0x2824b447, 0x2828883b, 0x2829183e, \ 338 | 0x2829a841, 0x282d1845, 0x2848247b, 0x284c885f, 0x284d1862, 0x28708874, 0x290014d3, 0x29004091, \ 339 | 0x2900a4d6, 0x290134d9, 0x2901c4db, 0x2904a4e0, 0x290534e3, 0x29090895, 0x29099898, 0x290a289a, \ 340 | 0x290d989c, 0x2920150a, 0x2920a50d, 0x29213510, 0x29241515, 0x2924a519, 0x2925351c, 0x2928a524, \ 341 | 0x292908c6, 0x292998ca, 0x292d08ce, 0x292d98d1, 0x29441554, 0x2944a557, 0x2948155d, 0x2948a560, \ 342 | 0x294d08f6, 0x295108fa, 0x29681589, 0x296c158f, 0x2a0005c6, 0x2a003166, 0x2a0095c9, 0x2a0125cb, \ 343 | 0x2a01b5cd, 0x2a0245ce, 0x2a0495d0, 0x2a0525d3, 0x2a05b5d5, 0x2a0925da, 0x2a0d8937, 0x2a0e1938, \ 344 | 0x2a2005ef, 0x2a203183, 0x2a2095f2, 0x2a2125f5, 0x2a21b5f7, 0x2a2405f8, 0x2a243190, 0x2a2495fb, \ 345 | 0x2a2525fe, 0x2a25b600, 0x2a289603, 0x2a292606, 0x2a2d895a, 0x2a31895b, 0x2a400624, 0x2a409627, \ 346 | 0x2a41262a, 0x2a44062d, 0x2a449631, 0x2a452635, 0x2a480638, 0x2a48963b, 0x2a49263e, 0x2a4c9643, \ 347 | 0x2a640662, 0x2a649665, 0x2a680668, 0x2a68966b, 0x2a6c066e, 0x2a6c9670, 0x2a880686, 0x2a8c0689, \ 348 | 0x2a90068b, 0x2b014255, 0x2b04869e, 0x2b04b25c, 0x2b0516a0, 0x2b05a6a2, 0x2b0636a3, 0x2b0916a5, \ 349 | 0x2b09a6a7, 0x2b1209d4, 0x2b20226a, 0x2b242274, 0x2b2486ba, 0x2b24b278, 0x2b2516bc, 0x2b25a6be, \ 350 | 0x2b2886bf, 0x2b2916c2, 0x2b29a6c4, 0x2b2d16c7, 0x2b402291, 0x2b4486de, 0x2b4516e1, 0x2b4886e3, \ 351 | 0x2b4916e6, 0x2b4c86e8, 0x2b4d16ea, 0x2b648702, 0x2b688707, 0x2b6c870a, 0x2b70870d, 0x2c004a6e, \ 352 | 0x2c00a327, 0x2c090728, 0x2c09972a, 0x2c0a272b, 0x2c0d972d, 0x2c20a333, 0x2c213335, 0x2c241339, \ 353 | 0x2c24a33c, 0x2c28a345, 0x2c290739, 0x2c29973b, 0x2c2d073c, 0x2c2d973e, 0x2c44a354, 0x2c48a35d, \ 354 | 0x2c49074b, 0x2c4d074d, 0x2c51074f, 0x2c681376, 0x2c6c1380, 0x2d003afa, 0x2d00cafb, 0x2d0d8772, \ 355 | 0x2d0e1773, 0x2d203afd, 0x2d243b00, 0x2d2d8778, 0x2d318779, 0x2e202b60, 0x2e20bb61, 0x2e402b63, \ 356 | 0x2e442b66, 0x2f401b92, 0x2f40ab93, 0x2f601b95, 0x2f641b98, 0x310024d4, 0x3100b4d7, 0x3104888c, \ 357 | 0x3105188f, 0x3105a892, 0x31091896, 0x31242516, 0x312888c3, 0x312918c7, 0x314c88f4, 0x320015c7, \ 358 | 0x3200a5ca, 0x320135cc, 0x3204a5d1, 0x32090934, 0x32099936, 0x322015f0, 0x3220a5f3, 0x322415f9, \ 359 | 0x3224a5fc, 0x32290955, 0x322d0959, 0x3244162e, 0x32481639, 0x33000698, 0x3300969a, 0x3301269c, \ 360 | 0x3301b69d, 0x3304969f, 0x330526a1, 0x330d89d3, 0x332006b2, 0x332096b5, 0x332126b7, 0x332406b8, \ 361 | 0x332496bb, 0x332526bd, 0x332896c0, 0x334006d6, 0x334096d9, 0x334406dc, 0x334496df, 0x334806e2, \ 362 | 0x334896e4, 0x33640700, 0x33680706, 0x336c0709, 0x34048724, 0x34051726, 0x3405a727, 0x34091729, \ 363 | 0x34248735, 0x34251737, 0x34288738, 0x3429173a, 0x34448748, 0x3448874a, 0x344c874c, 0x35090770, \ 364 | 0x35099771, 0x35290776, 0x352d0777, 0x3a0025c8, 0x3a04892e, 0x3a051931, 0x3a288953, 0x3b001699, \ 365 | 0x3b00a69b, 0x3b0909d2, 0x3b2016b3, 0x3b2416b9, 0x3c000721, 0x3c009722, 0x3c012723, 0x3c049725, \ 366 | 0x3c200731, 0x3c209733, 0x3c240734, 0x3c249736, 0x3c400744, 0x3c440747, 0x3c480749, 0x3d04876e, \ 367 | 0x3d05176f, 0x3d248774, 0x3d288775, \ 368 | ]) 369 | -------------------------------------------------------------------------------- /python/src/pdf417decoder/__init__.py: -------------------------------------------------------------------------------- 1 | from pdf417decoder.Decoder import PDF417Decoder 2 | 3 | __all__ = [PDF417Decoder] -------------------------------------------------------------------------------- /python/tests/binary_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/binary_data.png -------------------------------------------------------------------------------- /python/tests/blurred_error_correction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/blurred_error_correction.png -------------------------------------------------------------------------------- /python/tests/byte_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/byte_mode.png -------------------------------------------------------------------------------- /python/tests/character_type_switches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/character_type_switches.txt -------------------------------------------------------------------------------- /python/tests/character_type_transitions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/character_type_transitions.png -------------------------------------------------------------------------------- /python/tests/macropdf_part1and2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/macropdf_part1and2.png -------------------------------------------------------------------------------- /python/tests/macropdf_part3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/macropdf_part3.png -------------------------------------------------------------------------------- /python/tests/missing_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/missing_data.png -------------------------------------------------------------------------------- /python/tests/multiple_barcodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/multiple_barcodes.png -------------------------------------------------------------------------------- /python/tests/rotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/rotated.png -------------------------------------------------------------------------------- /python/tests/tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from PIL import Image as PIL 4 | from pdf417decoder import PDF417Decoder 5 | 6 | def test_rotated(): 7 | # given an image that has been rotated 8 | image = PIL.open("tests/rotated.png") 9 | 10 | # when we decode the image 11 | decoder = PDF417Decoder(image) 12 | barcode_count = decoder.decode() 13 | 14 | # then the message should be decoded 15 | assert barcode_count == 1 16 | assert decoder.barcode_data_index_to_string(0) == "Rotated Image Test" 17 | 18 | def test_blurred(): 19 | # given an image that has errors due to blurring 20 | image = PIL.open("tests/blurred_error_correction.png") 21 | 22 | # when we decode the image 23 | decoder = PDF417Decoder(image) 24 | barcode_count = decoder.decode() 25 | 26 | # then the message should be decoded 27 | assert barcode_count == 1 28 | assert decoder.barcode_data_index_to_string(0) == "Blurred Image Test: Additional data is being added to this test increase error count. ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" 29 | 30 | def test_missing_data(): 31 | # given an image that is upside down 32 | image = PIL.open("tests/missing_data.png") 33 | 34 | # when we decode the image 35 | decoder = PDF417Decoder(image) 36 | barcode_count = decoder.decode() 37 | 38 | # then the message should be decoded 39 | assert barcode_count == 1 40 | assert decoder.barcode_data_index_to_string(0) == "Barcode with missing data codewords." 41 | 42 | def test_character_decodes(): 43 | # given an image that has a barcode with each character type transition 44 | # permutation (Upper, Lower, Mixed and Punctuation) 45 | image = PIL.open("tests/character_type_transitions.png") 46 | 47 | # when we decode the image 48 | decoder = PDF417Decoder(image) 49 | barcode_count = decoder.decode() 50 | 51 | # then the message should be decoded 52 | assert barcode_count == 1 53 | assert decoder.barcode_data_index_to_string(0) == "Character Type Switches Test: AaAAA1A@bbbBb1b@1c1C1111@@d@D@1@@A aA AA 1A @b bb Bb 1b @1 c1 C1 11 1@@ d@ D@ 1@ @" 54 | 55 | def test_binary_data(): 56 | # given an image that has a barcode with binary data 57 | image = PIL.open("tests/binary_data.png") 58 | 59 | # when we decode the image 60 | decoder = PDF417Decoder(image) 61 | barcode_count = decoder.decode() 62 | 63 | # then the message should be decoded 64 | assert barcode_count == 1 65 | assert decoder.barcode_data_index_to_string(0) == "Pdf417DecoderDemo - Rev 1.0.0 - 2019-05-01 © 2019 Uzi Granot. All rights reserved." 66 | 67 | def test_byte_mode_data(): 68 | # given an image that has a barcode with a byte mode block 69 | image = PIL.open("tests/byte_mode.png") 70 | 71 | # when we decode the image 72 | decoder = PDF417Decoder(image) 73 | barcode_count = decoder.decode() 74 | 75 | # then the message should match the expected binary data block 76 | assert barcode_count == 1 77 | assert decoder.barcode_binary_data == bytearray(b"\x05\x01\xff\xff\x00\x00062S;Gp\x00\xf2\xed\x10\x00\x00\x14\x1e\x00VR3\x01Y3\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x02\x00\x00\\\xda\x00\x008034\xaeiW\rYB\x1c\xd4\x0b\x00\xf2\xd3\x7fO\xf8\xefiS\xa0\xaa\xfb\x9b\xcf0\x16\x13\xc3\x08>\x86Jz\xe8L\xfe\x1f\xebM,R\x05\x00o3\x01\x00") 78 | 79 | def test_macropdf(): 80 | # given an image that has two parts of a multi-bar code datum 81 | image = PIL.open("tests/macropdf_part1and2.png") 82 | # and another image that the thirst part (e.g. 2nd page_ 83 | image2 = PIL.open("tests/macropdf_part3.png") 84 | 85 | info_list = [] 86 | # when we decode first page 87 | decoder = PDF417Decoder(image) 88 | barcode_count = decoder.decode() 89 | assert barcode_count == 2 90 | info_list.extend(decoder.barcodes_info) 91 | 92 | # and the 2nd page 93 | decoder = PDF417Decoder(image2) 94 | barcode_count = decoder.decode() 95 | assert barcode_count == 1 96 | info_list.extend(decoder.barcodes_info) 97 | 98 | assert len(info_list) == 3 99 | assert PDF417Decoder.assemble_data(info_list) == b'One Two Three' 100 | 101 | def test_multiple_barcodes(): 102 | # given an image that has multiple barcodes 103 | image = PIL.open("tests/multiple_barcodes.png") 104 | 105 | # when we decode the image 106 | decoder = PDF417Decoder(image) 107 | barcode_count = decoder.decode() 108 | 109 | # then multiple barcodes should be decoded 110 | assert barcode_count == 2 111 | assert decoder.barcode_data_index_to_string(0) == "Multiple" 112 | assert decoder.barcode_data_index_to_string(1) == "Barcodes Test" 113 | 114 | def test_upside_down(): 115 | # given an image that is upside down 116 | image = PIL.open("tests/upside_down.png") 117 | 118 | # when we decode the image 119 | decoder = PDF417Decoder(image) 120 | barcode_count = decoder.decode() 121 | 122 | # then the message should be decoded 123 | assert barcode_count == 1 124 | assert decoder.barcode_data_index_to_string(0) == "Upside Down Test" 125 | 126 | -------------------------------------------------------------------------------- /python/tests/upside_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfish/pdf417decoder/08c01172b7150bb2d2c0591566f43d45f9294fac/python/tests/upside_down.png --------------------------------------------------------------------------------