Compare commits
	
		
			245 Commits
		
	
	
		
			v2.2.0
			...
			update-rea
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ea9a7b3fbb | ||
|   | 02a47d413c | ||
|   | 1f9e7bd45a | ||
|   | 062660e00f | ||
|   | 6a8cc54b97 | ||
|   | 6c6fe9bccf | ||
|   | c5e46a888d | ||
|   | 46942820a5 | ||
|   | e50e295acc | ||
|   | 2d7458fa49 | ||
|   | a6f635c0d5 | ||
|   | 2d0f045457 | ||
|   | 293b8632ff | ||
|   | 5ff73595b6 | ||
|   | 4ecab402b8 | ||
|   | c56e43da84 | ||
|   | 88af26f46e | ||
|   | 9dd966c2a3 | ||
|   | a7d959428f | ||
|   | 8bf0e08659 | ||
|   | 5ac5a24b50 | ||
|   | 63252515b5 | ||
|   | 29a546821b | ||
|   | 4ddd18279f | ||
|   | de761e96e3 | ||
|   | ed7bce6932 | ||
|   | edd567785c | ||
|   | f1054876da | ||
|   | 490e0657dd | ||
|   | b4c32b5a4a | ||
|   | 40b30225d3 | ||
|   | 19e3ec6895 | ||
|   | df539da854 | ||
|   | 4534038b39 | ||
|   | adf85f7784 | ||
|   | aaa8162967 | ||
|   | 0461adc212 | ||
|   | 91ff83e506 | ||
|   | 29ef88f591 | ||
|   | 7c01a30d6c | ||
|   | 035c425581 | ||
|   | d20646b2a9 | ||
|   | 206822ac69 | ||
|   | dc08792e72 | ||
|   | 2886075ce9 | ||
|   | 73fff6291d | ||
|   | d5b9d99093 | ||
|   | 18c51358aa | ||
|   | 52719c0b7d | ||
|   | 0051c876bf | ||
|   | 854759d312 | ||
|   | a0343c66af | ||
|   | 6c94f88c24 | ||
|   | 6fb23d6826 | ||
|   | f649814091 | ||
|   | 47b4d41b28 | ||
|   | 599f4f3ca5 | ||
|   | d085e5c467 | ||
|   | b418302311 | ||
|   | f6acb72bbe | ||
|   | 0c9856c1f6 | ||
|   | a748563395 | ||
|   | b84e280efb | ||
|   | 4390dc4a9c | ||
|   | 6e09fd1e97 | ||
|   | 100c6b5e89 | ||
|   | 26d72a69f0 | ||
|   | 01e28bbcf6 | ||
|   | 2a7e2a6a36 | ||
|   | 3c3dab95f5 | ||
|   | d1e7052fa1 | ||
|   | 10e1dfe1d1 | ||
|   | cd1bcb57b4 | ||
|   | d6d12f99d4 | ||
|   | 128221363f | ||
|   | 6b0b1371e5 | ||
|   | 44f01a212b | ||
|   | 456f5b7aa6 | ||
|   | d78a6f1699 | ||
|   | 5e784c060a | ||
|   | 1439b14686 | ||
|   | b648cfd67f | ||
|   | 50c627fbb0 | ||
|   | 99034d0575 | ||
|   | 19663ec574 | ||
|   | 281eea125f | ||
|   | 5bed0bfbb4 | ||
|   | f12470d3cd | ||
|   | fb0e4c28ba | ||
|   | af29fcca77 | ||
|   | f8b269d859 | ||
|   | c259263f26 | ||
|   | 27c843935f | ||
|   | f3da6a9725 | ||
|   | b64b534394 | ||
|   | 81426ab6dc | ||
|   | df1193ab35 | ||
|   | b494c9a4bd | ||
|   | f8a2174108 | ||
|   | 1f57b04974 | ||
|   | c7080165a7 | ||
|   | cf0cf127fe | ||
|   | 82c67aa646 | ||
|   | 9e47ff2505 | ||
|   | 9bc3b30a60 | ||
|   | 5bd6dcf471 | ||
|   | 89dfb6cf86 | ||
|   | 662aa30f4b | ||
|   | efec3e508c | ||
|   | c3e952befa | ||
|   | 83b426f5fd | ||
|   | b963c5668b | ||
|   | 4882c31531 | ||
|   | 7c17141fb2 | ||
|   | 3f9d44ff15 | ||
|   | a069009696 | ||
|   | 024b0df7d2 | ||
|   | 74ef9aed7f | ||
|   | c4dd4f2960 | ||
|   | d348712439 | ||
|   | 22cf6ad00b | ||
|   | 5bf2636753 | ||
|   | 1f427953a8 | ||
|   | a6e1d0df00 | ||
|   | c0b4d03692 | ||
|   | a16d6bde06 | ||
|   | 737cc3176e | ||
|   | 4b04a5c425 | ||
|   | df33945458 | ||
|   | c1a7d71fd7 | ||
|   | a7ee11a78c | ||
|   | 43e0a87a74 | ||
|   | abbe32f845 | ||
|   | 29cc8baab7 | ||
|   | 0f33d68f3a | ||
|   | a0ef820947 | ||
|   | 7b24df6581 | ||
|   | 5078c49b3a | ||
|   | 4b81a27407 | ||
|   | e8aefd8388 | ||
|   | 3ee787e8b1 | ||
|   | b76fb75702 | ||
|   | 2bcb1766ae | ||
|   | e92f00cdf3 | ||
|   | 1038844567 | ||
|   | 1579f05cb7 | ||
|   | f885de2f2a | ||
|   | fc5ad1673c | ||
|   | 337e8ee19e | ||
|   | 81028f9a35 | ||
|   | fb18bb34cc | ||
|   | 94a0f2b94b | ||
|   | ce94a1fc18 | ||
|   | ca880dd8ec | ||
|   | fc69754a21 | ||
|   | b4920aafe2 | ||
|   | 43e30707be | ||
|   | c48a0bee51 | ||
|   | 972c7a2238 | ||
|   | c2fc464c55 | ||
|   | 2ea8e69c66 | ||
|   | e4a21e94fc | ||
|   | e904500312 | ||
|   | efff2cbe43 | ||
|   | d3f76a5f6d | ||
|   | e595bc2150 | ||
|   | 7bf6a1791b | ||
|   | a15d02b50c | ||
|   | 938f9890ab | ||
|   | 1e5a41ba4e | ||
|   | f8b7238801 | ||
|   | e35f551a56 | ||
|   | 24731f433e | ||
|   | 6158ba344f | ||
|   | 78604573f3 | ||
|   | bd57c01794 | ||
|   | 211e7db22c | ||
|   | da0d49c787 | ||
|   | 46305faf30 | ||
|   | 94e1944d16 | ||
|   | 13785a5530 | ||
|   | 10c589ae8d | ||
|   | cd38cdf74e | ||
|   | f50910f76e | ||
|   | 398eb424a9 | ||
|   | 1b2f509758 | ||
|   | b795c02dd4 | ||
|   | 915adf8fd3 | ||
|   | a3ca2c2a16 | ||
|   | 47fd020abe | ||
|   | 4f2f1cf520 | ||
|   | a82fefb301 | ||
|   | 7b68d97f5f | ||
|   | 7a7ea23628 | ||
|   | 83f926758c | ||
|   | 9a65945fcd | ||
|   | d9a6960f07 | ||
|   | b5bf1125db | ||
|   | 2c8bc1180d | ||
|   | 4166ee2209 | ||
|   | 95a84d3673 | ||
|   | 57ed77e332 | ||
|   | 2b17e16737 | ||
|   | f467cacdd8 | ||
|   | 9662ca6efe | ||
|   | 587eee2ef0 | ||
|   | f5c706db34 | ||
|   | b29ef6df69 | ||
|   | a435b66006 | ||
|   | ddc93d7369 | ||
|   | c3e3ec5808 | ||
|   | b543ecea58 | ||
|   | c850a726cb | ||
|   | d246e945a2 | ||
|   | 1bd53697b9 | ||
|   | 73ef8c4c0a | ||
|   | 9aa1335fd2 | ||
|   | 2f89f3fe3a | ||
|   | 40be69347c | ||
|   | 58ea206c36 | ||
|   | 68c17f1181 | ||
|   | b37f4fc8cc | ||
|   | 630423d24a | ||
|   | b92eeed50b | ||
|   | 3681c5c7bf | ||
|   | 4496ea91bd | ||
|   | 3d8626d17e | ||
|   | ea7769f8b2 | ||
|   | 5fa89ff685 | ||
|   | a07f84a5bc | ||
|   | 216184f43f | ||
|   | c7864fd785 | ||
|   | f6ce7e45da | ||
|   | 3fd1a3de5d | ||
|   | 214aa0d363 | ||
|   | 8b5bc9cf8a | ||
|   | a8574ad9d7 | ||
|   | ed3077f00f | ||
|   | 87a6647053 | ||
|   | 046a08896c | ||
|   | f0bdbc4322 | ||
|   | 13937ab0da | ||
|   | 0cfedb5706 | ||
|   | b0af9e9652 | ||
|   | 3546e0c4bb | 
							
								
								
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| [run] | ||||
| branch = True | ||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -5,6 +5,7 @@ | ||||
| $~ | ||||
| .idea/ | ||||
| .directory | ||||
| .cache/ | ||||
|  | ||||
| # temporary data | ||||
| temp | ||||
| @@ -19,6 +20,12 @@ dist/ | ||||
| .coverage | ||||
| src/escpos/version.py | ||||
| .hypothesis | ||||
| .pytest_cache/ | ||||
|  | ||||
| # testing temporary directories | ||||
| test/test-cli-output/ | ||||
|  | ||||
| # vim swap files | ||||
| *.swp | ||||
| *.swn | ||||
| *.swo | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| [submodule "capabilities-data"] | ||||
| 	path = capabilities-data | ||||
| 	url = https://github.com/receipt-print-hq/escpos-printer-db.git | ||||
| 	branch = master | ||||
							
								
								
									
										15
									
								
								.mailmap
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| <dev@pkanzler.de>                                    <patrick.kanzler@fablab.fau.de> | ||||
| <manpaz@gmail.com>                                   <manpaz@bashlinux.com> | ||||
| Manuel F Martinez <manpaz@gmail.com>                 manpaz <manpaz@bashlinux.com> | ||||
| <emailofdavis@gmail.com>                             <davis.goglin@oregonicecream.com> | ||||
| Davis Goglin <emailofdavis@gmail.com>                davisgoglin <emailofdavis@gmail.com> | ||||
| Michael Billington <michael.billington@gmail.com>    Michael <michael.billington@gmail.com> | ||||
| Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.com> | ||||
| Renato Lorenzi <renato.lorenzi@senior.com.br>        Renato.Lorenzi <renato.lorenzi@senior.com.br> | ||||
| Ahmed Tahri <nyuubi.10@gmail.com>                    TAHRI Ahmed <nyuubi.10@gmail.com> | ||||
| Michael Elsdörfer <michael@elsdoerfer.com>           Michael Elsdörfer <michael@elsdoerfer.info> | ||||
| Juanmi Taboada <juanmi@juanmitaboada.com>            Juanmi Taboada <juanmi@juanmitaboada.com> | ||||
| csoft2k <csoft2k@hotmail.com> | ||||
| Sergio Pulgarin <sergio.pulgarin@gmail.com> | ||||
| reck31 <rakesh.gunduka@gmail.com> | ||||
| Alex Debiasio <alex.debiasio@thinkin.io>             <alex.debiasio@studenti.unitn.it> | ||||
							
								
								
									
										10
									
								
								.readthedocs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| version: 2 | ||||
