Merge branch 'master' into debian/jessie
							
								
								
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,2 @@
 | 
			
		||||
[run]
 | 
			
		||||
branch = True
 | 
			
		||||
							
								
								
									
										26
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
			
		||||
<!--
 | 
			
		||||
Please feel free to delete any sections that aren't relevant.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<!-- mark with x between the [ ] -->
 | 
			
		||||
I have:
 | 
			
		||||
- [ ] searched open and closed issues for duplicates
 | 
			
		||||
 | 
			
		||||
### Bug description
 | 
			
		||||
 | 
			
		||||
### Steps to reproduce
 | 
			
		||||
- add your steps here
 | 
			
		||||
- as a list
 | 
			
		||||
- using hyphens
 | 
			
		||||
 | 
			
		||||
### Device info
 | 
			
		||||
<!-- Replace examples with your info -->
 | 
			
		||||
 **Printer:** Manufacturer Model XVI
 | 
			
		||||
 | 
			
		||||
<!-- since version 2.0.1 you can type 'python-escpos version' in your shell.
 | 
			
		||||
Alternatively you could use '__version__' in module escpos. -->
 | 
			
		||||
 **python-escpos version:** 0.0.0
 | 
			
		||||
 | 
			
		||||
 **python version:** 0.0
 | 
			
		||||
 | 
			
		||||
 **operating system:**
 | 
			
		||||
							
								
								
									
										10
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
### Contributor checklist
 | 
			
		||||
<!-- mark with x between the brackets -->
 | 
			
		||||
- [ ] I have read the CONTRIBUTING.rst
 | 
			
		||||
- [ ] I have tested my contribution on these devices:
 | 
			
		||||
 * e.g. Epson TM-T88II
 | 
			
		||||
- [ ] My contribution is ready to be merged as is
 | 
			
		||||
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
### Description
 | 
			
		||||
							
								
								
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,9 +1,30 @@
 | 
			
		||||
# python temporary files
 | 
			
		||||
*.pyc
 | 
			
		||||
 | 
			
		||||
# editor autosaves
 | 
			
		||||
# editor autosaves, data and file browser files
 | 
			
		||||
$~
 | 
			
		||||
.idea/
 | 
			
		||||
.directory
 | 
			
		||||
.cache/
 | 
			
		||||
 | 
			
		||||
# temporary data
 | 
			
		||||
temp
 | 
			
		||||
 | 
			
		||||
# packaging and testing
 | 
			
		||||
.tox/
 | 
			
		||||
*.egg-info/
 | 
			
		||||
.eggs/
 | 
			
		||||
*.egg
 | 
			
		||||
build/
 | 
			
		||||
dist/
 | 
			
		||||
.coverage
 | 
			
		||||
src/escpos/version.py
 | 
			
		||||
.hypothesis
 | 
			
		||||
 | 
			
		||||
# testing temporary directories
 | 
			
		||||
test/test-cli-output/
 | 
			
		||||
 | 
			
		||||
# vim swap files
 | 
			
		||||
*.swp
 | 
			
		||||
*.swn
 | 
			
		||||
*.swo
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
[submodule "capabilities-data"]
 | 
			
		||||
	path = capabilities-data
 | 
			
		||||
	url = https://github.com/receipt-print-hq/escpos-printer-db.git
 | 
			
		||||
							
								
								
									
										59
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
language: python
 | 
			
		||||
sudo: false
 | 
			
		||||
cache: pip
 | 
			
		||||
addons:
 | 
			
		||||
  apt:
 | 
			
		||||
    packages:
 | 
			
		||||
      - graphviz
 | 
			
		||||
matrix:
 | 
			
		||||
  include:
 | 
			
		||||
    - python: 2.7
 | 
			
		||||
      env: TOXENV=py27
 | 
			
		||||
    - python: 3.3
 | 
			
		||||
      env: TOXENV=py33
 | 
			
		||||
    - python: 3.4
 | 
			
		||||
      env: TOXENV=py34
 | 
			
		||||
    - python: 3.5
 | 
			
		||||
      env: TOXENV=py35
 | 
			
		||||
    - python: 3.6
 | 
			
		||||
      env: TOXENV=py36
 | 
			
		||||
    - python: 3.6-dev
 | 
			
		||||
      env: TOXENV=py36
 | 
			
		||||
    - python: nightly
 | 
			
		||||
      env: TOXENV=py37
 | 
			
		||||
    - python: pypy
 | 
			
		||||
      env: TOXENV=pypy
 | 
			
		||||
    - python: pypy3
 | 
			
		||||
      env: TOXENV=pypy3
 | 
			
		||||
    - python: 2.7
 | 
			
		||||
      env: TOXENV=docs
 | 
			
		||||
    - python: 2.7
 | 
			
		||||
      env: TOXENV=flake8
 | 
			
		||||
    - python: 3.6
 | 
			
		||||
      env: TOXENV=flake8
 | 
			
		||||
  allow_failures:
 | 
			
		||||
    - python: 3.6-dev
 | 
			
		||||
    - python: nightly
 | 
			
		||||
    - python: pypy3
 | 
			
		||||
before_install:
 | 
			
		||||
    - pip install tox codecov 'sphinx>=1.5.1'
 | 
			
		||||
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.5"
 | 
			
		||||
							
								
								
									
										43
									
								
								CHANGELOG
									
									
									
									
									
								
							
							
						
						@@ -1,43 +0,0 @@
 | 
			
		||||
CHANGELOG
 | 
			
		||||
 | 
			
		||||
* 2012-11-15 - Version 1.0
 | 
			
		||||
- Issue #2: Added ethernet support
 | 
			
		||||
- Issue #3: Added compatibility with libusb-1.0.1
 | 
			
		||||
- Issue #4: Fixed typo in escpos.py
 | 
			
		||||
 | 
			
		||||
* 2013-03-14 - Version 1.0.1
 | 
			
		||||
- Issue #8: Fixed set font
 | 
			
		||||
- Added QR support
 | 
			
		||||
 | 
			
		||||
* 2013-12-30 - Version 1.0.2
 | 
			
		||||
- Issue #5: Fixed vertical tab
 | 
			
		||||
- Issue #9: Fixed identation inconsistence
 | 
			
		||||
 | 
			
		||||
* 2014-02-23 - Version 1.0.3
 | 
			
		||||
- Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com)
 | 
			
		||||
- Added exception for PIL import
 | 
			
		||||
 | 
			
		||||
* 2014-05-20 - Version 1.0.4
 | 
			
		||||
- Issue #20: Added Density support (Sent by thomas.erbacher@ragapack.de)
 | 
			
		||||
- Added charcode tables
 | 
			
		||||
- Fixed Horizontal Tab
 | 
			
		||||
- Fixed code tabulators
 | 
			
		||||
 | 
			
		||||
* 2015-04-21 - Version 1.0.5
 | 
			
		||||
- Merge pull request #45 from Krispy2009/master
 | 
			
		||||
  . Raising the right error when wrong charcode is used
 | 
			
		||||
  . Sent by Kristi <Krispy2009@gmail.com>
 | 
			
		||||
 | 
			
		||||
* 2015-07-06 - Version 1.0.6
 | 
			
		||||
- Merge pull request #53 from ldos/master
 | 
			
		||||
  . Extended params for serial printers
 | 
			
		||||
  . Sent by ldos <cafeteria.ldosalzira@gmail.com>
 | 
			
		||||
 | 
			
		||||
* 2015-08-22 - Version 1.0.7
 | 
			
		||||
- Issue #57: Fixed transparent images
 | 
			
		||||
 | 
			
		||||
* 2015-10-27 - Version 1.0.8
 | 
			
		||||
- Merge pull request #59 from zouppen/master
 | 
			
		||||
  . Support for images vertically longer than 256 pixels
 | 
			
		||||
  . Sent by Joel Lehtonen <joel.lehtonen@koodilehto.fi>
 | 
			
		||||
- Updated README
 | 
			
		||||
							
								
								
									
										218
									
								
								CHANGELOG.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,218 @@
 | 
			
		||||
*********
 | 
			
		||||
Changelog
 | 
			
		||||
*********
 | 
			
		||||
 | 
			
		||||
2017-03-29 - Version 3.0a1 - "Headcrash"
 | 
			
		||||
----------------------------------------
 | 
			
		||||
This release is the second alpha release of the new version 3.0. Please
 | 
			
		||||
be aware that the API will still change until v3.0 is released.
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- automatically upload releases to GitHub
 | 
			
		||||
- add environment variable ESCPOS_CAPABILITIES_FILE
 | 
			
		||||
- automatically handle cases where full cut or partial cut is not available
 | 
			
		||||
- add print_and_feed
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Sam Cheng
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
- Dmytro Katyukha
 | 
			
		||||
 | 
			
		||||
2017-01-31 - Version 3.0a - "Grey Area"
 | 
			
		||||
---------------------------------------
 | 
			
		||||
This release is the first alpha release of the new version 3.0. Please
 | 
			
		||||
be aware that the API will still change until v3.0 is released.
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
 | 
			
		||||
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
 | 
			
		||||
  `escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
 | 
			
		||||
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
 | 
			
		||||
- as an alternative you can force the codepage with the old API
 | 
			
		||||
- updated and improved documentation
 | 
			
		||||
- changed constructor of main class due to introduction of capablities
 | 
			
		||||
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
 | 
			
		||||
  on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
 | 
			
		||||
- add support for custom cash drawer sequence
 | 
			
		||||
- enforce flake8 on the src-files, test py36 and py37 on travis
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Michael Billington
 | 
			
		||||
- Michael Elsdörfer
 | 
			
		||||
- Patrick Kanzler (with code by Frédéric Van der Essen)
 | 
			
		||||
- Asuki Kono
 | 
			
		||||
- Benito López
 | 
			
		||||
- Curtis // mashedkeyboard
 | 
			
		||||
- Thijs Triemstra
 | 
			
		||||
- ysuolmai
 | 
			
		||||
 | 
			
		||||
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
 | 
			
		||||
------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- fix improper API-use in qrcode()
 | 
			
		||||
- change setup.py shebang to make it compatible with virtualenvs.
 | 
			
		||||
- add constants for sheet mode and colors
 | 
			
		||||
- support changing the linespacing
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Michael Elsdörfer
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
 | 
			
		||||
2016-08-10 - Version 2.1.3 - "Ethics Gradient"
 | 
			
		||||
----------------------------------------------
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- configure readthedocs and travis
 | 
			
		||||
- update doc with hint on image preprocessing
 | 
			
		||||
- add fix for printing large images (by splitting them into multiple images)
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
 | 
			
		||||
2016-08-02 - Version 2.1.2 - "Death and Gravity"
 | 
			
		||||
------------------------------------------------
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- fix File-printer: flush after every call of _raw()
 | 
			
		||||
- fix lists in documentation
 | 
			
		||||
- fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
 | 
			
		||||
2016-08-02 - Version 2.1.1 - "Contents May Differ"
 | 
			
		||||
--------------------------------------------------
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- rename variable interface in USB-class to timeout
 | 
			
		||||
- add support for hypothesis and move pypy3 to the allowed failures (pypy3 is not supported by hypothesis)
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
- Renato Lorenzi
 | 
			
		||||
 | 
			
		||||
2016-07-23 - Version 2.1.0 - "But Who's Counting?"
 | 
			
		||||
--------------------------------------------------
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- packaging: configured the coverage-analysis codecov.io
 | 
			
		||||
- GitHub: improved issues-template
 | 
			
		||||
- documentation: add troubleshooting tip to network-interface
 | 
			
		||||
- the module, cli and documentation is now aware of the version of python-escpos
 | 
			
		||||
- the cli does now support basic tabcompletion
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
 | 
			
		||||
2016-06-24 - Version 2.0.0 - "Attitude Adjuster"
 | 
			
		||||
------------------------------------------------
 | 
			
		||||
 | 
			
		||||
This version is based on the original version of python-escpos by Manuel F Martinez. However, many contributions have
 | 
			
		||||
greatly improved the old codebase. Since this version does not completely match the interface of the version published
 | 
			
		||||
on PyPi and has many improvements, it will be released as version 2.0.0.
 | 
			
		||||
 | 
			
		||||
changes
 | 
			
		||||
^^^^^^^
 | 
			
		||||
- refactor complete code in order to be compatible with Python 2 and 3
 | 
			
		||||
- modernize packaging
 | 
			
		||||
- add testing and CI
 | 
			
		||||
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cashdraw and structure
 | 
			
		||||
- improve the documentation
 | 
			
		||||
- extend support of barcode-codes to type B
 | 
			
		||||
- add function to disable panel-buttons
 | 
			
		||||
- the text-functions are now intended for unicode, the driver will automatically encode the string based on the selected
 | 
			
		||||
  codepage
 | 
			
		||||
- the image-functions are now much more flexible
 | 
			
		||||
- added a CLI
 | 
			
		||||
- restructured the constants
 | 
			
		||||
 | 
			
		||||
contributors
 | 
			
		||||
^^^^^^^^^^^^
 | 
			
		||||
- Thomas van den Berg
 | 
			
		||||
- Michael Billington
 | 
			
		||||
- Nate Bookham
 | 
			
		||||
- Davis Goglin
 | 
			
		||||
- Christoph Heuel
 | 
			
		||||
- Patrick Kanzler
 | 
			
		||||
- Qian LinFeng
 | 
			
		||||
 | 
			
		||||
2016-01-24 - Version 1.0.9
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- fix constant definition for PC1252
 | 
			
		||||
- move documentation to Sphinx
 | 
			
		||||
 | 
			
		||||
2015-10-27 - Version 1.0.8
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Merge pull request #59 from zouppen/master
 | 
			
		||||
    - Support for images vertically longer than 256 pixels
 | 
			
		||||
    - Sent by Joel Lehtonen <joel.lehtonen@koodilehto.fi>
 | 
			
		||||
- Updated README
 | 
			
		||||
 | 
			
		||||
2015-08-22 - Version 1.0.7
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #57: Fixed transparent images
 | 
			
		||||
 | 
			
		||||
2015-07-06 - Version 1.0.6
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Merge pull request #53 from ldos/master
 | 
			
		||||
    - Extended params for serial printers
 | 
			
		||||
    - Sent by ldos <cafeteria.ldosalzira@gmail.com>
 | 
			
		||||
 | 
			
		||||
2015-04-21 - Version 1.0.5
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Merge pull request #45 from Krispy2009/master
 | 
			
		||||
    - Raising the right error when wrong charcode is used
 | 
			
		||||
    - Sent by Kristi <Krispy2009@gmail.com>
 | 
			
		||||
 | 
			
		||||
2014-05-20 - Version 1.0.4
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #20: Added Density support (Sent by thomas.erbacher@ragapack.de)
 | 
			
		||||
- Added charcode tables
 | 
			
		||||
- Fixed Horizontal Tab
 | 
			
		||||
- Fixed code tabulators
 | 
			
		||||
 | 
			
		||||
2014-02-23 - Version 1.0.3
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com)
 | 
			
		||||
- Added exception for PIL import
 | 
			
		||||
 | 
			
		||||
2013-12-30 - Version 1.0.2
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #5: Fixed vertical tab
 | 
			
		||||
- Issue #9: Fixed identation inconsistence
 | 
			
		||||
 | 
			
		||||
2013-03-14 - Version 1.0.1
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #8: Fixed set font
 | 
			
		||||
- Added QR support
 | 
			
		||||
 | 
			
		||||
2012-11-15 - Version 1.0
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
- Issue #2: Added ethernet support
 | 
			
		||||
- Issue #3: Added compatibility with libusb-1.0.1
 | 
			
		||||
- Issue #4: Fixed typo in escpos.py
 | 
			
		||||
							
								
								
									
										79
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,79 @@
 | 
			
		||||
************
 | 
			
		||||
Contributing
 | 
			
		||||
************
 | 
			
		||||
 | 
			
		||||
This project is open to any kind of contribution. You can help with improving the documentation, adding fixes to the
 | 
			
		||||
code, providing test cases in code or as a description or just spreading the word. Please feel free to create an
 | 
			
		||||
issue or pull request.
 | 
			
		||||
In order to reduce the amount of work for everyone please try to adhere to good practice.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
well as possible.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
^^^^
 | 
			
		||||
This is not yet consequently done in every piece of code, but please try to ensure
 | 
			
		||||
that your code honors PEP8.
 | 
			
		||||
The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints.
 | 
			
		||||
 | 
			
		||||
GIT
 | 
			
		||||
^^^
 | 
			
		||||
The master-branch contains code that has been released to PyPi. A release is marked with a tag
 | 
			
		||||
corresponding to the version. Issues are closed when they have been resolved in the development-branch.
 | 
			
		||||
 | 
			
		||||
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`.
 | 
			
		||||
Name your branch to indicate what you are trying to achieve. Good branch names might
 | 
			
		||||
be `improve/text-handling`, `feature/enable-color-printing`.
 | 
			
		||||
 | 
			
		||||
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a
 | 
			
		||||
git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
 | 
			
		||||
easier to review, and improves the chance of your code being merged.
 | 
			
		||||
Please also make sure that before creating your PR, your branch is rebased on a recent commit or you merged a recent
 | 
			
		||||
commit into your branch. This way you can ensure that your PR is without merge conflicts.
 | 
			
		||||
 | 
			
		||||
Docstrings
 | 
			
		||||
^^^^^^^^^^
 | 
			
		||||
This project tries to have a good documentation.
 | 
			
		||||
Please add a docstring to every method and class. Have a look at existing methods and classes for the style.
 | 
			
		||||
We use basically standard rst-docstrings for Sphinx.
 | 
			
		||||
 | 
			
		||||
Test
 | 
			
		||||
^^^^
 | 
			
		||||
Try to write tests whenever possible. Our goal for the future is 100% coverage.
 | 
			
		||||
We are currently using `nose` but might change in the future.
 | 
			
		||||
You can copy the structure from other testcases. Please remember to adapt the docstrings.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
Thank you for your contribution!
 | 
			
		||||
							
								
								
									
										674
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						@@ -1,674 +0,0 @@
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 29 June 2007
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
 | 
			
		||||
  The GNU General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works.
 | 
			
		||||
 | 
			
		||||
  The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works.  By contrast,
 | 
			
		||||
the GNU General Public License is intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains free
 | 
			
		||||
software for all its users.  We, the Free Software Foundation, use the
 | 
			
		||||
GNU General Public License for most of our software; it applies also to
 | 
			
		||||
any other work released this way by its authors.  You can apply it to
 | 
			
		||||
your programs, too.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  To protect your rights, we need to prevent others from denying you
 | 
			
		||||
these rights or asking you to surrender the rights.  Therefore, you have
 | 
			
		||||
certain responsibilities if you distribute copies of the software, or if
 | 
			
		||||
you modify it: responsibilities to respect the freedom of others.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must pass on to the recipients the same
 | 
			
		||||
freedoms that you received.  You must make sure that they, too, receive
 | 
			
		||||
or can get the source code.  And you must show them these terms so they
 | 
			
		||||
know their rights.
 | 
			
		||||
 | 
			
		||||
  Developers that use the GNU GPL protect your rights with two steps:
 | 
			
		||||
(1) assert copyright on the software, and (2) offer you this License
 | 
			
		||||
giving you legal permission to copy, distribute and/or modify it.
 | 
			
		||||
 | 
			
		||||
  For the developers' and authors' protection, the GPL clearly explains
 | 
			
		||||
that there is no warranty for this free software.  For both users' and
 | 
			
		||||
authors' sake, the GPL requires that modified versions be marked as
 | 
			
		||||
changed, so that their problems will not be attributed erroneously to
 | 
			
		||||
authors of previous versions.
 | 
			
		||||
 | 
			
		||||
  Some devices are designed to deny users access to install or run
 | 
			
		||||
modified versions of the software inside them, although the manufacturer
 | 
			
		||||
can do so.  This is fundamentally incompatible with the aim of
 | 
			
		||||
protecting users' freedom to change the software.  The systematic
 | 
			
		||||
pattern of such abuse occurs in the area of products for individuals to
 | 
			
		||||
use, which is precisely where it is most unacceptable.  Therefore, we
 | 
			
		||||
have designed this version of the GPL to prohibit the practice for those
 | 
			
		||||
products.  If such problems arise substantially in other domains, we
 | 
			
		||||
stand ready to extend this provision to those domains in future versions
 | 
			
		||||
of the GPL, as needed to protect the freedom of users.
 | 
			
		||||
 | 
			
		||||
  Finally, every program is threatened constantly by software patents.
 | 
			
		||||
States should not allow patents to restrict development and use of
 | 
			
		||||
software on general-purpose computers, but in those that do, we wish to
 | 
			
		||||
avoid the special danger that patents applied to a free program could
 | 
			
		||||
make it effectively proprietary.  To prevent this, the GPL assures that
 | 
			
		||||
patents cannot be used to render the program non-free.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
                       TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
  0. Definitions.
 | 
			
		||||
 | 
			
		||||
  "This License" refers to version 3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
			
		||||
works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
  "The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License.  Each licensee is addressed as "you".  "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
  To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of an
 | 
			
		||||
exact copy.  The resulting work is called a "modified version" of the
 | 
			
		||||
earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
  A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
  To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy.  Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
  To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies.  Mere interaction with a user through
 | 
			
		||||
a computer network, with no transfer of a copy, is not conveying.
 | 
			
		||||
 | 
			
		||||
  An interactive user interface displays "Appropriate Legal Notices"
 | 
			
		||||
to the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License.  If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
  1. Source Code.
 | 
			
		||||
 | 
			
		||||
  The "source code" for a work means the preferred form of the work
 | 
			
		||||
for making modifications to it.  "Object code" means any non-source
 | 
			
		||||
form of a work.
 | 
			
		||||
 | 
			
		||||
  A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
  The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form.  A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
  The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities.  However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work.  For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source need not include anything that users
 | 
			
		||||
can regenerate automatically from other parts of the Corresponding
 | 
			
		||||
Source.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source for a work in source code form is that
 | 
			
		||||
same work.
 | 
			
		||||
 | 
			
		||||
  2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
  All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met.  This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program.  The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work.  This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
  You may make, run and propagate covered works that you do not
 | 
			
		||||
convey, without conditions so long as your license otherwise remains
 | 
			
		||||
in force.  You may convey covered works to others for the sole purpose
 | 
			
		||||
of having them make modifications exclusively for you, or provide you
 | 
			
		||||
with facilities for running those works, provided that you comply with
 | 
			
		||||
the terms of this License in conveying all material for which you do
 | 
			
		||||
not control copyright.  Those thus making or running the covered works
 | 
			
		||||
for you must do so exclusively on your behalf, under your direction
 | 
			
		||||
and control, on terms that prohibit them from making any copies of
 | 
			
		||||
your copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
  Conveying under any other circumstances is permitted solely under
 | 
			
		||||
the conditions stated below.  Sublicensing is not allowed; section 10
 | 
			
		||||
makes it unnecessary.
 | 
			
		||||
 | 
			
		||||
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
  No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
  When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such circumvention
 | 
			
		||||
is effected by exercising rights under this License with respect to
 | 
			
		||||
the covered work, and you disclaim any intention to limit operation or
 | 
			
		||||
modification of the work as a means of enforcing, against the work's
 | 
			
		||||
users, your or third parties' legal rights to forbid circumvention of
 | 
			
		||||
technological measures.
 | 
			
		||||
 | 
			
		||||
  4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
  You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
  You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
  5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
  You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
 | 
			
		||||
    b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under section
 | 
			
		||||
    7.  This requirement modifies the requirement in section 4 to
 | 
			
		||||
    "keep intact all notices".
 | 
			
		||||
 | 
			
		||||
    c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy.  This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged.  This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
 | 
			
		||||
    d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
  A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit.  Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
  6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
  You may convey a covered work in object code form under the terms
 | 
			
		||||
of sections 4 and 5, provided that you also convey the
 | 
			
		||||
machine-readable Corresponding Source under the terms of this License,
 | 
			
		||||
in one of these ways:
 | 
			
		||||
 | 
			
		||||
    a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
 | 
			
		||||
    b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the
 | 
			
		||||
    Corresponding Source from a network server at no charge.
 | 
			
		||||
 | 
			
		||||
    c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source.  This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
 | 
			
		||||
    d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge.  You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code.  If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source.  Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
 | 
			
		||||
    e) Convey the object code using peer-to-peer transmission, provided
 | 
			
		||||
    you inform other peers where the object code and Corresponding
 | 
			
		||||
    Source of the work are being offered to the general public at no
 | 
			
		||||
    charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
  A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
  A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal, family,
 | 
			
		||||
or household purposes, or (2) anything designed or sold for incorporation
 | 
			
		||||
into a dwelling.  In determining whether a product is a consumer product,
 | 
			
		||||
doubtful cases shall be resolved in favor of coverage.  For a particular
 | 
			
		||||
product received by a particular user, "normally used" refers to a
 | 
			
		||||
typical or common use of that class of product, regardless of the status
 | 
			
		||||
of the particular user or of the way in which the particular user
 | 
			
		||||
actually uses, or expects or is expected to use, the product.  A product
 | 
			
		||||
is a consumer product regardless of whether the product has substantial
 | 
			
		||||
commercial, industrial or non-consumer uses, unless such uses represent
 | 
			
		||||
the only significant mode of use of the product.
 | 
			
		||||
 | 
			
		||||
  "Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to install
 | 
			
		||||
and execute modified versions of a covered work in that User Product from
 | 
			
		||||
a modified version of its Corresponding Source.  The information must
 | 
			
		||||
suffice to ensure that the continued functioning of the modified object
 | 
			
		||||
code is in no case prevented or interfered with solely because
 | 
			
		||||
modification has been made.
 | 
			
		||||
 | 
			
		||||
  If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information.  But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
  The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or updates
 | 
			
		||||
for a work that has been modified or installed by the recipient, or for
 | 
			
		||||
the User Product in which it has been modified or installed.  Access to a
 | 
			
		||||
network may be denied when the modification itself materially and
 | 
			
		||||
adversely affects the operation of the network or violates the rules and
 | 
			
		||||
protocols for communication across the network.
 | 
			
		||||
 | 
			
		||||
  Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
  7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
  "Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law.  If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
  When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it.  (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.)  You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders of
 | 
			
		||||
that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
    a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
 | 
			
		||||
    b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
 | 
			
		||||
    c) Prohibiting misrepresentation of the origin of that material, or
 | 
			
		||||
    requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
 | 
			
		||||
    d) Limiting the use for publicity purposes of names of licensors or
 | 
			
		||||
    authors of the material; or
 | 
			
		||||
 | 
			
		||||
    e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
 | 
			
		||||
    f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions of
 | 
			
		||||
    it) with contractual assumptions of liability to the recipient, for
 | 
			
		||||
    any liability that these contractual assumptions directly impose on
 | 
			
		||||
    those licensors and authors.
 | 
			
		||||
 | 
			
		||||
  All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10.  If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term.  If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
  If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
  Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions;
 | 
			
		||||
the above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
  8. Termination.
 | 
			
		||||
 | 
			
		||||
  You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License.  Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
  However, if you cease all violation of this License, then your
 | 
			
		||||
license from a particular copyright holder is reinstated (a)
 | 
			
		||||
provisionally, unless and until the copyright holder explicitly and
 | 
			
		||||
finally terminates your license, and (b) permanently, if the copyright
 | 
			
		||||
holder fails to notify you of the violation by some reasonable means
 | 
			
		||||
prior to 60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
  Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
  Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License.  If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
  9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
  You are not required to accept this License in order to receive or
 | 
			
		||||
run a copy of the Program.  Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance.  However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work.  These actions infringe copyright if you do
 | 
			
		||||
not accept this License.  Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
  10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
  Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License.  You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
  An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations.  If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
  You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License.  For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
  11. Patents.
 | 
			
		||||
 | 
			
		||||
  A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based.  The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
  A contributor's "essential patent claims" are all patent claims
 | 
			
		||||
owned or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version.  For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
  In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement).  To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
  If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients.  "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
  If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
  A patent license is "discriminatory" if it does not include within
 | 
			
		||||
the scope of its coverage, prohibits the exercise of, or is
 | 
			
		||||
conditioned on the non-exercise of one or more of the rights that are
 | 
			
		||||
specifically granted under this License.  You may not convey a covered
 | 
			
		||||
work if you are a party to an arrangement with a third party that is
 | 
			
		||||
in the business of distributing software, under which you make payment
 | 
			
		||||
to the third party based on the extent of your activity of conveying
 | 
			
		||||
the work, and under which the third party grants, to any of the
 | 
			
		||||
