mirror of
				https://github.com/python-escpos/python-escpos
				synced 2025-10-23 09:30:00 +00:00 
			
		
		
		
	Merge branch 'master' into development
This commit is contained in:
		
							
								
								
									
										23
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # To get started with Dependabot version updates, you'll need to specify which | ||||
| # package ecosystems to update and where the package manifests are located. | ||||
| # Please see the documentation for all configuration options: | ||||
| # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | ||||
|  | ||||
| version: 2 | ||||
| updates: | ||||
|   - package-ecosystem: "pip" # See documentation for possible values | ||||
|     directory: "/" # Location of package manifests | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
|   - package-ecosystem: "pip" | ||||
|     directory: "/doc" | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
|   - package-ecosystem: "gitsubmodule" | ||||
|     directory: "/" | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
|   - package-ecosystem: "github-actions" | ||||
|     directory: "/" | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
							
								
								
									
										10
									
								
								.github/workflows/black.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/black.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| name: Lint | ||||
|  | ||||
| on: [push, pull_request] | ||||
|  | ||||
| jobs: | ||||
|   lint: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: psf/black@stable | ||||
							
								
								
									
										71
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| # For most projects, this workflow file will not need changing; you simply need | ||||
| # to commit it to your repository. | ||||
| # | ||||
| # You may wish to alter this file to override the set of languages analyzed, | ||||
| # or to provide custom queries or build logic. | ||||
| name: "CodeQL" | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [master] | ||||
|   pull_request: | ||||
|     # The branches below must be a subset of the branches above | ||||
|     branches: [master] | ||||
|   schedule: | ||||
|     - cron: '0 1 * * 5' | ||||
|  | ||||
| jobs: | ||||
|   analyze: | ||||
|     name: Analyze | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         # Override automatic language detection by changing the below list | ||||
|         # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] | ||||
|         language: ['python'] | ||||
|         # Learn more... | ||||
|         # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection | ||||
|  | ||||
|     steps: | ||||
|     - name: Checkout repository | ||||
|       uses: actions/checkout@v3 | ||||
|       with: | ||||
|         # We must fetch at least the immediate parents so that if this is | ||||
|         # a pull request then we can checkout the head. | ||||
|         fetch-depth: 2 | ||||
|  | ||||
|     # If this run was triggered by a pull request event, then checkout | ||||
|     # the head of the pull request instead of the merge commit. | ||||
|     - run: git checkout HEAD^2 | ||||
|       if: ${{ github.event_name == 'pull_request' }} | ||||
|  | ||||
|     # Initializes the CodeQL tools for scanning. | ||||
|     - name: Initialize CodeQL | ||||
|       uses: github/codeql-action/init@v1 | ||||
|       with: | ||||
|         languages: ${{ matrix.language }} | ||||
|         # If you wish to specify custom queries, you can do so here or in a config file. | ||||
|         # By default, queries listed here will override any specified in a config file.  | ||||
|         # Prefix the list here with "+" to use these queries and those in the config file. | ||||
|         # queries: ./path/to/local/query, your-org/your-repo/queries@main | ||||
|  | ||||
|     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java). | ||||
|     # If this step fails, then you should remove it and run the build manually (see below) | ||||
|     - name: Autobuild | ||||
|       uses: github/codeql-action/autobuild@v1 | ||||
|  | ||||
|     # ℹ️ Command-line programs to run using the OS shell. | ||||
|     # 📚 https://git.io/JvXDl | ||||
|  | ||||
|     # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines | ||||
|     #    and modify them (or add more) to build your code if your project | ||||
|     #    uses a compiled language | ||||
|  | ||||
|     #- run: | | ||||
|     #   make bootstrap | ||||
|     #   make release | ||||
|  | ||||
|     - name: Perform CodeQL Analysis | ||||
|       uses: github/codeql-action/analyze@v1 | ||||
							
								
								
									
										31
									
								
								.github/workflows/documentation.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/documentation.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # This is a basic workflow to help you get started with Actions | ||||
|  | ||||
| name: Documentation build | ||||
|  | ||||
| # Controls when the action will run. Triggers the workflow on push or pull request | ||||
| # events but only for the master branch | ||||
| on: | ||||
|   push: | ||||
|     branches: [ master ] | ||||
|   pull_request: | ||||
|     branches: [ master ] | ||||
|  | ||||
| # A workflow run is made up of one or more jobs that can run sequentially or in parallel | ||||
| jobs: | ||||
|   # This workflow contains a single job called "docs" | ||||
|   docs: | ||||
|     # The type of runner that the job will run on | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     # Steps represent a sequence of tasks that will be executed as part of the job | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|         with: | ||||
|           submodules: 'recursive' | ||||
|       - name: Install packages | ||||
|         run: | ||||
|           sudo apt-get update -y && | ||||
|           sudo apt-get install -y git python3-sphinx graphviz libenchant1c2a && | ||||
|           sudo pip install tox | ||||
|       - name: Test doc build | ||||
|         run: tox -e docs | ||||
							
								
								
									
										6
									
								
								.github/workflows/pythonpackage.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/pythonpackage.yml
									
									
									
									
										vendored
									
									
								
							| @@ -15,14 +15,14 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: [3.5, 3.6, 3.7, 3.8] | ||||