| formats: | ||||
|   - pdf | ||||
|   - epub | ||||
| python: | ||||
|   version: 3.7 | ||||
|   install: | ||||
|      - requirements: doc/requirements.txt | ||||
|      - method: setuptools | ||||
|        path: . | ||||
							
								
								
									
										65
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						| @@ -1,36 +1,72 @@ | ||||
| language: python | ||||
| sudo: false | ||||
| cache: pip | ||||
| dist: xenial | ||||
| git: | ||||
|   depth: 100000 | ||||
| addons: | ||||
|   apt: | ||||
|     packages: | ||||
|       - graphviz | ||||
| env: | ||||
|   global: | ||||
|     - ESCPOS_CAPABILITIES_FILE=/home/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
| matrix: | ||||
|   fast_finish: true | ||||
|   include: | ||||
|     - name: "Python 3.7 on Windows" | ||||
|       os: windows | ||||
|       language: shell | ||||
|       before_install: | ||||
|          - choco install python | ||||
|          - pip install tox codecov 'sphinx>=1.5.1' | ||||
|       env: | ||||
|          - TOXENV=py37 | ||||
|          - PATH=/c/Python37:/c/Python37/Scripts:$PATH | ||||
|          - ESCPOS_CAPABILITIES_FILE=C:/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
|     - name: "Python 3.7 on macOS" | ||||
|       os: osx | ||||
|       osx_image: xcode10.2 | ||||
|       language: shell | ||||
|       env: TOXENV=py37 ESCPOS_CAPABILITIES_FILE=/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json | ||||
|     - python: 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.5-dev | ||||
|       env: TOXENV=py35 | ||||
|     - python: nightly | ||||
|     - python: 3.6 | ||||
|       env: TOXENV=py36 | ||||
|     - python: 3.6-dev | ||||
|       env: TOXENV=py36 | ||||
|     - python: 3.7 | ||||
|       env: TOXENV=py37 | ||||
|     - python: 3.7-dev | ||||
|       env: TOXENV=py37 | ||||
|     - python: 3.8-dev | ||||
|       env: TOXENV=py38 | ||||
|     - python: nightly | ||||
|       env: TOXENV=py38 | ||||
|     - python: pypy | ||||
|       env: TOXENV=pypy | ||||
|     - python: pypy3 | ||||
|       env: TOXENV=pypy3 | ||||
|     - python: 2.7 | ||||
|     - python: 3.7 | ||||
|       env: TOXENV=docs | ||||
|     - python: 3.7 | ||||
|       env: TOXENV=flake8 | ||||
|   allow_failures: | ||||
|     - python: 3.5-dev | ||||
|     - python: 2.7 | ||||
|     - python: 3.6-dev | ||||
|     - python: 3.7-dev | ||||
|     - python: 3.8-dev | ||||
|     - python: nightly | ||||
|     - python: pypy3 | ||||
|     - os: windows | ||||
|     - os: osx | ||||
| before_install: | ||||
|     - pip install tox codecov | ||||
|     - pip install tox codecov 'sphinx>=1.5.1' | ||||
|     - ./doc/generate_authors.sh --check | ||||
| script: | ||||
|     - tox | ||||
|     - codecov | ||||
| @@ -38,3 +74,16 @@ 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.7" | ||||
|   | ||||
							
								
								
									
										39
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| Ahmed Tahri | ||||
| akeonly | ||||
| Alex Debiasio | ||||
| Asuki Kono | ||||
| belono | ||||
| Christoph Heuel | ||||
| Cody (Quantified Code Bot) | ||||
| csoft2k | ||||
| Curtis // mashedkeyboard | ||||
| Davis Goglin | ||||
| Dean Rispin | ||||
| Dmytro Katyukha | ||||
| Gerard Marull-Paretas | ||||
| Hark | ||||
| Joel Lehtonen | ||||
| Justin Vieira | ||||
| kennedy | ||||
| Kristi | ||||
| ldos | ||||
| Lucy Linder | ||||
| Manuel F Martinez | ||||
| Michael Billington | ||||
| Michael Elsdörfer | ||||
| mrwunderbar666 | ||||
| Nathan Bookham | ||||
| Omer Akram | ||||
| Patrick Kanzler | ||||
| primax79 | ||||
| Qian Linfeng | ||||
| Ramon Poca | ||||
| reck31 | ||||
| Renato Lorenzi | ||||
| Romain Porte | ||||
| Sam Cheng | ||||
| Sergio Pulgarin | ||||
| Stephan Sokolow | ||||
| Thijs Triemstra | ||||
| Thomas van den Berg | ||||
| ysuolmai | ||||
							
								
								
									
										145
									
								
								CHANGELOG.rst
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,151 @@ | ||||
| ********* | ||||
| Changelog | ||||
| ********* | ||||
| 2019-06-16 - Version 3.0a5 - "Lightly Seared On The Reality Grill" | ||||
| ------------------------------------------------------------------ | ||||
| This release is the sixth alpha release of the new version 3.0. Please | ||||
| be aware that the API is subject to change until v3.0 is released. | ||||
|  | ||||
| changes | ||||
| ^^^^^^^ | ||||
| - allow arbitrary USB arguments in USB-class | ||||
| - add Win32Raw-Printer on Windows-platforms | ||||
| - add and improve Windows support of USB-class | ||||
| - use pyyaml safe_load() | ||||
| - improve doc | ||||
| - implement _read method of Network printer class | ||||
|  | ||||
| contributors | ||||
| ^^^^^^^^^^^^ | ||||
| - Patrick Kanzler | ||||
| - Gerard Marull-Paretas | ||||
| - Ramon Poca | ||||
| - akeonly | ||||
| - Omer Akram | ||||
| - Justin Vieira | ||||
|  | ||||
| 2018-05-15 - Version 3.0a4 - "Kakistocrat" | ||||
| ------------------------------------------ | ||||
| This release is the fifth alpha release of the new version 3.0. Please | ||||
| be aware that the API will still change until v3.0 is released. | ||||
|  | ||||
| changes | ||||
| ^^^^^^^ | ||||
| - raise exception when TypeError occurs in cashdraw (#268) | ||||
| - Feature/clear content in dummy printer (#271) | ||||
| - fix is_online() (#282) | ||||
| - improve documentation | ||||
| - Modified submodule to always pull from master branch (#283) | ||||
| - parameter for implementation of nonnative qrcode (#289) | ||||
| - improve platform independence (#296) | ||||
|  | ||||
| contributors | ||||
| ^^^^^^^^^^^^ | ||||
| - Christoph Heuel | ||||
| - Patrick Kanzler | ||||
| - kennedy | ||||
| - primax79 | ||||
| - reck31 | ||||
| - Thijs Triemstra | ||||
|  | ||||
| 2017-10-08 - Version 3.0a3 - "Just Testing" | ||||
| ------------------------------------------- | ||||
| This release is the fourth alpha release of the new version 3.0. Please | ||||
| be aware that the API will still change until v3.0 is released. | ||||
|  | ||||
| changes | ||||
| ^^^^^^^ | ||||
| - minor changes in documentation, tests and examples | ||||
| - pickle capabilities for faster startup | ||||
| - first implementation of centering images and QR | ||||
| - check barcodes based on regex | ||||
|  | ||||
| contributors | ||||
| ^^^^^^^^^^^^ | ||||
| - Patrick Kanzler | ||||
| - Lucy Linder | ||||
| - Romain Porte | ||||
| - Sergio Pulgarin | ||||
|  | ||||
| 2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To" | ||||
| ----------------------------------------------------------------------- | ||||
| This release is the third alpha release of the new version 3.0. Please | ||||
| be aware that the API will still change until v3.0 is released. | ||||
|  | ||||
| changes | ||||
| ^^^^^^^ | ||||
| - refactor of the set-method | ||||
| - preliminary support of POS "line display" printing | ||||
| - improvement of tests | ||||
| - added ImageWidthError | ||||
| - list authors in repository | ||||
| - add support for software-based barcode-rendering | ||||
| - fix SerialException when trying to close device on __del__ | ||||
| - added the DLE EOT querying command for USB and Serial | ||||
| - ensure QR codes have a large enough border | ||||
| - make feed for cut optional | ||||
| - fix the behavior of horizontal tabs | ||||
| - added test script for hard an soft barcodes | ||||
| - implemented paper sensor querying command | ||||
| - added weather forecast example script | ||||
| - added a method for simpler newlines | ||||
|  | ||||
| contributors | ||||
| ^^^^^^^^^^^^ | ||||
| - csoft2k | ||||
| - Patrick Kanzler | ||||
| - mrwunderbar666 | ||||
| - Romain Porte | ||||
| - Ahmed Tahri | ||||
|  | ||||
| 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" | ||||
| ------------------------------------------------------ | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,16 @@ The pull requests and issues will be prefilled with templates. Please fill in yo | ||||
| This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as | ||||
| well as possible. | ||||
|  | ||||
| Author-list | ||||
| ----------- | ||||
|  | ||||
| This project keeps a list of authors. This can be auto-generated by calling `./doc/generate-authors.sh`. | ||||
| When contributing the first time, please include a commit with the output of this script in place. | ||||
| Otherwise the integration-check will fail. | ||||
|  | ||||
| When you change your username or mail-address, please also update the `.mailmap` and the authors-list. | ||||
| You can find a good documentation on the mapping-feature in the `documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_. | ||||
|  | ||||
| Style-Guide | ||||
| ----------- | ||||
|  | ||||
| @@ -38,15 +48,16 @@ Often you can achieve compatibility quite easily with a tool from the `six`-pack | ||||
|  | ||||
| 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. | ||||
| The entire codebase adheres to the rules of PEP8. | ||||
| These rules are enforced by running `flake8` in the integration-checks. | ||||
| Please adhere to these rules as your contribution can only be merged if the check succeeds. | ||||
| You can use flake8 or similar tools locally in order to check your code. | ||||
| Apart from that the travis-log and the check by Landscape 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 a released version | ||||
| of the package. | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										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. | ||||
| @@ -1,8 +1,10 @@ | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										48
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						| @@ -6,10 +6,6 @@ python-escpos - Python library to manipulate ESC/POS Printers | ||||
|     :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 | ||||
| @@ -37,15 +33,21 @@ 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 | ||||
| * `pyusb <https://github.com/walac/pyusb>`_ for USB-printers | ||||
| * `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing | ||||
| * `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes | ||||
| * `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers | ||||
| * `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes | ||||
|  | ||||
| Documentation and Usage | ||||
| ----------------------- | ||||
| @@ -56,16 +58,38 @@ The basic usage is: | ||||
|  | ||||
|     from escpos.printer import Usb | ||||
|  | ||||
|     """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ | ||||
|     p = Usb(0x04b8,0x0202,0) | ||||
|     """ 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.barcode('1324354657687', 'EAN13', 64, 2, '', '') | ||||
|     p.cut() | ||||
|  | ||||
|  | ||||
| Another example based on the Network printer class: | ||||
|  | ||||
| .. code:: python | ||||
|  | ||||
|     from escpos.printer import Network | ||||
|      | ||||
|     kitchen = Network("192.168.1.100") #Printer IP Address | ||||
|     kitchen.text("Hello World\n") | ||||
|     kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '') | ||||
|     kitchen.cut() | ||||
|  | ||||
|  | ||||
| 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. | ||||
| This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information. | ||||
|  | ||||
|  | ||||
| Disclaimer | ||||
| ---------- | ||||
|  | ||||
| None of the vendors cited in this project agree or endorse any of the patterns or implementations. | ||||
| Its names are used only to maintain context. | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								capabilities-data
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						| @@ -7,10 +7,6 @@ coverage: | ||||
|       default:  # status context | ||||
|         target: auto | ||||
|         threshold: "1%" | ||||
|     patch: | ||||
|       default: | ||||
|         target: auto | ||||
|         threshold: "1%" | ||||
|   range: "60...100" | ||||
|  | ||||
| comment: off | ||||
|   | ||||
							
								
								
									
										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/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 | ||||
							
								
								
									
										19
									
								
								doc/generate_authors.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| GENLIST=$(git shortlog -s -n | cut -f2 | sort -f) | ||||
| AUTHORSFILE="$(dirname $0)/../AUTHORS" | ||||
| TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" | ||||
|  | ||||
| if [ "$#" -eq 1 ] | ||||
|     then | ||||
|         echo "$GENLIST">$TEMPAUTHORSFILE | ||||
| 	echo "\nAuthorsfile in version control:\n" | ||||
| 	cat $AUTHORSFILE | ||||
| 	echo "\nNew authorsfile:\n" | ||||
| 	cat $TEMPAUTHORSFILE | ||||
| 	echo "\nUsing diff on files...\n" | ||||
|         diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE | ||||
|     else | ||||
| 	echo "$GENLIST">$AUTHORSFILE | ||||
| fi | ||||
|  | ||||
| @@ -35,8 +35,13 @@ Content | ||||
|    api/printer | ||||
|    api/constants | ||||
|    api/exceptions | ||||
|    api/capabilities | ||||
|    api/config | ||||
|    api/image | ||||
|    api/cli | ||||
|    api/magicencode | ||||
|    api/codepages | ||||
|    api/katakana | ||||
|  | ||||
| Indices and tables | ||||
| ================== | ||||
|   | ||||
| @@ -4,3 +4,5 @@ qrcode>=4.0 | ||||
| pyserial | ||||
| sphinx-rtd-theme | ||||
| setuptools-scm | ||||
| docutils>=0.12 | ||||
| viivakoodi | ||||
|   | ||||
| @@ -1,144 +1,17 @@ | ||||
| ******* | ||||
| Methods | ||||
| ******* | ||||
|  | ||||
| .. note:: **TODO** Merge this page with the API-description. (Make the API-description more pretty and then | ||||
|           replace this with the API-description.) | ||||
| :Last Reviewed: 2017-01-25 | ||||
|  | ||||
| Escpos class | ||||
| ------------ | ||||
|  | ||||
| Escpos inherits its methods to the printers. the following methods are | ||||
| defined: | ||||
| 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: | ||||
|  | ||||
| image("image\_name.ext") | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| .. autoclass:: escpos.escpos.Escpos | ||||
|     :members: | ||||
|     :member-order: bysource | ||||
|     :noindex: | ||||
|  | ||||
| Prints an image. Its adjust the size in order to print it. | ||||
|  | ||||
| * ``image_name.ext`` is the complete file name and location of any image type (jpg, gif, png, bmp) | ||||
|  | ||||
| Raises ``ImageSizeError`` exception. | ||||
|  | ||||
| qr("text") | ||||
| ^^^^^^^^^^ | ||||
|  | ||||
| Prints a QR code. The size has been adjusted to Version 4, so it can be | ||||
| enough small to be printed but also enough big to be read by a smart | ||||
| phone. | ||||
|  | ||||
| * ``text`` Any text that needs to be QR encoded. It could be a slogan, | ||||
|   salutation, url, etc. | ||||
|  | ||||
| barcode("code", "barcode\_type", width, height, "position", "font") | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Prints a barcode. | ||||
|  | ||||
| * ``code`` is an alphanumeric code to be printed as bar code | ||||
| * ``barcode_type`` must be one of the following type of codes for function type A: | ||||
|     | ||||
|   * UPC-A | ||||
|   * UPC-E | ||||
|   * EAN13 | ||||
|   * EAN8 | ||||
|   * CODE39 | ||||
|   * ITF | ||||
|   * NW7 | ||||
|  | ||||
|   And for function type B: | ||||
|  | ||||
|   * Any type above | ||||
|   * CODE93 | ||||
|   * CODE128 | ||||
|   * GS1-128 | ||||
|   * GS1 DataBar Omnidirectional | ||||
|   * GS1 DataBar Truncated | ||||
|   * GS1 DataBar Limited | ||||
|   * GS1 DataBar Expanded | ||||
|  | ||||
|  | ||||
| * ``width`` is a numeric value in the range between (1,255) *Default:* 64 | ||||
| * ``height`` is a numeric value in the range between (2,6) *Default:* 3 | ||||
| * ``position`` is where to place the code around the bars, could be one of the following values: | ||||
|    | ||||
|   * ABOVE | ||||
|   * BELOW | ||||
|   * BOTH | ||||
|   * OFF > *Default:* BELOW | ||||
|     | ||||
| * ``font`` is one of the 2 type of fonts, values could be: | ||||
|    | ||||
|   * A | ||||
|   * B > *Default:* A | ||||
|  | ||||
| *  ``fuction_type`` chooses between ESCPOS function type A or B. A is default, B has more barcode options. Choose which one based upon your printer support and require barcode. | ||||
|  | ||||
|   * A | ||||
|   * B > *Default* A | ||||
|  | ||||
| * Raises ``BarcodeTypeError``, ``BarcodeSizeError``, ``BarcodeCodeError`` exceptions. | ||||
|      | ||||
| text("text") | ||||
| ^^^^^^^^^^^^ | ||||
|  | ||||
| Prints raw text. Raises ``TextError`` exception. | ||||
|  | ||||
| set("align", "font", "type", width, height, invert, smooth, flip) | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Set text properties. | ||||
|  | ||||
| * ``align`` set horizontal position for text, the possible values are: | ||||
|     | ||||
|   * CENTER | ||||
|   * LEFT | ||||
|   * RIGHT > > *Default:* left | ||||
|     | ||||
| * ``font`` type could be ``A`` or ``B``. *Default:* A | ||||
| * ``type`` type could be ``B`` (Bold), ``U`` (Underline) or ``normal``. *Default:* normal | ||||
| * ``width`` is a numeric value, 1 is for regular size, and 2 is twice the standard size. *Default*: 1 | ||||
| * ``height`` is a numeric value, 1 is for regular size and 2 is twice the standard size. *Default*: 1 | ||||
| * ``invert`` is a boolean value, True enables white on black printing. *Default*: False | ||||
| * ``smooth`` is a boolean value, True enables text smoothing. *Default*: False | ||||
| * ``flip`` is a boolean value, True enables upside-down text. *Default*: False | ||||
|  | ||||
| cut("mode") | ||||
| ^^^^^^^^^^^ | ||||
|  | ||||
| Cut paper. | ||||
|  | ||||
| * ``mode`` set a full or partial cut. *Default:* full | ||||
|  | ||||
| **Partial cut is not implemented in all printers.** | ||||
|  | ||||
| cashdraw(pin) | ||||
| ^^^^^^^^^^^^^ | ||||
|  | ||||
| Sends a pulse to the cash drawer in the specified pin. | ||||
|  | ||||
| * ``pin`` is a numeric value which defines the pin to be used to send the pulse, it could be 2 or 5. Raises ``CashDrawerError()`` | ||||
|  | ||||
| hw("operation") | ||||
| ^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Hardware operations. | ||||
|  | ||||
| * ``operation`` is any of the following options: | ||||
|      | ||||
|   * INIT | ||||
|   * SELECT | ||||
|   * RESET | ||||
|  | ||||
| control("align") | ||||
| ^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Carrier feed and tabs. | ||||
|  | ||||
| * ``align`` is a string which takes any of the following values: | ||||
|    | ||||
|   * LF *for Line Feed* | ||||
|   * FF *for Form Feed* | ||||
|   * CR *for Carriage Return* | ||||
|   * HT *for Horizontal Tab* | ||||
|   * VT *for Vertical Tab* | ||||
|   | ||||
| @@ -1,56 +1,77 @@ | ||||
| ******** | ||||
| Printers | ||||
| ******** | ||||
| :Last Reviewed: 2017-01-25 | ||||
|  | ||||
| .. note:: **TODO** Merge this page into the API-description. | ||||
| As of now there are 5 different type of printer implementations. | ||||
|  | ||||
| There 3 different type of printers: | ||||
| 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. | ||||
|  | ||||
| USB(idVendor, idProduct, interface, in\_ep, out\_ep) | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| .. autoclass:: escpos.printer.Usb | ||||
|     :members: | ||||
|     :special-members: | ||||
|     :member-order: bysource | ||||
|     :noindex: | ||||
|  | ||||
| Based on pyusb and libusb-1.0 | ||||
| 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. | ||||
|  | ||||
| * ``idVendor`` is the Vendor ID | ||||
| * ``idProduct`` is the Product ID | ||||
| * ``interface`` is the USB device interface (default = 0) | ||||
| *  ``in_ep`` is the input end point (default = 0x82) | ||||
| * ``out_ep`` is the output end point (default = 0x01) | ||||
| .. autoclass:: escpos.printer.Serial | ||||
|      :members: | ||||
|      :special-members: | ||||
|      :member-order: bysource | ||||
|      :noindex: | ||||
|  | ||||
| Serial("devfile", baudrate, bytesize, timeout) | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| Network | ||||
| ------- | ||||
|  | ||||
| Based on pyserial, default values are based on the defaults set by | ||||
| DIP\_SWITCH\_1 on the documentation(hardware side). | ||||
| This driver is based on the socket class. | ||||
|  | ||||
| * ``devfile`` is an alphanumeric device file name under /dev filesystem (default = /ev/ttyS0) | ||||
| * ``baudrate`` is the Baud rate for serial transmission (default = 9600) | ||||
| * ``bytesize`` sets the serial buffer size (default = 8) | ||||
| * ``timeout`` defines Read/Write timeout (default = 1) | ||||
| .. autoclass:: escpos.printer.Network | ||||
|       :members: | ||||
|       :special-members: | ||||
|       :member-order: bysource | ||||
|       :noindex: | ||||
|  | ||||
| Network("host", port) | ||||
| ^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Based on socket | ||||
|  | ||||
| * ``host`` is an alphanumeric host name, could be either DNS host name or IP address. | ||||
| * ``port`` to write to (default = 9100) | ||||
|  | ||||
| Troubleshooting: | ||||
| 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("file\_name") | ||||
| ^^^^^^^^^^^^^^^^^^ | ||||
| File | ||||
| ---- | ||||
| This printer "prints" just into a file-handle. Especially on \*nix-systems this comes very handy. | ||||
|  | ||||
| Printcap printers | ||||
| .. 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: | ||||
|  | ||||
| * ``file_name`` is the full path to the device file name | ||||
|   | ||||
| @@ -2,10 +2,9 @@ | ||||
| Raspberry Pi | ||||
| ************ | ||||
|  | ||||
| This instructions were tested on Raspbian. | ||||
| :Last Reviewed: 2017-01-05 | ||||
|  | ||||
| Unless you have done any distro with libusb-1.0 on the Raspberry Pi, the | ||||
| following instructions should works fine on your raspberry distro. | ||||
| 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 | ||||
| @@ -14,60 +13,30 @@ following instructions should works fine on your raspberry distro. | ||||
|  | ||||
| Dependencies | ||||
| ------------ | ||||
|  | ||||
| First, install the packages available on Raspbian. | ||||
|  | ||||
| :: | ||||
|  | ||||
|     # apt-get install python-imaging python-serial python-setuptools | ||||
|  | ||||
| PyUSB | ||||
| ^^^^^ | ||||
|  | ||||
| PyUSB 1.0 is not available on Ubuntu, so you have to download and | ||||
| install it manually | ||||
|  | ||||
| 1. Download the latest tarball from | ||||
|    `Sourceforge <http://sourceforge.net/projects/pyusb/files/>`__ | ||||
| 2. Decompress the zip file | ||||
| 3. Install the library | ||||
|  | ||||
|    :: | ||||
|  | ||||
|        # wget ... | ||||
|        # unzip pyusb*.zip | ||||
|        # cd pyusb* | ||||
|        # python setup.py build | ||||
|        # sudo python setup.py install | ||||
|  | ||||
| python-qrcode | ||||
| ^^^^^^^^^^^^^ | ||||
|  | ||||
| 1. Checkout the code from github | ||||
| 2. Install the library | ||||
|  | ||||
|    :: | ||||
|  | ||||
|        # git clone https://github.com/lincolnloop/python-qrcode | ||||
|        # cd python-qrcode | ||||
|        # python setup.py build | ||||
|        # sudo python setup.py install | ||||
|        sudo apt-get install python3 python3-setuptools python3-pip libjpeg8-dev | ||||
|  | ||||
| Installation | ||||
| ------------ | ||||
|  | ||||
| If you have installed pyusb for libusb-1.0 then you need to: | ||||
|  | ||||
| 1. Download the latest file | ||||
| 2. Decompress the file | ||||
| 3. Install the library | ||||
| You can install by using pip3. | ||||
|  | ||||
| :: | ||||
|  | ||||
|     # git clone https://github.com/manpaz/python-escpos.git | ||||
|     # cd python-escpos | ||||
|     # python setup.py build | ||||
|     # sudo python setup.py install | ||||
|     sudo pip3 install --upgrade pip | ||||
|     sudo pip3 install python-escpos | ||||
|  | ||||
| Now you can attach your printer and and test it with the example code in | ||||
| the project's `home <https://github.com/manpaz/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>`. | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| ***** | ||||
| Usage | ||||
| ***** | ||||
| :Last Reviewed: 2017-06-10 | ||||
|  | ||||
| Define your printer | ||||
| ------------------- | ||||
| @@ -8,12 +9,11 @@ Define your printer | ||||
| USB printer | ||||
| ^^^^^^^^^^^ | ||||
|  | ||||
| Before start creating your Python ESC/POS printer instance, you must see | ||||
| at your system for the printer parameters. This is done with the 'lsusb' | ||||
| command. | ||||
| Before creating your Python ESC/POS printer instance, consult the system to obtain | ||||
| the printer parameters. This is done with the 'lsusb' command. | ||||
|  | ||||
| First run the command to look for the "Vendor ID" and "Product ID", then | ||||
| write down the values, these values are displayed just before the name | ||||
| 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: | ||||
|  | ||||
| :: | ||||
| @@ -37,25 +37,25 @@ so you can get the "Interface" number and "End Point" | ||||
|     # lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT | ||||
|           bEndpointAddress     0x01  EP 1 OUT | ||||
|  | ||||
| The first command will yields the "Interface" number that must be handy | ||||
| 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) | ||||
|     p = 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 with | ||||
| 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 looks like: | ||||
| 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) | ||||
|     p = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02) | ||||
|  | ||||
| Network printer | ||||
| ^^^^^^^^^^^^^^^ | ||||
| @@ -67,33 +67,36 @@ IP by DHCP or you set it manually. | ||||
|  | ||||
| :: | ||||
|  | ||||
|     Epson = printer.Network("192.168.1.99") | ||||
|     p = printer.Network("192.168.1.99") | ||||
|  | ||||
| Serial printer | ||||
| ^^^^^^^^^^^^^^ | ||||
|  | ||||
| Must of the default values set by the DIP switches for the serial | ||||
| 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 hooked | ||||
| up. | ||||
| only thing you need to know is which serial port the printer is connected | ||||
| to. | ||||
|  | ||||
| **Serial printer initialization** | ||||
|  | ||||
| :: | ||||
|  | ||||
|     Epson = printer.Serial("/dev/tty0") | ||||
|     p = printer.Serial("/dev/tty0") | ||||
|      | ||||
|     # on a Windows OS serial devices are typically accessible as COM | ||||
|     p = printer.Serial("COM1") | ||||
|  | ||||
| Other printers | ||||
| ^^^^^^^^^^^^^^ | ||||
|  | ||||
| Some printers under /dev can't be used or initialized with any of the | ||||
| 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 the initialize | ||||
| however, if you know the device name, you could try to initialize by | ||||
| passing the device node name. | ||||
|  | ||||
| :: | ||||
|  | ||||
|     Epson = printer.File("/dev/usb/lp1") | ||||
|     p = 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. | ||||
| @@ -101,33 +104,33 @@ node, then you don't necessary need to pass the node name. | ||||
| Define your instance | ||||
| -------------------- | ||||
|  | ||||
| The following example demonstrate how to initialize the Epson TM-TI88IV | ||||
| on USB interface | ||||
| 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) | ||||
|     p = printer.Usb(0x04b8,0x0202) | ||||
|     # Print text | ||||
|     Epson.text("Hello World\n") | ||||
|     p.text("Hello World\n") | ||||
|     # Print image | ||||
|     Epson.image("logo.gif") | ||||
|     p.image("logo.gif") | ||||
|     # Print QR Code | ||||
|     Epson.qr("You can readme from your smartphone") | ||||
|     p.qr("You can readme from your smartphone") | ||||
|     # Print barcode | ||||
|     Epson.barcode('1324354657687','EAN13',64,2,'','') | ||||
|     p.barcode('1324354657687','EAN13',64,2,'','') | ||||
|     # Cut paper | ||||
|     Epson.cut() | ||||
|     p.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 | ||||
| programically. | ||||
| programmatically. | ||||
|  | ||||
| The default configuration file is named ``config.yaml``. It's in the YAML | ||||
| The default configuration file is named ``config.yaml`` and uses the YAML | ||||
| format. For windows it is probably at:: | ||||
|  | ||||
|     %appdata%/python-escpos/config.yaml | ||||
| @@ -143,11 +146,10 @@ If you aren't sure, run:: | ||||
|     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 | ||||
| search to the ``load()`` method. | ||||
| you where it's looking. You can always pass a path, or a list of paths, to | ||||
| the ``load()`` method. | ||||
|  | ||||
|  | ||||
| To load the configured pritner, run:: | ||||
| To load the configured printer, run:: | ||||
|  | ||||
|     from escpos import config | ||||
|     c = config.Config() | ||||
| @@ -159,10 +161,12 @@ 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 | ||||
| The only required paramter is ``type``. The value of this has to be one of the | ||||
| printers defined in :doc:`/user/printers`. | ||||
|  | ||||
| The rest of the parameters are whatever you want to pass to the printer. | ||||
| The rest of the given parameters will be passed on to the initialization of the printer class. | ||||
| Use these to overwrite the default values as specified in :doc:`/user/printers`. | ||||
| This implies that the parameters have to match the parameter-names of the respective printer class. | ||||
|  | ||||
| An example file printer:: | ||||
|  | ||||
| @@ -173,10 +177,33 @@ An example file printer:: | ||||
| And for a network printer:: | ||||
|  | ||||
|     printer: | ||||
|                 type: network | ||||
|             type: Network | ||||
|             host: 127.0.0.1 | ||||
|             port: 9000 | ||||
|  | ||||
| An USB-printer could be defined by:: | ||||
|  | ||||
|     printer: | ||||
|             type: Usb | ||||
|             idVendor: 0x1234 | ||||
|             idProduct: 0x5678 | ||||
|             in_ep: 0x66 | ||||
|             out_ep: 0x01 | ||||
|  | ||||
| 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 | ||||
| -------------------------------------- | ||||
|  | ||||
| @@ -204,6 +231,32 @@ 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 | ||||
| ------------------------- | ||||
|  | ||||
| @@ -230,24 +283,8 @@ This is probably best explained by an example: | ||||
|    # send code to printer | ||||
|    p._raw(d.output) | ||||
|  | ||||
| This way you could also store the code in a file and print later. | ||||
| 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.) | ||||
|  | ||||
|  | ||||
| How to update your code for USB printers | ||||
| ---------------------------------------- | ||||
|  | ||||
| Old code | ||||
|  | ||||
| :: | ||||
|  | ||||
|     Epson = escpos.Escpos(0x04b8,0x0202,0) | ||||
|  | ||||
| New code | ||||
|  | ||||
| :: | ||||
|  | ||||
|     Epson = printer.Usb(0x04b8,0x0202) | ||||
|  | ||||
| Nothe that "0" which is the interface number is no longer needed. | ||||
|   | ||||
							
								
								
									
										11
									
								
								examples/barcodes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| from escpos.printer import Usb | ||||