parties who would receive the covered work from you, a discriminatory
 | 
			
		||||
patent license (a) in connection with copies of the covered work
 | 
			
		||||
conveyed by you (or copies made from those copies), or (b) primarily
 | 
			
		||||
for and in connection with specific products or compilations that
 | 
			
		||||
contain the covered work, unless you entered into that arrangement,
 | 
			
		||||
or that patent license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
  Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
  12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
  If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you may
 | 
			
		||||
not convey it at all.  For example, if you agree to terms that obligate you
 | 
			
		||||
to collect a royalty for further conveying from those to whom you convey
 | 
			
		||||
the Program, the only way you could satisfy both those terms and this
 | 
			
		||||
License would be to refrain entirely from conveying the Program.
 | 
			
		||||
 | 
			
		||||
  13. Use with the GNU Affero General Public License.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU Affero General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work.  The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the special requirements of the GNU Affero General Public License,
 | 
			
		||||
section 13, concerning interaction through a network will apply to the
 | 
			
		||||
combination as such.
 | 
			
		||||
 | 
			
		||||
  14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
  The Free Software Foundation may publish revised and/or new versions of
 | 
			
		||||
the GNU General Public License from time to time.  Such new versions will
 | 
			
		||||
be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
  Each version is given a distinguishing version number.  If the
 | 
			
		||||
Program specifies that a certain numbered version of the GNU General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation.  If the Program does not specify a version number of the
 | 
			
		||||
GNU General Public License, you may choose any version ever published
 | 
			
		||||
by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
  If the Program specifies that a proxy can decide which future
 | 
			
		||||
versions of the GNU General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
  Later license versions may give you additional or different
 | 
			
		||||
permissions.  However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
  15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
 | 
			
		||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
			
		||||
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
 | 
			
		||||
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
 | 
			
		||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
 | 
			
		||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
 | 
			
		||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
 | 
			
		||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
 | 
			
		||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
 | 
			
		||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
 | 
			
		||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
 | 
			
		||||
SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
  17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
  If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
state the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU General Public License as published by
 | 
			
		||||
    the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU General Public License
 | 
			
		||||
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
  If the program does terminal interaction, make it output a short
 | 
			
		||||
notice like this when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    <program>  Copyright (C) <year>  <name of author>
 | 
			
		||||
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, your program's commands
 | 
			
		||||
might be different; for a GUI interface, you would use an "about box".
 | 
			
		||||
 | 
			
		||||
  You should also get your employer (if you work as a programmer) or school,
 | 
			
		||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
			
		||||
For more information on this, and how to apply and follow the GNU GPL, see
 | 
			
		||||
<http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
  The GNU General Public License does not permit incorporating your program
 | 
			
		||||
into proprietary programs.  If your program is a subroutine library, you
 | 
			
		||||
may consider it more useful to permit linking proprietary applications with
 | 
			
		||||
the library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.  But first, please read
 | 
			
		||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
 | 
			
		||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2012-2017 python-escpos and Manuel F Martinez
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										8
									
								
								MANIFEST
									
									
									
									
									
								
							
							
						
						@@ -1,8 +0,0 @@
 | 
			
		||||
# file GENERATED by distutils, do NOT edit
 | 
			
		||||
README
 | 
			
		||||
setup.py
 | 
			
		||||
escpos/__init__.py
 | 
			
		||||
escpos/constants.py
 | 
			
		||||
escpos/escpos.py
 | 
			
		||||
escpos/exceptions.py
 | 
			
		||||
escpos/printer.py
 | 
			
		||||
							
								
								
									
										13
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
			
		||||
include *.rst
 | 
			
		||||
include *.txt
 | 
			
		||||
include COPYING
 | 
			
		||||
include LICENSE
 | 
			
		||||
include INSTALL
 | 
			
		||||
include tox.ini
 | 
			
		||||
include capabilities-data/dist/capabilities.json
 | 
			
		||||
recursive-include doc *.bat
 | 
			
		||||
recursive-include doc *.ico
 | 
			
		||||
recursive-include doc *.py
 | 
			
		||||
recursive-include doc *.rst
 | 
			
		||||
recursive-include doc *.txt
 | 
			
		||||
recursive-include doc Makefile
 | 
			
		||||
							
								
								
									
										45
									
								
								README
									
									
									
									
									
								
							
							
						
						@@ -1,45 +0,0 @@
 | 
			
		||||
ESCPOS
 | 
			
		||||
======
 | 
			
		||||
 | 
			
		||||
Python library to manipulate ESC/POS Printers.
 | 
			
		||||
 | 
			
		||||
------------------------------------------------------------------
 | 
			
		||||
1. Description
 | 
			
		||||
 | 
			
		||||
Python ESC/POS is a library which lets the user have access to all
 | 
			
		||||
those printers handled by ESC/POS commands, as defined by Epson,
 | 
			
		||||
from a Python application.
 | 
			
		||||
 | 
			
		||||
The standard usage is send raw text to the printer, but in also 
 | 
			
		||||
helps the user to enhance the experience with those printers by
 | 
			
		||||
facilitating the bar code printing in many different standards,
 | 
			
		||||
as well as manipulating images so they can be printed as brand
 | 
			
		||||
logo or any other usage images migh have. 
 | 
			
		||||
 | 
			
		||||
Text can be aligned/justified and fonts can be changed by size,
 | 
			
		||||
type and weight.
 | 
			
		||||
 | 
			
		||||
Also, this module handles some hardware functionalities like, cut
 | 
			
		||||
paper, carrier return, printer reset and others concerned to the
 | 
			
		||||
carriage alignment.
 | 
			
		||||
 | 
			
		||||
------------------------------------------------------------------
 | 
			
		||||
2. Documentation
 | 
			
		||||
 | 
			
		||||
Please visit project documentation at:
 | 
			
		||||
https://github.com/manpaz/python-escpos/wiki
 | 
			
		||||
 | 
			
		||||
------------------------------------------------------------------
 | 
			
		||||
3. Donations
 | 
			
		||||
 | 
			
		||||
There are some different prints I'd like to acquire, but unfortunately
 | 
			
		||||
not all, even used, are cheaper and easy to get.
 | 
			
		||||
 | 
			
		||||
If you want to help funding money to get more printers or just want to
 | 
			
		||||
donate because you like the project, please be in touch and I'll be
 | 
			
		||||
sending my PayPal info so you can donate.
 | 
			
		||||
 | 
			
		||||
Thank you!
 | 
			
		||||
 | 
			
		||||
Manuel F Martinez <manpaz@bashlinux.com>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										76
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,76 @@
 | 
			
		||||
#############################################################
 | 
			
		||||
python-escpos - Python library to manipulate ESC/POS Printers
 | 
			
		||||
#############################################################
 | 
			
		||||
 | 
			
		||||
.. image:: https://travis-ci.org/python-escpos/python-escpos.svg?branch=master
 | 
			
		||||
    :target: https://travis-ci.org/python-escpos/python-escpos
 | 
			
		||||
    :alt: Continous Integration
 | 
			
		||||
 | 
			
		||||
.. image:: https://www.quantifiedcode.com/api/v1/project/95748b89a3974700800b85e4ed3d32c4/badge.svg
 | 
			
		||||
    :target: https://www.quantifiedcode.com/app/project/95748b89a3974700800b85e4ed3d32c4
 | 
			
		||||
    :alt: Code issues
 | 
			
		||||
 | 
			
		||||
.. 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
 | 
			
		||||
    :alt: Documentation Status
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Description
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
Python ESC/POS is a library which lets the user have access to all those printers handled
 | 
			
		||||
by ESC/POS commands, as defined by Epson, from a Python application.
 | 
			
		||||
 | 
			
		||||
The library tries to implement the functions provided by the ESC/POS-commandset and supports sending text, images,
 | 
			
		||||
barcodes and qr-codes to the printer.
 | 
			
		||||
 | 
			
		||||
Text can be aligned/justified and fonts can be changed by size, type and weight.
 | 
			
		||||
 | 
			
		||||
Also, this module handles some hardware functionalities like cutting paper, control characters, printer reset
 | 
			
		||||
and similar functions.
 | 
			
		||||
 | 
			
		||||
Since supported commands differ from printer to printer the software tries to automatically apply the right
 | 
			
		||||
settings for the printer that you set. These settings are handled by
 | 
			
		||||
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_ which is also used in
 | 
			
		||||
`escpos-php <https://github.com/mike42/escpos-php>`_.
 | 
			
		||||
 | 
			
		||||
Dependencies
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
This library makes use of:
 | 
			
		||||
 | 
			
		||||
    * pyusb for USB-printers
 | 
			
		||||
    * Pillow for image printing
 | 
			
		||||
    * qrcode for the generation of QR-codes
 | 
			
		||||
    * pyserial for serial printers
 | 
			
		||||
 | 
			
		||||
Documentation and Usage
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
The basic usage is:
 | 
			
		||||
 | 
			
		||||
.. code:: python
 | 
			
		||||
 | 
			
		||||
    from escpos.printer import Usb
 | 
			
		||||
 | 
			
		||||
    """ Seiko Epson Corp. Receipt Printer (EPSON TM-T88III) """
 | 
			
		||||
    p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
 | 
			
		||||
    p.text("Hello World\n")
 | 
			
		||||
    p.image("logo.gif")
 | 
			
		||||
    p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
 | 
			
		||||
    p.cut()
 | 
			
		||||
 | 
			
		||||
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
 | 
			
		||||
 | 
			
		||||
Contributing
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
This project is open for any contribution! Please see CONTRIBUTING.rst for more information.
 | 
			
		||||
							
								
								
									
										1
									
								
								capabilities-data
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										13
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
			
		||||
codecov:
 | 
			
		||||
  bot: patkan
 | 
			
		||||
 | 
			
		||||
coverage:
 | 
			
		||||
  status:
 | 
			
		||||
    project:
 | 
			
		||||
      default:  # status context
 | 
			
		||||
        target: auto
 | 
			
		||||
        threshold: "1%"
 | 
			
		||||
  range: "60...100"
 | 
			
		||||
 | 
			
		||||
comment: off
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								doc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
_build/
 | 
			
		||||
							
								
								
									
										177
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,177 @@
 | 
			
		||||
# Makefile for Sphinx documentation
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# You can set these variables from the command line.
 | 
			
		||||
SPHINXOPTS    =
 | 
			
		||||
SPHINXBUILD   = sphinx-build
 | 
			
		||||
PAPER         =
 | 
			
		||||
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/)
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
# Internal variables.
 | 
			
		||||
PAPEROPT_a4     = -D latex_paper_size=a4
 | 
			
		||||