|         python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - uses: actions/checkout@v3 | ||||
|       with: | ||||
|         submodules: 'recursive' | ||||
|     - name: Set up Python ${{ matrix.python-version }} | ||||
|       uses: actions/setup-python@v1 | ||||
|       uses: actions/setup-python@v3.1.1 | ||||
|       with: | ||||
|         python-version: ${{ matrix.python-version }} | ||||
|     - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -35,4 +35,8 @@ test/test-cli-output/ | ||||
| *.swo | ||||
|  | ||||
| # vscode | ||||
| .vscode/settings.json | ||||
| .vscode/* | ||||
| !.vscode/settings.json | ||||
| !.vscode/tasks.json | ||||
| !.vscode/launch.json | ||||
| !.vscode/extensions.json | ||||
|   | ||||
							
								
								
									
										81
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,81 +0,0 @@ | ||||
| language: python | ||||
| sudo: false | ||||
| cache: pip | ||||
| dist: bionic | ||||
| git: | ||||
|   depth: 100000 | ||||
| addons: | ||||
|   apt: | ||||
|     packages: | ||||
|       - graphviz | ||||
| env: | ||||
|   global: | ||||
|     - ESCPOS_CAPABILITIES_FILE=/home/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
| matrix: | ||||
|   fast_finish: true | ||||
|   include: | ||||
|     - name: "Python 3.7 on Windows" | ||||
|       os: windows | ||||
|       language: shell | ||||
|       before_install: | ||||
|          - choco install python | ||||
|          - pip install tox codecov 'sphinx>=1.5.1' | ||||
|       env: | ||||
|          - TOXENV=py37 | ||||
|          - PATH=/c/Python37:/c/Python37/Scripts:$PATH | ||||
|          - ESCPOS_CAPABILITIES_FILE=C:/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
|     - name: "Python 3.7 on macOS" | ||||
|       os: osx | ||||
|       osx_image: xcode10.2 | ||||
|       language: shell | ||||
|       env: TOXENV=py37 ESCPOS_CAPABILITIES_FILE=/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
|     - python: 3.5 | ||||
|       env: TOXENV=py35 | ||||
|     - python: 3.6 | ||||
|       env: TOXENV=py36 | ||||
|     - python: 3.7 | ||||
|       env: TOXENV=py37 | ||||
|     - python: 3.7-dev | ||||
|       env: TOXENV=py37 | ||||
|     - python: 3.8 | ||||
|       env: TOXENV=py38 | ||||
|     - python: 3.8-dev | ||||
|       env: TOXENV=py38 | ||||
|     - python: nightly | ||||
|       env: TOXENV=py38 | ||||
|     - python: pypy3 | ||||
|       env: TOXENV=pypy3 | ||||
|     - python: 3.8 | ||||
|       env: TOXENV=docs | ||||
|     - python: 3.8 | ||||
|       env: TOXENV=flake8 | ||||
|   allow_failures: | ||||
|     - python: 3.7-dev | ||||
|     - python: 3.8-dev | ||||
|     - python: nightly | ||||
|     - python: pypy3 | ||||
|     - os: windows | ||||
|     - os: osx | ||||
| before_install: | ||||
|     - pip install tox codecov 'sphinx>=1.5.1' | ||||
|     - ./doc/generate_authors.sh --check | ||||
| script: | ||||
|     - tox | ||||
|     - codecov | ||||
| notifications: | ||||
|   email: | ||||
|     on_success: never | ||||
|     on_failure: change | ||||
| deploy: | ||||
| # Github deployment | ||||
|   - provider: releases | ||||
|     api_key: | ||||
|       secure: oiR3r5AIx9ENIRtbUKIxorRx8GMv4BxgVIZcieXbgSTN4DBZdRWdzs1Xxngu/90Xf79G0X+XGxZyXrYN7eFFNp0kUYj8kwZ1aS/dyR88scskumERWi1Hv5WUJrYGrDe7PcjNGsJ2jw0nNnRPKG87Y84aR4lQygyGBSlDcdrOBnBv0sHYJMxRvHSRkGgWpur06QIOGOk4oOipTXR/7E9cg3YQC5nvZAf2QiprwTa8IcOSFlZQPykEVRYSiAgXrgqBYcZzpX0hAGuIBv7DmPI2ORTF+t79Wbhxhnho3gGJleDv7Z96//sf1vQNCG6qOgeIc9ZY08Jm1AwXQoW0p6F1/XcEPxeyPDkXJzlojE9rjYNLCPL4gxb/LESEuUafm0U4JGMsZ6hnsBOw583yTuAdfQuJ9M+QaSyem6OVNkky3+DKAD3z0WJnl9jmGXIXigNSIxD25XhpvY+j9P0XTLBG1GT2Q+wXCIjSYJc2XnYcdgVJcLoxSWk1fKj/KPi7buAWtqwnL3tjeldpMMOZMliPUTWMM14zoGskHztt0JCkAtcotm9AQtvL8eZ2LHLDK/jyLzjv0wAwU5vzSVp14XHLZl7Q0AIoNc20p1EYGa9C/gSPd9CkrWZoG4lMOiAu3tp2PRLVrdXH3ZWSPQq4Ek5MczrUTkmB82XErNbOa8QB1Dw= | ||||
|     file: .tox/dist/python-escpos*.zip | ||||
|     file_glob: true | ||||
|     skip_cleanup: true | ||||
|     on: | ||||
|       tags: true | ||||
|       repo: python-escpos/python-escpos | ||||
|       branch: master | ||||
|       condition: $TRAVIS_PYTHON_VERSION = "3.8" | ||||
							
								
								
									
										14
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|   "restructuredtext.confPath": "${workspaceFolder}/doc", | ||||
|   "files.watcherExclude": { | ||||
|     "**/.git/objects/**": true, | ||||
|     "**/.git/subtree-cache/**": true, | ||||
|     "**/node_modules/*/**": true, | ||||
|     "**/.eggs/**": true, | ||||
|     "**/.hypothesis/**": true, | ||||
|     "**/.tox/**": true, | ||||
|   }, | ||||
|   "editor.formatOnSave": true, | ||||
|   "editor.formatOnPaste": true, | ||||
|   "python.formatting.provider": "black", | ||||
| } | ||||
| @@ -1,6 +1,28 @@ | ||||
| ********* | ||||
| Changelog | ||||
| ********* | ||||
| 2020-05-12 - Version 3.0a8 - "Only Slightly Bent" | ||||
| ------------------------------------------------- | ||||
| This release is the ninth alpha release of the new version 3.0. | ||||
| Please be aware that the API is subject to change until v3.0 is | ||||
| released. | ||||
|  | ||||
| This release drops support for Python 2, requiring at least | ||||
| version 3.5 of Python. | ||||
|  | ||||
| changes | ||||
| ^^^^^^^ | ||||
| - Drop support for Python 2 and mark in setuptools as only supporting 3.5 and upwards | ||||
| - remove landscape.io badge | ||||
| - replace viivakoodi with python-barcode which is maintained | ||||
| - add configuration for Visual Studio Code | ||||
| - use pkg_resources for the retrieval of the capabilities.json | ||||
|  | ||||
| contributors | ||||
| ^^^^^^^^^^^^ | ||||
| - Romain Porte | ||||
| - Patrick Kanzler | ||||
|  | ||||
| 2020-05-09 - Version 3.0a7 - "No Fixed Abode" | ||||
| --------------------------------------------- | ||||
| This release is the eight alpha release of the new version 3.0. | ||||
|   | ||||
| @@ -9,7 +9,7 @@ In order to reduce the amount of work for everyone please try to adhere to good | ||||
|  | ||||
| The pull requests and issues will be prefilled with templates. Please fill in your information where applicable. | ||||
|  | ||||
| This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as | ||||
| This project uses `semantic versioning <https://semver.org/>`_ and tries to adhere to the proposed rules as | ||||
| well as possible. | ||||
|  | ||||
| Author-list | ||||
| @@ -27,25 +27,6 @@ Style-Guide | ||||
|  | ||||
| When writing code please try to stick to these rules. | ||||
|  | ||||
| Python 2 and 3 | ||||
| ^^^^^^^^^^^^^^ | ||||
| We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3. | ||||
| In order to ensure that we do not miss any accidental degradation, please add these imports to the top | ||||
| of every file of code: | ||||
|  | ||||
| .. code-block:: Python | ||||
|  | ||||
|   from __future__ import absolute_import | ||||
|   from __future__ import division | ||||
|   from __future__ import print_function | ||||
|   from __future__ import unicode_literals | ||||
|  | ||||
| Furthermore please be aware of the differences between Python 2 and 3. For | ||||
| example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful. | ||||
| Special care has to be taken when dealing with strings and byte-strings. Please note | ||||
| that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings. | ||||
| Often you can achieve compatibility quite easily with a tool from the `six`-package. | ||||
|  | ||||
| PEP8 | ||||
| ^^^^ | ||||
| The entire codebase adheres to the rules of PEP8. | ||||
| @@ -81,7 +62,7 @@ You can copy the structure from other testcases. Please remember to adapt the do | ||||
| Further reading | ||||
| ^^^^^^^^^^^^^^^ | ||||
| For further best practices and hints on contributing please see the | ||||
| `contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide | ||||
| `contribution-guide <https://www.contribution-guide.org/>`_. Should there be any contradictions between this guide | ||||
| and the linked one, please stick to this text. | ||||
| Aside from that feel free to create an issue or write an email if anything is unclear. | ||||
|  | ||||
|   | ||||
							
								
								
									
										20
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.rst
									
									
									
									
									
								
							| @@ -6,16 +6,12 @@ python-escpos - Python library to manipulate ESC/POS Printers | ||||
|     :target: https://travis-ci.org/python-escpos/python-escpos | ||||
|     :alt: Continous Integration | ||||
|  | ||||
| .. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat | ||||
|     :target: https://landscape.io/github/python-escpos/python-escpos/master | ||||
|     :alt: Code Health | ||||
|  | ||||
| .. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master | ||||
|     :target: https://codecov.io/github/python-escpos/python-escpos?branch=master | ||||
|     :alt: Code Coverage | ||||
|  | ||||
| .. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable | ||||
|     :target: http://python-escpos.readthedocs.io/en/latest/?badge=stable | ||||
| .. image:: https://readthedocs.org/projects/python-escpos/badge/?version=latest | ||||
|     :target: https://python-escpos.readthedocs.io/en/latest/?badge=latest | ||||
|     :alt: Documentation Status | ||||
|  | ||||
|  | ||||
| @@ -47,7 +43,7 @@ This library makes use of: | ||||
| * `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing | ||||
| * `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes | ||||
| * `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers | ||||
| * `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes | ||||
| * `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ for the generation of barcodes | ||||
|  | ||||
| Documentation and Usage | ||||
| ----------------------- | ||||
| @@ -71,18 +67,18 @@ Another example based on the Network printer class: | ||||
| .. code:: python | ||||
|  | ||||
|     from escpos.printer import Network | ||||
|      | ||||
|  | ||||
|     kitchen = Network("192.168.1.100") #Printer IP Address | ||||
|     kitchen.text("Hello World\n") | ||||
|     kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '') | ||||
|     kitchen.cut() | ||||
|      | ||||
|  | ||||
| Another example based on the Serial printer class: | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|     from escpos.printer import Serial | ||||
|      | ||||
|  | ||||
|     """ 9600 Baud, 8N1, Flow Control Enabled """ | ||||
|     p = Serial(devfile='/dev/tty.usbserial', | ||||
|                baudrate=9600, | ||||
| @@ -102,7 +98,7 @@ The full project-documentation is available on `Read the Docs <https://python-es | ||||
| Contributing | ||||
| ------------ | ||||
|  | ||||
| This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information. | ||||
| This project is open for any contribution! Please see `CONTRIBUTING.rst <https://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information. | ||||
|  | ||||
|  | ||||
| Disclaimer | ||||
| @@ -110,5 +106,3 @@ Disclaimer | ||||
|  | ||||
| None of the vendors cited in this project agree or endorse any of the patterns or implementations. | ||||
| Its names are used only to maintain context. | ||||
|  | ||||
|  | ||||
|   | ||||
 Submodule capabilities-data updated: 3b5b35cfd3...3612db407d
									
								
							| @@ -3,10 +3,9 @@ codecov: | ||||
|  | ||||
| coverage: | ||||
|   status: | ||||
|     project: | ||||
|       default:  # status context | ||||
|         target: auto | ||||
|         threshold: "1%" | ||||
|     project: off | ||||
|     patch: off | ||||
|     changes: off | ||||
|   range: "60...100" | ||||
|  | ||||
| comment: off | ||||
|   | ||||
							
								
								
									
										10
									
								
								doc/Makefile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								doc/Makefile
									
									
									
									
									
								
							| @@ -9,7 +9,7 @@ BUILDDIR      = _build | ||||
|  | ||||
| # User-friendly check for sphinx-build | ||||
| ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) | ||||
| $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) | ||||
| $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/) | ||||
| endif | ||||
|  | ||||
| # Internal variables. | ||||
| @@ -19,7 +19,7 @@ ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||
| # the i18n builder cannot share the environment and doctrees with the others | ||||
| I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||
|  | ||||
| .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | ||||
| .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext spelling | ||||
|  | ||||
| help: | ||||
| 	@echo "Please use \`make <target>' where <target> is one of" | ||||
| @@ -45,6 +45,7 @@ help: | ||||
| 	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes" | ||||
| 	@echo "  linkcheck  to check all external links for integrity" | ||||
| 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)" | ||||
| 	@echo "  spelling   to run the spellchecker" | ||||
|  | ||||
| clean: | ||||
| 	rm -rf $(BUILDDIR)/* | ||||
| @@ -175,3 +176,8 @@ pseudoxml: | ||||
| 	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." | ||||
|  | ||||
| spelling: | ||||
| 	$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling | ||||
| 	@echo | ||||
| 	@echo "Spellchecker finished." | ||||
|   | ||||
							
								
								
									
										198
									
								
								doc/conf.py
									
									
									
									
									
								
							
							
						
						
									
										198
									
								
								doc/conf.py
									
									
									
									
									
								
							| @@ -14,111 +14,106 @@ | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| on_rtd = os.getenv('READTHEDOCS') == 'True' | ||||
| if on_rtd: | ||||
|     import escpos | ||||
| else: | ||||
|     from setuptools_scm import get_version | ||||
|  | ||||
| from importlib.metadata import version as imp_version | ||||
|  | ||||
| on_rtd = os.getenv("READTHEDOCS") == "True" | ||||
|  | ||||
| # If extensions (or modules to document with autodoc) are in another directory, | ||||
| # add these directories to sys.path here. If the directory is relative to the | ||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||
| sys.path.insert(0, os.path.abspath('../src')) | ||||
| root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..')) | ||||
| sys.path.insert(0, os.path.abspath("../src")) | ||||
| root = os.path.relpath(os.path.join(os.path.dirname(__file__), "..")) | ||||
|  | ||||
| # -- General configuration ------------------------------------------------ | ||||
|  | ||||
| # If your documentation needs a minimal Sphinx version, state it here. | ||||
| #needs_sphinx = '1.0' | ||||
| # needs_sphinx = '1.0' | ||||
|  | ||||
| # Add any Sphinx extension module names here, as strings. They can be | ||||
| # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | ||||
| # ones. | ||||
| extensions = [ | ||||
|     'sphinx.ext.autodoc', | ||||
|     'sphinx.ext.doctest', | ||||
|     'sphinx.ext.todo', | ||||
|     'sphinx.ext.coverage', | ||||
|     'sphinx.ext.viewcode', | ||||
|     'sphinx.ext.todo', | ||||
|     'sphinx.ext.graphviz', | ||||
|     'sphinx.ext.inheritance_diagram', | ||||
|     "sphinx.ext.autodoc", | ||||
|     "sphinx.ext.doctest", | ||||
|     "sphinx.ext.todo", | ||||
|     "sphinx.ext.coverage", | ||||
|     "sphinx.ext.viewcode", | ||||
|     "sphinx.ext.todo", | ||||
|     "sphinx.ext.graphviz", | ||||
|     "sphinx.ext.inheritance_diagram", | ||||
|     "sphinxcontrib.spelling", | ||||
| ] | ||||
|  | ||||
| # supress warnings for external images | ||||
| suppress_warnings = [ | ||||
|     'image.nonlocal_uri', | ||||
|     "image.nonlocal_uri", | ||||
| ] | ||||
|  | ||||
| # enable todos | ||||
| todo_include_todos = True | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['_templates'] | ||||
| templates_path = ["_templates"] | ||||
|  | ||||
| # The suffix of source filenames. | ||||
| source_suffix = '.rst' | ||||
| source_suffix = ".rst" | ||||
|  | ||||
| # The encoding of source files. | ||||
| #source_encoding = 'utf-8-sig' | ||||
| # source_encoding = 'utf-8-sig' | ||||
|  | ||||
| # The master toctree document. | ||||
| master_doc = 'index' | ||||
| master_doc = "index" | ||||
|  | ||||
| # General information about the project. | ||||
| project = u'python-escpos' | ||||
| copyright = u'2016, Manuel F Martinez and others' | ||||
| project = u"python-escpos" | ||||
| copyright = u"2016, Manuel F Martinez and others" | ||||
|  | ||||
| # The version info for the project you're documenting, acts as replacement for | ||||
| # |version| and |release|, also used in various other places throughout the | ||||
| # built documents. | ||||
| # | ||||
| if on_rtd: | ||||
|     # The full version, including alpha/beta/rc tags. | ||||
|     release = escpos.__version__ | ||||
| else: | ||||
|     # locally setuptools_scm should work | ||||
|     release = get_version(root=root) | ||||
| release = imp_version("python-escpos") | ||||
| # The short X.Y version. | ||||
| version = '.'.join(release.split('.')[:2])  # The short X.Y version. | ||||
| version = ".".join(release.split(".")[:2]) | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| #language = None | ||||
| # language = None | ||||
|  | ||||
| # There are two options for replacing |today|: either, you set today to some | ||||
| # non-false value, then it is used: | ||||
| #today = '' | ||||
| # today = '' | ||||
| # Else, today_fmt is used as the format for a strftime call. | ||||
| #today_fmt = '%B %d, %Y' | ||||
| # today_fmt = '%B %d, %Y' | ||||
|  | ||||
| # List of patterns, relative to source directory, that match files and | ||||
| # directories to ignore when looking for source files. | ||||
| exclude_patterns = ['_build'] | ||||
| exclude_patterns = ["_build"] | ||||
|  | ||||
| # The reST default role (used for this markup: `text`) to use for all | ||||
| # documents. | ||||
| #default_role = None | ||||
| # default_role = None | ||||
|  | ||||
| # If true, '()' will be appended to :func: etc. cross-reference text. | ||||
| #add_function_parentheses = True | ||||
| # add_function_parentheses = True | ||||
|  | ||||
| # If true, the current module name will be prepended to all description | ||||
| # unit titles (such as .. function::). | ||||
| #add_module_names = True | ||||
| # add_module_names = True | ||||
|  | ||||
| # If true, sectionauthor and moduleauthor directives will be shown in the | ||||
| # output. They are ignored by default. | ||||
| #show_authors = False | ||||
| # show_authors = False | ||||
|  | ||||
| # The name of the Pygments (syntax highlighting) style to use. | ||||
| pygments_style = 'sphinx' | ||||
| pygments_style = "sphinx" | ||||
|  | ||||
| # A list of ignored prefixes for module index sorting. | ||||
| #modindex_common_prefix = [] | ||||
| # modindex_common_prefix = [] | ||||
|  | ||||
| # If true, keep warnings as "system message" paragraphs in the built documents. | ||||
| #keep_warnings = False | ||||
| # keep_warnings = False | ||||
|  | ||||
|  | ||||
| # -- Options for HTML output ---------------------------------------------- | ||||
| @@ -126,135 +121,139 @@ pygments_style = 'sphinx' | ||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for | ||||
| # a list of builtin themes. | ||||
| if on_rtd: | ||||
|     html_theme = 'default' | ||||
|     html_theme = "default" | ||||
| else: | ||||
|     try: | ||||
|         import sphinx_rtd_theme | ||||
|         html_theme = 'sphinx_rtd_theme' | ||||
|  | ||||
|         html_theme = "sphinx_rtd_theme" | ||||
|         html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] | ||||
|     except ImportError: | ||||
|         print("no sphinx_rtd_theme found, switching to nature") | ||||
|         html_theme = 'default' | ||||
|         html_theme = "default" | ||||
|  | ||||
| # Theme options are theme-specific and customize the look and feel of a theme | ||||
| # further.  For a list of options available for each theme, see the | ||||
| # documentation. | ||||
| #html_theme_options = {} | ||||
| # html_theme_options = {} | ||||
|  | ||||
| # Add any paths that contain custom themes here, relative to this directory. | ||||
| #html_theme_path = [] | ||||
| # html_theme_path = [] | ||||
|  | ||||
| # The name for this set of Sphinx documents.  If None, it defaults to | ||||
| # "<project> v<release> documentation". | ||||
| #html_title = None | ||||
| # html_title = None | ||||
|  | ||||
| # A shorter title for the navigation bar.  Default is the same as html_title. | ||||
| #html_short_title = None | ||||
| # html_short_title = None | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top | ||||
| # of the sidebar. | ||||
| #html_logo = None | ||||
| # html_logo = None | ||||
|  | ||||
| # The name of an image file (within the static path) to use as favicon of the | ||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||||
| # pixels large. | ||||
| html_favicon = 'pyescpos.ico' | ||||
| html_favicon = "pyescpos.ico" | ||||
|  | ||||
| # Add any paths that contain custom static files (such as style sheets) here, | ||||
| # relative to this directory. They are copied after the builtin static files, | ||||
| # so a file named "default.css" will overwrite the builtin "default.css". | ||||
| html_static_path = ['_static'] | ||||
| html_static_path = ["_static"] | ||||
|  | ||||
| # Add any extra paths that contain custom files (such as robots.txt or | ||||
| # .htaccess) here, relative to this directory. These files are copied | ||||
| # directly to the root of the documentation. | ||||
| #html_extra_path = [] | ||||
| # html_extra_path = [] | ||||
|  | ||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | ||||
| # using the given strftime format. | ||||
| #html_last_updated_fmt = '%b %d, %Y' | ||||
| # html_last_updated_fmt = '%b %d, %Y' | ||||
|  | ||||
| # If true, SmartyPants will be used to convert quotes and dashes to | ||||
| # typographically correct entities. | ||||
| #html_use_smartypants = True | ||||
| # html_use_smartypants = True | ||||
|  | ||||
| # Custom sidebar templates, maps document names to template names. | ||||
| #html_sidebars = {} | ||||
| # html_sidebars = {} | ||||
|  | ||||
| # Additional templates that should be rendered to pages, maps page names to | ||||
| # template names. | ||||
| #html_additional_pages = {} | ||||
| # html_additional_pages = {} | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #html_domain_indices = True | ||||
| # html_domain_indices = True | ||||
|  | ||||
| # If false, no index is generated. | ||||
| #html_use_index = True | ||||
| # html_use_index = True | ||||
|  | ||||
| # If true, the index is split into individual pages for each letter. | ||||
| #html_split_index = False | ||||
| # html_split_index = False | ||||
|  | ||||
| # If true, links to the reST sources are added to the pages. | ||||
| #html_show_sourcelink = True | ||||
| # html_show_sourcelink = True | ||||
|  | ||||
| # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | ||||
| #html_show_sphinx = True | ||||
| # html_show_sphinx = True | ||||
|  | ||||
| # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | ||||
| #html_show_copyright = True | ||||
| # html_show_copyright = True | ||||
|  | ||||
| # If true, an OpenSearch description file will be output, and all pages will | ||||
| # contain a <link> tag referring to it.  The value of this option must be the | ||||
| # base URL from which the finished HTML is served. | ||||
| #html_use_opensearch = '' | ||||
| # html_use_opensearch = '' | ||||
|  | ||||
| # This is the file name suffix for HTML files (e.g. ".xhtml"). | ||||
| #html_file_suffix = None | ||||
| # html_file_suffix = None | ||||
|  | ||||
| # Output file base name for HTML help builder. | ||||
| htmlhelp_basename = 'python-escposdoc' | ||||
| htmlhelp_basename = "python-escposdoc" | ||||
|  | ||||
|  | ||||
| # -- Options for LaTeX output --------------------------------------------- | ||||
|  | ||||
| latex_elements = { | ||||
| # The paper size ('letterpaper' or 'a4paper'). | ||||
| #'papersize': 'letterpaper', | ||||
|  | ||||
| # The font size ('10pt', '11pt' or '12pt'). | ||||
| #'pointsize': '10pt', | ||||
|  | ||||
| # Additional stuff for the LaTeX preamble. | ||||
| #'preamble': '', | ||||
|     # The paper size ('letterpaper' or 'a4paper'). | ||||
|     #'papersize': 'letterpaper', | ||||
|     # The font size ('10pt', '11pt' or '12pt'). | ||||
|     #'pointsize': '10pt', | ||||
|     # Additional stuff for the LaTeX preamble. | ||||
|     #'preamble': '', | ||||
| } | ||||
|  | ||||
| # Grouping the document tree into LaTeX files. List of tuples | ||||
| # (source start file, target name, title, | ||||
| #  author, documentclass [howto, manual, or own class]). | ||||
| latex_documents = [ | ||||
|   ('index', 'python-escpos.tex', u'python-escpos Documentation', | ||||
|    u'Manuel F Martinez and others', 'manual'), | ||||
|     ( | ||||
|         "index", | ||||
|         "python-escpos.tex", | ||||
|         u"python-escpos Documentation", | ||||
|         u"Manuel F Martinez and others", | ||||
|         "manual", | ||||
|     ), | ||||
| ] | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top of | ||||
| # the title page. | ||||
| #latex_logo = None | ||||
| # latex_logo = None | ||||
|  | ||||
| # For "manual" documents, if this is true, then toplevel headings are parts, | ||||
| # not chapters. | ||||
| #latex_use_parts = False | ||||
| # latex_use_parts = False | ||||
|  | ||||
| # If true, show page references after internal links. | ||||
| #latex_show_pagerefs = False | ||||
| # latex_show_pagerefs = False | ||||
|  | ||||
| # If true, show URL addresses after external links. | ||||
| #latex_show_urls = False | ||||
| # latex_show_urls = False | ||||
|  | ||||
| # Documents to append as an appendix to all manuals. | ||||
| #latex_appendices = [] | ||||
| # latex_appendices = [] | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #latex_domain_indices = True | ||||
| # latex_domain_indices = True | ||||
|  | ||||
|  | ||||
| # -- Options for manual page output --------------------------------------- | ||||
| @@ -262,12 +261,17 @@ latex_documents = [ | ||||
| # One entry per manual page. List of tuples | ||||
| # (source start file, name, description, authors, manual section). | ||||
| man_pages = [ | ||||
|     ('index', 'python-escpos', u'python-escpos Documentation', | ||||
|      [u'Manuel F Martinez and others'], 1) | ||||
|     ( | ||||
|         "index", | ||||
|         "python-escpos", | ||||
|         u"python-escpos Documentation", | ||||
|         [u"Manuel F Martinez and others"], | ||||
|         1, | ||||
|     ) | ||||
| ] | ||||
|  | ||||
| # If true, show URL addresses after external links. | ||||
| #man_show_urls = False | ||||
| # man_show_urls = False | ||||
|  | ||||
|  | ||||
| # -- Options for Texinfo output ------------------------------------------- | ||||
| @@ -276,19 +280,31 @@ man_pages = [ | ||||
| # (source start file, target name, title, author, | ||||
| #  dir menu entry, description, category) | ||||
| texinfo_documents = [ | ||||
|   ('index', 'python-escpos', u'python-escpos Documentation', | ||||
|    u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.', | ||||
|    'Miscellaneous'), | ||||
|     ( | ||||
|         "index", | ||||
|         "python-escpos", | ||||
|         u"python-escpos Documentation", | ||||
|         u"Manuel F Martinez and others", | ||||
|         "python-escpos", | ||||
|         "One line description of project.", | ||||
|         "Miscellaneous", | ||||
|     ), | ||||
| ] | ||||
|  | ||||
| # Documents to append as an appendix to all manuals. | ||||
| #texinfo_appendices = [] | ||||
| # texinfo_appendices = [] | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #texinfo_domain_indices = True | ||||
| # texinfo_domain_indices = True | ||||
|  | ||||
| # How to display URL addresses: 'footnote', 'no', or 'inline'. | ||||
| #texinfo_show_urls = 'footnote' | ||||
| # texinfo_show_urls = 'footnote' | ||||
|  | ||||
| # If true, do not generate a @detailmenu in the "Top" node's menu. | ||||
| #texinfo_no_detailmenu = False | ||||
| # texinfo_no_detailmenu = False | ||||
|  | ||||
| # spellchecker | ||||
| spelling_ignore_pypi_package_names = True | ||||
| spelling_ignore_wiki_words = True | ||||
| spelling_ignore_python_builtins = True | ||||
| spelling_ignore_importable_modules = True | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| GENLIST=$(git shortlog -s -n | cut -f2 | sort -f) | ||||
| GENLIST_W_MAIL=$(git shortlog -s -e -n | cut -f2 | sort -f) | ||||
| AUTHORSFILE="$(dirname $0)/../AUTHORS" | ||||
| TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" | ||||
|  | ||||
| @@ -12,7 +13,9 @@ if [ "$#" -eq 1 ] | ||||
| 	echo "\nNew authorsfile:\n" | ||||
| 	cat $TEMPAUTHORSFILE | ||||
| 	echo "\nUsing diff on files...\n" | ||||
|         diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE | ||||
| 	diff --suppress-common-lines -b --from-file $AUTHORSFILE $TEMPAUTHORSFILE | ||||
| 	echo "Authors with mail addresses:\n" | ||||
| 	echo "$GENLIST_W_MAIL" | ||||
|     else | ||||
| 	echo "$GENLIST">$AUTHORSFILE | ||||
| fi | ||||
|   | ||||
| @@ -56,7 +56,7 @@ if errorlevel 9009 ( | ||||
| 	echo.may add the Sphinx directory to PATH. | ||||
| 	echo. | ||||
| 	echo.If you don't have Sphinx installed, grab it from | ||||
| 	echo.http://sphinx-doc.org/ | ||||
| 	echo.https://sphinx-doc.org/ | ||||
| 	exit /b 1 | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,8 @@ Pillow>=2.0 | ||||
| qrcode>=4.0 | ||||
| pyserial | ||||
| sphinx-rtd-theme | ||||
| setuptools | ||||
| setuptools-scm | ||||
| docutils>=0.12 | ||||
| viivakoodi | ||||
| sphinxcontrib-spelling>=7.2.0 | ||||
| python-barcode>=0.11.0,<1 | ||||
|   | ||||
							
								
								
									
										7
									
								
								doc/spelling_wordlist.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								doc/spelling_wordlist.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| Raspbian | ||||
| ESC | ||||
| POS | ||||
| Escpos | ||||
| Escpos | ||||
| baudrate | ||||
| lsusb | ||||
| @@ -48,9 +48,9 @@ to have and the second yields the "Output Endpoint" address. | ||||
|  | ||||
| By default the "Interface" number is "0" and the "Output Endpoint" | ||||
| address is "0x01". If you have other values then you can define them on | ||||
| your instance. So, assuming that we have another printer, CT-S2000,  | ||||
| manufactured by Citizen (with "Vendor ID" of 2730 and "Product ID" of 0fff)  | ||||
| where in\_ep is on 0x81 and out\_ep=0x02, then the printer definition should  | ||||
| your instance. So, assuming that we have another printer, CT-S2000, | ||||
| manufactured by Citizen (with "Vendor ID" of 2730 and "Product ID" of 0fff) | ||||
| where in\_ep is on 0x81 and out\_ep=0x02, then the printer definition should | ||||
| look like: | ||||
|  | ||||
| **Generic USB Printer initialization** | ||||
| @@ -84,7 +84,7 @@ to. | ||||
| :: | ||||
|  | ||||
|     p = printer.Serial("/dev/tty0") | ||||
|      | ||||
|  | ||||
|     # on a Windows OS serial devices are typically accessible as COM | ||||
|     p = printer.Serial("COM1") | ||||
|  | ||||
| @@ -125,6 +125,11 @@ on a USB interface. | ||||
|     # Cut paper | ||||
|     p.cut() | ||||
|  | ||||
| Standard python constraints on libraries apply. This means especially | ||||
| that you should not name the script in which you implement these lines | ||||
| should not be named ``escpos`` as this would collide with the name of | ||||
| the library. | ||||
|  | ||||
| Configuration File | ||||
| ------------------ | ||||
|  | ||||
| @@ -163,7 +168,7 @@ The printer section | ||||
|  | ||||
| The ``printer`` configuration section defines a default printer to create. | ||||
|  | ||||
| The only required paramter is ``type``. The value of this has to be one of the | ||||
| The only required parameter is ``type``. The value of this has to be one of the | ||||
| printers defined in :doc:`/user/printers`. | ||||
|  | ||||
| The rest of the given parameters will be passed on to the initialization of the printer class. | ||||
| @@ -194,12 +199,12 @@ An USB-printer could be defined by:: | ||||
|  | ||||
| Printing text right | ||||
| ------------------- | ||||
| Python-escpos is designed to accept unicode. So make sure that you use ``u'strings'`` or import ``unicode_literals`` | ||||
| from ``__future__`` if you are on Python 2. On Python 3 you should be fine. | ||||
|  | ||||
| Python-escpos is designed to accept unicode. | ||||
|  | ||||
| For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess | ||||
| the right codepage and then send the encoded data to the printer. If this feature does not work, please try to | ||||
| isolate the error and then create an issue on the Github project page. | ||||
| isolate the error and then create an issue on the GitHub project page. | ||||
|  | ||||
| If you want or need to you can manually set the codepage. For this please use the ``charcode()``-function. You can set | ||||
| any key-value that is in ``CHARCODE``. If something is wrong, an ``CharCodeError`` will be raised. | ||||
| @@ -289,4 +294,18 @@ This way you could also store the code in a file and print it later. | ||||
| You could then for example print the code from another process than your main-program and thus reduce the waiting time. | ||||
| (Of course this will not make the printer print faster.) | ||||
|  | ||||
| Troubleshooting | ||||
| --------------- | ||||
|  | ||||
| This section gathers various hints on troubleshooting. | ||||
|  | ||||
| Print with STAR TSP100 family | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| Printer of the STAR TSP100 family do not have a native ESC/POS mode, which | ||||
| is why you will not be able to directly print with this library to the printer. | ||||
|  | ||||
| More information on this topic can be found in the online documentation of | ||||
| `Star Micronics <https://www.starmicronics.com/help-center/knowledge-base/configure-tsp100-series-printers-esc-pos-mode/>`_ | ||||
| and the `discussion in the python-escpos project <https://github.com/python-escpos/python-escpos/issues/410>`_. | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from escpos.printer import Usb | ||||
| p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|  | ||||
| # Print software and then hardware barcode with the same content | ||||
| p.soft_barcode('code39', '123456') | ||||
| p.text('\n') | ||||
| p.text('\n') | ||||
| p.barcode('123456', 'CODE39') | ||||
| p.soft_barcode("code39", "123456") | ||||
| p.text("\n") | ||||
| p.text("\n") | ||||
| p.barcode("123456", "CODE39") | ||||
|   | ||||
| @@ -1,21 +1,28 @@ | ||||
| """Prints code page tables. | ||||
| """ | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
| import six | ||||
| import sys | ||||
|  | ||||
| from escpos import printer | ||||
| from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT | ||||
| from escpos.constants import ( | ||||
|     CODEPAGE_CHANGE, | ||||
|     ESC, | ||||
|     CTL_LF, | ||||
|     CTL_FF, | ||||
|     CTL_CR, | ||||
|     CTL_HT, | ||||
|     CTL_VT, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     dummy = printer.Dummy() | ||||
|  | ||||
|     dummy.hw('init') | ||||
|     dummy.hw("init") | ||||
|  | ||||
|     for codepage in sys.argv[1:] or ['USA']: | ||||
|     for codepage in sys.argv[1:] or ["USA"]: | ||||
|         dummy.set(height=2, width=2) | ||||
|         dummy._raw(codepage + "\n\n\n") | ||||
|         print_codepage(dummy, codepage) | ||||
| @@ -37,14 +44,14 @@ def print_codepage(printer, codepage): | ||||
|     sep = "" | ||||
|  | ||||
|     # Table header | ||||
|     printer.set(font='b') | ||||
|     printer.set(font="b") | ||||
|     printer._raw("  {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) | ||||
|     printer.set() | ||||
|  | ||||
|     # The table | ||||
|     for x in range(0, 16): | ||||
|         # First column | ||||
|         printer.set(font='b') | ||||
|         printer.set(font="b") | ||||
|         printer._raw("{} ".format(hex(x)[2:])) | ||||
|         printer.set() | ||||
|  | ||||
| @@ -52,12 +59,12 @@ def print_codepage(printer, codepage): | ||||
|             byte = six.int2byte(x * 16 + y) | ||||
|  | ||||
|             if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT): | ||||
|                 byte = ' ' | ||||
|                 byte = " " | ||||
|  | ||||
|             printer._raw(byte) | ||||
|             printer._raw(sep) | ||||
|         printer._raw('\n') | ||||
|         printer._raw("\n") | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| # Climacons by Adam Whitcroft | ||||
|  | ||||
| 75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft). | ||||
| 75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](https://www.twitter.com/#!/adamwhitcroft). | ||||
|  | ||||
| Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information. | ||||
| Visit the [Climacons](https://adamwhitcroft.com/climacons/) website for more information. | ||||
|  | ||||
| Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft) | ||||
|  | ||||
| ## License | ||||
| You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons. | ||||
| You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons. | ||||
|   | ||||
| @@ -7,7 +7,7 @@ def usage(): | ||||
|     print("usage: qr_code.py <content>") | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| if __name__ == "__main__": | ||||
|     if len(sys.argv) != 2: | ||||
|         usage() | ||||
|         sys.exit(1) | ||||
|   | ||||
| @@ -5,5 +5,5 @@ from escpos.printer import Usb | ||||
| p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|  | ||||
| # Some software barcodes | ||||
| p.soft_barcode('code128', 'Hello') | ||||
| p.soft_barcode('code39', '123456') | ||||
| p.soft_barcode("code128", "Hello") | ||||
| p.soft_barcode("code39", "1234") | ||||
|   | ||||
| @@ -9,11 +9,10 @@ | ||||
| # Written by Adafruit Industries.  MIT license. | ||||
| # Adapted and enhanced for escpos library by MrWunderbar666 | ||||
|  | ||||
| # Icons taken from http://adamwhitcroft.com/climacons/ | ||||
| # Icons taken from https://adamwhitcroft.com/climacons/ | ||||
| # Check out his github: https://github.com/AdamWhitcroft/climacons | ||||
|  | ||||
|  | ||||
| from __future__ import print_function | ||||
| from datetime import datetime | ||||
| import calendar | ||||
| import urllib | ||||
| @@ -34,93 +33,95 @@ printer = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
| # Technically you can use any other weather service, of course :) | ||||
| API_KEY = "YOUR API KEY" | ||||
|  | ||||
| LAT = "22.345490"       # Your Location | ||||
| LONG = "114.189945"     # Your Location | ||||
| LAT = "22.345490"  # Your Location | ||||
| LONG = "114.189945"  # Your Location | ||||
|  | ||||
|  | ||||
| def forecast_icon(idx): | ||||
|     icon = data['daily']['data'][idx]['icon'] | ||||
|     icon = data["daily"]["data"][idx]["icon"] | ||||
|     image = GRAPHICS_PATH + icon + ".png" | ||||
|     return image | ||||
|  | ||||
|  | ||||
| # Dumps one forecast line to the printer | ||||
| def forecast(idx): | ||||
|     date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time'])) | ||||
|     date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"])) | ||||
|     day = calendar.day_name[date.weekday()] | ||||
|     lo = data['daily']['data'][idx]['temperatureMin'] | ||||
|     hi = data['daily']['data'][idx]['temperatureMax'] | ||||
|     cond = data['daily']['data'][idx]['summary'] | ||||
|     lo = data["daily"]["data"][idx]["temperatureMin"] | ||||
|     hi = data["daily"]["data"][idx]["temperatureMax"] | ||||
|     cond = data["daily"]["data"][idx]["summary"] | ||||
|     print(date) | ||||
|     print(day) | ||||
|     print(lo) | ||||
|     print(hi) | ||||
|     print(cond) | ||||
|     time.sleep(1) | ||||
|     printer.set( | ||||
|         font='a', | ||||
|         height=2, | ||||
|         align='left', | ||||
|         bold=False, | ||||
|         double_height=False) | ||||
|     printer.text(day + ' \n ') | ||||
|     time.sleep(5)           # Sleep to prevent printer buffer overflow | ||||
|     printer.text('\n') | ||||
|     printer.set(font="a", height=2, align="left", bold=False, double_height=False) | ||||
|     printer.text(day + " \n ") | ||||
|     time.sleep(5)  # Sleep to prevent printer buffer overflow | ||||
|     printer.text("\n") | ||||
|     printer.image(forecast_icon(idx)) | ||||
|     printer.text('low ' + str(lo)) | ||||
|     printer.text("low " + str(lo)) | ||||
|     printer.text(deg) | ||||
|     printer.text('\n') | ||||
|     printer.text(' high ' + str(hi)) | ||||
|     printer.text("\n") | ||||
|     printer.text(" high " + str(hi)) | ||||
|     printer.text(deg) | ||||
|     printer.text('\n') | ||||
|     printer.text("\n") | ||||
|     # take care of pesky unicode dash | ||||
|     printer.text(cond.replace(u'\u2013', '-').encode('utf-8')) | ||||
|     printer.text('\n \n') | ||||
|     printer.text(cond.replace(u"\u2013", "-").encode("utf-8")) | ||||
|     printer.text("\n \n") | ||||
|  | ||||
|  | ||||
| def icon(): | ||||
|     icon = data['currently']['icon'] | ||||
|     icon = data["currently"]["icon"] | ||||
|     image = GRAPHICS_PATH + icon + ".png" | ||||
|     return image | ||||
|  | ||||
|  | ||||
| deg = ' C'      # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol | ||||
| deg = " C"  # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol | ||||
|  | ||||
| # if you want Fahrenheit change units= to 'us' | ||||
| url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \ | ||||
|     "?exclude=[alerts,minutely,hourly,flags]&units=si"  # change last bit to 'us' for Fahrenheit | ||||
| url = ( | ||||
|     "https://api.darksky.net/forecast/" | ||||
|     + API_KEY | ||||
|     + "/" | ||||
|     + LAT | ||||
|     + "," | ||||
|     + LONG | ||||
|     + "?exclude=[alerts,minutely,hourly,flags]&units=si" | ||||
| )  # change last bit to 'us' for Fahrenheit | ||||
| response = urllib.urlopen(url) | ||||
| data = json.loads(response.read()) | ||||
|  | ||||
| printer.print_and_feed(n=1) | ||||
| printer.control("LF") | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=True) | ||||
| printer.set(font="a", height=2, align="center", bold=True, double_height=True) | ||||
| printer.text("Weather Forecast") | ||||
| printer.text("\n") | ||||
| printer.set(align='center') | ||||
| printer.set(align="center") | ||||
|  | ||||
|  | ||||
| # Print current conditions | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=False) | ||||
| printer.text('Current conditions: \n') | ||||
| printer.set(font="a", height=2, align="center", bold=True, double_height=False) | ||||
| printer.text("Current conditions: \n") | ||||
| printer.image(icon()) | ||||
| printer.text("\n") | ||||
|  | ||||
| printer.set(font='a', height=2, align='left', bold=False, double_height=False) | ||||
| temp = data['currently']['temperature'] | ||||
| cond = data['currently']['summary'] | ||||
| printer.set(font="a", height=2, align="left", bold=False, double_height=False) | ||||
| temp = data["currently"]["temperature"] | ||||
| cond = data["currently"]["summary"] | ||||
| printer.text(temp) | ||||
| printer.text(' ') | ||||
| printer.text(" ") | ||||
| printer.text(deg) | ||||
| printer.text(' ') | ||||
| printer.text('\n') | ||||
| printer.text('Sky: ' + cond) | ||||
| printer.text('\n') | ||||
| printer.text('\n') | ||||
| printer.text(" ") | ||||
| printer.text("\n") | ||||
| printer.text("Sky: " + cond) | ||||
| printer.text("\n") | ||||
| printer.text("\n") | ||||
|  | ||||
| # Print forecast | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=False) | ||||
| printer.text('Forecast: \n') | ||||
| printer.set(font="a", height=2, align="center", bold=True, double_height=False) | ||||
| printer.text("Forecast: \n") | ||||
| forecast(0) | ||||
| forecast(1) | ||||
| printer.cut() | ||||
|   | ||||
							
								
								
									
										3
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [tool.black] | ||||
| extend-exclude = 'capabilities-data' | ||||
|  | ||||
							
								
								
									
										13
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								setup.cfg
									
									
									
									
									
								
							| @@ -18,10 +18,11 @@ classifiers = | ||||
|     Operating System :: OS Independent | ||||
|     Programming Language :: Python | ||||
|     Programming Language :: Python :: 3 | ||||
|     Programming Language :: Python :: 3.5 | ||||
|     Programming Language :: Python :: 3.6 | ||||
|     Programming Language :: Python :: 3.7 | ||||
|     Programming Language :: Python :: 3.8 | ||||
|     Programming Language :: Python :: 3.9 | ||||
|     Programming Language :: Python :: 3.10 | ||||
|     Programming Language :: Python :: Implementation :: CPython | ||||
|     Topic :: Software Development :: Libraries :: Python Modules | ||||
|     Topic :: Office/Business :: Financial :: Point-Of-Sale | ||||
| @@ -31,7 +32,7 @@ project_urls = | ||||
|     Release Notes = https://github.com/python-escpos/python-escpos/releases | ||||
|  | ||||
| [options] | ||||
| python_requires = >=3.5 | ||||
| python_requires = >=3.6 | ||||
| zip_safe = false | ||||
| include_package_data = true | ||||
| install_requires = | ||||
| @@ -39,13 +40,14 @@ install_requires = | ||||
|     Pillow>=2.0 | ||||
|     qrcode>=4.0 | ||||
|     pyserial | ||||
|     python-barcode>=0.9.1,<1 | ||||
|     setuptools | ||||
|     six | ||||
|     appdirs | ||||
|     PyYAML | ||||
|     argparse | ||||
|     argcomplete | ||||
|     future | ||||
|     viivakoodi>=0.8 | ||||
| setup_requires = setuptools_scm | ||||
| tests_require = | ||||
|     jaconv | ||||
| @@ -58,15 +60,12 @@ tests_require = | ||||
|     mock | ||||
|     hypothesis>4 | ||||
|     flake8 | ||||
|     sphinxcontrib-spelling>=7.2.0 | ||||
|  | ||||
| [nosetests] | ||||
| verbosity=3 | ||||
| with-doctest=1 | ||||
|  | ||||
| [bdist_wheel] | ||||
| # This flag says that the code is written to work on both Python 2 and Python 3. | ||||
| universal=1 | ||||
|  | ||||
| [flake8] | ||||
| exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py | ||||
| max-line-length = 120 | ||||
|   | ||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								setup.py
									
									
									
									
									
								
							| @@ -22,10 +22,6 @@ setuptools_scm_template = """\ | ||||
| # coding: utf-8 | ||||
| # file generated by setuptools_scm | ||||
| # don't change, don't track in version control | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| version = '{version}' | ||||
| """ | ||||
|   | ||||
| @@ -2,10 +2,6 @@ | ||||
| """ | ||||
| python-escpos enables you to manipulate escpos-printers | ||||
| """ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| __all__ = ["constants", "escpos", "exceptions", "printer"] | ||||
|  | ||||
| @@ -13,7 +9,7 @@ try: | ||||
|     from .version import version as __version__  # noqa | ||||
| except ImportError:  # pragma: no cover | ||||
|     raise ImportError( | ||||
|         'Failed to find (autogenerated) version.py. ' | ||||
|         'This might be because you are installing from GitHub\'s tarballs, ' | ||||
|         'use the PyPI ones.' | ||||
|         "Failed to find (autogenerated) version.py. " | ||||
|         "This might be because you are installing from GitHub's tarballs, " | ||||
|         "use the PyPI ones." | ||||
|     ) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import re | ||||
| from os import environ, path | ||||
| import pkg_resources | ||||
| import pickle | ||||
| import logging | ||||
| import time | ||||
| @@ -10,116 +11,120 @@ import yaml | ||||
| from tempfile import gettempdir | ||||
| import platform | ||||
|  | ||||
| from typing import Any, Dict | ||||
|  | ||||
| logging.basicConfig() | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir()) | ||||
| pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version())) | ||||
| pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir()) | ||||
| pickle_path = path.join( | ||||
|     pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version()) | ||||
| ) | ||||
| # get a temporary file from pkg_resources if no file is specified in env | ||||
| capabilities_path = environ.get( | ||||
|     'ESCPOS_CAPABILITIES_FILE', | ||||
|     path.join(path.dirname(__file__), 'capabilities.json')) | ||||
|     "ESCPOS_CAPABILITIES_FILE", | ||||
|     pkg_resources.resource_filename(__name__, "capabilities.json"), | ||||
| ) | ||||
|  | ||||
| # Load external printer database | ||||
| t0 = time.time() | ||||
| logger.debug('Using capabilities from file: %s', capabilities_path) | ||||
| logger.debug("Using capabilities from file: %s", capabilities_path) | ||||
| if path.exists(pickle_path): | ||||
|     if path.getmtime(capabilities_path) > path.getmtime(pickle_path): | ||||
|         logger.debug('Found a more recent capabilities file') | ||||
|         logger.debug("Found a more recent capabilities file") | ||||
|         full_load = True | ||||
|     else: | ||||
|         full_load = False | ||||
|         logger.debug('Loading capabilities from pickle in %s', pickle_path) | ||||
|         with open(pickle_path, 'rb') as cf: | ||||
|         logger.debug("Loading capabilities from pickle in %s", pickle_path) | ||||
|         with open(pickle_path, "rb") as cf: | ||||
|             CAPABILITIES = pickle.load(cf) | ||||
| else: | ||||
|     logger.debug('Capabilities pickle file not found: %s', pickle_path) | ||||
|     logger.debug("Capabilities pickle file not found: %s", pickle_path) | ||||
|     full_load = True | ||||
|  | ||||
| if full_load: | ||||
|     logger.debug('Loading and pickling capabilities') | ||||
|     with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp: | ||||
|     logger.debug("Loading and pickling capabilities") | ||||
|     with open(capabilities_path) as cp, open(pickle_path, "wb") as pp: | ||||
|         CAPABILITIES = yaml.safe_load(cp) | ||||
|         pickle.dump(CAPABILITIES, pp, protocol=2) | ||||
|  | ||||
| logger.debug('Finished loading capabilities took %.2fs', time.time() - t0) | ||||
| logger.debug("Finished loading capabilities took %.2fs", time.time() - t0) | ||||
|  | ||||
|  | ||||
| PROFILES = CAPABILITIES['profiles'] | ||||
| PROFILES: Dict[str, Any] = CAPABILITIES["profiles"] | ||||
|  | ||||
|  | ||||
| class NotSupported(Exception): | ||||
|     """Raised if a requested feature is not supported by the | ||||
|     printer profile. | ||||
|     """ | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| BARCODE_B = 'barcodeB' | ||||
| BARCODE_B = "barcodeB" | ||||
|  | ||||
|  | ||||
| class BaseProfile(object): | ||||
|     """This respresents a printer profile. | ||||
|     """This represents a printer profile. | ||||
|  | ||||
|     A printer profile knows about the number of columns, supported | ||||
|     features, colors and more. | ||||
|     """ | ||||
|  | ||||
|     profile_data = {} | ||||
|     profile_data: Dict[str, Any] = {} | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         return self.profile_data[name] | ||||
|  | ||||
|     def get_font(self, font): | ||||
|     def get_font(self, font) -> int: | ||||
|         """Return the escpos index for `font`. Makes sure that | ||||
|         the requested `font` is valid. | ||||
|         """ | ||||
|         font = {'a': 0, 'b': 1}.get(font, font) | ||||
|         font = {"a": 0, "b": 1}.get(font, font) | ||||
|         if not six.text_type(font) in self.fonts: | ||||
|             raise NotSupported( | ||||
|                 '"{}" is not a valid font in the current profile'.format(font)) | ||||
|                 '"{}" is not a valid font in the current profile'.format(font) | ||||
|             ) | ||||
|         return font | ||||
|  | ||||
|     def get_columns(self, font): | ||||
|         """ Return the number of columns for the given font. | ||||
|         """ | ||||
|         """Return the number of columns for the given font.""" | ||||
|         font = self.get_font(font) | ||||
|         return self.fonts[six.text_type(font)]['columns'] | ||||
|         return self.fonts[six.text_type(font)]["columns"] | ||||
|  | ||||
|     def supports(self, feature): | ||||
|         """Return true/false for the given feature. | ||||
|         """ | ||||
|         """Return true/false for the given feature.""" | ||||
|         return self.features.get(feature) | ||||
|  | ||||
|     def get_code_pages(self): | ||||
|         """Return the support code pages as a ``{name: index}`` dict. | ||||
|         """ | ||||
|         """Return the support code pages as a ``{name: index}`` dict.""" | ||||
|         return {v: k for k, v in self.codePages.items()} | ||||
|  | ||||
|  | ||||
| def get_profile(name=None, **kwargs): | ||||
| def get_profile(name: str = None, **kwargs): | ||||
|     """Get the profile by name; if no name is given, return the | ||||
|     default profile. | ||||
|     """ | ||||
|     if isinstance(name, Profile): | ||||
|         return name | ||||
|  | ||||
|     clazz = get_profile_class(name or 'default') | ||||
|     clazz = get_profile_class(name or "default") | ||||
|     return clazz(**kwargs) | ||||
|  | ||||
|  | ||||
| CLASS_CACHE = {} | ||||
|  | ||||
|  | ||||
| def get_profile_class(name): | ||||
| def get_profile_class(name: str): | ||||
|     """For the given profile name, load the data from the external | ||||
|     database, then generate dynamically a class. | ||||
|     """ | ||||
|     if name not in CLASS_CACHE: | ||||
|         profile_data = PROFILES[name] | ||||
|         profile_name = clean(name) | ||||
|         class_name = '{}{}Profile'.format( | ||||
|             profile_name[0].upper(), profile_name[1:]) | ||||
|         new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data}) | ||||
|         class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:]) | ||||
|         new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data}) | ||||
|         CLASS_CACHE[name] = new_class | ||||
|  | ||||
|     return CLASS_CACHE[name] | ||||
| @@ -127,13 +132,13 @@ def get_profile_class(name): | ||||
|  | ||||
| def clean(s): | ||||
|     # Remove invalid characters | ||||
|     s = re.sub('[^0-9a-zA-Z_]', '', s) | ||||
|     s = re.sub("[^0-9a-zA-Z_]", "", s) | ||||
|     # Remove leading characters until we find a letter or underscore | ||||
|     s = re.sub('^[^a-zA-Z_]+', '', s) | ||||
|     s = re.sub("^[^a-zA-Z_]+", "", s) | ||||
|     return str(s) | ||||
|  | ||||
|  | ||||
| class Profile(get_profile_class('default')): | ||||
| class Profile(get_profile_class("default")): | ||||
|     """ | ||||
|     For users, who want to provide their profile | ||||
|     """ | ||||
|   | ||||
| @@ -3,18 +3,15 @@ | ||||
| """ CLI | ||||
|  | ||||
| This module acts as a command line interface for python-escpos. It mirrors | ||||
| closely the available ESCPOS commands while adding a couple extra ones for convience. | ||||
| closely the available ESCPOS commands while adding a couple extra ones for convenience. | ||||
|  | ||||
| It requires you to have a configuration file. See documentation for details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import argparse | ||||
|  | ||||
| try: | ||||
|     import argcomplete | ||||
| except ImportError: | ||||
| @@ -28,14 +25,14 @@ from . import version | ||||
|  | ||||
| # Must be defined before it's used in DEMO_FUNCTIONS | ||||
| def str_to_bool(string): | ||||
|     """ Used as a type in argparse so that we get back a proper | ||||
|     """Used as a type in argparse so that we get back a proper | ||||
|     bool instead of always True | ||||
|     """ | ||||
|     return string.lower() in ('y', 'yes', '1', 'true') | ||||
|     return string.lower() in ("y", "yes", "1", "true") | ||||
|  | ||||
|  | ||||
| # A list of functions that work better with a newline to be sent after them. | ||||
| REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text') | ||||
| REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text") | ||||
|  | ||||
|  | ||||
| # Used in demo method | ||||
| @@ -43,40 +40,46 @@ REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text') | ||||
| #   manual translation is done in the case of barcodes_a -> barcode. | ||||
| # Value: A list of dictionaries to pass to the escpos function as arguments. | ||||
| DEMO_FUNCTIONS = { | ||||
|     'text': [ | ||||
|         {'txt': 'Hello, World!\n', } | ||||
|     "text": [ | ||||
|         { | ||||
|             "txt": "Hello, World!\n", | ||||
|         } | ||||
|     ], | ||||
|     'qr': [ | ||||
|         {'content': 'This tests a QR code'}, | ||||
|         {'content': 'https://en.wikipedia.org/'} | ||||
|     "qr": [ | ||||
|         {"content": "This tests a QR code"}, | ||||
|         {"content": "https://en.wikipedia.org/"}, | ||||
|     ], | ||||
|     'barcodes_a': [ | ||||
|         {'bc': 'UPC-A', 'code': '13243546576'}, | ||||
|         {'bc': 'UPC-E', 'code': '132435'}, | ||||
|         {'bc': 'EAN13', 'code': '1324354657687'}, | ||||
|         {'bc': 'EAN8', 'code': '1324354'}, | ||||
|         {'bc': 'CODE39', 'code': 'TEST'}, | ||||
|         {'bc': 'ITF', 'code': '55867492279103'}, | ||||
|         {'bc': 'NW7', 'code': 'A00000000A'}, | ||||
|     "barcodes_a": [ | ||||
|         {"bc": "UPC-A", "code": "13243546576"}, | ||||
|         {"bc": "UPC-E", "code": "132435"}, | ||||
|         {"bc": "EAN13", "code": "1324354657687"}, | ||||
|         {"bc": "EAN8", "code": "1324354"}, | ||||
|         {"bc": "CODE39", "code": "TEST"}, | ||||
|         {"bc": "ITF", "code": "55867492279103"}, | ||||
|         {"bc": "NW7", "code": "A00000000A"}, | ||||
|     ], | ||||
|     'barcodes_b': [ | ||||
|         {'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'}, | ||||
|         {'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'}, | ||||
|         {'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'}, | ||||
|         {'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'}, | ||||
|         {'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'}, | ||||
|         {'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'}, | ||||
|         {'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'}, | ||||
|         {'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'}, | ||||
|         {'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'}, | ||||
|         {'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'}, | ||||
|         {'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'}, | ||||
|         {'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'}, | ||||
|     "barcodes_b": [ | ||||
|         {"bc": "UPC-A", "code": "13243546576", "function_type": "B"}, | ||||
|         {"bc": "UPC-E", "code": "132435", "function_type": "B"}, | ||||
|         {"bc": "EAN13", "code": "1324354657687", "function_type": "B"}, | ||||
|         {"bc": "EAN8", "code": "1324354", "function_type": "B"}, | ||||
|         {"bc": "CODE39", "code": "TEST", "function_type": "B"}, | ||||
|         {"bc": "ITF", "code": "55867492279103", "function_type": "B"}, | ||||
|         {"bc": "NW7", "code": "A00000000A", "function_type": "B"}, | ||||
|         {"bc": "CODE93", "code": "A00000000A", "function_type": "B"}, | ||||
|         {"bc": "CODE93", "code": "1324354657687", "function_type": "B"}, | ||||
|         {"bc": "CODE128A", "code": "TEST", "function_type": "B"}, | ||||
|         {"bc": "CODE128B", "code": "TEST", "function_type": "B"}, | ||||
|         {"bc": "CODE128C", "code": "TEST", "function_type": "B"}, | ||||
|         {"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"}, | ||||
|         { | ||||
|             "bc": "GS1 DataBar Omnidirectional", | ||||
|             "code": "0000000000000", | ||||
|             "function_type": "B", | ||||
|         }, | ||||
|         {"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"}, | ||||
|         {"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"}, | ||||
|         {"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"}, | ||||
|     ], | ||||
| } | ||||
|  | ||||
| @@ -88,356 +91,355 @@ DEMO_FUNCTIONS = { | ||||
| # arguments: A list of dicts of args for subparser.add_argument | ||||
| ESCPOS_COMMANDS = [ | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'qr', | ||||
|             'help': 'Print a QR code', | ||||
|         "parser": { | ||||
|             "name": "qr", | ||||
|             "help": "Print a QR code", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'qr', | ||||
|         "defaults": { | ||||
|             "func": "qr", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--content',), | ||||
|                 'help': 'Text to print as a qr code', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--content",), | ||||
|                 "help": "Text to print as a qr code", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--size',), | ||||
|                 'help': 'QR code size (1-16) [default:3]', | ||||
|                 'required': False, | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--size",), | ||||
|                 "help": "QR code size (1-16) [default:3]", | ||||
|                 "required": False, | ||||
|                 "type": int, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         "parser": { | ||||
|             "name": "barcode", | ||||
|             "help": "Print a barcode", | ||||
|         }, | ||||
|         "defaults": { | ||||
|             "func": "barcode", | ||||
|         }, | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 "option_strings": ("--code",), | ||||
|                 "help": "Barcode data to print", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--bc",), | ||||
|                 "help": "Barcode format", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--height",), | ||||
|                 "help": "Barcode height in px", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--width",), | ||||
|                 "help": "Barcode width", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--pos",), | ||||
|                 "help": "Label position", | ||||
|                 "choices": ["BELOW", "ABOVE", "BOTH", "OFF"], | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--font",), | ||||
|                 "help": "Label font", | ||||
|                 "choices": ["A", "B"], | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--align_ct",), | ||||
|                 "help": "Align barcode center", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--function_type",), | ||||
|                 "help": "ESCPOS function type", | ||||
|                 "choices": ["A", "B"], | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         "parser": { | ||||
|             "name": "text", | ||||
|             "help": "Print plain text", | ||||
|         }, | ||||
|         "defaults": { | ||||
|             "func": "text", | ||||
|         }, | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 "option_strings": ("--txt",), | ||||
|                 "help": "Plain text to print", | ||||
|                 "required": True, | ||||
|             } | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'barcode', | ||||
|             'help': 'Print a barcode', | ||||
|         "parser": { | ||||
|             "name": "block_text", | ||||
|             "help": "Print wrapped text", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'barcode', | ||||
|         "defaults": { | ||||
|             "func": "block_text", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--code',), | ||||
|                 'help': 'Barcode data to print', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--txt",), | ||||
|                 "help": "block_text to print", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--bc',), | ||||
|                 'help': 'Barcode format', | ||||
|                 'required': True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--height',), | ||||
|                 'help': 'Barcode height in px', | ||||
|                 'type': int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--width',), | ||||
|                 'help': 'Barcode width', | ||||
|                 'type': int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--pos',), | ||||
|                 'help': 'Label position', | ||||
|                 'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'], | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--font',), | ||||
|                 'help': 'Label font', | ||||
|                 'choices': ['A', 'B'], | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--align_ct',), | ||||
|                 'help': 'Align barcode center', | ||||
|                 'type': str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--function_type',), | ||||
|                 'help': 'ESCPOS function type', | ||||
|                 'choices': ['A', 'B'], | ||||
|                 "option_strings": ("--columns",), | ||||
|                 "help": "Number of columns", | ||||
|                 "type": int, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'text', | ||||
|             'help': 'Print plain text', | ||||
|         "parser": { | ||||
|             "name": "cut", | ||||
|             "help": "Cut the paper", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'text', | ||||
|         "defaults": { | ||||
|             "func": "cut", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--txt',), | ||||
|                 'help': 'Plain text to print', | ||||
|                 'required': True, | ||||
|             } | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'block_text', | ||||
|             'help': 'Print wrapped text', | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'block_text', | ||||
|         }, | ||||
|         'arguments': [ | ||||
|             { | ||||
|                 'option_strings': ('--txt',), | ||||
|                 'help': 'block_text to print', | ||||
|                 'required': True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--columns',), | ||||
|                 'help': 'Number of columns', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--mode",), | ||||
|                 "help": "Type of cut", | ||||
|                 "choices": ["FULL", "PART"], | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'cut', | ||||
|             'help': 'Cut the paper', | ||||
|         "parser": { | ||||
|             "name": "cashdraw", | ||||
|             "help": "Kick the cash drawer", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'cut', | ||||
|         "defaults": { | ||||
|             "func": "cashdraw", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--mode',), | ||||
|                 'help': 'Type of cut', | ||||
|                 'choices': ['FULL', 'PART'], | ||||
|                 "option_strings": ("--pin",), | ||||
|                 "help": "Which PIN to kick", | ||||
|                 "choices": [2, 5], | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'cashdraw', | ||||
|             'help': 'Kick the cash drawer', | ||||
|         "parser": { | ||||
|             "name": "image", | ||||
|             "help": "Print an image", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'cashdraw', | ||||
|         "defaults": { | ||||
|             "func": "image", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--pin',), | ||||
|                 'help': 'Which PIN to kick', | ||||
|                 'choices': [2, 5], | ||||
|                 "option_strings": ("--img_source",), | ||||
|                 "help": "Path to image", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--impl",), | ||||
|                 "help": "Implementation to use", | ||||
|                 "choices": ["bitImageRaster", "bitImageColumn", "graphics"], | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--high_density_horizontal",), | ||||
|                 "help": "Image density (horizontal)", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 "option_strings": ("--high_density_vertical",), | ||||
|                 "help": "Image density (vertical)", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'image', | ||||
|             'help': 'Print an image', | ||||
|         "parser": { | ||||
|             "name": "fullimage", | ||||
|             "help": "Print a fullimage", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'image', | ||||
|         "defaults": { | ||||
|             "func": "fullimage", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--img_source',), | ||||
|                 'help': 'Path to image', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--img",), | ||||
|                 "help": "Path to img", | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--impl',), | ||||
|                 'help': 'Implementation to use', | ||||
|                 'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'], | ||||
|                 "option_strings": ("--max_height",), | ||||
|                 "help": "Max height of image in px", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--high_density_horizontal',), | ||||
|                 'help': 'Image density (horizontal)', | ||||
|                 'type': str_to_bool, | ||||
|                 "option_strings": ("--width",), | ||||
|                 "help": "Max width of image in px", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--high_density_vertical',), | ||||
|                 'help': 'Image density (vertical)', | ||||
|                 'type': str_to_bool, | ||||
|             }, | ||||
|  | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'fullimage', | ||||
|             'help': 'Print a fullimage', | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'fullimage', | ||||
|         }, | ||||
|         'arguments': [ | ||||
|             { | ||||
|                 'option_strings': ('--img',), | ||||
|                 'help': 'Path to img', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--histeq",), | ||||
|                 "help": "Equalize the histrogram", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--max_height',), | ||||
|                 'help': 'Max height of image in px', | ||||
|                 'type': int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--width',), | ||||
|                 'help': 'Max width of image in px', | ||||
|                 'type': int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--histeq',), | ||||
|                 'help': 'Equalize the histrogram', | ||||
|                 'type': str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--bandsize',), | ||||
|                 'help': 'Size of bands to divide into when printing', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--bandsize",), | ||||
|                 "help": "Size of bands to divide into when printing", | ||||
|                 "type": int, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'charcode', | ||||
|             'help': 'Set character code table', | ||||
|         "parser": { | ||||
|             "name": "charcode", | ||||
|             "help": "Set character code table", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'charcode', | ||||
|         "defaults": { | ||||
|             "func": "charcode", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--code',), | ||||
|                 'help': 'Character code', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--code",), | ||||
|                 "help": "Character code", | ||||
|                 "required": True, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'set', | ||||
|             'help': 'Set text properties', | ||||
|         "parser": { | ||||
|             "name": "set", | ||||
|             "help": "Set text properties", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'set', | ||||
|         "defaults": { | ||||
|             "func": "set", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--align',), | ||||
|                 'help': 'Horizontal alignment', | ||||
|                 'choices': ['left', 'center', 'right'], | ||||
|                 "option_strings": ("--align",), | ||||
|                 "help": "Horizontal alignment", | ||||
|                 "choices": ["left", "center", "right"], | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--font',), | ||||
|                 'help': 'Font choice', | ||||
|                 'choices': ['left', 'center', 'right'], | ||||
|                 "option_strings": ("--font",), | ||||
|                 "help": "Font choice", | ||||
|                 "choices": ["left", "center", "right"], | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--text_type',), | ||||
|                 'help': 'Text properties', | ||||
|                 'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'], | ||||
|                 "option_strings": ("--text_type",), | ||||
|                 "help": "Text properties", | ||||
|                 "choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"], | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--width',), | ||||
|                 'help': 'Width multiplier', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--width",), | ||||
|                 "help": "Width multiplier", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--height',), | ||||
|                 'help': 'Height multiplier', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--height",), | ||||
|                 "help": "Height multiplier", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--density',), | ||||
|                 'help': 'Print density', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--density",), | ||||
|                 "help": "Print density", | ||||
|                 "type": int, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--invert',), | ||||
|                 'help': 'White on black printing', | ||||
|                 'type': str_to_bool, | ||||
|                 "option_strings": ("--invert",), | ||||
|                 "help": "White on black printing", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--smooth',), | ||||
|                 'help': 'Text smoothing. Effective on >:  4x4 text', | ||||
|                 'type': str_to_bool, | ||||
|                 "option_strings": ("--smooth",), | ||||
|                 "help": "Text smoothing. Effective on >:  4x4 text", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--flip',), | ||||
|                 'help': 'Text smoothing. Effective on >:  4x4 text', | ||||
|                 'type': str_to_bool, | ||||
|                 "option_strings": ("--flip",), | ||||
|                 "help": "Text smoothing. Effective on >:  4x4 text", | ||||
|                 "type": str_to_bool, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'hw', | ||||
|             'help': 'Hardware operations', | ||||
|         "parser": { | ||||
|             "name": "hw", | ||||
|             "help": "Hardware operations", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'hw', | ||||
|         "defaults": { | ||||
|             "func": "hw", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--hw',), | ||||
|                 'help': 'Operation', | ||||
|                 'choices': ['INIT', 'SELECT', 'RESET'], | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--hw",), | ||||
|                 "help": "Operation", | ||||
|                 "choices": ["INIT", "SELECT", "RESET"], | ||||
|                 "required": True, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'control', | ||||
|             'help': 'Control sequences', | ||||
|         "parser": { | ||||
|             "name": "control", | ||||
|             "help": "Control sequences", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'control', | ||||
|         "defaults": { | ||||
|             "func": "control", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--ctl',), | ||||
|                 'help': 'Control sequence', | ||||
|                 'choices': ['LF', 'FF', 'CR', 'HT', 'VT'], | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--ctl",), | ||||
|                 "help": "Control sequence", | ||||
|                 "choices": ["LF", "FF", "CR", "HT", "VT"], | ||||
|                 "required": True, | ||||
|             }, | ||||
|             { | ||||
|                 'option_strings': ('--pos',), | ||||
|                 'help': 'Horizontal tab position (1-4)', | ||||
|                 'type': int, | ||||
|                 "option_strings": ("--pos",), | ||||
|                 "help": "Horizontal tab position (1-4)", | ||||
|                 "type": int, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'panel_buttons', | ||||
|             'help': 'Controls panel buttons', | ||||
|         "parser": { | ||||
|             "name": "panel_buttons", | ||||
|             "help": "Controls panel buttons", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': 'panel_buttons', | ||||
|         "defaults": { | ||||
|             "func": "panel_buttons", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--enable',), | ||||
|                 'help': 'Feed button enabled', | ||||
|                 'type': str_to_bool, | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--enable",), | ||||
|                 "help": "Feed button enabled", | ||||
|                 "type": str_to_bool, | ||||
|                 "required": True, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     { | ||||
|         'parser': { | ||||
|             'name': 'raw', | ||||
|             'help': 'Raw data', | ||||
|         "parser": { | ||||
|             "name": "raw", | ||||
|             "help": "Raw data", | ||||
|         }, | ||||
|         'defaults': { | ||||
|             'func': '_raw', | ||||
|         "defaults": { | ||||
|             "func": "_raw", | ||||
|         }, | ||||
|         'arguments': [ | ||||
|         "arguments": [ | ||||
|             { | ||||
|                 'option_strings': ('--msg',), | ||||
|                 'help': 'Raw data to send', | ||||
|                 'required': True, | ||||
|                 "option_strings": ("--msg",), | ||||
|                 "help": "Raw data to send", | ||||
|                 "required": True, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
| @@ -453,68 +455,71 @@ def main(): | ||||
|     """ | ||||
|  | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description='CLI for python-escpos', | ||||
|         epilog='Printer configuration is defined in the python-escpos config' | ||||
|         'file. See documentation for details.', | ||||
|         description="CLI for python-escpos", | ||||
|         epilog="Printer configuration is defined in the python-escpos config" | ||||
|         "file. See documentation for details.", | ||||
|     ) | ||||
|  | ||||
|     parser.register('type', 'bool', str_to_bool) | ||||
|     parser.register("type", "bool", str_to_bool) | ||||
|  | ||||
|     # Allow config file location to be passed | ||||
|     parser.add_argument( | ||||
|         '-c', '--config', | ||||
|         help='Alternate path to the configuration file', | ||||
|         "-c", | ||||
|         "--config", | ||||
|         help="Alternate path to the configuration file", | ||||
|     ) | ||||
|  | ||||
|     # Everything interesting runs off of a subparser so we can use the format | ||||
|     # cli [subparser] -args | ||||
|     command_subparsers = parser.add_subparsers( | ||||
|         title='ESCPOS Command', | ||||
|         dest='parser', | ||||
|         title="ESCPOS Command", | ||||
|         dest="parser", | ||||
|     ) | ||||
|     # fix inconsistencies in the behaviour of some versions of argparse | ||||
|     command_subparsers.required = False   # force 'required' testing | ||||
|     command_subparsers.required = False  # force 'required' testing | ||||
|  | ||||
|     # Build the ESCPOS command arguments | ||||
|     for command in ESCPOS_COMMANDS: | ||||
|         parser_command = command_subparsers.add_parser(**command['parser']) | ||||
|         parser_command.set_defaults(**command['defaults']) | ||||
|         for argument in command['arguments']: | ||||
|             option_strings = argument.pop('option_strings') | ||||
|         parser_command = command_subparsers.add_parser(**command["parser"]) | ||||
|         parser_command.set_defaults(**command["defaults"]) | ||||
|         for argument in command["arguments"]: | ||||
|             option_strings = argument.pop("option_strings") | ||||
|             parser_command.add_argument(*option_strings, **argument) | ||||
|  | ||||
|     # Build any custom arguments | ||||
|     parser_command_demo = command_subparsers.add_parser('demo', | ||||
|                                                         help='Demonstrates various functions') | ||||
|     parser_command_demo.set_defaults(func='demo') | ||||
|     parser_command_demo = command_subparsers.add_parser( | ||||
|         "demo", help="Demonstrates various functions" | ||||
|     ) | ||||
|     parser_command_demo.set_defaults(func="demo") | ||||
|     demo_group = parser_command_demo.add_mutually_exclusive_group() | ||||
|     demo_group.add_argument( | ||||
|         '--barcodes-a', | ||||
|         help='Print demo barcodes for function type A', | ||||
|         action='store_true', | ||||
|         "--barcodes-a", | ||||
|         help="Print demo barcodes for function type A", | ||||
|         action="store_true", | ||||
|     ) | ||||
|     demo_group.add_argument( | ||||
|         '--barcodes-b', | ||||
|         help='Print demo barcodes for function type B', | ||||
|         action='store_true', | ||||
|         "--barcodes-b", | ||||
|         help="Print demo barcodes for function type B", | ||||
|         action="store_true", | ||||
|     ) | ||||
|     demo_group.add_argument( | ||||
|         '--qr', | ||||
|         help='Print some demo QR codes', | ||||
|         action='store_true', | ||||
|         "--qr", | ||||
|         help="Print some demo QR codes", | ||||
|         action="store_true", | ||||
|     ) | ||||
|     demo_group.add_argument( | ||||
|         '--text', | ||||
|         help='Print some demo text', | ||||
|         action='store_true', | ||||
|         "--text", | ||||
|         help="Print some demo text", | ||||
|         action="store_true", | ||||
|     ) | ||||
|  | ||||
|     parser_command_version = command_subparsers.add_parser('version', | ||||
|                                                            help='Print the version of python-escpos') | ||||
|     parser_command_version = command_subparsers.add_parser( | ||||
|         "version", help="Print the version of python-escpos" | ||||
|     ) | ||||
|     parser_command_version.set_defaults(version=True) | ||||
|  | ||||
|     # hook in argcomplete | ||||
|     if 'argcomplete' in globals(): | ||||
|     if "argcomplete" in globals(): | ||||
|         argcomplete.autocomplete(parser) | ||||
|  | ||||
|     # Get only arguments actually passed | ||||
| @@ -522,16 +527,18 @@ def main(): | ||||
|     if not args_dict: | ||||
|         parser.print_help() | ||||
|         sys.exit() | ||||
|     command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None) | ||||
|     command_arguments = dict( | ||||
|         [k, v] for k, v in six.iteritems(args_dict) if v is not None | ||||
|     ) | ||||
|  | ||||
|     # If version should be printed, do this, then exit | ||||
|     print_version = command_arguments.pop('version', None) | ||||
|     print_version = command_arguments.pop("version", None) | ||||
|     if print_version: | ||||
|         print(version.version) | ||||
|         sys.exit() | ||||
|  | ||||
|     # If there was a config path passed, grab it | ||||
|     config_path = command_arguments.pop('config', None) | ||||
|     config_path = command_arguments.pop("config", None) | ||||
|  | ||||
|     # Load the configuration and defined printer | ||||
|     saved_config = config.Config() | ||||
| @@ -539,12 +546,12 @@ def main(): | ||||
|     printer = saved_config.printer() | ||||
|  | ||||
|     if not printer: | ||||
|         raise Exception('No printers loaded from config') | ||||
|         raise Exception("No printers loaded from config") | ||||
|  | ||||
|     target_command = command_arguments.pop('func') | ||||
|     target_command = command_arguments.pop("func") | ||||
|  | ||||
|     # remove helper-argument 'parser' from dict | ||||
|     command_arguments.pop('parser', None) | ||||
|     command_arguments.pop("parser", None) | ||||
|  | ||||
|     if hasattr(printer, target_command): | ||||
|         # print command with args | ||||
| @@ -552,13 +559,13 @@ def main(): | ||||
|         if target_command in REQUIRES_NEWLINE: | ||||
|             printer.text("\n") | ||||
|     else: | ||||
|         command_arguments['printer'] = printer | ||||
|         command_arguments["printer"] = printer | ||||
|         globals()[target_command](**command_arguments) | ||||
|  | ||||
|  | ||||
| def demo(printer, **kwargs): | ||||
|     """ | ||||
|     Prints specificed demos. Called when CLI is passed `demo`. This function | ||||
|     Prints demos. Called when CLI is passed `demo`. This function | ||||
|     uses the DEMO_FUNCTIONS dictionary. | ||||
|  | ||||
|     :param printer: A printer from escpos.printer | ||||
| @@ -568,14 +575,14 @@ def demo(printer, **kwargs): | ||||
|     for demo_choice in kwargs.keys(): | ||||
|         command = getattr( | ||||
|             printer, | ||||
|             demo_choice | ||||
|             .replace('barcodes_a', 'barcode') | ||||
|             .replace('barcodes_b', 'barcode') | ||||
|             demo_choice.replace("barcodes_a", "barcode").replace( | ||||
|                 "barcodes_b", "barcode" | ||||
|             ), | ||||
|         ) | ||||
|         for params in DEMO_FUNCTIONS[demo_choice]: | ||||
|             command(**params) | ||||
|         printer.cut() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
|   | ||||
| @@ -21,4 +21,4 @@ class CodePageManager: | ||||
|         return self.data[encoding] | ||||
|  | ||||
|  | ||||
| CodePages = CodePageManager(CAPABILITIES['encodings']) | ||||
| CodePages = CodePageManager(CAPABILITIES["encodings"]) | ||||
|   | ||||
| @@ -1,13 +1,9 @@ | ||||
| """ ESC/POS configuration manager. | ||||
|  | ||||
| This module contains the implentations of abstract base class :py:class:`Config`. | ||||
| This module contains the implementations of abstract base class :py:class:`Config`. | ||||
|  | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import os | ||||
| import appdirs | ||||
| @@ -18,16 +14,17 @@ from . import exceptions | ||||
|  | ||||
|  | ||||
| class Config(object): | ||||
|     """  Configuration handler class. | ||||
|     """Configuration handler class. | ||||
|  | ||||
|     This class loads configuration from a default or specificed directory. It | ||||
|     can create your defined printer and return it to you. | ||||
|     """ | ||||
|     _app_name = 'python-escpos' | ||||
|     _config_file = 'config.yaml' | ||||
|  | ||||
|     _app_name = "python-escpos" | ||||
|     _config_file = "config.yaml" | ||||
|  | ||||
|     def __init__(self): | ||||
|         """ Initialize configuration. | ||||
|         """Initialize configuration. | ||||
|  | ||||
|         Remember to add anything that needs to be reset between configurations | ||||
|         to self._reset_config | ||||
| @@ -39,7 +36,7 @@ class Config(object): | ||||
|         self._printer_config = None | ||||
|  | ||||
|     def _reset_config(self): | ||||
|         """ Clear the loaded configuration. | ||||
|         """Clear the loaded configuration. | ||||
|  | ||||
|         If we are loading a changed config, we don't want to have leftover | ||||
|         data. | ||||
| @@ -51,7 +48,7 @@ class Config(object): | ||||
|         self._printer_config = None | ||||
|  | ||||
|     def load(self, config_path=None): | ||||
|         """ Load and parse the configuration file using pyyaml | ||||
|         """Load and parse the configuration file using pyyaml | ||||
|  | ||||
|         :param config_path: An optional file path, file handle, or byte string | ||||
|             for the configuration file. | ||||
| @@ -62,31 +59,32 @@ class Config(object): | ||||
|  | ||||
|         if not config_path: | ||||
|             config_path = os.path.join( | ||||
|                 appdirs.user_config_dir(self._app_name), | ||||
|                 self._config_file | ||||
|                 appdirs.user_config_dir(self._app_name), self._config_file | ||||
|             ) | ||||
|  | ||||
|         try: | ||||
|             # First check if it's file like. If it is, pyyaml can load it. | ||||
|             # I'm checking type instead of catching exceptions to keep the | ||||
|             # exception handling simple | ||||
|             if hasattr(config_path, 'read'): | ||||
|             if hasattr(config_path, "read"): | ||||
|                 config = yaml.safe_load(config_path) | ||||
|             else: | ||||
|                 # If it isn't, it's a path. We have to open it first, otherwise | ||||
|                 # pyyaml will try to read it as yaml | ||||
|                 with open(config_path, 'rb') as config_file: | ||||
|                 with open(config_path, "rb") as config_file: | ||||
|                     config = yaml.safe_load(config_file) | ||||
|         except EnvironmentError: | ||||
|             raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format( | ||||
|                 config_path=str(config_path), | ||||
|             )) | ||||
|             raise exceptions.ConfigNotFoundError( | ||||
|                 "Couldn't read config at {config_path}".format( | ||||
|                     config_path=str(config_path), | ||||
|                 ) | ||||
|             ) | ||||
|         except yaml.YAMLError: | ||||
|             raise exceptions.ConfigSyntaxError('Error parsing YAML') | ||||
|             raise exceptions.ConfigSyntaxError("Error parsing YAML") | ||||
|  | ||||
|         if 'printer' in config: | ||||
|             self._printer_config = config['printer'] | ||||
|             self._printer_name = self._printer_config.pop('type').title() | ||||
|         if "printer" in config: | ||||
|             self._printer_config = config["printer"] | ||||
|             self._printer_name = self._printer_config.pop("type").title() | ||||
|  | ||||
|             if not self._printer_name or not hasattr(printer, self._printer_name): | ||||
|                 raise exceptions.ConfigSyntaxError( | ||||
| @@ -98,7 +96,7 @@ class Config(object): | ||||
|         self._has_loaded = True | ||||
|  | ||||
|     def printer(self): | ||||
|         """ Returns a printer that was defined in the config, or throws an | ||||
|         """Returns a printer that was defined in the config, or throws an | ||||
|         exception. | ||||
|  | ||||
|         This method loads the default config if one hasn't beeen already loaded. | ||||
| @@ -108,7 +106,7 @@ class Config(object): | ||||
|             self.load() | ||||
|  | ||||
|         if not self._printer_name: | ||||
|             raise exceptions.ConfigSectionMissingError('printer') | ||||
|             raise exceptions.ConfigSectionMissingError("printer") | ||||
|  | ||||
|         if not self._printer: | ||||
|             # We could catch init errors and make them a ConfigSyntaxError, | ||||
|   | ||||
| @@ -11,129 +11,130 @@ moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| # Control characters | ||||
| # as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf | ||||
| NUL = b'\x00' | ||||
| EOT = b'\x04' | ||||
| ENQ = b'\x05' | ||||
| DLE = b'\x10' | ||||
| DC4 = b'\x14' | ||||
| CAN = b'\x18' | ||||
| ESC = b'\x1b' | ||||
| FS  = b'\x1c' | ||||
| GS  = b'\x1d' | ||||
| # as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf | ||||
| NUL = b"\x00" | ||||
| EOT = b"\x04" | ||||
| ENQ = b"\x05" | ||||
| DLE = b"\x10" | ||||
| DC4 = b"\x14" | ||||
| CAN = b"\x18" | ||||
| ESC = b"\x1b" | ||||
| FS = b"\x1c" | ||||
| GS = b"\x1d" | ||||
|  | ||||
| # Feed control sequences | ||||
| CTL_LF = b'\n'              # Print and line feed | ||||
| CTL_FF = b'\f'              # Form feed | ||||
| CTL_CR = b'\r'              # Carriage return | ||||
| CTL_HT = b'\t'              # Horizontal tab | ||||
| CTL_SET_HT = ESC + b'\x44'  # Set horizontal tab positions | ||||
| CTL_VT = b'\v'              # Vertical tab | ||||
| CTL_LF = b"\n"  # Print and line feed | ||||
| CTL_FF = b"\f"  # Form feed | ||||
| CTL_CR = b"\r"  # Carriage return | ||||
| CTL_HT = b"\t"  # Horizontal tab | ||||
| CTL_SET_HT = ESC + b"\x44"  # Set horizontal tab positions | ||||
| CTL_VT = b"\v"  # Vertical tab | ||||
|  | ||||
| # Printer hardware | ||||
| HW_INIT   = ESC + b'@'             # Clear data in buffer and reset modes | ||||
| HW_SELECT = ESC + b'=\x01'         # Printer select | ||||
| HW_INIT = ESC + b"@"  # Clear data in buffer and reset modes | ||||
| HW_SELECT = ESC + b"=\x01"  # Printer select | ||||
|  | ||||
| HW_RESET  = ESC + b'\x3f\x0a\x00'   # Reset printer hardware | ||||
|                                     # (TODO: Where is this specified?) | ||||
| HW_RESET = ESC + b"\x3f\x0a\x00"  # Reset printer hardware | ||||
| # (TODO: Where is this specified?) | ||||
|  | ||||
| # Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>) | ||||
| _CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2) | ||||
| CD_KICK_DEC_SEQUENCE = lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) + six.int2byte(p) + six.int2byte(m) + six.int2byte(t1) + six.int2byte(t2) | ||||
| CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50)  # Sends a pulse to pin 2 [] | ||||
| CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50)  # Sends a pulse to pin 5 [] | ||||
| _CASH_DRAWER = ( | ||||
|     lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2) | ||||
| ) | ||||
| CD_KICK_DEC_SEQUENCE = ( | ||||
|     lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) | ||||
|     + six.int2byte(p) | ||||
|     + six.int2byte(m) | ||||
|     + six.int2byte(t1) | ||||
|     + six.int2byte(t2) | ||||
| ) | ||||
| CD_KICK_2 = _CASH_DRAWER(b"\x00", 50, 50)  # Sends a pulse to pin 2 [] | ||||
| CD_KICK_5 = _CASH_DRAWER(b"\x01", 50, 50)  # Sends a pulse to pin 5 [] | ||||
|  | ||||
| # Paper Cutter | ||||
| _CUT_PAPER = lambda m: GS + b'V' + m | ||||
| PAPER_FULL_CUT = _CUT_PAPER(b'\x00')  # Full cut paper | ||||
| PAPER_PART_CUT = _CUT_PAPER(b'\x01')  # Partial cut paper | ||||
| _CUT_PAPER = lambda m: GS + b"V" + m | ||||
| PAPER_FULL_CUT = _CUT_PAPER(b"\x00")  # Full cut paper | ||||
| PAPER_PART_CUT = _CUT_PAPER(b"\x01")  # Partial cut paper | ||||
|  | ||||
| # Beep | ||||
| BEEP = b'\x07' | ||||
| # Beep (please note that the actual beep sequence may differ between devices) | ||||
| BEEP = b"\x07" | ||||
|  | ||||
| # Panel buttons (e.g. the FEED button) | ||||
| _PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) | ||||
| _PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n) | ||||
| PANEL_BUTTON_ON = _PANEL_BUTTON(0)  # enable all panel buttons | ||||
| PANEL_BUTTON_OFF = _PANEL_BUTTON(1)  # disable all panel buttons | ||||
|  | ||||
| # Line display printing | ||||
| LINE_DISPLAY_OPEN = ESC + b'\x3d\x02' | ||||
| LINE_DISPLAY_CLEAR = ESC + b'\x40' | ||||
| LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01' | ||||
| LINE_DISPLAY_OPEN = ESC + b"\x3d\x02" | ||||
| LINE_DISPLAY_CLEAR = ESC + b"\x40" | ||||
| LINE_DISPLAY_CLOSE = ESC + b"\x3d\x01" | ||||
|  | ||||
| # Sheet modes | ||||
| SHEET_SLIP_MODE = ESC + b'\x63\x30\x04'  # slip paper | ||||
| SHEET_ROLL_MODE = ESC + b'\x63\x30\x01'  # paper roll | ||||
| SHEET_SLIP_MODE = ESC + b"\x63\x30\x04"  # slip paper | ||||
| SHEET_ROLL_MODE = ESC + b"\x63\x30\x01"  # paper roll | ||||
|  | ||||
| # Text format | ||||
| # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll | ||||
| #       Printers" and tidy up this stuff too. | ||||
| TXT_SIZE       = GS + b'!' | ||||
| TXT_SIZE = GS + b"!" | ||||
|  | ||||
| TXT_NORMAL     = ESC + b'!\x00'     # Normal text | ||||
| TXT_NORMAL = ESC + b"!\x00"  # Normal text | ||||
|  | ||||
|  | ||||
| TXT_STYLE = { | ||||
|     'bold': { | ||||
|         False: ESC + b'\x45\x00',               # Bold font OFF | ||||
|         True: ESC + b'\x45\x01'                 # Bold font ON | ||||
|     "bold": { | ||||
|         False: ESC + b"\x45\x00",  # Bold font OFF | ||||
|         True: ESC + b"\x45\x01",  # Bold font ON | ||||
|     }, | ||||
|     'underline': { | ||||
|         0: ESC + b'\x2d\x00',                   # Underline font OFF | ||||
|         1: ESC + b'\x2d\x01',                   # Underline font 1-dot ON | ||||
|         2: ESC + b'\x2d\x02'                    # Underline font 2-dot ON | ||||
|     "underline": { | ||||
|         0: ESC + b"\x2d\x00",  # Underline font OFF | ||||
|         1: ESC + b"\x2d\x01",  # Underline font 1-dot ON | ||||
|         2: ESC + b"\x2d\x02",  # Underline font 2-dot ON | ||||
|     }, | ||||
|     'size': { | ||||
|         'normal': TXT_NORMAL + ESC + b'!\x00',  # Normal text | ||||
|         '2h': TXT_NORMAL + ESC + b'!\x10',      # Double height text | ||||
|         '2w': TXT_NORMAL + ESC + b'!\x20',      # Double width text | ||||
|         '2x': TXT_NORMAL + ESC + b'!\x30'       # Quad area text | ||||
|     "size": { | ||||
|         "normal": TXT_NORMAL + ESC + b"!\x00",  # Normal text | ||||
|         "2h": TXT_NORMAL + ESC + b"!\x10",  # Double height text | ||||
|         "2w": TXT_NORMAL + ESC + b"!\x20",  # Double width text | ||||
|         "2x": TXT_NORMAL + ESC + b"!\x30",  # Quad area text | ||||
|     }, | ||||
|     'font': { | ||||
|         'a': ESC + b'\x4d\x00',                 # Font type A | ||||
|         'b': ESC + b'\x4d\x00'                  # Font type B | ||||
|     "font": { | ||||
|         "a": ESC + b"\x4d\x00",  # Font type A | ||||
|         "b": ESC + b"\x4d\x00",  # Font type B | ||||
|     }, | ||||
|     'align': { | ||||
|         'left': ESC + b'\x61\x00',              # Left justification | ||||
|         'center': ESC + b'\x61\x01',            # Centering | ||||
|         'right': ESC + b'\x61\x02'              # Right justification | ||||
|     "align": { | ||||
|         "left": ESC + b"\x61\x00",  # Left justification | ||||
|         "center": ESC + b"\x61\x01",  # Centering | ||||
|         "right": ESC + b"\x61\x02",  # Right justification | ||||
|     }, | ||||
|     'invert': { | ||||
|         True: GS  + b'\x42\x01',                # Inverse Printing ON | ||||
|         False: GS  + b'\x42\x00'                # Inverse Printing OFF | ||||
|     "invert": { | ||||
|         True: GS + b"\x42\x01",  # Inverse Printing ON | ||||
|         False: GS + b"\x42\x00",  # Inverse Printing OFF | ||||
|     }, | ||||
|     'color': { | ||||
|         'black': ESC + b'\x72\x00',             # Default Color | ||||
|         'red': ESC + b'\x72\x01'                # Alternative Color, Usually Red | ||||
|     "color": { | ||||
|         "black": ESC + b"\x72\x00",  # Default Color | ||||
|         "red": ESC + b"\x72\x01",  # Alternative Color, Usually Red | ||||
|     }, | ||||
|     'flip': { | ||||
|         True: ESC + b'\x7b\x01',                # Flip ON | ||||
|         False: ESC + b'\x7b\x00'                # Flip OFF | ||||
|     "flip": {True: ESC + b"\x7b\x01", False: ESC + b"\x7b\x00"},  # Flip ON  # Flip OFF | ||||
|     "density": { | ||||
|         0: GS + b"\x7c\x00",  # Printing Density -50% | ||||
|         1: GS + b"\x7c\x01",  # Printing Density -37.5% | ||||
|         2: GS + b"\x7c\x02",  # Printing Density -25% | ||||
|         3: GS + b"\x7c\x03",  # Printing Density -12.5% | ||||
|         4: GS + b"\x7c\x04",  # Printing Density  0% | ||||
|         5: GS + b"\x7c\x08",  # Printing Density +50% | ||||
|         6: GS + b"\x7c\x07",  # Printing Density +37.5% | ||||
|         7: GS + b"\x7c\x06",  # Printing Density +25% | ||||
|         8: GS + b"\x7c\x05",  # Printing Density +12.5% | ||||
|     }, | ||||
|     'density': { | ||||
|         0: GS + b'\x7c\x00',                    # Printing Density -50% | ||||
|         1: GS + b'\x7c\x01',                    # Printing Density -37.5% | ||||
|         2: GS + b'\x7c\x02',                    # Printing Density -25% | ||||
|         3: GS + b'\x7c\x03',                    # Printing Density -12.5% | ||||
|         4: GS + b'\x7c\x04',                    # Printing Density  0% | ||||
|         5: GS + b'\x7c\x08',                    # Printing Density +50% | ||||
|         6: GS + b'\x7c\x07',                    # Printing Density +37.5% | ||||
|         7: GS + b'\x7c\x06',                    # Printing Density +25% | ||||
|         8: GS + b'\x7c\x05'                     # Printing Density +12.5% | ||||
|     "smooth": { | ||||
|         True: GS + b"\x62\x01",  # Smooth ON | ||||
|         False: GS + b"\x62\x00",  # Smooth OFF | ||||
|     }, | ||||
|     'smooth': { | ||||
|         True: GS + b'\x62\x01',                 # Smooth ON | ||||
|         False: GS + b'\x62\x00'                 # Smooth OFF | ||||
|     }, | ||||
|     'height': {                                 # Custom text height | ||||
|     "height": {  # Custom text height | ||||
|         1: 0x00, | ||||
|         2: 0x01, | ||||
|         3: 0x02, | ||||
| @@ -141,9 +142,9 @@ TXT_STYLE = { | ||||
|         5: 0x04, | ||||
|         6: 0x05, | ||||
|         7: 0x06, | ||||
|         8: 0x07 | ||||
|         8: 0x07, | ||||
|     }, | ||||
|     'width': {                                  # Custom text width | ||||
|     "width": {  # Custom text width | ||||
|         1: 0x00, | ||||
|         2: 0x10, | ||||
|         3: 0x20, | ||||
| @@ -151,101 +152,104 @@ TXT_STYLE = { | ||||
|         5: 0x40, | ||||
|         6: 0x50, | ||||
|         7: 0x60, | ||||
|         8: 0x70 | ||||
|     } | ||||
|         8: 0x70, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| # Fonts | ||||
| SET_FONT = lambda n: ESC + b'\x4d' + n | ||||
| TXT_FONT_A     = SET_FONT(b'\x00')  # Font type A | ||||
| TXT_FONT_B     = SET_FONT(b'\x01')  # Font type B | ||||
| SET_FONT = lambda n: ESC + b"\x4d" + n | ||||
| TXT_FONT_A = SET_FONT(b"\x00")  # Font type A | ||||
| TXT_FONT_B = SET_FONT(b"\x01")  # Font type B | ||||
|  | ||||
| # Spacing | ||||
| LINESPACING_RESET = ESC + b'2' | ||||
| LINESPACING_RESET = ESC + b"2" | ||||
| LINESPACING_FUNCS = { | ||||
|   60: ESC + b'A',  # line_spacing/60 of an inch, 0 <= line_spacing <= 85 | ||||
|   360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255 | ||||
|   180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255 | ||||
|     60: ESC + b"A",  # line_spacing/60 of an inch, 0 <= line_spacing <= 85 | ||||
|     360: ESC + b"+",  # line_spacing/360 of an inch, 0 <= line_spacing <= 255 | ||||
|     180: ESC + b"3",  # line_spacing/180 of an inch, 0 <= line_spacing <= 255 | ||||
| } | ||||
|  | ||||
| # Prefix to change the codepage. You need to attach a byte to indicate | ||||
| # the codepage to use. We use escpos-printer-db as the data source. | ||||
| CODEPAGE_CHANGE = ESC + b'\x74' | ||||
| CODEPAGE_CHANGE = ESC + b"\x74" | ||||
|  | ||||
| # Barcode format | ||||
| _SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n | ||||
| BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00')  # HRI barcode chars OFF | ||||
| BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01')  # HRI barcode chars above | ||||
| BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02')  # HRI barcode chars below | ||||
| BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03')  # HRI both above and below | ||||
| _SET_BARCODE_TXT_POS = lambda n: GS + b"H" + n | ||||
| BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b"\x00")  # HRI barcode chars OFF | ||||
| BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b"\x01")  # HRI barcode chars above | ||||
| BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b"\x02")  # HRI barcode chars below | ||||
| BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b"\x03")  # HRI both above and below | ||||
|  | ||||
| _SET_HRI_FONT = lambda n: GS + b'f' + n | ||||
| BARCODE_FONT_A = _SET_HRI_FONT(b'\x00')  # Font type A for HRI barcode chars | ||||
| BARCODE_FONT_B = _SET_HRI_FONT(b'\x01')  # Font type B for HRI barcode chars | ||||
| _SET_HRI_FONT = lambda n: GS + b"f" + n | ||||
| BARCODE_FONT_A = _SET_HRI_FONT(b"\x00")  # Font type A for HRI barcode chars | ||||
| BARCODE_FONT_B = _SET_HRI_FONT(b"\x01")  # Font type B for HRI barcode chars | ||||
|  | ||||
| BARCODE_HEIGHT = GS + b'h'  # Barcode Height [1-255] | ||||
| BARCODE_WIDTH  = GS + b'w'  # Barcode Width  [2-6] | ||||
| BARCODE_HEIGHT = GS + b"h"  # Barcode Height [1-255] | ||||
| BARCODE_WIDTH = GS + b"w"  # Barcode Width  [2-6] | ||||
|  | ||||
| # NOTE: This isn't actually an ESC/POS command. It's the common prefix to the | ||||
| #      two "print bar code" commands: | ||||
| #      -  Type A: "GS k <type as integer> <data> NUL" | ||||
| #      -  TYPE B: "GS k <type as letter> <data length> <data>" | ||||
| #      The latter command supports more barcode types | ||||
| _SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m) | ||||
| _SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m) | ||||
|  | ||||
| # Barcodes for printing function type A | ||||
| BARCODE_TYPE_A = { | ||||
|     'UPC-A':   _SET_BARCODE_TYPE(0), | ||||
|     'UPC-E':   _SET_BARCODE_TYPE(1), | ||||
|     'EAN13':   _SET_BARCODE_TYPE(2), | ||||
|     'EAN8':    _SET_BARCODE_TYPE(3), | ||||
|     'CODE39':  _SET_BARCODE_TYPE(4), | ||||
|     'ITF':     _SET_BARCODE_TYPE(5), | ||||
|     'NW7':     _SET_BARCODE_TYPE(6), | ||||
|     'CODABAR': _SET_BARCODE_TYPE(6),  # Same as NW7 | ||||
|     "UPC-A": _SET_BARCODE_TYPE(0), | ||||
|     "UPC-E": _SET_BARCODE_TYPE(1), | ||||
|     "EAN13": _SET_BARCODE_TYPE(2), | ||||
|     "EAN8": _SET_BARCODE_TYPE(3), | ||||
|     "CODE39": _SET_BARCODE_TYPE(4), | ||||
|     "ITF": _SET_BARCODE_TYPE(5), | ||||
|     "NW7": _SET_BARCODE_TYPE(6), | ||||
|     "CODABAR": _SET_BARCODE_TYPE(6),  # Same as NW7 | ||||
| } | ||||
|  | ||||
| # Barcodes for printing function type B | ||||
| # The first 8 are the same barcodes as type A | ||||
| BARCODE_TYPE_B = { | ||||
|     'UPC-A':                       _SET_BARCODE_TYPE(65), | ||||
|     'UPC-E':                       _SET_BARCODE_TYPE(66), | ||||
|     'EAN13':                       _SET_BARCODE_TYPE(67), | ||||
|     'EAN8':                        _SET_BARCODE_TYPE(68), | ||||
|     'CODE39':                      _SET_BARCODE_TYPE(69), | ||||
|     'ITF':                         _SET_BARCODE_TYPE(70), | ||||
|     'NW7':                         _SET_BARCODE_TYPE(71), | ||||
|     'CODABAR':                     _SET_BARCODE_TYPE(71),  # Same as NW7 | ||||
|     'CODE93':                      _SET_BARCODE_TYPE(72), | ||||
|     'CODE128':                     _SET_BARCODE_TYPE(73), | ||||
|     'GS1-128':                     _SET_BARCODE_TYPE(74), | ||||
|     'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75), | ||||
|     'GS1 DATABAR TRUNCATED':       _SET_BARCODE_TYPE(76), | ||||
|     'GS1 DATABAR LIMITED':         _SET_BARCODE_TYPE(77), | ||||
|     'GS1 DATABAR EXPANDED':        _SET_BARCODE_TYPE(78), | ||||
|     "UPC-A": _SET_BARCODE_TYPE(65), | ||||
|     "UPC-E": _SET_BARCODE_TYPE(66), | ||||
|     "EAN13": _SET_BARCODE_TYPE(67), | ||||
|     "EAN8": _SET_BARCODE_TYPE(68), | ||||
|     "CODE39": _SET_BARCODE_TYPE(69), | ||||
|     "ITF": _SET_BARCODE_TYPE(70), | ||||
|     "NW7": _SET_BARCODE_TYPE(71), | ||||
|     "CODABAR": _SET_BARCODE_TYPE(71),  # Same as NW7 | ||||
|     "CODE93": _SET_BARCODE_TYPE(72), | ||||
|     "CODE128": _SET_BARCODE_TYPE(73), | ||||
|     "GS1-128": _SET_BARCODE_TYPE(74), | ||||
|     "GS1 DATABAR OMNIDIRECTIONAL": _SET_BARCODE_TYPE(75), | ||||
|     "GS1 DATABAR TRUNCATED": _SET_BARCODE_TYPE(76), | ||||
|     "GS1 DATABAR LIMITED": _SET_BARCODE_TYPE(77), | ||||
|     "GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78), | ||||
| } | ||||
|  | ||||
| BARCODE_FORMATS = { | ||||
|     'UPC-A':                       ([(11, 12)], "^[0-9]{11,12}$"), | ||||
|     'UPC-E':                       ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"), | ||||
|     'EAN13':                       ([(12, 13)], "^[0-9]{12,13}$"), | ||||
|     'EAN8':                        ([(7, 8)], "^[0-9]{7,8}$"), | ||||
|     'CODE39':                      ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"), | ||||
|     'ITF':                         ([(2, 255)], "^([0-9]{2})+$"), | ||||
|     'NW7':                         ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), | ||||
|     'CODABAR':                     ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),  # Same as NW7 | ||||
|     'CODE93':                      ([(1, 255)], "^[\\x00-\\x7F]+$"), | ||||
|     'CODE128':                     ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), | ||||
|     'GS1-128':                     ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128 | ||||
|     'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"), | ||||
|     'GS1 DATABAR TRUNCATED':       ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional | ||||
|     'GS1 DATABAR LIMITED':         ([(13,13)], "^[01][0-9]{12}$"), | ||||
|     'GS1 DATABAR EXPANDED':        ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"), | ||||
|     "UPC-A": ([(11, 12)], "^[0-9]{11,12}$"), | ||||
|     "UPC-E": ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"), | ||||
|     "EAN13": ([(12, 13)], "^[0-9]{12,13}$"), | ||||
|     "EAN8": ([(7, 8)], "^[0-9]{7,8}$"), | ||||
|     "CODE39": ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"), | ||||
|     "ITF": ([(2, 255)], "^([0-9]{2})+$"), | ||||
|     "NW7": ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), | ||||
|     "CODABAR": ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),  # Same as NW7 | ||||
|     "CODE93": ([(1, 255)], "^[\\x00-\\x7F]+$"), | ||||
|     "CODE128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), | ||||
|     "GS1-128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),  # same as CODE128 | ||||
|     "GS1 DATABAR OMNIDIRECTIONAL": ([(13, 13)], "^[0-9]{13}$"), | ||||
|     "GS1 DATABAR TRUNCATED": ([(13, 13)], "^[0-9]{13}$"),  # same as GS1 omnidirectional | ||||
|     "GS1 DATABAR LIMITED": ([(13, 13)], "^[01][0-9]{12}$"), | ||||
|     "GS1 DATABAR EXPANDED": ( | ||||
|         [(2, 255)], | ||||
|         "^\([0-9][A-Za-z0-9 \!\"\%\&'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$", | ||||
|     ), | ||||
| } | ||||
|  | ||||
| BARCODE_TYPES = { | ||||
|     'A': BARCODE_TYPE_A, | ||||
|     'B': BARCODE_TYPE_B, | ||||
|     "A": BARCODE_TYPE_A, | ||||
|     "B": BARCODE_TYPE_B, | ||||
| } | ||||
|  | ||||
| # QRCode error correction levels | ||||
| @@ -262,17 +266,17 @@ QR_MICRO = 3 | ||||
| # Image format | ||||
| # NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image" | ||||
| #       command. The constants include a fragment of the data's header. | ||||
| _PRINT_RASTER_IMG = lambda data: GS + b'v0' + data | ||||
| S_RASTER_N  = _PRINT_RASTER_IMG(b'\x00')  # Set raster image normal size | ||||
| S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01')  # Set raster image double width | ||||
| S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02')  # Set raster image double height | ||||
| S_RASTER_Q  = _PRINT_RASTER_IMG(b'\x03')  # Set raster image quadruple | ||||
| _PRINT_RASTER_IMG = lambda data: GS + b"v0" + data | ||||
| S_RASTER_N = _PRINT_RASTER_IMG(b"\x00")  # Set raster image normal size | ||||
| S_RASTER_2W = _PRINT_RASTER_IMG(b"\x01")  # Set raster image double width | ||||
| S_RASTER_2H = _PRINT_RASTER_IMG(b"\x02")  # Set raster image double height | ||||
| S_RASTER_Q = _PRINT_RASTER_IMG(b"\x03")  # Set raster image quadruple | ||||
|  | ||||
| # Status Command | ||||
| RT_STATUS = DLE + EOT | ||||
| RT_STATUS_ONLINE = RT_STATUS +  b'\x01' | ||||
| RT_STATUS_PAPER = RT_STATUS +  b'\x04' | ||||
| RT_STATUS_ONLINE = RT_STATUS + b"\x01" | ||||
| RT_STATUS_PAPER = RT_STATUS + b"\x04" | ||||
| RT_MASK_ONLINE = 8 | ||||
| RT_MASK_PAPER = 18 | ||||
| RT_MASK_LOWPAPER = 30 | ||||
| RT_MASK_NOPAPER = 114 | ||||
| RT_MASK_NOPAPER = 114 | ||||
|   | ||||
| @@ -10,10 +10,6 @@ This module contains the abstract base class :py:class:`Escpos`. | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import qrcode | ||||
| import textwrap | ||||
| @@ -26,17 +22,51 @@ from barcode.writer import ImageWriter | ||||
|  | ||||
| import os | ||||
|  | ||||
| from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q | ||||
| from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH | ||||
| from .constants import ( | ||||
|     ESC, | ||||
|     GS, | ||||
|     NUL, | ||||
|     QR_ECLEVEL_L, | ||||
|     QR_ECLEVEL_M, | ||||
|     QR_ECLEVEL_H, | ||||
|     QR_ECLEVEL_Q, | ||||
| ) | ||||
| from .constants import ( | ||||
|     QR_MODEL_1, | ||||
|     QR_MODEL_2, | ||||
|     QR_MICRO, | ||||
|     BARCODE_TYPES, | ||||
|     BARCODE_HEIGHT, | ||||
|     BARCODE_WIDTH, | ||||
| ) | ||||
| from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS | ||||
| from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW | ||||
| from .constants import ( | ||||
|     BARCODE_TXT_OFF, | ||||
|     BARCODE_TXT_BTH, | ||||
|     BARCODE_TXT_ABV, | ||||
|     BARCODE_TXT_BLW, | ||||
| ) | ||||
| from .constants import TXT_SIZE, TXT_NORMAL | ||||
| from .constants import SET_FONT | ||||
| from .constants import LINESPACING_FUNCS, LINESPACING_RESET | ||||
| from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE | ||||
| from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT | ||||
| from .constants import ( | ||||
|     CD_KICK_DEC_SEQUENCE, | ||||
|     CD_KICK_5, | ||||
|     CD_KICK_2, | ||||
|     PAPER_FULL_CUT, | ||||
|     PAPER_PART_CUT, | ||||
| ) | ||||
| from .constants import HW_RESET, HW_SELECT, HW_INIT | ||||
| from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON | ||||
| from .constants import ( | ||||
|     CTL_VT, | ||||
|     CTL_CR, | ||||
|     CTL_FF, | ||||
|     CTL_LF, | ||||
|     CTL_SET_HT, | ||||
|     PANEL_BUTTON_OFF, | ||||
|     PANEL_BUTTON_ON, | ||||
| ) | ||||
| from .constants import TXT_STYLE | ||||
| from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE | ||||
| from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER | ||||
| @@ -54,27 +84,28 @@ from escpos.capabilities import get_profile, BARCODE_B | ||||
|  | ||||
| @six.add_metaclass(ABCMeta) | ||||
| class Escpos(object): | ||||
|     """ ESC/POS Printer object | ||||
|     """ESC/POS Printer object | ||||
|  | ||||
|     This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this | ||||
|     class. | ||||
|     """ | ||||
|  | ||||
|     device = None | ||||
|  | ||||
|     def __init__(self, profile=None, magic_encode_args=None, **kwargs): | ||||
|         """ Initialize ESCPOS Printer | ||||
|         """Initialize ESCPOS Printer | ||||
|  | ||||
|         :param profile: Printer profile""" | ||||
|         self.profile = get_profile(profile) | ||||
|         self.magic = MagicEncode(self, **(magic_encode_args or {})) | ||||
|  | ||||
|     def __del__(self): | ||||
|         """ call self.close upon deletion """ | ||||
|         """call self.close upon deletion""" | ||||
|         self.close() | ||||
|  | ||||
|     @abstractmethod | ||||
|     def _raw(self, msg): | ||||
|         """ Sends raw data to the printer | ||||
|         """Sends raw data to the printer | ||||
|  | ||||
|         This function has to be individually implemented by the implementations. | ||||
|  | ||||
| @@ -84,14 +115,21 @@ class Escpos(object): | ||||
|         pass | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Returns a NotImplementedError if the instance of the class doesn't override this method. | ||||
|         """Returns a NotImplementedError if the instance of the class doesn't override this method. | ||||
|         :raises NotImplementedError | ||||
|         """ | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", | ||||
|               fragment_height=960, center=False): | ||||
|         """ Print an image | ||||
|     def image( | ||||
|         self, | ||||
|         img_source, | ||||
|         high_density_vertical=True, | ||||
|         high_density_horizontal=True, | ||||
|         impl="bitImageRaster", | ||||
|         fragment_height=960, | ||||
|         center=False, | ||||
|     ): | ||||
|         """Print an image | ||||
|  | ||||
|         You can select whether the printer should print in high density or not. The default value is high density. | ||||
|         When printing in low density, the image will be stretched. | ||||
| @@ -120,14 +158,16 @@ class Escpos(object): | ||||
|         im = EscposImage(img_source) | ||||
|  | ||||
|         try: | ||||
|             if self.profile.profile_data['media']['width']['pixels'] == "Unknown": | ||||
|                 print("The media.width.pixel field of the printer profile is not set. " + | ||||
|                       "The center flag will have no effect.") | ||||
|             if self.profile.profile_data["media"]["width"]["pixels"] == "Unknown": | ||||
|                 print( | ||||
|                     "The media.width.pixel field of the printer profile is not set. " | ||||
|                     + "The center flag will have no effect." | ||||
|                 ) | ||||
|  | ||||
|             max_width = int(self.profile.profile_data['media']['width']['pixels']) | ||||
|             max_width = int(self.profile.profile_data["media"]["width"]["pixels"]) | ||||
|  | ||||
|             if im.width > max_width: | ||||
|                 raise ImageWidthError('{} > {}'.format(im.width, max_width)) | ||||
|                 raise ImageWidthError("{} > {}".format(im.width, max_width)) | ||||
|  | ||||
|             if center: | ||||
|                 im.center(max_width) | ||||
| @@ -141,41 +181,59 @@ class Escpos(object): | ||||
|         if im.height > fragment_height: | ||||
|             fragments = im.split(fragment_height) | ||||
|             for fragment in fragments: | ||||
|                 self.image(fragment, | ||||
|                            high_density_vertical=high_density_vertical, | ||||
|                            high_density_horizontal=high_density_horizontal, | ||||
|                            impl=impl, | ||||
|                            fragment_height=fragment_height) | ||||
|                 self.image( | ||||
|                     fragment, | ||||
|                     high_density_vertical=high_density_vertical, | ||||
|                     high_density_horizontal=high_density_horizontal, | ||||
|                     impl=impl, | ||||
|                     fragment_height=fragment_height, | ||||
|                 ) | ||||
|             return | ||||
|  | ||||
|         if impl == "bitImageRaster": | ||||
|             # GS v 0, raster format bit image | ||||
|             density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2) | ||||
|             header = GS + b"v0" + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\ | ||||
|                 self._int_low_high(im.height, 2) | ||||
|             density_byte = (0 if high_density_horizontal else 1) + ( | ||||
|                 0 if high_density_vertical else 2 | ||||
|             ) | ||||
|             header = ( | ||||
|                 GS | ||||
|                 + b"v0" | ||||
|                 + six.int2byte(density_byte) | ||||
|                 + self._int_low_high(im.width_bytes, 2) | ||||
|                 + self._int_low_high(im.height, 2) | ||||
|             ) | ||||
|             self._raw(header + im.to_raster_format()) | ||||
|  | ||||
|         if impl == "graphics": | ||||
|             # GS ( L raster format graphics | ||||
|             img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2) | ||||
|             tone = b'0' | ||||
|             colors = b'1' | ||||
|             img_header = self._int_low_high(im.width, 2) + self._int_low_high( | ||||
|                 im.height, 2 | ||||
|             ) | ||||
|             tone = b"0" | ||||
|             colors = b"1" | ||||
|             ym = six.int2byte(1 if high_density_vertical else 2) | ||||
|             xm = six.int2byte(1 if high_density_horizontal else 2) | ||||
|             header = tone + xm + ym + colors + img_header | ||||
|             raster_data = im.to_raster_format() | ||||
|             self._image_send_graphics_data(b'0', b'p', header + raster_data) | ||||
|             self._image_send_graphics_data(b'0', b'2', b'') | ||||
|             self._image_send_graphics_data(b"0", b"p", header + raster_data) | ||||
|             self._image_send_graphics_data(b"0", b"2", b"") | ||||
|  | ||||
|         if impl == "bitImageColumn": | ||||
|             # ESC *, column format bit image | ||||
|             density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0) | ||||
|             header = ESC + b"*" + six.int2byte(density_byte) + self._int_low_high(im.width, 2) | ||||
|             density_byte = (1 if high_density_horizontal else 0) + ( | ||||
|                 32 if high_density_vertical else 0 | ||||
|             ) | ||||
|             header = ( | ||||
|                 ESC | ||||
|                 + b"*" | ||||
|                 + six.int2byte(density_byte) | ||||
|                 + self._int_low_high(im.width, 2) | ||||
|             ) | ||||
|             outp = [ESC + b"3" + six.int2byte(16)]  # Adjust line-feed size | ||||
|             for blob in im.to_column_format(high_density_vertical): | ||||
|                 outp.append(header + blob + b"\n") | ||||
|             outp.append(ESC + b"2")  # Reset line-feed size | ||||
|             self._raw(b''.join(outp)) | ||||
|             self._raw(b"".join(outp)) | ||||
|  | ||||
|     def _image_send_graphics_data(self, m, fn, data): | ||||
|         """ | ||||
| @@ -186,11 +244,19 @@ class Escpos(object): | ||||
|         :param data: Data to send | ||||
|         """ | ||||
|         header = self._int_low_high(len(data) + 2, 2) | ||||
|         self._raw(GS + b'(L' + header + m + fn + data) | ||||
|         self._raw(GS + b"(L" + header + m + fn + data) | ||||
|  | ||||
|     def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, | ||||
|            native=False, center=False, impl="bitImageRaster"): | ||||
|         """ Print QR Code for the provided string | ||||
|     def qr( | ||||
|         self, | ||||
|         content, | ||||
|         ec=QR_ECLEVEL_L, | ||||
|         size=3, | ||||
|         model=QR_MODEL_2, | ||||
|         native=False, | ||||
|         center=False, | ||||
|         impl="bitImageRaster", | ||||
|     ): | ||||
|         """Print QR Code for the provided string | ||||
|  | ||||
|         :param content: The content of the code. Numeric data will be more efficiently compacted. | ||||
|         :param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or | ||||
| @@ -210,50 +276,60 @@ class Escpos(object): | ||||
|         if not 1 <= size <= 16: | ||||
|             raise ValueError("Invalid block size (must be 1-16)") | ||||
|         if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]: | ||||
|             raise ValueError("Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)") | ||||
|             raise ValueError( | ||||
|                 "Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)" | ||||
|             ) | ||||
|         if content == "": | ||||
|             # Handle edge case by printing nothing. | ||||
|             return | ||||
|         if not native: | ||||
|             # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image | ||||
|             if model != QR_MODEL_2: | ||||
|                 raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)") | ||||
|                 raise ValueError( | ||||
|                     "Invalid QR model for qrlib rendering (must be QR_MODEL_2)" | ||||
|                 ) | ||||
|             python_qr_ec = { | ||||
|                 QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, | ||||
|                 QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, | ||||
|                 QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M, | ||||
|                 QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q | ||||
|                 QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q, | ||||
|             } | ||||
|             qr_code = qrcode.QRCode(version=None, box_size=size, border=1, error_correction=python_qr_ec[ec]) | ||||
|             qr_code = qrcode.QRCode( | ||||
|                 version=None, box_size=size, border=1, error_correction=python_qr_ec[ec] | ||||
|             ) | ||||
|             qr_code.add_data(content) | ||||
|             qr_code.make(fit=True) | ||||
|             qr_img = qr_code.make_image() | ||||
|             im = qr_img._img.convert("RGB") | ||||
|  | ||||
|             # Convert the RGB image in printable image | ||||
|             self.text('\n') | ||||
|             self.text("\n") | ||||
|             self.image(im, center=center, impl=impl) | ||||
|             self.text('\n') | ||||
|             self.text('\n') | ||||
|             self.text("\n") | ||||
|             self.text("\n") | ||||
|             return | ||||
|  | ||||
|         if center: | ||||
|             raise NotImplementedError("Centering not implemented for native QR rendering") | ||||
|             raise NotImplementedError( | ||||
|                 "Centering not implemented for native QR rendering" | ||||
|             ) | ||||
|  | ||||
|         # Native 2D code printing | ||||
|         cn = b'1'  # Code type for QR code | ||||
|         cn = b"1"  # Code type for QR code | ||||
|         # Select model: 1, 2 or micro. | ||||
|         self._send_2d_code_data(six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0)) | ||||
|         self._send_2d_code_data( | ||||
|             six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0) | ||||
|         ) | ||||
|         # Set dot size. | ||||
|         self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) | ||||
|         # Set error correction level: L, M, Q, or H | ||||
|         self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) | ||||
|         # Send content & print | ||||
|         self._send_2d_code_data(six.int2byte(80), cn, content.encode('utf-8'), b'0') | ||||
|         self._send_2d_code_data(six.int2byte(81), cn, b'', b'0') | ||||
|         self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0") | ||||
|         self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") | ||||
|  | ||||
|     def _send_2d_code_data(self, fn, cn, data, m=b''): | ||||
|         """ Wrapper for GS ( k, to calculate and send correct data length. | ||||
|     def _send_2d_code_data(self, fn, cn, data, m=b""): | ||||
|         """Wrapper for GS ( k, to calculate and send correct data length. | ||||
|  | ||||
|         :param fn: Function to use. | ||||
|         :param cn: Output code type. Affects available data. | ||||
| @@ -263,28 +339,32 @@ class Escpos(object): | ||||
|         if len(m) > 1 or len(cn) != 1 or len(fn) != 1: | ||||
|             raise ValueError("cn and fn must be one byte each.") | ||||
|         header = self._int_low_high(len(data) + len(m) + 2, 2) | ||||
|         self._raw(GS + b'(k' + header + cn + fn + m + data) | ||||
|         self._raw(GS + b"(k" + header + cn + fn + m + data) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _int_low_high(inp_number, out_bytes): | ||||
|         """ Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. | ||||
|         """Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. | ||||
|  | ||||
|         :param inp_number: Input number | ||||
|         :param out_bytes: The number of bytes to output (1 - 4). | ||||
|         """ | ||||
|         max_input = (256 << (out_bytes * 8) - 1) | ||||
|         max_input = 256 << (out_bytes * 8) - 1 | ||||
|         if not 1 <= out_bytes <= 4: | ||||
|             raise ValueError("Can only output 1-4 bytes") | ||||
|         if not 0 <= inp_number <= max_input: | ||||
|             raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes)) | ||||
|         outp = b'' | ||||
|             raise ValueError( | ||||
|                 "Number too large. Can only output up to {0} in {1} bytes".format( | ||||
|                     max_input, out_bytes | ||||
|                 ) | ||||
|             ) | ||||
|         outp = b"" | ||||
|         for _ in range(0, out_bytes): | ||||
|             outp += six.int2byte(inp_number % 256) | ||||
|             inp_number //= 256 | ||||
|         return outp | ||||
|  | ||||
|     def charcode(self, code="AUTO"): | ||||
|         """ Set Character Code Table | ||||
|         """Set Character Code Table | ||||
|  | ||||
|         Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with | ||||
|         the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the | ||||
| @@ -322,11 +402,23 @@ class Escpos(object): | ||||
|             return False | ||||
|  | ||||
|         bounds, regex = BARCODE_FORMATS[bc] | ||||
|         return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code) | ||||
|         return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match( | ||||
|             regex, code | ||||
|         ) | ||||
|  | ||||
|     def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", | ||||
|                 align_ct=True, function_type=None, check=True): | ||||
|         """ Print Barcode | ||||
|     def barcode( | ||||
|         self, | ||||
|         code, | ||||
|         bc, | ||||
|         height=64, | ||||
|         width=3, | ||||
|         pos="BELOW", | ||||
|         font="A", | ||||
|         align_ct=True, | ||||
|         function_type=None, | ||||
|         check=True, | ||||
|     ): | ||||
|         """Print Barcode | ||||
|  | ||||
|         This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to | ||||
|         be supported by the unit. By default, this method will check whether your barcode text is correct, that is | ||||
| @@ -402,7 +494,7 @@ class Escpos(object): | ||||
|             *default*: A | ||||
|  | ||||
|         :param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc | ||||
|             requirements as defigned in the esc/pos documentation. See :py:meth:`.check_barcode()` | ||||
|             requirements as definged in the ESC/POS documentation. See :py:meth:`.check_barcode()` | ||||
|             for more information. *default*: True. | ||||
|  | ||||
|         :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, | ||||
| @@ -411,38 +503,46 @@ class Escpos(object): | ||||
|         """ | ||||
|         if function_type is None: | ||||
|             # Choose the function type automatically. | ||||
|             if bc in BARCODE_TYPES['A']: | ||||
|                 function_type = 'A' | ||||
|             if bc in BARCODE_TYPES["A"]: | ||||
|                 function_type = "A" | ||||
|             else: | ||||
|                 if bc in BARCODE_TYPES['B']: | ||||
|                 if bc in BARCODE_TYPES["B"]: | ||||
|                     if not self.profile.supports(BARCODE_B): | ||||
|                         raise BarcodeTypeError(( | ||||
|                             "Barcode type '{bc} not supported for " | ||||
|                             "the current printer profile").format(bc=bc)) | ||||
|                     function_type = 'B' | ||||
|                         raise BarcodeTypeError( | ||||
|                             ( | ||||
|                                 "Barcode type '{bc} not supported for " | ||||
|                                 "the current printer profile" | ||||
|                             ).format(bc=bc) | ||||
|                         ) | ||||
|                     function_type = "B" | ||||
|                 else: | ||||
|                     raise BarcodeTypeError(( | ||||
|                         "Barcode type '{bc} is not valid").format(bc=bc)) | ||||
|                     raise BarcodeTypeError( | ||||
|                         ("Barcode type '{bc} is not valid").format(bc=bc) | ||||
|                     ) | ||||
|  | ||||
|         bc_types = BARCODE_TYPES[function_type.upper()] | ||||
|         if bc.upper() not in bc_types.keys(): | ||||
|             raise BarcodeTypeError(( | ||||
|                 "Barcode '{bc}' not valid for barcode function type " | ||||
|                 "{function_type}").format( | ||||
|             raise BarcodeTypeError( | ||||
|                 ( | ||||
|                     "Barcode '{bc}' not valid for barcode function type " | ||||
|                     "{function_type}" | ||||
|                 ).format( | ||||
|                     bc=bc, | ||||
|                     function_type=function_type, | ||||
|                 )) | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         if check and not self.check_barcode(bc, code): | ||||
|             raise BarcodeCodeError(( | ||||
|                 "Barcode '{code}' not in a valid format for type '{bc}'").format( | ||||
|                 code=code, | ||||
|                 bc=bc, | ||||
|             )) | ||||
|             raise BarcodeCodeError( | ||||
|                 ("Barcode '{code}' not in a valid format for type '{bc}'").format( | ||||
|                     code=code, | ||||
|                     bc=bc, | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         # Align Bar Code() | ||||
|         if align_ct: | ||||
|             self._raw(TXT_STYLE['align']['center']) | ||||
|             self._raw(TXT_STYLE["align"]["center"]) | ||||
|         # Height | ||||
|         if 1 <= height <= 255: | ||||
|             self._raw(BARCODE_HEIGHT + six.int2byte(height)) | ||||
| @@ -482,34 +582,47 @@ class Escpos(object): | ||||
|         if function_type.upper() == "A": | ||||
|             self._raw(NUL) | ||||
|  | ||||
|     def soft_barcode(self, barcode_type, data, impl='bitImageColumn', | ||||
|                      module_height=5, module_width=0.2, text_distance=1): | ||||
|     def soft_barcode( | ||||
|         self, | ||||
|         barcode_type, | ||||
|         data, | ||||
|         impl="bitImageColumn", | ||||
|         module_height=5, | ||||
|         module_width=0.2, | ||||
|         text_distance=1, | ||||
|         center=True, | ||||
|     ): | ||||
|  | ||||
|         image_writer = ImageWriter() | ||||
|  | ||||
|         # Check if barcode type exists | ||||
|         if barcode_type not in barcode.PROVIDED_BARCODES: | ||||
|             raise BarcodeTypeError( | ||||
|                 'Barcode type {} not supported by software barcode renderer' | ||||
|                 .format(barcode_type)) | ||||
|                 "Barcode type {} not supported by software barcode renderer".format( | ||||
|                     barcode_type | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         # Render the barcode to a fake file | ||||
|         barcode_class = barcode.get_barcode_class(barcode_type) | ||||
|         my_code = barcode_class(data, writer=image_writer) | ||||
|  | ||||
|         with open(os.devnull, "wb") as nullfile: | ||||
|             my_code.write(nullfile, { | ||||
|                 'module_height': module_height, | ||||
|                 'module_width': module_width, | ||||
|                 'text_distance': text_distance | ||||
|             }) | ||||
|             my_code.write( | ||||
|                 nullfile, | ||||
|                 { | ||||
|                     "module_height": module_height, | ||||
|                     "module_width": module_width, | ||||
|                     "text_distance": text_distance, | ||||
|                 }, | ||||
|             ) | ||||
|  | ||||
|         # Retrieve the Pillow image and print it | ||||
|         image = my_code.writer._image | ||||
|         self.image(image, impl=impl) | ||||
|         self.image(image, impl=impl, center=center) | ||||
|  | ||||
|     def text(self, txt): | ||||
|         """ Print alpha-numeric text | ||||
|         """Print alpha-numeric text | ||||
|  | ||||
|         The text has to be encoded in the currently selected codepage. | ||||
|         The input text has to be encoded in unicode. | ||||
| @@ -520,7 +633,7 @@ class Escpos(object): | ||||
|         txt = six.text_type(txt) | ||||
|         self.magic.write(txt) | ||||
|  | ||||
|     def textln(self, txt=''): | ||||
|     def textln(self, txt=""): | ||||
|         """Print alpha-numeric text with a newline | ||||
|  | ||||
|         The text has to be encoded in the currently selected codepage. | ||||
| @@ -529,7 +642,7 @@ class Escpos(object): | ||||
|         :param txt: text to be printed with a newline | ||||
|         :raises: :py:exc:`~escpos.exceptions.TextError` | ||||
|         """ | ||||
|         self.text('{}\n'.format(txt)) | ||||
|         self.text("{}\n".format(txt)) | ||||
|  | ||||
|     def ln(self, count=1): | ||||
|         """Print a newline or more | ||||
| @@ -538,12 +651,12 @@ class Escpos(object): | ||||
|         :raises: :py:exc:`ValueError` if count < 0 | ||||
|         """ | ||||
|         if count < 0: | ||||
|             raise ValueError('Count cannot be lesser than 0') | ||||
|             raise ValueError("Count cannot be lesser than 0") | ||||
|         if count > 0: | ||||
|             self.text('\n' * count) | ||||
|             self.text("\n" * count) | ||||
|  | ||||
|     def block_text(self, txt, font=None, columns=None): | ||||
|         """ Text is printed wrapped to specified columns | ||||
|     def block_text(self, txt, font="0", columns=None): | ||||
|         """Text is printed wrapped to specified columns | ||||
|  | ||||
|         Text has to be encoded in unicode. | ||||
|  | ||||
| @@ -555,10 +668,23 @@ class Escpos(object): | ||||
|         col_count = self.profile.get_columns(font) if columns is None else columns | ||||
|         self.text(textwrap.fill(txt, col_count)) | ||||
|  | ||||
|     def set(self, align='left', font='a', bold=False, underline=0, width=1, | ||||
|             height=1, density=9, invert=False, smooth=False, flip=False, | ||||
|             double_width=False, double_height=False, custom_size=False): | ||||
|         """ Set text properties by sending them to the printer | ||||
|     def set( | ||||
|         self, | ||||
|         align="left", | ||||
|         font="a", | ||||
|         bold=False, | ||||
|         underline=0, | ||||
|         width=1, | ||||
|         height=1, | ||||
|         density=9, | ||||
|         invert=False, | ||||
|         smooth=False, | ||||
|         flip=False, | ||||
|         double_width=False, | ||||
|         double_height=False, | ||||
|         custom_size=False, | ||||
|     ): | ||||
|         """Set text properties by sending them to the printer | ||||
|  | ||||
|         :param align: horizontal position for text, possible values are: | ||||
|  | ||||
| @@ -599,37 +725,41 @@ class Escpos(object): | ||||
|         """ | ||||
|  | ||||
|         if custom_size: | ||||
|             if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\ | ||||
|               isinstance(height, int): | ||||
|                 size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height] | ||||
|             if ( | ||||
|                 1 <= width <= 8 | ||||
|                 and 1 <= height <= 8 | ||||
|                 and isinstance(width, int) | ||||
|                 and isinstance(height, int) | ||||
|             ): | ||||
|                 size_byte = TXT_STYLE["width"][width] + TXT_STYLE["height"][height] | ||||
|                 self._raw(TXT_SIZE + six.int2byte(size_byte)) | ||||
|             else: | ||||
|                 raise SetVariableError() | ||||
|         else: | ||||
|             self._raw(TXT_NORMAL) | ||||
|             if double_width and double_height: | ||||
|                 self._raw(TXT_STYLE['size']['2x']) | ||||
|                 self._raw(TXT_STYLE["size"]["2x"]) | ||||
|             elif double_width: | ||||
|                 self._raw(TXT_STYLE['size']['2w']) | ||||
|                 self._raw(TXT_STYLE["size"]["2w"]) | ||||
|             elif double_height: | ||||
|                 self._raw(TXT_STYLE['size']['2h']) | ||||
|                 self._raw(TXT_STYLE["size"]["2h"]) | ||||
|             else: | ||||
|                 self._raw(TXT_STYLE['size']['normal']) | ||||
|                 self._raw(TXT_STYLE["size"]["normal"]) | ||||
|  | ||||
|         self._raw(TXT_STYLE['flip'][flip]) | ||||
|         self._raw(TXT_STYLE['smooth'][smooth]) | ||||
|         self._raw(TXT_STYLE['bold'][bold]) | ||||
|         self._raw(TXT_STYLE['underline'][underline]) | ||||
|         self._raw(TXT_STYLE["flip"][flip]) | ||||
|         self._raw(TXT_STYLE["smooth"][smooth]) | ||||
|         self._raw(TXT_STYLE["bold"][bold]) | ||||
|         self._raw(TXT_STYLE["underline"][underline]) | ||||
|         self._raw(SET_FONT(six.int2byte(self.profile.get_font(font)))) | ||||
|         self._raw(TXT_STYLE['align'][align]) | ||||
|         self._raw(TXT_STYLE["align"][align]) | ||||
|  | ||||
|         if density != 9: | ||||
|             self._raw(TXT_STYLE['density'][density]) | ||||
|             self._raw(TXT_STYLE["density"][density]) | ||||
|  | ||||
|         self._raw(TXT_STYLE['invert'][invert]) | ||||
|         self._raw(TXT_STYLE["invert"][invert]) | ||||
|  | ||||
|     def line_spacing(self, spacing=None, divisor=180): | ||||
|         """ Set line character spacing. | ||||
|         """Set line character spacing. | ||||
|  | ||||
|         If no spacing is given, we reset it to the default. | ||||
|  | ||||
| @@ -649,51 +779,52 @@ class Escpos(object): | ||||
|  | ||||
|         if divisor not in LINESPACING_FUNCS: | ||||
|             raise ValueError("divisor must be either 360, 180 or 60") | ||||
|         if (divisor in [360, 180] | ||||
|                 and (not(0 <= spacing <= 255))): | ||||
|             raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180") | ||||
|         if divisor == 60 and (not(0 <= spacing <= 85)): | ||||
|             raise ValueError("spacing must be a int between 0 and 85 when divisor is 60") | ||||
|         if divisor in [360, 180] and (not (0 <= spacing <= 255)): | ||||
|             raise ValueError( | ||||
|                 "spacing must be a int between 0 and 255 when divisor is 360 or 180" | ||||
|             ) | ||||
|         if divisor == 60 and (not (0 <= spacing <= 85)): | ||||
|             raise ValueError( | ||||
|                 "spacing must be a int between 0 and 85 when divisor is 60" | ||||
|             ) | ||||
|  | ||||
|         self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) | ||||
|  | ||||
|     def cut(self, mode='FULL', feed=True): | ||||
|         """ Cut paper. | ||||
|     def cut(self, mode="FULL", feed=True): | ||||
|         """Cut paper. | ||||
|  | ||||
|         Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will | ||||
|         be attempted. Note however, that not all models can do a partial cut. See the documentation of | ||||
|         your printer for details. | ||||
|  | ||||
|         .. todo:: Check this function on TM-T88II. | ||||
|  | ||||
|         :param mode: set to 'PART' for a partial cut. default: 'FULL' | ||||
|         :param feed: print and feed before cutting. default: true | ||||
|         :raises ValueError: if mode not in ('FULL', 'PART') | ||||
|         """ | ||||
|  | ||||
|         if not feed: | ||||
|             self._raw(GS + b'V' + six.int2byte(66) + b'\x00') | ||||
|             self._raw(GS + b"V" + six.int2byte(66) + b"\x00") | ||||
|             return | ||||
|  | ||||
|         self.print_and_feed(6) | ||||
|  | ||||
|         mode = mode.upper() | ||||
|         if mode not in ('FULL', 'PART'): | ||||
|         if mode not in ("FULL", "PART"): | ||||
|             raise ValueError("Mode must be one of ('FULL', 'PART')") | ||||
|  | ||||
|         if mode == "PART": | ||||
|             if self.profile.supports('paperPartCut'): | ||||
|             if self.profile.supports("paperPartCut"): | ||||
|                 self._raw(PAPER_PART_CUT) | ||||
|             elif self.profile.supports('paperFullCut'): | ||||
|             elif self.profile.supports("paperFullCut"): | ||||
|                 self._raw(PAPER_FULL_CUT) | ||||
|         elif mode == "FULL": | ||||
|             if self.profile.supports('paperFullCut'): | ||||
|             if self.profile.supports("paperFullCut"): | ||||
|                 self._raw(PAPER_FULL_CUT) | ||||
|             elif self.profile.supports('paperPartCut'): | ||||
|             elif self.profile.supports("paperPartCut"): | ||||
|                 self._raw(PAPER_PART_CUT) | ||||
|  | ||||
|     def cashdraw(self, pin): | ||||
|         """ Send pulse to kick the cash drawer | ||||
|         """Send pulse to kick the cash drawer | ||||
|  | ||||
|         Kick cash drawer on pin 2 or pin 5 according to default parameter. | ||||
|         For non default parameter send a decimal sequence i.e. [27,112,48] or [27,112,0,25,255] | ||||
| @@ -712,7 +843,7 @@ class Escpos(object): | ||||
|                 raise CashDrawerError(err) | ||||
|  | ||||
|     def linedisplay_select(self, select_display=False): | ||||
|         """ Selects the line display or the printer | ||||
|         """Selects the line display or the printer | ||||
|  | ||||
|         This method is used for line displays that are daisy-chained between your computer and printer. | ||||
|         If you set `select_display` to true, only the display is selected and if you set it to false, | ||||
| @@ -727,7 +858,7 @@ class Escpos(object): | ||||
|             self._raw(LINE_DISPLAY_CLOSE) | ||||
|  | ||||
|     def linedisplay_clear(self): | ||||
|         """ Clears the line display and resets the cursor | ||||
|         """Clears the line display and resets the cursor | ||||
|  | ||||
|         This method is used for line displays that are daisy-chained between your computer and printer. | ||||
|         """ | ||||
| @@ -748,7 +879,7 @@ class Escpos(object): | ||||
|         self.linedisplay_select(select_display=False) | ||||
|  | ||||
|     def hw(self, hw): | ||||
|         """ Hardware operations | ||||
|         """Hardware operations | ||||
|  | ||||
|         :param hw: hardware action, may be: | ||||
|  | ||||
| @@ -766,12 +897,12 @@ class Escpos(object): | ||||
|             pass | ||||
|  | ||||
|     def print_and_feed(self, n=1): | ||||
|         """ Print data in print buffer and feed *n* lines | ||||
|         """Print data in print buffer and feed *n* lines | ||||
|  | ||||
|             if n not in range (0, 255) then ValueError will be raised | ||||
|         if n not in range (0, 255) then ValueError will be raised | ||||
|  | ||||
|             :param n: number of n to feed. 0 <= n <= 255. default: 1 | ||||
|             :raises ValueError: if not 0 <= n <= 255 | ||||
|         :param n: number of n to feed. 0 <= n <= 255. default: 1 | ||||
|         :raises ValueError: if not 0 <= n <= 255 | ||||
|         """ | ||||
|         if 0 <= n <= 255: | ||||
|             # ESC d n | ||||
| @@ -780,7 +911,7 @@ class Escpos(object): | ||||
|             raise ValueError("n must be betwen 0 and 255") | ||||
|  | ||||
|     def control(self, ctl, count=5, tab_size=8): | ||||
|         """ Feed control sequences | ||||
|         """Feed control sequences | ||||
|  | ||||
|         :param ctl: string for the following control sequences: | ||||
|  | ||||
| @@ -802,9 +933,9 @@ class Escpos(object): | ||||
|         elif ctl.upper() == "CR": | ||||
|             self._raw(CTL_CR) | ||||
|         elif ctl.upper() == "HT": | ||||
|             if not (0 <= count <= 32 and | ||||
|                     1 <= tab_size <= 255 and | ||||
|                     count * tab_size < 256): | ||||
|             if not ( | ||||
|                 0 <= count <= 32 and 1 <= tab_size <= 255 and count * tab_size < 256 | ||||
|             ): | ||||
|                 raise TabPosError() | ||||
|             else: | ||||
|                 # Set tab positions | ||||
| @@ -816,7 +947,7 @@ class Escpos(object): | ||||
|             self._raw(CTL_VT) | ||||
|  | ||||
|     def panel_buttons(self, enable=True): | ||||
|         """ Controls the panel buttons on the printer (e.g. FEED) | ||||
|         """Controls the panel buttons on the printer (e.g. FEED) | ||||
|  | ||||
|         When enable is set to False the panel buttons on the printer will be disabled. Calling the method with | ||||
|         enable=True or without argument will enable the panel buttons. | ||||
| @@ -877,11 +1008,11 @@ class Escpos(object): | ||||
|         status = self.query_status(RT_STATUS_PAPER) | ||||
|         if len(status) == 0: | ||||
|             return 2 | ||||
|         if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER): | ||||
|         if status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER: | ||||
|             return 0 | ||||
|         if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER): | ||||
|         if status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER: | ||||
|             return 1 | ||||
|         if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER): | ||||
|         if status[0] & RT_MASK_PAPER == RT_MASK_PAPER: | ||||
|             return 2 | ||||
|  | ||||
|  | ||||
| @@ -918,7 +1049,7 @@ class EscposIO(object): | ||||
|         self.autoclose = autoclose | ||||
|  | ||||
|     def set(self, **kwargs): | ||||
|         """ Set the printer-parameters | ||||
|         """Set the printer-parameters | ||||
|  | ||||
|         Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`. | ||||
|         For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods | ||||
| @@ -934,11 +1065,13 @@ class EscposIO(object): | ||||
|         params.update(kwargs) | ||||
|  | ||||
|         if isinstance(text, six.text_type): | ||||
|             lines = text.split('\n') | ||||
|             lines = text.split("\n") | ||||
|         elif isinstance(text, list) or isinstance(text, tuple): | ||||
|             lines = text | ||||
|         else: | ||||
|             lines = ["{0}".format(text), ] | ||||
|             lines = [ | ||||
|                 "{0}".format(text), | ||||
|             ] | ||||
|  | ||||
|         # TODO check unicode handling | ||||
|         # TODO flush? or on print? (this should prob rather be handled by the _raw-method) | ||||
| @@ -950,8 +1083,7 @@ class EscposIO(object): | ||||
|                 self.printer.text("{0}\n".format(line)) | ||||
|  | ||||
|     def close(self): | ||||
|         """ called upon closing the `with`-statement | ||||
|         """ | ||||
|         """called upon closing the `with`-statement""" | ||||
|         self.printer.close() | ||||
|  | ||||
|     def __enter__(self, **kwargs): | ||||
|   | ||||
| @@ -25,14 +25,10 @@ Result/Exit codes: | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
|  | ||||
| class Error(Exception): | ||||
|     """ Base class for ESC/POS errors """ | ||||
|     """Base class for ESC/POS errors""" | ||||
|  | ||||
|     def __init__(self, msg, status=None): | ||||
|         Exception.__init__(self) | ||||
|         self.msg = msg | ||||
| @@ -45,12 +41,13 @@ class Error(Exception): | ||||
|  | ||||
|  | ||||
| class BarcodeTypeError(Error): | ||||
|     """ No Barcode type defined. | ||||
|     """No Barcode type defined. | ||||
|  | ||||
|     This exception indicates that no known barcode-type has been entered. The barcode-type has to be | ||||
|     one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`. | ||||
|     The returned error code is `10`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -61,12 +58,13 @@ class BarcodeTypeError(Error): | ||||
|  | ||||
|  | ||||
| class BarcodeSizeError(Error): | ||||
|     """ Barcode size is out of range. | ||||
|     """Barcode size is out of range. | ||||
|  | ||||
|     This exception indicates that the values for the barcode size are out of range. | ||||
|     The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`. | ||||
|     The resulting returncode is `20`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -77,12 +75,13 @@ class BarcodeSizeError(Error): | ||||
|  | ||||
|  | ||||
| class BarcodeCodeError(Error): | ||||
|     """ No Barcode code was supplied, or it is incorrect. | ||||
|     """No Barcode code was supplied, or it is incorrect. | ||||
|  | ||||
|     No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter | ||||
|     was True and the check failed. | ||||
|     The returncode for this exception is `30`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -93,24 +92,28 @@ class BarcodeCodeError(Error): | ||||
|  | ||||
|  | ||||
| class ImageSizeError(Error): | ||||
|     """ Image height is longer than 255px and can't be printed. | ||||
|     """Image height is longer than 255px and can't be printed. | ||||
|  | ||||
|     The returncode for this exception is `40`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
|         self.resultcode = 40 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) | ||||
|         return "Image height is longer than 255px and can't be printed ({msg})".format( | ||||
|             msg=self.msg | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class ImageWidthError(Error): | ||||
|     """ Image width is too large. | ||||
|     """Image width is too large. | ||||
|  | ||||
|     The return code for this exception is `41`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -121,26 +124,30 @@ class ImageWidthError(Error): | ||||
|  | ||||
|  | ||||
| class TextError(Error): | ||||
|     """ Text string must be supplied to the `text()` method. | ||||
|     """Text string must be supplied to the `text()` method. | ||||
|  | ||||
|     This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`. | ||||
|     The returncode for this exception is `50`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
|         self.resultcode = 50 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg) | ||||
|         return "Text string must be supplied to the text() method ({msg})".format( | ||||
|             msg=self.msg | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class CashDrawerError(Error): | ||||
|     """ Valid pin must be set in order to send pulse. | ||||
|     """Valid pin must be set in order to send pulse. | ||||
|  | ||||
|     A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`. | ||||
|     The returncode for this exception is `60`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -151,27 +158,31 @@ class CashDrawerError(Error): | ||||
|  | ||||
|  | ||||
| class TabPosError(Error): | ||||
|     """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. | ||||
|     """Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. | ||||
|     Both values multiplied must not exceed 255, since it is the maximum tab value. | ||||
|  | ||||
|     This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. | ||||
|     The returncode for this exception is `70`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
|         self.resultcode = 70 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg) | ||||
|         return "Valid tab positions must be in the range 0 to 16 ({msg})".format( | ||||
|             msg=self.msg | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class CharCodeError(Error): | ||||
|     """ Valid char code must be set. | ||||
|     """Valid char code must be set. | ||||
|  | ||||
|     The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown. | ||||
|     Ths returncode for this exception is `80`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -182,11 +193,12 @@ class CharCodeError(Error): | ||||
|  | ||||
|  | ||||
| class USBNotFoundError(Error): | ||||
|     """ Device wasn't found (probably not plugged in) | ||||
|     """Device wasn't found (probably not plugged in) | ||||
|  | ||||
|     The USB device seems to be not plugged in. | ||||
|     Ths returncode for this exception is `90`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -197,11 +209,12 @@ class USBNotFoundError(Error): | ||||
|  | ||||
|  | ||||
| class SetVariableError(Error): | ||||
|     """ A set method variable was out of range | ||||
|     """A set method variable was out of range | ||||
|  | ||||
|     Check set variables against minimum and maximum values | ||||
|     Ths returncode for this exception is `100`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -213,12 +226,14 @@ class SetVariableError(Error): | ||||
|  | ||||
| # Configuration errors | ||||
|  | ||||
|  | ||||
| class ConfigNotFoundError(Error): | ||||
|     """ The configuration file was not found | ||||
|     """The configuration file was not found | ||||
|  | ||||
|     The default or passed configuration file could not be read | ||||
|     Ths returncode for this exception is `200`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -229,11 +244,12 @@ class ConfigNotFoundError(Error): | ||||
|  | ||||
|  | ||||
| class ConfigSyntaxError(Error): | ||||
|     """ The configuration file is invalid | ||||
|     """The configuration file is invalid | ||||
|  | ||||
|     The syntax is incorrect | ||||
|     Ths returncode for this exception is `210`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
| @@ -244,11 +260,12 @@ class ConfigSyntaxError(Error): | ||||
|  | ||||
|  | ||||
| class ConfigSectionMissingError(Error): | ||||
|     """ The configuration file is missing a section | ||||
|     """The configuration file is missing a section | ||||
|  | ||||
|     The part of the config asked for doesn't exist in the loaded configuration | ||||
|     Ths returncode for this exception is `220`. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
|   | ||||
| @@ -8,10 +8,6 @@ This module contains the image format handler :py:class:`EscposImage`. | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import math | ||||
| from PIL import Image, ImageOps | ||||
| @@ -41,7 +37,7 @@ class EscposImage(object): | ||||
|  | ||||
|         # Convert to white RGB background, paste over white background | ||||
|         # to strip alpha. | ||||
|         img_original = img_original.convert('RGBA') | ||||
|         img_original = img_original.convert("RGBA") | ||||
|         im = Image.new("RGB", img_original.size, (255, 255, 255)) | ||||
|         im.paste(img_original, mask=img_original.split()[3]) | ||||
|         # Convert down to greyscale | ||||
| @@ -89,7 +85,7 @@ class EscposImage(object): | ||||
|             box = (left, top, left + line_height, top + height_pixels) | ||||
|             im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box) | ||||
|             im_bytes = im_slice.tobytes() | ||||
|             yield(im_bytes) | ||||
|             yield (im_bytes) | ||||
|             left += line_height | ||||
|  | ||||
|     def to_raster_format(self): | ||||
| @@ -105,7 +101,7 @@ class EscposImage(object): | ||||
|         :param fragment_height: height of fragment | ||||
|         :return: list of PIL objects | ||||
|         """ | ||||
|         passes = int(math.ceil(self.height/fragment_height)) | ||||
|         passes = int(math.ceil(self.height / fragment_height)) | ||||
|         fragments = [] | ||||
|         for n in range(0, passes): | ||||
|             left = 0 | ||||
|   | ||||
| @@ -4,11 +4,6 @@ | ||||
| I doubt that this currently works correctly. | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
|  | ||||
| try: | ||||
|     import jaconv | ||||
| @@ -39,69 +34,68 @@ def encode_katakana(text): | ||||
| TXT_ENC_KATAKANA_MAP = { | ||||
|     # Maps UTF-8 Katakana symbols to KATAKANA Page Codes | ||||
|     # TODO: has this really to be hardcoded? | ||||
|  | ||||
|     # Half-Width Katakanas | ||||
|     '。': b'\xa1', | ||||
|     '「': b'\xa2', | ||||
|     '」': b'\xa3', | ||||
|     '、': b'\xa4', | ||||
|     '・': b'\xa5', | ||||
|     'ヲ': b'\xa6', | ||||
|     'ァ': b'\xa7', | ||||
|     'ィ': b'\xa8', | ||||
|     'ゥ': b'\xa9', | ||||
|     'ェ': b'\xaa', | ||||
|     'ォ': b'\xab', | ||||
|     'ャ': b'\xac', | ||||
|     'ュ': b'\xad', | ||||
|     'ョ': b'\xae', | ||||
|     'ッ': b'\xaf', | ||||
|     'ー': b'\xb0', | ||||
|     'ア': b'\xb1', | ||||
|     'イ': b'\xb2', | ||||
|     'ウ': b'\xb3', | ||||
|     'エ': b'\xb4', | ||||
|     'オ': b'\xb5', | ||||
|     'カ': b'\xb6', | ||||
|     'キ': b'\xb7', | ||||
|     'ク': b'\xb8', | ||||
|     'ケ': b'\xb9', | ||||
|     'コ': b'\xba', | ||||
|     'サ': b'\xbb', | ||||
|     'シ': b'\xbc', | ||||
|     'ス': b'\xbd', | ||||
|     'セ': b'\xbe', | ||||
|     'ソ': b'\xbf', | ||||
|     'タ': b'\xc0', | ||||
|     'チ': b'\xc1', | ||||
|     'ツ': b'\xc2', | ||||
|     'テ': b'\xc3', | ||||
|     'ト': b'\xc4', | ||||
|     'ナ': b'\xc5', | ||||
|     'ニ': b'\xc6', | ||||
|     'ヌ': b'\xc7', | ||||
|     'ネ': b'\xc8', | ||||
|     'ノ': b'\xc9', | ||||
|     'ハ': b'\xca', | ||||
|     'ヒ': b'\xcb', | ||||
|     'フ': b'\xcc', | ||||
|     'ヘ': b'\xcd', | ||||
|     'ホ': b'\xce', | ||||
|     'マ': b'\xcf', | ||||
|     'ミ': b'\xd0', | ||||
|     'ム': b'\xd1', | ||||
|     'メ': b'\xd2', | ||||
|     'モ': b'\xd3', | ||||
|     'ヤ': b'\xd4', | ||||
|     'ユ': b'\xd5', | ||||
|     'ヨ': b'\xd6', | ||||
|     'ラ': b'\xd7', | ||||
|     'リ': b'\xd8', | ||||
|     'ル': b'\xd9', | ||||
|     'レ': b'\xda', | ||||
|     'ロ': b'\xdb', | ||||
|     'ワ': b'\xdc', | ||||
|     'ン': b'\xdd', | ||||
|     '゙': b'\xde', | ||||
|     '゚': b'\xdf', | ||||
|     "。": b"\xa1", | ||||
|     "「": b"\xa2", | ||||
|     "」": b"\xa3", | ||||
|     "、": b"\xa4", | ||||
|     "・": b"\xa5", | ||||
|     "ヲ": b"\xa6", | ||||
|     "ァ": b"\xa7", | ||||
|     "ィ": b"\xa8", | ||||
|     "ゥ": b"\xa9", | ||||
|     "ェ": b"\xaa", | ||||
|     "ォ": b"\xab", | ||||
|     "ャ": b"\xac", | ||||
|     "ュ": b"\xad", | ||||
|     "ョ": b"\xae", | ||||
|     "ッ": b"\xaf", | ||||
|     "ー": b"\xb0", | ||||
|     "ア": b"\xb1", | ||||
|     "イ": b"\xb2", | ||||
|     "ウ": b"\xb3", | ||||
|     "エ": b"\xb4", | ||||
|     "オ": b"\xb5", | ||||
|     "カ": b"\xb6", | ||||
|     "キ": b"\xb7", | ||||
|     "ク": b"\xb8", | ||||
|     "ケ": b"\xb9", | ||||
|     "コ": b"\xba", | ||||
|     "サ": b"\xbb", | ||||
|     "シ": b"\xbc", | ||||
|     "ス": b"\xbd", | ||||
|     "セ": b"\xbe", | ||||
|     "ソ": b"\xbf", | ||||
|     "タ": b"\xc0", | ||||
|     "チ": b"\xc1", | ||||
|     "ツ": b"\xc2", | ||||
|     "テ": b"\xc3", | ||||
|     "ト": b"\xc4", | ||||
|     "ナ": b"\xc5", | ||||
|     "ニ": b"\xc6", | ||||
|     "ヌ": b"\xc7", | ||||
|     "ネ": b"\xc8", | ||||
|     "ノ": b"\xc9", | ||||
|     "ハ": b"\xca", | ||||
|     "ヒ": b"\xcb", | ||||
|     "フ": b"\xcc", | ||||
|     "ヘ": b"\xcd", | ||||
|     "ホ": b"\xce", | ||||
|     "マ": b"\xcf", | ||||
|     "ミ": b"\xd0", | ||||
|     "ム": b"\xd1", | ||||
|     "メ": b"\xd2", | ||||
|     "モ": b"\xd3", | ||||
|     "ヤ": b"\xd4", | ||||
|     "ユ": b"\xd5", | ||||
|     "ヨ": b"\xd6", | ||||
|     "ラ": b"\xd7", | ||||
|     "リ": b"\xd8", | ||||
|     "ル": b"\xd9", | ||||
|     "レ": b"\xda", | ||||
|     "ロ": b"\xdb", | ||||
|     "ワ": b"\xdc", | ||||
|     "ン": b"\xdd", | ||||
|     "゙": b"\xde", | ||||
|     "゚": b"\xdf", | ||||
| } | ||||
|   | ||||
| @@ -12,10 +12,6 @@ The code is based on the encoding-code in py-xml-escpos by @fvdsn. | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from builtins import bytes | ||||
| from .constants import CODEPAGE_CHANGE | ||||
| @@ -59,10 +55,12 @@ class Encoder(object): | ||||
|         """ | ||||
|         encoding = CodePages.get_encoding_name(encoding) | ||||
|         if encoding not in self.codepages: | ||||
|             raise ValueError(( | ||||
|                 'Encoding "{}" cannot be used for the current profile. ' | ||||
|                 'Valid encodings are: {}' | ||||
|                 ).format(encoding, ','.join(self.codepages.keys()))) | ||||
|             raise ValueError( | ||||
|                 ( | ||||
|                     'Encoding "{}" cannot be used for the current profile. ' | ||||
|                     "Valid encodings are: {}" | ||||
|                 ).format(encoding, ",".join(self.codepages.keys())) | ||||
|             ) | ||||
|         return encoding | ||||
|  | ||||
|     @staticmethod | ||||
| @@ -74,16 +72,18 @@ class Encoder(object): | ||||
|         :param encoding: The name of the encoding. This must appear in the CodePage list | ||||
|         """ | ||||
|         codepage = CodePages.get_encoding(encoding) | ||||
|         if 'data' in codepage: | ||||
|             encodable_chars = list("".join(codepage['data'])) | ||||
|             assert(len(encodable_chars) == 128) | ||||
|         if "data" in codepage: | ||||
|             encodable_chars = list("".join(codepage["data"])) | ||||
|             assert len(encodable_chars) == 128 | ||||
|             return encodable_chars | ||||
|         elif 'python_encode' in codepage: | ||||
|         elif "python_encode" in codepage: | ||||
|             encodable_chars = [u" "] * 128 | ||||
|             for i in range(0, 128): | ||||
|                 codepoint = i + 128 | ||||
|                 try: | ||||
|                     encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode']) | ||||
|                     encodable_chars[i] = bytes([codepoint]).decode( | ||||
|                         codepage["python_encode"] | ||||
|                     ) | ||||
|                 except UnicodeDecodeError: | ||||
|                     # Non-encodable character, just skip it | ||||
|                     pass | ||||
| @@ -91,7 +91,7 @@ class Encoder(object): | ||||
|         raise LookupError("Can't find a known encoding for {}".format(encoding)) | ||||
|  | ||||
|     def _get_codepage_char_map(self, encoding): | ||||
|         """ Get codepage character map | ||||
|         """Get codepage character map | ||||
|  | ||||
|         Process an encoding and return a map of UTF-characters to code points | ||||
|         in this encoding. | ||||
| @@ -104,7 +104,9 @@ class Encoder(object): | ||||
|         if encoding in self.available_characters: | ||||
|             return self.available_characters[encoding] | ||||
|         codepage_char_list = self._get_codepage_char_list(encoding) | ||||
|         codepage_char_map = dict((utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)) | ||||
|         codepage_char_map = dict( | ||||
|             (utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list) | ||||
|         ) | ||||
|         self.available_characters[encoding] = codepage_char_map | ||||
|         return codepage_char_map | ||||
|  | ||||
| @@ -127,7 +129,7 @@ class Encoder(object): | ||||
|  | ||||
|     @staticmethod | ||||
|     def _encode_char(char, charmap, defaultchar): | ||||
|         """ Encode a single character with the given encoding map | ||||
|         """Encode a single character with the given encoding map | ||||
|  | ||||
|         :param char: char to encode | ||||
|         :param charmap: dictionary for mapping characters in this code page | ||||
| @@ -138,23 +140,22 @@ class Encoder(object): | ||||
|             return charmap[char] | ||||
|         return ord(defaultchar) | ||||
|  | ||||
|     def encode(self, text, encoding, defaultchar='?'): | ||||
|         """ Encode text under the given encoding | ||||
|     def encode(self, text, encoding, defaultchar="?"): | ||||
|         """Encode text under the given encoding | ||||
|  | ||||
|         :param text: Text to encode | ||||
|         :param encoding: Encoding name to use (must be defined in capabilities) | ||||
|         :param defaultchar: Fallback for non-encodable characters | ||||
|         """ | ||||
|         codepage_char_map = self._get_codepage_char_map(encoding) | ||||
|         output_bytes = bytes([self._encode_char(char, codepage_char_map, defaultchar) for char in text]) | ||||
|         output_bytes = bytes( | ||||
|             [self._encode_char(char, codepage_char_map, defaultchar) for char in text] | ||||
|         ) | ||||
|         return output_bytes | ||||
|  | ||||
|     def __encoding_sort_func(self, item): | ||||
|         key, index = item | ||||
|         return ( | ||||
|             key in self.used_encodings, | ||||
|             index | ||||
|         ) | ||||
|         return (key in self.used_encodings, index) | ||||
|  | ||||
|     def find_suitable_encoding(self, char): | ||||
|         """The order of our search is a specific one: | ||||
| @@ -172,9 +173,7 @@ class Encoder(object): | ||||
|            that the code page we pick for this character is actually | ||||
|            supported. | ||||
|         """ | ||||
|         sorted_encodings = sorted( | ||||
|             self.codepages.items(), | ||||
|             key=self.__encoding_sort_func) | ||||
|         sorted_encodings = sorted(self.codepages.items(), key=self.__encoding_sort_func) | ||||
|  | ||||
|         for encoding, _ in sorted_encodings: | ||||
|             if self.can_encode(encoding, char): | ||||
| @@ -209,8 +208,10 @@ class MagicEncode(object): | ||||
|     If the printer does not support a suitable code page, it can | ||||
|     insert an error character. | ||||
|     """ | ||||
|     def __init__(self, driver, encoding=None, disabled=False, | ||||
|                  defaultsymbol='?', encoder=None): | ||||
|  | ||||
|     def __init__( | ||||
|         self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None | ||||
|     ): | ||||
|         """ | ||||
|  | ||||
|         :param driver: | ||||
| @@ -223,7 +224,7 @@ class MagicEncode(object): | ||||
|         :param encoder: | ||||
|         """ | ||||
|         if disabled and not encoding: | ||||
|             raise Error('If you disable magic encode, you need to define an encoding!') | ||||
|             raise Error("If you disable magic encode, you need to define an encoding!") | ||||
|  | ||||
|         self.driver = driver | ||||
|         self.encoder = encoder or Encoder(driver.profile.get_code_pages()) | ||||
| @@ -245,8 +246,7 @@ class MagicEncode(object): | ||||
|             self.disabled = True | ||||
|  | ||||
|     def write(self, text): | ||||
|         """Write the text, automatically switching encodings. | ||||
|         """ | ||||
|         """Write the text, automatically switching encodings.""" | ||||
|  | ||||
|         if self.disabled: | ||||
|             self.write_with_encoding(self.encoding, text) | ||||
| @@ -272,25 +272,26 @@ class MagicEncode(object): | ||||
|                 self.write_with_encoding(encoding, to_write) | ||||
|  | ||||
|     def _handle_character_failed(self, char): | ||||
|         """Called when no codepage was found to render a character. | ||||
|         """ | ||||
|         """Called when no codepage was found to render a character.""" | ||||
|         # Writing the default symbol via write() allows us to avoid | ||||
|         # unnecesary codepage switches. | ||||
|         self.write(self.defaultsymbol) | ||||
|  | ||||
|     def write_with_encoding(self, encoding, text): | ||||
|         if text is not None and type(text) is not six.text_type: | ||||
|             raise Error("The supplied text has to be unicode, but is of type {type}.".format( | ||||
|                 type=type(text) | ||||
|             )) | ||||
|             raise Error( | ||||
|                 "The supplied text has to be unicode, but is of type {type}.".format( | ||||
|                     type=type(text) | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|         # We always know the current code page; if the new codepage | ||||
|         # is different, emit a change command. | ||||
|         if encoding != self.encoding: | ||||
|             self.encoding = encoding | ||||
|             self.driver._raw( | ||||
|                 CODEPAGE_CHANGE + | ||||
|                 six.int2byte(self.encoder.get_sequence(encoding))) | ||||
|                 CODEPAGE_CHANGE + six.int2byte(self.encoder.get_sequence(encoding)) | ||||
|             ) | ||||
|  | ||||
|         if text: | ||||
|             self.driver._raw(self.encoder.encode(text, encoding)) | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import, division, print_function, unicode_literals | ||||
|  | ||||
| import serial | ||||
| import socket | ||||
| @@ -20,7 +19,7 @@ from .exceptions import USBNotFoundError | ||||
|  | ||||
|  | ||||
| class Usb(Escpos): | ||||
|     """ USB printer | ||||
|     """USB printer | ||||
|  | ||||
|     This class describes a printer that natively speaks USB. | ||||
|  | ||||
| @@ -31,8 +30,17 @@ class Usb(Escpos): | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, idVendor, idProduct, usb_args=None, timeout=0, in_ep=0x82, out_ep=0x01, | ||||
|                  *args, **kwargs):  # noqa: N803 | ||||
|     def __init__( | ||||
|         self, | ||||
|         idVendor, | ||||
|         idProduct, | ||||
|         usb_args=None, | ||||
|         timeout=0, | ||||
|         in_ep=0x82, | ||||
|         out_ep=0x01, | ||||
|         *args, | ||||
|         **kwargs | ||||
|     ):  # noqa: N803 | ||||
|         """ | ||||
|         :param idVendor: Vendor ID | ||||
|         :param idProduct: Product ID | ||||
| @@ -48,13 +56,13 @@ class Usb(Escpos): | ||||
|  | ||||
|         usb_args = usb_args or {} | ||||
|         if idVendor: | ||||
|             usb_args['idVendor'] = idVendor | ||||
|             usb_args["idVendor"] = idVendor | ||||
|         if idProduct: | ||||
|             usb_args['idProduct'] = idProduct | ||||
|             usb_args["idProduct"] = idProduct | ||||
|         self.open(usb_args) | ||||
|  | ||||
|     def open(self, usb_args): | ||||
|         """ Search device on USB tree and set it as escpos device. | ||||
|         """Search device on USB tree and set it as escpos device. | ||||
|  | ||||
|         :param usb_args: USB arguments | ||||
|         """ | ||||
| @@ -93,7 +101,7 @@ class Usb(Escpos): | ||||
|             print("Could not set configuration: {0}".format(str(e))) | ||||
|  | ||||
|     def _raw(self, msg): | ||||
|         """ Print any command sent in raw format | ||||
|         """Print any command sent in raw format | ||||
|  | ||||
|         :param msg: arbitrary code to be printed | ||||
|         :type msg: bytes | ||||
| @@ -101,18 +109,18 @@ class Usb(Escpos): | ||||
|         self.device.write(self.out_ep, msg, self.timeout) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Reads a data buffer and returns it to the caller. """ | ||||
|         """Reads a data buffer and returns it to the caller.""" | ||||
|         return self.device.read(self.in_ep, 16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Release USB interface """ | ||||
|         """Release USB interface""" | ||||
|         if self.device: | ||||
|             usb.util.dispose_resources(self.device) | ||||
|         self.device = None | ||||
|  | ||||
|  | ||||
| class Serial(Escpos): | ||||
|     """ Serial printer | ||||
|     """Serial printer | ||||
|  | ||||
|     This class describes a printer that is connected by serial interface. | ||||
|  | ||||
| @@ -123,9 +131,19 @@ class Serial(Escpos): | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1, | ||||
|                  parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, | ||||
|                  xonxoff=False, dsrdtr=True, *args, **kwargs): | ||||
|     def __init__( | ||||
|         self, | ||||
|         devfile="/dev/ttyS0", | ||||
|         baudrate=9600, | ||||
|         bytesize=8, | ||||
|         timeout=1, | ||||
|         parity=serial.PARITY_NONE, | ||||
|         stopbits=serial.STOPBITS_ONE, | ||||
|         xonxoff=False, | ||||
|         dsrdtr=True, | ||||
|         *args, | ||||
|         **kwargs | ||||
|     ): | ||||
|         """ | ||||
|  | ||||
|         :param devfile:  Device file under dev filesystem | ||||
| @@ -150,13 +168,19 @@ class Serial(Escpos): | ||||
|         self.open() | ||||
|  | ||||
|     def open(self): | ||||
|         """ Setup serial port and set is as escpos device """ | ||||
|         """Setup serial port and set is as escpos device""" | ||||
|         if self.device is not None and self.device.is_open: | ||||
|             self.close() | ||||
|         self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, | ||||
|                                     bytesize=self.bytesize, parity=self.parity, | ||||
|                                     stopbits=self.stopbits, timeout=self.timeout, | ||||
|                                     xonxoff=self.xonxoff, dsrdtr=self.dsrdtr) | ||||
|         self.device = serial.Serial( | ||||
|             port=self.devfile, | ||||
|             baudrate=self.baudrate, | ||||
|             bytesize=self.bytesize, | ||||
|             parity=self.parity, | ||||
|             stopbits=self.stopbits, | ||||
|             timeout=self.timeout, | ||||
|             xonxoff=self.xonxoff, | ||||
|             dsrdtr=self.dsrdtr, | ||||
|         ) | ||||
|  | ||||
|         if self.device is not None: | ||||
|             print("Serial printer enabled") | ||||
| @@ -164,7 +188,7 @@ class Serial(Escpos): | ||||
|             print("Unable to open serial printer on: {0}".format(str(self.devfile))) | ||||
|  | ||||
|     def _raw(self, msg): | ||||
|         """ Print any command sent in raw format | ||||
|         """Print any command sent in raw format | ||||
|  | ||||
|         :param msg: arbitrary code to be printed | ||||
|         :type msg: bytes | ||||
| @@ -172,18 +196,18 @@ class Serial(Escpos): | ||||
|         self.device.write(msg) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Reads a data buffer and returns it to the caller. """ | ||||
|         """Reads a data buffer and returns it to the caller.""" | ||||
|         return self.device.read(16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close Serial interface """ | ||||
|         """Close Serial interface""" | ||||
|         if self.device is not None and self.device.is_open: | ||||
|             self.device.flush() | ||||
|             self.device.close() | ||||
|  | ||||
|  | ||||
| class Network(Escpos): | ||||
|     """ Network printer | ||||
|     """Network printer | ||||
|  | ||||
|     This class is used to attach to a networked printer. You can also use this in order to attach to a printer that | ||||
|     is forwarded with ``socat``. | ||||
| @@ -219,7 +243,7 @@ class Network(Escpos): | ||||
|         self.open() | ||||
|  | ||||
|     def open(self): | ||||
|         """ Open TCP socket with ``socket``-library and set it as escpos device """ | ||||
|         """Open TCP socket with ``socket``-library and set it as escpos device""" | ||||
|         self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
|         self.device.settimeout(self.timeout) | ||||
|         self.device.connect((self.host, self.port)) | ||||
| @@ -228,7 +252,7 @@ class Network(Escpos): | ||||
|             print("Could not open socket for {0}".format(self.host)) | ||||
|  | ||||
|     def _raw(self, msg): | ||||
|         """ Print any command sent in raw format | ||||
|         """Print any command sent in raw format | ||||
|  | ||||
|         :param msg: arbitrary code to be printed | ||||
|         :type msg: bytes | ||||
| @@ -236,19 +260,22 @@ class Network(Escpos): | ||||
|         self.device.sendall(msg) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Read data from the TCP socket """ | ||||
|         """Read data from the TCP socket""" | ||||
|  | ||||
|         return self.device.recv(16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close TCP connection """ | ||||
|         """Close TCP connection""" | ||||
|         if self.device is not None: | ||||
|             self.device.shutdown(socket.SHUT_RDWR) | ||||
|             try: | ||||
|                 self.device.shutdown(socket.SHUT_RDWR) | ||||
|             except socket.error: | ||||
|                 pass | ||||
|             self.device.close() | ||||
|  | ||||
|  | ||||
| class File(Escpos): | ||||
|     """ Generic file printer | ||||
|     """Generic file printer | ||||
|  | ||||
|     This class is used for parallel port printer or other printers that are directly attached to the filesystem. | ||||
|     Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable | ||||
| @@ -273,18 +300,18 @@ class File(Escpos): | ||||
|         self.open() | ||||
|  | ||||
|     def open(self): | ||||
|         """ Open system file """ | ||||
|         """Open system file""" | ||||
|         self.device = open(self.devfile, "wb") | ||||
|  | ||||
|         if self.device is None: | ||||
|             print("Could not open the specified file {0}".format(self.devfile)) | ||||
|  | ||||
|     def flush(self): | ||||
|         """ Flush printing content """ | ||||
|         """Flush printing content""" | ||||
|         self.device.flush() | ||||
|  | ||||
|     def _raw(self, msg): | ||||
|         """ Print any command sent in raw format | ||||
|         """Print any command sent in raw format | ||||
|  | ||||
|         :param msg: arbitrary code to be printed | ||||
|         :type msg: bytes | ||||
| @@ -294,14 +321,14 @@ class File(Escpos): | ||||
|             self.flush() | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close system file """ | ||||
|         """Close system file""" | ||||
|         if self.device is not None: | ||||
|             self.device.flush() | ||||
|             self.device.close() | ||||
|  | ||||
|  | ||||
| class Dummy(Escpos): | ||||
|     """ Dummy printer | ||||
|     """Dummy printer | ||||
|  | ||||
|     This class is used for saving commands to a variable, for use in situations where | ||||
|     there is no need to send commands to an actual printer. This includes | ||||
| @@ -315,13 +342,12 @@ class Dummy(Escpos): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         """ | ||||
|         """ | ||||
|         """ """ | ||||
|         Escpos.__init__(self, *args, **kwargs) | ||||
|         self._output_list = [] | ||||
|  | ||||
|     def _raw(self, msg): | ||||
|         """ Print any command sent in raw format | ||||
|         """Print any command sent in raw format | ||||
|  | ||||
|         :param msg: arbitrary code to be printed | ||||
|         :type msg: bytes | ||||
| @@ -330,11 +356,11 @@ class Dummy(Escpos): | ||||
|  | ||||
|     @property | ||||
|     def output(self): | ||||
|         """ Get the data that was sent to this printer """ | ||||
|         return b''.join(self._output_list) | ||||
|         """Get the data that was sent to this printer""" | ||||
|         return b"".join(self._output_list) | ||||
|  | ||||
|     def clear(self): | ||||
|         """ Clear the buffer of the printer | ||||
|         """Clear the buffer of the printer | ||||
|  | ||||
|         This method can be called if you send the contents to a physical printer | ||||
|         and want to use the Dummy printer for new output. | ||||
| @@ -354,6 +380,7 @@ except ImportError: | ||||
|     pass | ||||
|  | ||||
| if _WIN32PRINT: | ||||
|  | ||||
|     class Win32Raw(Escpos): | ||||
|         def __init__(self, printer_name=None, *args, **kwargs): | ||||
|             Escpos.__init__(self, *args, **kwargs) | ||||
| @@ -362,12 +389,15 @@ if _WIN32PRINT: | ||||
|             else: | ||||
|                 self.printer_name = win32print.GetDefaultPrinter() | ||||
|             self.hPrinter = None | ||||
|             self.open() | ||||
|  | ||||
|         def open(self, job_name="python-escpos"): | ||||
|             if self.printer_name is None: | ||||
|                 raise Exception("Printer not found") | ||||
|             self.hPrinter = win32print.OpenPrinter(self.printer_name) | ||||
|             self.current_job = win32print.StartDocPrinter(self.hPrinter, 1, (job_name, None, "RAW")) | ||||
|             self.current_job = win32print.StartDocPrinter( | ||||
|                 self.hPrinter, 1, (job_name, None, "RAW") | ||||
|             ) | ||||
|             win32print.StartPagePrinter(self.hPrinter) | ||||
|  | ||||
|         def close(self): | ||||
| @@ -379,7 +409,7 @@ if _WIN32PRINT: | ||||
|             self.hPrinter = None | ||||
|  | ||||
|         def _raw(self, msg): | ||||
|             """ Print any command sent in raw format | ||||
|             """Print any command sent in raw format | ||||
|  | ||||
|             :param msg: arbitrary code to be printed | ||||
|             :type msg: bytes | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #!/usr/bin/python | ||||
| """verifies that the metaclass abc is properly used by Escpos | ||||
| """verifies that the metaclass abc is properly used by ESC/POS | ||||
|  | ||||
| :author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ | ||||
| :organization: `python-escpos <https://github.com/python-escpos>`_ | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from nose.tools import raises | ||||
|  | ||||
| @@ -20,11 +16,11 @@ from abc import ABCMeta | ||||
|  | ||||
| @raises(TypeError) | ||||
| def test_abstract_base_class_raises(): | ||||
|     """test whether the abstract base class raises an exception for Escpos""" | ||||
|     """test whether the abstract base class raises an exception for ESC/POS""" | ||||
|     escpos.Escpos()  # This call should raise TypeError because of abstractmethod _raw() | ||||
|  | ||||
|  | ||||
| def test_abstract_base_class(): | ||||
|     """ test whether Escpos has the metaclass ABCMeta """ | ||||
|     """test whether Escpos has the metaclass ABCMeta""" | ||||
|     assert issubclass(escpos.Escpos, object) | ||||
|     assert type(escpos.Escpos) is ABCMeta | ||||
|   | ||||
| @@ -2,51 +2,46 @@ | ||||
|  | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| from scripttest import TestFileEnvironment | ||||
| from nose.tools import assert_equals, nottest | ||||
| from nose.tools import assert_equal, nottest | ||||
| import escpos | ||||
|  | ||||
| TEST_DIR = os.path.abspath('test/test-cli-output') | ||||
| TEST_DIR = os.path.abspath("test/test-cli-output") | ||||
|  | ||||
| DEVFILE_NAME = 'testfile' | ||||
| DEVFILE_NAME = "testfile" | ||||
|  | ||||
| DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME) | ||||
| CONFIGFILE = 'testconfig.yaml' | ||||
| CONFIG_YAML = ''' | ||||
| CONFIGFILE = "testconfig.yaml" | ||||
| CONFIG_YAML = """ | ||||
| --- | ||||
|  | ||||
| printer: | ||||
|     type: file | ||||
|     devfile: {testfile} | ||||
| '''.format( | ||||
| """.format( | ||||
|     testfile=DEVFILE, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class TestCLI: | ||||
|     """ Contains setups, teardowns, and tests for CLI | ||||
|     """ | ||||
|     """Contains setups, teardowns, and tests for CLI""" | ||||
|  | ||||
|     @classmethod | ||||
|     def setup_class(cls): | ||||
|         """ Create a config file to read from """ | ||||
|         with open(CONFIGFILE, 'w') as config: | ||||
|         """Create a config file to read from""" | ||||
|         with open(CONFIGFILE, "w") as config: | ||||
|             config.write(CONFIG_YAML) | ||||
|  | ||||
|     @classmethod | ||||
|     def teardown_class(cls): | ||||
|         """ Remove config file """ | ||||
|         """Remove config file""" | ||||
|         os.remove(CONFIGFILE) | ||||
|  | ||||
|     def setup(self): | ||||
|         """ Create a file to print to and set up env""" | ||||
|         """Create a file to print to and set up env""" | ||||
|         self.env = None | ||||
|         self.default_args = None | ||||
|  | ||||
| @@ -56,63 +51,59 @@ class TestCLI: | ||||
|         ) | ||||
|  | ||||
|         self.default_args = ( | ||||
|             'python-escpos', | ||||
|             '-c', | ||||
|             "python-escpos", | ||||
|             "-c", | ||||
|             CONFIGFILE, | ||||
|         ) | ||||
|  | ||||
|         fhandle = open(DEVFILE, 'a') | ||||
|         fhandle = open(DEVFILE, "a") | ||||
|         try: | ||||
|             os.utime(DEVFILE, None) | ||||
|         finally: | ||||
|             fhandle.close() | ||||
|  | ||||
|     def teardown(self): | ||||
|         """ Destroy printer file and env """ | ||||
|         """Destroy printer file and env""" | ||||
|         os.remove(DEVFILE) | ||||
|         self.env.clear() | ||||
|  | ||||
|     def test_cli_help(self): | ||||
|         """ Test getting help from cli """ | ||||
|         result = self.env.run('python-escpos', '-h') | ||||
|         """Test getting help from cli""" | ||||
|         result = self.env.run("python-escpos", "-h") | ||||
|         assert not result.stderr | ||||
|         assert 'usage' in result.stdout | ||||
|         assert "usage" in result.stdout | ||||
|  | ||||
|     def test_cli_version(self): | ||||
|         """ Test the version string """ | ||||
|         result = self.env.run('python-escpos', 'version') | ||||
|         """Test the version string""" | ||||
|         result = self.env.run("python-escpos", "version") | ||||
|         assert not result.stderr | ||||
|         assert_equals(escpos.__version__, result.stdout.strip()) | ||||
|         assert_equal(escpos.__version__, result.stdout.strip()) | ||||
|  | ||||
|     @nottest  # disable this test as it is not that easy anymore to predict the outcome of this call | ||||
|     def test_cli_text(self): | ||||
|         """ Make sure text returns what we sent it """ | ||||
|         test_text = 'this is some text' | ||||
|         """Make sure text returns what we sent it""" | ||||
|         test_text = "this is some text" | ||||
|         result = self.env.run( | ||||
|             *(self.default_args + ( | ||||
|                 'text', | ||||
|                 '--txt', | ||||
|                 test_text, | ||||
|             )) | ||||
|             *( | ||||
|                 self.default_args | ||||
|                 + ( | ||||
|                     "text", | ||||
|                     "--txt", | ||||
|                     test_text, | ||||
|                 ) | ||||
|             ) | ||||
|         ) | ||||
|         assert not result.stderr | ||||
|         assert DEVFILE_NAME in result.files_updated.keys() | ||||
|         assert_equals( | ||||
|             result.files_updated[DEVFILE_NAME].bytes, | ||||
|             test_text + '\n' | ||||
|         ) | ||||
|         assert_equals(result.files_updated[DEVFILE_NAME].bytes, test_text + "\n") | ||||
|  | ||||
|     def test_cli_text_inavlid_args(self): | ||||
|         """ Test a failure to send valid arguments """ | ||||
|         """Test a failure to send valid arguments""" | ||||
|         result = self.env.run( | ||||
|             *(self.default_args + ( | ||||
|                 'text', | ||||
|                 '--invalid-param', | ||||
|                 'some data' | ||||
|             )), | ||||
|             *(self.default_args + ("text", "--invalid-param", "some data")), | ||||
|             expect_error=True, | ||||
|             expect_stderr=True | ||||
|         ) | ||||
|         assert_equals(result.returncode, 2) | ||||
|         assert 'error:' in result.stderr | ||||
|         assert_equal(result.returncode, 2) | ||||
|         assert "error:" in result.stderr | ||||
|         assert not result.files_updated | ||||
|   | ||||
| @@ -1,52 +1,56 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B | ||||
| from escpos.capabilities import Profile, BARCODE_B | ||||
| from escpos.exceptions import BarcodeTypeError, BarcodeCodeError | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data,expected", [ | ||||
|     ('EAN13', '4006381333931', | ||||
|         b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00') | ||||
| ]) | ||||
| @pytest.mark.parametrize( | ||||
|     "bctype,data,expected", | ||||
|     [ | ||||
|         ( | ||||
|             "EAN13", | ||||
|             "4006381333931", | ||||
|             b"\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00", | ||||
|         ) | ||||
|     ], | ||||
| ) | ||||
| def test_barcode(bctype, data, expected): | ||||
|     """should generate different barcode types correctly. | ||||
|     """ | ||||
|     """should generate different barcode types correctly.""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.barcode(data, bctype) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,supports_b", [ | ||||
|     ('invalid', True), | ||||
|     ('CODE128', False), | ||||
| ]) | ||||
| @pytest.mark.parametrize( | ||||
|     "bctype,supports_b", | ||||
|     [ | ||||
|         ("invalid", True), | ||||
|         ("CODE128", False), | ||||
|     ], | ||||
| ) | ||||
| def test_lacks_support(bctype, supports_b): | ||||
|     """should raise an error if the barcode type is not supported. | ||||
|     """ | ||||
|     """should raise an error if the barcode type is not supported.""" | ||||
|     profile = Profile(features={BARCODE_B: supports_b}) | ||||
|     instance = printer.Dummy(profile=profile) | ||||
|     with pytest.raises(BarcodeTypeError): | ||||
|         instance.barcode('test', bctype) | ||||
|         instance.barcode("test", bctype) | ||||
|  | ||||
|     assert instance.output == b'' | ||||
|     assert instance.output == b"" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('EAN13', 'AA'), | ||||
|     ('CODE128', '{D2354AA'), | ||||
| ]) | ||||
| @pytest.mark.parametrize( | ||||
|     "bctype,data", | ||||
|     [ | ||||
|         ("EAN13", "AA"), | ||||
|         ("CODE128", "{D2354AA"), | ||||
|     ], | ||||
| ) | ||||
| def test_code_check(bctype, data): | ||||
|     """should raise an error if the barcode code is invalid. | ||||
|     """ | ||||
|     """should raise an error if the barcode code is invalid.""" | ||||
|     instance = printer.Dummy() | ||||
|     with pytest.raises(BarcodeCodeError): | ||||
|         instance.barcode(data, bctype) | ||||
|  | ||||
|     assert instance.output == b'' | ||||
|     assert instance.output == b"" | ||||
|   | ||||
| @@ -1,8 +1,4 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.exceptions import CashDrawerError | ||||
| @@ -10,10 +6,8 @@ import pytest | ||||
|  | ||||
|  | ||||
| def test_raise_CashDrawerError(): | ||||
|     """should raise an error if the sequence is invalid. | ||||
|     """ | ||||
|     """should raise an error if the sequence is invalid.""" | ||||
|     instance = printer.Dummy() | ||||
|     with pytest.raises(CashDrawerError): | ||||
|         # call with sequence that is too long | ||||
|         instance.cashdraw([1,1,1,1,1,1]) | ||||
|  | ||||
|         instance.cashdraw([1, 1, 1, 1, 1, 1]) | ||||
|   | ||||
| @@ -1,104 +1,106 @@ | ||||
| #!/usr/bin/python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('UPC-A', '01234567890'), | ||||
|     ('UPC-A', '012345678905'), | ||||
|     ('UPC-E', '01234567'), | ||||
|     ('UPC-E', '0123456'), | ||||
|     ('UPC-E', '012345678905'), | ||||
|     ('EAN13', '0123456789012'), | ||||
|     ('EAN13', '012345678901'), | ||||
|     ('EAN8', '01234567'), | ||||
|     ('EAN8', '0123456'), | ||||
|     ('CODE39', 'ABC-1234'), | ||||
|     ('CODE39', 'ABC-1234-$$-+A'), | ||||
|     ('CODE39', '*WIKIPEDIA*'), | ||||
|     ('ITF', '010203040506070809'), | ||||
|     ('ITF', '11221133113344556677889900'), | ||||
|     ('CODABAR', 'A2030405060B'), | ||||
|     ('CODABAR', 'C11221133113344556677889900D'), | ||||
|     ('CODABAR', 'D0D'), | ||||
|     ('NW7', 'A2030405060B'), | ||||
|     ('NW7', 'C11221133113344556677889900D'), | ||||
|     ('NW7', 'D0D'), | ||||
|     ('CODE93', 'A2030405060B'), | ||||
|     ('CODE93', '+:$&23-7@$'), | ||||
|     ('CODE93', 'D0D'), | ||||
|     ('CODE128', '{A2030405060B'), | ||||
|     ('CODE128', '{C+:$&23-7@$'), | ||||
|     ('CODE128', '{B0D'), | ||||
|     ('GS1-128', '{A2030405060B'), | ||||
|     ('GS1-128', '{C+:$&23-7@$'), | ||||
|     ('GS1-128', '{B0D'), | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'), | ||||
|     ('GS1 DATABAR TRUNCATED', '0123456789123'), | ||||
|     ('GS1 DATABAR LIMITED', '0123456789123'), | ||||
|     ('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'), | ||||
|     ('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'), | ||||
|     ('GS1 DATABAR EXPANDED', '(00000001234567678'), | ||||
| ]) | ||||
| @pytest.mark.parametrize( | ||||
|     "bctype,data", | ||||
|     [ | ||||
|         ("UPC-A", "01234567890"), | ||||
|         ("UPC-A", "012345678905"), | ||||
|         ("UPC-E", "01234567"), | ||||
|         ("UPC-E", "0123456"), | ||||
|         ("UPC-E", "012345678905"), | ||||
|         ("EAN13", "0123456789012"), | ||||
|         ("EAN13", "012345678901"), | ||||
|         ("EAN8", "01234567"), | ||||
|         ("EAN8", "0123456"), | ||||
|         ("CODE39", "ABC-1234"), | ||||
|         ("CODE39", "ABC-1234-$$-+A"), | ||||
|         ("CODE39", "*WIKIPEDIA*"), | ||||
|         ("ITF", "010203040506070809"), | ||||
|         ("ITF", "11221133113344556677889900"), | ||||
|         ("CODABAR", "A2030405060B"), | ||||
|         ("CODABAR", "C11221133113344556677889900D"), | ||||
|         ("CODABAR", "D0D"), | ||||
|         ("NW7", "A2030405060B"), | ||||
|         ("NW7", "C11221133113344556677889900D"), | ||||
|         ("NW7", "D0D"), | ||||
|         ("CODE93", "A2030405060B"), | ||||
|         ("CODE93", "+:$&23-7@$"), | ||||
|         ("CODE93", "D0D"), | ||||
|         ("CODE128", "{A2030405060B"), | ||||
|         ("CODE128", "{C+:$&23-7@$"), | ||||
|         ("CODE128", "{B0D"), | ||||
|         ("GS1-128", "{A2030405060B"), | ||||
|         ("GS1-128", "{C+:$&23-7@$"), | ||||
|         ("GS1-128", "{B0D"), | ||||
|         ("GS1 DATABAR OMNIDIRECTIONAL", "0123456789123"), | ||||
|         ("GS1 DATABAR TRUNCATED", "0123456789123"), | ||||
|         ("GS1 DATABAR LIMITED", "0123456789123"), | ||||
|         ("GS1 DATABAR EXPANDED", "(9A{A20304+-%&06a0B"), | ||||
|         ("GS1 DATABAR EXPANDED", "(1 {C+:&23-7%"), | ||||
|         ("GS1 DATABAR EXPANDED", "(00000001234567678"), | ||||
|     ], | ||||
| ) | ||||
| def test_check_valid_barcode(bctype, data): | ||||
|     assert (printer.Escpos.check_barcode(bctype, data)) | ||||
|     assert printer.Escpos.check_barcode(bctype, data) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('UPC-A', '01234567890123'),  # too long | ||||
|     ('UPC-A', '0123456789'),  # too short | ||||
|     ('UPC-A', '72527273-711'),  # invalid '-' | ||||
|     ('UPC-A', 'A12345678901'),  # invalid 'A' | ||||
|     ('UPC-E', '01234567890123'),  # too long | ||||
|     ('UPC-E', '012345'),  # too short | ||||
|     ('UPC-E', '72527-2'),  # invalid '-' | ||||
|     ('UPC-E', 'A123456'),  # invalid 'A' | ||||
|     ('EAN13', '0123456789'),  # too short | ||||
|     ('EAN13', 'A123456789012'),  # invalid 'A' | ||||
|     ('EAN13', '012345678901234'),  # too long | ||||
|     ('EAN8', '012345'),  # too short | ||||
|     ('EAN8', 'A123456789012'),  # invalid 'A' | ||||
|     ('EAN8', '012345678901234'),  # too long | ||||
|     ('CODE39', 'ALKJ_34'),  # invalid '_' | ||||
|     ('CODE39', 'A' * 256),  # too long | ||||
|     ('ITF', '010203040'),  # odd length | ||||
|     ('ITF', '0' * 256),  # too long | ||||
|     ('ITF', 'AB01'),  # invalid 'A' | ||||
|     ('CODABAR', '010203040'),  # no start/stop | ||||
|     ('CODABAR', '0' * 256),  # too long | ||||
|     ('CODABAR', 'AB-01F'),  # invalid 'B' | ||||
|     ('NW7', '010203040'),  # no start/stop | ||||
|     ('NW7', '0' * 256),  # too long | ||||
|     ('NW7', 'AB-01F'),  # invalid 'B' | ||||
|     ('CODE93', 'é010203040'),  # invalid 'é' | ||||
|     ('CODE93', '0' * 256),  # too long | ||||
|     ('CODE128', '010203040'),  # missing leading { | ||||
|     ('CODE128', '{D2354AA'),  # second char not between A-C | ||||
|     ('CODE128', '0' * 256),  # too long | ||||
|     ('GS1-128', '010203040'),  # missing leading { | ||||
|     ('GS1-128', '{D2354AA'),  # second char not between A-C | ||||
|     ('GS1-128', '0' * 256),  # too long | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR TRUNCATED', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR TRUNCATED', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR TRUNCATED', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR LIMITED', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR LIMITED', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR LIMITED', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR LIMITED', '02345678912341'),  # invalid start (should be 01) | ||||
|     ('GS1 DATABAR EXPANDED', '010203040'),  # missing leading ( | ||||
|     ('GS1-128', '(' + ('0' * 256)),  # too long | ||||
|     ('GS1 DATABAR EXPANDED', '(a{D2354AA'),  # second char not between 0-9 | ||||
|     ('GS1 DATABAR EXPANDED', 'IT will fail'),  # first char not '(' | ||||
| ]) | ||||
| @pytest.mark.parametrize( | ||||
|     "bctype,data", | ||||
|     [ | ||||
|         ("UPC-A", "01234567890123"),  # too long | ||||
|         ("UPC-A", "0123456789"),  # too short | ||||
|         ("UPC-A", "72527273-711"),  # invalid '-' | ||||
|         ("UPC-A", "A12345678901"),  # invalid 'A' | ||||
|         ("UPC-E", "01234567890123"),  # too long | ||||
|         ("UPC-E", "012345"),  # too short | ||||
|         ("UPC-E", "72527-2"),  # invalid '-' | ||||
|         ("UPC-E", "A123456"),  # invalid 'A' | ||||
|         ("EAN13", "0123456789"),  # too short | ||||
|         ("EAN13", "A123456789012"),  # invalid 'A' | ||||
|         ("EAN13", "012345678901234"),  # too long | ||||
|         ("EAN8", "012345"),  # too short | ||||
|         ("EAN8", "A123456789012"),  # invalid 'A' | ||||
|         ("EAN8", "012345678901234"),  # too long | ||||
|         ("CODE39", "ALKJ_34"),  # invalid '_' | ||||
|         ("CODE39", "A" * 256),  # too long | ||||
|         ("ITF", "010203040"),  # odd length | ||||
|         ("ITF", "0" * 256),  # too long | ||||
|         ("ITF", "AB01"),  # invalid 'A' | ||||
|         ("CODABAR", "010203040"),  # no start/stop | ||||
|         ("CODABAR", "0" * 256),  # too long | ||||
|         ("CODABAR", "AB-01F"),  # invalid 'B' | ||||
|         ("NW7", "010203040"),  # no start/stop | ||||
|         ("NW7", "0" * 256),  # too long | ||||
|         ("NW7", "AB-01F"),  # invalid 'B' | ||||
|         ("CODE93", "é010203040"),  # invalid 'é' | ||||
|         ("CODE93", "0" * 256),  # too long | ||||
|         ("CODE128", "010203040"),  # missing leading { | ||||
|         ("CODE128", "{D2354AA"),  # second char not between A-C | ||||
|         ("CODE128", "0" * 256),  # too long | ||||
|         ("GS1-128", "010203040"),  # missing leading { | ||||
|         ("GS1-128", "{D2354AA"),  # second char not between A-C | ||||
|         ("GS1-128", "0" * 256),  # too long | ||||
|         ("GS1 DATABAR OMNIDIRECTIONAL", "01234567891234"),  # too long | ||||
|         ("GS1 DATABAR OMNIDIRECTIONAL", "012345678912"),  # too short | ||||
|         ("GS1 DATABAR OMNIDIRECTIONAL", "012345678A1234"),  # invalid 'A' | ||||
|         ("GS1 DATABAR TRUNCATED", "01234567891234"),  # too long | ||||
|         ("GS1 DATABAR TRUNCATED", "012345678912"),  # too short | ||||
|         ("GS1 DATABAR TRUNCATED", "012345678A1234"),  # invalid 'A' | ||||
|         ("GS1 DATABAR LIMITED", "01234567891234"),  # too long | ||||
|         ("GS1 DATABAR LIMITED", "012345678912"),  # too short | ||||
|         ("GS1 DATABAR LIMITED", "012345678A1234"),  # invalid 'A' | ||||
|         ("GS1 DATABAR LIMITED", "02345678912341"),  # invalid start (should be 01) | ||||
|         ("GS1 DATABAR EXPANDED", "010203040"),  # missing leading ( | ||||
|         ("GS1-128", "(" + ("0" * 256)),  # too long | ||||
|         ("GS1 DATABAR EXPANDED", "(a{D2354AA"),  # second char not between 0-9 | ||||
|         ("GS1 DATABAR EXPANDED", "IT will fail"),  # first char not '(' | ||||
|     ], | ||||
| ) | ||||
| def test_check_invalid_barcode(bctype, data): | ||||
|     assert (not printer.Escpos.check_barcode(bctype, data)) | ||||
|     assert not printer.Escpos.check_barcode(bctype, data) | ||||
|   | ||||
| @@ -1,8 +1,3 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| import escpos.printer as printer | ||||
| @@ -13,5 +8,5 @@ def test_cut_without_feed(): | ||||
|     """Test cut without feeding paper""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.cut(feed=False) | ||||
|     expected = GS + b'V' + six.int2byte(66) + b'\x00' | ||||
|     assert(instance.output == expected) | ||||
|     expected = GS + b"V" + six.int2byte(66) + b"\x00" | ||||
|     assert instance.output == expected | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| from nose.tools import assert_raises | ||||
| from escpos.printer import Dummy | ||||
|  | ||||
|  | ||||
| def test_printer_dummy_clear(): | ||||
|     printer = Dummy() | ||||
|     printer.text("Hello") | ||||
|     printer.clear() | ||||
|     assert(printer.output == b'') | ||||
|     assert printer.output == b"" | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| @@ -26,13 +22,13 @@ def test_bit_image_black(): | ||||
|     Test printing solid black bit image (raster) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_black.png', impl="bitImageRaster") | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80') | ||||
|     instance.image("test/resources/canvas_black.png", impl="bitImageRaster") | ||||
|     assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80" | ||||
|     # Same thing w/ object created on the fly, rather than a filename | ||||
|     instance = printer.Dummy() | ||||
|     im = Image.new("RGB", (1, 1), (0, 0, 0)) | ||||
|     instance.image(im, impl="bitImageRaster") | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80') | ||||
|     assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80" | ||||
|  | ||||
|  | ||||
| def test_bit_image_white(): | ||||
| @@ -40,8 +36,8 @@ def test_bit_image_white(): | ||||
|     Test printing solid white bit image (raster) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_white.png', impl="bitImageRaster") | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x00') | ||||
|     instance.image("test/resources/canvas_white.png", impl="bitImageRaster") | ||||
|     assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00" | ||||
|  | ||||
|  | ||||
| def test_bit_image_both(): | ||||
| @@ -49,8 +45,8 @@ def test_bit_image_both(): | ||||
|     Test printing black/white bit image (raster) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_white.png', impl="bitImageRaster") | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00') | ||||
|     instance.image("test/resources/black_white.png", impl="bitImageRaster") | ||||
|     assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00" | ||||
|  | ||||
|  | ||||
| def test_bit_image_transparent(): | ||||
| @@ -58,8 +54,8 @@ def test_bit_image_transparent(): | ||||
|     Test printing black/transparent bit image (raster) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_transparent.png', impl="bitImageRaster") | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00') | ||||
|     instance.image("test/resources/black_transparent.png", impl="bitImageRaster") | ||||
|     assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00" | ||||
|  | ||||
|  | ||||
| # Column format print | ||||
| @@ -68,8 +64,8 @@ def test_bit_image_colfmt_black(): | ||||
|     Test printing solid black bit image (column format) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_black.png', impl="bitImageColumn") | ||||
|     assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2') | ||||
|     instance.image("test/resources/canvas_black.png", impl="bitImageColumn") | ||||
|     assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2" | ||||
|  | ||||
|  | ||||
| def test_bit_image_colfmt_white(): | ||||
| @@ -77,8 +73,8 @@ def test_bit_image_colfmt_white(): | ||||
|     Test printing solid white bit image (column format) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_white.png', impl="bitImageColumn") | ||||
|     assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2') | ||||
|     instance.image("test/resources/canvas_white.png", impl="bitImageColumn") | ||||
|     assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2" | ||||
|  | ||||
|  | ||||
| def test_bit_image_colfmt_both(): | ||||
| @@ -86,8 +82,10 @@ def test_bit_image_colfmt_both(): | ||||
|     Test printing black/white bit image (column format) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_white.png', impl="bitImageColumn") | ||||
|     assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2') | ||||
|     instance.image("test/resources/black_white.png", impl="bitImageColumn") | ||||
|     assert ( | ||||
|         instance.output == b"\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_bit_image_colfmt_transparent(): | ||||
| @@ -95,8 +93,10 @@ def test_bit_image_colfmt_transparent(): | ||||
|     Test printing black/transparent bit image (column format) | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_transparent.png', impl="bitImageColumn") | ||||
|     assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2') | ||||
|     instance.image("test/resources/black_transparent.png", impl="bitImageColumn") | ||||
|     assert ( | ||||
|         instance.output == b"\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| # Graphics print | ||||
| @@ -105,8 +105,11 @@ def test_graphics_black(): | ||||
|     Test printing solid black graphics | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_black.png', impl="graphics") | ||||
|     assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002') | ||||
|     instance.image("test/resources/canvas_black.png", impl="graphics") | ||||
|     assert ( | ||||
|         instance.output | ||||
|         == b"\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_graphics_white(): | ||||
| @@ -114,8 +117,11 @@ def test_graphics_white(): | ||||
|     Test printing solid white graphics | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/canvas_white.png', impl="graphics") | ||||
|     assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002') | ||||
|     instance.image("test/resources/canvas_white.png", impl="graphics") | ||||
|     assert ( | ||||
|         instance.output | ||||
|         == b"\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_graphics_both(): | ||||
| @@ -123,8 +129,11 @@ def test_graphics_both(): | ||||
|     Test printing black/white graphics | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_white.png', impl="graphics") | ||||
|     assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') | ||||
|     instance.image("test/resources/black_white.png", impl="graphics") | ||||
|     assert ( | ||||
|         instance.output | ||||
|         == b"\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_graphics_transparent(): | ||||
| @@ -132,8 +141,11 @@ def test_graphics_transparent(): | ||||
|     Test printing black/transparent graphics | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_transparent.png', impl="graphics") | ||||
|     assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') | ||||
|     instance.image("test/resources/black_transparent.png", impl="graphics") | ||||
|     assert ( | ||||
|         instance.output | ||||
|         == b"\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_large_graphics(): | ||||
| @@ -141,20 +153,19 @@ def test_large_graphics(): | ||||
|     Test whether 'large' graphics that induce a fragmentation are handled correctly. | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1) | ||||
|     assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') | ||||
|     instance.image( | ||||
|         "test/resources/black_white.png", impl="bitImageRaster", fragment_height=1 | ||||
|     ) | ||||
|     assert ( | ||||
|         instance.output | ||||
|         == b"\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def dummy_with_width(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.profile.profile_data = { | ||||
|         'media': { | ||||
|             'width': { | ||||
|                 'pixels': 384 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     instance.profile.profile_data = {"media": {"width": {"pixels": 384}}} | ||||
|     return instance | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
|  | ||||
| @@ -19,17 +15,18 @@ def test_function_linedisplay_select_on(): | ||||
|     """test the linedisplay_select function (activate)""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_select(select_display=True) | ||||
|     assert(instance.output == b'\x1B\x3D\x02') | ||||
|     assert instance.output == b"\x1B\x3D\x02" | ||||
|  | ||||
|  | ||||
| def test_function_linedisplay_select_off(): | ||||
|     """test the linedisplay_select function (deactivate)""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_select(select_display=False) | ||||
|     assert(instance.output == b'\x1B\x3D\x01') | ||||
|     assert instance.output == b"\x1B\x3D\x01" | ||||
|  | ||||
|  | ||||
| def test_function_linedisplay_clear(): | ||||
|     """test the linedisplay_clear function""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_clear() | ||||
|     assert(instance.output == b'\x1B\x40') | ||||
|  | ||||
|     assert instance.output == b"\x1B\x40" | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
|  | ||||
| @@ -19,11 +15,11 @@ def test_function_panel_button_on(): | ||||
|     """test the panel button function (enabling) by comparing output""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.panel_buttons() | ||||
|     assert(instance.output == b'\x1B\x63\x35\x00') | ||||
|     assert instance.output == b"\x1B\x63\x35\x00" | ||||
|  | ||||
|  | ||||
| def test_function_panel_button_off(): | ||||
|     """test the panel button function (disabling) by comparing output""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.panel_buttons(False) | ||||
|     assert(instance.output == b'\x1B\x63\x35\x01') | ||||
|     assert instance.output == b"\x1B\x63\x35\x01" | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from nose.tools import raises | ||||
| import pytest | ||||
| @@ -23,42 +19,51 @@ def test_defaults(): | ||||
|     """Test QR code with defaults""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1234", native=True) | ||||
|     expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \ | ||||
|         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||
|     assert(instance.output == expected) | ||||
|     expected = ( | ||||
|         b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d" | ||||
|         b"(k\x07\x001P01234\x1d(k\x03\x001Q0" | ||||
|     ) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| def test_empty(): | ||||
|     """Test QR printing blank code""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("", native=True) | ||||
|     assert(instance.output == b'') | ||||
|     assert instance.output == b"" | ||||
|  | ||||
|  | ||||
| def test_ec(): | ||||
|     """Test QR error correction setting""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1234", native=True, ec=QR_ECLEVEL_H) | ||||
|     expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d' \ | ||||
|         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||
|     assert(instance.output == expected) | ||||
|     expected = ( | ||||
|         b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d" | ||||
|         b"(k\x07\x001P01234\x1d(k\x03\x001Q0" | ||||
|     ) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| def test_size(): | ||||
|     """Test QR box size""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1234", native=True, size=7) | ||||
|     expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d' \ | ||||
|         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||
|     assert(instance.output == expected) | ||||
|     expected = ( | ||||
|         b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d" | ||||
|         b"(k\x07\x001P01234\x1d(k\x03\x001Q0" | ||||
|     ) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| def test_model(): | ||||
|     """Test QR model""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1234", native=True, model=QR_MODEL_1) | ||||
|     expected = b'\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \ | ||||
|         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||
|     assert(instance.output == expected) | ||||
|     expected = ( | ||||
|         b"\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d" | ||||
|         b"(k\x07\x001P01234\x1d(k\x03\x001Q0" | ||||
|     ) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| @raises(ValueError) | ||||
| @@ -88,12 +93,14 @@ def test_image(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1", native=False, size=1) | ||||
|     print(instance.output) | ||||
|     expected = b'\x1bt\x00\n' \ | ||||
|         b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ | ||||
|         b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \ | ||||
|         b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \ | ||||
|         b'\n\n' | ||||
|     assert(instance.output == expected) | ||||
|     expected = ( | ||||
|         b"\x1bt\x00\n" | ||||
|         b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et" | ||||
|         b"]ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00" | ||||
|         b"i(\x7f<\xa8A \xd8]'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00" | ||||
|         b"\n\n" | ||||
|     ) | ||||
|     assert instance.output == expected | ||||
|  | ||||
|  | ||||
| @raises(ValueError) | ||||
|   | ||||
| @@ -8,10 +8,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
| import mock | ||||
| @@ -20,7 +16,7 @@ from escpos.printer import Dummy | ||||
| from PIL import Image | ||||
|  | ||||
|  | ||||
| @mock.patch('escpos.printer.Dummy.image', spec=Dummy) | ||||
| @mock.patch("escpos.printer.Dummy.image", spec=Dummy) | ||||
| def test_type_of_object_passed_to_image_function(img_function): | ||||
|     """ | ||||
|     Test the type of object that is passed to the image function during non-native qr-printing. | ||||
|   | ||||
| @@ -1,8 +1,3 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| import escpos.printer as printer | ||||
| @@ -12,41 +7,46 @@ from escpos.constants import TXT_SIZE | ||||
|  | ||||
| # Default test, please copy and paste this block to test set method calls | ||||
|  | ||||
|  | ||||
| def test_default_values(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set() | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Size tests | ||||
|  | ||||
|  | ||||
| def test_set_size_2h(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(double_height=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2h'],  # Double height text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["2h"],  # Double height text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_set_size_2w(): | ||||
| @@ -54,17 +54,18 @@ def test_set_size_2w(): | ||||
|     instance.set(double_width=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2w'],  # Double width text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["2w"],  # Double width text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_set_size_2x(): | ||||
| @@ -72,17 +73,18 @@ def test_set_size_2x(): | ||||
|     instance.set(double_height=True, double_width=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2x'],  # Double text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["2x"],  # Double text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_set_size_custom(): | ||||
| @@ -91,55 +93,61 @@ def test_set_size_custom(): | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_SIZE,  # Custom text size, no normal reset | ||||
|         six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]), | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]), | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Flip | ||||
|  | ||||
|  | ||||
| def test_set_flip(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(flip=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][True],  # Flip ON | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][True],  # Flip ON | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Smooth | ||||
|  | ||||
|  | ||||
| def test_smooth(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(smooth=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][True],  # Smooth ON | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][True],  # Smooth ON | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Type | ||||
| @@ -150,17 +158,18 @@ def test_set_bold(): | ||||
|     instance.set(bold=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][True],  # Bold ON | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][True],  # Bold ON | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_set_underline(): | ||||
| @@ -168,17 +177,18 @@ def test_set_underline(): | ||||
|     instance.set(underline=1) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][1],  # Underline ON, type 1 | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][1],  # Underline ON, type 1 | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_set_underline2(): | ||||
| @@ -186,59 +196,64 @@ def test_set_underline2(): | ||||
|     instance.set(underline=2) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][2],  # Underline ON, type 2 | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][2],  # Underline ON, type 2 | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Align | ||||
|  | ||||
|  | ||||
| def test_align_center(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(align='center') | ||||
|     instance.set(align="center") | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['center'],  # Align center | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["center"],  # Align center | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| def test_align_right(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(align='right') | ||||
|     instance.set(align="right") | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['right'],  # Align right | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["right"],  # Align right | ||||
|         TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Densities | ||||
|  | ||||
|  | ||||
| def test_densities(): | ||||
|  | ||||
|     for density in range(8): | ||||
| @@ -246,35 +261,38 @@ def test_densities(): | ||||
|         instance.set(density=density) | ||||
|  | ||||
|         expected_sequence = ( | ||||
|             TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|             TXT_STYLE['flip'][False],  # Flip OFF | ||||
|             TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|             TXT_STYLE['bold'][False],  # Bold OFF | ||||
|             TXT_STYLE['underline'][0],  # Underline OFF | ||||
|             SET_FONT(b'\x00'),  # Default font | ||||
|             TXT_STYLE['align']['left'],  # Align left | ||||
|             TXT_STYLE['density'][density],  # Custom density from 0 to 8 | ||||
|             TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|             TXT_NORMAL, | ||||
|             TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|             TXT_STYLE["flip"][False],  # Flip OFF | ||||
|             TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|             TXT_STYLE["bold"][False],  # Bold OFF | ||||
|             TXT_STYLE["underline"][0],  # Underline OFF | ||||
|             SET_FONT(b"\x00"),  # Default font | ||||
|             TXT_STYLE["align"]["left"],  # Align left | ||||
|             TXT_STYLE["density"][density],  # Custom density from 0 to 8 | ||||
|             TXT_STYLE["invert"][False],  # Inverted OFF | ||||
|         ) | ||||
|  | ||||
|         assert(instance.output == b''.join(expected_sequence)) | ||||
|         assert instance.output == b"".join(expected_sequence) | ||||
|  | ||||
|  | ||||
| # Invert | ||||
|  | ||||
|  | ||||
| def test_invert(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(invert=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][True]  # Inverted ON | ||||
|         TXT_NORMAL, | ||||
|         TXT_STYLE["size"]["normal"],  # Normal text size | ||||
|         TXT_STYLE["flip"][False],  # Flip OFF | ||||
|         TXT_STYLE["smooth"][False],  # Smooth OFF | ||||
|         TXT_STYLE["bold"][False],  # Bold OFF | ||||
|         TXT_STYLE["underline"][0],  # Underline OFF | ||||
|         SET_FONT(b"\x00"),  # Default font | ||||
|         TXT_STYLE["align"]["left"],  # Align left | ||||
|         TXT_STYLE["invert"][True],  # Inverted ON | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|     assert instance.output == b"".join(expected_sequence) | ||||
|   | ||||
| @@ -1,16 +1,25 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import barcode.errors | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| def test_soft_barcode(): | ||||
|     """just execute soft_barcode | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.soft_barcode("ean8", "1234") | ||||
| @pytest.fixture | ||||
| def instance(): | ||||
|     return printer.Dummy() | ||||
|  | ||||
|  | ||||
| def test_soft_barcode_ean8_invalid(instance): | ||||
|     """test with an invalid barcode""" | ||||
|     with pytest.raises(barcode.errors.BarcodeError): | ||||
|         instance.soft_barcode("ean8", "1234") | ||||
|  | ||||
|  | ||||
| def test_soft_barcode_ean8(instance): | ||||
|     """test with a valid ean8 barcode""" | ||||
|     instance.soft_barcode("ean8", "1234567") | ||||
|  | ||||
|  | ||||
| def test_soft_barcode_ean8_nocenter(instance): | ||||
|     instance.soft_barcode("ean8", "1234567", center=False) | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
| import mock | ||||
| @@ -20,13 +16,12 @@ from escpos.printer import Dummy | ||||
|  | ||||
|  | ||||
| def get_printer(): | ||||
|     return Dummy(magic_encode_args={'disabled': True, 'encoding': 'CP437'}) | ||||
|     return Dummy(magic_encode_args={"disabled": True, "encoding": "CP437"}) | ||||
|  | ||||
|  | ||||
| @given(text=st.text()) | ||||
| def test_text(text): | ||||
|     """Test that text() calls the MagicEncode object. | ||||
|     """ | ||||
|     """Test that text() calls the MagicEncode object.""" | ||||
|     instance = get_printer() | ||||
|     instance.magic.write = mock.Mock() | ||||
|     instance.text(text) | ||||
| @@ -36,30 +31,32 @@ def test_text(text): | ||||
| def test_block_text(): | ||||
|     printer = get_printer() | ||||
|     printer.block_text( | ||||
|         "All the presidents men were eating falafel for breakfast.", font='a') | ||||
|     assert printer.output == \ | ||||
|         b'All the presidents men were eating falafel\nfor breakfast.' | ||||
|         "All the presidents men were eating falafel for breakfast.", font="a" | ||||
|     ) | ||||
|     assert ( | ||||
|         printer.output == b"All the presidents men were eating falafel\nfor breakfast." | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_textln(): | ||||
|     printer = get_printer() | ||||
|     printer.textln('hello, world') | ||||
|     assert printer.output == b'hello, world\n' | ||||
|     printer.textln("hello, world") | ||||
|     assert printer.output == b"hello, world\n" | ||||
|  | ||||
|  | ||||
| def test_textln_empty(): | ||||
|     printer = get_printer() | ||||
|     printer.textln() | ||||
|     assert printer.output == b'\n' | ||||
|     assert printer.output == b"\n" | ||||
|  | ||||
|  | ||||
| def test_ln(): | ||||
|     printer = get_printer() | ||||
|     printer.ln() | ||||
|     assert printer.output == b'\n' | ||||
|     assert printer.output == b"\n" | ||||
|  | ||||
|  | ||||
| def test_multiple_ln(): | ||||
|     printer = get_printer() | ||||
|     printer.ln(3) | ||||
|     assert printer.output == b'\n\n\n' | ||||
|     assert printer.output == b"\n\n\n" | ||||
|   | ||||
| @@ -5,13 +5,13 @@ from escpos.printer import Dummy | ||||
| def test_line_spacing_code_gen(): | ||||
|     printer = Dummy() | ||||
|     printer.line_spacing(10) | ||||
|     assert printer.output == b'\x1b3\n' | ||||
|     assert printer.output == b"\x1b3\n" | ||||
|  | ||||
|  | ||||
| def test_line_spacing_rest(): | ||||
|     printer = Dummy() | ||||
|     printer.line_spacing() | ||||
|     assert printer.output == b'\x1b2' | ||||
|     assert printer.output == b"\x1b2" | ||||
|  | ||||
|  | ||||
| def test_line_spacing_error_handling(): | ||||
|   | ||||
| @@ -15,57 +15,67 @@ def test_image_black(): | ||||
|     """ | ||||
|     Test rendering solid black image | ||||
|     """ | ||||
|     for img_format in ['png', 'jpg', 'gif']: | ||||
|         _load_and_check_img('canvas_black.' + img_format, 1, 1, b'\x80', [b'\x80']) | ||||
|     for img_format in ["png", "jpg", "gif"]: | ||||
|         _load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"]) | ||||
|  | ||||
|  | ||||
| def test_image_black_transparent(): | ||||
|     """ | ||||
|     Test rendering black/transparent image | ||||
|     """ | ||||
|     for img_format in ['png', 'gif']: | ||||
|         _load_and_check_img('black_transparent.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80']) | ||||
|     for img_format in ["png", "gif"]: | ||||
|         _load_and_check_img( | ||||
|             "black_transparent." + img_format, 2, 2, b"\xc0\x00", [b"\x80\x80"] | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def test_image_black_white(): | ||||
|     """ | ||||
|     Test rendering black/white image | ||||
|     """ | ||||
|     for img_format in ['png', 'jpg', 'gif']: | ||||
|         _load_and_check_img('black_white.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80']) | ||||
|     for img_format in ["png", "jpg", "gif"]: | ||||
|         _load_and_check_img( | ||||
|             "black_white." + img_format, 2, 2, b"\xc0\x00", [b"\x80\x80"] | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def test_image_white(): | ||||
|     """ | ||||
|     Test rendering solid white image | ||||
|     """ | ||||
|     for img_format in ['png', 'jpg', 'gif']: | ||||
|         _load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00']) | ||||
|     for img_format in ["png", "jpg", "gif"]: | ||||
|         _load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"]) | ||||
|  | ||||
|  | ||||
| def test_split(): | ||||
|     """ | ||||
|     test whether the split-function works as expected | ||||
|     """ | ||||
|     im = EscposImage('test/resources/black_white.png') | ||||
|     im = EscposImage("test/resources/black_white.png") | ||||
|     (upper_part, lower_part) = im.split(1) | ||||
|     upper_part = EscposImage(upper_part) | ||||
|     lower_part = EscposImage(lower_part) | ||||
|     assert(upper_part.width == lower_part.width == 2) | ||||
|     assert(upper_part.height == lower_part.height == 1) | ||||
|     assert(upper_part.to_raster_format() == b'\xc0') | ||||
|     assert(lower_part.to_raster_format() == b'\x00') | ||||
|     assert upper_part.width == lower_part.width == 2 | ||||
|     assert upper_part.height == lower_part.height == 1 | ||||
|     assert upper_part.to_raster_format() == b"\xc0" | ||||
|     assert lower_part.to_raster_format() == b"\x00" | ||||
|  | ||||
|  | ||||
| def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected): | ||||
| def _load_and_check_img( | ||||
|     filename, | ||||
|     width_expected, | ||||
|     height_expected, | ||||
|     raster_format_expected, | ||||
|     column_format_expected, | ||||
| ): | ||||
|     """ | ||||
|     Load an image, and test whether raster & column formatted output, sizes, etc match expectations. | ||||
|     """ | ||||
|     im = EscposImage('test/resources/' + filename) | ||||
|     assert(im.width == width_expected) | ||||
|     assert(im.height == height_expected) | ||||
|     assert(im.to_raster_format() == raster_format_expected) | ||||
|     i = 0  | ||||
|     im = EscposImage("test/resources/" + filename) | ||||
|     assert im.width == width_expected | ||||
|     assert im.height == height_expected | ||||
|     assert im.to_raster_format() == raster_format_expected | ||||
|     i = 0 | ||||
|     for row in im.to_column_format(False): | ||||
|         assert(row == column_format_expected[i]) | ||||
|         assert row == column_format_expected[i] | ||||
|         i += 1 | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
|  | ||||
| @@ -18,4 +14,4 @@ import escpos.printer as printer | ||||
| def test_instantiation(): | ||||
|     """test the instantiation of a escpos-printer class and basic printing""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.text('This is a test\n') | ||||
|     instance.text("This is a test\n") | ||||
|   | ||||
| @@ -8,10 +8,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
| from nose.tools import raises, assert_raises | ||||
| @@ -28,17 +24,17 @@ class TestEncoder: | ||||
|     """ | ||||
|  | ||||
|     def test_can_encode(self): | ||||
|         assert not Encoder({'CP437': 1}).can_encode('CP437', u'€') | ||||
|         assert Encoder({'CP437': 1}).can_encode('CP437', u'á') | ||||
|         assert not Encoder({'foobar': 1}).can_encode('foobar', 'a') | ||||
|         assert not Encoder({"CP437": 1}).can_encode("CP437", u"€") | ||||
|         assert Encoder({"CP437": 1}).can_encode("CP437", u"á") | ||||
|         assert not Encoder({"foobar": 1}).can_encode("foobar", "a") | ||||
|  | ||||
|     def test_find_suitable_encoding(self): | ||||
|         assert not Encoder({'CP437': 1}).find_suitable_encoding(u'€') | ||||
|         assert Encoder({'CP858': 1}).find_suitable_encoding(u'€') == 'CP858' | ||||
|         assert not Encoder({"CP437": 1}).find_suitable_encoding(u"€") | ||||
|         assert Encoder({"CP858": 1}).find_suitable_encoding(u"€") == "CP858" | ||||
|  | ||||
|     @raises(ValueError) | ||||
|     def test_get_encoding(self): | ||||
|         Encoder({}).get_encoding_name('latin1') | ||||
|         Encoder({}).get_encoding_name("latin1") | ||||
|  | ||||
|  | ||||
| class TestMagicEncode: | ||||
| @@ -61,50 +57,50 @@ class TestMagicEncode: | ||||
|                 MagicEncode(driver, disabled=True) | ||||
|  | ||||
|     class TestWriteWithEncoding: | ||||
|  | ||||
|         def test_init_from_none(self, driver): | ||||
|             encode = MagicEncode(driver, encoding=None) | ||||
|             encode.write_with_encoding('CP858', '€ ist teuro.') | ||||
|             assert driver.output == b'\x1bt\x13\xd5 ist teuro.' | ||||
|             encode.write_with_encoding("CP858", "€ ist teuro.") | ||||
|             assert driver.output == b"\x1bt\x13\xd5 ist teuro." | ||||
|  | ||||
|         def test_change_from_another(self, driver): | ||||
|             encode = MagicEncode(driver, encoding='CP437') | ||||
|             encode.write_with_encoding('CP858', '€ ist teuro.') | ||||
|             assert driver.output == b'\x1bt\x13\xd5 ist teuro.' | ||||
|             encode = MagicEncode(driver, encoding="CP437") | ||||
|             encode.write_with_encoding("CP858", "€ ist teuro.") | ||||
|             assert driver.output == b"\x1bt\x13\xd5 ist teuro." | ||||
|  | ||||
|         def test_no_change(self, driver): | ||||
|             encode = MagicEncode(driver, encoding='CP858') | ||||
|             encode.write_with_encoding('CP858', '€ ist teuro.') | ||||
|             assert driver.output == b'\xd5 ist teuro.' | ||||
|             encode = MagicEncode(driver, encoding="CP858") | ||||
|             encode.write_with_encoding("CP858", "€ ist teuro.") | ||||
|             assert driver.output == b"\xd5 ist teuro." | ||||
|  | ||||
|     class TestWrite: | ||||
|  | ||||
|         def test_write(self, driver): | ||||
|             encode = MagicEncode(driver) | ||||
|             encode.write('€ ist teuro.') | ||||
|             assert driver.output == b'\x1bt\x0f\xa4 ist teuro.' | ||||
|             encode.write("€ ist teuro.") | ||||
|             assert driver.output == b"\x1bt\x0f\xa4 ist teuro." | ||||
|  | ||||
|         def test_write_disabled(self, driver): | ||||
|             encode = MagicEncode(driver, encoding='CP437', disabled=True) | ||||
|             encode.write('€ ist teuro.') | ||||
|             assert driver.output == b'? ist teuro.' | ||||
|             encode = MagicEncode(driver, encoding="CP437", disabled=True) | ||||
|             encode.write("€ ist teuro.") | ||||
|             assert driver.output == b"? ist teuro." | ||||
|  | ||||
|         def test_write_no_codepage(self, driver): | ||||
|             encode = MagicEncode( | ||||
|                 driver, defaultsymbol="_", encoder=Encoder({'CP437': 1}), | ||||
|                 encoding='CP437') | ||||
|             encode.write(u'€ ist teuro.') | ||||
|             assert driver.output == b'_ ist teuro.' | ||||
|                 driver, | ||||
|                 defaultsymbol="_", | ||||
|                 encoder=Encoder({"CP437": 1}), | ||||
|                 encoding="CP437", | ||||
|             ) | ||||
|             encode.write(u"€ ist teuro.") | ||||
|             assert driver.output == b"_ ist teuro." | ||||
|  | ||||
|     class TestForceEncoding: | ||||
|  | ||||
|         def test(self, driver): | ||||
|             encode = MagicEncode(driver) | ||||
|             encode.force_encoding('CP437') | ||||
|             assert driver.output == b'\x1bt\x00' | ||||
|             encode.force_encoding("CP437") | ||||
|             assert driver.output == b"\x1bt\x00" | ||||
|  | ||||
|             encode.write('€ ist teuro.') | ||||
|             assert driver.output == b'\x1bt\x00? ist teuro.' | ||||
|             encode.write("€ ist teuro.") | ||||
|             assert driver.output == b"\x1bt\x00? ist teuro." | ||||
|  | ||||
|  | ||||
| try: | ||||
| @@ -123,5 +119,5 @@ class TestKatakana: | ||||
|         encode_katakana(text) | ||||
|  | ||||
|     def test_result(self): | ||||
|         assert encode_katakana('カタカナ') == b'\xb6\xc0\xb6\xc5' | ||||
|         assert encode_katakana("あいうえお") == b'\xb1\xb2\xb3\xb4\xb5' | ||||
|         assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5" | ||||
|         assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5" | ||||
|   | ||||
| @@ -8,10 +8,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| @@ -22,16 +18,16 @@ from hypothesis.strategies import text | ||||
| import escpos.printer as printer | ||||
|  | ||||
| if six.PY3: | ||||
|     mock_open_call = 'builtins.open' | ||||
|     mock_open_call = "builtins.open" | ||||
| else: | ||||
|     mock_open_call = '__builtin__.open' | ||||
|     mock_open_call = "__builtin__.open" | ||||
|  | ||||
|  | ||||
| @pytest.mark.skip("this test is broken and has to be fixed or discarded") | ||||
| @given(path=text()) | ||||
| def test_load_file_printer(mocker, path): | ||||
|     """test the loading of the file-printer""" | ||||
|     mock_escpos = mocker.patch('escpos.escpos.Escpos.__init__') | ||||
|     mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__") | ||||
|     mock_open = mocker.patch(mock_open_call) | ||||
|     printer.File(devfile=path) | ||||
|     assert mock_escpos.called | ||||
| @@ -42,9 +38,9 @@ def test_load_file_printer(mocker, path): | ||||
| @given(txt=text()) | ||||
| def test_auto_flush(mocker, txt): | ||||
|     """test auto_flush in file-printer""" | ||||
|     mock_escpos = mocker.patch('escpos.escpos.Escpos.__init__') | ||||
|     mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__") | ||||
|     mock_open = mocker.patch(mock_open_call) | ||||
|     mock_device = mocker.patch.object(printer.File, 'device') | ||||
|     mock_device = mocker.patch.object(printer.File, "device") | ||||
|  | ||||
|     p = printer.File(auto_flush=False) | ||||
|     # inject the mocked device-object | ||||
| @@ -64,7 +60,7 @@ def test_auto_flush(mocker, txt): | ||||
| def test_flush_on_close(mocker, txt): | ||||
|     """test flush on close in file-printer""" | ||||
|     mock_open = mocker.patch(mock_open_call) | ||||
|     mock_device = mocker.patch.object(printer.File, 'device') | ||||
|     mock_device = mocker.patch.object(printer.File, "device") | ||||
|  | ||||
|     p = printer.File(auto_flush=False) | ||||
|     # inject the mocked device-object | ||||
|   | ||||
							
								
								
									
										24
									
								
								test/test_printer_network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/test_printer_network.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #!/usr/bin/python | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import pytest | ||||
| import mock | ||||
| import socket | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def instance(): | ||||
|     socket.socket.connect = mock.Mock() | ||||
|     return printer.Network("localhost") | ||||
|  | ||||
|  | ||||
| def test_close_without_open(instance): | ||||
|     """try to close without opening (should fail gracefully) | ||||
|  | ||||
|     Currently we never open from our fixture, so calling close once | ||||
|     should be enough. In the future this might not be enough, | ||||
|     therefore we have to close twice in order to provoke an error | ||||
|     (if possible, this should not raise) | ||||
|     """ | ||||
|     instance.close() | ||||
|     instance.close() | ||||
| @@ -4,35 +4,33 @@ from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile | ||||
|  | ||||
| @pytest.fixture | ||||
| def profile(): | ||||
|     return get_profile('default') | ||||
|     return get_profile("default") | ||||
|  | ||||
|  | ||||
| class TestBaseProfile: | ||||
|     """Test the `BaseProfile` class. | ||||
|     """ | ||||
|     """Test the `BaseProfile` class.""" | ||||
|  | ||||
|     def test_get_font(self, profile): | ||||
|         with pytest.raises(NotSupported): | ||||
|             assert profile.get_font('3') | ||||
|             assert profile.get_font("3") | ||||
|         assert profile.get_font(1) == 1 | ||||
|         assert profile.get_font('a') == 0 | ||||
|         assert profile.get_font("a") == 0 | ||||
|  | ||||
|     def test_supports(self, profile): | ||||
|         assert not profile.supports('asdf asdf') | ||||
|         assert not profile.supports("asdf asdf") | ||||
|         assert profile.supports(BARCODE_B) | ||||
|  | ||||
|     def test_get_columns(self, profile): | ||||
|         assert profile.get_columns('a') > 5 | ||||
|         assert profile.get_columns("a") > 5 | ||||
|         with pytest.raises(NotSupported): | ||||
|             assert profile.get_columns('asdfasdf') | ||||
|             assert profile.get_columns("asdfasdf") | ||||
|  | ||||
|  | ||||
| class TestCustomProfile: | ||||
|     """Test custom profile options with the `Profile` class. | ||||
|     """ | ||||
|     """Test custom profile options with the `Profile` class.""" | ||||
|  | ||||
|     def test_columns(self): | ||||
|         assert Profile(columns=10).get_columns('sdfasdf') == 10 | ||||
|         assert Profile(columns=10).get_columns("sdfasdf") == 10 | ||||
|  | ||||
|     def test_features(self): | ||||
|         assert Profile(features={'foo': True}).supports('foo') | ||||
|         assert Profile(features={"foo": True}).supports("foo") | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
| import escpos | ||||
|   | ||||
| @@ -7,10 +7,6 @@ | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import escpos.escpos as escpos | ||||
| @@ -20,5 +16,5 @@ def test_with_statement(): | ||||
|     """Use with statement""" | ||||
|     dummy_printer = printer.Dummy() | ||||
|     with escpos.EscposIO(dummy_printer) as p: | ||||
|             p.writelines('Some text.\n') | ||||
|         p.writelines("Some text.\n") | ||||
|     # TODO extend these tests as they don't really do anything at the moment | ||||
|   | ||||
							
								
								
									
										12
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| [tox] | ||||
| envlist = py35, py36, py37, py38, docs, flake8 | ||||
| envlist = py35, py36, py37, py38, py39, py310, docs, flake8 | ||||
|  | ||||
| [gh-actions] | ||||
| python = | ||||
| @@ -7,6 +7,8 @@ python = | ||||
|     3.6: py36 | ||||
|     3.7: py37 | ||||
|     3.8: py38 | ||||
|     3.9: py39 | ||||
|     3.10: py310 | ||||
|  | ||||
| [testenv] | ||||
| deps = nose | ||||
| @@ -18,16 +20,18 @@ deps = nose | ||||
|        pytest-cov | ||||
|        pytest-mock | ||||
|        hypothesis>4 | ||||
|        viivakoodi | ||||
|        python-barcode | ||||
| commands = pytest --cov escpos | ||||
| passenv = ESCPOS_CAPABILITIES_PICKLE_DIR ESCPOS_CAPABILITIES_FILE CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_* | ||||
|  | ||||
| [testenv:docs] | ||||
| basepython = python | ||||
| changedir = doc | ||||
| deps = sphinx>=1.5.1 | ||||
| deps = sphinx>=3.0.0 | ||||
|        setuptools_scm | ||||
|        viivakoodi | ||||
|        python-barcode | ||||
|        sphinxcontrib-spelling>=7.2.0 | ||||
|        sphinx_rtd_theme | ||||
| commands = sphinx-build -W -b html -d {envtmpdir}/doctrees .  {envtmpdir}/html | ||||
|  | ||||
| [testenv:flake8] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Benito López
					Benito López