|  | ||||
|  | ||||
| # Adapt to your needs | ||||
| p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|  | ||||
| # Print software and then hardware barcode with the same content | ||||
| p.soft_barcode('code39', '123456') | ||||
| p.text('\n') | ||||
| p.text('\n') | ||||
| p.barcode('123456', 'CODE39') | ||||
							
								
								
									
										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(font='b') | ||||
|     printer._raw("  {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) | ||||
|     printer.set() | ||||
|  | ||||
|     # The table | ||||
|     for x in range(0, 16): | ||||
|         # First column | ||||
|         printer.set(font='b') | ||||
|         printer._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() | ||||
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/clear-day.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/clear-night.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/cloudy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/fog.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/partly-cloudy-day.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/partly-cloudy-night.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/rain.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.7 KiB | 
							
								
								
									
										10
									
								
								examples/graphics/climacons/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| # Climacons by Adam Whitcroft | ||||
|  | ||||
| 75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft). | ||||
|  | ||||
| Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information. | ||||
|  | ||||
| Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft) | ||||
|  | ||||
| ## License | ||||
| You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons. | ||||
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/sleet.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/snow.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/graphics/climacons/wind.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										19
									
								
								examples/qr_code.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| import sys | ||||
|  | ||||
| from escpos.printer import Usb | ||||
|  | ||||
|  | ||||
| def usage(): | ||||
|     print("usage: qr_code.py <content>") | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     if len(sys.argv) != 2: | ||||
|         usage() | ||||
|         sys.exit(1) | ||||
|  | ||||
|     content = sys.argv[1] | ||||
|  | ||||
|     # Adapt to your needs | ||||
|     p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|     p.qr(content, center=True) | ||||
							
								
								
									
										9
									
								
								examples/software_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| from escpos.printer import Usb | ||||
|  | ||||
|  | ||||
| # Adapt to your needs | ||||
| p = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|  | ||||
| # Some software barcodes | ||||
| p.soft_barcode('code128', 'Hello') | ||||
| p.soft_barcode('code39', '123456') | ||||
							
								
								
									
										127
									
								
								examples/weather.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,127 @@ | ||||