PAPEROPT_letter = -D latex_paper_size=letter
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
help:
 | 
			
		||||
	@echo "Please use \`make <target>' where <target> is one of"
 | 
			
		||||
	@echo "  html       to make standalone HTML files"
 | 
			
		||||
	@echo "  dirhtml    to make HTML files named index.html in directories"
 | 
			
		||||
	@echo "  singlehtml to make a single large HTML file"
 | 
			
		||||
	@echo "  pickle     to make pickle files"
 | 
			
		||||
	@echo "  json       to make JSON files"
 | 
			
		||||
	@echo "  htmlhelp   to make HTML files and a HTML help project"
 | 
			
		||||
	@echo "  qthelp     to make HTML files and a qthelp project"
 | 
			
		||||
	@echo "  devhelp    to make HTML files and a Devhelp project"
 | 
			
		||||
	@echo "  epub       to make an epub"
 | 
			
		||||
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
 | 
			
		||||
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
 | 
			
		||||
	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
 | 
			
		||||
	@echo "  text       to make text files"
 | 
			
		||||
	@echo "  man        to make manual pages"
 | 
			
		||||
	@echo "  texinfo    to make Texinfo files"
 | 
			
		||||
	@echo "  info       to make Texinfo files and run them through makeinfo"
 | 
			
		||||
	@echo "  gettext    to make PO message catalogs"
 | 
			
		||||
	@echo "  changes    to make an overview of all changed/added/deprecated items"
 | 
			
		||||
	@echo "  xml        to make Docutils-native XML files"
 | 
			
		||||
	@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)"
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	rm -rf $(BUILDDIR)/*
 | 
			
		||||
 | 
			
		||||
html:
 | 
			
		||||
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 | 
			
		||||
 | 
			
		||||
dirhtml:
 | 
			
		||||
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 | 
			
		||||
 | 
			
		||||
singlehtml:
 | 
			
		||||
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
 | 
			
		||||
 | 
			
		||||
pickle:
 | 
			
		||||
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can process the pickle files."
 | 
			
		||||
 | 
			
		||||
json:
 | 
			
		||||
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can process the JSON files."
 | 
			
		||||
 | 
			
		||||
htmlhelp:
 | 
			
		||||
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can run HTML Help Workshop with the" \
 | 
			
		||||
	      ".hhp project file in $(BUILDDIR)/htmlhelp."
 | 
			
		||||
 | 
			
		||||
qthelp:
 | 
			
		||||
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
 | 
			
		||||
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
 | 
			
		||||
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-escpos.qhcp"
 | 
			
		||||
	@echo "To view the help file:"
 | 
			
		||||
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-escpos.qhc"
 | 
			
		||||
 | 
			
		||||
devhelp:
 | 
			
		||||
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished."
 | 
			
		||||
	@echo "To view the help file:"
 | 
			
		||||
	@echo "# mkdir -p $$HOME/.local/share/devhelp/python-escpos"
 | 
			
		||||
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-escpos"
 | 
			
		||||
	@echo "# devhelp"
 | 
			
		||||
 | 
			
		||||
epub:
 | 
			
		||||
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
 | 
			
		||||
 | 
			
		||||
latex:
 | 
			
		||||
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
 | 
			
		||||
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
 | 
			
		||||
	      "(use \`make latexpdf' here to do that automatically)."
 | 
			
		||||
 | 
			
		||||
latexpdf:
 | 
			
		||||
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
			
		||||
	@echo "Running LaTeX files through pdflatex..."
 | 
			
		||||
	$(MAKE) -C $(BUILDDIR)/latex all-pdf
 | 
			
		||||
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
 | 
			
		||||
 | 
			
		||||
latexpdfja:
 | 
			
		||||
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
			
		||||
	@echo "Running LaTeX files through platex and dvipdfmx..."
 | 
			
		||||
	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
 | 
			
		||||
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
 | 
			
		||||
 | 
			
		||||
text:
 | 
			
		||||
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The text files are in $(BUILDDIR)/text."
 | 
			
		||||
 | 
			
		||||
man:
 | 
			
		||||
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
 | 
			
		||||
 | 
			
		||||
texinfo:
 | 
			
		||||
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
 | 
			
		||||
	@echo "Run \`make' in that directory to run these through makeinfo" \
 | 
			
		||||
	      "(use \`make info' here to do that automatically)."
 | 
			
		||||
 | 
			
		||||
info:
 | 
			
		||||
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
 | 
			
		||||
	@echo "Running Texinfo files through makeinfo..."
 | 
			
		||||
	make -C $(BUILDDIR)/texinfo info
 | 
			
		||||
	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
 | 
			
		||||
 | 
			
		||||
gettext:
 | 
			
		||||
	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
 | 
			
		||||
 | 
			
		||||
changes:
 | 
			
		||||
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "The overview file is in $(BUILDDIR)/changes."
 | 
			
		||||
 | 
			
		||||
linkcheck:
 | 
			
		||||
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Link check complete; look for any errors in the above output " \
 | 
			
		||||
	      "or in $(BUILDDIR)/linkcheck/output.txt."
 | 
			
		||||
 | 
			
		||||
doctest:
 | 
			
		||||
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
 | 
			
		||||
	@echo "Testing of doctests in the sources finished, look at the " \
 | 
			
		||||
	      "results in $(BUILDDIR)/doctest/output.txt."
 | 
			
		||||
 | 
			
		||||
xml:
 | 
			
		||||
	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
 | 
			
		||||
 | 
			
		||||
pseudoxml:
 | 
			
		||||
	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
 | 
			
		||||
							
								
								
									
										0
									
								
								doc/_static/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										10
									
								
								doc/api/capabilities.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Capabilities
 | 
			
		||||
------------
 | 
			
		||||
Module :py:mod:`escpos.capabilities`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.capabilities
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/cli.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
CLI
 | 
			
		||||
---
 | 
			
		||||
Module :py:mod:`escpos.cli`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.cli
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/codepages.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Codepages
 | 
			
		||||
---------
 | 
			
		||||
Module :py:mod:`escpos.codepages`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.codepages
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/config.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Config
 | 
			
		||||
------
 | 
			
		||||
Module :py:mod:`escpos.config`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.config
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/constants.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Constants
 | 
			
		||||
---------
 | 
			
		||||
Module :py:mod:`escpos.constants`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.constants
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/escpos.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Esc/Pos
 | 
			
		||||
-------
 | 
			
		||||
Module :py:mod:`escpos.escpos`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.escpos
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										9
									
								
								doc/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
Exceptions
 | 
			
		||||
----------
 | 
			
		||||
Module :py:mod:`escpos.exceptions`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.exceptions
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/image.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Image helper
 | 
			
		||||
------------
 | 
			
		||||
Module :py:mod:`escpos.image`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.image
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/katakana.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Katakana
 | 
			
		||||
--------
 | 
			
		||||
Module :py:mod:`escpos.katakana`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.katakana
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/api/magicencode.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
Magic Encode
 | 
			
		||||
------------
 | 
			
		||||
Module :py:mod:`escpos.magicencode`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.magicencode
 | 
			
		||||
    :members:
 | 
			
		||||
    :inherited-members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										9
									
								
								doc/api/printer.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
Printer implementations
 | 
			
		||||
-----------------------
 | 
			
		||||
Module :py:mod:`escpos.printer`
 | 
			
		||||
 | 
			
		||||
.. automodule:: escpos.printer
 | 
			
		||||
    :members:
 | 
			
		||||
    :undoc-members:
 | 
			
		||||
    :show-inheritance:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
							
								
								
									
										294
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,294 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# python-escpos documentation build configuration file, created by
 | 
			
		||||
# sphinx-quickstart on Sat Dec 26 14:28:42 2015.
 | 
			
		||||
#
 | 
			
		||||
# This file is execfile()d with the current directory set to its
 | 
			
		||||
# containing dir.
 | 
			
		||||
#
 | 
			
		||||
# Note that not all possible configuration values are present in this
 | 
			
		||||
# autogenerated file.
 | 
			
		||||
#
 | 
			
		||||
# All configuration values have a default; values that are commented out
 | 
			
		||||
# serve to show the default.
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
on_rtd = os.getenv('READTHEDOCS') == 'True'
 | 
			
		||||
if on_rtd:
 | 
			
		||||
    import escpos
 | 
			
		||||
else:
 | 
			
		||||
    from setuptools_scm import get_version
 | 
			
		||||
 | 
			
		||||
# 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__), '..'))
 | 
			
		||||
 | 
			
		||||
# -- General configuration ------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# If your documentation needs a minimal Sphinx version, state it here.
 | 
			
		||||
#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',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# supress warnings for external images
 | 
			
		||||
suppress_warnings = [
 | 
			
		||||
    'image.nonlocal_uri',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# enable todos
 | 
			
		||||
todo_include_todos = True
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
 | 
			
		||||
# The suffix of source filenames.
 | 
			
		||||
source_suffix = '.rst'
 | 
			
		||||
 | 
			
		||||
# The encoding of source files.
 | 
			
		||||
#source_encoding = 'utf-8-sig'
 | 
			
		||||
 | 
			
		||||
# The master toctree document.
 | 
			
		||||
master_doc = 'index'
 | 
			
		||||
 | 
			
		||||
# General information about the project.
 | 
			
		||||
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)
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '.'.join(release.split('.')[:2])  # The short X.Y version.
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
#language = None
 | 
			
		||||
 | 
			
		||||
# There are two options for replacing |today|: either, you set today to some
 | 
			
		||||
# non-false value, then it is used:
 | 
			
		||||
#today = ''
 | 
			
		||||
# Else, today_fmt is used as the format for a strftime call.
 | 
			
		||||
#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']
 | 
			
		||||
 | 
			
		||||
# The reST default role (used for this markup: `text`) to use for all
 | 
			
		||||
# documents.
 | 
			
		||||
#default_role = None
 | 
			
		||||
 | 
			
		||||
# If true, '()' will be appended to :func: etc. cross-reference text.
 | 
			
		||||
#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
 | 
			
		||||
 | 
			
		||||
# If true, sectionauthor and moduleauthor directives will be shown in the
 | 
			
		||||
# output. They are ignored by default.
 | 
			
		||||
#show_authors = False
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = 'sphinx'
 | 
			
		||||
 | 
			
		||||
# A list of ignored prefixes for module index sorting.
 | 
			
		||||
#modindex_common_prefix = []
 | 
			
		||||
 | 
			
		||||
# If true, keep warnings as "system message" paragraphs in the built documents.
 | 
			
		||||
#keep_warnings = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for HTML output ----------------------------------------------
 | 
			
		||||
 | 
			
		||||
# 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'
 | 
			
		||||
else:
 | 
			
		||||
    try:
 | 
			
		||||
        import 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'
 | 
			
		||||
 | 
			
		||||
# 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 = {}
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain custom themes here, relative to this directory.
 | 
			
		||||
#html_theme_path = []
 | 
			
		||||
 | 
			
		||||
# The name for this set of Sphinx documents.  If None, it defaults to
 | 
			
		||||
# "<project> v<release> documentation".
 | 
			
		||||
#html_title = None
 | 
			
		||||
 | 
			
		||||
# A shorter title for the navigation bar.  Default is the same as html_title.
 | 
			
		||||
#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
 | 
			
		||||
 | 
			
		||||
# 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'
 | 
			
		||||
 | 
			
		||||
# 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']
 | 
			
		||||
 | 
			
		||||
# 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 = []
 | 
			
		||||
 | 
			
		||||
# 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'
 | 
			
		||||
 | 
			
		||||
# If true, SmartyPants will be used to convert quotes and dashes to
 | 
			
		||||
# typographically correct entities.
 | 
			
		||||
#html_use_smartypants = True
 | 
			
		||||
 | 
			
		||||
# Custom sidebar templates, maps document names to template names.
 | 
			
		||||
#html_sidebars = {}
 | 
			
		||||
 | 
			
		||||
# Additional templates that should be rendered to pages, maps page names to
 | 
			
		||||
# template names.
 | 
			
		||||
#html_additional_pages = {}
 | 
			
		||||
 | 
			
		||||
# If false, no module index is generated.
 | 
			
		||||
#html_domain_indices = True
 | 
			
		||||
 | 
			
		||||
# If false, no index is generated.
 | 
			
		||||
#html_use_index = True
 | 
			
		||||
 | 
			
		||||
# If true, the index is split into individual pages for each letter.
 | 
			
		||||
#html_split_index = False
 | 
			
		||||
 | 
			
		||||
# If true, links to the reST sources are added to the pages.
 | 
			
		||||
#html_show_sourcelink = True
 | 
			
		||||
 | 
			
		||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
 | 
			
		||||
#html_show_sphinx = True
 | 
			
		||||
 | 
			
		||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is 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 = ''
 | 
			
		||||
 | 
			
		||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
 | 
			
		||||
#html_file_suffix = None
 | 
			
		||||
 | 
			
		||||
# Output file base name for HTML help builder.
 | 
			
		||||
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': '',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# 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'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# The name of an image file (relative to this directory) to place at the top of
 | 
			
		||||
# the title page.
 | 
			
		||||
#latex_logo = None
 | 
			
		||||
 | 
			
		||||
# For "manual" documents, if this is true, then toplevel headings are parts,
 | 
			
		||||
# not chapters.
 | 
			
		||||
#latex_use_parts = False
 | 
			
		||||
 | 
			
		||||
# If true, show page references after internal links.
 | 
			
		||||
#latex_show_pagerefs = False
 | 
			
		||||
 | 
			
		||||
# If true, show URL addresses after external links.
 | 
			
		||||
#latex_show_urls = False
 | 
			
		||||
 | 
			
		||||
# Documents to append as an appendix to all manuals.
 | 
			
		||||
#latex_appendices = []
 | 
			
		||||
 | 
			
		||||
# If false, no module index is generated.
 | 
			
		||||
#latex_domain_indices = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for manual page output ---------------------------------------
 | 
			
		||||
 | 
			
		||||
# 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)
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# If true, show URL addresses after external links.
 | 
			
		||||
#man_show_urls = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for Texinfo output -------------------------------------------
 | 
			
		||||
 | 
			
		||||
# Grouping the document tree into Texinfo files. List of tuples
 | 
			
		||||
# (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'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# Documents to append as an appendix to all manuals.
 | 
			
		||||
#texinfo_appendices = []
 | 
			
		||||
 | 
			
		||||
# If false, no module index is generated.
 | 
			
		||||
#texinfo_domain_indices = True
 | 
			
		||||
 | 
			
		||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
 | 
			
		||||
#texinfo_show_urls = 'footnote'
 | 
			
		||||
 | 
			
		||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
 | 
			
		||||
#texinfo_no_detailmenu = False
 | 
			
		||||
							
								
								
									
										1
									
								
								doc/dev/changelog.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
.. include:: ../../CHANGELOG.rst
 | 
			
		||||
							
								
								
									
										1
									
								
								doc/dev/contributing.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
.. include:: ../../CONTRIBUTING.rst
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/download/barcode.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										52
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,52 @@
 | 
			
		||||
.. python-escpos documentation master file, created by
 | 
			
		||||
   sphinx-quickstart on Sat Dec 26 14:28:42 2015.
 | 
			
		||||
   You can adapt this file completely to your liking, but it should at least
 | 
			
		||||
   contain the root `toctree` directive.
 | 
			
		||||
 | 
			
		||||
.. include:: ../README.rst
 | 
			
		||||
 | 
			
		||||
Content
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
   :caption: User Documentation
 | 
			
		||||
 | 
			
		||||
   user/installation
 | 
			
		||||
   user/methods
 | 
			
		||||
   user/printers
 | 
			
		||||
   user/raspi
 | 
			
		||||
   user/todo
 | 
			
		||||
   user/usage
 | 
			
		||||
   user/barcode
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
   :caption: Developer Documentation
 | 
			
		||||
 | 
			
		||||
   dev/contributing
 | 
			
		||||
   dev/changelog
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
   :caption: API Documentation
 | 
			
		||||
 | 
			
		||||
   api/escpos
 | 
			
		||||
   api/printer
 | 
			
		||||
   api/constants
 | 
			
		||||
   api/exceptions
 | 
			
		||||
   api/capabilities
 | 
			
		||||
   api/config
 | 
			
		||||
   api/image
 | 
			
		||||
   api/cli
 | 
			
		||||
   api/magicencode
 | 
			
		||||
   api/codepages
 | 
			
		||||
   api/katakana
 | 
			
		||||
 | 
			
		||||
Indices and tables
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
* :ref:`genindex`
 | 
			
		||||
* :ref:`modindex`
 | 
			
		||||
* :ref:`search`
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										242
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,242 @@
 | 
			
		||||
@ECHO OFF
 | 
			
		||||
 | 
			
		||||
REM Command file for Sphinx documentation
 | 
			
		||||
 | 
			
		||||
if "%SPHINXBUILD%" == "" (
 | 
			
		||||
	set SPHINXBUILD=sphinx-build
 | 
			
		||||
)
 | 
			
		||||
set BUILDDIR=_build
 | 
			
		||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
 | 
			
		||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
 | 
			
		||||
if NOT "%PAPER%" == "" (
 | 
			
		||||
	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
 | 
			
		||||
	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "" goto help
 | 
			
		||||
 | 
			
		||||
if "%1" == "help" (
 | 
			
		||||
	:help
 | 
			
		||||
	echo.Please use `make ^<target^>` where ^<target^> is one of
 | 
			
		||||
	echo.  html       to make standalone HTML files
 | 
			
		||||
	echo.  dirhtml    to make HTML files named index.html in directories
 | 
			
		||||
	echo.  singlehtml to make a single large HTML file
 | 
			
		||||
	echo.  pickle     to make pickle files
 | 
			
		||||
	echo.  json       to make JSON files
 | 
			
		||||
	echo.  htmlhelp   to make HTML files and a HTML help project
 | 
			
		||||
	echo.  qthelp     to make HTML files and a qthelp project
 | 
			
		||||
	echo.  devhelp    to make HTML files and a Devhelp project
 | 
			
		||||
	echo.  epub       to make an epub
 | 
			
		||||
	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
 | 
			
		||||
	echo.  text       to make text files
 | 
			
		||||
	echo.  man        to make manual pages
 | 
			
		||||
	echo.  texinfo    to make Texinfo files
 | 
			
		||||
	echo.  gettext    to make PO message catalogs
 | 
			
		||||
	echo.  changes    to make an overview over all changed/added/deprecated items
 | 
			
		||||
	echo.  xml        to make Docutils-native XML files
 | 
			
		||||
	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
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "clean" (
 | 
			
		||||
	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
 | 
			
		||||
	del /q /s %BUILDDIR%\*
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
%SPHINXBUILD% 2> nul
 | 
			
		||||
if errorlevel 9009 (
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
 | 
			
		||||
	echo.installed, then set the SPHINXBUILD environment variable to point
 | 
			
		||||
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
 | 
			
		||||
	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/
 | 
			
		||||
	exit /b 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "html" (
 | 
			
		||||
	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "dirhtml" (
 | 
			
		||||
	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "singlehtml" (
 | 
			
		||||
	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "pickle" (
 | 
			
		||||
	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can process the pickle files.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "json" (
 | 
			
		||||
	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can process the JSON files.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "htmlhelp" (
 | 
			
		||||
	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can run HTML Help Workshop with the ^
 | 
			
		||||
.hhp project file in %BUILDDIR%/htmlhelp.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "qthelp" (
 | 
			
		||||
	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can run "qcollectiongenerator" with the ^
 | 
			
		||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
 | 
			
		||||
	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-escpos.qhcp
 | 
			
		||||
	echo.To view the help file:
 | 
			
		||||
	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-escpos.ghc
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "devhelp" (
 | 
			
		||||
	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "epub" (
 | 
			
		||||
	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The epub file is in %BUILDDIR%/epub.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "latex" (
 | 
			
		||||
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "latexpdf" (
 | 
			
		||||
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
 | 
			
		||||
	cd %BUILDDIR%/latex
 | 
			
		||||
	make all-pdf
 | 
			
		||||
	cd %BUILDDIR%/..
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "latexpdfja" (
 | 
			
		||||
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
 | 
			
		||||
	cd %BUILDDIR%/latex
 | 
			
		||||
	make all-pdf-ja
 | 
			
		||||
	cd %BUILDDIR%/..
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "text" (
 | 
			
		||||
	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The text files are in %BUILDDIR%/text.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "man" (
 | 
			
		||||
	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The manual pages are in %BUILDDIR%/man.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "texinfo" (
 | 
			
		||||
	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "gettext" (
 | 
			
		||||
	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "changes" (
 | 
			
		||||
	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.The overview file is in %BUILDDIR%/changes.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "linkcheck" (
 | 
			
		||||
	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Link check complete; look for any errors in the above output ^
 | 
			
		||||
or in %BUILDDIR%/linkcheck/output.txt.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "doctest" (
 | 
			
		||||
	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Testing of doctests in the sources finished, look at the ^
 | 
			
		||||
results in %BUILDDIR%/doctest/output.txt.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "xml" (
 | 
			
		||||
	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The XML files are in %BUILDDIR%/xml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "pseudoxml" (
 | 
			
		||||
	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
:end
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/pyescpos.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 162 KiB  | 
							
								
								
									
										7
									
								
								doc/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
			
		||||
pyusb
 | 
			
		||||
Pillow>=2.0
 | 
			
		||||
qrcode>=4.0
 | 
			
		||||
pyserial
 | 
			
		||||
sphinx-rtd-theme
 | 
			
		||||
setuptools-scm
 | 
			
		||||
docutils>=0.12
 | 
			
		||||
							
								
								
									
										34
									
								
								doc/user/barcode.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,34 @@
 | 
			
		||||
Printing Barcodes
 | 
			
		||||
-----------------
 | 
			
		||||
:Last Reviewed: 2016-07-31
 | 
			
		||||
 | 
			
		||||
Most ESC/POS-printers implement barcode-printing.
 | 
			
		||||
The barcode-commandset is implemented in the barcode-method.
 | 
			
		||||
For a list of compatible barcodes you should check the manual of your printer.
 | 
			
		||||
As a rule of thumb: even older Epson-models support most 1D-barcodes.
 | 
			
		||||
To be sure just try some implementations and have a look at the notices below.
 | 
			
		||||
 | 
			
		||||
barcode-method
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
 | 
			
		||||
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
 | 
			
		||||
 | 
			
		||||
.. py:currentmodule:: escpos.escpos
 | 
			
		||||
 | 
			
		||||
.. automethod:: Escpos.barcode
 | 
			
		||||
    :noindex:
 | 
			
		||||
 | 
			
		||||
CODE128
 | 
			
		||||
~~~~~~~
 | 
			
		||||
Code128 barcodes need a certain format.
 | 
			
		||||
For now the user has to make sure that the payload is correct.
 | 
			
		||||
For alphanumeric CODE128 you have to preface your payload with `{B`.
 | 
			
		||||
 | 
			
		||||
.. code-block:: Python
 | 
			
		||||
 | 
			
		||||
   from escpos.printer import Dummy, Serial
 | 
			
		||||
   p = Serial()
 | 
			
		||||
   # print CODE128 012ABCDabcd
 | 
			
		||||
   p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
 | 
			
		||||
 | 
			
		||||
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.
 | 
			
		||||
							
								
								
									
										48
									
								
								doc/user/installation.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,48 @@
 | 
			
		||||
************
 | 
			
		||||
Installation
 | 
			
		||||
************
 | 
			
		||||
 | 
			
		||||
:Last Reviewed: 2016-07-23
 | 
			
		||||
 | 
			
		||||
Installation with PIP
 | 
			
		||||
---------------------
 | 
			
		||||
Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter:
 | 
			
		||||
 | 
			
		||||
    ::
 | 
			
		||||
 | 
			
		||||
        pip install python-escpos
 | 
			
		||||
 | 
			
		||||
This should install all necessary dependencies. Apart from that python-escpos should also be
 | 
			
		||||
available as a Debian package. If you want to always benefit from the newest stable releases you should probably
 | 
			
		||||
install from PyPi.
 | 
			
		||||
 | 
			
		||||
Setup udev for USB-Printers
 | 
			
		||||
---------------------------
 | 
			
		||||
1. Get the *Product ID* and *Vendor ID* from the lsusb command
 | 
			
		||||
   ``# lsusb  Bus 002 Device 001: ID 1a2b:1a2b Device name``
 | 
			
		||||
 | 
			
		||||
2. Create a udev rule to let users belonging to *dialout* group use the
 | 
			
		||||
   printer. You can create the file
 | 
			
		||||
   ``/etc/udev/rules.d/99-escpos.rules`` and add the following:
 | 
			
		||||
   ``SUBSYSTEM=="usb", ATTRS{idVendor}=="1a2b", ATTRS{idProduct}=="1a2b", MODE="0664", GROUP="dialout"``
 | 
			
		||||
   Replace *idVendor* and *idProduct* hex numbers with the ones that you
 | 
			
		||||
   got from the previous step. Note that you can either, add yourself to
 | 
			
		||||
   "dialout" group, or use another group you already belongs instead
 | 
			
		||||
   "dialout" and set it in the ``GROUP`` parameter in the above rule.
 | 
			
		||||
 | 
			
		||||
3. Restart udev ``# sudo service udev restart`` In some new systems it
 | 
			
		||||
   is done with ``# sudo udevadm control --reload``
 | 
			
		||||
 | 
			
		||||
Enabling tab-completion in CLI
 | 
			
		||||
------------------------------
 | 
			
		||||
python-escpos has a CLI with tab-completion. This is realised with ``argcomplete``.
 | 
			
		||||
In order for this to work you have to enable tab-completion, which is described in
 | 
			
		||||
the `manual of argcomplete <https://argcomplete.readthedocs.io>`__.
 | 
			
		||||
 | 
			
		||||
If you only want to enable it for python-escpos, or global activation does not work, try this:
 | 
			
		||||
 | 
			
		||||
    ::
 | 
			
		||||
 | 
			
		||||
        eval "$(register-python-argcomplete python-escpos)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								doc/user/methods.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
*******
 | 
			
		||||
Methods
 | 
			
		||||
*******
 | 
			
		||||
:Last Reviewed: 2017-01-25
 | 
			
		||||
 | 
			
		||||
Escpos class
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
The core part of this libraries API is the Escpos class.
 | 
			
		||||
You use it by instantiating a  :doc:`printer <printers>` which is a child of Escpos.
 | 
			
		||||
The following methods are available:
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.escpos.Escpos
 | 
			
		||||
    :members:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
    :noindex:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										77
									
								
								doc/user/printers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,77 @@
 | 
			
		||||
********
 | 
			
		||||
Printers
 | 
			
		||||
********
 | 
			
		||||
:Last Reviewed: 2017-01-25
 | 
			
		||||
 | 
			
		||||
As of now there are 5 different type of printer implementations.
 | 
			
		||||
 | 
			
		||||
USB
 | 
			
		||||
---
 | 
			
		||||
The USB-class uses pyusb and libusb to communicate with USB-based
 | 
			
		||||
printers. Note that this driver is not suited for USB-to-Serial-adapters
 | 
			
		||||
and similiar devices, but only for those implementing native USB.
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.printer.Usb
 | 
			
		||||
    :members:
 | 
			
		||||
    :special-members:
 | 
			
		||||
    :member-order: bysource
 | 
			
		||||
    :noindex:
 | 
			
		||||
 | 
			
		||||
Serial
 | 
			
		||||
------
 | 
			
		||||
This driver uses pyserial in order to communicate with serial devices.
 | 
			
		||||
If you are using an USB-based adapter to connect to the serial port,
 | 
			
		||||
then you should also use this driver.
 | 
			
		||||
The configuration is often based on DIP-switches that you can set on your
 | 
			
		||||
printer. For the hardware-configuration please refer to your printer's manual.
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.printer.Serial
 | 
			
		||||
     :members:
 | 
			
		||||
     :special-members:
 | 
			
		||||
     :member-order: bysource
 | 
			
		||||
     :noindex:
 | 
			
		||||
 | 
			
		||||
Network
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
This driver is based on the socket class.
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.printer.Network
 | 
			
		||||
      :members:
 | 
			
		||||
      :special-members:
 | 
			
		||||
      :member-order: bysource
 | 
			
		||||
      :noindex:
 | 
			
		||||
 | 
			
		||||
Troubleshooting
 | 
			
		||||
^^^^^^^^^^^^^^^
 | 
			
		||||
Problems with a network-attached printer can have numerous causes. Make sure that your device has a proper IP address.
 | 
			
		||||
Often you can check the IP address by triggering the self-test of the device. As a next step try to send text
 | 
			
		||||
manually to the device. You could use for example:
 | 
			
		||||
 | 
			
		||||
    ::
 | 
			
		||||
 | 
			
		||||
            echo "OK\n" | nc IPADDRESS 9100
 | 
			
		||||
            # the port number is often 9100
 | 
			
		||||
 | 
			
		||||
As a last resort try to reset the interface of the printer. This should be described in its manual.
 | 
			
		||||
 | 
			
		||||
File
 | 
			
		||||
----
 | 
			
		||||
This printer "prints" just into a file-handle. Especially on \*nix-systems this comes very handy.
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.printer.File
 | 
			
		||||
      :members:
 | 
			
		||||
      :special-members:
 | 
			
		||||
      :member-order: bysource
 | 
			
		||||
      :noindex:
 | 
			
		||||
 | 
			
		||||
Dummy
 | 
			
		||||
-----
 | 
			
		||||
The Dummy-printer is mainly for testing- and debugging-purposes. It stores
 | 
			
		||||
all of the "output" as raw ESC/POS in a string and returns that.
 | 
			
		||||
 | 
			
		||||
.. autoclass:: escpos.printer.Dummy
 | 
			
		||||
      :members:
 | 
			
		||||
      :member-order: bysource
 | 
			
		||||
      :noindex:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										42
									
								
								doc/user/raspi.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
			
		||||
************
 | 
			
		||||
Raspberry Pi
 | 
			
		||||
************
 | 
			
		||||
 | 
			
		||||
:Last Reviewed: 2017-01-05
 | 
			
		||||
 | 
			
		||||
This instructions were tested on Raspbian Jessie.
 | 
			
		||||
 | 
			
		||||
.. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to
 | 
			
		||||
    a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are
 | 
			
		||||
    based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would
 | 
			
		||||
    **thus destroy your interface**. Connect both systems with an appropriate *level shifter*.
 | 
			
		||||
 | 
			
		||||
Dependencies
 | 
			
		||||
------------
 | 
			
		||||
First, install the packages available on Raspbian.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
       sudo apt-get install python3 python3-setuptools python3-pip libjpeg8-dev
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
You can install by using pip3.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    sudo pip3 install --upgrade pip
 | 
			
		||||
    sudo pip3 install python-escpos
 | 
			
		||||
 | 
			
		||||
Run
 | 
			
		||||
---
 | 
			
		||||
You need sudo and python3 to run your program.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    sudo python3 your-program.py
 | 
			
		||||
 | 
			
		||||
Now you can attach your printer and and test it with the example code in the project's set of examples.
 | 
			
		||||
You can find that in the `project-repository <https://github.com/python-escpos/python-escpos>`__.
 | 
			
		||||
 | 
			
		||||
For more details on this check the :doc:`installation-manual <installation>`.
 | 
			
		||||
							
								
								
									
										43
									
								
								doc/user/todo.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,43 @@
 | 
			
		||||
****
 | 
			
		||||
TODO
 | 
			
		||||
****
 | 
			
		||||
 | 
			
		||||
Introduction
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
python-escpos is the initial idea, from here we can start to build a
 | 
			
		||||
robust library to get most of the ESC/POS printers working with this
 | 
			
		||||
library.
 | 
			
		||||
 | 
			
		||||
Eventually, this library must be able to cover almost all the defined
 | 
			
		||||
models detailed in the ESC/POS Command Specification Manual.
 | 
			
		||||
 | 
			
		||||
Details
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
What things are planned to work on?
 | 
			
		||||
 | 
			
		||||
Testing
 | 
			
		||||
~~~~~~~
 | 
			
		||||
 | 
			
		||||
* Test on many printers as possible (USB, Serial, Network)
 | 
			
		||||
* automate testing
 | 
			
		||||
 | 
			
		||||
Design
 | 
			
		||||
~~~~~~
 | 
			
		||||
 | 
			
		||||
* Add all those sequences which are not common, but part of the ESC/POS
 | 
			
		||||
  Command Specifications.
 | 
			
		||||
 | 
			
		||||
  *  Port to Python 3
 | 
			
		||||
  *  Windows compatibility (hidapi instead libusb?)
 | 
			
		||||
  *  PDF417 support
 | 
			
		||||
 | 
			
		||||
* use something similar to the `capabilities` in escpos-php
 | 
			
		||||
 | 
			
		||||
Todos in the codebase
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. todolist::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										275
									
								
								doc/user/usage.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,275 @@
 | 
			
		||||
*****
 | 
			
		||||
Usage
 | 
			
		||||
*****
 | 
			
		||||
 | 
			
		||||
Define your printer
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
USB printer
 | 
			
		||||
^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
Before creating your Python ESC/POS printer instance, consult the system to obtain
 | 
			
		||||
the printer parameters. This is done with the 'lsusb' command.
 | 
			
		||||
 | 
			
		||||
Run the command and look for the "Vendor ID" and "Product ID" and write
 | 
			
		||||
down the values. These values are displayed just before the name
 | 
			
		||||
of the device with the following format:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    xxxx:xxxx
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    # lsusb
 | 
			
		||||
    Bus 002 Device 001: ID 04b8:0202 Epson ...
 | 
			
		||||
 | 
			
		||||
Write down the the values in question, then issue the following command
 | 
			
		||||
so you can get the "Interface" number and "End Point"
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    # lsusb -vvv -d xxxx:xxxx | grep iInterface
 | 
			
		||||
        iInterface              0
 | 
			
		||||
    # lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT
 | 
			
		||||
          bEndpointAddress     0x01  EP 1 OUT
 | 
			
		||||
 | 
			
		||||
The first command will yield the "Interface" number that must be handy
 | 
			
		||||
to have and the second yields the "Output Endpoint" address.
 | 
			
		||||
 | 
			
		||||
**USB Printer initialization**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Epson = printer.Usb(0x04b8,0x0202)
 | 
			
		||||
 | 
			
		||||
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 where in\_ep is
 | 
			
		||||
on 0x81 and out\_ep=0x02, then the printer definition should look like:
 | 
			
		||||
 | 
			
		||||
**Generic USB Printer initialization**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Generic = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02)
 | 
			
		||||
 | 
			
		||||
Network printer
 | 
			
		||||
^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
You only need the IP of your printer, either because it is getting its
 | 
			
		||||
IP by DHCP or you set it manually.
 | 
			
		||||
 | 
			
		||||
**Network Printer initialization**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Epson = printer.Network("192.168.1.99")
 | 
			
		||||
 | 
			
		||||
Serial printer
 | 
			
		||||
^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
Most of the default values set by the DIP switches for the serial
 | 
			
		||||
printers, have been set as default on the serial printer class, so the
 | 
			
		||||
only thing you need to know is which serial port the printer is connected
 | 
			
		||||
to.
 | 
			
		||||
 | 
			
		||||
**Serial printer initialization**
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Epson = printer.Serial("/dev/tty0")
 | 
			
		||||
 | 
			
		||||
Other printers
 | 
			
		||||
^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
Some printers under `/dev` can't be used or initialized with any of the
 | 
			
		||||
methods described above. Usually, those are printers used by printcap,
 | 
			
		||||
however, if you know the device name, you could try to initialize by
 | 
			
		||||
passing the device node name.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    Epson = printer.File("/dev/usb/lp1")
 | 
			
		||||
 | 
			
		||||
The default is "/dev/usb/lp0", so if the printer is located on that
 | 
			
		||||
node, then you don't necessary need to pass the node name.
 | 
			
		||||
 | 
			
		||||
Define your instance
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
The following example demonstrates how to initialize the Epson TM-TI88IV
 | 
			
		||||
on a USB interface.
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    from escpos import *
 | 
			
		||||
    """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
 | 
			
		||||
    Epson = printer.Usb(0x04b8,0x0202)
 | 
			
		||||
    # Print text
 | 
			
		||||
    Epson.text("Hello World\n")
 | 
			
		||||
    # Print image
 | 
			
		||||
    Epson.image("logo.gif")
 | 
			
		||||
    # Print QR Code
 | 
			
		||||
    Epson.qr("You can readme from your smartphone")
 | 
			
		||||
    # Print barcode
 | 
			
		||||
    Epson.barcode('1324354657687','EAN13',64,2,'','')
 | 
			
		||||
    # Cut paper
 | 
			
		||||
    Epson.cut()
 | 
			
		||||
 | 
			
		||||
Configuration File
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
You can create a configuration file for python-escpos. This will
 | 
			
		||||
allow you to use the CLI, and skip some setup when using the library
 | 
			
		||||
programmatically.
 | 
			
		||||
 | 
			
		||||
The default configuration file is named ``config.yaml`` and uses the YAML
 | 
			
		||||
format. For windows it is probably at::
 | 
			
		||||
 | 
			
		||||
    %appdata%/python-escpos/config.yaml
 | 
			
		||||
 | 
			
		||||
And for linux::
 | 
			
		||||
 | 
			
		||||
        $HOME/.config/python-escpos/config.yaml
 | 
			
		||||
 | 
			
		||||
If you aren't sure, run::
 | 
			
		||||
 | 
			
		||||
        from escpos import config
 | 
			
		||||
        c = config.Config()
 | 
			
		||||
        c.load()
 | 
			
		||||
 | 
			
		||||
If it can't find the configuration file in the default location, it will tell
 | 
			
		||||
you where it's looking. You can always pass a path, or a list of paths, to
 | 
			
		||||
the ``load()`` method.
 | 
			
		||||
 | 
			
		||||
To load the configured printer, run::
 | 
			
		||||
 | 
			
		||||
        from escpos import config
 | 
			
		||||
        c = config.Config()
 | 
			
		||||
        printer = c.printer()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The printer section
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
The ``printer`` configuration section defines a default printer to create.
 | 
			
		||||
 | 
			
		||||
The only required paramter is ``type``. The value of this should be one of the
 | 
			
		||||
printers defined in :doc:`/user/printers`.
 | 
			
		||||
 | 
			
		||||
The rest of the parameters are whatever you want to pass to the printer.
 | 
			
		||||
 | 
			
		||||
An example file printer::
 | 
			
		||||
 | 
			
		||||
        printer:
 | 
			
		||||
                type: File
 | 
			
		||||
                devfile: /dev/someprinter
 | 
			
		||||
 | 
			
		||||
And for a network printer::
 | 
			
		||||
 | 
			
		||||
        printer:
 | 
			
		||||
                type: network
 | 
			
		||||
                host: 127.0.0.1
 | 
			
		||||
                port: 9000
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
After you have manually set the codepage the printer won't change it anymore. You can revert to normal behaviour
 | 
			
		||||
by setting charcode to ``AUTO``.
 | 
			
		||||
 | 
			
		||||
Advanced Usage: Print from binary blob
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
Imagine you have a file with ESC/POS-commands in binary form. This could be useful for testing capabilities of your
 | 
			
		||||
printer with a known working combination of commands.
 | 
			
		||||
You can print this data with the following code, using the standard methods of python-escpos. (This is an
 | 
			
		||||
advantage of the fact that `_raw()` accepts binary strings.)
 | 
			
		||||
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    from escpos import printer
 | 
			
		||||
    p = printer.Serial()  # adapt this to your printer model
 | 
			
		||||
 | 
			
		||||
    file = open("binary-blob.bin", "rb")  # read in the file containing your commands in binary-mode
 | 
			
		||||
    data = file.read()
 | 
			
		||||
    file.close()
 | 
			
		||||
 | 
			
		||||
    p._raw(data)
 | 
			
		||||
 | 
			
		||||
That's all, the printer should then print your data. You can also use this technique to let others reproduce an issue
 | 
			
		||||
that you have found. (Just "print" your commands to a File-printer on your local filesystem.)
 | 
			
		||||
However, please keep in mind, that often it is easier and better to just supply the code that you are using.
 | 
			
		||||
 | 
			
		||||
Here you can download an example, that will print a set of common barcodes:
 | 
			
		||||
 | 
			
		||||
    * :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
 | 
			
		||||
 | 
			
		||||
Advanced Usage: change capabilities-profile
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
 | 
			
		||||
Packaged together with the escpos-code is a capabilities-file. This file in
 | 
			
		||||
JSON-format describes the capabilities of different printers. It is developed and hosted in
 | 
			
		||||
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_.
 | 
			
		||||
 | 
			
		||||
Certain applications like the usage of `cx_freeze <https://cx-freeze.readthedocs.io>`_ might change the
 | 
			
		||||
packaging structure. This leads to the capabilities-profile not being found.
 | 
			
		||||
In this case you can use the environment-variable `ESCPOS_CAPABILITIES_FILE`.
 | 
			
		||||
The following code is an example.
 | 
			
		||||
 | 
			
		||||
.. code-block:: shell
 | 
			
		||||
 | 
			
		||||
   # use packaged capabilities-profile
 | 
			
		||||
   python-escpos cut
 | 
			
		||||
 | 
			
		||||
   # use capabilities-profile that you have put in /usr/python-escpos
 | 
			
		||||
   export ESCPOS_CAPABILITIES_FILE=/usr/python-escpos/capabilities.json
 | 
			
		||||
   python-escpos cut
 | 
			
		||||
 | 
			
		||||
   # use packaged file again
 | 
			
		||||
   unset ESCPOS_CAPABILITIES_FILE
 | 
			
		||||
   python-escpos cut
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Hint: preprocess printing
 | 
			
		||||
-------------------------
 | 
			
		||||
 | 
			
		||||
Printing images directly to the printer is rather slow.
 | 
			
		||||
One factor that slows down the process is the transmission over e.g. serial port.
 | 
			
		||||
 | 
			
		||||
Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much
 | 
			
		||||
that you can do.
 | 
			
		||||
However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image.
 | 
			
		||||
This is probably best explained by an example:
 | 
			
		||||
 | 
			
		||||
.. code-block:: Python
 | 
			
		||||
 | 
			
		||||
   from escpos.printer import Serial, Dummy
 | 
			
		||||
 | 
			
		||||
   p = Serial()
 | 
			
		||||
   d = Dummy()
 | 
			
		||||
 | 
			
		||||
   # create ESC/POS for the print job, this should go really fast
 | 
			
		||||
   d.text("This is my image:\n")
 | 
			
		||||
   d.image("funny_cat.png")
 | 
			
		||||
   d.cut()
 | 
			
		||||
 | 
			
		||||
   # send code to printer
 | 
			
		||||
   p._raw(d.output)
 | 
			
		||||
 | 
			
		||||
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.)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
__all__ = ["constants", "escpos", "exceptions", "printer"]
 | 
			
		||||
@@ -1,86 +0,0 @@
 | 
			
		||||
""" ESC/POS Commands (Constants) """
 | 
			
		||||
 | 
			
		||||
# Feed control sequences
 | 
			
		||||
CTL_LF     = '\x0a'               # Print and line feed
 | 
			
		||||
CTL_FF     = '\x0c'               # Form feed
 | 
			
		||||
CTL_CR     = '\x0d'               # Carriage return
 | 
			
		||||
CTL_HT     = '\x09'               # Horizontal tab
 | 
			
		||||
CTL_SET_HT = '\x1b\x44'           # Set horizontal tab positions
 | 
			
		||||
CTL_VT     = '\x1b\x64\x04'       # Vertical tab
 | 
			
		||||
# Printer hardware
 | 
			
		||||
HW_INIT    = '\x1b\x40'           # Clear data in buffer and reset modes
 | 
			
		||||
HW_SELECT  = '\x1b\x3d\x01'       # Printer select
 | 
			
		||||
HW_RESET   = '\x1b\x3f\x0a\x00'   # Reset printer hardware
 | 
			
		||||
# Cash Drawer
 | 
			
		||||
CD_KICK_2  = '\x1b\x70\x00'       # Sends a pulse to pin 2 []
 | 
			
		||||
CD_KICK_5  = '\x1b\x70\x01'       # Sends a pulse to pin 5 []
 | 
			
		||||
# Paper
 | 
			
		||||
PAPER_FULL_CUT  = '\x1d\x56\x00'  # Full cut paper
 | 
			
		||||
PAPER_PART_CUT  = '\x1d\x56\x01'  # Partial cut paper
 | 
			
		||||
# Text format
 | 
			
		||||
TXT_NORMAL      = '\x1b\x21\x00'  # Normal text
 | 
			
		||||
TXT_2HEIGHT     = '\x1b\x21\x10'  # Double height text
 | 
			
		||||
TXT_2WIDTH      = '\x1b\x21\x20'  # Double width text
 | 
			
		||||
TXT_4SQUARE     = '\x1b\x21\x30'  # Quad area text
 | 
			
		||||
TXT_UNDERL_OFF  = '\x1b\x2d\x00'  # Underline font OFF
 | 
			
		||||
TXT_UNDERL_ON   = '\x1b\x2d\x01'  # Underline font 1-dot ON
 | 
			
		||||
TXT_UNDERL2_ON  = '\x1b\x2d\x02'  # Underline font 2-dot ON
 | 
			
		||||
TXT_BOLD_OFF    = '\x1b\x45\x00'  # Bold font OFF
 | 
			
		||||
TXT_BOLD_ON     = '\x1b\x45\x01'  # Bold font ON
 | 
			
		||||
TXT_FONT_A      = '\x1b\x4d\x00'  # Font type A
 | 
			
		||||
TXT_FONT_B      = '\x1b\x4d\x01'  # Font type B
 | 
			
		||||
TXT_ALIGN_LT    = '\x1b\x61\x00'  # Left justification
 | 
			
		||||
TXT_ALIGN_CT    = '\x1b\x61\x01'  # Centering
 | 
			
		||||
TXT_ALIGN_RT    = '\x1b\x61\x02'  # Right justification
 | 
			
		||||
# Char code table
 | 
			
		||||
CHARCODE_PC437  = '\x1b\x74\x00'  # USA: Standard Europe
 | 
			
		||||
CHARCODE_JIS    = '\x1b\x74\x01'  # Japanese Katakana
 | 
			
		||||
CHARCODE_PC850  = '\x1b\x74\x02'  # Multilingual
 | 
			
		||||
CHARCODE_PC860  = '\x1b\x74\x03'  # Portuguese
 | 
			
		||||
CHARCODE_PC863  = '\x1b\x74\x04'  # Canadian-French
 | 
			
		||||
CHARCODE_PC865  = '\x1b\x74\x05'  # Nordic
 | 
			
		||||
CHARCODE_WEU    = '\x1b\x74\x06'  # Simplified Kanji, Hirakana
 | 
			
		||||
CHARCODE_GREEK  = '\x1b\x74\x07'  # Simplified Kanji
 | 
			
		||||
CHARCODE_HEBREW = '\x1b\x74\x08'  # Simplified Kanji
 | 
			
		||||
CHARCODE_PC1252 = '\x1b\x74\x11'  # Western European Windows Code Set
 | 
			
		||||
CHARCODE_PC866  = '\x1b\x74\x12'  # Cirillic #2
 | 
			
		||||
CHARCODE_PC852  = '\x1b\x74\x13'  # Latin 2
 | 
			
		||||
CHARCODE_PC858  = '\x1b\x74\x14'  # Euro
 | 
			
		||||
CHARCODE_THAI42 = '\x1b\x74\x15'  # Thai character code 42
 | 
			
		||||
CHARCODE_THAI11 = '\x1b\x74\x16'  # Thai character code 11
 | 
			
		||||
CHARCODE_THAI13 = '\x1b\x74\x17'  # Thai character code 13
 | 
			
		||||
CHARCODE_THAI14 = '\x1b\x74\x18'  # Thai character code 14
 | 
			
		||||
CHARCODE_THAI16 = '\x1b\x74\x19'  # Thai character code 16
 | 
			
		||||
CHARCODE_THAI17 = '\x1b\x74\x1a'  # Thai character code 17
 | 
			
		||||
CHARCODE_THAI18 = '\x1b\x74\x1b'  # Thai character code 18
 | 
			
		||||
# Barcode format
 | 
			
		||||
BARCODE_TXT_OFF = '\x1d\x48\x00'  # HRI barcode chars OFF
 | 
			
		||||
BARCODE_TXT_ABV = '\x1d\x48\x01'  # HRI barcode chars above
 | 
			
		||||
BARCODE_TXT_BLW = '\x1d\x48\x02'  # HRI barcode chars below
 | 
			
		||||
BARCODE_TXT_BTH = '\x1d\x48\x03'  # HRI barcode chars both above and below
 | 
			
		||||
BARCODE_FONT_A  = '\x1d\x66\x00'  # Font type A for HRI barcode chars
 | 
			
		||||
BARCODE_FONT_B  = '\x1d\x66\x01'  # Font type B for HRI barcode chars
 | 
			
		||||
BARCODE_HEIGHT  = '\x1d\x68\x64'  # Barcode Height [1-255]
 | 
			
		||||
BARCODE_WIDTH   = '\x1d\x77\x03'  # Barcode Width  [2-6]
 | 
			
		||||
BARCODE_UPC_A   = '\x1d\x6b\x00'  # Barcode type UPC-A
 | 
			
		||||
BARCODE_UPC_E   = '\x1d\x6b\x01'  # Barcode type UPC-E
 | 
			
		||||
BARCODE_EAN13   = '\x1d\x6b\x02'  # Barcode type EAN13
 | 
			
		||||
BARCODE_EAN8    = '\x1d\x6b\x03'  # Barcode type EAN8
 | 
			
		||||
BARCODE_CODE39  = '\x1d\x6b\x04'  # Barcode type CODE39
 | 
			
		||||
BARCODE_ITF     = '\x1d\x6b\x05'  # Barcode type ITF
 | 
			
		||||
BARCODE_NW7     = '\x1d\x6b\x06'  # Barcode type NW7
 | 
			
		||||
# Image format
 | 
			
		||||
S_RASTER_N      = '\x1d\x76\x30\x00'  # Set raster image normal size
 | 
			
		||||
S_RASTER_2W     = '\x1d\x76\x30\x01'  # Set raster image double width
 | 
			
		||||
S_RASTER_2H     = '\x1d\x76\x30\x02'  # Set raster image double height
 | 
			
		||||
S_RASTER_Q      = '\x1d\x76\x30\x03'  # Set raster image quadruple
 | 
			
		||||
# Printing Density
 | 
			
		||||
PD_N50          = '\x1d\x7c\x00'  # Printing Density -50%
 | 
			
		||||
PD_N37          = '\x1d\x7c\x01'  # Printing Density -37.5%
 | 
			
		||||
PD_N25          = '\x1d\x7c\x02'  # Printing Density -25%
 | 
			
		||||
PD_N12          = '\x1d\x7c\x03'  # Printing Density -12.5%
 | 
			
		||||
PD_0            = '\x1d\x7c\x04'  # Printing Density  0%
 | 
			
		||||
PD_P50          = '\x1d\x7c\x08'  # Printing Density +50%
 | 
			
		||||
PD_P37          = '\x1d\x7c\x07'  # Printing Density +37.5%
 | 
			
		||||
PD_P25          = '\x1d\x7c\x06'  # Printing Density +25%
 | 
			
		||||
PD_P12          = '\x1d\x7c\x05'  # Printing Density +12.5%
 | 
			
		||||
							
								
								
									
										468
									
								
								escpos/escpos.py
									
									
									
									
									
								
							
							
						
						@@ -1,468 +0,0 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""
 | 
			
		||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
 | 
			
		||||
@organization: Bashlinux
 | 
			
		||||
@copyright: Copyright (c) 2012 Bashlinux
 | 
			
		||||
@license: GNU GPL v3
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import Image
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
import qrcode
 | 
			
		||||
import time
 | 
			
		||||
import textwrap
 | 
			
		||||
import binascii
 | 
			
		||||
 | 
			
		||||
from .constants import *
 | 
			
		||||
from .exceptions import *
 | 
			
		||||
 | 
			
		||||
from abc import ABCMeta, abstractmethod  # abstract base class support
 | 
			
		||||
 | 
			
		||||
class Escpos(object):
 | 
			
		||||
    """ ESC/POS Printer object """
 | 
			
		||||
    __metaclass__ = ABCMeta
 | 
			
		||||
    device = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, columns=32):
 | 
			
		||||
        """ Initialize ESCPOS Printer
 | 
			
		||||
 | 
			
		||||
        :param columns: Text columns used by the printer. Defaults to 32."""
 | 
			
		||||
        self.columns = columns
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Sends raw data to the printer
 | 
			
		||||
 | 
			
		||||
        This function has to be individually implemented by the implementations.
 | 
			
		||||
        :param msg: message string to be sent to the printer
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _check_image_size(size):
 | 
			
		||||
        """ Check and fix the size of the image to 32 bits
 | 
			
		||||
 | 
			
		||||
        :param size: size of the image
 | 
			
		||||
        :returns: tuple of image borders
 | 
			
		||||
        :rtype: (int, int)
 | 
			
		||||
        """
 | 
			
		||||
        if size % 32 == 0:
 | 
			
		||||
            return 0, 0
 | 
			
		||||
        else:
 | 
			
		||||
            image_border = 32 - (size % 32)
 | 
			
		||||
            if (image_border % 2) == 0:
 | 
			
		||||
                return (round(image_border / 2), round(image_border / 2))
 | 
			
		||||
            else:
 | 
			
		||||
                return (round(image_border / 2), round((image_border / 2) + 1))
 | 
			
		||||
 | 
			
		||||
    def _print_image(self, line, size):
 | 
			
		||||
        """ Print formatted image
 | 
			
		||||
 | 
			
		||||
        :param line:
 | 
			
		||||
        :param size:
 | 
			
		||||
        """
 | 
			
		||||
        i = 0
 | 
			
		||||
        cont = 0
 | 
			
		||||
        pbuffer = ""
 | 
			
		||||
 | 
			
		||||
        self._raw(S_RASTER_N)
 | 
			
		||||
        pbuffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1] & 0xff, size[1] >> 8)
 | 
			
		||||
        self._raw(binascii.unhexlify(pbuffer))
 | 
			
		||||
        pbuffer = ""
 | 
			
		||||
 | 
			
		||||
        while i < len(line):
 | 
			
		||||
            hex_string = int(line[i:i+8], 2)
 | 
			
		||||
            pbuffer += "%02X" % hex_string
 | 
			
		||||
            i += 8
 | 
			
		||||
            cont += 1
 | 
			
		||||
            if cont % 4 == 0:
 | 
			
		||||
                self._raw(binascii.unhexlify(pbuffer))
 | 
			
		||||
                pbuffer = ""
 | 
			
		||||
                cont = 0
 | 
			
		||||
 | 
			
		||||
    def _convert_image(self, im):
 | 
			
		||||
        """ Parse image and prepare it to a printable format
 | 
			
		||||
 | 
			
		||||
        :param im: image data
 | 
			
		||||
        :raises: ImageSizeError
 | 
			
		||||
        """
 | 
			
		||||
        pixels = []
 | 
			
		||||
        pix_line = ""
 | 
			
		||||
        im_left = ""
 | 
			
		||||
        im_right = ""
 | 
			
		||||
        switch = 0
 | 
			
		||||
        img_size = [0, 0]
 | 
			
		||||
 | 
			
		||||
        if im.size[0] > 512:
 | 
			
		||||
            print ("WARNING: Image is wider than 512 and could be truncated at print time ")
 | 
			
		||||
        if im.size[1] > 0xffff:
 | 
			
		||||
            raise ImageSizeError()
 | 
			
		||||
 | 
			
		||||
        im_border = self._check_image_size(im.size[0])
 | 
			
		||||
        for i in range(im_border[0]):
 | 
			
		||||
            im_left += "0"
 | 
			
		||||
        for i in range(im_border[1]):
 | 
			
		||||
            im_right += "0"
 | 
			
		||||
 | 
			
		||||
        for y in range(im.size[1]):
 | 
			
		||||
            img_size[1] += 1
 | 
			
		||||
            pix_line += im_left
 | 
			
		||||
            img_size[0] += im_border[0]
 | 
			
		||||
            for x in range(im.size[0]):
 | 
			
		||||
                img_size[0] += 1
 | 
			
		||||
                RGB = im.getpixel((x, y))
 | 
			
		||||
                im_color = (RGB[0] + RGB[1] + RGB[2])
 | 
			
		||||
                im_pattern = "1X0"
 | 
			
		||||
                pattern_len = len(im_pattern)
 | 
			
		||||
                switch = (switch - 1) * (-1)
 | 
			
		||||
                for x in range(pattern_len):
 | 
			
		||||
                    if im_color <= (255 * 3 / pattern_len * (x+1)):
 | 
			
		||||
                        if im_pattern[x] == "X":
 | 
			
		||||
                            pix_line += "%d" % switch
 | 
			
		||||
                        else:
 | 
			
		||||
                            pix_line += im_pattern[x]
 | 
			
		||||
                        break
 | 
			
		||||
                    elif (255 * 3 / pattern_len * pattern_len) < im_color <= (255 * 3):
 | 
			
		||||
                        pix_line += im_pattern[-1]
 | 
			
		||||
                        break
 | 
			
		||||
            pix_line += im_right
 | 
			
		||||
            img_size[0] += im_border[1]
 | 
			
		||||
 | 
			
		||||
        self._print_image(pix_line, img_size)
 | 
			
		||||
 | 
			
		||||
    def image(self, path_img):
 | 
			
		||||
        """ Open image file
 | 
			
		||||
 | 
			
		||||
        :param path_img: path to image
 | 
			
		||||
        """
 | 
			
		||||
        im_open = Image.open(path_img)
 | 
			
		||||
 | 
			
		||||
        # Remove the alpha channel on transparent images
 | 
			
		||||
        if im_open.mode == 'RGBA':
 | 
			
		||||
            im_open.load()
 | 
			
		||||
            im = Image.new("RGB", im_open.size, (255, 255, 255))
 | 
			
		||||
            im.paste(im_open, mask=im_open.split()[3])
 | 
			
		||||
        else:
 | 
			
		||||
            im = im_open.convert("RGB")
 | 
			
		||||
 | 
			
		||||
        # Convert the RGB image in printable image
 | 
			
		||||
        self._convert_image(im)
 | 
			
		||||
 | 
			
		||||
    def direct_image(self, image):
 | 
			
		||||
        """ Send image to printer"""
 | 
			
		||||
        mask = 0x80
 | 
			
		||||
        i = 0
 | 
			
		||||
        temp = 0
 | 
			
		||||
 | 
			
		||||
        (width, height) = image.size
 | 
			
		||||
        self._raw(S_RASTER_N)
 | 
			
		||||
        headerX = int(width / 8)
 | 
			
		||||
        headerY = height
 | 
			
		||||
        buf = "%02X" % (headerX & 0xff)
 | 
			
		||||
        buf += "%02X" % ((headerX >> 8) & 0xff)
 | 
			
		||||
        buf += "%02X" % (headerY & 0xff)
 | 
			
		||||
        buf += "%02X" % ((headerY >> 8) & 0xff)
 | 
			
		||||
        #self._raw(binascii.unhexlify(buf))
 | 
			
		||||
        for y in range(height):
 | 
			
		||||
            for x in range(width):
 | 
			
		||||
                value = image.getpixel((x,y))
 | 
			
		||||
                value = (value << 8) | value;
 | 
			
		||||
                if value == 0:
 | 
			
		||||
                    temp |= mask
 | 
			
		||||
 | 
			
		||||
                mask = mask >> 1
 | 
			
		||||
 | 
			
		||||
                i += 1
 | 
			
		||||
                if i == 8:
 | 
			
		||||
                    buf +=   ("%02X" % temp)
 | 
			
		||||
                    mask = 0x80
 | 
			
		||||
                    i = 0
 | 
			
		||||
                    temp = 0
 | 
			
		||||
        self._raw(binascii.unhexlify(bytes(buf, "ascii")))
 | 
			
		||||
        self._raw('\n')
 | 
			
		||||
 | 
			
		||||
    def qr(self, text):
 | 
			
		||||
        """ Print QR Code for the provided string
 | 
			
		||||
 | 
			
		||||
        :param text: text to generate a QR-Code from
 | 
			
		||||
        """
 | 
			
		||||
        qr_code = qrcode.QRCode(version=4, box_size=4, border=1)
 | 
			
		||||
        qr_code.add_data(text)
 | 
			
		||||
        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._convert_image(im)
 | 
			
		||||
 | 
			
		||||
    def charcode(self, code):
 | 
			
		||||
        """ Set Character Code Table
 | 
			
		||||
 | 
			
		||||
        Sends the control sequence from constants.py to the printer with :py:meth:`escpos.printer._raw()`.
 | 
			
		||||
 | 
			
		||||
        :param code: Name of CharCode
 | 
			
		||||
        :raises: CharCodeError
 | 
			
		||||
        """
 | 
			
		||||
        if code.upper() == "USA":
 | 
			
		||||
            self._raw(CHARCODE_PC437)
 | 
			
		||||
        elif code.upper() == "JIS":
 | 
			
		||||
            self._raw(CHARCODE_JIS)
 | 
			
		||||
        elif code.upper() == "MULTILINGUAL":
 | 
			
		||||
            self._raw(CHARCODE_PC850)
 | 
			
		||||
        elif code.upper() == "PORTUGUESE":
 | 
			
		||||
            self._raw(CHARCODE_PC860)
 | 
			
		||||
        elif code.upper() == "CA_FRENCH":
 | 
			
		||||
            self._raw(CHARCODE_PC863)
 | 
			
		||||
        elif code.upper() == "NORDIC":
 | 
			
		||||
            self._raw(CHARCODE_PC865)
 | 
			
		||||
        elif code.upper() == "WEST_EUROPE":
 | 
			
		||||
            self._raw(CHARCODE_WEU)
 | 
			
		||||
        elif code.upper() == "GREEK":
 | 
			
		||||
            self._raw(CHARCODE_GREEK)
 | 
			
		||||
        elif code.upper() == "HEBREW":
 | 
			
		||||
            self._raw(CHARCODE_HEBREW)
 | 
			
		||||
        # elif code.upper() == "LATVIAN":  # this is not listed in the constants
 | 
			
		||||
        #    self._raw(CHARCODE_PC755)
 | 
			
		||||
        elif code.upper() == "WPC1252":
 | 
			
		||||
            self._raw(CHARCODE_PC1252)
 | 
			
		||||
        elif code.upper() == "CIRILLIC2":
 | 
			
		||||
            self._raw(CHARCODE_PC866)
 | 
			
		||||
        elif code.upper() == "LATIN2":
 | 
			
		||||
            self._raw(CHARCODE_PC852)
 | 
			
		||||
        elif code.upper() == "EURO":
 | 
			
		||||
            self._raw(CHARCODE_PC858)
 | 
			
		||||
        elif code.upper() == "THAI42":
 | 
			
		||||
            self._raw(CHARCODE_THAI42)
 | 
			
		||||
        elif code.upper() == "THAI11":
 | 
			
		||||
            self._raw(CHARCODE_THAI11)
 | 
			
		||||
        elif code.upper() == "THAI13":
 | 
			
		||||
            self._raw(CHARCODE_THAI13)
 | 
			
		||||
        elif code.upper() == "THAI14":
 | 
			
		||||
            self._raw(CHARCODE_THAI14)
 | 
			
		||||
        elif code.upper() == "THAI16":
 | 
			
		||||
            self._raw(CHARCODE_THAI16)
 | 
			
		||||
        elif code.upper() == "THAI17":
 | 
			
		||||
            self._raw(CHARCODE_THAI17)
 | 
			
		||||
        elif code.upper() == "THAI18":
 | 
			
		||||
            self._raw(CHARCODE_THAI18)
 | 
			
		||||
        else:
 | 
			
		||||
            raise CharCodeError()
 | 
			
		||||
 | 
			
		||||
    def barcode(self, code, bc, width, height, pos, font):
 | 
			
		||||
        """ Print Barcode
 | 
			
		||||
 | 
			
		||||
        :param code: data for barcode
 | 
			
		||||
        :param bc: barcode format, see constants.py
 | 
			
		||||
        :param width: barcode width, has to be between 1 and 255
 | 
			
		||||
        :param height: barcode height, has to be between 2 and 6
 | 
			
		||||
        :param pos: position of text in barcode, default when nothing supplied is below
 | 
			
		||||
        :param font: select font, default is font A
 | 
			
		||||
        :raises: BarcodeSizeError, BarcodeTypeError, BarcodeCodeError
 | 
			
		||||
        """
 | 
			
		||||
        # Align Bar Code()
 | 
			
		||||
        self._raw(TXT_ALIGN_CT)
 | 
			
		||||
        # Height
 | 
			
		||||
        if height >= 2 or height <= 6:
 | 
			
		||||
            self._raw(BARCODE_HEIGHT)
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeSizeError()
 | 
			
		||||
        # Width
 | 
			
		||||
        if width >= 1 or width <= 255:
 | 
			
		||||
            self._raw(BARCODE_WIDTH)
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeSizeError()
 | 
			
		||||
        # Font
 | 
			
		||||
        if font.upper() == "B":
 | 
			
		||||
            self._raw(BARCODE_FONT_B)
 | 
			
		||||
        else:  # DEFAULT FONT: A
 | 
			
		||||
            self._raw(BARCODE_FONT_A)
 | 
			
		||||
        # Position
 | 
			
		||||
        if pos.upper() == "OFF":
 | 
			
		||||
            self._raw(BARCODE_TXT_OFF)
 | 
			
		||||
        elif pos.upper() == "BOTH":
 | 
			
		||||
            self._raw(BARCODE_TXT_BTH)
 | 
			
		||||
        elif pos.upper() == "ABOVE":
 | 
			
		||||
            self._raw(BARCODE_TXT_ABV)
 | 
			
		||||
        else:  # DEFAULT POSITION: BELOW
 | 
			
		||||
            self._raw(BARCODE_TXT_BLW)
 | 
			
		||||
        # Type
 | 
			
		||||
        if bc.upper() == "UPC-A":
 | 
			
		||||
            self._raw(BARCODE_UPC_A)
 | 
			
		||||
        elif bc.upper() == "UPC-E":
 | 
			
		||||
            self._raw(BARCODE_UPC_E)
 | 
			
		||||
        elif bc.upper() == "EAN13":
 | 
			
		||||
            self._raw(BARCODE_EAN13)
 | 
			
		||||
        elif bc.upper() == "EAN8":
 | 
			
		||||
            self._raw(BARCODE_EAN8)
 | 
			
		||||
        elif bc.upper() == "CODE39":
 | 
			
		||||
            self._raw(BARCODE_CODE39)
 | 
			
		||||
        elif bc.upper() == "ITF":
 | 
			
		||||
            self._raw(BARCODE_ITF)
 | 
			
		||||
        elif bc.upper() == "NW7":
 | 
			
		||||
            self._raw(BARCODE_NW7)
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeTypeError()
 | 
			
		||||
        # Print Code
 | 
			
		||||
        if code:
 | 
			
		||||
            self._raw(code)
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeCodeError()
 | 
			
		||||
 | 
			
		||||
    def text(self, txt):
 | 
			
		||||
        """ Print alpha-numeric text
 | 
			
		||||
 | 
			
		||||
        The text has to be encoded in the currently selected codepage.
 | 
			
		||||
        :param txt: text to be printed
 | 
			
		||||
        :raises: TextError
 | 
			
		||||
        """
 | 
			
		||||
        if txt:
 | 
			
		||||
            self._raw(txt)
 | 
			
		||||
        else:
 | 
			
		||||
            raise TextError()
 | 
			
		||||
 | 
			
		||||
    def block_text(self, txt, columns=None):
 | 
			
		||||
        '''Text is printed wrapped to specified columns'''
 | 
			
		||||
        colCount = self.columns if columns == None else columns
 | 
			
		||||
        self.text(textwrap.fill(txt, colCount))
 | 
			
		||||
 | 
			
		||||
    def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9):
 | 
			
		||||
        """ Set text properties by sending them to the printer
 | 
			
		||||
 | 
			
		||||
        :param align: alignment of text
 | 
			
		||||
        :param font: font A or B
 | 
			
		||||
        :param text_type: add bold or underlined
 | 
			
		||||
        :param width: text width, normal or double width
 | 
			
		||||
        :param height: text height, normal or double height
 | 
			
		||||
        :param density: print density
 | 
			
		||||
        """
 | 
			
		||||
        # Width
 | 
			
		||||
        if height == 2 and width == 2:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_4SQUARE)
 | 
			
		||||
        elif height == 2 and width != 2:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_2HEIGHT)
 | 
			
		||||
        elif width == 2 and height != 2:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_2WIDTH)
 | 
			
		||||
        else:  # DEFAULT SIZE: NORMAL
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
        # Type
 | 
			
		||||
        if text_type.upper() == "B":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL_OFF)
 | 
			
		||||
        elif text_type.upper() == "U":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL_ON)
 | 
			
		||||
        elif text_type.upper() == "U2":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL2_ON)
 | 
			
		||||
        elif text_type.upper() == "BU":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL_ON)
 | 
			
		||||
        elif text_type.upper() == "BU2":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL2_ON)
 | 
			
		||||
        elif text_type.upper == "NORMAL":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL_OFF)
 | 
			
		||||
        # Font
 | 
			
		||||
        if font.upper() == "B":
 | 
			
		||||
            self._raw(TXT_FONT_B)
 | 
			
		||||
        else:  # DEFAULT FONT: A
 | 
			
		||||
            self._raw(TXT_FONT_A)
 | 
			
		||||
        # Align
 | 
			
		||||
        if align.upper() == "CENTER":
 | 
			
		||||
            self._raw(TXT_ALIGN_CT)
 | 
			
		||||
        elif align.upper() == "RIGHT":
 | 
			
		||||
            self._raw(TXT_ALIGN_RT)
 | 
			
		||||
        elif align.upper() == "LEFT":
 | 
			
		||||
            self._raw(TXT_ALIGN_LT)
 | 
			
		||||
        # Density
 | 
			
		||||
        if density == 0:
 | 
			
		||||
            self._raw(PD_N50)
 | 
			
		||||
        elif density == 1:
 | 
			
		||||
            self._raw(PD_N37)
 | 
			
		||||
        elif density == 2:
 | 
			
		||||
            self._raw(PD_N25)
 | 
			
		||||
        elif density == 3:
 | 
			
		||||
            self._raw(PD_N12)
 | 
			
		||||
        elif density == 4:
 | 
			
		||||
            self._raw(PD_0)
 | 
			
		||||
        elif density == 5:
 | 
			
		||||
            self._raw(PD_P12)
 | 
			
		||||
        elif density == 6:
 | 
			
		||||
            self._raw(PD_P25)
 | 
			
		||||
        elif density == 7:
 | 
			
		||||
            self._raw(PD_P37)
 | 
			
		||||
        elif density == 8:
 | 
			
		||||
            self._raw(PD_P50)
 | 
			
		||||
        else:  # DEFAULT: DOES NOTHING
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def cut(self, mode=''):
 | 
			
		||||
        """ Cut paper
 | 
			
		||||
 | 
			
		||||
        :param mode: set to 'PART' for a partial cut
 | 
			
		||||
        """
 | 
			
		||||
        # Fix the size between last line and cut
 | 
			
		||||
        # TODO: handle this with a line feed
 | 
			
		||||
        self._raw("\n\n\n\n\n\n")
 | 
			
		||||
        if mode.upper() == "PART":
 | 
			
		||||
            self._raw(PAPER_PART_CUT)
 | 
			
		||||
        else:  # DEFAULT MODE: FULL CUT
 | 
			
		||||
            self._raw(PAPER_FULL_CUT)
 | 
			
		||||
 | 
			
		||||
    def cashdraw(self, pin):
 | 
			
		||||
        """ Send pulse to kick the cash drawer
 | 
			
		||||
 | 
			
		||||
        Kick cash drawer on pin 2 or pin 5.
 | 
			
		||||
        :param pin: pin number
 | 
			
		||||
        :raises: CashDrawerError
 | 
			
		||||
        """
 | 
			
		||||
        if pin == 2:
 | 
			
		||||
            self._raw(CD_KICK_2)
 | 
			
		||||
        elif pin == 5:
 | 
			
		||||
            self._raw(CD_KICK_5)
 | 
			
		||||
        else:
 | 
			
		||||
            raise CashDrawerError()
 | 
			
		||||
 | 
			
		||||
    def hw(self, hw):
 | 
			
		||||
        """ Hardware operations
 | 
			
		||||
 | 
			
		||||
        :param hw: hardware action
 | 
			
		||||
        """
 | 
			
		||||
        if hw.upper() == "INIT":
 | 
			
		||||
            self._raw(HW_INIT)
 | 
			
		||||
        elif hw.upper() == "SELECT":
 | 
			
		||||
            self._raw(HW_SELECT)
 | 
			
		||||
        elif hw.upper() == "RESET":
 | 
			
		||||
            self._raw(HW_RESET)
 | 
			
		||||
        else:  # DEFAULT: DOES NOTHING
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def control(self, ctl, pos=4):
 | 
			
		||||
        """ Feed control sequences
 | 
			
		||||
 | 
			
		||||
        :raises: TabPosError
 | 
			
		||||
        """
 | 
			
		||||
        # Set tab positions
 | 
			
		||||
        if pos < 1 or pos > 16:
 | 
			
		||||
            raise TabPosError()
 | 
			
		||||
        else:
 | 
			
		||||
            self._raw("".join([CTL_SET_HT, hex(pos)]))
 | 
			
		||||
        # Set position
 | 
			
		||||
        if ctl.upper() == "LF":
 | 
			
		||||
            self._raw(CTL_LF)
 | 
			
		||||
        elif ctl.upper() == "FF":
 | 
			
		||||
            self._raw(CTL_FF)
 | 
			
		||||
        elif ctl.upper() == "CR":
 | 
			
		||||
            self._raw(CTL_CR)
 | 
			
		||||
        elif ctl.upper() == "HT":
 | 
			
		||||
            self._raw(CTL_HT)
 | 
			
		||||
        elif ctl.upper() == "VT":
 | 
			
		||||
            self._raw(CTL_VT)
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
""" ESC/POS Exceptions classes """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Error(Exception):
 | 
			
		||||
    """ Base class for ESC/POS errors """
 | 
			
		||||
    def __init__(self, msg, status=None):
 | 
			
		||||
        Exception.__init__(self)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 1
 | 
			
		||||
        if status is not None:
 | 
			
		||||
            self.resultcode = status
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.msg
 | 
			
		||||
 | 
			
		||||
# Result/Exit codes
 | 
			
		||||
# 0  = success
 | 
			
		||||
# 10 = No Barcode type defined
 | 
			
		||||
# 20 = Barcode size values are out of range
 | 
			
		||||
# 30 = Barcode text not supplied
 | 
			
		||||
# 40 = Image height is too large
 | 
			
		||||
# 50 = No string supplied to be printed
 | 
			
		||||
# 60 = Invalid pin to send Cash Drawer pulse
 | 
			
		||||
# 70 = Invalid number of tab positions
 | 
			
		||||
# 80 = Invalid char code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeTypeError(Error):
 | 
			
		||||
    """No Barcode type defined """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 10
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "No Barcode type is defined"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeSizeError(Error):
 | 
			
		||||
    """ Barcode size is out of range """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 20
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Barcode size is out of range"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeCodeError(Error):
 | 
			
		||||
    """ No Barcode code was supplied """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 30
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "No Barcode code was supplied"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImageSizeError(Error):
 | 
			
		||||
    """ Image height is longer than 255px and can't be printed """
 | 
			
		||||
    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"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TextError(Error):
 | 
			
		||||
    """ Test sting must be supplied to the text() method """
 | 
			
		||||
    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"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CashDrawerError(Error):
 | 
			
		||||
    """ Valid pin must be set to send pulse """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 60
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Valid pin must be set to send pulse"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TabPosError(Error):
 | 
			
		||||
    """ Valid tab positions must be in the range 0 to 16 """
 | 
			
		||||
    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"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CharCodeError(Error):
 | 
			
		||||
    """ Valid char code must be set """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 70
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Valid char code must be set"
 | 
			
		||||
@@ -1,187 +0,0 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""
 | 
			
		||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
 | 
			
		||||
@organization: Bashlinux
 | 
			
		||||
@copyright: Copyright (c) 2012 Bashlinux
 | 
			
		||||
@license: GNU GPL v3
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import usb.core
 | 
			
		||||
import usb.util
 | 
			
		||||
import serial
 | 
			
		||||
import socket
 | 
			
		||||
 | 
			
		||||
from .escpos import *
 | 
			
		||||
from .constants import *
 | 
			
		||||
from .exceptions import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Usb(Escpos):
 | 
			
		||||
    """ Define USB printer """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        :param idVendor: Vendor ID
 | 
			
		||||
        :param idProduct: Product ID
 | 
			
		||||
        :param interface: USB device interface
 | 
			
		||||
        :param in_ep: Input end point
 | 
			
		||||
        :param out_ep: Output end point
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.idVendor  = idVendor
 | 
			
		||||
        self.idProduct = idProduct
 | 
			
		||||
        self.interface = interface
 | 
			
		||||
        self.in_ep = in_ep
 | 
			
		||||
        self.out_ep = out_ep
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Search device on USB tree and set it as escpos device """
 | 
			
		||||
        self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
 | 
			
		||||
        if self.device is None:
 | 
			
		||||
            print("Cable isn't plugged in")
 | 
			
		||||
 | 
			
		||||
        check_driver = None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            check_driver = self.device.is_kernel_driver_active(0)
 | 
			
		||||
        except NotImplementedError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if check_driver is None or check_driver:
 | 
			
		||||
            try:
 | 
			
		||||
                self.device.detach_kernel_driver(0)
 | 
			
		||||
            except usb.core.USBError as e:
 | 
			
		||||
                if check_driver is not None:
 | 
			
		||||
                    print("Could not detatch kernel driver: %s" % str(e))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.device.set_configuration()
 | 
			
		||||
            self.device.reset()
 | 
			
		||||
        except usb.core.USBError as e:
 | 
			
		||||
            print("Could not set configuration: %s" % str(e))
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format """
 | 
			
		||||
        self.device.write(self.out_ep, msg, self.interface)
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        """ Release USB interface """
 | 
			
		||||
        if self.device:
 | 
			
		||||
            usb.util.dispose_resources(self.device)
 | 
			
		||||
        self.device = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Serial(Escpos):
 | 
			
		||||
    """ Define Serial printer """
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
        @param baudrate : Baud rate for serial transmission
 | 
			
		||||
        @param bytesize : Serial buffer size
 | 
			
		||||
        @param timeout  : Read/Write timeout
 | 
			
		||||
 | 
			
		||||
        @param parity   : Parity checking
 | 
			
		||||
        @param stopbits : Number of stop bits
 | 
			
		||||
        @param xonxoff  : Software flow control
 | 
			
		||||
        @param dsrdtr   : Hardware flow control (False to enable RTS/CTS)
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.devfile  = devfile
 | 
			
		||||
        self.baudrate = baudrate
 | 
			
		||||
        self.bytesize = bytesize
 | 
			
		||||
        self.timeout  = timeout
 | 
			
		||||
        self.parity = parity
 | 
			
		||||
        self.stopbits = stopbits
 | 
			
		||||
        self.xonxoff = xonxoff
 | 
			
		||||
        self.dsrdtr = dsrdtr
 | 
			
		||||
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Setup serial port and set is as escpos device """
 | 
			
		||||
        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")
 | 
			
		||||
        else:
 | 
			
		||||
            print("Unable to open serial printer on: %s" % self.devfile)
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format """
 | 
			
		||||
        self.device.write(msg)
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        """ Close Serial interface """
 | 
			
		||||
        if self.device is not None:
 | 
			
		||||
            self.device.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Network(Escpos):
 | 
			
		||||
    """ Define Network printer """
 | 
			
		||||
 | 
			
		||||
    def __init__(self,host,port=9100, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        @param host : Printer's hostname or IP address
 | 
			
		||||
        @param port : Port to write to
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.host = host
 | 
			
		||||
        self.port = port
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Open TCP socket and set it as escpos device """
 | 
			
		||||
        self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
			
		||||
        self.device.connect((self.host, self.port))
 | 
			
		||||
 | 
			
		||||
        if self.device is None:
 | 
			
		||||
            print("Could not open socket for %s" % self.host)
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format """
 | 
			
		||||
        self.device.send(msg)
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        """ Close TCP connection """
 | 
			
		||||
        self.device.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class File(Escpos):
 | 
			
		||||
    """ Define Generic file printer """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, devfile="/dev/usb/lp0", *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        @param devfile : Device file under dev filesystem
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.devfile = devfile
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Open system file """
 | 
			
		||||
        self.device = open(self.devfile, "wb")
 | 
			
		||||
 | 
			
		||||
        if self.device is None:
 | 
			
		||||
            print("Could not open the specified file %s" % self.devfile)
 | 
			
		||||
 | 
			
		||||
    def flush(self):
 | 
			
		||||
        """Flush printing content"""
 | 
			
		||||
        self.device.flush()
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format """
 | 
			
		||||
        if type(msg) is str:
 | 
			
		||||
            self.device.write(msg.encode());
 | 
			
		||||
        else:
 | 
			
		||||
            self.device.write(msg);
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        """ Close system file """
 | 
			
		||||
        self.device.close()
 | 
			
		||||
							
								
								
									
										63
									
								
								examples/codepage_tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,63 @@
 | 
			
		||||
"""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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    dummy = printer.Dummy()
 | 
			
		||||
 | 
			
		||||
    dummy.hw('init')
 | 
			
		||||
 | 
			
		||||
    for codepage in sys.argv[1:] or ['USA']:
 | 
			
		||||
        dummy.set(height=2, width=2)
 | 
			
		||||
        dummy._raw(codepage + "\n\n\n")
 | 
			
		||||
        print_codepage(dummy, codepage)
 | 
			
		||||
        dummy._raw("\n\n")
 | 
			
		||||
 | 
			
		||||
    dummy.cut()
 | 
			
		||||
 | 
			
		||||
    print(dummy.output)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_codepage(printer, codepage):
 | 
			
		||||
    if codepage.isdigit():
 | 
			
		||||
        codepage = int(codepage)
 | 
			
		||||
        printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
 | 
			
		||||
        printer._raw("after")
 | 
			
		||||
    else:
 | 
			
		||||
        printer.charcode(codepage)
 | 
			
		||||
 | 
			
		||||
    sep = ""
 | 
			
		||||
 | 
			
		||||
    # Table header
 | 
			
		||||
    printer.set(text_type='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(text_type='B')
 | 
			
		||||
        printer._raw("{} ".format(hex(x)[2:]))
 | 
			
		||||
        printer.set()
 | 
			
		||||
 | 
			
		||||
        for y in range(0, 16):
 | 
			
		||||
            byte = six.int2byte(x * 16 + y)
 | 
			
		||||
 | 
			
		||||
            if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
 | 
			
		||||
                byte = ' '
 | 
			
		||||
 | 
			
		||||
            printer._raw(byte)
 | 
			
		||||
            printer._raw(sep)
 | 
			
		||||
        printer._raw('\n')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										7
									
								
								readthedocs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
			
		||||
formats:
 | 
			
		||||
  - pdf
 | 
			
		||||
  - epub
 | 
			
		||||
requirements_file: doc/requirements.txt
 | 
			
		||||
python:
 | 
			
		||||
  version: 2
 | 
			
		||||
  setup_py_install: true
 | 
			
		||||
							
								
								
									
										12
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
[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
 | 
			
		||||
# future-imports = absolute_import, division, print_function, unicode_literals  # we are not there yet
 | 
			
		||||
							
								
								
									
										142
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						@@ -1,35 +1,141 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
from setuptools import find_packages, setup
 | 
			
		||||
from setuptools.command.test import test as test_command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
base_dir = os.path.dirname(__file__)
 | 
			
		||||
src_dir = os.path.join(base_dir, "src")
 | 
			
		||||
 | 
			
		||||
# When executing the setup.py, we need to be able to import ourselves, this
 | 
			
		||||
# means that we need to add the src/ directory to the sys.path.
 | 
			
		||||
sys.path.insert(0, src_dir)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read(fname):
 | 
			
		||||
    """read file from same path as setup.py"""
 | 
			
		||||
    return open(os.path.join(os.path.dirname(__file__), fname)).read()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tox(test_command):
 | 
			
		||||
    """proxy class that enables tox to be run with setup.py test"""
 | 
			
		||||
    user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
 | 
			
		||||
 | 
			
		||||
    def initialize_options(self):
 | 
			
		||||
        """initialize the user-options"""
 | 
			
		||||
        test_command.initialize_options(self)
 | 
			
		||||
        self.tox_args = None
 | 
			
		||||
 | 
			
		||||
    def finalize_options(self):
 | 
			
		||||
        """finalize user-options"""
 | 
			
		||||
        test_command.finalize_options(self)
 | 
			
		||||
        self.test_args = []
 | 
			
		||||
        self.test_suite = True
 | 
			
		||||
 | 
			
		||||
    def run_tests(self):
 | 
			
		||||
        """run tox and pass on user-options"""
 | 
			
		||||
        # import here, cause outside the eggs aren't loaded
 | 
			
		||||
        import tox
 | 
			
		||||
        import shlex
 | 
			
		||||
        args = self.tox_args
 | 
			
		||||
        if args:
 | 
			
		||||
            args = shlex.split(self.tox_args)
 | 
			
		||||
        errno = tox.cmdline(args=args)
 | 
			
		||||
        sys.exit(errno)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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}'
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from distutils.core import setup
 | 
			
		||||
 | 
			
		||||
setup(
 | 
			
		||||
    name='escpos',
 | 
			
		||||
    version='1.0.8',
 | 
			
		||||
    url='https://github.com/manpaz/python-escpos',
 | 
			
		||||
    download_url='https://github.com/manpaz/python-escpos.git',
 | 
			
		||||
    name='python-escpos',
 | 
			
		||||
    use_scm_version={
 | 
			
		||||
        'write_to': 'src/escpos/version.py',
 | 
			
		||||
        'write_to_template': setuptools_scm_template,
 | 
			
		||||
    },
 | 
			
		||||
    url='https://github.com/python-escpos/python-escpos',
 | 
			
		||||
    download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
 | 
			
		||||
    description='Python library to manipulate ESC/POS Printers',
 | 
			
		||||
    license='GNU GPL v3',
 | 
			
		||||
    long_description=open('README').read(),
 | 
			
		||||
    author='Manuel F Martinez',
 | 
			
		||||
    bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
 | 
			
		||||
    license='MIT',
 | 
			
		||||
    long_description=read('README.rst'),
 | 
			
		||||
    author='Manuel F Martinez and others',
 | 
			
		||||
    author_email='manpaz@bashlinux.com',
 | 
			
		||||
    platforms=['linux'],
 | 
			
		||||
    packages=[
 | 
			
		||||
        'escpos',
 | 
			
		||||
    maintainer='Patrick Kanzler',
 | 
			
		||||
    maintainer_email='dev@pkanzler.de',
 | 
			
		||||
    keywords=[
 | 
			
		||||
        'ESC/POS',
 | 
			
		||||
        'thermoprinter',
 | 
			
		||||
        'voucher printer',
 | 
			
		||||
        'printing',
 | 
			
		||||
        'receipt,',
 | 
			
		||||
    ],
 | 
			
		||||
    package_data={'': ['COPYING']},
 | 
			
		||||
    platforms='any',
 | 
			
		||||
    package_dir={"": "src"},
 | 
			
		||||
    packages=find_packages(where="src", exclude=["tests", "tests.*"]),
 | 
			
		||||
    package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
 | 
			
		||||
    include_package_data=True,
 | 
			
		||||
    classifiers=[
 | 
			
		||||
        'Development Status :: 1 - Alpha',
 | 
			
		||||
        'License :: OSI Approved :: GNU GPL v3',
 | 
			
		||||
        'Operating System :: GNU/Linux',
 | 
			
		||||
        'Development Status :: 4 - Beta',
 | 
			
		||||
        'Environment :: Console',
 | 
			
		||||
        'Intended Audience :: Developers',
 | 
			
		||||
        'License :: OSI Approved :: MIT License',
 | 
			
		||||
        'Operating System :: OS Independent',
 | 
			
		||||
        'Programming Language :: Python',
 | 
			
		||||
        'Topic :: System :: Peripherals',
 | 
			
		||||
        'Programming Language :: Python :: 2',
 | 
			
		||||
        'Programming Language :: Python :: 2.7',
 | 
			
		||||
        'Programming Language :: Python :: 3',
 | 
			
		||||
        'Programming Language :: Python :: 3.3',
 | 
			
		||||
        'Programming Language :: Python :: 3.4',
 | 
			
		||||
        'Programming Language :: Python :: 3.5',
 | 
			
		||||
        'Programming Language :: Python :: Implementation :: CPython',
 | 
			
		||||
        'Programming Language :: Python :: Implementation :: PyPy',
 | 
			
		||||
        'Topic :: Software Development :: Libraries :: Python Modules',
 | 
			
		||||
        'Topic :: Office/Business :: Financial :: Point-Of-Sale',
 | 
			
		||||
    ],
 | 
			
		||||
    install_requires=[
 | 
			
		||||
        'pyusb',
 | 
			
		||||
        'pyusb>=1.0.0',
 | 
			
		||||
        'Pillow>=2.0',
 | 
			
		||||
        'qrcode>=4.0',
 | 
			
		||||
        'pyserial',
 | 
			
		||||
        'six',
 | 
			
		||||
        'appdirs',
 | 
			
		||||
        'pyyaml',
 | 
			
		||||
        'argparse',
 | 
			
		||||
        'argcomplete',
 | 
			
		||||
        'future'
 | 
			
		||||
    ],
 | 
			
		||||
    setup_requires=[
 | 
			
		||||
        'setuptools_scm',
 | 
			
		||||
    ],
 | 
			
		||||
    tests_require=[
 | 
			
		||||
        'jaconv',
 | 
			
		||||
        'tox',
 | 
			
		||||
        'pytest',
 | 
			
		||||
        'pytest-cov',
 | 
			
		||||
        'pytest-mock',
 | 
			
		||||
        'nose',
 | 
			
		||||
        'scripttest',
 | 
			
		||||
        'mock',
 | 
			
		||||
        'hypothesis',
 | 
			
		||||
        'flake8'
 | 
			
		||||
    ],
 | 
			
		||||
    cmdclass={'test': Tox},
 | 
			
		||||
    entry_points={
 | 
			
		||||
        'console_scripts': [
 | 
			
		||||
            'python-escpos = escpos.cli:main'
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/escpos/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
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"]
 | 
			
		||||
 | 
			
		||||
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.'
 | 
			
		||||
    )
 | 
			
		||||
							
								
								
									
										1
									
								
								src/escpos/capabilities.json
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
../../capabilities-data/dist/capabilities.json
 | 
			
		||||
							
								
								
									
										119
									
								
								src/escpos/capabilities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,119 @@
 | 
			
		||||
import re
 | 
			
		||||
import six
 | 
			
		||||
from os import environ, path
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
# Load external printer database
 | 
			
		||||
if 'ESCPOS_CAPABILITIES_FILE' in environ:
 | 
			
		||||
    file_path = environ['ESCPOS_CAPABILITIES_FILE']
 | 
			
		||||
else:
 | 
			
		||||
    file_path = path.join(path.dirname(__file__), 'capabilities.json')
 | 
			
		||||
 | 
			
		||||
with open(file_path) as f:
 | 
			
		||||
    CAPABILITIES = yaml.load(f)
 | 
			
		||||
 | 
			
		||||
PROFILES = CAPABILITIES['profiles']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotSupported(Exception):
 | 
			
		||||
    """Raised if a requested feature is not suppored by the
 | 
			
		||||
    printer profile.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BARCODE_B = 'barcodeB'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseProfile(object):
 | 
			
		||||
    """This respresents a printer profile.
 | 
			
		||||
 | 
			
		||||
    A printer profile knows about the number of columns, supported
 | 
			
		||||
    features, colors and more.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    profile_data = {}
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, name):
 | 
			
		||||
        return self.profile_data[name]
 | 
			
		||||
 | 
			
		||||
    def get_font(self, font):
 | 
			
		||||
        """Return the escpos index for `font`. Makes sure that
 | 
			
		||||
        the requested `font` is valid.
 | 
			
		||||
        """
 | 
			
		||||
        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))
 | 
			
		||||
        return font
 | 
			
		||||
 | 
			
		||||
    def get_columns(self, font):
 | 
			
		||||
        """ Return the number of columns for the given font.
 | 
			
		||||
        """
 | 
			
		||||
        font = self.get_font(font)
 | 
			
		||||
        return self.fonts[six.text_type(font)]['columns']
 | 
			
		||||
 | 
			
		||||
    def supports(self, 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 {v: k for k, v in self.codePages.items()}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_profile(name=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')
 | 
			
		||||
    return clazz(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLASS_CACHE = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_profile_class(name):
 | 
			
		||||
    """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_CACHE[name] = new_class
 | 
			
		||||
 | 
			
		||||
    return CLASS_CACHE[name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def clean(s):
 | 
			
		||||
    # Remove invalid characters
 | 
			
		||||
    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)
 | 
			
		||||
    return str(s)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Profile(get_profile_class('default')):
 | 
			
		||||
    """
 | 
			
		||||
    For users, who want to provide their profile
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, columns=None, features=None):
 | 
			
		||||
        super(Profile, self).__init__()
 | 
			
		||||
 | 
			
		||||
        self.columns = columns
 | 
			
		||||
        self.features = features or {}
 | 
			
		||||
 | 
			
		||||
    def get_columns(self, font):
 | 
			
		||||
        if self.columns is not None:
 | 
			
		||||
            return self.columns
 | 
			
		||||
 | 
			
		||||
        return super(Profile, self).get_columns(font)
 | 
			
		||||
							
								
								
									
										581
									
								
								src/escpos/cli.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,581 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# PYTHON_ARGCOMPLETE_OK
 | 
			
		||||
""" 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.
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
    # this CLI works nevertheless without argcomplete
 | 
			
		||||
    pass  # noqa
 | 
			
		||||
import sys
 | 
			
		||||
import six
 | 
			
		||||
from . import config
 | 
			
		||||
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
 | 
			
		||||
    bool instead of always 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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Used in demo method
 | 
			
		||||
# Key: The name of escpos function and the argument passed on the CLI. Some
 | 
			
		||||
#   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', }
 | 
			
		||||
    ],
 | 
			
		||||
    '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_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'},
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Used to build the CLI
 | 
			
		||||
# A list of dictionaries. Each dict is a CLI argument.
 | 
			
		||||
# Keys:
 | 
			
		||||
# parser: A dict of args for command_parsers.add_parser
 | 
			
		||||
# defaults: A dict of args for subparser.set_defaults
 | 
			
		||||
# arguments: A list of dicts of args for subparser.add_argument
 | 
			
		||||
ESCPOS_COMMANDS = [
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'qr',
 | 
			
		||||
            'help': 'Print a QR code',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'qr',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                '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,
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        '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': '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,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'cut',
 | 
			
		||||
            'help': 'Cut the paper',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'cut',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--mode',),
 | 
			
		||||
                'help': 'Type of cut',
 | 
			
		||||
                'choices': ['FULL', 'PART'],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'cashdraw',
 | 
			
		||||
            'help': 'Kick the cash drawer',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'cashdraw',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--pin',),
 | 
			
		||||
                'help': 'Which PIN to kick',
 | 
			
		||||
                'choices': [2, 5],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'image',
 | 
			
		||||
            'help': 'Print an image',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'image',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                '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': 'fullimage',
 | 
			
		||||
            'help': 'Print a fullimage',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'fullimage',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--img',),
 | 
			
		||||
                'help': 'Path to img',
 | 
			
		||||
                'required': True,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                '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,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'charcode',
 | 
			
		||||
            'help': 'Set character code table',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'charcode',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--code',),
 | 
			
		||||
                'help': 'Character code',
 | 
			
		||||
                'required': True,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'set',
 | 
			
		||||
            'help': 'Set text properties',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'set',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--align',),
 | 
			
		||||
                'help': 'Horizontal alignment',
 | 
			
		||||
                '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': ('--width',),
 | 
			
		||||
                'help': 'Width multiplier',
 | 
			
		||||
                'type': int,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--height',),
 | 
			
		||||
                'help': 'Height multiplier',
 | 
			
		||||
                'type': int,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--density',),
 | 
			
		||||
                'help': 'Print density',
 | 
			
		||||
                'type': int,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                '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': ('--flip',),
 | 
			
		||||
                'help': 'Text smoothing. Effective on >:  4x4 text',
 | 
			
		||||
                'type': str_to_bool,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'hw',
 | 
			
		||||
            'help': 'Hardware operations',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'hw',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--hw',),
 | 
			
		||||
                'help': 'Operation',
 | 
			
		||||
                'choices': ['INIT', 'SELECT', 'RESET'],
 | 
			
		||||
                'required': True,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'control',
 | 
			
		||||
            'help': 'Control sequences',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'control',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                '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,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'panel_buttons',
 | 
			
		||||
            'help': 'Controls panel buttons',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': 'panel_buttons',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--enable',),
 | 
			
		||||
                'help': 'Feed button enabled',
 | 
			
		||||
                'type': str_to_bool,
 | 
			
		||||
                'required': True,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        'parser': {
 | 
			
		||||
            'name': 'raw',
 | 
			
		||||
            'help': 'Raw data',
 | 
			
		||||
        },
 | 
			
		||||
        'defaults': {
 | 
			
		||||
            'func': '_raw',
 | 
			
		||||
        },
 | 
			
		||||
        'arguments': [
 | 
			
		||||
            {
 | 
			
		||||
                'option_strings': ('--msg',),
 | 
			
		||||
                'help': 'Raw data to send',
 | 
			
		||||
                'required': True,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    Handles loading of configuration and creating and processing of command
 | 
			
		||||
    line arguments. Called when run from a CLI.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    # Allow config file location to be passed
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-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',
 | 
			
		||||
    )
 | 
			
		||||
    # fix inconsistencies in the behaviour of some versions of argparse
 | 
			
		||||
    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.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')
 | 
			
		||||
    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',
 | 
			
		||||
    )
 | 
			
		||||
    demo_group.add_argument(
 | 
			
		||||
        '--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',
 | 
			
		||||
    )
 | 
			
		||||
    demo_group.add_argument(
 | 
			
		||||
        '--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.set_defaults(version=True)
 | 
			
		||||
 | 
			
		||||
    # hook in argcomplete
 | 
			
		||||
    if 'argcomplete' in globals():
 | 
			
		||||
        argcomplete.autocomplete(parser)
 | 
			
		||||
 | 
			
		||||
    # Get only arguments actually passed
 | 
			
		||||
    args_dict = vars(parser.parse_args())
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    # If version should be printed, do this, then exit
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    # Load the configuration and defined printer
 | 
			
		||||
    saved_config = config.Config()
 | 
			
		||||
    saved_config.load(config_path)
 | 
			
		||||
    printer = saved_config.printer()
 | 
			
		||||
 | 
			
		||||
    if not printer:
 | 
			
		||||
        raise Exception('No printers loaded from config')
 | 
			
		||||
 | 
			
		||||
    target_command = command_arguments.pop('func')
 | 
			
		||||
 | 
			
		||||
    # remove helper-argument 'parser' from dict
 | 
			
		||||
    command_arguments.pop('parser', None)
 | 
			
		||||
 | 
			
		||||
    if hasattr(printer, target_command):
 | 
			
		||||
        # print command with args
 | 
			
		||||
        getattr(printer, target_command)(**command_arguments)
 | 
			
		||||
        if target_command in REQUIRES_NEWLINE:
 | 
			
		||||
            printer.text("\n")
 | 
			
		||||
    else:
 | 
			
		||||
        command_arguments['printer'] = printer
 | 
			
		||||
        globals()[target_command](**command_arguments)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def demo(printer, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    Prints specificed demos. Called when CLI is passed `demo`. This function
 | 
			
		||||
    uses the DEMO_FUNCTIONS dictionary.
 | 
			
		||||
 | 
			
		||||
    :param printer: A printer from escpos.printer
 | 
			
		||||
    :param kwargs: A dict with a key for each function you want to test. It's
 | 
			
		||||
        in this format since it usually comes from argparse.
 | 
			
		||||
    """
 | 
			
		||||
    for demo_choice in kwargs.keys():
 | 
			
		||||
        command = getattr(
 | 
			
		||||
            printer,
 | 
			
		||||
            demo_choice
 | 
			
		||||
            .replace('barcodes_a', 'barcode')
 | 
			
		||||
            .replace('barcodes_b', 'barcode')
 | 
			
		||||
        )
 | 
			
		||||
        for params in DEMO_FUNCTIONS[demo_choice]:
 | 
			
		||||
            command(**params)
 | 
			
		||||
        printer.cut()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										24
									
								
								src/escpos/codepages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
from .capabilities import CAPABILITIES
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CodePageManager:
 | 
			
		||||
    """Holds information about all the code pages (as defined
 | 
			
		||||
    in escpos-printer-db).
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        self.data = data
 | 
			
		||||
 | 
			
		||||
    def get_all(self):
 | 
			
		||||
        return self.data.values()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_encoding_name(encoding):
 | 
			
		||||
        # TODO resolve the encoding alias
 | 
			
		||||
        return encoding.upper()
 | 
			
		||||
 | 
			
		||||
    def get_encoding(self, encoding):
 | 
			
		||||
        return self.data[encoding]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CodePages = CodePageManager(CAPABILITIES['encodings'])
 | 
			
		||||
							
								
								
									
										118
									
								
								src/escpos/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,118 @@
 | 
			
		||||
""" ESC/POS configuration manager.
 | 
			
		||||
 | 
			
		||||
This module contains the implentations 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
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
from . import printer
 | 
			
		||||
from . import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Config(object):
 | 
			
		||||
    """  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'
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        """ Initialize configuration.
 | 
			
		||||
 | 
			
		||||
        Remember to add anything that needs to be reset between configurations
 | 
			
		||||
        to self._reset_config
 | 
			
		||||
        """
 | 
			
		||||
        self._has_loaded = False
 | 
			
		||||
        self._printer = None
 | 
			
		||||
 | 
			
		||||
        self._printer_name = None
 | 
			
		||||
        self._printer_config = None
 | 
			
		||||
 | 
			
		||||
    def _reset_config(self):
 | 
			
		||||
        """ Clear the loaded configuration.
 | 
			
		||||
 | 
			
		||||
        If we are loading a changed config, we don't want to have leftover
 | 
			
		||||
        data.
 | 
			
		||||
        """
 | 
			
		||||
        self._has_loaded = False
 | 
			
		||||
        self._printer = None
 | 
			
		||||
 | 
			
		||||
        self._printer_name = None
 | 
			
		||||
        self._printer_config = None
 | 
			
		||||
 | 
			
		||||
    def load(self, config_path=None):
 | 
			
		||||
        """ Load and parse the configuration file using pyyaml
 | 
			
		||||
 | 
			
		||||
        :param config_path: An optional file path, file handle, or byte string
 | 
			
		||||
            for the configuration file.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        self._reset_config()
 | 
			
		||||
 | 
			
		||||
        if not config_path:
 | 
			
		||||
            config_path = os.path.join(
 | 
			
		||||
                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'):
 | 
			
		||||
                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:
 | 
			
		||||
                    config = yaml.safe_load(config_file)
 | 
			
		||||
        except EnvironmentError:
 | 
			
		||||
            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')
 | 
			
		||||
 | 
			
		||||
        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(
 | 
			
		||||
                    'Printer type "{printer_name}" is invalid'.format(
 | 
			
		||||
                        printer_name=self._printer_name,
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        self._has_loaded = True
 | 
			
		||||
 | 
			
		||||
    def printer(self):
 | 
			
		||||
        """ 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.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if not self._has_loaded:
 | 
			
		||||
            self.load()
 | 
			
		||||
 | 
			
		||||
        if not self._printer_name:
 | 
			
		||||
            raise exceptions.ConfigSectionMissingError('printer')
 | 
			
		||||
 | 
			
		||||
        if not self._printer:
 | 
			
		||||
            # We could catch init errors and make them a ConfigSyntaxError,
 | 
			
		||||
            # but I'll just let them pass
 | 
			
		||||
            self._printer = getattr(printer, self._printer_name)(**self._printer_config)
 | 
			
		||||
 | 
			
		||||
        return self._printer
 | 
			
		||||
							
								
								
									
										218
									
								
								src/escpos/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,218 @@
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
""" Set of ESC/POS Commands (Constants)
 | 
			
		||||
 | 
			
		||||
This module contains constants that are described in the esc/pos-documentation.
 | 
			
		||||
Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be
 | 
			
		||||
moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/escpos-php>`_.
 | 
			
		||||
 | 
			
		||||
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
 | 
			
		||||
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
 | 
			
		||||
: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'
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Printer hardware
 | 
			
		||||
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?)
 | 
			
		||||
 | 
			
		||||
# 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 []
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Beep
 | 
			
		||||
BEEP = b'\x07'
 | 
			
		||||
 | 
			
		||||
# Panel buttons (e.g. the FEED button)
 | 
			
		||||
_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
 | 
			
		||||
 | 
			
		||||
# Sheet modes
 | 
			
		||||
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_FLIP_ON    = ESC + b'\x7b\x01'
 | 
			
		||||
TXT_FLIP_OFF   = ESC + b'\x7b\x00'
 | 
			
		||||
TXT_SMOOTH_ON  = GS + b'\x62\x01'
 | 
			
		||||
TXT_SMOOTH_OFF = GS + b'\x62\x00'
 | 
			
		||||
TXT_SIZE       = GS + b'!'
 | 
			
		||||
TXT_WIDTH      = {1: 0x00,
 | 
			
		||||
                  2: 0x10,
 | 
			
		||||
                  3: 0x20,
 | 
			
		||||
                  4: 0x30,
 | 
			
		||||
                  5: 0x40,
 | 
			
		||||
                  6: 0x50,
 | 
			
		||||
                  7: 0x60,
 | 
			
		||||
                  8: 0x70}
 | 
			
		||||
TXT_HEIGHT     = {1: 0x00,
 | 
			
		||||
                  2: 0x01,
 | 
			
		||||
                  3: 0x02,
 | 
			
		||||
                  4: 0x03,
 | 
			
		||||
                  5: 0x04,
 | 
			
		||||
                  6: 0x05,
 | 
			
		||||
                  7: 0x06,
 | 
			
		||||
                  8: 0x07}
 | 
			
		||||
TXT_NORMAL     = ESC + b'!\x00'     # Normal text
 | 
			
		||||
TXT_2HEIGHT    = ESC + b'!\x10'     # Double height text
 | 
			
		||||
TXT_2WIDTH     = ESC + b'!\x20'     # Double width text
 | 
			
		||||
TXT_4SQUARE    = ESC + b'!\x30'     # Quad area text
 | 
			
		||||
TXT_UNDERL_OFF = ESC + b'\x2d\x00'  # Underline font OFF
 | 
			
		||||
TXT_UNDERL_ON  = ESC + b'\x2d\x01'  # Underline font 1-dot ON
 | 
			
		||||
TXT_UNDERL2_ON = ESC + b'\x2d\x02'  # Underline font 2-dot ON
 | 
			
		||||
TXT_BOLD_OFF   = ESC + b'\x45\x00'  # Bold font OFF
 | 
			
		||||
TXT_BOLD_ON    = ESC + b'\x45\x01'  # Bold font ON
 | 
			
		||||
TXT_ALIGN_LT   = ESC + b'\x61\x00'  # Left justification
 | 
			
		||||
TXT_ALIGN_CT   = ESC + b'\x61\x01'  # Centering
 | 
			
		||||
TXT_ALIGN_RT   = ESC + b'\x61\x02'  # Right justification
 | 
			
		||||
TXT_INVERT_ON  = GS  + b'\x42\x01'  # Inverse Printing ON
 | 
			
		||||
TXT_INVERT_OFF = GS  + b'\x42\x00'  # Inverse Printing OFF
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Text colors
 | 
			
		||||
TXT_COLOR_BLACK = ESC + b'\x72\x00'  # Default Color
 | 
			
		||||
TXT_COLOR_RED = ESC + b'\x72\x01'    # Alternative Color (Usually Red)
 | 
			
		||||
 | 
			
		||||
# Spacing
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# 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'
 | 
			
		||||
 | 
			
		||||
# 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_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]
 | 
			
		||||
 | 
			
		||||
# 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)
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# 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),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BARCODE_TYPES = {
 | 
			
		||||
    'A': BARCODE_TYPE_A,
 | 
			
		||||
    'B': BARCODE_TYPE_B,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# QRCode error correction levels
 | 
			
		||||
QR_ECLEVEL_L = 0
 | 
			
		||||
QR_ECLEVEL_M = 1
 | 
			
		||||
QR_ECLEVEL_Q = 2
 | 
			
		||||
QR_ECLEVEL_H = 3
 | 
			
		||||
 | 
			
		||||
# QRcode models
 | 
			
		||||
QR_MODEL_1 = 1
 | 
			
		||||
QR_MODEL_2 = 2
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
# Printing Density
 | 
			
		||||
PD_N50 = GS + b'\x7c\x00'  # Printing Density -50%
 | 
			
		||||
PD_N37 = GS + b'\x7c\x01'  # Printing Density -37.5%
 | 
			
		||||
PD_N25 = GS + b'\x7c\x02'  # Printing Density -25%
 | 
			
		||||
PD_N12 = GS + b'\x7c\x03'  # Printing Density -12.5%
 | 
			
		||||
PD_0   = GS + b'\x7c\x04'  # Printing Density  0%
 | 
			
		||||
PD_P50 = GS + b'\x7c\x08'  # Printing Density +50%
 | 
			
		||||
PD_P37 = GS + b'\x7c\x07'  # Printing Density +37.5%
 | 
			
		||||
PD_P25 = GS + b'\x7c\x06'  # Printing Density +25%
 | 
			
		||||
PD_P12 = GS + b'\x7c\x05'  # Printing Density +12.5%
 | 
			
		||||
							
								
								
									
										786
									
								
								src/escpos/escpos.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,786 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
""" Main class
 | 
			
		||||
 | 
			
		||||
This module contains the abstract base class :py:class:`Escpos`.
 | 
			
		||||
 | 
			
		||||
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
 | 
			
		||||
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2012-2017 Bashlinux and python-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
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
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 TXT_ALIGN_CT, TXT_ALIGN_LT, TXT_ALIGN_RT, BARCODE_FONT_A, BARCODE_FONT_B
 | 
			
		||||
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
 | 
			
		||||
from .constants import TXT_HEIGHT, TXT_WIDTH, TXT_SIZE, TXT_NORMAL, TXT_SMOOTH_OFF, TXT_SMOOTH_ON
 | 
			
		||||
from .constants import TXT_FLIP_OFF, TXT_FLIP_ON, TXT_2WIDTH, TXT_2HEIGHT, TXT_4SQUARE
 | 
			
		||||
from .constants import TXT_UNDERL_OFF, TXT_UNDERL_ON, TXT_BOLD_OFF, TXT_BOLD_ON, SET_FONT, TXT_UNDERL2_ON
 | 
			
		||||
from .constants import TXT_INVERT_OFF, TXT_INVERT_ON, LINESPACING_FUNCS, LINESPACING_RESET
 | 
			
		||||
from .constants import PD_0, PD_N12, PD_N25, PD_N37, PD_N50, PD_P50, PD_P37, PD_P25, PD_P12
 | 
			
		||||
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_HT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON
 | 
			
		||||
 | 
			
		||||
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
 | 
			
		||||
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
 | 
			
		||||
 | 
			
		||||
from .magicencode import MagicEncode
 | 
			
		||||
 | 
			
		||||
from abc import ABCMeta, abstractmethod  # abstract base class support
 | 
			
		||||
from escpos.image import EscposImage
 | 
			
		||||
from escpos.capabilities import get_profile, BARCODE_B
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@six.add_metaclass(ABCMeta)
 | 
			
		||||
class Escpos(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
 | 
			
		||||
 | 
			
		||||
        :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 """
 | 
			
		||||
        self.close()
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Sends raw data to the printer
 | 
			
		||||
 | 
			
		||||
        This function has to be individually implemented by the implementations.
 | 
			
		||||
 | 
			
		||||
        :param msg: message string to be sent to the printer
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
 | 
			
		||||
              fragment_height=960):
 | 
			
		||||
        """ 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.
 | 
			
		||||
 | 
			
		||||
        Esc/Pos supplies several commands for printing. This function supports three of them. Please try to vary the
 | 
			
		||||
        implementations if you have any problems. For example the printer `IT80-002` will have trouble aligning
 | 
			
		||||
        images that are not printed in Column-mode.
 | 
			
		||||
 | 
			
		||||
        The available printing implementations are:
 | 
			
		||||
 | 
			
		||||
            * `bitImageRaster`: prints with the `GS v 0`-command
 | 
			
		||||
            * `graphics`: prints with the `GS ( L`-command
 | 
			
		||||
            * `bitImageColumn`: prints with the `ESC *`-command
 | 
			
		||||
 | 
			
		||||
        :param img_source: PIL image or filename to load: `jpg`, `gif`, `png` or `bmp`
 | 
			
		||||
        :param high_density_vertical: print in high density in vertical direction *default:* True
 | 
			
		||||
        :param high_density_horizontal: print in high density in horizontal direction *default:* True
 | 
			
		||||
        :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn`
 | 
			
		||||
        :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        im = EscposImage(img_source)
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
            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)
 | 
			
		||||
            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'
 | 
			
		||||
            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'')
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
            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))
 | 
			
		||||
 | 
			
		||||
    def _image_send_graphics_data(self, m, fn, data):
 | 
			
		||||
        """
 | 
			
		||||
        Wrapper for GS ( L, to calculate and send correct data length.
 | 
			
		||||
 | 
			
		||||
        :param m: Modifier//variant for function. Usually '0'
 | 
			
		||||
        :param fn: Function number to use, as byte
 | 
			
		||||
        :param data: Data to send
 | 
			
		||||
        """
 | 
			
		||||
        header = self._int_low_high(len(data) + 2, 2)
 | 
			
		||||
        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):
 | 
			
		||||
        """ 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
 | 
			
		||||
            QR_ECLEVEL_H.
 | 
			
		||||
            Higher error correction results in a less compact code.
 | 
			
		||||
        :param size: Pixel size to use. Must be 1-16 (default 3)
 | 
			
		||||
        :param model: QR code model to use. Must be one of QR_MODEL_1, QR_MODEL_2 (default) or QR_MICRO (not supported
 | 
			
		||||
            by all printers).
 | 
			
		||||
        :param native: True to render the code on the printer, False to render the code as an image and send it to the
 | 
			
		||||
            printer (Default)
 | 
			
		||||
        """
 | 
			
		||||
        # Basic validation
 | 
			
		||||
        if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
 | 
			
		||||
            raise ValueError("Invalid error correction level")
 | 
			
		||||
        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)")
 | 
			
		||||
        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)")
 | 
			
		||||
            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_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.image(im)
 | 
			
		||||
            return
 | 
			
		||||
        # Native 2D code printing
 | 
			
		||||
        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))
 | 
			
		||||
        # 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')
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
        :param data: Data to send.
 | 
			
		||||
        :param m: Modifier/variant for function. Often '0' where used.
 | 
			
		||||
        """
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    @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.
 | 
			
		||||
 | 
			
		||||
        :param inp_number: Input number
 | 
			
		||||
        :param out_bytes: The number of bytes to output (1 - 4).
 | 
			
		||||
        """
 | 
			
		||||
        max_input = (256 << (out_bytes * 8) - 1)
 | 
			
		||||
        if not 1 <= out_bytes <= 4:
 | 
			
		||||
            raise ValueError("Can only output 1-4 byes")
 | 
			
		||||
        if not 0 <= inp_number <= max_input:
 | 
			
		||||
            raise ValueError("Number too large. Can only output up to {0} in {1} byes".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
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        right codepage. (This is the standard behaviour.)
 | 
			
		||||
 | 
			
		||||
        :param code: Name of CharCode
 | 
			
		||||
        :raises: :py:exc:`~escpos.exceptions.CharCodeError`
 | 
			
		||||
        """
 | 
			
		||||
        if code.upper() == "AUTO":
 | 
			
		||||
            self.magic.force_encoding(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.magic.force_encoding(code)
 | 
			
		||||
 | 
			
		||||
    def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
 | 
			
		||||
                align_ct=True, function_type=None):
 | 
			
		||||
        """ 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. Currently you have to check manually whether your barcode text is correct. Uncorrect
 | 
			
		||||
        barcodes may lead to unexpected printer behaviour. There are two forms of the barcode function. Type A is
 | 
			
		||||
        default but has fewer barcodes, while type B has some more to choose from.
 | 
			
		||||
 | 
			
		||||
        .. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each
 | 
			
		||||
                  barcode-type. Research whether the check digits can be computed autmatically.
 | 
			
		||||
 | 
			
		||||
        Use the parameters `height` and `width` for adjusting of the barcode size. Please take notice that the barcode
 | 
			
		||||
        will not be printed if it is outside of the printable area. (Which should be impossible with this method, so
 | 
			
		||||
        this information is probably more useful for debugging purposes.)
 | 
			
		||||
 | 
			
		||||
        .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code.
 | 
			
		||||
        .. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this
 | 
			
		||||
                  is true.
 | 
			
		||||
 | 
			
		||||
        If you do not want to center the barcode you can call the method with `align_ct=False`, which will disable
 | 
			
		||||
        automatic centering. Please note that when you use center alignment, then the alignment of text will be changed
 | 
			
		||||
        automatically to centered. You have to manually restore the alignment if necessary.
 | 
			
		||||
 | 
			
		||||
        .. todo:: If further barcode-types are needed they could be rendered transparently as an image. (This could also
 | 
			
		||||
                  be of help if the printer does not support types that others do.)
 | 
			
		||||
 | 
			
		||||
        :param code: alphanumeric data to be printed as bar code
 | 
			
		||||
        :param bc: barcode format, possible values are for type A are:
 | 
			
		||||
 | 
			
		||||
            * UPC-A
 | 
			
		||||
            * UPC-E
 | 
			
		||||
            * EAN13
 | 
			
		||||
            * EAN8
 | 
			
		||||
            * CODE39
 | 
			
		||||
            * ITF
 | 
			
		||||
            * NW7
 | 
			
		||||
 | 
			
		||||
            Possible values for type B:
 | 
			
		||||
 | 
			
		||||
            * All types from function type A
 | 
			
		||||
            * CODE93
 | 
			
		||||
            * CODE128
 | 
			
		||||
            * GS1-128
 | 
			
		||||
            * GS1 DataBar Omnidirectional
 | 
			
		||||
            * GS1 DataBar Truncated
 | 
			
		||||
            * GS1 DataBar Limited
 | 
			
		||||
            * GS1 DataBar Expanded
 | 
			
		||||
 | 
			
		||||
            If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`.
 | 
			
		||||
        :param height: barcode height, has to be between 1 and 255
 | 
			
		||||
            *default*: 64
 | 
			
		||||
        :type height: int
 | 
			
		||||
        :param width: barcode width, has to be between 2 and 6
 | 
			
		||||
            *default*: 3
 | 
			
		||||
        :type width: int
 | 
			
		||||
        :param pos: where to place the text relative to the barcode, *default*: BELOW
 | 
			
		||||
 | 
			
		||||
            * ABOVE
 | 
			
		||||
            * BELOW
 | 
			
		||||
            * BOTH
 | 
			
		||||
            * OFF
 | 
			
		||||
 | 
			
		||||
        :param font: select font (see ESC/POS-documentation, the device often has two fonts), *default*: A
 | 
			
		||||
 | 
			
		||||
            * A
 | 
			
		||||
            * B
 | 
			
		||||
 | 
			
		||||
        :param align_ct: If this parameter is True the barcode will be centered. Otherwise no alignment command will be
 | 
			
		||||
                         issued.
 | 
			
		||||
        :type align_ct: bool
 | 
			
		||||
 | 
			
		||||
        :param function_type: Choose between ESCPOS function type A or B,
 | 
			
		||||
            depending on printer support and desired barcode. If not given,
 | 
			
		||||
            the printer will attempt to automatically choose the correct
 | 
			
		||||
            function based on the current profile.
 | 
			
		||||
            *default*: A
 | 
			
		||||
 | 
			
		||||
        :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
 | 
			
		||||
                 :py:exc:`~escpos.exceptions.BarcodeTypeError`,
 | 
			
		||||
                 :py:exc:`~escpos.exceptions.BarcodeCodeError`
 | 
			
		||||
        """
 | 
			
		||||
        if function_type is None:
 | 
			
		||||
            # Choose the function type automatically.
 | 
			
		||||
            if bc in BARCODE_TYPES['A']:
 | 
			
		||||
                function_type = 'A'
 | 
			
		||||
            else:
 | 
			
		||||
                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'
 | 
			
		||||
                else:
 | 
			
		||||
                    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 type '{bc}' not valid for barcode function type "
 | 
			
		||||
                "{function_type}").format(
 | 
			
		||||
                    bc=bc,
 | 
			
		||||
                    function_type=function_type,
 | 
			
		||||
                ))
 | 
			
		||||
 | 
			
		||||
        # Align Bar Code()
 | 
			
		||||
        if align_ct:
 | 
			
		||||
            self._raw(TXT_ALIGN_CT)
 | 
			
		||||
        # Height
 | 
			
		||||
        if 1 <= height <= 255:
 | 
			
		||||
            self._raw(BARCODE_HEIGHT + six.int2byte(height))
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeSizeError("height = {height}".format(height=height))
 | 
			
		||||
        # Width
 | 
			
		||||
        if 2 <= width <= 6:
 | 
			
		||||
            self._raw(BARCODE_WIDTH + six.int2byte(width))
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeSizeError("width = {width}".format(width=width))
 | 
			
		||||
        # Font
 | 
			
		||||
        if font.upper() == "B":
 | 
			
		||||
            self._raw(BARCODE_FONT_B)
 | 
			
		||||
        else:  # DEFAULT FONT: A
 | 
			
		||||
            self._raw(BARCODE_FONT_A)
 | 
			
		||||
        # Position
 | 
			
		||||
        if pos.upper() == "OFF":
 | 
			
		||||
            self._raw(BARCODE_TXT_OFF)
 | 
			
		||||
        elif pos.upper() == "BOTH":
 | 
			
		||||
            self._raw(BARCODE_TXT_BTH)
 | 
			
		||||
        elif pos.upper() == "ABOVE":
 | 
			
		||||
            self._raw(BARCODE_TXT_ABV)
 | 
			
		||||
        else:  # DEFAULT POSITION: BELOW
 | 
			
		||||
            self._raw(BARCODE_TXT_BLW)
 | 
			
		||||
 | 
			
		||||
        self._raw(bc_types[bc.upper()])
 | 
			
		||||
 | 
			
		||||
        if function_type.upper() == "B":
 | 
			
		||||
            self._raw(six.int2byte(len(code)))
 | 
			
		||||
 | 
			
		||||
        # Print Code
 | 
			
		||||
        if code:
 | 
			
		||||
            self._raw(code.encode())
 | 
			
		||||
        else:
 | 
			
		||||
            raise BarcodeCodeError()
 | 
			
		||||
 | 
			
		||||
        if function_type.upper() == "A":
 | 
			
		||||
            self._raw(NUL)
 | 
			
		||||
 | 
			
		||||
    def text(self, txt):
 | 
			
		||||
        """ Print alpha-numeric text
 | 
			
		||||
 | 
			
		||||
        The text has to be encoded in the currently selected codepage.
 | 
			
		||||
        The input text has to be encoded in unicode.
 | 
			
		||||
 | 
			
		||||
        :param txt: text to be printed
 | 
			
		||||
        :raises: :py:exc:`~escpos.exceptions.TextError`
 | 
			
		||||
        """
 | 
			
		||||
        txt = six.text_type(txt)
 | 
			
		||||
        self.magic.write(txt)
 | 
			
		||||
 | 
			
		||||
    def block_text(self, txt, font=None, columns=None):
 | 
			
		||||
        """ Text is printed wrapped to specified columns
 | 
			
		||||
 | 
			
		||||
        Text has to be encoded in unicode.
 | 
			
		||||
 | 
			
		||||
        :param txt: text to be printed
 | 
			
		||||
        :param font: font to be used, can be :code:`a` or :code`b`
 | 
			
		||||
        :param columns: amount of columns
 | 
			
		||||
        :return: None
 | 
			
		||||
        """
 | 
			
		||||
        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', text_type='normal', width=1,
 | 
			
		||||
            height=1, density=9, invert=False, smooth=False, flip=False):
 | 
			
		||||
        """ Set text properties by sending them to the printer
 | 
			
		||||
 | 
			
		||||
        :param align: horizontal position for text, possible values are:
 | 
			
		||||
 | 
			
		||||
            * CENTER
 | 
			
		||||
            * LEFT
 | 
			
		||||
            * RIGHT
 | 
			
		||||
 | 
			
		||||
            *default*: LEFT
 | 
			
		||||
 | 
			
		||||
        :param font: font given as an index, a name, or one of the
 | 
			
		||||
            special values 'a' or 'b', refering to fonts 0 and 1.
 | 
			
		||||
        :param text_type: text type, possible values are:
 | 
			
		||||
 | 
			
		||||
            * B for bold
 | 
			
		||||
            * U for underlined
 | 
			
		||||
            * U2 for underlined, version 2
 | 
			
		||||
            * BU for bold and underlined
 | 
			
		||||
            * BU2 for bold and underlined, version 2
 | 
			
		||||
            * NORMAL for normal text
 | 
			
		||||
 | 
			
		||||
            *default*: NORMAL
 | 
			
		||||
        :param width: text width multiplier, decimal range 1-8,  *default*: 1
 | 
			
		||||
        :param height: text height multiplier, decimal range 1-8, *default*: 1
 | 
			
		||||
        :param density: print density, value from 0-8, if something else is supplied the density remains unchanged
 | 
			
		||||
        :param invert: True enables white on black printing, *default*: False
 | 
			
		||||
        :param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False
 | 
			
		||||
        :param flip: True enables upside-down printing, *default*: False
 | 
			
		||||
        :type invert: bool
 | 
			
		||||
        """
 | 
			
		||||
        # Width
 | 
			
		||||
        if height == 2 and width == 2:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_4SQUARE)
 | 
			
		||||
        elif height == 2 and width == 1:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_2HEIGHT)
 | 
			
		||||
        elif width == 2 and height == 1:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
            self._raw(TXT_2WIDTH)
 | 
			
		||||
        elif width == 1 and height == 1:
 | 
			
		||||
            self._raw(TXT_NORMAL)
 | 
			
		||||
        elif 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and isinstance(height, int):
 | 
			
		||||
            self._raw(TXT_SIZE + six.int2byte(TXT_WIDTH[width] + TXT_HEIGHT[height]))
 | 
			
		||||
        else:
 | 
			
		||||
            raise SetVariableError()
 | 
			
		||||
        # Upside down
 | 
			
		||||
        if flip:
 | 
			
		||||
            self._raw(TXT_FLIP_ON)
 | 
			
		||||
        else:
 | 
			
		||||
            self._raw(TXT_FLIP_OFF)
 | 
			
		||||
        # Smoothing
 | 
			
		||||
        if smooth:
 | 
			
		||||
            self._raw(TXT_SMOOTH_ON)
 | 
			
		||||
        else:
 | 
			
		||||
            self._raw(TXT_SMOOTH_OFF)
 | 
			
		||||
        # Type
 | 
			
		||||
        if text_type.upper() == "B":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL_OFF)
 | 
			
		||||
        elif text_type.upper() == "U":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL_ON)
 | 
			
		||||
        elif text_type.upper() == "U2":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL2_ON)
 | 
			
		||||
        elif text_type.upper() == "BU":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL_ON)
 | 
			
		||||
        elif text_type.upper() == "BU2":
 | 
			
		||||
            self._raw(TXT_BOLD_ON)
 | 
			
		||||
            self._raw(TXT_UNDERL2_ON)
 | 
			
		||||
        elif text_type.upper() == "NORMAL":
 | 
			
		||||
            self._raw(TXT_BOLD_OFF)
 | 
			
		||||
            self._raw(TXT_UNDERL_OFF)
 | 
			
		||||
        # Font
 | 
			
		||||
        self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
 | 
			
		||||
 | 
			
		||||
        # Align
 | 
			
		||||
        if align.upper() == "CENTER":
 | 
			
		||||
            self._raw(TXT_ALIGN_CT)
 | 
			
		||||
        elif align.upper() == "RIGHT":
 | 
			
		||||
            self._raw(TXT_ALIGN_RT)
 | 
			
		||||
        elif align.upper() == "LEFT":
 | 
			
		||||
            self._raw(TXT_ALIGN_LT)
 | 
			
		||||
        # Density
 | 
			
		||||
        if density == 0:
 | 
			
		||||
            self._raw(PD_N50)
 | 
			
		||||
        elif density == 1:
 | 
			
		||||
            self._raw(PD_N37)
 | 
			
		||||
        elif density == 2:
 | 
			
		||||
            self._raw(PD_N25)
 | 
			
		||||
        elif density == 3:
 | 
			
		||||
            self._raw(PD_N12)
 | 
			
		||||
        elif density == 4:
 | 
			
		||||
            self._raw(PD_0)
 | 
			
		||||
        elif density == 5:
 | 
			
		||||
            self._raw(PD_P12)
 | 
			
		||||
        elif density == 6:
 | 
			
		||||
            self._raw(PD_P25)
 | 
			
		||||
        elif density == 7:
 | 
			
		||||
            self._raw(PD_P37)
 | 
			
		||||
        elif density == 8:
 | 
			
		||||
            self._raw(PD_P50)
 | 
			
		||||
        else:  # DEFAULT: DOES NOTHING
 | 
			
		||||
            pass
 | 
			
		||||
        # Invert Printing
 | 
			
		||||
        if invert:
 | 
			
		||||
            self._raw(TXT_INVERT_ON)
 | 
			
		||||
        else:
 | 
			
		||||
            self._raw(TXT_INVERT_OFF)
 | 
			
		||||
 | 
			
		||||
    def line_spacing(self, spacing=None, divisor=180):
 | 
			
		||||
        """ Set line character spacing.
 | 
			
		||||
 | 
			
		||||
        If no spacing is given, we reset it to the default.
 | 
			
		||||
 | 
			
		||||
        There are different commands for setting the line spacing, using
 | 
			
		||||
        a different denominator:
 | 
			
		||||
 | 
			
		||||
        '+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255
 | 
			
		||||
        '3' line_spacing/180 of an inch, 0 <= line_spacing <= 255
 | 
			
		||||
        'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85
 | 
			
		||||
 | 
			
		||||
        Some printers may not support all of them. The most commonly
 | 
			
		||||
        available command (using a divisor of 180) is chosen.
 | 
			
		||||
        """
 | 
			
		||||
        if spacing is None:
 | 
			
		||||
            self._raw(LINESPACING_RESET)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        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")
 | 
			
		||||
 | 
			
		||||
        self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
 | 
			
		||||
 | 
			
		||||
    def cut(self, mode='FULL'):
 | 
			
		||||
        """ 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'
 | 
			
		||||
        :raises ValueError: if mode not in ('FULL', 'PART')
 | 
			
		||||
        """
 | 
			
		||||
        self.print_and_feed(6)
 | 
			
		||||
 | 
			
		||||
        mode = mode.upper()
 | 
			
		||||
        if mode not in ('FULL', 'PART'):
 | 
			
		||||
            raise ValueError("Mode must be one of ('FULL', 'PART')")
 | 
			
		||||
 | 
			
		||||
        if mode == "PART":
 | 
			
		||||
            if self.profile.supports('paperPartCut'):
 | 
			
		||||
                self._raw(PAPER_PART_CUT)
 | 
			
		||||
            elif self.profile.supports('paperFullCut'):
 | 
			
		||||
                self._raw(PAPER_FULL_CUT)
 | 
			
		||||
        elif mode == "FULL":
 | 
			
		||||
            if self.profile.supports('paperFullCut'):
 | 
			
		||||
                self._raw(PAPER_FULL_CUT)
 | 
			
		||||
            elif self.profile.supports('paperPartCut'):
 | 
			
		||||
                self._raw(PAPER_PART_CUT)
 | 
			
		||||
 | 
			
		||||
    def cashdraw(self, pin):
 | 
			
		||||
        """ 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]
 | 
			
		||||
 | 
			
		||||
        :param pin: pin number, 2 or 5 or list of decimals
 | 
			
		||||
        :raises: :py:exc:`~escpos.exceptions.CashDrawerError`
 | 
			
		||||
        """
 | 
			
		||||
        if pin == 2:
 | 
			
		||||
            self._raw(CD_KICK_2)
 | 
			
		||||
        elif pin == 5:
 | 
			
		||||
            self._raw(CD_KICK_5)
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                self._raw(CD_KICK_DEC_SEQUENCE(*pin))
 | 
			
		||||
            except:
 | 
			
		||||
                raise CashDrawerError()
 | 
			
		||||
 | 
			
		||||
    def hw(self, hw):
 | 
			
		||||
        """ Hardware operations
 | 
			
		||||
 | 
			
		||||
        :param hw: hardware action, may be:
 | 
			
		||||
 | 
			
		||||
            * INIT
 | 
			
		||||
            * SELECT
 | 
			
		||||
            * RESET
 | 
			
		||||
        """
 | 
			
		||||
        if hw.upper() == "INIT":
 | 
			
		||||
            self._raw(HW_INIT)
 | 
			
		||||
        elif hw.upper() == "SELECT":
 | 
			
		||||
            self._raw(HW_SELECT)
 | 
			
		||||
        elif hw.upper() == "RESET":
 | 
			
		||||
            self._raw(HW_RESET)
 | 
			
		||||
        else:  # DEFAULT: DOES NOTHING
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def print_and_feed(self, n=1):
 | 
			
		||||
        """ Print data in print buffer and feed *n* lines
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
        """
 | 
			
		||||
        if 0 <= n <= 255:
 | 
			
		||||
            # ESC d n
 | 
			
		||||
            self._raw(ESC + b"d" + six.int2byte(n))
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("n must be betwen 0 and 255")
 | 
			
		||||
 | 
			
		||||
    def control(self, ctl, pos=4):
 | 
			
		||||
        """ Feed control sequences
 | 
			
		||||
 | 
			
		||||
        :param ctl: string for the following control sequences:
 | 
			
		||||
 | 
			
		||||
            * LF *for Line Feed*
 | 
			
		||||
            * FF *for Form Feed*
 | 
			
		||||
            * CR *for Carriage Return*
 | 
			
		||||
            * HT *for Horizontal Tab*
 | 
			
		||||
            * VT *for Vertical Tab*
 | 
			
		||||
 | 
			
		||||
        :param pos: integer between 1 and 16, controls the horizontal tab position
 | 
			
		||||
        :raises: :py:exc:`~escpos.exceptions.TabPosError`
 | 
			
		||||
        """
 | 
			
		||||
        # Set position
 | 
			
		||||
        if ctl.upper() == "LF":
 | 
			
		||||
            self._raw(CTL_LF)
 | 
			
		||||
        elif ctl.upper() == "FF":
 | 
			
		||||
            self._raw(CTL_FF)
 | 
			
		||||
        elif ctl.upper() == "CR":
 | 
			
		||||
            self._raw(CTL_CR)
 | 
			
		||||
        elif ctl.upper() == "HT":
 | 
			
		||||
            if not (1 <= pos <= 16):
 | 
			
		||||
                raise TabPosError()
 | 
			
		||||
            else:
 | 
			
		||||
                # Set tab positions
 | 
			
		||||
                self._raw(CTL_SET_HT + six.int2byte(pos))
 | 
			
		||||
 | 
			
		||||
            self._raw(CTL_HT)
 | 
			
		||||
        elif ctl.upper() == "VT":
 | 
			
		||||
            self._raw(CTL_VT)
 | 
			
		||||
 | 
			
		||||
    def panel_buttons(self, enable=True):
 | 
			
		||||
        """ 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.
 | 
			
		||||
 | 
			
		||||
        If panel buttons are enabled, the function of the panel button, such as feeding, will be executed upon pressing
 | 
			
		||||
        the button. If the panel buttons are disabled, pressing them will not have any effect.
 | 
			
		||||
 | 
			
		||||
        This command is effective until the printer is initialized, reset or power-cycled. The default is enabled panel
 | 
			
		||||
        buttons.
 | 
			
		||||
 | 
			
		||||
        Some panel buttons will always work, especially when printer is opened. See for more information the manual
 | 
			
		||||
        of your printer and the escpos-command-reference.
 | 
			
		||||
 | 
			
		||||
        :param enable: controls the panel buttons
 | 
			
		||||
        :rtype: None
 | 
			
		||||
        """
 | 
			
		||||
        if enable:
 | 
			
		||||
            self._raw(PANEL_BUTTON_ON)
 | 
			
		||||
        else:
 | 
			
		||||
            self._raw(PANEL_BUTTON_OFF)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EscposIO(object):
 | 
			
		||||
    """ESC/POS Printer IO object
 | 
			
		||||
 | 
			
		||||
    Allows the class to be used together with the `with`-statement. You have to define a printer instance
 | 
			
		||||
    and assign it to the EsposIO-class.
 | 
			
		||||
    This example explains the usage:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: Python
 | 
			
		||||
 | 
			
		||||
        with EscposIO(printer.Serial('/dev/ttyUSB0')) as p:
 | 
			
		||||
            p.set(font='a', height=2, align='center', text_type='bold')
 | 
			
		||||
            p.printer.set(align='left')
 | 
			
		||||
            p.printer.image('logo.gif')
 | 
			
		||||
            p.writelines('Big line\\n', font='b')
 | 
			
		||||
            p.writelines('Привет')
 | 
			
		||||
            p.writelines('BIG TEXT', width=2)
 | 
			
		||||
 | 
			
		||||
    After the `with`-statement the printer automatically cuts the paper if `autocut` is `True`.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, printer, autocut=True, autoclose=True, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        :param printer: An EscPos-printer object
 | 
			
		||||
        :type printer: escpos.Escpos
 | 
			
		||||
        :param autocut: If True, paper is automatically cut after the `with`-statement *default*: True
 | 
			
		||||
        :param kwargs: These arguments will be passed to :py:meth:`escpos.Escpos.set()`
 | 
			
		||||
        """
 | 
			
		||||
        self.printer = printer
 | 
			
		||||
        self.params = kwargs
 | 
			
		||||
        self.autocut = autocut
 | 
			
		||||
        self.autoclose = autoclose
 | 
			
		||||
 | 
			
		||||
    def set(self, **kwargs):
 | 
			
		||||
        """ 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
 | 
			
		||||
        documentation. These parameters can also be passed with this class' constructor or the
 | 
			
		||||
        :py:meth:`~escpos.escpos.EscposIO.writelines()`-method.
 | 
			
		||||
 | 
			
		||||
        :param kwargs: keyword-parameters that will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`
 | 
			
		||||
        """
 | 
			
		||||
        self.params.update(kwargs)
 | 
			
		||||
 | 
			
		||||
    def writelines(self, text, **kwargs):
 | 
			
		||||
        params = dict(self.params)
 | 
			
		||||
        params.update(kwargs)
 | 
			
		||||
 | 
			
		||||
        if isinstance(text, six.text_type):
 | 
			
		||||
            lines = text.split('\n')
 | 
			
		||||
        elif isinstance(text, list) or isinstance(text, tuple):
 | 
			
		||||
            lines = text
 | 
			
		||||
        else:
 | 
			
		||||
            lines = ["{0}".format(text), ]
 | 
			
		||||
 | 
			
		||||
        # TODO check unicode handling
 | 
			
		||||
        # TODO flush? or on print? (this should prob rather be handled by the _raw-method)
 | 
			
		||||
        for line in lines:
 | 
			
		||||
            self.printer.set(**params)
 | 
			
		||||
            if isinstance(text, six.text_type):
 | 
			
		||||
                self.printer.text(u"{0}\n".format(line))
 | 
			
		||||
            else:
 | 
			
		||||
                self.printer.text("{0}\n".format(line))
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        """ called upon closing the `with`-statement
 | 
			
		||||
        """
 | 
			
		||||
        self.printer.close()
 | 
			
		||||
 | 
			
		||||
    def __enter__(self, **kwargs):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __exit__(self, type, value, traceback):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor),
 | 
			
		||||
        then :py:meth:`printer.cut() <escpos.escpos.Escpos.cut()>` will be called here.
 | 
			
		||||
        """
 | 
			
		||||
        if not (type is not None and issubclass(type, Exception)):
 | 
			
		||||
            if self.autocut:
 | 
			
		||||
                self.printer.cut()
 | 
			
		||||
 | 
			
		||||
        if self.autoclose:
 | 
			
		||||
            self.close()
 | 
			
		||||
							
								
								
									
										241
									
								
								src/escpos/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,241 @@
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
""" ESC/POS Exceptions classes
 | 
			
		||||
 | 
			
		||||
Result/Exit codes:
 | 
			
		||||
 | 
			
		||||
    - `0`  = success
 | 
			
		||||
    - `10` = No Barcode type defined :py:exc:`~escpos.exceptions.BarcodeTypeError`
 | 
			
		||||
    - `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError`
 | 
			
		||||
    - `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError`
 | 
			
		||||
    - `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError`
 | 
			
		||||
    - `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError`
 | 
			
		||||
    - `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
 | 
			
		||||
    - `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
 | 
			
		||||
    - `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError`
 | 
			
		||||
    - `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
 | 
			
		||||
    - `100` = Set variable out of range :py:exc:`~escpos.exceptions.SetVariableError`
 | 
			
		||||
    - `200` = Configuration not found :py:exc:`~escpos.exceptions.ConfigNotFoundError`
 | 
			
		||||
    - `210` = Configuration syntax error :py:exc:`~escpos.exceptions.ConfigSyntaxError`
 | 
			
		||||
    - `220` = Configuration section not found :py:exc:`~escpos.exceptions.ConfigSectionMissingError`
 | 
			
		||||
 | 
			
		||||
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
 | 
			
		||||
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
 | 
			
		||||
: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 """
 | 
			
		||||
    def __init__(self, msg, status=None):
 | 
			
		||||
        Exception.__init__(self)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 1
 | 
			
		||||
        if status is not None:
 | 
			
		||||
            self.resultcode = status
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.msg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeTypeError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 10
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "No Barcode type is defined ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeSizeError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 20
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Barcode size is out of range ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeCodeError(Error):
 | 
			
		||||
    """ No Barcode code was supplied.
 | 
			
		||||
 | 
			
		||||
    No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
 | 
			
		||||
    The returncode for this exception is `30`.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, msg=""):
 | 
			
		||||
        Error.__init__(self, msg)
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
        self.resultcode = 30
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "No Barcode code was supplied ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImageSizeError(Error):
 | 
			
		||||
    """ 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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TextError(Error):
 | 
			
		||||
    """ 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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CashDrawerError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 60
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TabPosError(Error):
 | 
			
		||||
    """ Valid tab positions must be in the range 0 to 16.
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CharCodeError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 80
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Valid char code must be set ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class USBNotFoundError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 90
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "USB device not found ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SetVariableError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 100
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Set variable out of range ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Configuration errors
 | 
			
		||||
 | 
			
		||||
class ConfigNotFoundError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 200
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Configuration not found ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConfigSyntaxError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 210
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConfigSectionMissingError(Error):
 | 
			
		||||
    """ 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
 | 
			
		||||
        self.resultcode = 220
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Configuration section is missing ({msg})".format(msg=self.msg)
 | 
			
		||||
							
								
								
									
										117
									
								
								src/escpos/image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,117 @@
 | 
			
		||||
""" Image format handling class
 | 
			
		||||
 | 
			
		||||
This module contains the image format handler :py:class:`EscposImage`.
 | 
			
		||||
 | 
			
		||||
:author: `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 Michael Billington <michael.billington@gmail.com>
 | 
			
		||||
: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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EscposImage(object):
 | 
			
		||||
    """
 | 
			
		||||
    Load images in, and output ESC/POS formats.
 | 
			
		||||
 | 
			
		||||
    The class is designed to efficiently delegate image processing to
 | 
			
		||||
    PIL, rather than spend CPU cycles looping over pixels.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, img_source):
 | 
			
		||||
        """
 | 
			
		||||
        Load in an image
 | 
			
		||||
 | 
			
		||||
        :param img_source: PIL.Image, or filename to load one from.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(img_source, Image.Image):
 | 
			
		||||
            img_original = img_source
 | 
			
		||||
        else:
 | 
			
		||||
            img_original = Image.open(img_source)
 | 
			
		||||
 | 
			
		||||
        # store image for eventual further processing (splitting)
 | 
			
		||||
        self.img_original = img_original
 | 
			
		||||
 | 
			
		||||
        # Convert to white RGB background, paste over white background
 | 
			
		||||
        # to strip alpha.
 | 
			
		||||
        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
 | 
			
		||||
        im = im.convert("L")
 | 
			
		||||
        # Invert: Only works on 'L' images
 | 
			
		||||
        im = ImageOps.invert(im)
 | 
			
		||||
        # Pure black and white
 | 
			
		||||
        self._im = im.convert("1")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def width(self):
 | 
			
		||||
        """
 | 
			
		||||
        Width of image in pixels
 | 
			
		||||
        """
 | 
			
		||||
        width_pixels, _ = self._im.size
 | 
			
		||||
        return width_pixels
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def width_bytes(self):
 | 
			
		||||
        """
 | 
			
		||||
        Width of image if you use 8 pixels per byte and 0-pad at the end.
 | 
			
		||||
        """
 | 
			
		||||
        return (self.width + 7) >> 3
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def height(self):
 | 
			
		||||
        """
 | 
			
		||||
        Height of image in pixels
 | 
			
		||||
        """
 | 
			
		||||
        _, height_pixels = self._im.size
 | 
			
		||||
        return height_pixels
 | 
			
		||||
 | 
			
		||||
    def to_column_format(self, high_density_vertical=True):
 | 
			
		||||
        """
 | 
			
		||||
        Extract slices of an image as equal-sized blobs of column-format data.
 | 
			
		||||
 | 
			
		||||
        :param high_density_vertical: Printed line height in dots
 | 
			
		||||
        """
 | 
			
		||||
        im = self._im.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
 | 
			
		||||
        line_height = 24 if high_density_vertical else 8
 | 
			
		||||
        width_pixels, height_pixels = im.size
 | 
			
		||||
        top = 0
 | 
			
		||||
        left = 0
 | 
			
		||||
        while left < width_pixels:
 | 
			
		||||
            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)
 | 
			
		||||
            left += line_height
 | 
			
		||||
 | 
			
		||||
    def to_raster_format(self):
 | 
			
		||||
        """
 | 
			
		||||
        Convert image to raster-format binary
 | 
			
		||||
        """
 | 
			
		||||
        return self._im.tobytes()
 | 
			
		||||
 | 
			
		||||
    def split(self, fragment_height):
 | 
			
		||||
        """
 | 
			
		||||
        Split an image into multiple fragments after fragment_height pixels
 | 
			
		||||
 | 
			
		||||
        :param fragment_height: height of fragment
 | 
			
		||||
        :return: list of PIL objects
 | 
			
		||||
        """
 | 
			
		||||
        passes = int(math.ceil(self.height/fragment_height))
 | 
			
		||||
        fragments = []
 | 
			
		||||
        for n in range(0, passes):
 | 
			
		||||
            left = 0
 | 
			
		||||
            right = self.width
 | 
			
		||||
            upper = n * fragment_height
 | 
			
		||||
            lower = min((n + 1) * fragment_height, self.height)
 | 
			
		||||
            box = (left, upper, right, lower)
 | 
			
		||||
            fragments.append(self.img_original.crop(box))
 | 
			
		||||
        return fragments
 | 
			
		||||
							
								
								
									
										107
									
								
								src/escpos/katakana.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,107 @@
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
"""Helpers to encode Japanese characters.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
except ImportError:
 | 
			
		||||
    jaconv = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def encode_katakana(text):
 | 
			
		||||
    """I don't think this quite works yet."""
 | 
			
		||||
    encoded = []
 | 
			
		||||
    for char in text:
 | 
			
		||||
        if jaconv:
 | 
			
		||||
            # try to convert japanese text to half-katakanas
 | 
			
		||||
            char = jaconv.z2h(jaconv.hira2kata(char))
 | 
			
		||||
            # TODO: "the conversion may result in multiple characters"
 | 
			
		||||
            # If that really can happen (I am not really shure), than the string would have to be split and every single
 | 
			
		||||
            #  character has to passed through the following lines.
 | 
			
		||||
 | 
			
		||||
        if char in TXT_ENC_KATAKANA_MAP:
 | 
			
		||||
            encoded.append(TXT_ENC_KATAKANA_MAP[char])
 | 
			
		||||
        else:
 | 
			
		||||
            # TODO doesn't this discard all that is not in the map? Can we be sure that the input does contain only
 | 
			
		||||
            # encodable characters? We could at least throw an exception if encoding is not possible.
 | 
			
		||||
            pass
 | 
			
		||||
    return b"".join(encoded)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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',
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										296
									
								
								src/escpos/magicencode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,296 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
""" Magic Encode
 | 
			
		||||
 | 
			
		||||
This module tries to convert an UTF-8 string to an encoded string for the printer.
 | 
			
		||||
It uses trial and error in order to guess the right codepage.
 | 
			
		||||
The code is based on the encoding-code in py-xml-escpos by @fvdsn.
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <dev@pkanzler.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 Patrick Kanzler and Frédéric van der Essen
 | 
			
		||||
: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
 | 
			
		||||
from .exceptions import Error
 | 
			
		||||
from .codepages import CodePages
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Encoder(object):
 | 
			
		||||
    """Takes a list of available code spaces. Picks the right one for a
 | 
			
		||||
    given character.
 | 
			
		||||
 | 
			
		||||
    Note: To determine the code page, it needs to do the conversion, and
 | 
			
		||||
    thus already knows what the final byte in the target encoding would
 | 
			
		||||
    be. Nevertheless, the API of this class doesn't return the byte.
 | 
			
		||||
 | 
			
		||||
    The caller use to do the character conversion itself.
 | 
			
		||||
 | 
			
		||||
        $ python -m timeit -s "{u'ö':'a'}.get(u'ö')"
 | 
			
		||||
        100000000 loops, best of 3: 0.0133 usec per loop
 | 
			
		||||
 | 
			
		||||
        $ python -m timeit -s "u'ö'.encode('latin1')"
 | 
			
		||||
        100000000 loops, best of 3: 0.0141 usec per loop
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, codepage_map):
 | 
			
		||||
        self.codepages = codepage_map
 | 
			
		||||
        self.available_encodings = set(codepage_map.keys())
 | 
			
		||||
        self.available_characters = {}
 | 
			
		||||
        self.used_encodings = set()
 | 
			
		||||
 | 
			
		||||
    def get_sequence(self, encoding):
 | 
			
		||||
        return int(self.codepages[encoding])
 | 
			
		||||
 | 
			
		||||
    def get_encoding_name(self, encoding):
 | 
			
		||||
        """Given an encoding provided by the user, will return a
 | 
			
		||||
        canonical encoding name; and also validate that the encoding
 | 
			
		||||
        is supported.
 | 
			
		||||
 | 
			
		||||
        TODO: Support encoding aliases: pc437 instead of cp437.
 | 
			
		||||
        """
 | 
			
		||||
        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())))
 | 
			
		||||
        return encoding
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _get_codepage_char_list(encoding):
 | 
			
		||||
        """Get codepage character list
 | 
			
		||||
 | 
			
		||||
        Gets characters 128-255 for a given code page, as an array.
 | 
			
		||||
 | 
			
		||||
        :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)
 | 
			
		||||
            return encodable_chars
 | 
			
		||||
        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'])
 | 
			
		||||
                except UnicodeDecodeError:
 | 
			
		||||
                    # Non-encodable character, just skip it
 | 
			
		||||
                    pass
 | 
			
		||||
            return encodable_chars
 | 
			
		||||
        raise LookupError("Can't find a known encoding for {}".format(encoding))
 | 
			
		||||
 | 
			
		||||
    def _get_codepage_char_map(self, encoding):
 | 
			
		||||
        """ Get codepage character map
 | 
			
		||||
 | 
			
		||||
        Process an encoding and return a map of UTF-characters to code points
 | 
			
		||||
        in this encoding.
 | 
			
		||||
 | 
			
		||||
        This is generated once only, and returned from a cache.
 | 
			
		||||
 | 
			
		||||
        :param encoding: The name of the encoding.
 | 
			
		||||
        """
 | 
			
		||||
        # Skip things that were loaded previously
 | 
			
		||||
        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))
 | 
			
		||||
        self.available_characters[encoding] = codepage_char_map
 | 
			
		||||
        return codepage_char_map
 | 
			
		||||
 | 
			
		||||
    def can_encode(self, encoding, char):
 | 
			
		||||
        """Determine if a character is encodeable in the given code page.
 | 
			
		||||
 | 
			
		||||
        :param encoding: The name of the encoding.
 | 
			
		||||
        :param char: The character to attempt to encode.
 | 
			
		||||
        """
 | 
			
		||||
        available_map = {}
 | 
			
		||||
        try:
 | 
			
		||||
            available_map = self._get_codepage_char_map(encoding)
 | 
			
		||||
        except LookupError:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Decide whether this character is encodeable in this code page
 | 
			
		||||
        is_ascii = ord(char) < 128
 | 
			
		||||
        is_encodable = char in available_map
 | 
			
		||||
        return is_ascii or is_encodable
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _encode_char(char, charmap, defaultchar):
 | 
			
		||||
        """ Encode a single character with the given encoding map
 | 
			
		||||
 | 
			
		||||
        :param char: char to encode
 | 
			
		||||
        :param charmap: dictionary for mapping characters in this code page
 | 
			
		||||
        """
 | 
			
		||||
        if ord(char) < 128:
 | 
			
		||||
            return ord(char)
 | 
			
		||||
        if char in charmap:
 | 
			
		||||
            return charmap[char]
 | 
			
		||||
        return ord(defaultchar)
 | 
			
		||||
 | 
			
		||||
    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])
 | 
			
		||||
        return output_bytes
 | 
			
		||||
 | 
			
		||||
    def __encoding_sort_func(self, item):
 | 
			
		||||
        key, index = item
 | 
			
		||||
        return (
 | 
			
		||||
            key in self.used_encodings,
 | 
			
		||||
            index
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def find_suitable_encoding(self, char):
 | 
			
		||||
        """The order of our search is a specific one:
 | 
			
		||||
 | 
			
		||||
        1. code pages that we already tried before; there is a good
 | 
			
		||||
           chance they might work again, reducing the search space,
 | 
			
		||||
           and by re-using already used encodings we might also
 | 
			
		||||
           reduce the number of codepage change instructiosn we have
 | 
			
		||||
           to send. Still, any performance gains will presumably be
 | 
			
		||||
           fairly minor.
 | 
			
		||||
 | 
			
		||||
        2. code pages in lower ESCPOS slots first. Presumably, they
 | 
			
		||||
           are more likely to be supported, so if a printer profile
 | 
			
		||||
           is missing or incomplete, we might increase our change
 | 
			
		||||
           that the code page we pick for this character is actually
 | 
			
		||||
           supported.
 | 
			
		||||
        """
 | 
			
		||||
        sorted_encodings = sorted(
 | 
			
		||||
            self.codepages.items(),
 | 
			
		||||
            key=self.__encoding_sort_func)
 | 
			
		||||
 | 
			
		||||
        for encoding, _ in sorted_encodings:
 | 
			
		||||
            if self.can_encode(encoding, char):
 | 
			
		||||
                # This encoding worked; at it to the set of used ones.
 | 
			
		||||
                self.used_encodings.add(encoding)
 | 
			
		||||
                return encoding
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def split_writable_text(encoder, text, encoding):
 | 
			
		||||
    """Splits off as many characters from the begnning of text as
 | 
			
		||||
    are writable with "encoding". Returns a 2-tuple (writable, rest).
 | 
			
		||||
    """
 | 
			
		||||
    if not encoding:
 | 
			
		||||
        return None, text
 | 
			
		||||
 | 
			
		||||
    for idx, char in enumerate(text):
 | 
			
		||||
        if encoder.can_encode(encoding, char):
 | 
			
		||||
            continue
 | 
			
		||||
        return text[:idx], text[idx:]
 | 
			
		||||
 | 
			
		||||
    return text, None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MagicEncode(object):
 | 
			
		||||
    """A helper that helps us to automatically switch to the right
 | 
			
		||||
    code page to encode any given Unicode character.
 | 
			
		||||
 | 
			
		||||
    This will consider the printers supported codepages, according
 | 
			
		||||
    to the printer profile, and if a character cannot be encoded
 | 
			
		||||
    with the current profile, it will attempt to find a suitable one.
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        :param driver:
 | 
			
		||||
        :param encoding: If you know the current encoding of the printer
 | 
			
		||||
        when initializing this class, set it here. If the current
 | 
			
		||||
        encoding is unknown, the first character emitted will be a
 | 
			
		||||
        codepage switch.
 | 
			
		||||
        :param disabled:
 | 
			
		||||
        :param defaultsymbol:
 | 
			
		||||
        :param encoder:
 | 
			
		||||
        """
 | 
			
		||||
        if disabled and not 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())
 | 
			
		||||
 | 
			
		||||
        self.encoding = self.encoder.get_encoding_name(encoding) if encoding else None
 | 
			
		||||
        self.defaultsymbol = defaultsymbol
 | 
			
		||||
        self.disabled = disabled
 | 
			
		||||
 | 
			
		||||
    def force_encoding(self, encoding):
 | 
			
		||||
        """Sets a fixed encoding. The change is emitted right away.
 | 
			
		||||
 | 
			
		||||
        From now one, this buffer will switch the code page anymore.
 | 
			
		||||
        However, it will still keep track of the current code page.
 | 
			
		||||
        """
 | 
			
		||||
        if not encoding:
 | 
			
		||||
            self.disabled = False
 | 
			
		||||
        else:
 | 
			
		||||
            self.write_with_encoding(encoding, None)
 | 
			
		||||
            self.disabled = True
 | 
			
		||||
 | 
			
		||||
    def write(self, text):
 | 
			
		||||
        """Write the text, automatically switching encodings.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if self.disabled:
 | 
			
		||||
            self.write_with_encoding(self.encoding, text)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # See how far we can go into the text with the current encoding
 | 
			
		||||
        to_write, text = split_writable_text(self.encoder, text, self.encoding)
 | 
			
		||||
        if to_write:
 | 
			
		||||
            self.write_with_encoding(self.encoding, to_write)
 | 
			
		||||
 | 
			
		||||
        while text:
 | 
			
		||||
            # See if any of the code pages that the printer profile
 | 
			
		||||
            # supports can encode this character.
 | 
			
		||||
            encoding = self.encoder.find_suitable_encoding(text[0])
 | 
			
		||||
            if not encoding:
 | 
			
		||||
                self._handle_character_failed(text[0])
 | 
			
		||||
                text = text[1:]
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # Write as much text as possible with the encoding found.
 | 
			
		||||
            to_write, text = split_writable_text(self.encoder, text, encoding)
 | 
			
		||||
            if to_write:
 | 
			
		||||
                self.write_with_encoding(encoding, to_write)
 | 
			
		||||
 | 
			
		||||
    def _handle_character_failed(self, char):
 | 
			
		||||
        """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)
 | 
			
		||||
            ))
 | 
			
		||||
 | 
			
		||||
        # 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)))
 | 
			
		||||
 | 
			
		||||
        if text:
 | 
			
		||||
            self.driver._raw(self.encoder.encode(text, encoding))
 | 
			
		||||
							
								
								
									
										306
									
								
								src/escpos/printer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,306 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
""" This module contains the implementations of abstract base class :py:class:`Escpos`.
 | 
			
		||||
 | 
			
		||||
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
 | 
			
		||||
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
 | 
			
		||||
:license: MIT
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
from __future__ import division
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import usb.core
 | 
			
		||||
import usb.util
 | 
			
		||||
import serial
 | 
			
		||||
import socket
 | 
			
		||||
 | 
			
		||||
from .escpos import Escpos
 | 
			
		||||
from .exceptions import USBNotFoundError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Usb(Escpos):
 | 
			
		||||
    """ USB printer
 | 
			
		||||
 | 
			
		||||
    This class describes a printer that natively speaks USB.
 | 
			
		||||
 | 
			
		||||
    inheritance:
 | 
			
		||||
 | 
			
		||||
    .. inheritance-diagram:: escpos.printer.Usb
 | 
			
		||||
        :parts: 1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):  # noqa: N803
 | 
			
		||||
        """
 | 
			
		||||
        :param idVendor: Vendor ID
 | 
			
		||||
        :param idProduct: Product ID
 | 
			
		||||
        :param timeout: Is the time limit of the USB operation. Default without timeout.
 | 
			
		||||
        :param in_ep: Input end point
 | 
			
		||||
        :param out_ep: Output end point
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.idVendor = idVendor
 | 
			
		||||
        self.idProduct = idProduct
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.in_ep = in_ep
 | 
			
		||||
        self.out_ep = out_ep
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Search device on USB tree and set it as escpos device """
 | 
			
		||||
        self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
 | 
			
		||||
        if self.device is None:
 | 
			
		||||
            raise USBNotFoundError("Device not found or cable not plugged in.")
 | 
			
		||||
 | 
			
		||||
        check_driver = None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            check_driver = self.device.is_kernel_driver_active(0)
 | 
			
		||||
        except NotImplementedError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if check_driver is None or check_driver:
 | 
			
		||||
            try:
 | 
			
		||||
                self.device.detach_kernel_driver(0)
 | 
			
		||||
            except usb.core.USBError as e:
 | 
			
		||||
                if check_driver is not None:
 | 
			
		||||
                    print("Could not detatch kernel driver: {0}".format(str(e)))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.device.set_configuration()
 | 
			
		||||
            self.device.reset()
 | 
			
		||||
        except usb.core.USBError as e:
 | 
			
		||||
            print("Could not set configuration: {0}".format(str(e)))
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format
 | 
			
		||||
 | 
			
		||||
        :param msg: arbitrary code to be printed
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        self.device.write(self.out_ep, msg, self.timeout)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        """ Release USB interface """
 | 
			
		||||
        if self.device:
 | 
			
		||||
            usb.util.dispose_resources(self.device)
 | 
			
		||||
        self.device = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Serial(Escpos):
 | 
			
		||||
    """ Serial printer
 | 
			
		||||
 | 
			
		||||
    This class describes a printer that is connected by serial interface.
 | 
			
		||||
 | 
			
		||||
    inheritance:
 | 
			
		||||
 | 
			
		||||
    .. inheritance-diagram:: escpos.printer.Serial
 | 
			
		||||
        :parts: 1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
        :param baudrate: Baud rate for serial transmission
 | 
			
		||||
        :param bytesize: Serial buffer size
 | 
			
		||||
        :param timeout:  Read/Write timeout
 | 
			
		||||
        :param parity:   Parity checking
 | 
			
		||||
        :param stopbits: Number of stop bits
 | 
			
		||||
        :param xonxoff:  Software flow control
 | 
			
		||||
        :param dsrdtr:   Hardware flow control (False to enable RTS/CTS)
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.devfile = devfile
 | 
			
		||||
        self.baudrate = baudrate
 | 
			
		||||
        self.bytesize = bytesize
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.parity = parity
 | 
			
		||||
        self.stopbits = stopbits
 | 
			
		||||
        self.xonxoff = xonxoff
 | 
			
		||||
        self.dsrdtr = dsrdtr
 | 
			
		||||
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ Setup serial port and set is as escpos device """
 | 
			
		||||
        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")
 | 
			
		||||
        else:
 | 
			
		||||
            print("Unable to open serial printer on: {0}".format(str(self.devfile)))
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format
 | 
			
		||||
 | 
			
		||||
        :param msg: arbitrary code to be printed
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        self.device.write(msg)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        """ Close Serial interface """
 | 
			
		||||
        if self.device is not None:
 | 
			
		||||
            self.device.flush()
 | 
			
		||||
            self.device.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Network(Escpos):
 | 
			
		||||
    """ 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``.
 | 
			
		||||
 | 
			
		||||
    If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: none
 | 
			
		||||
 | 
			
		||||
        socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
 | 
			
		||||
 | 
			
		||||
    Then you should be able to attach to port ``4242`` with this class.
 | 
			
		||||
    Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should
 | 
			
		||||
    work the same with this class. For the address of the printer check its manuals.
 | 
			
		||||
 | 
			
		||||
    inheritance:
 | 
			
		||||
 | 
			
		||||
    .. inheritance-diagram:: escpos.printer.Network
 | 
			
		||||
        :parts: 1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, host, port=9100, timeout=60, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        :param host:    Printer's hostname or IP address
 | 
			
		||||
        :param port:    Port to write to
 | 
			
		||||
        :param timeout: timeout in seconds for the socket-library
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.host = host
 | 
			
		||||
        self.port = port
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ 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))
 | 
			
		||||
 | 
			
		||||
        if self.device is None:
 | 
			
		||||
            print("Could not open socket for {0}".format(self.host))
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format
 | 
			
		||||
 | 
			
		||||
        :param msg: arbitrary code to be printed
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        self.device.sendall(msg)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        """ Close TCP connection """
 | 
			
		||||
        if self.device is not None:
 | 
			
		||||
            self.device.shutdown(socket.SHUT_RDWR)
 | 
			
		||||
            self.device.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class File(Escpos):
 | 
			
		||||
    """ 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
 | 
			
		||||
    and produce arbitrary errors.
 | 
			
		||||
 | 
			
		||||
    inheritance:
 | 
			
		||||
 | 
			
		||||
    .. inheritance-diagram:: escpos.printer.File
 | 
			
		||||
        :parts: 1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        :param devfile : Device file under dev filesystem
 | 
			
		||||
        :param auto_flush: automatically call flush after every call of _raw()
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.devfile = devfile
 | 
			
		||||
        self.auto_flush = auto_flush
 | 
			
		||||
        self.open()
 | 
			
		||||
 | 
			
		||||
    def open(self):
 | 
			
		||||
        """ 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 """
 | 
			
		||||
        self.device.flush()
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format
 | 
			
		||||
 | 
			
		||||
        :param msg: arbitrary code to be printed
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        self.device.write(msg)
 | 
			
		||||
        if self.auto_flush:
 | 
			
		||||
            self.flush()
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        """ Close system file """
 | 
			
		||||
        if self.device is not None:
 | 
			
		||||
            self.device.flush()
 | 
			
		||||
            self.device.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Dummy(Escpos):
 | 
			
		||||
    """ 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
 | 
			
		||||
    generating print jobs for later use, or testing output.
 | 
			
		||||
 | 
			
		||||
    inheritance:
 | 
			
		||||
 | 
			
		||||
    .. inheritance-diagram:: escpos.printer.Dummy
 | 
			
		||||
        :parts: 1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        Escpos.__init__(self, *args, **kwargs)
 | 
			
		||||
        self._output_list = []
 | 
			
		||||
 | 
			
		||||
    def _raw(self, msg):
 | 
			
		||||
        """ Print any command sent in raw format
 | 
			
		||||
 | 
			
		||||
        :param msg: arbitrary code to be printed
 | 
			
		||||
        :type msg: bytes
 | 
			
		||||
        """
 | 
			
		||||
        self._output_list.append(msg)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def output(self):
 | 
			
		||||
        """ Get the data that was sent to this printer """
 | 
			
		||||
        return b''.join(self._output_list)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										7
									
								
								test/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from escpos.printer import Dummy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def driver():
 | 
			
		||||
    return Dummy()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_transparent.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 65 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_transparent.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 167 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 65 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 175 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 156 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 72 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 160 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 239 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 72 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 160 B  | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 239 B  | 
							
								
								
									
										30
									
								
								test/test_abstract_base_class.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,30 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""verifies that the metaclass abc is properly used by Escpos
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 Patrick Kanzler
 | 
			
		||||
: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 escpos.escpos as escpos
 | 
			
		||||
from abc import ABCMeta
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@raises(TypeError)
 | 
			
		||||
def test_abstract_base_class_raises():
 | 
			
		||||
    """test whether the abstract base class raises an exception for Escpos"""
 | 
			
		||||
    escpos.Escpos()  # This call should raise TypeError because of abstractmethod _raw()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_abstract_base_class():
 | 
			
		||||
    """ test whether Escpos has the metaclass ABCMeta """
 | 
			
		||||
    assert issubclass(escpos.Escpos, object)
 | 
			
		||||
    assert type(escpos.Escpos) is ABCMeta
 | 
			
		||||
							
								
								
									
										118
									
								
								test/test_cli.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,118 @@
 | 
			
		||||
"""Test for the CLI
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
import escpos
 | 
			
		||||
 | 
			
		||||
TEST_DIR = os.path.abspath('test/test-cli-output')
 | 
			
		||||
 | 
			
		||||
DEVFILE_NAME = 'testfile'
 | 
			
		||||
 | 
			
		||||
DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME)
 | 
			
		||||
CONFIGFILE = 'testconfig.yaml'
 | 
			
		||||
CONFIG_YAML = '''
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
printer:
 | 
			
		||||
    type: file
 | 
			
		||||
    devfile: {testfile}
 | 
			
		||||
'''.format(
 | 
			
		||||
    testfile=DEVFILE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCLI:
 | 
			
		||||
    """ 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:
 | 
			
		||||
            config.write(CONFIG_YAML)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def teardown_class(cls):
 | 
			
		||||
        """ Remove config file """
 | 
			
		||||
        os.remove(CONFIGFILE)
 | 
			
		||||
 | 
			
		||||
    def setup(self):
 | 
			
		||||
        """ Create a file to print to and set up env"""
 | 
			
		||||
        self.env = None
 | 
			
		||||
        self.default_args = None
 | 
			
		||||
 | 
			
		||||
        self.env = TestFileEnvironment(
 | 
			
		||||
            base_path=TEST_DIR,
 | 
			
		||||
            cwd=os.getcwd(),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.default_args = (
 | 
			
		||||
            'python-escpos',
 | 
			
		||||
            '-c',
 | 
			
		||||
            CONFIGFILE,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        fhandle = open(DEVFILE, 'a')
 | 
			
		||||
        try:
 | 
			
		||||
            os.utime(DEVFILE, None)
 | 
			
		||||
        finally:
 | 
			
		||||
            fhandle.close()
 | 
			
		||||
 | 
			
		||||
    def teardown(self):
 | 
			
		||||
        """ 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')
 | 
			
		||||
        assert not result.stderr
 | 
			
		||||
        assert 'usage' in result.stdout
 | 
			
		||||
 | 
			
		||||
    def test_cli_version(self):
 | 
			
		||||
        """ Test the version string """
 | 
			
		||||
        result = self.env.run('python-escpos', 'version')
 | 
			
		||||
        assert not result.stderr
 | 
			
		||||
        assert_equals(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'
 | 
			
		||||
        result = self.env.run(
 | 
			
		||||
            *(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'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_cli_text_inavlid_args(self):
 | 
			
		||||
        """ Test a failure to send valid arguments """
 | 
			
		||||
        result = self.env.run(
 | 
			
		||||
            *(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 not result.files_updated
 | 
			
		||||
							
								
								
									
										38
									
								
								test/test_function_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
			
		||||
#!/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
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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.
 | 
			
		||||
    """
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.barcode(data, bctype)
 | 
			
		||||
    assert instance.output == expected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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.
 | 
			
		||||
    """
 | 
			
		||||
    profile = Profile(features={BARCODE_B: supports_b})
 | 
			
		||||
    instance = printer.Dummy(profile=profile)
 | 
			
		||||
    with pytest.raises(BarcodeTypeError):
 | 
			
		||||
        instance.barcode('test', bctype)
 | 
			
		||||
 | 
			
		||||
    assert instance.output == b''
 | 
			
		||||
							
								
								
									
										141
									
								
								test/test_function_image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,141 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
""" Image function tests- Check that image print commands are sent correctly.
 | 
			
		||||
 | 
			
		||||
:author: `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
: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
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Raster format print
 | 
			
		||||
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')
 | 
			
		||||
    # 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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Column format print
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Graphics print
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
							
								
								
									
										54
									
								
								test/test_function_panel_button.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""tests for panel button function
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
: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 with_setup
 | 
			
		||||
 | 
			
		||||
import escpos.printer as printer
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
devfile = 'testfile'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_testfile():
 | 
			
		||||
    """create a testfile as devfile"""
 | 
			
		||||
    fhandle = open(devfile, 'a')
 | 
			
		||||
    try:
 | 
			
		||||
        os.utime(devfile, None)
 | 
			
		||||
    finally:
 | 
			
		||||
        fhandle.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def teardown_testfile():
 | 
			
		||||
    """destroy testfile again"""
 | 
			
		||||
    os.remove(devfile)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@with_setup(setup_testfile, teardown_testfile)
 | 
			
		||||
def test_function_panel_button_on():
 | 
			
		||||
    """test the panel button function (enabling) by comparing output"""
 | 
			
		||||
    instance = printer.File(devfile=devfile)
 | 
			
		||||
    instance.panel_buttons()
 | 
			
		||||
    instance.flush()
 | 
			
		||||
    with open(devfile, "rb") as f:
 | 
			
		||||
        assert(f.read() == b'\x1B\x63\x35\x00')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@with_setup(setup_testfile, teardown_testfile)
 | 
			
		||||
def test_function_panel_button_off():
 | 
			
		||||
    """test the panel button function (disabling) by comparing output"""
 | 
			
		||||
    instance = printer.File(devfile=devfile)
 | 
			
		||||
    instance.panel_buttons(False)
 | 
			
		||||
    instance.flush()
 | 
			
		||||
    with open(devfile, "rb") as f:
 | 
			
		||||
        assert(f.read() == b'\x1B\x63\x35\x01')
 | 
			
		||||
							
								
								
									
										99
									
								
								test/test_function_qr_native.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,99 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""test native QR code printing
 | 
			
		||||
 | 
			
		||||
:author: `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
: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 escpos.printer as printer
 | 
			
		||||
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_empty():
 | 
			
		||||
    """Test QR printing blank code"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("", native=True)
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@raises(ValueError)
 | 
			
		||||
def test_invalid_ec():
 | 
			
		||||
    """Test invalid QR error correction"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("1234", native=True, ec=-1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@raises(ValueError)
 | 
			
		||||
def test_invalid_size():
 | 
			
		||||
    """Test invalid QR size"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("1234", native=True, size=0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@raises(ValueError)
 | 
			
		||||
def test_invalid_model():
 | 
			
		||||
    """Test invalid QR model"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("1234", native=True, model="Hello")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_image():
 | 
			
		||||
    """Test QR as image"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("1", native=False, size=1)
 | 
			
		||||
    print(instance.output)
 | 
			
		||||
    expected = 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'
 | 
			
		||||
    assert(instance.output == expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@raises(ValueError)
 | 
			
		||||
def test_image_invalid_model():
 | 
			
		||||
    """Test unsupported QR model as image"""
 | 
			
		||||
    instance = printer.Dummy()
 | 
			
		||||
    instance.qr("1234", native=False, model=QR_MODEL_1)
 | 
			
		||||
							
								
								
									
										32
									
								
								test/test_function_qr_non-native.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,32 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
"""tests for the non-native part of qr()
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:license: MIT
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
from __future__ import division
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from escpos.printer import Dummy
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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.
 | 
			
		||||
 | 
			
		||||
    The type should be PIL.Image
 | 
			
		||||
    """
 | 
			
		||||
    d = Dummy()
 | 
			
		||||
    d.qr("LoremIpsum")
 | 
			
		||||
    args, kwargs = img_function.call_args
 | 
			
		||||
    assert isinstance(args[0], Image.Image)
 | 
			
		||||
							
								
								
									
										41
									
								
								test/test_function_text.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,41 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""tests for the text printing function
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
: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
 | 
			
		||||
from hypothesis import given, assume
 | 
			
		||||
import hypothesis.strategies as st
 | 
			
		||||
from escpos.printer import Dummy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_printer():
 | 
			
		||||
    return Dummy(magic_encode_args={'disabled': True, 'encoding': 'CP437'})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@given(text=st.text())
 | 
			
		||||
def test_text(text):
 | 
			
		||||
    """Test that text() calls the MagicEncode object.
 | 
			
		||||
    """
 | 
			
		||||
    instance = get_printer()
 | 
			
		||||
    instance.magic.write = mock.Mock()
 | 
			
		||||
    instance.text(text)
 | 
			
		||||
    instance.magic.write.assert_called_with(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.'
 | 
			
		||||
							
								
								
									
										26
									
								
								test/test_functions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
			
		||||
from nose.tools import assert_raises
 | 
			
		||||
from escpos.printer import Dummy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_line_spacing_code_gen():
 | 
			
		||||
    printer = Dummy()
 | 
			
		||||
    printer.line_spacing(10)
 | 
			
		||||
    assert printer.output == b'\x1b3\n'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_line_spacing_rest():
 | 
			
		||||
    printer = Dummy()
 | 
			
		||||
    printer.line_spacing()
 | 
			
		||||
    assert printer.output == b'\x1b2'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_line_spacing_error_handling():
 | 
			
		||||
    printer = Dummy()
 | 
			
		||||
    with assert_raises(ValueError):
 | 
			
		||||
        printer.line_spacing(99, divisor=44)
 | 
			
		||||
    with assert_raises(ValueError):
 | 
			
		||||
        printer.line_spacing(divisor=80, spacing=86)
 | 
			
		||||
    with assert_raises(ValueError):
 | 
			
		||||
        printer.line_spacing(divisor=360, spacing=256)
 | 
			
		||||
    with assert_raises(ValueError):
 | 
			
		||||
        printer.line_spacing(divisor=180, spacing=256)
 | 
			
		||||
							
								
								
									
										71
									
								
								test/test_image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,71 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
""" Image tests- Check that images from different source formats are correctly
 | 
			
		||||
converted to ESC/POS column & raster formats.
 | 
			
		||||
 | 
			
		||||
:author: `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
 | 
			
		||||
:license: MIT
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from escpos.image import EscposImage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_split():
 | 
			
		||||
    """
 | 
			
		||||
    test whether the split-function works as expected
 | 
			
		||||
    """
 | 
			
		||||
    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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 
 | 
			
		||||
    for row in im.to_column_format(False):
 | 
			
		||||
        assert(row == column_format_expected[i])
 | 
			
		||||
        i += 1
 | 
			
		||||
							
								
								
									
										41
									
								
								test/test_load_module.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,41 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""very basic test cases that load the classes
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
: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 with_setup
 | 
			
		||||
 | 
			
		||||
import escpos.printer as printer
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
devfile = 'testfile'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_testfile():
 | 
			
		||||
    """create a testfile as devfile"""
 | 
			
		||||
    fhandle = open(devfile, 'a')
 | 
			
		||||
    try:
 | 
			
		||||
        os.utime(devfile, None)
 | 
			
		||||
    finally:
 | 
			
		||||
        fhandle.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def teardown_testfile():
 | 
			
		||||
    """destroy testfile again"""
 | 
			
		||||
    os.remove(devfile)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@with_setup(setup_testfile, teardown_testfile)
 | 
			
		||||
def test_instantiation():
 | 
			
		||||
    """test the instantiation of a escpos-printer class and basic printing"""
 | 
			
		||||
    instance = printer.File(devfile=devfile)
 | 
			
		||||
    instance.text('This is a test\n')
 | 
			
		||||
							
								
								
									
										127
									
								
								test/test_magicencode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,127 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
"""tests for the magic encode module
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
: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
 | 
			
		||||
from hypothesis import given, example
 | 
			
		||||
import hypothesis.strategies as st
 | 
			
		||||
from escpos.magicencode import MagicEncode, Encoder
 | 
			
		||||
from escpos.katakana import encode_katakana
 | 
			
		||||
from escpos.exceptions import CharCodeError, Error
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEncoder:
 | 
			
		||||
    """
 | 
			
		||||
    Tests the single encoders.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    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')
 | 
			
		||||
 | 
			
		||||
    def test_find_suitable_encoding(self):
 | 
			
		||||
        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')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMagicEncode:
 | 
			
		||||
    """
 | 
			
		||||
    Tests the magic encode functionality.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class TestInit:
 | 
			
		||||
        """
 | 
			
		||||
        Test initialization.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def test_disabled_requires_encoding(self, driver):
 | 
			
		||||
            """
 | 
			
		||||
            Test that disabled without encoder raises an error.
 | 
			
		||||
 | 
			
		||||
            :param driver:
 | 
			
		||||
            """
 | 
			
		||||
            with pytest.raises(Error):
 | 
			
		||||
                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.'
 | 
			
		||||
 | 
			
		||||
        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.'
 | 
			
		||||
 | 
			
		||||
        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.'
 | 
			
		||||
 | 
			
		||||
    class TestWrite:
 | 
			
		||||
 | 
			
		||||
        def test_write(self, driver):
 | 
			
		||||
            encode = MagicEncode(driver)
 | 
			
		||||
            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.'
 | 
			
		||||
 | 
			
		||||
        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.'
 | 
			
		||||
 | 
			
		||||
    class TestForceEncoding:
 | 
			
		||||
 | 
			
		||||
        def test(self, driver):
 | 
			
		||||
            encode = MagicEncode(driver)
 | 
			
		||||
            encode.force_encoding('CP437')
 | 
			
		||||
            assert driver.output == b'\x1bt\x00'
 | 
			
		||||
 | 
			
		||||
            encode.write('€ ist teuro.')
 | 
			
		||||
            assert driver.output == b'\x1bt\x00? ist teuro.'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import jaconv
 | 
			
		||||
except ImportError:
 | 
			
		||||
    jaconv = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.skipif(not jaconv, reason="jaconv not installed")
 | 
			
		||||
class TestKatakana:
 | 
			
		||||
    @given(st.text())
 | 
			
		||||
    @example("カタカナ")
 | 
			
		||||
    @example("あいうえお")
 | 
			
		||||
    @example("ハンカクカタカナ")
 | 
			
		||||
    def test_accept(self, text):
 | 
			
		||||
        encode_katakana(text)
 | 
			
		||||
 | 
			
		||||
    def test_result(self):
 | 
			
		||||
        assert encode_katakana('カタカナ') == b'\xb6\xc0\xb6\xc5'
 | 
			
		||||
        assert encode_katakana("あいうえお") == b'\xb1\xb2\xb3\xb4\xb5'
 | 
			
		||||
							
								
								
									
										73
									
								
								test/test_printer_file.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,73 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
#  -*- coding: utf-8 -*-
 | 
			
		||||
"""tests for the File printer
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:license: MIT
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
from __future__ import division
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from hypothesis import given
 | 
			
		||||
from hypothesis.strategies import text
 | 
			
		||||
 | 
			
		||||
import escpos.printer as printer
 | 
			
		||||
 | 
			
		||||
if six.PY3:
 | 
			
		||||
    mock_open_call = 'builtins.open'
 | 
			
		||||
else:
 | 
			
		||||
    mock_open_call = '__builtin__.open'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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_open = mocker.patch(mock_open_call)
 | 
			
		||||
    printer.File(devfile=path)
 | 
			
		||||
    assert mock_escpos.called
 | 
			
		||||
    mock_open.assert_called_with(path, "wb")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@given(txt=text())
 | 
			
		||||
def test_auto_flush(mocker, txt):
 | 
			
		||||
    """test auto_flush in file-printer"""
 | 
			
		||||
    mock_escpos = mocker.patch('escpos.escpos.Escpos.__init__')
 | 
			
		||||
    mock_open = mocker.patch(mock_open_call)
 | 
			
		||||
    mock_device = mocker.patch.object(printer.File, 'device')
 | 
			
		||||
 | 
			
		||||
    p = printer.File(auto_flush=False)
 | 
			
		||||
    # inject the mocked device-object
 | 
			
		||||
    p.device = mock_device
 | 
			
		||||
    p._raw(txt)
 | 
			
		||||
    assert not mock_device.flush.called
 | 
			
		||||
    mock_device.reset_mock()
 | 
			
		||||
    p = printer.File(auto_flush=True)
 | 
			
		||||
    # inject the mocked device-object
 | 
			
		||||
    p.device = mock_device
 | 
			
		||||
    p._raw(txt)
 | 
			
		||||
    assert mock_device.flush.called
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@given(txt=text())
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
    p = printer.File(auto_flush=False)
 | 
			
		||||
    # inject the mocked device-object
 | 
			
		||||
    p.device = mock_device
 | 
			
		||||
    p._raw(txt)
 | 
			
		||||
    assert not mock_device.flush.called
 | 
			
		||||
    p.close()
 | 
			
		||||
    assert mock_device.flush.called
 | 
			
		||||
    assert mock_device.close.called
 | 
			
		||||
							
								
								
									
										38
									
								
								test/test_profile.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def profile():
 | 
			
		||||
    return get_profile('default')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBaseProfile:
 | 
			
		||||
    """Test the `BaseProfile` class.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def test_get_font(self, profile):
 | 
			
		||||
        with pytest.raises(NotSupported):
 | 
			
		||||
            assert profile.get_font('3')
 | 
			
		||||
        assert profile.get_font(1) == 1
 | 
			
		||||
        assert profile.get_font('a') == 0
 | 
			
		||||
 | 
			
		||||
    def test_supports(self, profile):
 | 
			
		||||
        assert not profile.supports('asdf asdf')
 | 
			
		||||
        assert profile.supports(BARCODE_B)
 | 
			
		||||
 | 
			
		||||
    def test_get_columns(self, profile):
 | 
			
		||||
        assert profile.get_columns('a') > 5
 | 
			
		||||
        with pytest.raises(NotSupported):
 | 
			
		||||
            assert profile.get_columns('asdfasdf')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCustomProfile:
 | 
			
		||||
    """Test custom profile options with the `Profile` class.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def test_columns(self):
 | 
			
		||||
        assert Profile(columns=10).get_columns('sdfasdf') == 10
 | 
			
		||||
 | 
			
		||||
    def test_features(self):
 | 
			
		||||
        assert Profile(features={'foo': True}).supports('foo')
 | 
			
		||||
							
								
								
									
										24
									
								
								test/test_with_statement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
#!/usr/bin/python
 | 
			
		||||
"""test the facility which enables usage of the with-statement
 | 
			
		||||
 | 
			
		||||
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
 | 
			
		||||
:organization: `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
 | 
			
		||||
: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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_with_statement():
 | 
			
		||||
    """Use with statement"""
 | 
			
		||||
    dummy_printer = printer.Dummy()
 | 
			
		||||
    with escpos.EscposIO(dummy_printer) as p:
 | 
			
		||||
            p.writelines('Some text.\n')
 | 
			
		||||
    # TODO extend these tests as they don't really do anything at the moment
 | 
			
		||||
							
								
								
									
										28
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
			
		||||
[tox]
 | 
			
		||||
envlist = py27, py34, py35, docs, flake8
 | 
			
		||||
 | 
			
		||||
[testenv]
 | 
			
		||||
deps = nose
 | 
			
		||||
       jaconv
 | 
			
		||||
       coverage
 | 
			
		||||
       scripttest
 | 
			
		||||
       mock
 | 
			
		||||
       pytest
 | 
			
		||||
       pytest-cov
 | 
			
		||||
       pytest-mock
 | 
			
		||||
       hypothesis
 | 
			
		||||
commands = py.test --cov escpos
 | 
			
		||||
 | 
			
		||||
[testenv:docs]
 | 
			
		||||
basepython = python
 | 
			
		||||
changedir = doc
 | 
			
		||||
deps = sphinx>=1.5.1
 | 
			
		||||
       setuptools_scm
 | 
			
		||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees .  {envtmpdir}/html
 | 
			
		||||
 | 
			
		||||
[testenv:flake8]
 | 
			
		||||
basepython = python
 | 
			
		||||
# TODO add flake8-future
 | 
			
		||||
# TODO add flake8-docstrings
 | 
			
		||||
deps = flake8
 | 
			
		||||
commands = flake8
 | 
			
		||||