| #!/usr/bin/python | ||||
|  | ||||
|  | ||||
| # Adapted script from Adafruit | ||||
| # Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer. | ||||
| # Retrieves data from DarkSky.net's API, prints current conditions and | ||||
| # forecasts for next two days. | ||||
| # Weather example using nice bitmaps. | ||||
| # Written by Adafruit Industries.  MIT license. | ||||
| # Adapted and enhanced for escpos library by MrWunderbar666 | ||||
|  | ||||
| # Icons taken from http://adamwhitcroft.com/climacons/ | ||||
| # Check out his github: https://github.com/AdamWhitcroft/climacons | ||||
|  | ||||
|  | ||||
| from __future__ import print_function | ||||
| from datetime import datetime | ||||
| import calendar | ||||
| import urllib | ||||
| import json | ||||
| import time | ||||
| import os | ||||
|  | ||||
| from escpos.printer import Usb | ||||
|  | ||||
| """ Setting up the main pathing """ | ||||
| this_dir, this_filename = os.path.split(__file__) | ||||
| GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/") | ||||
|  | ||||
| # Adapt to your needs | ||||
| printer = Usb(0x0416, 0x5011, profile="POS-5890") | ||||
|  | ||||
| # You can get your API Key on www.darksky.net and register a dev account. | ||||
| # Technically you can use any other weather service, of course :) | ||||
| API_KEY = "YOUR API KEY" | ||||
|  | ||||
| LAT = "22.345490"       # Your Location | ||||
| LONG = "114.189945"     # Your Location | ||||
|  | ||||
|  | ||||
| def forecast_icon(idx): | ||||
|     icon = data['daily']['data'][idx]['icon'] | ||||
|     image = GRAPHICS_PATH + icon + ".png" | ||||
|     return image | ||||
|  | ||||
|  | ||||
| # Dumps one forecast line to the printer | ||||
| def forecast(idx): | ||||
|     date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time'])) | ||||
|     day = calendar.day_name[date.weekday()] | ||||
|     lo = data['daily']['data'][idx]['temperatureMin'] | ||||
|     hi = data['daily']['data'][idx]['temperatureMax'] | ||||
|     cond = data['daily']['data'][idx]['summary'] | ||||
|     print(date) | ||||
|     print(day) | ||||
|     print(lo) | ||||
|     print(hi) | ||||
|     print(cond) | ||||
|     time.sleep(1) | ||||
|     printer.set( | ||||
|         font='a', | ||||
|         height=2, | ||||
|         align='left', | ||||
|         bold=False, | ||||
|         double_height=False) | ||||
|     printer.text(day + ' \n ') | ||||
|     time.sleep(5)           # Sleep to prevent printer buffer overflow | ||||
|     printer.text('\n') | ||||
|     printer.image(forecast_icon(idx)) | ||||
|     printer.text('low ' + str(lo)) | ||||
|     printer.text(deg) | ||||
|     printer.text('\n') | ||||
|     printer.text(' high ' + str(hi)) | ||||
|     printer.text(deg) | ||||
|     printer.text('\n') | ||||
|     # take care of pesky unicode dash | ||||
|     printer.text(cond.replace(u'\u2013', '-').encode('utf-8')) | ||||
|     printer.text('\n \n') | ||||
|  | ||||
|  | ||||
| def icon(): | ||||
|     icon = data['currently']['icon'] | ||||
|     image = GRAPHICS_PATH + icon + ".png" | ||||
|     return image | ||||
|  | ||||
|  | ||||
| deg = ' C'      # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol | ||||
|  | ||||
| # if you want Fahrenheit change units= to 'us' | ||||
| url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \ | ||||
|     "?exclude=[alerts,minutely,hourly,flags]&units=si"  # change last bit to 'us' for Fahrenheit | ||||
| response = urllib.urlopen(url) | ||||
| data = json.loads(response.read()) | ||||
|  | ||||
| printer.print_and_feed(n=1) | ||||
| printer.control("LF") | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=True) | ||||
| printer.text("Weather Forecast") | ||||
| printer.text("\n") | ||||
| printer.set(align='center') | ||||
|  | ||||
|  | ||||
| # Print current conditions | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=False) | ||||
| printer.text('Current conditions: \n') | ||||
| printer.image(icon()) | ||||
| printer.text("\n") | ||||
|  | ||||
| printer.set(font='a', height=2, align='left', bold=False, double_height=False) | ||||
| temp = data['currently']['temperature'] | ||||
| cond = data['currently']['summary'] | ||||
| printer.text(temp) | ||||
| printer.text(' ') | ||||
| printer.text(deg) | ||||
| printer.text(' ') | ||||
| printer.text('\n') | ||||
| printer.text('Sky: ' + cond) | ||||
| printer.text('\n') | ||||
| printer.text('\n') | ||||
|  | ||||
| # Print forecast | ||||
| printer.set(font='a', height=2, align='center', bold=True, double_height=False) | ||||
| printer.text('Forecast: \n') | ||||
| forecast(0) | ||||
| forecast(1) | ||||
| printer.cut() | ||||
| printer.control("LF") | ||||
| @@ -1,7 +0,0 @@ | ||||
| formats: | ||||
|   - pdf | ||||
|   - epub | ||||
| requirements_file: doc/requirements.txt | ||||
| python: | ||||
|   version: 2 | ||||
|   setup_py_install: true | ||||
| @@ -5,3 +5,8 @@ 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 | ||||
|   | ||||
							
								
								
									
										65
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						| @@ -3,7 +3,6 @@ | ||||
| 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__) | ||||
| @@ -19,41 +18,16 @@ def read(fname): | ||||
|     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}' | ||||
|  | ||||
| """ | ||||
|  | ||||
|  | ||||
| @@ -66,13 +40,12 @@ setup( | ||||
|     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', | ||||
|     bugtrack_url='https://github.com/python-escpos/python-escpos/issues', | ||||
|     license='GNU GPL v3', | ||||
|     license='MIT', | ||||
|     long_description=read('README.rst'), | ||||
|     author='Manuel F Martinez and others', | ||||
|     author_email='manpaz@bashlinux.com', | ||||
|     maintainer='Patrick Kanzler', | ||||
|     maintainer_email='patrick.kanzler@fablab.fau.de', | ||||
|     maintainer_email='dev@pkanzler.de', | ||||
|     keywords=[ | ||||
|         'ESC/POS', | ||||
|         'thermoprinter', | ||||
| @@ -83,20 +56,20 @@ setup( | ||||
|     platforms='any', | ||||
|     package_dir={"": "src"}, | ||||
|     packages=find_packages(where="src", exclude=["tests", "tests.*"]), | ||||
|     package_data={'': ['COPYING']}, | ||||
|     package_data={'': ['COPYING', 'src/escpos/capabilities.json']}, | ||||
|     include_package_data=True, | ||||
|     classifiers=[ | ||||
|         'Development Status :: 4 - Beta', | ||||
|         'Environment :: Console', | ||||
|         'Intended Audience :: Developers', | ||||
|         'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', | ||||
|         'License :: OSI Approved :: MIT License', | ||||
|         'Operating System :: OS Independent', | ||||
|         'Programming Language :: Python', | ||||
|         '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 :: 3.6', | ||||
|         'Programming Language :: Python :: 3.7', | ||||
|         'Programming Language :: Python :: Implementation :: CPython', | ||||
|         'Programming Language :: Python :: Implementation :: PyPy', | ||||
|         'Topic :: Software Development :: Libraries :: Python Modules', | ||||
| @@ -109,15 +82,27 @@ setup( | ||||
|         'pyserial', | ||||
|         'six', | ||||
|         'appdirs', | ||||
|         'pyyaml', | ||||
|         'PyYAML', | ||||
|         'argparse', | ||||
|         'argcomplete', | ||||
|         'future', | ||||
|         'viivakoodi>=0.8' | ||||
|     ], | ||||
|     setup_requires=[ | ||||
|         'setuptools_scm', | ||||
|     ], | ||||
|     tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'], | ||||
|     cmdclass={'test': Tox}, | ||||
|     tests_require=[ | ||||
|         'jaconv', | ||||
|         'tox', | ||||
|         'pytest!=3.2.0,!=3.3.0', | ||||
|         'pytest-cov', | ||||
|         'pytest-mock', | ||||
|         'nose', | ||||
|         'scripttest', | ||||
|         'mock', | ||||
|         'hypothesis!=3.56.9,<4', | ||||
|         'flake8' | ||||
|     ], | ||||
|     entry_points={ | ||||
|         'console_scripts': [ | ||||
|             'python-escpos = escpos.cli:main' | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/escpos/capabilities.json
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| ../../capabilities-data/dist/capabilities.json | ||||
							
								
								
									
										151
									
								
								src/escpos/capabilities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,151 @@ | ||||
| import re | ||||
| from os import environ, path | ||||
| import pickle | ||||
| import logging | ||||
| import time | ||||
|  | ||||
| import six | ||||
| import yaml | ||||
|  | ||||
| from tempfile import gettempdir | ||||
| import platform | ||||
|  | ||||
| logging.basicConfig() | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir()) | ||||
| pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version())) | ||||
| capabilities_path = environ.get( | ||||
|     'ESCPOS_CAPABILITIES_FILE', | ||||
|     path.join(path.dirname(__file__), 'capabilities.json')) | ||||
|  | ||||
| # Load external printer database | ||||
| t0 = time.time() | ||||
| logger.debug('Using capabilities from file: %s', capabilities_path) | ||||
| if path.exists(pickle_path): | ||||
|     if path.getmtime(capabilities_path) > path.getmtime(pickle_path): | ||||
|         logger.debug('Found a more recent capabilities file') | ||||
|         full_load = True | ||||
|     else: | ||||
|         full_load = False | ||||
|         logger.debug('Loading capabilities from pickle in %s', pickle_path) | ||||
|         with open(pickle_path, 'rb') as cf: | ||||
|             CAPABILITIES = pickle.load(cf) | ||||
| else: | ||||
|     logger.debug('Capabilities pickle file not found: %s', pickle_path) | ||||
|     full_load = True | ||||
|  | ||||
| if full_load: | ||||
|     logger.debug('Loading and pickling capabilities') | ||||
|     with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp: | ||||
|         CAPABILITIES = yaml.safe_load(cp) | ||||
|         pickle.dump(CAPABILITIES, pp, protocol=2) | ||||
|  | ||||
| logger.debug('Finished loading capabilities took %.2fs', time.time() - t0) | ||||
|  | ||||
|  | ||||
| PROFILES = CAPABILITIES['profiles'] | ||||
|  | ||||
|  | ||||
| class NotSupported(Exception): | ||||
|     """Raised if a requested feature is not supported 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) | ||||
| @@ -33,9 +33,11 @@ def str_to_bool(string): | ||||
|     """ | ||||
|     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. | ||||
| @@ -98,6 +100,12 @@ ESCPOS_COMMANDS = [ | ||||
|                 '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, | ||||
|             } | ||||
|         ], | ||||
|     }, | ||||
| @@ -568,5 +576,6 @@ def demo(printer, **kwargs): | ||||
|             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']) | ||||
| @@ -7,8 +7,8 @@ moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/ | ||||
|  | ||||
| :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||
| :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2012 Bashlinux | ||||
| :license: GNU GPL v3 | ||||
| :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -47,6 +47,7 @@ HW_RESET  = ESC + b'\x3f\x0a\x00'   # Reset printer hardware | ||||
|  | ||||
| # 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 [] | ||||
|  | ||||
| @@ -63,6 +64,11 @@ _PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) | ||||
| PANEL_BUTTON_ON = _PANEL_BUTTON(0)  # enable all panel buttons | ||||
| PANEL_BUTTON_OFF = _PANEL_BUTTON(1)  # disable all panel buttons | ||||
|  | ||||
| # Line display printing | ||||
| LINE_DISPLAY_OPEN = ESC + b'\x3d\x02' | ||||
| LINE_DISPLAY_CLEAR = ESC + b'\x40' | ||||
| LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01' | ||||
|  | ||||
| # Sheet modes | ||||
| SHEET_SLIP_MODE = ESC + b'\x63\x30\x04'  # slip paper | ||||
| SHEET_ROLL_MODE = ESC + b'\x63\x30\x01'  # paper roll | ||||
| @@ -70,47 +76,89 @@ 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, | ||||
|  | ||||
| TXT_NORMAL     = ESC + b'!\x00'     # Normal text | ||||
|  | ||||
|  | ||||
| TXT_STYLE = { | ||||
|     'bold': { | ||||
|         False: ESC + b'\x45\x00',               # Bold font OFF | ||||
|         True: ESC + b'\x45\x01'                 # Bold font ON | ||||
|     }, | ||||
|     'underline': { | ||||
|         0: ESC + b'\x2d\x00',                   # Underline font OFF | ||||
|         1: ESC + b'\x2d\x01',                   # Underline font 1-dot ON | ||||
|         2: ESC + b'\x2d\x02'                    # Underline font 2-dot ON | ||||
|     }, | ||||
|     'size': { | ||||
|         'normal': TXT_NORMAL + ESC + b'!\x00',  # Normal text | ||||
|         '2h': TXT_NORMAL + ESC + b'!\x10',      # Double height text | ||||
|         '2w': TXT_NORMAL + ESC + b'!\x20',      # Double width text | ||||
|         '2x': TXT_NORMAL + ESC + b'!\x30'       # Quad area text | ||||
|     }, | ||||
|     'font': { | ||||
|         'a': ESC + b'\x4d\x00',                 # Font type A | ||||
|         'b': ESC + b'\x4d\x00'                  # Font type B | ||||
|     }, | ||||
|     'align': { | ||||
|         'left': ESC + b'\x61\x00',              # Left justification | ||||
|         'center': ESC + b'\x61\x01',            # Centering | ||||
|         'right': ESC + b'\x61\x02'              # Right justification | ||||
|     }, | ||||
|     'invert': { | ||||
|         True: GS  + b'\x42\x01',                # Inverse Printing ON | ||||
|         False: GS  + b'\x42\x00'                # Inverse Printing OFF | ||||
|     }, | ||||
|     'color': { | ||||
|         'black': ESC + b'\x72\x00',             # Default Color | ||||
|         'red': ESC + b'\x72\x01'                # Alternative Color, Usually Red | ||||
|     }, | ||||
|     'flip': { | ||||
|         True: ESC + b'\x7b\x01',                # Flip ON | ||||
|         False: ESC + b'\x7b\x00'                # Flip OFF | ||||
|     }, | ||||
|     'density': { | ||||
|         0: GS + b'\x7c\x00',                    # Printing Density -50% | ||||
|         1: GS + b'\x7c\x01',                    # Printing Density -37.5% | ||||
|         2: GS + b'\x7c\x02',                    # Printing Density -25% | ||||
|         3: GS + b'\x7c\x03',                    # Printing Density -12.5% | ||||
|         4: GS + b'\x7c\x04',                    # Printing Density  0% | ||||
|         5: GS + b'\x7c\x08',                    # Printing Density +50% | ||||
|         6: GS + b'\x7c\x07',                    # Printing Density +37.5% | ||||
|         7: GS + b'\x7c\x06',                    # Printing Density +25% | ||||
|         8: GS + b'\x7c\x05'                     # Printing Density +12.5% | ||||
|     }, | ||||
|     'smooth': { | ||||
|         True: GS + b'\x62\x01',                 # Smooth ON | ||||
|         False: GS + b'\x62\x00'                 # Smooth OFF | ||||
|     }, | ||||
|     'height': {                                 # Custom text 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_FONT_A     = ESC + b'\x4d\x00'  # Font type A | ||||
| TXT_FONT_B     = ESC + b'\x4d\x01'  # Font type B | ||||
| 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 | ||||
|         8: 0x07 | ||||
|     }, | ||||
|     'width': {                                  # Custom text width | ||||
|         1: 0x00, | ||||
|         2: 0x10, | ||||
|         3: 0x20, | ||||
|         4: 0x30, | ||||
|         5: 0x40, | ||||
|         6: 0x50, | ||||
|         7: 0x60, | ||||
|         8: 0x70 | ||||
|     } | ||||
| } | ||||
|  | ||||
| # Text colors | ||||
| TXT_COLOR_BLACK = ESC + b'\x72\x00'  # Default Color | ||||
| TXT_COLOR_RED = ESC + b'\x72\x01'    # Alternative Color (Usually Red) | ||||
| # 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 | ||||
|  | ||||
| # Spacing | ||||
| LINESPACING_RESET = ESC + b'2' | ||||
| @@ -120,27 +168,9 @@ LINESPACING_FUNCS = { | ||||
|   180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255 | ||||
| } | ||||
|  | ||||
| # Char code table | ||||
| CHARCODE_PC437  = ESC + b'\x74\x00'  # USA: Standard Europe | ||||
| CHARCODE_JIS    = ESC + b'\x74\x01'  # Japanese Katakana | ||||
| CHARCODE_PC850  = ESC + b'\x74\x02'  # Multilingual | ||||
| CHARCODE_PC860  = ESC + b'\x74\x03'  # Portuguese | ||||
| CHARCODE_PC863  = ESC + b'\x74\x04'  # Canadian-French | ||||
| CHARCODE_PC865  = ESC + b'\x74\x05'  # Nordic | ||||
| CHARCODE_WEU    = ESC + b'\x74\x06'  # Simplified Kanji, Hirakana | ||||
| CHARCODE_GREEK  = ESC + b'\x74\x07'  # Simplified Kanji | ||||
| CHARCODE_HEBREW = ESC + b'\x74\x08'  # Simplified Kanji | ||||
| CHARCODE_PC1252 = ESC + b'\x74\x11'  # Western European Windows Code Set | ||||
| CHARCODE_PC866  = ESC + b'\x74\x12'  # Cirillic #2 | ||||
| CHARCODE_PC852  = ESC + b'\x74\x13'  # Latin 2 | ||||
| CHARCODE_PC858  = ESC + b'\x74\x14'  # Euro | ||||
| CHARCODE_THAI42 = ESC + b'\x74\x15'  # Thai character code 42 | ||||
| CHARCODE_THAI11 = ESC + b'\x74\x16'  # Thai character code 11 | ||||
| CHARCODE_THAI13 = ESC + b'\x74\x17'  # Thai character code 13 | ||||
| CHARCODE_THAI14 = ESC + b'\x74\x18'  # Thai character code 14 | ||||
| CHARCODE_THAI16 = ESC + b'\x74\x19'  # Thai character code 16 | ||||
| CHARCODE_THAI17 = ESC + b'\x74\x1a'  # Thai character code 17 | ||||
| CHARCODE_THAI18 = ESC + b'\x74\x1b'  # Thai character code 18 | ||||
| # 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 | ||||
| @@ -195,6 +225,24 @@ BARCODE_TYPE_B = { | ||||
|     'GS1 DATABAR EXPANDED':        _SET_BARCODE_TYPE(78), | ||||
| } | ||||
|  | ||||
| BARCODE_FORMATS = { | ||||
|     'UPC-A':                       ([(11, 12)], "^[0-9]{11,12}$"), | ||||
|     'UPC-E':                       ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"), | ||||
|     'EAN13':                       ([(12, 13)], "^[0-9]{12,13}$"), | ||||
|     'EAN8':                        ([(7, 8)], "^[0-9]{7,8}$"), | ||||
|     'CODE39':                      ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"), | ||||
|     'ITF':                         ([(2, 255)], "^([0-9]{2})+$"), | ||||
|     'NW7':                         ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), | ||||
|     'CODABAR':                     ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),  # Same as NW7 | ||||
|     'CODE93':                      ([(1, 255)], "^[\\x00-\\x7F]+$"), | ||||
|     'CODE128':                     ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), | ||||
|     'GS1-128':                     ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128 | ||||
|     'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"), | ||||
|     'GS1 DATABAR TRUNCATED':       ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional | ||||
|     'GS1 DATABAR LIMITED':         ([(13,13)], "^[01][0-9]{12}$"), | ||||
|     'GS1 DATABAR EXPANDED':        ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"), | ||||
| } | ||||
|  | ||||
| BARCODE_TYPES = { | ||||
|     'A': BARCODE_TYPE_A, | ||||
|     'B': BARCODE_TYPE_B, | ||||
| @@ -220,13 +268,11 @@ 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% | ||||
| # Status Command | ||||
| RT_STATUS = DLE + EOT | ||||
| RT_STATUS_ONLINE = RT_STATUS +  b'\x01' | ||||
| RT_STATUS_PAPER = RT_STATUS +  b'\x04' | ||||
| RT_MASK_ONLINE = 8 | ||||
| RT_MASK_PAPER = 18 | ||||
| RT_MASK_LOWPAPER = 30 | ||||
| RT_MASK_NOPAPER = 114 | ||||
| @@ -6,8 +6,8 @@ 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 Bashlinux | ||||
| :license: GNU GPL v3 | ||||
| :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -17,12 +17,39 @@ from __future__ import unicode_literals | ||||
|  | ||||
| import qrcode | ||||
| import textwrap | ||||
| import six | ||||
| import time | ||||
| from re import match as re_match | ||||
|  | ||||
| from .constants import * | ||||
| from .exceptions import * | ||||
| import barcode | ||||
| from barcode.writer import ImageWriter | ||||
|  | ||||
| import os | ||||
|  | ||||
| from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q | ||||
| from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH | ||||
| from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS | ||||
| from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW | ||||
| from .constants import TXT_SIZE, TXT_NORMAL | ||||
| from .constants import SET_FONT | ||||
| from .constants import LINESPACING_FUNCS, LINESPACING_RESET | ||||
| from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE | ||||
| from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT | ||||
| from .constants import HW_RESET, HW_SELECT, HW_INIT | ||||
| from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON | ||||
| from .constants import TXT_STYLE | ||||
| from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE | ||||
| from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER | ||||
|  | ||||
| from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError | ||||
| from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError | ||||
| from .exceptions import ImageWidthError | ||||
|  | ||||
| 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) | ||||
| @@ -33,13 +60,13 @@ class Escpos(object): | ||||
|     class. | ||||
|     """ | ||||
|     device = None | ||||
|     codepage = None | ||||
|  | ||||
|     def __init__(self, columns=32): | ||||
|     def __init__(self, profile=None, magic_encode_args=None, **kwargs): | ||||
|         """ Initialize ESCPOS Printer | ||||
|  | ||||
|         :param columns: Text columns used by the printer. Defaults to 32.""" | ||||
|         self.columns = columns | ||||
|         :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 """ | ||||
| @@ -56,8 +83,14 @@ class Escpos(object): | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Returns a NotImplementedError if the instance of the class doesn't override this method. | ||||
|         :raises NotImplementedError | ||||
|         """ | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", | ||||
|               fragment_height=1024): | ||||
|               fragment_height=960, center=False): | ||||
|         """ Print an image | ||||
|  | ||||
|         You can select whether the printer should print in high density or not. The default value is high density. | ||||
| @@ -77,11 +110,27 @@ class Escpos(object): | ||||
|         :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:* 1024 | ||||
|         :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960 | ||||
|         :param center: Center image horizontally *default:* False | ||||
|  | ||||
|         """ | ||||
|         im = EscposImage(img_source) | ||||
|  | ||||
|         try: | ||||
|             max_width = int(self.profile.profile_data['media']['width']['pixels']) | ||||
|  | ||||
|             if im.width > max_width: | ||||
|                 raise ImageWidthError('{} > {}'.format(im.width, max_width)) | ||||
|  | ||||
|             if center: | ||||
|                 im.center(max_width) | ||||
|         except KeyError: | ||||
|             # If the printer's pixel width is not known, print anyways... | ||||
|             pass | ||||
|         except ValueError: | ||||
|             # If the max_width cannot be converted to an int, print anyways... | ||||
|             pass | ||||
|  | ||||
|         if im.height > fragment_height: | ||||
|             fragments = im.split(fragment_height) | ||||
|             for fragment in fragments: | ||||
| @@ -95,7 +144,8 @@ class Escpos(object): | ||||
|         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) | ||||
|             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": | ||||
| @@ -131,7 +181,8 @@ class Escpos(object): | ||||
|         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): | ||||
|     def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, | ||||
|            native=False, center=False, impl="bitImageRaster"): | ||||
|         """ Print QR Code for the provided string | ||||
|  | ||||
|         :param content: The content of the code. Numeric data will be more efficiently compacted. | ||||
| @@ -143,6 +194,8 @@ class Escpos(object): | ||||
|             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) | ||||
|         :param center: Centers the code *default:* False | ||||
|         :param impl: Image-printing-implementation, refer to :meth:`.image()` for details | ||||
|         """ | ||||
|         # Basic validation | ||||
|         if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: | ||||
| @@ -169,9 +222,17 @@ class Escpos(object): | ||||
|             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) | ||||
|             self.text('\n') | ||||
|             self.image(im, center=center, impl=impl) | ||||
|             self.text('\n') | ||||
|             self.text('\n') | ||||
|             return | ||||
|  | ||||
|         if center: | ||||
|             raise NotImplementedError("Centering not implemented for native QR rendering") | ||||
|  | ||||
|         # Native 2D code printing | ||||
|         cn = b'1'  # Code type for QR code | ||||
|         # Select model: 1, 2 or micro. | ||||
| @@ -206,102 +267,66 @@ class Escpos(object): | ||||
|         """ | ||||
|         max_input = (256 << (out_bytes * 8) - 1) | ||||
|         if not 1 <= out_bytes <= 4: | ||||
|             raise ValueError("Can only output 1-4 byes") | ||||
|             raise ValueError("Can only output 1-4 bytes") | ||||
|         if not 0 <= inp_number <= max_input: | ||||
|             raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes)) | ||||
|             raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes)) | ||||
|         outp = b'' | ||||
|         for _ in range(0, out_bytes): | ||||
|             outp += six.int2byte(inp_number % 256) | ||||
|             inp_number //= 256 | ||||
|         return outp | ||||
|  | ||||
|     def charcode(self, code): | ||||
|     def charcode(self, code="AUTO"): | ||||
|         """ Set Character Code Table | ||||
|  | ||||
|         Sends the control sequence from :py:mod:`escpos.constants` to the printer | ||||
|         with :py:meth:`escpos.printer.'implementation'._raw()`. | ||||
|         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` | ||||
|         """ | ||||
|         # TODO improve this (rather unhandy code) | ||||
|         # TODO check the codepages | ||||
|         if code.upper() == "USA": | ||||
|             self._raw(CHARCODE_PC437) | ||||
|             self.codepage = 'cp437' | ||||
|         elif code.upper() == "JIS": | ||||
|             self._raw(CHARCODE_JIS) | ||||
|             self.codepage = 'cp932' | ||||
|         elif code.upper() == "MULTILINGUAL": | ||||
|             self._raw(CHARCODE_PC850) | ||||
|             self.codepage = 'cp850' | ||||
|         elif code.upper() == "PORTUGUESE": | ||||
|             self._raw(CHARCODE_PC860) | ||||
|             self.codepage = 'cp860' | ||||
|         elif code.upper() == "CA_FRENCH": | ||||
|             self._raw(CHARCODE_PC863) | ||||
|             self.codepage = 'cp863' | ||||
|         elif code.upper() == "NORDIC": | ||||
|             self._raw(CHARCODE_PC865) | ||||
|             self.codepage = 'cp865' | ||||
|         elif code.upper() == "WEST_EUROPE": | ||||
|             self._raw(CHARCODE_WEU) | ||||
|             self.codepage = 'latin_1' | ||||
|         elif code.upper() == "GREEK": | ||||
|             self._raw(CHARCODE_GREEK) | ||||
|             self.codepage = 'cp737' | ||||
|         elif code.upper() == "HEBREW": | ||||
|             self._raw(CHARCODE_HEBREW) | ||||
|             self.codepage = 'cp862' | ||||
|         # elif code.upper() == "LATVIAN":  # this is not listed in the constants | ||||
|         #    self._raw(CHARCODE_PC755) | ||||
|         #    self.codepage = 'cp' | ||||
|         elif code.upper() == "WPC1252": | ||||
|             self._raw(CHARCODE_PC1252) | ||||
|             self.codepage = 'cp1252' | ||||
|         elif code.upper() == "CIRILLIC2": | ||||
|             self._raw(CHARCODE_PC866) | ||||
|             self.codepage = 'cp866' | ||||
|         elif code.upper() == "LATIN2": | ||||
|             self._raw(CHARCODE_PC852) | ||||
|             self.codepage = 'cp852' | ||||
|         elif code.upper() == "EURO": | ||||
|             self._raw(CHARCODE_PC858) | ||||
|             self.codepage = 'cp858' | ||||
|         elif code.upper() == "THAI42": | ||||
|             self._raw(CHARCODE_THAI42) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI11": | ||||
|             self._raw(CHARCODE_THAI11) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI13": | ||||
|             self._raw(CHARCODE_THAI13) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI14": | ||||
|             self._raw(CHARCODE_THAI14) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI16": | ||||
|             self._raw(CHARCODE_THAI16) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI17": | ||||
|             self._raw(CHARCODE_THAI17) | ||||
|             self.codepage = 'cp874' | ||||
|         elif code.upper() == "THAI18": | ||||
|             self._raw(CHARCODE_THAI18) | ||||
|             self.codepage = 'cp874' | ||||
|         if code.upper() == "AUTO": | ||||
|             self.magic.force_encoding(False) | ||||
|         else: | ||||
|             raise CharCodeError() | ||||
|             self.magic.force_encoding(code) | ||||
|  | ||||
|     def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True, function_type="A"): | ||||
|     @staticmethod | ||||
|     def check_barcode(bc, code): | ||||
|         """ | ||||
|         This method checks if the barcode is in the proper format. | ||||
|         The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum. | ||||
|         The full set of requirement for each barcode type is available in the ESC/POS documentation. | ||||
|  | ||||
|         As an example, using EAN13, the barcode `12345678901` will be correct, because it can be rendered by the | ||||
|         printer. But it does not suit the EAN13 standard, because the checksum digit is missing. Adding a wrong | ||||
|         checksum in the end will also be considered correct, but adding a letter won't (EAN13 is numeric only). | ||||
|  | ||||
|         .. todo:: Add a method to compute the checksum for the different standards | ||||
|  | ||||
|         .. todo:: For fixed-length standards with mandatory checksum (EAN, UPC), | ||||
|             compute and add the checksum automatically if missing. | ||||
|  | ||||
|         :param bc: barcode format, see :py:meth:`.barcode()` | ||||
|         :param code: alphanumeric data to be printed as bar code, see :py:meth:`.barcode()` | ||||
|         :return: bool | ||||
|         """ | ||||
|         if bc not in BARCODE_FORMATS: | ||||
|             return False | ||||
|  | ||||
|         bounds, regex = BARCODE_FORMATS[bc] | ||||
|         return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code) | ||||
|  | ||||
|     def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", | ||||
|                 align_ct=True, function_type=None, check=True): | ||||
|         """ Print Barcode | ||||
|  | ||||
|         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. | ||||
|         be supported by the unit. By default, this method will check whether your barcode text is correct, that is | ||||
|         the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but | ||||
|         note that 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. | ||||
|  | ||||
|         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 | ||||
| @@ -363,17 +388,54 @@ class Escpos(object): | ||||
|                          issued. | ||||
|         :type align_ct: bool | ||||
|  | ||||
|         :param function_type: Choose between ESCPOS function type A or B, depending on printer support and desired | ||||
|             barcode. | ||||
|         :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 | ||||
|  | ||||
|         :param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc | ||||
|             requirements as defigned in the esc/pos documentation. See :py:meth:`.check_barcode()` | ||||
|             for more information. *default*: True. | ||||
|  | ||||
|         :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 '{bc}' not valid for barcode function type " | ||||
|                 "{function_type}").format( | ||||
|                     bc=bc, | ||||
|                     function_type=function_type, | ||||
|                 )) | ||||
|  | ||||
|         if check and not self.check_barcode(bc, code): | ||||
|             raise BarcodeCodeError(( | ||||
|                 "Barcode '{code}' not in a valid format for type '{bc}'").format( | ||||
|                 code=code, | ||||
|                 bc=bc, | ||||
|             )) | ||||
|  | ||||
|         # Align Bar Code() | ||||
|         if align_ct: | ||||
|             self._raw(TXT_ALIGN_CT) | ||||
|             self._raw(TXT_STYLE['align']['center']) | ||||
|         # Height | ||||
|         if 1 <= height <= 255: | ||||
|             self._raw(BARCODE_HEIGHT + six.int2byte(height)) | ||||
| @@ -399,14 +461,6 @@ class Escpos(object): | ||||
|         else:  # DEFAULT POSITION: BELOW | ||||
|             self._raw(BARCODE_TXT_BLW) | ||||
|  | ||||
|         bc_types = BARCODE_TYPES[function_type.upper()] | ||||
|         if bc.upper() not in bc_types.keys(): | ||||
|             # TODO: Raise a better error, or fix the message of this error type | ||||
|             raise BarcodeTypeError("Barcode type {bc} not valid for barcode function type {function_type}".format( | ||||
|                 bc=bc, | ||||
|                 function_type=function_type, | ||||
|             )) | ||||
|  | ||||
|         self._raw(bc_types[bc.upper()]) | ||||
|  | ||||
|         if function_type.upper() == "B": | ||||
| @@ -421,6 +475,32 @@ class Escpos(object): | ||||
|         if function_type.upper() == "A": | ||||
|             self._raw(NUL) | ||||
|  | ||||
|     def soft_barcode(self, barcode_type, data, impl='bitImageColumn', | ||||
|                      module_height=5, module_width=0.2, text_distance=1): | ||||
|  | ||||
|         image_writer = ImageWriter() | ||||
|  | ||||
|         # Check if barcode type exists | ||||
|         if barcode_type not in barcode.PROVIDED_BARCODES: | ||||
|             raise BarcodeTypeError( | ||||
|                 'Barcode type {} not supported by software barcode renderer' | ||||
|                 .format(barcode_type)) | ||||
|  | ||||
|         # Render the barcode to a fake file | ||||
|         barcode_class = barcode.get_barcode_class(barcode_type) | ||||
|         my_code = barcode_class(data, writer=image_writer) | ||||
|  | ||||
|         with open(os.devnull, "wb") as nullfile: | ||||
|             my_code.write(nullfile, { | ||||
|                 'module_height': module_height, | ||||
|                 'module_width': module_width, | ||||
|                 'text_distance': text_distance | ||||
|             }) | ||||
|  | ||||
|         # Retrieve the Pillow image and print it | ||||
|         image = my_code.writer._image | ||||
|         self.image(image, impl=impl) | ||||
|  | ||||
|     def text(self, txt): | ||||
|         """ Print alpha-numeric text | ||||
|  | ||||
| @@ -430,140 +510,116 @@ class Escpos(object): | ||||
|         :param txt: text to be printed | ||||
|         :raises: :py:exc:`~escpos.exceptions.TextError` | ||||
|         """ | ||||
|         if txt: | ||||
|             if self.codepage: | ||||
|                 self._raw(txt.encode(self.codepage)) | ||||
|             else: | ||||
|                 self._raw(txt.encode()) | ||||
|         else: | ||||
|             # TODO: why is it problematic to print an empty string? | ||||
|             raise TextError() | ||||
|         txt = six.text_type(txt) | ||||
|         self.magic.write(txt) | ||||
|  | ||||
|     def block_text(self, txt, columns=None): | ||||
|     def textln(self, txt=''): | ||||
|         """Print alpha-numeric text with a newline | ||||
|  | ||||
|         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 with a newline | ||||
|         :raises: :py:exc:`~escpos.exceptions.TextError` | ||||
|         """ | ||||
|         self.text('{}\n'.format(txt)) | ||||
|  | ||||
|     def ln(self, count=1): | ||||
|         """Print a newline or more | ||||
|  | ||||
|         :param count: number of newlines to print | ||||
|         :raises: :py:exc:`ValueError` if count < 0 | ||||
|         """ | ||||
|         if count < 0: | ||||
|             raise ValueError('Count cannot be lesser than 0') | ||||
|         if count > 0: | ||||
|             self.text('\n' * count) | ||||
|  | ||||
|     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.columns if columns is None else columns | ||||
|         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): | ||||
|     def set(self, align='left', font='a', bold=False, underline=0, width=1, | ||||
|             height=1, density=9, invert=False, smooth=False, flip=False, | ||||
|             double_width=False, double_height=False, custom_size=False): | ||||
|         """ Set text properties by sending them to the printer | ||||
|  | ||||
|         :param align: horizontal position for text, possible values are: | ||||
|  | ||||
|             * CENTER | ||||
|             * LEFT | ||||
|             * RIGHT | ||||
|             * 'center' | ||||
|             * 'left' | ||||
|             * 'right' | ||||
|  | ||||
|             *default*: LEFT | ||||
|         :param font: font type, possible values are A or B, *default*: A | ||||
|         :param text_type: text type, possible values are: | ||||
|             *default*: 'left' | ||||
|  | ||||
|             * 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 font: font given as an index, a name, or one of the | ||||
|             special values 'a' or 'b', referring to fonts 0 and 1. | ||||
|         :param bold: text in bold, *default*: False | ||||
|         :param underline: underline mode for text, decimal range 0-2,  *default*: 0 | ||||
|         :param double_height: doubles the height of the text | ||||
|         :param double_width: doubles the width of the text | ||||
|         :param custom_size: uses custom size specified by width and height | ||||
|             parameters. Cannot be used with double_width or double_height. | ||||
|         :param width: text width multiplier when custom_size is used, decimal range 1-8,  *default*: 1 | ||||
|         :param height: text height multiplier when custom_size is used, 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 font: str | ||||
|         :type invert: bool | ||||
|         :type bold: bool | ||||
|         :type underline: bool | ||||
|         :type smooth: bool | ||||
|         :type flip: bool | ||||
|         :type custom_size: bool | ||||
|         :type double_width: bool | ||||
|         :type double_height: bool | ||||
|         :type align: str | ||||
|         :type width: int | ||||
|         :type height: int | ||||
|         :type density: int | ||||
|         """ | ||||
|         # 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])) | ||||
|  | ||||
|         if custom_size: | ||||
|             if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\ | ||||
|               isinstance(height, int): | ||||
|                 size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height] | ||||
|                 self._raw(TXT_SIZE + six.int2byte(size_byte)) | ||||
|             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) | ||||
|             self._raw(TXT_NORMAL) | ||||
|             if double_width and double_height: | ||||
|                 self._raw(TXT_STYLE['size']['2x']) | ||||
|             elif double_width: | ||||
|                 self._raw(TXT_STYLE['size']['2w']) | ||||
|             elif double_height: | ||||
|                 self._raw(TXT_STYLE['size']['2h']) | ||||
|             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 | ||||
|         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 | ||||
|         # Invert Printing | ||||
|         if invert: | ||||
|             self._raw(TXT_INVERT_ON) | ||||
|         else: | ||||
|             self._raw(TXT_INVERT_OFF) | ||||
|                 self._raw(TXT_STYLE['size']['normal']) | ||||
|  | ||||
|         self._raw(TXT_STYLE['flip'][flip]) | ||||
|         self._raw(TXT_STYLE['smooth'][smooth]) | ||||
|         self._raw(TXT_STYLE['bold'][bold]) | ||||
|         self._raw(TXT_STYLE['underline'][underline]) | ||||
|         self._raw(SET_FONT(six.int2byte(self.profile.get_font(font)))) | ||||
|         self._raw(TXT_STYLE['align'][align]) | ||||
|  | ||||
|         if density != 9: | ||||
|             self._raw(TXT_STYLE['density'][density]) | ||||
|  | ||||
|         self._raw(TXT_STYLE['invert'][invert]) | ||||
|  | ||||
|     def line_spacing(self, spacing=None, divisor=180): | ||||
|         """ Set line character spacing. | ||||
| @@ -586,7 +642,7 @@ class Escpos(object): | ||||
|  | ||||
|         if divisor not in LINESPACING_FUNCS: | ||||
|             raise ValueError("divisor must be either 360, 180 or 60") | ||||
|         if (divisor in [360, 180] \ | ||||
|         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)): | ||||
| @@ -594,7 +650,7 @@ class Escpos(object): | ||||
|  | ||||
|         self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) | ||||
|  | ||||
|     def cut(self, mode=''): | ||||
|     def cut(self, mode='FULL', feed=True): | ||||
|         """ Cut paper. | ||||
|  | ||||
|         Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will | ||||
| @@ -603,22 +659,39 @@ class Escpos(object): | ||||
|  | ||||
|         .. todo:: Check this function on TM-T88II. | ||||
|  | ||||
|         :param mode: set to 'PART' for a partial cut | ||||
|         :param mode: set to 'PART' for a partial cut. default: 'FULL' | ||||
|         :param feed: print and feed before cutting. default: true | ||||
|         :raises ValueError: if mode not in ('FULL', 'PART') | ||||
|         """ | ||||
|         # Fix the size between last line and cut | ||||
|         # TODO: handle this with a line feed | ||||
|         self._raw(b"\n\n\n\n\n\n") | ||||
|         if mode.upper() == "PART": | ||||
|  | ||||
|         if not feed: | ||||
|             self._raw(GS + b'V' + six.int2byte(66) + b'\x00') | ||||
|             return | ||||
|  | ||||
|         self.print_and_feed(6) | ||||
|  | ||||
|         mode = mode.upper() | ||||
|         if mode not in ('FULL', 'PART'): | ||||
|             raise ValueError("Mode must be one of ('FULL', 'PART')") | ||||
|  | ||||
|         if mode == "PART": | ||||
|             if self.profile.supports('paperPartCut'): | ||||
|                 self._raw(PAPER_PART_CUT) | ||||
|         else:  # DEFAULT MODE: FULL 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 parameter. | ||||
|         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 | ||||
|         :param pin: pin number, 2 or 5 or list of decimals | ||||
|         :raises: :py:exc:`~escpos.exceptions.CashDrawerError` | ||||
|         """ | ||||
|         if pin == 2: | ||||
| @@ -626,7 +699,46 @@ class Escpos(object): | ||||
|         elif pin == 5: | ||||
|             self._raw(CD_KICK_5) | ||||
|         else: | ||||
|             raise CashDrawerError() | ||||
|             try: | ||||
|                 self._raw(CD_KICK_DEC_SEQUENCE(*pin)) | ||||
|             except TypeError as err: | ||||
|                 raise CashDrawerError(err) | ||||
|  | ||||
|     def linedisplay_select(self, select_display=False): | ||||
|         """ Selects the line display or the printer | ||||
|  | ||||
|         This method is used for line displays that are daisy-chained between your computer and printer. | ||||
|         If you set `select_display` to true, only the display is selected and if you set it to false, | ||||
|         only the printer is selected. | ||||
|  | ||||
|         :param select_display: whether the display should be selected or the printer | ||||
|         :type select_display: bool | ||||
|         """ | ||||
|         if select_display: | ||||
|             self._raw(LINE_DISPLAY_OPEN) | ||||
|         else: | ||||
|             self._raw(LINE_DISPLAY_CLOSE) | ||||
|  | ||||
|     def linedisplay_clear(self): | ||||
|         """ Clears the line display and resets the cursor | ||||
|  | ||||
|         This method is used for line displays that are daisy-chained between your computer and printer. | ||||
|         """ | ||||
|         self._raw(LINE_DISPLAY_CLEAR) | ||||
|  | ||||
|     def linedisplay(self, text): | ||||
|         """ | ||||
|         Display text on a line display connected to your printer | ||||
|  | ||||
|         You should connect a line display to your printer. You can do this by daisy-chaining | ||||
|         the display between your computer and printer. | ||||
|  | ||||
|         :param text: Text to display | ||||
|         """ | ||||
|         self.linedisplay_select(select_display=True) | ||||
|         self.linedisplay_clear() | ||||
|         self.text(text) | ||||
|         self.linedisplay_select(select_display=False) | ||||
|  | ||||
|     def hw(self, hw): | ||||
|         """ Hardware operations | ||||
| @@ -646,7 +758,21 @@ class Escpos(object): | ||||
|         else:  # DEFAULT: DOES NOTHING | ||||
|             pass | ||||
|  | ||||
|     def control(self, ctl, pos=4): | ||||
|     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, count=5, tab_size=8): | ||||
|         """ Feed control sequences | ||||
|  | ||||
|         :param ctl: string for the following control sequences: | ||||
| @@ -657,14 +783,10 @@ class Escpos(object): | ||||
|             * HT *for Horizontal Tab* | ||||
|             * VT *for Vertical Tab* | ||||
|  | ||||
|         :param pos: integer between 1 and 16, controls the horizontal tab position | ||||
|         :param count: integer between 1 and 32, controls the horizontal tab count. Defaults to 5. | ||||
|         :param tab_size: integer between 1 and 255, controls the horizontal tab size in characters. Defaults to 8 | ||||
|         :raises: :py:exc:`~escpos.exceptions.TabPosError` | ||||
|         """ | ||||
|         # Set tab positions | ||||
|         if not (1 <= pos <= 16): | ||||
|             raise TabPosError() | ||||
|         else: | ||||
|             self._raw(CTL_SET_HT + six.int2byte(pos)) | ||||
|         # Set position | ||||
|         if ctl.upper() == "LF": | ||||
|             self._raw(CTL_LF) | ||||
| @@ -673,7 +795,16 @@ class Escpos(object): | ||||
|         elif ctl.upper() == "CR": | ||||
|             self._raw(CTL_CR) | ||||
|         elif ctl.upper() == "HT": | ||||
|             self._raw(CTL_HT) | ||||
|             if not (0 <= count <= 32 and | ||||
|                     1 <= tab_size <= 255 and | ||||
|                     count * tab_size < 256): | ||||
|                 raise TabPosError() | ||||
|             else: | ||||
|                 # Set tab positions | ||||
|                 self._raw(CTL_SET_HT) | ||||
|                 for iterator in range(1, count): | ||||
|                     self._raw(six.int2byte(iterator * tab_size)) | ||||
|                 self._raw(NUL) | ||||
|         elif ctl.upper() == "VT": | ||||
|             self._raw(CTL_VT) | ||||
|  | ||||
| @@ -700,12 +831,58 @@ class Escpos(object): | ||||
|         else: | ||||
|             self._raw(PANEL_BUTTON_OFF) | ||||
|  | ||||
|     def query_status(self, mode): | ||||
|         """ | ||||
|         Queries the printer for its status, and returns an array of integers containing it. | ||||
|  | ||||
|         :param mode: Integer that sets the status mode queried to the printer. | ||||
|             - RT_STATUS_ONLINE: Printer status. | ||||
|             - RT_STATUS_PAPER: Paper sensor. | ||||
|         :rtype: array(integer) | ||||
|         """ | ||||
|         self._raw(mode) | ||||
|         time.sleep(1) | ||||
|         status = self._read() | ||||
|         return status | ||||
|  | ||||
|     def is_online(self): | ||||
|         """ | ||||
|         Queries the online status of the printer. | ||||
|  | ||||
|         :returns: When online, returns ``True``; ``False`` otherwise. | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         status = self.query_status(RT_STATUS_ONLINE) | ||||
|         if len(status) == 0: | ||||
|             return False | ||||
|         return not (status[0] & RT_MASK_ONLINE) | ||||
|  | ||||
|     def paper_status(self): | ||||
|         """ | ||||
|         Queries the paper status of the printer. | ||||
|  | ||||
|         Returns 2 if there is plenty of paper, 1 if the paper has arrived to | ||||
|         the near-end sensor and 0 if there is no paper. | ||||
|  | ||||
|         :returns: 2: Paper is adequate. 1: Paper ending. 0: No paper. | ||||
|         :rtype: int | ||||
|         """ | ||||
|         status = self.query_status(RT_STATUS_PAPER) | ||||
|         if len(status) == 0: | ||||
|             return 2 | ||||
|         if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER): | ||||
|             return 0 | ||||
|         if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER): | ||||
|             return 1 | ||||
|         if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER): | ||||
|             return 2 | ||||
|  | ||||
|  | ||||
| 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. | ||||
|     and assign it to the EscposIO class. | ||||
|     This example explains the usage: | ||||
|  | ||||
|     .. code-block:: Python | ||||
|   | ||||
| @@ -8,6 +8,7 @@ Result/Exit codes: | ||||
|     - `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` | ||||
|     - `41` = Image width is too large :py:exc:`~escpos.exceptions.ImageWidthError` | ||||
|     - `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` | ||||
| @@ -20,8 +21,8 @@ Result/Exit codes: | ||||
|  | ||||
| :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||
| :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2012 Bashlinux | ||||
| :license: GNU GPL v3 | ||||
| :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -76,9 +77,10 @@ class BarcodeSizeError(Error): | ||||
|  | ||||
|  | ||||
| class BarcodeCodeError(Error): | ||||
|     """ No Barcode code was supplied. | ||||
|     """ No Barcode code was supplied, or it is incorrect. | ||||
|  | ||||
|     No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`. | ||||
|     No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter | ||||
|     was True and the check failed. | ||||
|     The returncode for this exception is `30`. | ||||
|     """ | ||||
|     def __init__(self, msg=""): | ||||
| @@ -87,7 +89,7 @@ class BarcodeCodeError(Error): | ||||
|         self.resultcode = 30 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "No Barcode code was supplied" | ||||
|         return "No Barcode code was supplied ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class ImageSizeError(Error): | ||||
| @@ -101,7 +103,21 @@ class ImageSizeError(Error): | ||||
|         self.resultcode = 40 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Image height is longer than 255px and can't be printed" | ||||
|         return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class ImageWidthError(Error): | ||||
|     """ Image width is too large. | ||||
|  | ||||
|     The return code for this exception is `41`. | ||||
|     """ | ||||
|     def __init__(self, msg=""): | ||||
|         Error.__init__(self, msg) | ||||
|         self.msg = msg | ||||
|         self.resultcode = 41 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Image width is too large ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class TextError(Error): | ||||
| @@ -116,7 +132,7 @@ class TextError(Error): | ||||
|         self.resultcode = 50 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Text string must be supplied to the text() method" | ||||
|         return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class CashDrawerError(Error): | ||||
| @@ -131,11 +147,12 @@ class CashDrawerError(Error): | ||||
|         self.resultcode = 60 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Valid pin must be set to send pulse" | ||||
|         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. | ||||
|     """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. | ||||
|     Both values multiplied must not exceed 255, since it is the maximum tab value. | ||||
|  | ||||
|     This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. | ||||
|     The returncode for this exception is `70`. | ||||
| @@ -146,7 +163,7 @@ class TabPosError(Error): | ||||
|         self.resultcode = 70 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Valid tab positions must be in the range 0 to 16" | ||||
|         return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class CharCodeError(Error): | ||||
| @@ -161,7 +178,7 @@ class CharCodeError(Error): | ||||
|         self.resultcode = 80 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Valid char code must be set" | ||||
|         return "Valid char code must be set ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class USBNotFoundError(Error): | ||||
| @@ -176,7 +193,7 @@ class USBNotFoundError(Error): | ||||
|         self.resultcode = 90 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "USB device not found" | ||||
|         return "USB device not found ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| class SetVariableError(Error): | ||||
| @@ -191,7 +208,7 @@ class SetVariableError(Error): | ||||
|         self.resultcode = 100 | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Set variable out of range" | ||||
|         return "Set variable out of range ({msg})".format(msg=self.msg) | ||||
|  | ||||
|  | ||||
| # Configuration errors | ||||
|   | ||||
| @@ -5,7 +5,7 @@ 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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -115,3 +115,19 @@ class EscposImage(object): | ||||
|             box = (left, upper, right, lower) | ||||
|             fragments.append(self.img_original.crop(box)) | ||||
|         return fragments | ||||
|  | ||||
|     def center(self, max_width): | ||||
|         """In-place image centering | ||||
|  | ||||
|         :param: Maximum width in order to deduce x offset for centering | ||||
|         :return: None | ||||
|         """ | ||||
|         old_width, height = self._im.size | ||||
|         new_size = (max_width, height) | ||||
|  | ||||
|         new_im = Image.new("1", new_size) | ||||
|         paste_x = int((max_width - old_width) / 2) | ||||
|  | ||||
|         new_im.paste(self._im, (paste_x, 0)) | ||||
|  | ||||
|         self._im = new_im | ||||
|   | ||||
							
								
								
									
										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)) | ||||
| @@ -4,8 +4,8 @@ | ||||
|  | ||||
| :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||
| :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2012 Bashlinux | ||||
| :license: GNU GPL v3 | ||||
| :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -34,28 +34,45 @@ class Usb(Escpos): | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): | ||||
|     def __init__(self, idVendor, idProduct, usb_args=None, timeout=0, in_ep=0x82, out_ep=0x01, | ||||
|                  *args, **kwargs):  # noqa: N803 | ||||
|         """ | ||||
|         :param idVendor: Vendor ID | ||||
|         :param idProduct: Product ID | ||||
|         :param usb_args: Optional USB arguments (e.g. custom_match) | ||||
|         :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) | ||||
|         usb_args = usb_args or {} | ||||
|         if idVendor: | ||||
|             usb_args['idVendor'] = idVendor | ||||
|         if idProduct: | ||||
|             usb_args['idProduct'] = idProduct | ||||
|         self.open(usb_args) | ||||
|  | ||||
|     def open(self, usb_args): | ||||
|         """ Search device on USB tree and set it as escpos device. | ||||
|  | ||||
|         :param usb_args: USB arguments | ||||
|         """ | ||||
|         self.device = usb.core.find(**usb_args) | ||||
|         if self.device is None: | ||||
|             raise USBNotFoundError("Device not found or cable not plugged in.") | ||||
|  | ||||
|         self.idVendor = self.device.idVendor | ||||
|         self.idProduct = self.device.idProduct | ||||
|  | ||||
|         # pyusb has three backends: libusb0, libusb1 and openusb but | ||||
|         # only libusb1 backend implements the methods is_kernel_driver_active() | ||||
|         # and detach_kernel_driver(). | ||||
|         # This helps enable this library to work on Windows. | ||||
|         if self.device.backend.__module__.endswith("libusb1"): | ||||
|             check_driver = None | ||||
|  | ||||
|             try: | ||||
| @@ -84,6 +101,10 @@ class Usb(Escpos): | ||||
|         """ | ||||
|         self.device.write(self.out_ep, msg, self.timeout) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Reads a data buffer and returns it to the caller. """ | ||||
|         return self.device.read(self.in_ep, 16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Release USB interface """ | ||||
|         if self.device: | ||||
| @@ -131,6 +152,8 @@ class Serial(Escpos): | ||||
|  | ||||
|     def open(self): | ||||
|         """ Setup serial port and set is as escpos device """ | ||||
|         if self.device is not None and self.device.is_open: | ||||
|             self.close() | ||||
|         self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, | ||||
|                                     bytesize=self.bytesize, parity=self.parity, | ||||
|                                     stopbits=self.stopbits, timeout=self.timeout, | ||||
| @@ -149,9 +172,13 @@ class Serial(Escpos): | ||||
|         """ | ||||
|         self.device.write(msg) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Reads a data buffer and returns it to the caller. """ | ||||
|         return self.device.read(16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close Serial interface """ | ||||
|         if self.device is not None: | ||||
|         if self.device is not None and self.device.is_open: | ||||
|             self.device.flush() | ||||
|             self.device.close() | ||||
|  | ||||
| @@ -182,9 +209,9 @@ class Network(Escpos): | ||||
|     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 | ||||
|         :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 | ||||
| @@ -209,8 +236,14 @@ class Network(Escpos): | ||||
|         """ | ||||
|         self.device.sendall(msg) | ||||
|  | ||||
|     def _read(self): | ||||
|         """ Read data from the TCP socket """ | ||||
|  | ||||
|         return self.device.recv(16) | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close TCP connection """ | ||||
|         if self.device is not None: | ||||
|             self.device.shutdown(socket.SHUT_RDWR) | ||||
|             self.device.close() | ||||
|  | ||||
| @@ -232,7 +265,7 @@ class File(Escpos): | ||||
|     def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs): | ||||
|         """ | ||||
|  | ||||
|         :param devfile : Device file under dev filesystem | ||||
|         :param devfile: Device file under dev filesystem | ||||
|         :param auto_flush: automatically call flush after every call of _raw() | ||||
|         """ | ||||
|         Escpos.__init__(self, *args, **kwargs) | ||||
| @@ -263,6 +296,7 @@ class File(Escpos): | ||||
|  | ||||
|     def close(self): | ||||
|         """ Close system file """ | ||||
|         if self.device is not None: | ||||
|             self.device.flush() | ||||
|             self.device.close() | ||||
|  | ||||
| @@ -300,5 +334,58 @@ class Dummy(Escpos): | ||||
|         """ Get the data that was sent to this printer """ | ||||
|         return b''.join(self._output_list) | ||||
|  | ||||
|     def clear(self): | ||||
|         """ Clear the buffer of the printer | ||||
|  | ||||
|         This method can be called if you send the contents to a physical printer | ||||
|         and want to use the Dummy printer for new output. | ||||
|         """ | ||||
|         del self._output_list[:] | ||||
|  | ||||
|     def close(self): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| _WIN32PRINT = False | ||||
| try: | ||||
|     import win32print | ||||
|     _WIN32PRINT = True | ||||
| except ImportError: | ||||
|     pass | ||||
|  | ||||
| if _WIN32PRINT: | ||||
|     class Win32Raw(Escpos): | ||||
|         def __init__(self, printer_name=None, *args, **kwargs): | ||||
|             Escpos.__init__(self, *args, **kwargs) | ||||
|             if printer_name is not None: | ||||
|                 self.printer_name = printer_name | ||||
|             else: | ||||
|                 self.printer_name = win32print.GetDefaultPrinter() | ||||
|             self.hPrinter = None | ||||
|  | ||||
|         def open(self, job_name="python-escpos"): | ||||
|             if self.printer_name is None: | ||||
|                 raise Exception("Printer not found") | ||||
|             self.hPrinter = win32print.OpenPrinter(self.printer_name) | ||||
|             self.current_job = win32print.StartDocPrinter(self.hPrinter, 1, (job_name, None, "RAW")) | ||||
|             win32print.StartPagePrinter(self.hPrinter) | ||||
|  | ||||
|         def close(self): | ||||
|             if not self.hPrinter: | ||||
|                 return | ||||
|             win32print.EndPagePrinter(self.hPrinter) | ||||
|             win32print.EndDocPrinter(self.hPrinter) | ||||
|             win32print.ClosePrinter(self.hPrinter) | ||||
|             self.hPrinter = None | ||||
|  | ||||
|         def _raw(self, msg): | ||||
|             """ Print any command sent in raw format | ||||
|  | ||||
|             :param msg: arbitrary code to be printed | ||||
|             :type msg: bytes | ||||
|             """ | ||||
|             if self.printer_name is None: | ||||
|                 raise Exception("Printer not found") | ||||
|             if self.hPrinter is None: | ||||
|                 raise Exception("Printer job not opened") | ||||
|             win32print.WritePrinter(self.hPrinter, msg) | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| Dies ist ein Test. | ||||
							
								
								
									
										7
									
								
								test/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| import pytest | ||||
| from escpos.printer import Dummy | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def driver(): | ||||
|     return Dummy() | ||||
| @@ -4,7 +4,7 @@ | ||||
| :author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ | ||||
| :organization: `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2016 Patrick Kanzler | ||||
| :license: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from __future__ import unicode_literals | ||||
| import os | ||||
| import sys | ||||
| from scripttest import TestFileEnvironment | ||||
| from nose.tools import assert_equals | ||||
| from nose.tools import assert_equals, nottest | ||||
| import escpos | ||||
|  | ||||
| TEST_DIR = os.path.abspath('test/test-cli-output') | ||||
| @@ -30,31 +30,26 @@ printer: | ||||
| ) | ||||
|  | ||||
|  | ||||
| class TestCLI(): | ||||
| class TestCLI: | ||||
|     """ Contains setups, teardowns, and tests for CLI | ||||
|     """ | ||||
|  | ||||
|     def __init__(self): | ||||
|         """ Initalize the tests. | ||||
|         Just define some vars here since most of them get set during | ||||
|         setup_class and teardown_class | ||||
|         """ | ||||
|         self.env = None | ||||
|         self.default_args = None | ||||
|  | ||||
|     @staticmethod | ||||
|     def setup_class(): | ||||
|     @classmethod | ||||
|     def setup_class(cls): | ||||
|         """ Create a config file to read from """ | ||||
|         with open(CONFIGFILE, 'w') as config: | ||||
|             config.write(CONFIG_YAML) | ||||
|  | ||||
|     @staticmethod | ||||
|     def teardown_class(): | ||||
|     @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(), | ||||
| @@ -89,6 +84,7 @@ class TestCLI(): | ||||
|         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' | ||||
|   | ||||
							
								
								
									
										52
									
								
								test/test_function_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B | ||||
| from escpos.capabilities import Profile, BARCODE_B | ||||
| from escpos.exceptions import BarcodeTypeError, BarcodeCodeError | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data,expected", [ | ||||
|     ('EAN13', '4006381333931', | ||||
|         b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00') | ||||
| ]) | ||||
| 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'' | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('EAN13', 'AA'), | ||||
|     ('CODE128', '{D2354AA'), | ||||
| ]) | ||||
| def test_code_check(bctype, data): | ||||
|     """should raise an error if the barcode code is invalid. | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     with pytest.raises(BarcodeCodeError): | ||||
|         instance.barcode(data, bctype) | ||||
|  | ||||
|     assert instance.output == b'' | ||||
							
								
								
									
										19
									
								
								test/test_function_cashdraw.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.exceptions import CashDrawerError | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| def test_raise_CashDrawerError(): | ||||
|     """should raise an error if the sequence is invalid. | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     with pytest.raises(CashDrawerError): | ||||
|         # call with sequence that is too long | ||||
|         instance.cashdraw([1,1,1,1,1,1]) | ||||
|  | ||||
							
								
								
									
										104
									
								
								test/test_function_check_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,104 @@ | ||||
| #!/usr/bin/python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('UPC-A', '01234567890'), | ||||
|     ('UPC-A', '012345678905'), | ||||
|     ('UPC-E', '01234567'), | ||||
|     ('UPC-E', '0123456'), | ||||
|     ('UPC-E', '012345678905'), | ||||
|     ('EAN13', '0123456789012'), | ||||
|     ('EAN13', '012345678901'), | ||||
|     ('EAN8', '01234567'), | ||||
|     ('EAN8', '0123456'), | ||||
|     ('CODE39', 'ABC-1234'), | ||||
|     ('CODE39', 'ABC-1234-$$-+A'), | ||||
|     ('CODE39', '*WIKIPEDIA*'), | ||||
|     ('ITF', '010203040506070809'), | ||||
|     ('ITF', '11221133113344556677889900'), | ||||
|     ('CODABAR', 'A2030405060B'), | ||||
|     ('CODABAR', 'C11221133113344556677889900D'), | ||||
|     ('CODABAR', 'D0D'), | ||||
|     ('NW7', 'A2030405060B'), | ||||
|     ('NW7', 'C11221133113344556677889900D'), | ||||
|     ('NW7', 'D0D'), | ||||
|     ('CODE93', 'A2030405060B'), | ||||
|     ('CODE93', '+:$&23-7@$'), | ||||
|     ('CODE93', 'D0D'), | ||||
|     ('CODE128', '{A2030405060B'), | ||||
|     ('CODE128', '{C+:$&23-7@$'), | ||||
|     ('CODE128', '{B0D'), | ||||
|     ('GS1-128', '{A2030405060B'), | ||||
|     ('GS1-128', '{C+:$&23-7@$'), | ||||
|     ('GS1-128', '{B0D'), | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'), | ||||
|     ('GS1 DATABAR TRUNCATED', '0123456789123'), | ||||
|     ('GS1 DATABAR LIMITED', '0123456789123'), | ||||
|     ('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'), | ||||
|     ('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'), | ||||
|     ('GS1 DATABAR EXPANDED', '(00000001234567678'), | ||||
| ]) | ||||
| def test_check_valid_barcode(bctype, data): | ||||
|     assert (printer.Escpos.check_barcode(bctype, data)) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("bctype,data", [ | ||||
|     ('UPC-A', '01234567890123'),  # too long | ||||
|     ('UPC-A', '0123456789'),  # too short | ||||
|     ('UPC-A', '72527273-711'),  # invalid '-' | ||||
|     ('UPC-A', 'A12345678901'),  # invalid 'A' | ||||
|     ('UPC-E', '01234567890123'),  # too long | ||||
|     ('UPC-E', '012345'),  # too short | ||||
|     ('UPC-E', '72527-2'),  # invalid '-' | ||||
|     ('UPC-E', 'A123456'),  # invalid 'A' | ||||
|     ('EAN13', '0123456789'),  # too short | ||||
|     ('EAN13', 'A123456789012'),  # invalid 'A' | ||||
|     ('EAN13', '012345678901234'),  # too long | ||||
|     ('EAN8', '012345'),  # too short | ||||
|     ('EAN8', 'A123456789012'),  # invalid 'A' | ||||
|     ('EAN8', '012345678901234'),  # too long | ||||
|     ('CODE39', 'ALKJ_34'),  # invalid '_' | ||||
|     ('CODE39', 'A' * 256),  # too long | ||||
|     ('ITF', '010203040'),  # odd length | ||||
|     ('ITF', '0' * 256),  # too long | ||||
|     ('ITF', 'AB01'),  # invalid 'A' | ||||
|     ('CODABAR', '010203040'),  # no start/stop | ||||
|     ('CODABAR', '0' * 256),  # too long | ||||
|     ('CODABAR', 'AB-01F'),  # invalid 'B' | ||||
|     ('NW7', '010203040'),  # no start/stop | ||||
|     ('NW7', '0' * 256),  # too long | ||||
|     ('NW7', 'AB-01F'),  # invalid 'B' | ||||
|     ('CODE93', 'é010203040'),  # invalid 'é' | ||||
|     ('CODE93', '0' * 256),  # too long | ||||
|     ('CODE128', '010203040'),  # missing leading { | ||||
|     ('CODE128', '{D2354AA'),  # second char not between A-C | ||||
|     ('CODE128', '0' * 256),  # too long | ||||
|     ('GS1-128', '010203040'),  # missing leading { | ||||
|     ('GS1-128', '{D2354AA'),  # second char not between A-C | ||||
|     ('GS1-128', '0' * 256),  # too long | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR TRUNCATED', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR TRUNCATED', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR TRUNCATED', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR LIMITED', '01234567891234'),  # too long | ||||
|     ('GS1 DATABAR LIMITED', '012345678912'),  # too short | ||||
|     ('GS1 DATABAR LIMITED', '012345678A1234'),  # invalid 'A' | ||||
|     ('GS1 DATABAR LIMITED', '02345678912341'),  # invalid start (should be 01) | ||||
|     ('GS1 DATABAR EXPANDED', '010203040'),  # missing leading ( | ||||
|     ('GS1-128', '(' + ('0' * 256)),  # too long | ||||
|     ('GS1 DATABAR EXPANDED', '(a{D2354AA'),  # second char not between 0-9 | ||||
|     ('GS1 DATABAR EXPANDED', 'IT will fail'),  # first char not '(' | ||||
| ]) | ||||
| def test_check_invalid_barcode(bctype, data): | ||||
|     assert (not printer.Escpos.check_barcode(bctype, data)) | ||||
							
								
								
									
										17
									
								
								test/test_function_cut.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.constants import GS | ||||
|  | ||||
|  | ||||
| def test_cut_without_feed(): | ||||
|     """Test cut without feeding paper""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.cut(feed=False) | ||||
|     expected = GS + b'V' + six.int2byte(66) + b'\x00' | ||||
|     assert(instance.output == expected) | ||||
							
								
								
									
										8
									
								
								test/test_function_dummy_clear.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| from nose.tools import assert_raises | ||||
| from escpos.printer import Dummy | ||||
|  | ||||
| def test_printer_dummy_clear(): | ||||
|     printer = Dummy() | ||||
|     printer.text("Hello") | ||||
|     printer.clear() | ||||
|     assert(printer.output == b'') | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -12,9 +12,13 @@ from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import pytest | ||||
|  | ||||
| from PIL import Image | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.exceptions import ImageWidthError | ||||
|  | ||||
|  | ||||
| # Raster format print | ||||
| def test_bit_image_black(): | ||||
| @@ -139,3 +143,37 @@ def test_large_graphics(): | ||||
|     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') | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def dummy_with_width(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.profile.profile_data = { | ||||
|         'media': { | ||||
|             'width': { | ||||
|                 'pixels': 384 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return instance | ||||
|  | ||||
|  | ||||
| def test_width_too_large(dummy_with_width): | ||||
|     """ | ||||
|     Test printing an image that is too large in width. | ||||
|     """ | ||||
|     instance = dummy_with_width | ||||
|  | ||||
|     with pytest.raises(ImageWidthError): | ||||
|         instance.image(Image.new("RGB", (385, 200))) | ||||
|  | ||||
|     instance.image(Image.new("RGB", (384, 200))) | ||||
|  | ||||
|  | ||||
| def test_center_image(dummy_with_width): | ||||
|     instance = dummy_with_width | ||||
|  | ||||
|     with pytest.raises(ImageWidthError): | ||||
|         instance.image(Image.new("RGB", (385, 200)), center=True) | ||||
|  | ||||
|     instance.image(Image.new("RGB", (384, 200)), center=True) | ||||
|   | ||||
							
								
								
									
										35
									
								
								test/test_function_linedisplay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,35 @@ | ||||
| #!/usr/bin/python | ||||
| """tests for line display | ||||
|  | ||||
| :author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ | ||||
| :organization: `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2017 `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 | ||||
|  | ||||
|  | ||||
| def test_function_linedisplay_select_on(): | ||||
|     """test the linedisplay_select function (activate)""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_select(select_display=True) | ||||
|     assert(instance.output == b'\x1B\x3D\x02') | ||||
|  | ||||
| def test_function_linedisplay_select_off(): | ||||
|     """test the linedisplay_select function (deactivate)""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_select(select_display=False) | ||||
|     assert(instance.output == b'\x1B\x3D\x01') | ||||
|  | ||||
| def test_function_linedisplay_clear(): | ||||
|     """test the linedisplay_clear function""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.linedisplay_clear() | ||||
|     assert(instance.output == b'\x1B\x40') | ||||
|  | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -12,43 +12,18 @@ 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 = printer.Dummy() | ||||
|     instance.panel_buttons() | ||||
|     instance.flush() | ||||
|     with open(devfile, "rb") as f: | ||||
|         assert(f.read() == b'\x1B\x63\x35\x00') | ||||
|     assert(instance.output == 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 = printer.Dummy() | ||||
|     instance.panel_buttons(False) | ||||
|     instance.flush() | ||||
|     with open(devfile, "rb") as f: | ||||
|         assert(f.read() == b'\x1B\x63\x35\x01') | ||||
|     assert(instance.output == b'\x1B\x63\x35\x01') | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -13,6 +13,8 @@ from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from nose.tools import raises | ||||
| import pytest | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 | ||||
|  | ||||
| @@ -25,7 +27,6 @@ def test_defaults(): | ||||
|         b'(k\x07\x001P01234\x1d(k\x03\x001Q0' | ||||
|     assert(instance.output == expected) | ||||
|  | ||||
|  | ||||
| def test_empty(): | ||||
|     """Test QR printing blank code""" | ||||
|     instance = printer.Dummy() | ||||
| @@ -81,14 +82,17 @@ def test_invalid_model(): | ||||
|     instance.qr("1234", native=True, model="Hello") | ||||
|  | ||||
|  | ||||
| @pytest.mark.skip("this test has to be debugged") | ||||
| 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' \ | ||||
|     expected = b'\x1bt\x00\n' \ | ||||
|         b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ | ||||
|         b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \ | ||||
|         b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' | ||||
|         b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \ | ||||
|         b'\n\n' | ||||
|     assert(instance.output == expected) | ||||
|  | ||||
|  | ||||
| @@ -97,3 +101,13 @@ def test_image_invalid_model(): | ||||
|     """Test unsupported QR model as image""" | ||||
|     instance = printer.Dummy() | ||||
|     instance.qr("1234", native=False, model=QR_MODEL_1) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def instance(): | ||||
|     return printer.Dummy() | ||||
|  | ||||
|  | ||||
| def test_center_not_implementer(instance): | ||||
|     with pytest.raises(NotImplementedError): | ||||
|         instance.qr("test", center=True, native=True) | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -13,6 +13,7 @@ from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import pytest | ||||
| import mock | ||||
|  | ||||
| from escpos.printer import Dummy | ||||
| @@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function): | ||||
|     d.qr("LoremIpsum") | ||||
|     args, kwargs = img_function.call_args | ||||
|     assert isinstance(args[0], Image.Image) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def instance(): | ||||
|     return Dummy() | ||||
|  | ||||
|  | ||||
| def test_center(instance): | ||||
|     instance.qr("LoremIpsum", center=True) | ||||
|   | ||||
							
								
								
									
										280
									
								
								test/test_function_set.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,280 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| import escpos.printer as printer | ||||
| from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT | ||||
| from escpos.constants import TXT_SIZE | ||||
|  | ||||
|  | ||||
| # Default test, please copy and paste this block to test set method calls | ||||
|  | ||||
| def test_default_values(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set() | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
| # Size tests | ||||
|  | ||||
| def test_set_size_2h(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(double_height=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2h'],  # Double height text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_set_size_2w(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(double_width=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2w'],  # Double width text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_set_size_2x(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(double_height=True, double_width=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['2x'],  # Double text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_set_size_custom(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(custom_size=True, width=8, height=7) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_SIZE,  # Custom text size, no normal reset | ||||
|         six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]), | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
| # Flip | ||||
|  | ||||
| def test_set_flip(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(flip=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][True],  # Flip ON | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
| # Smooth | ||||
|  | ||||
| def test_smooth(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(smooth=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][True],  # Smooth ON | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| # Type | ||||
|  | ||||
|  | ||||
| def test_set_bold(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(bold=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][True],  # Bold ON | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_set_underline(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(underline=1) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][1],  # Underline ON, type 1 | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_set_underline2(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(underline=2) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][2],  # Underline ON, type 2 | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert (instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| # Align | ||||
|  | ||||
| def test_align_center(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(align='center') | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['center'],  # Align center | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| def test_align_right(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(align='right') | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['right'],  # Align right | ||||
|         TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| # Densities | ||||
|  | ||||
| def test_densities(): | ||||
|  | ||||
|     for density in range(8): | ||||
|         instance = printer.Dummy() | ||||
|         instance.set(density=density) | ||||
|  | ||||
|         expected_sequence = ( | ||||
|             TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|             TXT_STYLE['flip'][False],  # Flip OFF | ||||
|             TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|             TXT_STYLE['bold'][False],  # Bold OFF | ||||
|             TXT_STYLE['underline'][0],  # Underline OFF | ||||
|             SET_FONT(b'\x00'),  # Default font | ||||
|             TXT_STYLE['align']['left'],  # Align left | ||||
|             TXT_STYLE['density'][density],  # Custom density from 0 to 8 | ||||
|             TXT_STYLE['invert'][False]  # Inverted OFF | ||||
|         ) | ||||
|  | ||||
|         assert(instance.output == b''.join(expected_sequence)) | ||||
|  | ||||
|  | ||||
| # Invert | ||||
|  | ||||
| def test_invert(): | ||||
|     instance = printer.Dummy() | ||||
|     instance.set(invert=True) | ||||
|  | ||||
|     expected_sequence = ( | ||||
|         TXT_NORMAL, TXT_STYLE['size']['normal'],  # Normal text size | ||||
|         TXT_STYLE['flip'][False],  # Flip OFF | ||||
|         TXT_STYLE['smooth'][False],  # Smooth OFF | ||||
|         TXT_STYLE['bold'][False],  # Bold OFF | ||||
|         TXT_STYLE['underline'][0],  # Underline OFF | ||||
|         SET_FONT(b'\x00'),  # Default font | ||||
|         TXT_STYLE['align']['left'],  # Align left | ||||
|         TXT_STYLE['invert'][True]  # Inverted ON | ||||
|     ) | ||||
|  | ||||
|     assert(instance.output == b''.join(expected_sequence)) | ||||
							
								
								
									
										16
									
								
								test/test_function_softbarcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | ||||
| #!/usr/bin/python | ||||
| from __future__ import absolute_import | ||||
| from __future__ import division | ||||
| from __future__ import print_function | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import escpos.printer as printer | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| def test_soft_barcode(): | ||||
|     """just execute soft_barcode | ||||
|     """ | ||||
|     instance = printer.Dummy() | ||||
|     instance.soft_barcode("ean8", "1234") | ||||
|  | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -12,34 +12,54 @@ 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 | ||||
|  | ||||
| import filecmp | ||||
|  | ||||
| devfile = 'testfile' | ||||
| import pytest | ||||
| import mock | ||||
| from hypothesis import given, assume | ||||
| import hypothesis.strategies as st | ||||
| from escpos.printer import Dummy | ||||
|  | ||||
|  | ||||
| def setup_testfile(): | ||||
|     """create a testfile as devfile""" | ||||
|     fhandle = open(devfile, 'a') | ||||
|     try: | ||||
|         os.utime(devfile, None) | ||||
|     finally: | ||||
|         fhandle.close() | ||||
| def get_printer(): | ||||
|     return Dummy(magic_encode_args={'disabled': True, 'encoding': 'CP437'}) | ||||
|  | ||||
|  | ||||
| def teardown_testfile(): | ||||
|     """destroy testfile again""" | ||||
|     os.remove(devfile) | ||||
| @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) | ||||
|  | ||||
|  | ||||
| @with_setup(setup_testfile, teardown_testfile) | ||||
| def test_function_text_dies_ist_ein_test_lf(): | ||||
|     """test the text printing function with simple string and compare output""" | ||||
|     instance = printer.File(devfile=devfile) | ||||
|     instance.text('Dies ist ein Test.\n') | ||||
|     instance.flush() | ||||
|     assert(filecmp.cmp('test/Dies ist ein Test.LF.txt', devfile)) | ||||
| 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.' | ||||
|  | ||||
|  | ||||
| def test_textln(): | ||||
|     printer = get_printer() | ||||
|     printer.textln('hello, world') | ||||
|     assert printer.output == b'hello, world\n' | ||||
|  | ||||
|  | ||||
| def test_textln_empty(): | ||||
|     printer = get_printer() | ||||
|     printer.textln() | ||||
|     assert printer.output == b'\n' | ||||
|  | ||||
|  | ||||
| def test_ln(): | ||||
|     printer = get_printer() | ||||
|     printer.ln() | ||||
|     assert printer.output == b'\n' | ||||
|  | ||||
|  | ||||
| def test_multiple_ln(): | ||||
|     printer = get_printer() | ||||
|     printer.ln(3) | ||||
|     assert printer.output == b'\n\n\n' | ||||
|   | ||||
| @@ -5,7 +5,7 @@ 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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from escpos.image import EscposImage | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -12,30 +12,10 @@ 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 = printer.Dummy() | ||||
|     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' | ||||
| @@ -5,7 +5,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
| @@ -15,8 +15,8 @@ from __future__ import unicode_literals | ||||
|  | ||||
| import six | ||||
|  | ||||
| import mock | ||||
| from hypothesis import given | ||||
| import pytest | ||||
| from hypothesis import given, settings | ||||
| from hypothesis.strategies import text | ||||
|  | ||||
| import escpos.printer as printer | ||||
| @@ -26,22 +26,28 @@ if six.PY3: | ||||
| else: | ||||
|     mock_open_call = '__builtin__.open' | ||||
|  | ||||
|  | ||||
| @pytest.mark.skip("this test is broken and has to be fixed or discarded") | ||||
| @settings(use_coverage=False) | ||||
| @given(path=text()) | ||||
| @mock.patch(mock_open_call) | ||||
| @mock.patch('escpos.escpos.Escpos.__init__') | ||||
| def test_load_file_printer(mock_escpos, mock_open, path): | ||||
| 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") | ||||
|  | ||||
|  | ||||
| @pytest.mark.skip("this test is broken and has to be fixed or discarded") | ||||
| @settings(deadline=None, use_coverage=False) | ||||
| @given(txt=text()) | ||||
| @mock.patch.object(printer.File, 'device') | ||||
| @mock.patch(mock_open_call) | ||||
| @mock.patch('escpos.escpos.Escpos.__init__') | ||||
| def test_auto_flush(mock_escpos, mock_open, mock_device, txt): | ||||
| 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 | ||||
| @@ -55,11 +61,14 @@ def test_auto_flush(mock_escpos, mock_open, mock_device, txt): | ||||
|     assert mock_device.flush.called | ||||
|  | ||||
|  | ||||
| @pytest.mark.skip("this test is broken and has to be fixed or discarded") | ||||
| @settings(deadline=None, use_coverage=False) | ||||
| @given(txt=text()) | ||||
| @mock.patch.object(printer.File, 'device') | ||||
| @mock.patch(mock_open_call) | ||||
| def test_flush_on_close(mock_open, mock_device, txt): | ||||
| def test_flush_on_close(mocker, txt): | ||||
|     """test flush on close in file-printer""" | ||||
|     mock_open = mocker.patch(mock_open_call) | ||||
|     mock_device = mocker.patch.object(printer.File, 'device') | ||||
|  | ||||
|     p = printer.File(auto_flush=False) | ||||
|     # inject the mocked device-object | ||||
|     p.device = mock_device | ||||
|   | ||||
							
								
								
									
										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') | ||||
							
								
								
									
										32
									
								
								test/test_raise_arbitrary_error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | ||||
| #!/usr/bin/python | ||||
| """test the raising of errors with the error module | ||||
|  | ||||
| :author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ | ||||
| :organization: `python-escpos <https://github.com/python-escpos>`_ | ||||
| :copyright: Copyright (c) 2017 `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 escpos | ||||
| import escpos.exceptions | ||||
|  | ||||
|  | ||||
| def test_raise_error_wrongly(): | ||||
|     """raise error the wrong way | ||||
|  | ||||
|     should reproduce https://github.com/python-escpos/python-escpos/issues/257 | ||||
|     """ | ||||
|     with pytest.raises(AttributeError): | ||||
|         raise escpos.Error("This should raise an AttributeError.") | ||||
|  | ||||
|  | ||||
| def tests_raise_error(): | ||||
|     """raise error the right way""" | ||||
|     with pytest.raises(escpos.exceptions.Error): | ||||
|         raise escpos.exceptions.Error("This should raise an error.") | ||||
| @@ -4,7 +4,7 @@ | ||||
| :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: GNU GPL v3 | ||||
| :license: MIT | ||||
| """ | ||||
|  | ||||
| from __future__ import absolute_import | ||||
|   | ||||
							
								
								
									
										22
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						| @@ -1,17 +1,31 @@ | ||||
| [tox] | ||||
| envlist = py27, py34, py35, docs | ||||
| envlist = py27, py34, py35, py36, py37, docs, flake8 | ||||
|  | ||||
| [testenv] | ||||
| deps = nose | ||||
|        jaconv | ||||
|        coverage | ||||
|        scripttest | ||||
|        mock | ||||
|        hypothesis | ||||
| commands = nosetests --with-coverage --cover-erase --cover-branches | ||||
|        pytest!=3.2.0,!=3.3.0 | ||||
|        pytest-cov | ||||
|        pytest-mock | ||||
|        hypothesis!=3.56.9,<4 | ||||
|        viivakoodi | ||||
| commands = py.test --cov escpos | ||||
| passenv = ESCPOS_CAPABILITIES_PICKLE_DIR ESCPOS_CAPABILITIES_FILE CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_* | ||||
|  | ||||
| [testenv:docs] | ||||
| basepython = python | ||||
| changedir = doc | ||||
| deps = sphinx | ||||
| deps = sphinx>=1.5.1 | ||||
|        setuptools_scm | ||||
|        viivakoodi | ||||
| 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 | ||||
|   | ||||