Compare commits
	
		
			518 Commits
		
	
	
		
			v1.0.4
			...
			update-fla
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4d479337d3 | ||
|   | 87b33367c0 | ||
|   | 594ab83fc3 | ||
|   | 9b8e56cd59 | ||
|   | c0af9aaaf4 | ||
|   | 18e4c1b0ac | ||
|   | 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 | ||
|   | cbe38648f5 | ||
|   | f6ce7e45da | ||
|   | 3fd1a3de5d | ||
|   | 214aa0d363 | ||
|   | d43bcd187e | ||
|   | 6b069a4529 | ||
|   | cf41069829 | ||
|   | bde6eaa336 | ||
|   | 632a104219 | ||
|   | ae9b3785c2 | ||
|   | 8b5bc9cf8a | ||
|   | a8574ad9d7 | ||
|   | ed3077f00f | ||
|   | 07d47765aa | ||
|   | 854b75be30 | ||
|   | df0c874f6e | ||
|   | 5c3d7dab72 | ||
|   | 798893caee | ||
|   | e8d91a6735 | ||
|   | 996b3fd332 | ||
|   | a38c124bb1 | ||
|   | e44d89bd33 | ||
|   | 7312db4adb | ||
|   | 59dccd79da | ||
|   | 603b34cadb | ||
|   | 340a47d2f6 | ||
|   | dfe2cdbff8 | ||
|   | eea2a6f9c0 | ||
|   | 11452034a3 | ||
|   | d2e2ea88a6 | ||
|   | 2416303805 | ||
|   | 38f9835931 | ||
|   | 87a6647053 | ||
|   | 046a08896c | ||
|   | f0bdbc4322 | ||
|   | 13937ab0da | ||
|   | 0cfedb5706 | ||
|   | b0af9e9652 | ||
|   | 3546e0c4bb | ||
|   | 7c732ee615 | ||
|   | 3d98eb8b9c | ||
|   | 619d80a867 | ||
|   | d3f74ced5d | ||
|   | 0524b0576e | ||
|   | c92d4463ae | ||
|   | 6e74748773 | ||
|   | a2e188cecf | ||
|   | c1a6da9aaa | ||
|   | 2ecf73074c | ||
|   | 37baf5cd34 | ||
|   | 5c209dd557 | ||
|   | bef1a9cccf | ||
|   | 10977b06e7 | ||
|   | 042f945a09 | ||
|   | 2cf30c7f05 | ||
|   | 34d929806c | ||
|   | c5d34cc268 | ||
|   | fd3f1067fe | ||
|   | 80b714fdae | ||
|   | 457c62cc7f | ||
|   | a5cae3adb7 | ||
|   | 9f5eed0020 | ||
|   | 57dd60c13f | ||
|   | 0ec83387d5 | ||
|   | 36e0a52e2d | ||
|   | 214b4def14 | ||
|   | c13a0715e4 | ||
|   | 925d19447d | ||
|   | 5be81e4703 | ||
|   | ab39fd2b99 | ||
|   | 685a1f504c | ||
|   | 7529642788 | ||
|   | 0907b6aa8b | ||
|   | 87438f9efa | ||
|   | e8e91eba80 | ||
|   | 3e5509238e | ||
|   | 08051d48d0 | ||
|   | 2676a802bd | ||
|   | 6697922b74 | ||
|   | e814396bd8 | ||
|   | adf73f3790 | ||
|   | 1e490b6de8 | ||
|   | 910f2fbf2f | ||
|   | 16569067c2 | ||
|   | a0dc993f2f | ||
|   | 1b00477144 | ||
|   | ddaf126c20 | ||
|   | 6a2673d01d | ||
|   | f6ae109bb9 | ||
|   | 0a8057414c | ||
|   | a34e306ea8 | ||
|   | 2588406831 | ||
|   | b6ada13f9b | ||
|   | eadd9f7583 | ||
|   | 471222eda9 | ||
|   | c6cc28254e | ||
|   | f903af6730 | ||
|   | a0d8689141 | ||
|   | 44c79eaf11 | ||
|   | 4584e3138a | ||
|   | 6b445b3fb1 | ||
|   | ba03538c50 | ||
|   | b45afbb297 | ||
|   | 59afcf778f | ||
|   | 36debff72c | ||
|   | 2aa0878f54 | ||
|   | 7b18afec75 | ||
|   | f39c4227ec | ||
|   | 481285625b | ||
|   | 1ee657a750 | ||
|   | 0121aa0bfb | ||
|   | 7547bfddd2 | ||
|   | f6e0edc7c7 | ||
|   | df77b7dec3 | ||
|   | bf3012b882 | ||
|   | 68b3ec4d89 | ||
|   | a236ccebe9 | ||
|   | 1a1c032d6a | ||
|   | f5a7d681eb | ||
|   | abebf7eb99 | ||
|   | fca363119c | ||
|   | 8ed1441c4c | ||
|   | 062282bf47 | ||
|   | 3fe4589b8b | ||
|   | b9c9189ca7 | ||
|   | 5ecae9d585 | ||
|   | 39da32ca85 | ||
|   | e545999aa2 | ||
|   | 3017c14df2 | ||
|   | 3831665da4 | ||
|   | b6b30d7c82 | ||
|   | 39165fcb41 | ||
|   | fd6a0e4bda | ||
|   | 38b58eb39a | ||
|   | cabb2c930a | ||
|   | 0c3f273fa1 | ||
|   | 76f300ea18 | ||
|   | f504d2dc15 | ||
|   | 5a2ca10874 | ||
|   | 8101e1ec9f | ||
|   | 1a000d29fd | ||
|   | 28b82fb54f | ||
|   | ee223670bf | ||
|   | a445c4205a | ||
|   | 00ef7f129b | ||
|   | 8c186d912d | ||
|   | 14ae1a7d89 | ||
|   | ca3b4665a2 | ||
|   | 4f92247ed6 | ||
|   | 5ba751d89b | ||
|   | 8e44c5126e | ||
|   | 7a58109928 | ||
|   | 9b40c0860f | ||
|   | 3ec00ae16e | ||
|   | 7afd5e75d4 | ||
|   | 7d74dcac00 | ||
|   | cdf8f6be09 | ||
|   | d5e3d85c4b | ||
|   | 2bb9756d28 | ||
|   | 5dc676bea7 | ||
|   | c26c875b61 | ||
|   | 39e912bef4 | ||
|   | c7b36916e7 | ||
|   | 4548dd3830 | ||
|   | d5073626ae | ||
|   | 1a866f4d1f | ||
|   | ef31e58d26 | ||
|   | 01328db808 | ||
|   | 1adc66992d | ||
|   | 73be1f2c48 | ||
|   | 27352b071c | ||
|   | 87a73beb31 | ||
|   | 6096c15b80 | ||
|   | 133241e7e9 | ||
|   | eea3e76eed | ||
|   | 28be6a2041 | ||
|   | d523b4d342 | ||
|   | 99291abd10 | ||
|   | 81b3c1a63a | ||
|   | 323b205f70 | ||
|   | 2dc70677d7 | ||
|   | e355d2cbdf | ||
|   | 126e0e269a | ||
|   | 309866f8c9 | ||
|   | 8596148271 | ||
|   | 8b454c4765 | ||
|   | f76db4e0d4 | ||
|   | e43e95bcff | ||
|   | b2ff39b6b1 | ||
|   | 64e63b0180 | ||
|   | e49c35abc1 | ||
|   | f676782130 | ||
|   | 086b407b62 | ||
|   | 1a2b8f1df2 | ||
|   | 68641572f9 | ||
|   | 7fdccb7245 | ||
|   | fee650faba | ||
|   | 1009cf8988 | ||
|   | 916b09a84c | ||
|   | 80f6200915 | ||
|   | f354791285 | ||
|   | 205728f5be | ||
|   | 046cf1ff5b | ||
|   | 75252a3797 | ||
|   | 21992d7017 | ||
|   | 4c406c1775 | ||
|   | 5b5c2c0dba | ||
|   | 6b56de67a7 | ||
|   | c9ea90cd82 | ||
|   | 5d5412ba73 | ||
|   | 7005ba5899 | ||
|   | 18518fa901 | ||
|   | bda7f85346 | ||
|   | 70307d0f24 | ||
|   | 5a03f0fc7c | ||
|   | 4cfc4fd564 | ||
|   | d96dcd8e7e | ||
|   | 9d12c7faab | ||
|   | 39d1c1d587 | ||
|   | dbb0081287 | ||
|   | 68a9dcc47b | ||
|   | 7f921c667b | ||
|   | d888a39b6f | ||
|   | 250455ae23 | ||
|   | d9ffb03089 | ||
|   | 5c49e0103c | ||
|   | 60bc6b7d5c | ||
|   | cdf997aff5 | ||
|   | 8b8162c1a9 | ||
|   | 870144aac6 | ||
|   | e48755f7d0 | ||
|   | 8d00e63b87 | ||
|   | 7c98de6727 | ||
|   | 8000cf258b | ||
|   | b7c6edc9e1 | ||
|   | f303a38a8d | ||
|   | d672ca1268 | ||
|   | 3e200a86b9 | ||
|   | 95f5b5ed48 | ||
|   | 331fe6a93a | ||
|   | ae37de2577 | ||
|   | 720545979c | ||
|   | 0c56f5c831 | ||
|   | 248ddf8456 | ||
|   | 713380baf9 | ||
|   | cd2da59fc2 | ||
|   | d00fc5016b | ||
|   | 939e66834e | ||
|   | 198b50ac5e | ||
|   | 41c6afd3b8 | ||
|   | f25521f22f | ||
|   | 0e907644d9 | ||
|   | a6ec674828 | ||
|   | 598c893943 | ||
|   | 1614298863 | ||
|   | a921061b40 | ||
|   | 050419f117 | ||
|   | 3e89a10bed | ||
|   | e988873999 | ||
|   | f7a2caee72 | ||
|   | 4e1f9db5c7 | ||
|   | 9159aafd18 | ||
|   | 020ba4145c | ||
|   | 592dbe15f6 | ||
|   | 69680b04e9 | ||
|   | e88a19ef2d | ||
|   | c2b45748e1 | ||
|   | 8b8ca76af6 | ||
|   | e5cd37bfbc | ||
|   | 3d61445e2c | ||
|   | 8b8ab80e5f | ||
|   | 029549aaae | ||
|   | 6cc325b395 | ||
|   | a9844b1a1b | ||
|   | e16e666dce | ||
|   | 50fbf2873f | ||
|   | ce0b0d5ba3 | ||
|   | 2aafc34ae1 | ||
|   | c3fbdf28d4 | ||
|   | 8a77c963c3 | ||
|   | b68a5782fe | ||
|   | 06a68d1c97 | ||
|   | aa4ffdd21b | ||
|   | 05ea0e929e | ||
|   | 50b0691e68 | ||
|   | 06f2e1e731 | ||
|   | 577de10cb4 | ||
|   | 242399ebe2 | ||
|   | 8fd05eb02b | ||
|   | c1d985eeaf | ||
|   | 0dacc35d94 | ||
|   | 8b5798eedf | ||
|   | ef8035527c | ||
|   | 07d8e073ae | ||
|   | 096445631f | ||
|   | 3ea52e52fd | ||
|   | 371d5e78bd | ||
|   | 7e3b6ce586 | ||
|   | 4bb94c2662 | ||
|   | ef84a5150f | ||
|   | cd5969e843 | ||
|   | 1a1ed5e7fc | ||
|   | e23ce9bfc2 | ||
|   | 546f47edcc | ||
|   | 3f6528da07 | ||
|   | 5eaa6f26d0 | ||
|   | 37d7f34241 | ||
|   | 280000d6ed | ||
|   | 95082067e4 | ||
|   | f0f84e1215 | ||
|   | cae02976a3 | ||
|   | 25b650c935 | ||
|   | 6734864a5b | ||
|   | 9550ad1068 | ||
|   | 0ef2951c7e | ||
|   | e623799fd7 | ||
|   | 47aa4a96c9 | ||
|   | bb329b093b | ||
|   | dd228c9fda | ||
|   | 7da2e32e3c | ||
|   | b99c076bae | ||
|   | afc6834082 | ||
|   | d93e76e904 | ||
|   | f3933d5d20 | ||
|   | 517435efad | ||
|   | 122ff9a363 | ||
|   | 708f7e97d1 | ||
|   | 8a3850ea64 | ||
|   | 34f562d64a | 
							
								
								
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | [run] | ||||||
|  | branch = True | ||||||
							
								
								
									
										26
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | <!-- | ||||||
|  | Please feel free to delete any sections that aren't relevant. | ||||||
|  | --> | ||||||
|  |  | ||||||
|  | <!-- mark with x between the [ ] --> | ||||||
|  | I have: | ||||||
|  | - [ ] searched open and closed issues for duplicates | ||||||
|  |  | ||||||
|  | ### Bug description | ||||||
|  |  | ||||||
|  | ### Steps to reproduce | ||||||
|  | - add your steps here | ||||||
|  | - as a list | ||||||
|  | - using hyphens | ||||||
|  |  | ||||||
|  | ### Device info | ||||||
|  | <!-- Replace examples with your info --> | ||||||
|  |  **Printer:** Manufacturer Model XVI | ||||||
|  |  | ||||||
|  | <!-- since version 2.0.1 you can type 'python-escpos version' in your shell. | ||||||
|  | Alternatively you could use '__version__' in module escpos. --> | ||||||
|  |  **python-escpos version:** 0.0.0 | ||||||
|  |  | ||||||
|  |  **python version:** 0.0 | ||||||
|  |  | ||||||
|  |  **operating system:** | ||||||
							
								
								
									
										10
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | ### Contributor checklist | ||||||
|  | <!-- mark with x between the brackets --> | ||||||
|  | - [ ] I have read the CONTRIBUTING.rst | ||||||
|  | - [ ] I have tested my contribution on these devices: | ||||||
|  |  * e.g. Epson TM-T88II | ||||||
|  | - [ ] My contribution is ready to be merged as is | ||||||
|  |  | ||||||
|  | ---------- | ||||||
|  |  | ||||||
|  | ### Description | ||||||
							
								
								
									
										31
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | # python temporary files | ||||||
|  | *.pyc | ||||||
|  |  | ||||||
|  | # editor autosaves, data and file browser files | ||||||
|  | $~ | ||||||
|  | .idea/ | ||||||
|  | .directory | ||||||
|  | .cache/ | ||||||
|  |  | ||||||
|  | # temporary data | ||||||
|  | temp | ||||||
|  |  | ||||||
|  | # packaging and testing | ||||||
|  | .tox/ | ||||||
|  | *.egg-info/ | ||||||
|  | .eggs/ | ||||||
|  | *.egg | ||||||
|  | build/ | ||||||
|  | dist/ | ||||||
|  | .coverage | ||||||
|  | src/escpos/version.py | ||||||
|  | .hypothesis | ||||||
|  | .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 | ||||||
							
								
								
									
										10
									
								
								.hgignore
									
									
									
									
									
								
							
							
						
						| @@ -1,10 +0,0 @@ | |||||||
| # python temporary files |  | ||||||
| syntax: glob |  | ||||||
| *.pyc |  | ||||||
|  |  | ||||||
| # editor autosaves |  | ||||||
| $~ |  | ||||||
|  |  | ||||||
| # temporary data |  | ||||||
| syntax: regexp |  | ||||||
| temp |  | ||||||
							
								
								
									
										14
									
								
								.mailmap
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | <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> | ||||||
							
								
								
									
										66
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | |||||||
|  | language: python | ||||||
|  | sudo: false | ||||||
|  | cache: pip | ||||||
|  | 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: | ||||||
|  |   include: | ||||||
|  |     - python: 2.7 | ||||||
|  |       env: TOXENV=py27 | ||||||
|  |     - python: 3.4 | ||||||
|  |       env: TOXENV=py34 | ||||||
|  |     - python: 3.5 | ||||||
|  |       env: TOXENV=py35 | ||||||
|  |     - python: 3.6 | ||||||
|  |       env: TOXENV=py36 | ||||||
|  |     - python: 3.6-dev | ||||||
|  |       env: TOXENV=py36 | ||||||
|  |     - python: 3.7-dev | ||||||
|  |       env: TOXENV=py37 | ||||||
|  |     - python: nightly | ||||||
|  |       env: TOXENV=py37 | ||||||
|  |     - python: pypy | ||||||
|  |       env: TOXENV=pypy | ||||||
|  |     - python: pypy3 | ||||||
|  |       env: TOXENV=pypy3 | ||||||
|  |     - python: 2.7 | ||||||
|  |       env: TOXENV=docs | ||||||
|  |     - python: 2.7 | ||||||
|  |       env: TOXENV=flake8 | ||||||
|  |     - python: 3.6 | ||||||
|  |       env: TOXENV=flake8 | ||||||
|  |   allow_failures: | ||||||
|  |     - python: 3.6-dev | ||||||
|  |     - python: 3.7-dev | ||||||
|  |     - python: nightly | ||||||
|  |     - python: pypy3 | ||||||
|  | before_install: | ||||||
|  |     - pip install tox codecov 'sphinx>=1.5.1' | ||||||
|  |     - ./doc/generate_authors.sh --check | ||||||
|  | script: | ||||||
|  |     - tox | ||||||
|  |     - codecov | ||||||
|  | notifications: | ||||||
|  |   email: | ||||||
|  |     on_success: never | ||||||
|  |     on_failure: change | ||||||
|  | deploy: | ||||||
|  | # Github deployment | ||||||
|  |   - provider: releases | ||||||
|  |     api_key: | ||||||
|  |       secure: oiR3r5AIx9ENIRtbUKIxorRx8GMv4BxgVIZcieXbgSTN4DBZdRWdzs1Xxngu/90Xf79G0X+XGxZyXrYN7eFFNp0kUYj8kwZ1aS/dyR88scskumERWi1Hv5WUJrYGrDe7PcjNGsJ2jw0nNnRPKG87Y84aR4lQygyGBSlDcdrOBnBv0sHYJMxRvHSRkGgWpur06QIOGOk4oOipTXR/7E9cg3YQC5nvZAf2QiprwTa8IcOSFlZQPykEVRYSiAgXrgqBYcZzpX0hAGuIBv7DmPI2ORTF+t79Wbhxhnho3gGJleDv7Z96//sf1vQNCG6qOgeIc9ZY08Jm1AwXQoW0p6F1/XcEPxeyPDkXJzlojE9rjYNLCPL4gxb/LESEuUafm0U4JGMsZ6hnsBOw583yTuAdfQuJ9M+QaSyem6OVNkky3+DKAD3z0WJnl9jmGXIXigNSIxD25XhpvY+j9P0XTLBG1GT2Q+wXCIjSYJc2XnYcdgVJcLoxSWk1fKj/KPi7buAWtqwnL3tjeldpMMOZMliPUTWMM14zoGskHztt0JCkAtcotm9AQtvL8eZ2LHLDK/jyLzjv0wAwU5vzSVp14XHLZl7Q0AIoNc20p1EYGa9C/gSPd9CkrWZoG4lMOiAu3tp2PRLVrdXH3ZWSPQq4Ek5MczrUTkmB82XErNbOa8QB1Dw= | ||||||
|  |     file: .tox/dist/python-escpos*.zip | ||||||
|  |     file_glob: true | ||||||
|  |     skip_cleanup: true | ||||||
|  |     on: | ||||||
|  |       tags: true | ||||||
|  |       repo: python-escpos/python-escpos | ||||||
|  |       branch: master | ||||||
|  |       condition: $TRAVIS_PYTHON_VERSION = "3.6" | ||||||
							
								
								
									
										33
									
								
								AUTHORS
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | Ahmed Tahri | ||||||
|  | Asuki Kono | ||||||
|  | belono | ||||||
|  | Christoph Heuel | ||||||
|  | Cody (Quantified Code Bot) | ||||||
|  | csoft2k | ||||||
|  | Curtis // mashedkeyboard | ||||||
|  | Davis Goglin | ||||||
|  | Dean Rispin | ||||||
|  | Dmytro Katyukha | ||||||
|  | Hark | ||||||
|  | Joel Lehtonen | ||||||
|  | kennedy | ||||||
|  | Kristi | ||||||
|  | ldos | ||||||
|  | Lucy Linder | ||||||
|  | Manuel F Martinez | ||||||
|  | Michael Billington | ||||||
|  | Michael Elsdörfer | ||||||
|  | mrwunderbar666 | ||||||
|  | Nathan Bookham | ||||||
|  | Patrick Kanzler | ||||||
|  | primax79 | ||||||
|  | Qian Linfeng | ||||||
|  | reck31 | ||||||
|  | Renato Lorenzi | ||||||
|  | Romain Porte | ||||||
|  | Sam Cheng | ||||||
|  | Sergio Pulgarin | ||||||
|  | Stephan Sokolow | ||||||
|  | Thijs Triemstra | ||||||
|  | Thomas van den Berg | ||||||
|  | ysuolmai | ||||||
							
								
								
									
										22
									
								
								CHANGELOG
									
									
									
									
									
								
							
							
						
						| @@ -1,22 +0,0 @@ | |||||||
| CHANGELOG |  | ||||||
|  |  | ||||||
| * 2012-11-15 - Version 1.0 |  | ||||||
| - Issue #2: Added ethernet support |  | ||||||
| - Issue #3: Added compatibility with libusb-1.0.1 |  | ||||||
| - Issue #4: Fixed typo in escpos.py |  | ||||||
|  |  | ||||||
| * 2013-03-14 - Version 1.0.1 |  | ||||||
| - Issue #8: Fixed set font |  | ||||||
| - Added QR support |  | ||||||
|  |  | ||||||
| * 2013-12-30 - Version 1.0.2 |  | ||||||
| - Issue #5: Fixed vertical tab |  | ||||||
| - Issue #9: Fixed identation inconsistence |  | ||||||
|  |  | ||||||
| * 2014-02-23 - Version 1.0.3 |  | ||||||
| - Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com) |  | ||||||
| - Added exception for PIL import |  | ||||||
|  |  | ||||||
| * 2014-05-20 - Version 1.0.4 |  | ||||||
| - Added charcode tables |  | ||||||
| - Fixed Horizontal Tab |  | ||||||
							
								
								
									
										291
									
								
								CHANGELOG.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,291 @@ | |||||||
|  | ********* | ||||||
|  | Changelog | ||||||
|  | ********* | ||||||
|  | 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" | ||||||
|  | ------------------------------------------------------ | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - fix improper API-use in qrcode() | ||||||
|  | - change setup.py shebang to make it compatible with virtualenvs. | ||||||
|  | - add constants for sheet mode and colors | ||||||
|  | - support changing the linespacing | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Michael Elsdörfer | ||||||
|  | - Patrick Kanzler | ||||||
|  |  | ||||||
|  | 2016-08-10 - Version 2.1.3 - "Ethics Gradient" | ||||||
|  | ---------------------------------------------- | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - configure readthedocs and travis | ||||||
|  | - update doc with hint on image preprocessing | ||||||
|  | - add fix for printing large images (by splitting them into multiple images) | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Patrick Kanzler | ||||||
|  |  | ||||||
|  | 2016-08-02 - Version 2.1.2 - "Death and Gravity" | ||||||
|  | ------------------------------------------------ | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - fix File-printer: flush after every call of _raw() | ||||||
|  | - fix lists in documentation | ||||||
|  | - fix CODE128: by adding the control character to the barcode-selection-sequence the barcode became unusable | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Patrick Kanzler | ||||||
|  |  | ||||||
|  | 2016-08-02 - Version 2.1.1 - "Contents May Differ" | ||||||
|  | -------------------------------------------------- | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - rename variable interface in USB-class to timeout | ||||||
|  | - add support for hypothesis and move pypy3 to the allowed failures (pypy3 is not supported by hypothesis) | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Patrick Kanzler | ||||||
|  | - Renato Lorenzi | ||||||
|  |  | ||||||
|  | 2016-07-23 - Version 2.1.0 - "But Who's Counting?" | ||||||
|  | -------------------------------------------------- | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - packaging: configured the coverage-analysis codecov.io | ||||||
|  | - GitHub: improved issues-template | ||||||
|  | - documentation: add troubleshooting tip to network-interface | ||||||
|  | - the module, cli and documentation is now aware of the version of python-escpos | ||||||
|  | - the cli does now support basic tabcompletion | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Patrick Kanzler | ||||||
|  |  | ||||||
|  | 2016-06-24 - Version 2.0.0 - "Attitude Adjuster" | ||||||
|  | ------------------------------------------------ | ||||||
|  |  | ||||||
|  | This version is based on the original version of python-escpos by Manuel F Martinez. However, many contributions have | ||||||
|  | greatly improved the old codebase. Since this version does not completely match the interface of the version published | ||||||
|  | on PyPi and has many improvements, it will be released as version 2.0.0. | ||||||
|  |  | ||||||
|  | changes | ||||||
|  | ^^^^^^^ | ||||||
|  | - refactor complete code in order to be compatible with Python 2 and 3 | ||||||
|  | - modernize packaging | ||||||
|  | - add testing and CI | ||||||
|  | - merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cashdraw and structure | ||||||
|  | - improve the documentation | ||||||
|  | - extend support of barcode-codes to type B | ||||||
|  | - add function to disable panel-buttons | ||||||
|  | - the text-functions are now intended for unicode, the driver will automatically encode the string based on the selected | ||||||
|  |   codepage | ||||||
|  | - the image-functions are now much more flexible | ||||||
|  | - added a CLI | ||||||
|  | - restructured the constants | ||||||
|  |  | ||||||
|  | contributors | ||||||
|  | ^^^^^^^^^^^^ | ||||||
|  | - Thomas van den Berg | ||||||
|  | - Michael Billington | ||||||
|  | - Nate Bookham | ||||||
|  | - Davis Goglin | ||||||
|  | - Christoph Heuel | ||||||
|  | - Patrick Kanzler | ||||||
|  | - Qian LinFeng | ||||||
|  |  | ||||||
|  | 2016-01-24 - Version 1.0.9 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - fix constant definition for PC1252 | ||||||
|  | - move documentation to Sphinx | ||||||
|  |  | ||||||
|  | 2015-10-27 - Version 1.0.8 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Merge pull request #59 from zouppen/master | ||||||
|  |     - Support for images vertically longer than 256 pixels | ||||||
|  |     - Sent by Joel Lehtonen <joel.lehtonen@koodilehto.fi> | ||||||
|  | - Updated README | ||||||
|  |  | ||||||
|  | 2015-08-22 - Version 1.0.7 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Issue #57: Fixed transparent images | ||||||
|  |  | ||||||
|  | 2015-07-06 - Version 1.0.6 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Merge pull request #53 from ldos/master | ||||||
|  |     - Extended params for serial printers | ||||||
|  |     - Sent by ldos <cafeteria.ldosalzira@gmail.com> | ||||||
|  |  | ||||||
|  | 2015-04-21 - Version 1.0.5 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Merge pull request #45 from Krispy2009/master | ||||||
|  |     - Raising the right error when wrong charcode is used | ||||||
|  |     - Sent by Kristi <Krispy2009@gmail.com> | ||||||
|  |  | ||||||
|  | 2014-05-20 - Version 1.0.4 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Issue #20: Added Density support (Sent by thomas.erbacher@ragapack.de) | ||||||
|  | - Added charcode tables | ||||||
|  | - Fixed Horizontal Tab | ||||||
|  | - Fixed code tabulators | ||||||
|  |  | ||||||
|  | 2014-02-23 - Version 1.0.3 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com) | ||||||
|  | - Added exception for PIL import | ||||||
|  |  | ||||||
|  | 2013-12-30 - Version 1.0.2 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Issue #5: Fixed vertical tab | ||||||
|  | - Issue #9: Fixed identation inconsistence | ||||||
|  |  | ||||||
|  | 2013-03-14 - Version 1.0.1 | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | - Issue #8: Fixed set font | ||||||
|  | - Added QR support | ||||||
|  |  | ||||||
|  | 2012-11-15 - Version 1.0 | ||||||
|  | ------------------------ | ||||||
|  |  | ||||||
|  | - Issue #2: Added ethernet support | ||||||
|  | - Issue #3: Added compatibility with libusb-1.0.1 | ||||||
|  | - Issue #4: Fixed typo in escpos.py | ||||||
							
								
								
									
										91
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,91 @@ | |||||||
|  | ************ | ||||||
|  | Contributing | ||||||
|  | ************ | ||||||
|  |  | ||||||
|  | This project is open to any kind of contribution. You can help with improving the documentation, adding fixes to the | ||||||
|  | code, providing test cases in code or as a description or just spreading the word. Please feel free to create an | ||||||
|  | issue or pull request. | ||||||
|  | In order to reduce the amount of work for everyone please try to adhere to good practice. | ||||||
|  |  | ||||||
|  | The pull requests and issues will be prefilled with templates. Please fill in your information where applicable. | ||||||
|  |  | ||||||
|  | This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as | ||||||
|  | well as possible. | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | ----------- | ||||||
|  |  | ||||||
|  | When writing code please try to stick to these rules. | ||||||
|  |  | ||||||
|  | Python 2 and 3 | ||||||
|  | ^^^^^^^^^^^^^^ | ||||||
|  | We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3. | ||||||
|  | In order to ensure that we do not miss any accidental degradation, please add these imports to the top | ||||||
|  | of every file of code: | ||||||
|  |  | ||||||
|  | .. code-block:: Python | ||||||
|  |  | ||||||
|  |   from __future__ import absolute_import | ||||||
|  |   from __future__ import division | ||||||
|  |   from __future__ import print_function | ||||||
|  |   from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | Furthermore please be aware of the differences between Python 2 and 3. For | ||||||
|  | example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful. | ||||||
|  | Special care has to be taken when dealing with strings and byte-strings. Please note | ||||||
|  | that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings. | ||||||
|  | Often you can achieve compatibility quite easily with a tool from the `six`-package. | ||||||
|  |  | ||||||
|  | PEP8 | ||||||
|  | ^^^^ | ||||||
|  | The entire codebase adheres to the rules of PEP8. | ||||||
|  | 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 the development-branch. | ||||||
|  |  | ||||||
|  | When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`. | ||||||
|  | Name your branch to indicate what you are trying to achieve. Good branch names might | ||||||
|  | be `improve/text-handling`, `feature/enable-color-printing`. | ||||||
|  |  | ||||||
|  | Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a | ||||||
|  | git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is | ||||||
|  | easier to review, and improves the chance of your code being merged. | ||||||
|  | Please also make sure that before creating your PR, your branch is rebased on a recent commit or you merged a recent | ||||||
|  | commit into your branch. This way you can ensure that your PR is without merge conflicts. | ||||||
|  |  | ||||||
|  | Docstrings | ||||||
|  | ^^^^^^^^^^ | ||||||
|  | This project tries to have a good documentation. | ||||||
|  | Please add a docstring to every method and class. Have a look at existing methods and classes for the style. | ||||||
|  | We use basically standard rst-docstrings for Sphinx. | ||||||
|  |  | ||||||
|  | Test | ||||||
|  | ^^^^ | ||||||
|  | Try to write tests whenever possible. Our goal for the future is 100% coverage. | ||||||
|  | We are currently using `nose` but might change in the future. | ||||||
|  | You can copy the structure from other testcases. Please remember to adapt the docstrings. | ||||||
|  |  | ||||||
|  | Further reading | ||||||
|  | ^^^^^^^^^^^^^^^ | ||||||
|  | For further best practices and hints on contributing please see the | ||||||
|  | `contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide | ||||||
|  | and the linked one, please stick to this text. | ||||||
|  | Aside from that feel free to create an issue or write an email if anything is unclear. | ||||||
|  |  | ||||||
|  | Thank you for your contribution! | ||||||
							
								
								
									
										674
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						| @@ -1,674 +0,0 @@ | |||||||
|                     GNU GENERAL PUBLIC LICENSE |  | ||||||
|                        Version 3, 29 June 2007 |  | ||||||
|  |  | ||||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |  | ||||||
|  Everyone is permitted to copy and distribute verbatim copies |  | ||||||
|  of this license document, but changing it is not allowed. |  | ||||||
|  |  | ||||||
|                             Preamble |  | ||||||
|  |  | ||||||
|   The GNU General Public License is a free, copyleft license for |  | ||||||
| software and other kinds of works. |  | ||||||
|  |  | ||||||
|   The licenses for most software and other practical works are designed |  | ||||||
| to take away your freedom to share and change the works.  By contrast, |  | ||||||
| the GNU General Public License is intended to guarantee your freedom to |  | ||||||
| share and change all versions of a program--to make sure it remains free |  | ||||||
| software for all its users.  We, the Free Software Foundation, use the |  | ||||||
| GNU General Public License for most of our software; it applies also to |  | ||||||
| any other work released this way by its authors.  You can apply it to |  | ||||||
| your programs, too. |  | ||||||
|  |  | ||||||
|   When we speak of free software, we are referring to freedom, not |  | ||||||
| price.  Our General Public Licenses are designed to make sure that you |  | ||||||
| have the freedom to distribute copies of free software (and charge for |  | ||||||
| them if you wish), that you receive source code or can get it if you |  | ||||||
| want it, that you can change the software or use pieces of it in new |  | ||||||
| free programs, and that you know you can do these things. |  | ||||||
|  |  | ||||||
|   To protect your rights, we need to prevent others from denying you |  | ||||||
| these rights or asking you to surrender the rights.  Therefore, you have |  | ||||||
| certain responsibilities if you distribute copies of the software, or if |  | ||||||
| you modify it: responsibilities to respect the freedom of others. |  | ||||||
|  |  | ||||||
|   For example, if you distribute copies of such a program, whether |  | ||||||
| gratis or for a fee, you must pass on to the recipients the same |  | ||||||
| freedoms that you received.  You must make sure that they, too, receive |  | ||||||
| or can get the source code.  And you must show them these terms so they |  | ||||||
| know their rights. |  | ||||||
|  |  | ||||||
|   Developers that use the GNU GPL protect your rights with two steps: |  | ||||||
| (1) assert copyright on the software, and (2) offer you this License |  | ||||||
| giving you legal permission to copy, distribute and/or modify it. |  | ||||||
|  |  | ||||||
|   For the developers' and authors' protection, the GPL clearly explains |  | ||||||
| that there is no warranty for this free software.  For both users' and |  | ||||||
| authors' sake, the GPL requires that modified versions be marked as |  | ||||||
| changed, so that their problems will not be attributed erroneously to |  | ||||||
| authors of previous versions. |  | ||||||
|  |  | ||||||
|   Some devices are designed to deny users access to install or run |  | ||||||
| modified versions of the software inside them, although the manufacturer |  | ||||||
| can do so.  This is fundamentally incompatible with the aim of |  | ||||||
| protecting users' freedom to change the software.  The systematic |  | ||||||
| pattern of such abuse occurs in the area of products for individuals to |  | ||||||
| use, which is precisely where it is most unacceptable.  Therefore, we |  | ||||||
| have designed this version of the GPL to prohibit the practice for those |  | ||||||
| products.  If such problems arise substantially in other domains, we |  | ||||||
| stand ready to extend this provision to those domains in future versions |  | ||||||
| of the GPL, as needed to protect the freedom of users. |  | ||||||
|  |  | ||||||
|   Finally, every program is threatened constantly by software patents. |  | ||||||
| States should not allow patents to restrict development and use of |  | ||||||
| software on general-purpose computers, but in those that do, we wish to |  | ||||||
| avoid the special danger that patents applied to a free program could |  | ||||||
| make it effectively proprietary.  To prevent this, the GPL assures that |  | ||||||
| patents cannot be used to render the program non-free. |  | ||||||
|  |  | ||||||
|   The precise terms and conditions for copying, distribution and |  | ||||||
| modification follow. |  | ||||||
|  |  | ||||||
|                        TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|   0. Definitions. |  | ||||||
|  |  | ||||||
|   "This License" refers to version 3 of the GNU General Public License. |  | ||||||
|  |  | ||||||
|   "Copyright" also means copyright-like laws that apply to other kinds of |  | ||||||
| works, such as semiconductor masks. |  | ||||||
|  |  | ||||||
|   "The Program" refers to any copyrightable work licensed under this |  | ||||||
| License.  Each licensee is addressed as "you".  "Licensees" and |  | ||||||
| "recipients" may be individuals or organizations. |  | ||||||
|  |  | ||||||
|   To "modify" a work means to copy from or adapt all or part of the work |  | ||||||
| in a fashion requiring copyright permission, other than the making of an |  | ||||||
| exact copy.  The resulting work is called a "modified version" of the |  | ||||||
| earlier work or a work "based on" the earlier work. |  | ||||||
|  |  | ||||||
|   A "covered work" means either the unmodified Program or a work based |  | ||||||
| on the Program. |  | ||||||
|  |  | ||||||
|   To "propagate" a work means to do anything with it that, without |  | ||||||
| permission, would make you directly or secondarily liable for |  | ||||||
| infringement under applicable copyright law, except executing it on a |  | ||||||
| computer or modifying a private copy.  Propagation includes copying, |  | ||||||
| distribution (with or without modification), making available to the |  | ||||||
| public, and in some countries other activities as well. |  | ||||||
|  |  | ||||||
|   To "convey" a work means any kind of propagation that enables other |  | ||||||
| parties to make or receive copies.  Mere interaction with a user through |  | ||||||
| a computer network, with no transfer of a copy, is not conveying. |  | ||||||
|  |  | ||||||
|   An interactive user interface displays "Appropriate Legal Notices" |  | ||||||
| to the extent that it includes a convenient and prominently visible |  | ||||||
| feature that (1) displays an appropriate copyright notice, and (2) |  | ||||||
| tells the user that there is no warranty for the work (except to the |  | ||||||
| extent that warranties are provided), that licensees may convey the |  | ||||||
| work under this License, and how to view a copy of this License.  If |  | ||||||
| the interface presents a list of user commands or options, such as a |  | ||||||
| menu, a prominent item in the list meets this criterion. |  | ||||||
|  |  | ||||||
|   1. Source Code. |  | ||||||
|  |  | ||||||
|   The "source code" for a work means the preferred form of the work |  | ||||||
| for making modifications to it.  "Object code" means any non-source |  | ||||||
| form of a work. |  | ||||||
|  |  | ||||||
|   A "Standard Interface" means an interface that either is an official |  | ||||||
| standard defined by a recognized standards body, or, in the case of |  | ||||||
| interfaces specified for a particular programming language, one that |  | ||||||
| is widely used among developers working in that language. |  | ||||||
|  |  | ||||||
|   The "System Libraries" of an executable work include anything, other |  | ||||||
| than the work as a whole, that (a) is included in the normal form of |  | ||||||
| packaging a Major Component, but which is not part of that Major |  | ||||||
| Component, and (b) serves only to enable use of the work with that |  | ||||||
| Major Component, or to implement a Standard Interface for which an |  | ||||||
| implementation is available to the public in source code form.  A |  | ||||||
| "Major Component", in this context, means a major essential component |  | ||||||
| (kernel, window system, and so on) of the specific operating system |  | ||||||
| (if any) on which the executable work runs, or a compiler used to |  | ||||||
| produce the work, or an object code interpreter used to run it. |  | ||||||
|  |  | ||||||
|   The "Corresponding Source" for a work in object code form means all |  | ||||||
| the source code needed to generate, install, and (for an executable |  | ||||||
| work) run the object code and to modify the work, including scripts to |  | ||||||
| control those activities.  However, it does not include the work's |  | ||||||
| System Libraries, or general-purpose tools or generally available free |  | ||||||
| programs which are used unmodified in performing those activities but |  | ||||||
| which are not part of the work.  For example, Corresponding Source |  | ||||||
| includes interface definition files associated with source files for |  | ||||||
| the work, and the source code for shared libraries and dynamically |  | ||||||
| linked subprograms that the work is specifically designed to require, |  | ||||||
| such as by intimate data communication or control flow between those |  | ||||||
| subprograms and other parts of the work. |  | ||||||
|  |  | ||||||
|   The Corresponding Source need not include anything that users |  | ||||||
| can regenerate automatically from other parts of the Corresponding |  | ||||||
| Source. |  | ||||||
|  |  | ||||||
|   The Corresponding Source for a work in source code form is that |  | ||||||
| same work. |  | ||||||
|  |  | ||||||
|   2. Basic Permissions. |  | ||||||
|  |  | ||||||
|   All rights granted under this License are granted for the term of |  | ||||||
| copyright on the Program, and are irrevocable provided the stated |  | ||||||
| conditions are met.  This License explicitly affirms your unlimited |  | ||||||
| permission to run the unmodified Program.  The output from running a |  | ||||||
| covered work is covered by this License only if the output, given its |  | ||||||
| content, constitutes a covered work.  This License acknowledges your |  | ||||||
| rights of fair use or other equivalent, as provided by copyright law. |  | ||||||
|  |  | ||||||
|   You may make, run and propagate covered works that you do not |  | ||||||
| convey, without conditions so long as your license otherwise remains |  | ||||||
| in force.  You may convey covered works to others for the sole purpose |  | ||||||
| of having them make modifications exclusively for you, or provide you |  | ||||||
| with facilities for running those works, provided that you comply with |  | ||||||
| the terms of this License in conveying all material for which you do |  | ||||||
| not control copyright.  Those thus making or running the covered works |  | ||||||
| for you must do so exclusively on your behalf, under your direction |  | ||||||
| and control, on terms that prohibit them from making any copies of |  | ||||||
| your copyrighted material outside their relationship with you. |  | ||||||
|  |  | ||||||
|   Conveying under any other circumstances is permitted solely under |  | ||||||
| the conditions stated below.  Sublicensing is not allowed; section 10 |  | ||||||
| makes it unnecessary. |  | ||||||
|  |  | ||||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. |  | ||||||
|  |  | ||||||
|   No covered work shall be deemed part of an effective technological |  | ||||||
| measure under any applicable law fulfilling obligations under article |  | ||||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or |  | ||||||
| similar laws prohibiting or restricting circumvention of such |  | ||||||
| measures. |  | ||||||
|  |  | ||||||
|   When you convey a covered work, you waive any legal power to forbid |  | ||||||
| circumvention of technological measures to the extent such circumvention |  | ||||||
| is effected by exercising rights under this License with respect to |  | ||||||
| the covered work, and you disclaim any intention to limit operation or |  | ||||||
| modification of the work as a means of enforcing, against the work's |  | ||||||
| users, your or third parties' legal rights to forbid circumvention of |  | ||||||
| technological measures. |  | ||||||
|  |  | ||||||
|   4. Conveying Verbatim Copies. |  | ||||||
|  |  | ||||||
|   You may convey verbatim copies of the Program's source code as you |  | ||||||
| receive it, in any medium, provided that you conspicuously and |  | ||||||
| appropriately publish on each copy an appropriate copyright notice; |  | ||||||
| keep intact all notices stating that this License and any |  | ||||||
| non-permissive terms added in accord with section 7 apply to the code; |  | ||||||
| keep intact all notices of the absence of any warranty; and give all |  | ||||||
| recipients a copy of this License along with the Program. |  | ||||||
|  |  | ||||||
|   You may charge any price or no price for each copy that you convey, |  | ||||||
| and you may offer support or warranty protection for a fee. |  | ||||||
|  |  | ||||||
|   5. Conveying Modified Source Versions. |  | ||||||
|  |  | ||||||
|   You may convey a work based on the Program, or the modifications to |  | ||||||
| produce it from the Program, in the form of source code under the |  | ||||||
| terms of section 4, provided that you also meet all of these conditions: |  | ||||||
|  |  | ||||||
|     a) The work must carry prominent notices stating that you modified |  | ||||||
|     it, and giving a relevant date. |  | ||||||
|  |  | ||||||
|     b) The work must carry prominent notices stating that it is |  | ||||||
|     released under this License and any conditions added under section |  | ||||||
|     7.  This requirement modifies the requirement in section 4 to |  | ||||||
|     "keep intact all notices". |  | ||||||
|  |  | ||||||
|     c) You must license the entire work, as a whole, under this |  | ||||||
|     License to anyone who comes into possession of a copy.  This |  | ||||||
|     License will therefore apply, along with any applicable section 7 |  | ||||||
|     additional terms, to the whole of the work, and all its parts, |  | ||||||
|     regardless of how they are packaged.  This License gives no |  | ||||||
|     permission to license the work in any other way, but it does not |  | ||||||
|     invalidate such permission if you have separately received it. |  | ||||||
|  |  | ||||||
|     d) If the work has interactive user interfaces, each must display |  | ||||||
|     Appropriate Legal Notices; however, if the Program has interactive |  | ||||||
|     interfaces that do not display Appropriate Legal Notices, your |  | ||||||
|     work need not make them do so. |  | ||||||
|  |  | ||||||
|   A compilation of a covered work with other separate and independent |  | ||||||
| works, which are not by their nature extensions of the covered work, |  | ||||||
| and which are not combined with it such as to form a larger program, |  | ||||||
| in or on a volume of a storage or distribution medium, is called an |  | ||||||
| "aggregate" if the compilation and its resulting copyright are not |  | ||||||
| used to limit the access or legal rights of the compilation's users |  | ||||||
| beyond what the individual works permit.  Inclusion of a covered work |  | ||||||
| in an aggregate does not cause this License to apply to the other |  | ||||||
| parts of the aggregate. |  | ||||||
|  |  | ||||||
|   6. Conveying Non-Source Forms. |  | ||||||
|  |  | ||||||
|   You may convey a covered work in object code form under the terms |  | ||||||
| of sections 4 and 5, provided that you also convey the |  | ||||||
| machine-readable Corresponding Source under the terms of this License, |  | ||||||
| in one of these ways: |  | ||||||
|  |  | ||||||
|     a) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by the |  | ||||||
|     Corresponding Source fixed on a durable physical medium |  | ||||||
|     customarily used for software interchange. |  | ||||||
|  |  | ||||||
|     b) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by a |  | ||||||
|     written offer, valid for at least three years and valid for as |  | ||||||
|     long as you offer spare parts or customer support for that product |  | ||||||
|     model, to give anyone who possesses the object code either (1) a |  | ||||||
|     copy of the Corresponding Source for all the software in the |  | ||||||
|     product that is covered by this License, on a durable physical |  | ||||||
|     medium customarily used for software interchange, for a price no |  | ||||||
|     more than your reasonable cost of physically performing this |  | ||||||
|     conveying of source, or (2) access to copy the |  | ||||||
|     Corresponding Source from a network server at no charge. |  | ||||||
|  |  | ||||||
|     c) Convey individual copies of the object code with a copy of the |  | ||||||
|     written offer to provide the Corresponding Source.  This |  | ||||||
|     alternative is allowed only occasionally and noncommercially, and |  | ||||||
|     only if you received the object code with such an offer, in accord |  | ||||||
|     with subsection 6b. |  | ||||||
|  |  | ||||||
|     d) Convey the object code by offering access from a designated |  | ||||||
|     place (gratis or for a charge), and offer equivalent access to the |  | ||||||
|     Corresponding Source in the same way through the same place at no |  | ||||||
|     further charge.  You need not require recipients to copy the |  | ||||||
|     Corresponding Source along with the object code.  If the place to |  | ||||||
|     copy the object code is a network server, the Corresponding Source |  | ||||||
|     may be on a different server (operated by you or a third party) |  | ||||||
|     that supports equivalent copying facilities, provided you maintain |  | ||||||
|     clear directions next to the object code saying where to find the |  | ||||||
|     Corresponding Source.  Regardless of what server hosts the |  | ||||||
|     Corresponding Source, you remain obligated to ensure that it is |  | ||||||
|     available for as long as needed to satisfy these requirements. |  | ||||||
|  |  | ||||||
|     e) Convey the object code using peer-to-peer transmission, provided |  | ||||||
|     you inform other peers where the object code and Corresponding |  | ||||||
|     Source of the work are being offered to the general public at no |  | ||||||
|     charge under subsection 6d. |  | ||||||
|  |  | ||||||
|   A separable portion of the object code, whose source code is excluded |  | ||||||
| from the Corresponding Source as a System Library, need not be |  | ||||||
| included in conveying the object code work. |  | ||||||
|  |  | ||||||
|   A "User Product" is either (1) a "consumer product", which means any |  | ||||||
| tangible personal property which is normally used for personal, family, |  | ||||||
| or household purposes, or (2) anything designed or sold for incorporation |  | ||||||
| into a dwelling.  In determining whether a product is a consumer product, |  | ||||||
| doubtful cases shall be resolved in favor of coverage.  For a particular |  | ||||||
| product received by a particular user, "normally used" refers to a |  | ||||||
| typical or common use of that class of product, regardless of the status |  | ||||||
| of the particular user or of the way in which the particular user |  | ||||||
| actually uses, or expects or is expected to use, the product.  A product |  | ||||||
| is a consumer product regardless of whether the product has substantial |  | ||||||
| commercial, industrial or non-consumer uses, unless such uses represent |  | ||||||
| the only significant mode of use of the product. |  | ||||||
|  |  | ||||||
|   "Installation Information" for a User Product means any methods, |  | ||||||
| procedures, authorization keys, or other information required to install |  | ||||||
| and execute modified versions of a covered work in that User Product from |  | ||||||
| a modified version of its Corresponding Source.  The information must |  | ||||||
| suffice to ensure that the continued functioning of the modified object |  | ||||||
| code is in no case prevented or interfered with solely because |  | ||||||
| modification has been made. |  | ||||||
|  |  | ||||||
|   If you convey an object code work under this section in, or with, or |  | ||||||
| specifically for use in, a User Product, and the conveying occurs as |  | ||||||
| part of a transaction in which the right of possession and use of the |  | ||||||
| User Product is transferred to the recipient in perpetuity or for a |  | ||||||
| fixed term (regardless of how the transaction is characterized), the |  | ||||||
| Corresponding Source conveyed under this section must be accompanied |  | ||||||
| by the Installation Information.  But this requirement does not apply |  | ||||||
| if neither you nor any third party retains the ability to install |  | ||||||
| modified object code on the User Product (for example, the work has |  | ||||||
| been installed in ROM). |  | ||||||
|  |  | ||||||
|   The requirement to provide Installation Information does not include a |  | ||||||
| requirement to continue to provide support service, warranty, or updates |  | ||||||
| for a work that has been modified or installed by the recipient, or for |  | ||||||
| the User Product in which it has been modified or installed.  Access to a |  | ||||||
| network may be denied when the modification itself materially and |  | ||||||
| adversely affects the operation of the network or violates the rules and |  | ||||||
| protocols for communication across the network. |  | ||||||
|  |  | ||||||
|   Corresponding Source conveyed, and Installation Information provided, |  | ||||||
| in accord with this section must be in a format that is publicly |  | ||||||
| documented (and with an implementation available to the public in |  | ||||||
| source code form), and must require no special password or key for |  | ||||||
| unpacking, reading or copying. |  | ||||||
|  |  | ||||||
|   7. Additional Terms. |  | ||||||
|  |  | ||||||
|   "Additional permissions" are terms that supplement the terms of this |  | ||||||
| License by making exceptions from one or more of its conditions. |  | ||||||
| Additional permissions that are applicable to the entire Program shall |  | ||||||
| be treated as though they were included in this License, to the extent |  | ||||||
| that they are valid under applicable law.  If additional permissions |  | ||||||
| apply only to part of the Program, that part may be used separately |  | ||||||
| under those permissions, but the entire Program remains governed by |  | ||||||
| this License without regard to the additional permissions. |  | ||||||
|  |  | ||||||
|   When you convey a copy of a covered work, you may at your option |  | ||||||
| remove any additional permissions from that copy, or from any part of |  | ||||||
| it.  (Additional permissions may be written to require their own |  | ||||||
| removal in certain cases when you modify the work.)  You may place |  | ||||||
| additional permissions on material, added by you to a covered work, |  | ||||||
| for which you have or can give appropriate copyright permission. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, for material you |  | ||||||
| add to a covered work, you may (if authorized by the copyright holders of |  | ||||||
| that material) supplement the terms of this License with terms: |  | ||||||
|  |  | ||||||
|     a) Disclaiming warranty or limiting liability differently from the |  | ||||||
|     terms of sections 15 and 16 of this License; or |  | ||||||
|  |  | ||||||
|     b) Requiring preservation of specified reasonable legal notices or |  | ||||||
|     author attributions in that material or in the Appropriate Legal |  | ||||||
|     Notices displayed by works containing it; or |  | ||||||
|  |  | ||||||
|     c) Prohibiting misrepresentation of the origin of that material, or |  | ||||||
|     requiring that modified versions of such material be marked in |  | ||||||
|     reasonable ways as different from the original version; or |  | ||||||
|  |  | ||||||
|     d) Limiting the use for publicity purposes of names of licensors or |  | ||||||
|     authors of the material; or |  | ||||||
|  |  | ||||||
|     e) Declining to grant rights under trademark law for use of some |  | ||||||
|     trade names, trademarks, or service marks; or |  | ||||||
|  |  | ||||||
|     f) Requiring indemnification of licensors and authors of that |  | ||||||
|     material by anyone who conveys the material (or modified versions of |  | ||||||
|     it) with contractual assumptions of liability to the recipient, for |  | ||||||
|     any liability that these contractual assumptions directly impose on |  | ||||||
|     those licensors and authors. |  | ||||||
|  |  | ||||||
|   All other non-permissive additional terms are considered "further |  | ||||||
| restrictions" within the meaning of section 10.  If the Program as you |  | ||||||
| received it, or any part of it, contains a notice stating that it is |  | ||||||
| governed by this License along with a term that is a further |  | ||||||
| restriction, you may remove that term.  If a license document contains |  | ||||||
| a further restriction but permits relicensing or conveying under this |  | ||||||
| License, you may add to a covered work material governed by the terms |  | ||||||
| of that license document, provided that the further restriction does |  | ||||||
| not survive such relicensing or conveying. |  | ||||||
|  |  | ||||||
|   If you add terms to a covered work in accord with this section, you |  | ||||||
| must place, in the relevant source files, a statement of the |  | ||||||
| additional terms that apply to those files, or a notice indicating |  | ||||||
| where to find the applicable terms. |  | ||||||
|  |  | ||||||
|   Additional terms, permissive or non-permissive, may be stated in the |  | ||||||
| form of a separately written license, or stated as exceptions; |  | ||||||
| the above requirements apply either way. |  | ||||||
|  |  | ||||||
|   8. Termination. |  | ||||||
|  |  | ||||||
|   You may not propagate or modify a covered work except as expressly |  | ||||||
| provided under this License.  Any attempt otherwise to propagate or |  | ||||||
| modify it is void, and will automatically terminate your rights under |  | ||||||
| this License (including any patent licenses granted under the third |  | ||||||
| paragraph of section 11). |  | ||||||
|  |  | ||||||
|   However, if you cease all violation of this License, then your |  | ||||||
| license from a particular copyright holder is reinstated (a) |  | ||||||
| provisionally, unless and until the copyright holder explicitly and |  | ||||||
| finally terminates your license, and (b) permanently, if the copyright |  | ||||||
| holder fails to notify you of the violation by some reasonable means |  | ||||||
| prior to 60 days after the cessation. |  | ||||||
|  |  | ||||||
|   Moreover, your license from a particular copyright holder is |  | ||||||
| reinstated permanently if the copyright holder notifies you of the |  | ||||||
| violation by some reasonable means, this is the first time you have |  | ||||||
| received notice of violation of this License (for any work) from that |  | ||||||
| copyright holder, and you cure the violation prior to 30 days after |  | ||||||
| your receipt of the notice. |  | ||||||
|  |  | ||||||
|   Termination of your rights under this section does not terminate the |  | ||||||
| licenses of parties who have received copies or rights from you under |  | ||||||
| this License.  If your rights have been terminated and not permanently |  | ||||||
| reinstated, you do not qualify to receive new licenses for the same |  | ||||||
| material under section 10. |  | ||||||
|  |  | ||||||
|   9. Acceptance Not Required for Having Copies. |  | ||||||
|  |  | ||||||
|   You are not required to accept this License in order to receive or |  | ||||||
| run a copy of the Program.  Ancillary propagation of a covered work |  | ||||||
| occurring solely as a consequence of using peer-to-peer transmission |  | ||||||
| to receive a copy likewise does not require acceptance.  However, |  | ||||||
| nothing other than this License grants you permission to propagate or |  | ||||||
| modify any covered work.  These actions infringe copyright if you do |  | ||||||
| not accept this License.  Therefore, by modifying or propagating a |  | ||||||
| covered work, you indicate your acceptance of this License to do so. |  | ||||||
|  |  | ||||||
|   10. Automatic Licensing of Downstream Recipients. |  | ||||||
|  |  | ||||||
|   Each time you convey a covered work, the recipient automatically |  | ||||||
| receives a license from the original licensors, to run, modify and |  | ||||||
| propagate that work, subject to this License.  You are not responsible |  | ||||||
| for enforcing compliance by third parties with this License. |  | ||||||
|  |  | ||||||
|   An "entity transaction" is a transaction transferring control of an |  | ||||||
| organization, or substantially all assets of one, or subdividing an |  | ||||||
| organization, or merging organizations.  If propagation of a covered |  | ||||||
| work results from an entity transaction, each party to that |  | ||||||
| transaction who receives a copy of the work also receives whatever |  | ||||||
| licenses to the work the party's predecessor in interest had or could |  | ||||||
| give under the previous paragraph, plus a right to possession of the |  | ||||||
| Corresponding Source of the work from the predecessor in interest, if |  | ||||||
| the predecessor has it or can get it with reasonable efforts. |  | ||||||
|  |  | ||||||
|   You may not impose any further restrictions on the exercise of the |  | ||||||
| rights granted or affirmed under this License.  For example, you may |  | ||||||
| not impose a license fee, royalty, or other charge for exercise of |  | ||||||
| rights granted under this License, and you may not initiate litigation |  | ||||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that |  | ||||||
| any patent claim is infringed by making, using, selling, offering for |  | ||||||
| sale, or importing the Program or any portion of it. |  | ||||||
|  |  | ||||||
|   11. Patents. |  | ||||||
|  |  | ||||||
|   A "contributor" is a copyright holder who authorizes use under this |  | ||||||
| License of the Program or a work on which the Program is based.  The |  | ||||||
| work thus licensed is called the contributor's "contributor version". |  | ||||||
|  |  | ||||||
|   A contributor's "essential patent claims" are all patent claims |  | ||||||
| owned or controlled by the contributor, whether already acquired or |  | ||||||
| hereafter acquired, that would be infringed by some manner, permitted |  | ||||||
| by this License, of making, using, or selling its contributor version, |  | ||||||
| but do not include claims that would be infringed only as a |  | ||||||
| consequence of further modification of the contributor version.  For |  | ||||||
| purposes of this definition, "control" includes the right to grant |  | ||||||
| patent sublicenses in a manner consistent with the requirements of |  | ||||||
| this License. |  | ||||||
|  |  | ||||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free |  | ||||||
| patent license under the contributor's essential patent claims, to |  | ||||||
| make, use, sell, offer for sale, import and otherwise run, modify and |  | ||||||
| propagate the contents of its contributor version. |  | ||||||
|  |  | ||||||
|   In the following three paragraphs, a "patent license" is any express |  | ||||||
| agreement or commitment, however denominated, not to enforce a patent |  | ||||||
| (such as an express permission to practice a patent or covenant not to |  | ||||||
| sue for patent infringement).  To "grant" such a patent license to a |  | ||||||
| party means to make such an agreement or commitment not to enforce a |  | ||||||
| patent against the party. |  | ||||||
|  |  | ||||||
|   If you convey a covered work, knowingly relying on a patent license, |  | ||||||
| and the Corresponding Source of the work is not available for anyone |  | ||||||
| to copy, free of charge and under the terms of this License, through a |  | ||||||
| publicly available network server or other readily accessible means, |  | ||||||
| then you must either (1) cause the Corresponding Source to be so |  | ||||||
| available, or (2) arrange to deprive yourself of the benefit of the |  | ||||||
| patent license for this particular work, or (3) arrange, in a manner |  | ||||||
| consistent with the requirements of this License, to extend the patent |  | ||||||
| license to downstream recipients.  "Knowingly relying" means you have |  | ||||||
| actual knowledge that, but for the patent license, your conveying the |  | ||||||
| covered work in a country, or your recipient's use of the covered work |  | ||||||
| in a country, would infringe one or more identifiable patents in that |  | ||||||
| country that you have reason to believe are valid. |  | ||||||
|  |  | ||||||
|   If, pursuant to or in connection with a single transaction or |  | ||||||
| arrangement, you convey, or propagate by procuring conveyance of, a |  | ||||||
| covered work, and grant a patent license to some of the parties |  | ||||||
| receiving the covered work authorizing them to use, propagate, modify |  | ||||||
| or convey a specific copy of the covered work, then the patent license |  | ||||||
| you grant is automatically extended to all recipients of the covered |  | ||||||
| work and works based on it. |  | ||||||
|  |  | ||||||
|   A patent license is "discriminatory" if it does not include within |  | ||||||
| the scope of its coverage, prohibits the exercise of, or is |  | ||||||
| conditioned on the non-exercise of one or more of the rights that are |  | ||||||
| specifically granted under this License.  You may not convey a covered |  | ||||||
| work if you are a party to an arrangement with a third party that is |  | ||||||
| in the business of distributing software, under which you make payment |  | ||||||
| to the third party based on the extent of your activity of conveying |  | ||||||
| the work, and under which the third party grants, to any of the |  | ||||||
| parties who would receive the covered work from you, a discriminatory |  | ||||||
| patent license (a) in connection with copies of the covered work |  | ||||||
| conveyed by you (or copies made from those copies), or (b) primarily |  | ||||||
| for and in connection with specific products or compilations that |  | ||||||
| contain the covered work, unless you entered into that arrangement, |  | ||||||
| or that patent license was granted, prior to 28 March 2007. |  | ||||||
|  |  | ||||||
|   Nothing in this License shall be construed as excluding or limiting |  | ||||||
| any implied license or other defenses to infringement that may |  | ||||||
| otherwise be available to you under applicable patent law. |  | ||||||
|  |  | ||||||
|   12. No Surrender of Others' Freedom. |  | ||||||
|  |  | ||||||
|   If conditions are imposed on you (whether by court order, agreement or |  | ||||||
| otherwise) that contradict the conditions of this License, they do not |  | ||||||
| excuse you from the conditions of this License.  If you cannot convey a |  | ||||||
| covered work so as to satisfy simultaneously your obligations under this |  | ||||||
| License and any other pertinent obligations, then as a consequence you may |  | ||||||
| not convey it at all.  For example, if you agree to terms that obligate you |  | ||||||
| to collect a royalty for further conveying from those to whom you convey |  | ||||||
| the Program, the only way you could satisfy both those terms and this |  | ||||||
| License would be to refrain entirely from conveying the Program. |  | ||||||
|  |  | ||||||
|   13. Use with the GNU Affero General Public License. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, you have |  | ||||||
| permission to link or combine any covered work with a work licensed |  | ||||||
| under version 3 of the GNU Affero General Public License into a single |  | ||||||
| combined work, and to convey the resulting work.  The terms of this |  | ||||||
| License will continue to apply to the part which is the covered work, |  | ||||||
| but the special requirements of the GNU Affero General Public License, |  | ||||||
| section 13, concerning interaction through a network will apply to the |  | ||||||
| combination as such. |  | ||||||
|  |  | ||||||
|   14. Revised Versions of this License. |  | ||||||
|  |  | ||||||
|   The Free Software Foundation may publish revised and/or new versions of |  | ||||||
| the GNU General Public License from time to time.  Such new versions will |  | ||||||
| be similar in spirit to the present version, but may differ in detail to |  | ||||||
| address new problems or concerns. |  | ||||||
|  |  | ||||||
|   Each version is given a distinguishing version number.  If the |  | ||||||
| Program specifies that a certain numbered version of the GNU General |  | ||||||
| Public License "or any later version" applies to it, you have the |  | ||||||
| option of following the terms and conditions either of that numbered |  | ||||||
| version or of any later version published by the Free Software |  | ||||||
| Foundation.  If the Program does not specify a version number of the |  | ||||||
| GNU General Public License, you may choose any version ever published |  | ||||||
| by the Free Software Foundation. |  | ||||||
|  |  | ||||||
|   If the Program specifies that a proxy can decide which future |  | ||||||
| versions of the GNU General Public License can be used, that proxy's |  | ||||||
| public statement of acceptance of a version permanently authorizes you |  | ||||||
| to choose that version for the Program. |  | ||||||
|  |  | ||||||
|   Later license versions may give you additional or different |  | ||||||
| permissions.  However, no additional obligations are imposed on any |  | ||||||
| author or copyright holder as a result of your choosing to follow a |  | ||||||
| later version. |  | ||||||
|  |  | ||||||
|   15. Disclaimer of Warranty. |  | ||||||
|  |  | ||||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |  | ||||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |  | ||||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |  | ||||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |  | ||||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |  | ||||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |  | ||||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |  | ||||||
|  |  | ||||||
|   16. Limitation of Liability. |  | ||||||
|  |  | ||||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |  | ||||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |  | ||||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |  | ||||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |  | ||||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |  | ||||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |  | ||||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |  | ||||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |  | ||||||
| SUCH DAMAGES. |  | ||||||
|  |  | ||||||
|   17. Interpretation of Sections 15 and 16. |  | ||||||
|  |  | ||||||
|   If the disclaimer of warranty and limitation of liability provided |  | ||||||
| above cannot be given local legal effect according to their terms, |  | ||||||
| reviewing courts shall apply local law that most closely approximates |  | ||||||
| an absolute waiver of all civil liability in connection with the |  | ||||||
| Program, unless a warranty or assumption of liability accompanies a |  | ||||||
| copy of the Program in return for a fee. |  | ||||||
|  |  | ||||||
|                      END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|             How to Apply These Terms to Your New Programs |  | ||||||
|  |  | ||||||
|   If you develop a new program, and you want it to be of the greatest |  | ||||||
| possible use to the public, the best way to achieve this is to make it |  | ||||||
| free software which everyone can redistribute and change under these terms. |  | ||||||
|  |  | ||||||
|   To do so, attach the following notices to the program.  It is safest |  | ||||||
| to attach them to the start of each source file to most effectively |  | ||||||
| state the exclusion of warranty; and each file should have at least |  | ||||||
| the "copyright" line and a pointer to where the full notice is found. |  | ||||||
|  |  | ||||||
|     <one line to give the program's name and a brief idea of what it does.> |  | ||||||
|     Copyright (C) <year>  <name of author> |  | ||||||
|  |  | ||||||
|     This program is free software: you can redistribute it and/or modify |  | ||||||
|     it under the terms of the GNU General Public License as published by |  | ||||||
|     the Free Software Foundation, either version 3 of the License, or |  | ||||||
|     (at your option) any later version. |  | ||||||
|  |  | ||||||
|     This program is distributed in the hope that it will be useful, |  | ||||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|     GNU General Public License for more details. |  | ||||||
|  |  | ||||||
|     You should have received a copy of the GNU General Public License |  | ||||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| Also add information on how to contact you by electronic and paper mail. |  | ||||||
|  |  | ||||||
|   If the program does terminal interaction, make it output a short |  | ||||||
| notice like this when it starts in an interactive mode: |  | ||||||
|  |  | ||||||
|     <program>  Copyright (C) <year>  <name of author> |  | ||||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |  | ||||||
|     This is free software, and you are welcome to redistribute it |  | ||||||
|     under certain conditions; type `show c' for details. |  | ||||||
|  |  | ||||||
| The hypothetical commands `show w' and `show c' should show the appropriate |  | ||||||
| parts of the General Public License.  Of course, your program's commands |  | ||||||
| might be different; for a GUI interface, you would use an "about box". |  | ||||||
|  |  | ||||||
|   You should also get your employer (if you work as a programmer) or school, |  | ||||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. |  | ||||||
| For more information on this, and how to apply and follow the GNU GPL, see |  | ||||||
| <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
|   The GNU General Public License does not permit incorporating your program |  | ||||||
| into proprietary programs.  If your program is a subroutine library, you |  | ||||||
| may consider it more useful to permit linking proprietary applications with |  | ||||||
| the library.  If this is what you want to do, use the GNU Lesser General |  | ||||||
| Public License instead of this License.  But first, please read |  | ||||||
| <http://www.gnu.org/philosophy/why-not-lgpl.html>. |  | ||||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | MIT License | ||||||
|  |  | ||||||
|  | Copyright (c) 2012-2017 python-escpos and Manuel F Martinez | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										8
									
								
								MANIFEST
									
									
									
									
									
								
							
							
						
						| @@ -1,8 +0,0 @@ | |||||||
| # file GENERATED by distutils, do NOT edit |  | ||||||
| README |  | ||||||
| setup.py |  | ||||||
| escpos/__init__.py |  | ||||||
| escpos/constants.py |  | ||||||
| escpos/escpos.py |  | ||||||
| escpos/exceptions.py |  | ||||||
| escpos/printer.py |  | ||||||
							
								
								
									
										13
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | include *.rst | ||||||
|  | include *.txt | ||||||
|  | include COPYING | ||||||
|  | include LICENSE | ||||||
|  | include INSTALL | ||||||
|  | include tox.ini | ||||||
|  | include capabilities-data/dist/capabilities.json | ||||||
|  | recursive-include doc *.bat | ||||||
|  | recursive-include doc *.ico | ||||||
|  | recursive-include doc *.py | ||||||
|  | recursive-include doc *.rst | ||||||
|  | recursive-include doc *.txt | ||||||
|  | recursive-include doc Makefile | ||||||
							
								
								
									
										89
									
								
								README
									
									
									
									
									
								
							
							
						
						| @@ -1,89 +0,0 @@ | |||||||
| ESCPOS |  | ||||||
| ====== |  | ||||||
|  |  | ||||||
| Python library to manipulate ESC/POS Printers. |  | ||||||
|  |  | ||||||
| ------------------------------------------------------------------ |  | ||||||
| 1. Dependencies |  | ||||||
|  |  | ||||||
| In order to start getting access to your printer, you must ensure |  | ||||||
| you have previously installed the following python modules: |  | ||||||
|  |  | ||||||
|   * pyusb (python-usb) |  | ||||||
|   * PIL (Python Image Library) |  | ||||||
|  |  | ||||||
| ------------------------------------------------------------------ |  | ||||||
| 2. Description |  | ||||||
|  |  | ||||||
| Python ESC/POS is a library which lets the user have access to all |  | ||||||
| those printers handled by ESC/POS commands, as defined by Epson, |  | ||||||
| from a Python application. |  | ||||||
|  |  | ||||||
| The standard usage is send raw text to the printer, but in also  |  | ||||||
| helps the user to enhance the experience with those printers by |  | ||||||
| facilitating the bar code printing in many different standards, |  | ||||||
| as well as manipulating images so they can be printed as brand |  | ||||||
| logo or any other usage images migh have.  |  | ||||||
|  |  | ||||||
| Text can be aligned/justified and fonts can be changed by size, |  | ||||||
| type and weight. |  | ||||||
|  |  | ||||||
| Also, this module handles some hardware functionalities like, cut |  | ||||||
| paper, carrier return, printer reset and others concerned to the |  | ||||||
| carriage alignment. |  | ||||||
|  |  | ||||||
| ------------------------------------------------------------------ |  | ||||||
| 3. Define your printer |  | ||||||
|  |  | ||||||
| Before start create your Python ESC/POS printer instance, you must |  | ||||||
| see at your system for 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 of the device with the following format: |  | ||||||
|  |  | ||||||
|   xxxx:xxxx |  | ||||||
|  |  | ||||||
| Example: |  | ||||||
|   Bus 002 Device 001: ID 1a2b:1a2b Device name |  | ||||||
|  |  | ||||||
| Write down the the values in question, then issue the following |  | ||||||
| command so you can get the "Interface" number and "End Point" |  | ||||||
|  |  | ||||||
|   lsusb -vvv -d xxxx:xxxx | grep iInterface |  | ||||||
|   lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT |  | ||||||
|  |  | ||||||
| The first command will yields the "Interface" number that must |  | ||||||
| be handy to have and the second yields the "Output Endpoint" |  | ||||||
| address. |  | ||||||
|  |  | ||||||
| By default the "Interface" number is "0" and the "Output Endpoint" |  | ||||||
| address is "0x82",  if you have other values then you can define |  | ||||||
| with your instance. |  | ||||||
|  |  | ||||||
| ------------------------------------------------------------------ |  | ||||||
| 4. Define your instance |  | ||||||
|  |  | ||||||
| The following example shows how to initialize the Epson TM-TI88IV |  | ||||||
| *** NOTE: Always finish the sequence with Epson.cut() otherwise |  | ||||||
|           you will endup with weird chars being printed. |  | ||||||
|  |  | ||||||
|   from escpos import * |  | ||||||
|  |  | ||||||
|   """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ |  | ||||||
|   Epson = escpos.Escpos(0x04b8,0x0202,0) |  | ||||||
|   Epson.text("Hello World") |  | ||||||
|   Epson.image("logo.gif") |  | ||||||
|   Epson.barcode |  | ||||||
|   Epson.barcode('1324354657687','EAN13',64,2,'','') |  | ||||||
|   Epson.cut() |  | ||||||
|  |  | ||||||
| ------------------------------------------------------------------ |  | ||||||
| 5. Links |  | ||||||
|  |  | ||||||
| Please visit project homepage at: |  | ||||||
| http://repo.bashlinux.com/projects/escpos.html |  | ||||||
|  |  | ||||||
| Manuel F Martinez <manpaz@bashlinux.com> |  | ||||||
|  |  | ||||||
							
								
								
									
										82
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,82 @@ | |||||||
|  | ############################################################# | ||||||
|  | python-escpos - Python library to manipulate ESC/POS Printers | ||||||
|  | ############################################################# | ||||||
|  |  | ||||||
|  | .. image:: https://travis-ci.org/python-escpos/python-escpos.svg?branch=master | ||||||
|  |     :target: https://travis-ci.org/python-escpos/python-escpos | ||||||
|  |     :alt: Continous Integration | ||||||
|  |  | ||||||
|  | .. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat | ||||||
|  |     :target: https://landscape.io/github/python-escpos/python-escpos/master | ||||||
|  |     :alt: Code Health | ||||||
|  |  | ||||||
|  | .. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master | ||||||
|  |     :target: https://codecov.io/github/python-escpos/python-escpos?branch=master | ||||||
|  |     :alt: Code Coverage | ||||||
|  |  | ||||||
|  | .. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable | ||||||
|  |     :target: http://python-escpos.readthedocs.io/en/latest/?badge=stable | ||||||
|  |     :alt: Documentation Status | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Description | ||||||
|  | ----------- | ||||||
|  |  | ||||||
|  | Python ESC/POS is a library which lets the user have access to all those printers handled | ||||||
|  | by ESC/POS commands, as defined by Epson, from a Python application. | ||||||
|  |  | ||||||
|  | The library tries to implement the functions provided by the ESC/POS-commandset and supports sending text, images, | ||||||
|  | barcodes and qr-codes to the printer. | ||||||
|  |  | ||||||
|  | Text can be aligned/justified and fonts can be changed by size, type and weight. | ||||||
|  |  | ||||||
|  | Also, this module handles some hardware functionalities like cutting paper, control characters, printer reset | ||||||
|  | and similar functions. | ||||||
|  |  | ||||||
|  | Since supported commands differ from printer to printer the software tries to automatically apply the right | ||||||
|  | settings for the printer that you set. These settings are handled by | ||||||
|  | `escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_ which is also used in | ||||||
|  | `escpos-php <https://github.com/mike42/escpos-php>`_. | ||||||
|  |  | ||||||
|  | Dependencies | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | This library makes use of: | ||||||
|  |  | ||||||
|  | * `pyusb <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 | ||||||
|  | ----------------------- | ||||||
|  |  | ||||||
|  | The basic usage is: | ||||||
|  |  | ||||||
|  | .. code:: python | ||||||
|  |  | ||||||
|  |     from escpos.printer import Usb | ||||||
|  |  | ||||||
|  |     """ Seiko Epson Corp. Receipt Printer (EPSON TM-T88III) """ | ||||||
|  |     p = Usb(0x04b8, 0x0202, 0, profile='TM-T88III') | ||||||
|  |     p.text('Hello World\n') | ||||||
|  |     p.image('logo.gif') | ||||||
|  |     p.barcode('1324354657687', 'EAN13', 64, 2, '', '') | ||||||
|  |     p.cut() | ||||||
|  |  | ||||||
|  | The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_. | ||||||
|  |  | ||||||
|  | Contributing | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | This project is open for any contribution! Please see `CONTRIBUTING.rst <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
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										13
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | codecov: | ||||||
|  |   bot: patkan | ||||||
|  |  | ||||||
|  | coverage: | ||||||
|  |   status: | ||||||
|  |     project: | ||||||
|  |       default:  # status context | ||||||
|  |         target: auto | ||||||
|  |         threshold: "1%" | ||||||
|  |   range: "60...100" | ||||||
|  |  | ||||||
|  | comment: off | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								doc/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | _build/ | ||||||
							
								
								
									
										177
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,177 @@ | |||||||
|  | # Makefile for Sphinx documentation | ||||||
|  | # | ||||||
|  |  | ||||||
|  | # You can set these variables from the command line. | ||||||
|  | SPHINXOPTS    = | ||||||
|  | SPHINXBUILD   = sphinx-build | ||||||
|  | PAPER         = | ||||||
|  | BUILDDIR      = _build | ||||||
|  |  | ||||||
|  | # User-friendly check for sphinx-build | ||||||
|  | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) | ||||||
|  | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) | ||||||
|  | endif | ||||||
|  |  | ||||||
|  | # Internal variables. | ||||||
|  | PAPEROPT_a4     = -D latex_paper_size=a4 | ||||||
|  | PAPEROPT_letter = -D latex_paper_size=letter | ||||||
|  | ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||||
|  | # the i18n builder cannot share the environment and doctrees with the others | ||||||
|  | I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||||
|  |  | ||||||
|  | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | ||||||
|  |  | ||||||
|  | help: | ||||||
|  | 	@echo "Please use \`make <target>' where <target> is one of" | ||||||
|  | 	@echo "  html       to make standalone HTML files" | ||||||
|  | 	@echo "  dirhtml    to make HTML files named index.html in directories" | ||||||
|  | 	@echo "  singlehtml to make a single large HTML file" | ||||||
|  | 	@echo "  pickle     to make pickle files" | ||||||
|  | 	@echo "  json       to make JSON files" | ||||||
|  | 	@echo "  htmlhelp   to make HTML files and a HTML help project" | ||||||
|  | 	@echo "  qthelp     to make HTML files and a qthelp project" | ||||||
|  | 	@echo "  devhelp    to make HTML files and a Devhelp project" | ||||||
|  | 	@echo "  epub       to make an epub" | ||||||
|  | 	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | ||||||
|  | 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex" | ||||||
|  | 	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx" | ||||||
|  | 	@echo "  text       to make text files" | ||||||
|  | 	@echo "  man        to make manual pages" | ||||||
|  | 	@echo "  texinfo    to make Texinfo files" | ||||||
|  | 	@echo "  info       to make Texinfo files and run them through makeinfo" | ||||||
|  | 	@echo "  gettext    to make PO message catalogs" | ||||||
|  | 	@echo "  changes    to make an overview of all changed/added/deprecated items" | ||||||
|  | 	@echo "  xml        to make Docutils-native XML files" | ||||||
|  | 	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes" | ||||||
|  | 	@echo "  linkcheck  to check all external links for integrity" | ||||||
|  | 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)" | ||||||
|  |  | ||||||
|  | clean: | ||||||
|  | 	rm -rf $(BUILDDIR)/* | ||||||
|  |  | ||||||
|  | html: | ||||||
|  | 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | ||||||
|  |  | ||||||
|  | dirhtml: | ||||||
|  | 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | ||||||
|  |  | ||||||
|  | singlehtml: | ||||||
|  | 	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | ||||||
|  |  | ||||||
|  | pickle: | ||||||
|  | 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished; now you can process the pickle files." | ||||||
|  |  | ||||||
|  | json: | ||||||
|  | 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished; now you can process the JSON files." | ||||||
|  |  | ||||||
|  | htmlhelp: | ||||||
|  | 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished; now you can run HTML Help Workshop with the" \ | ||||||
|  | 	      ".hhp project file in $(BUILDDIR)/htmlhelp." | ||||||
|  |  | ||||||
|  | qthelp: | ||||||
|  | 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | ||||||
|  | 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | ||||||
|  | 	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-escpos.qhcp" | ||||||
|  | 	@echo "To view the help file:" | ||||||
|  | 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-escpos.qhc" | ||||||
|  |  | ||||||
|  | devhelp: | ||||||
|  | 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished." | ||||||
|  | 	@echo "To view the help file:" | ||||||
|  | 	@echo "# mkdir -p $$HOME/.local/share/devhelp/python-escpos" | ||||||
|  | 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-escpos" | ||||||
|  | 	@echo "# devhelp" | ||||||
|  |  | ||||||
|  | epub: | ||||||
|  | 	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The epub file is in $(BUILDDIR)/epub." | ||||||
|  |  | ||||||
|  | latex: | ||||||
|  | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | ||||||
|  | 	@echo "Run \`make' in that directory to run these through (pdf)latex" \ | ||||||
|  | 	      "(use \`make latexpdf' here to do that automatically)." | ||||||
|  |  | ||||||
|  | latexpdf: | ||||||
|  | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||||
|  | 	@echo "Running LaTeX files through pdflatex..." | ||||||
|  | 	$(MAKE) -C $(BUILDDIR)/latex all-pdf | ||||||
|  | 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||||||
|  |  | ||||||
|  | latexpdfja: | ||||||
|  | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||||
|  | 	@echo "Running LaTeX files through platex and dvipdfmx..." | ||||||
|  | 	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja | ||||||
|  | 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||||||
|  |  | ||||||
|  | text: | ||||||
|  | 	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The text files are in $(BUILDDIR)/text." | ||||||
|  |  | ||||||
|  | man: | ||||||
|  | 	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man." | ||||||
|  |  | ||||||
|  | texinfo: | ||||||
|  | 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | ||||||
|  | 	@echo "Run \`make' in that directory to run these through makeinfo" \ | ||||||
|  | 	      "(use \`make info' here to do that automatically)." | ||||||
|  |  | ||||||
|  | info: | ||||||
|  | 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | ||||||
|  | 	@echo "Running Texinfo files through makeinfo..." | ||||||
|  | 	make -C $(BUILDDIR)/texinfo info | ||||||
|  | 	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | ||||||
|  |  | ||||||
|  | gettext: | ||||||
|  | 	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | ||||||
|  |  | ||||||
|  | changes: | ||||||
|  | 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | ||||||
|  | 	@echo | ||||||
|  | 	@echo "The overview file is in $(BUILDDIR)/changes." | ||||||
|  |  | ||||||
|  | linkcheck: | ||||||
|  | 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Link check complete; look for any errors in the above output " \ | ||||||
|  | 	      "or in $(BUILDDIR)/linkcheck/output.txt." | ||||||
|  |  | ||||||
|  | doctest: | ||||||
|  | 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | ||||||
|  | 	@echo "Testing of doctests in the sources finished, look at the " \ | ||||||
|  | 	      "results in $(BUILDDIR)/doctest/output.txt." | ||||||
|  |  | ||||||
|  | xml: | ||||||
|  | 	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The XML files are in $(BUILDDIR)/xml." | ||||||
|  |  | ||||||
|  | pseudoxml: | ||||||
|  | 	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml | ||||||
|  | 	@echo | ||||||
|  | 	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." | ||||||
							
								
								
									
										0
									
								
								doc/_static/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										10
									
								
								doc/api/capabilities.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Capabilities | ||||||
|  | ------------ | ||||||
|  | Module :py:mod:`escpos.capabilities` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.capabilities | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/cli.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | CLI | ||||||
|  | --- | ||||||
|  | Module :py:mod:`escpos.cli` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.cli | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/codepages.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Codepages | ||||||
|  | --------- | ||||||
|  | Module :py:mod:`escpos.codepages` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.codepages | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/config.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Config | ||||||
|  | ------ | ||||||
|  | Module :py:mod:`escpos.config` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.config | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/constants.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Constants | ||||||
|  | --------- | ||||||
|  | Module :py:mod:`escpos.constants` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.constants | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/escpos.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Esc/Pos | ||||||
|  | ------- | ||||||
|  | Module :py:mod:`escpos.escpos` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.escpos | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										9
									
								
								doc/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | Exceptions | ||||||
|  | ---------- | ||||||
|  | Module :py:mod:`escpos.exceptions` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.exceptions | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/image.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Image helper | ||||||
|  | ------------ | ||||||
|  | Module :py:mod:`escpos.image` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.image | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/katakana.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Katakana | ||||||
|  | -------- | ||||||
|  | Module :py:mod:`escpos.katakana` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.katakana | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										10
									
								
								doc/api/magicencode.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | Magic Encode | ||||||
|  | ------------ | ||||||
|  | Module :py:mod:`escpos.magicencode` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.magicencode | ||||||
|  |     :members: | ||||||
|  |     :inherited-members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										9
									
								
								doc/api/printer.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | Printer implementations | ||||||
|  | ----------------------- | ||||||
|  | Module :py:mod:`escpos.printer` | ||||||
|  |  | ||||||
|  | .. automodule:: escpos.printer | ||||||
|  |     :members: | ||||||
|  |     :undoc-members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :member-order: bysource | ||||||
							
								
								
									
										294
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,294 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # | ||||||
|  | # python-escpos documentation build configuration file, created by | ||||||
|  | # sphinx-quickstart on Sat Dec 26 14:28:42 2015. | ||||||
|  | # | ||||||
|  | # This file is execfile()d with the current directory set to its | ||||||
|  | # containing dir. | ||||||
|  | # | ||||||
|  | # Note that not all possible configuration values are present in this | ||||||
|  | # autogenerated file. | ||||||
|  | # | ||||||
|  | # All configuration values have a default; values that are commented out | ||||||
|  | # serve to show the default. | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import os | ||||||
|  | on_rtd = os.getenv('READTHEDOCS') == 'True' | ||||||
|  | if on_rtd: | ||||||
|  |     import escpos | ||||||
|  | else: | ||||||
|  |     from setuptools_scm import get_version | ||||||
|  |  | ||||||
|  | # If extensions (or modules to document with autodoc) are in another directory, | ||||||
|  | # add these directories to sys.path here. If the directory is relative to the | ||||||
|  | # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||||
|  | sys.path.insert(0, os.path.abspath('../src')) | ||||||
|  | root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..')) | ||||||
|  |  | ||||||
|  | # -- General configuration ------------------------------------------------ | ||||||
|  |  | ||||||
|  | # If your documentation needs a minimal Sphinx version, state it here. | ||||||
|  | #needs_sphinx = '1.0' | ||||||
|  |  | ||||||
|  | # Add any Sphinx extension module names here, as strings. They can be | ||||||
|  | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | ||||||
|  | # ones. | ||||||
|  | extensions = [ | ||||||
|  |     'sphinx.ext.autodoc', | ||||||
|  |     'sphinx.ext.doctest', | ||||||
|  |     'sphinx.ext.todo', | ||||||
|  |     'sphinx.ext.coverage', | ||||||
|  |     'sphinx.ext.viewcode', | ||||||
|  |     'sphinx.ext.todo', | ||||||
|  |     'sphinx.ext.graphviz', | ||||||
|  |     'sphinx.ext.inheritance_diagram', | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # supress warnings for external images | ||||||
|  | suppress_warnings = [ | ||||||
|  |     'image.nonlocal_uri', | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # enable todos | ||||||
|  | todo_include_todos = True | ||||||
|  |  | ||||||
|  | # Add any paths that contain templates here, relative to this directory. | ||||||
|  | templates_path = ['_templates'] | ||||||
|  |  | ||||||
|  | # The suffix of source filenames. | ||||||
|  | source_suffix = '.rst' | ||||||
|  |  | ||||||
|  | # The encoding of source files. | ||||||
|  | #source_encoding = 'utf-8-sig' | ||||||
|  |  | ||||||
|  | # The master toctree document. | ||||||
|  | master_doc = 'index' | ||||||
|  |  | ||||||
|  | # General information about the project. | ||||||
|  | project = u'python-escpos' | ||||||
|  | copyright = u'2016, Manuel F Martinez and others' | ||||||
|  |  | ||||||
|  | # The version info for the project you're documenting, acts as replacement for | ||||||
|  | # |version| and |release|, also used in various other places throughout the | ||||||
|  | # built documents. | ||||||
|  | # | ||||||
|  | if on_rtd: | ||||||
|  |     # The full version, including alpha/beta/rc tags. | ||||||
|  |     release = escpos.__version__ | ||||||
|  | else: | ||||||
|  |     # locally setuptools_scm should work | ||||||
|  |     release = get_version(root=root) | ||||||
|  | # The short X.Y version. | ||||||
|  | version = '.'.join(release.split('.')[:2])  # The short X.Y version. | ||||||
|  |  | ||||||
|  | # The language for content autogenerated by Sphinx. Refer to documentation | ||||||
|  | # for a list of supported languages. | ||||||
|  | #language = None | ||||||
|  |  | ||||||
|  | # There are two options for replacing |today|: either, you set today to some | ||||||
|  | # non-false value, then it is used: | ||||||
|  | #today = '' | ||||||
|  | # Else, today_fmt is used as the format for a strftime call. | ||||||
|  | #today_fmt = '%B %d, %Y' | ||||||
|  |  | ||||||
|  | # List of patterns, relative to source directory, that match files and | ||||||
|  | # directories to ignore when looking for source files. | ||||||
|  | exclude_patterns = ['_build'] | ||||||
|  |  | ||||||
|  | # The reST default role (used for this markup: `text`) to use for all | ||||||
|  | # documents. | ||||||
|  | #default_role = None | ||||||
|  |  | ||||||
|  | # If true, '()' will be appended to :func: etc. cross-reference text. | ||||||
|  | #add_function_parentheses = True | ||||||
|  |  | ||||||
|  | # If true, the current module name will be prepended to all description | ||||||
|  | # unit titles (such as .. function::). | ||||||
|  | #add_module_names = True | ||||||
|  |  | ||||||
|  | # If true, sectionauthor and moduleauthor directives will be shown in the | ||||||
|  | # output. They are ignored by default. | ||||||
|  | #show_authors = False | ||||||
|  |  | ||||||
|  | # The name of the Pygments (syntax highlighting) style to use. | ||||||
|  | pygments_style = 'sphinx' | ||||||
|  |  | ||||||
|  | # A list of ignored prefixes for module index sorting. | ||||||
|  | #modindex_common_prefix = [] | ||||||
|  |  | ||||||
|  | # If true, keep warnings as "system message" paragraphs in the built documents. | ||||||
|  | #keep_warnings = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # -- Options for HTML output ---------------------------------------------- | ||||||
|  |  | ||||||
|  | # The theme to use for HTML and HTML Help pages.  See the documentation for | ||||||
|  | # a list of builtin themes. | ||||||
|  | if on_rtd: | ||||||
|  |     html_theme = 'default' | ||||||
|  | else: | ||||||
|  |     try: | ||||||
|  |         import sphinx_rtd_theme | ||||||
|  |         html_theme = 'sphinx_rtd_theme' | ||||||
|  |         html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] | ||||||
|  |     except ImportError: | ||||||
|  |         print("no sphinx_rtd_theme found, switching to nature") | ||||||
|  |         html_theme = 'default' | ||||||
|  |  | ||||||
|  | # Theme options are theme-specific and customize the look and feel of a theme | ||||||
|  | # further.  For a list of options available for each theme, see the | ||||||
|  | # documentation. | ||||||
|  | #html_theme_options = {} | ||||||
|  |  | ||||||
|  | # Add any paths that contain custom themes here, relative to this directory. | ||||||
|  | #html_theme_path = [] | ||||||
|  |  | ||||||
|  | # The name for this set of Sphinx documents.  If None, it defaults to | ||||||
|  | # "<project> v<release> documentation". | ||||||
|  | #html_title = None | ||||||
|  |  | ||||||
|  | # A shorter title for the navigation bar.  Default is the same as html_title. | ||||||
|  | #html_short_title = None | ||||||
|  |  | ||||||
|  | # The name of an image file (relative to this directory) to place at the top | ||||||
|  | # of the sidebar. | ||||||
|  | #html_logo = None | ||||||
|  |  | ||||||
|  | # The name of an image file (within the static path) to use as favicon of the | ||||||
|  | # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||||||
|  | # pixels large. | ||||||
|  | html_favicon = 'pyescpos.ico' | ||||||
|  |  | ||||||
|  | # Add any paths that contain custom static files (such as style sheets) here, | ||||||
|  | # relative to this directory. They are copied after the builtin static files, | ||||||
|  | # so a file named "default.css" will overwrite the builtin "default.css". | ||||||
|  | html_static_path = ['_static'] | ||||||
|  |  | ||||||
|  | # Add any extra paths that contain custom files (such as robots.txt or | ||||||
|  | # .htaccess) here, relative to this directory. These files are copied | ||||||
|  | # directly to the root of the documentation. | ||||||
|  | #html_extra_path = [] | ||||||
|  |  | ||||||
|  | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | ||||||
|  | # using the given strftime format. | ||||||
|  | #html_last_updated_fmt = '%b %d, %Y' | ||||||
|  |  | ||||||
|  | # If true, SmartyPants will be used to convert quotes and dashes to | ||||||
|  | # typographically correct entities. | ||||||
|  | #html_use_smartypants = True | ||||||
|  |  | ||||||
|  | # Custom sidebar templates, maps document names to template names. | ||||||
|  | #html_sidebars = {} | ||||||
|  |  | ||||||
|  | # Additional templates that should be rendered to pages, maps page names to | ||||||
|  | # template names. | ||||||
|  | #html_additional_pages = {} | ||||||
|  |  | ||||||
|  | # If false, no module index is generated. | ||||||
|  | #html_domain_indices = True | ||||||
|  |  | ||||||
|  | # If false, no index is generated. | ||||||
|  | #html_use_index = True | ||||||
|  |  | ||||||
|  | # If true, the index is split into individual pages for each letter. | ||||||
|  | #html_split_index = False | ||||||
|  |  | ||||||
|  | # If true, links to the reST sources are added to the pages. | ||||||
|  | #html_show_sourcelink = True | ||||||
|  |  | ||||||
|  | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | ||||||
|  | #html_show_sphinx = True | ||||||
|  |  | ||||||
|  | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | ||||||
|  | #html_show_copyright = True | ||||||
|  |  | ||||||
|  | # If true, an OpenSearch description file will be output, and all pages will | ||||||
|  | # contain a <link> tag referring to it.  The value of this option must be the | ||||||
|  | # base URL from which the finished HTML is served. | ||||||
|  | #html_use_opensearch = '' | ||||||
|  |  | ||||||
|  | # This is the file name suffix for HTML files (e.g. ".xhtml"). | ||||||
|  | #html_file_suffix = None | ||||||
|  |  | ||||||
|  | # Output file base name for HTML help builder. | ||||||
|  | htmlhelp_basename = 'python-escposdoc' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # -- Options for LaTeX output --------------------------------------------- | ||||||
|  |  | ||||||
|  | latex_elements = { | ||||||
|  | # The paper size ('letterpaper' or 'a4paper'). | ||||||
|  | #'papersize': 'letterpaper', | ||||||
|  |  | ||||||
|  | # The font size ('10pt', '11pt' or '12pt'). | ||||||
|  | #'pointsize': '10pt', | ||||||
|  |  | ||||||
|  | # Additional stuff for the LaTeX preamble. | ||||||
|  | #'preamble': '', | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Grouping the document tree into LaTeX files. List of tuples | ||||||
|  | # (source start file, target name, title, | ||||||
|  | #  author, documentclass [howto, manual, or own class]). | ||||||
|  | latex_documents = [ | ||||||
|  |   ('index', 'python-escpos.tex', u'python-escpos Documentation', | ||||||
|  |    u'Manuel F Martinez and others', 'manual'), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # The name of an image file (relative to this directory) to place at the top of | ||||||
|  | # the title page. | ||||||
|  | #latex_logo = None | ||||||
|  |  | ||||||
|  | # For "manual" documents, if this is true, then toplevel headings are parts, | ||||||
|  | # not chapters. | ||||||
|  | #latex_use_parts = False | ||||||
|  |  | ||||||
|  | # If true, show page references after internal links. | ||||||
|  | #latex_show_pagerefs = False | ||||||
|  |  | ||||||
|  | # If true, show URL addresses after external links. | ||||||
|  | #latex_show_urls = False | ||||||
|  |  | ||||||
|  | # Documents to append as an appendix to all manuals. | ||||||
|  | #latex_appendices = [] | ||||||
|  |  | ||||||
|  | # If false, no module index is generated. | ||||||
|  | #latex_domain_indices = True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # -- Options for manual page output --------------------------------------- | ||||||
|  |  | ||||||
|  | # One entry per manual page. List of tuples | ||||||
|  | # (source start file, name, description, authors, manual section). | ||||||
|  | man_pages = [ | ||||||
|  |     ('index', 'python-escpos', u'python-escpos Documentation', | ||||||
|  |      [u'Manuel F Martinez and others'], 1) | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # If true, show URL addresses after external links. | ||||||
|  | #man_show_urls = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # -- Options for Texinfo output ------------------------------------------- | ||||||
|  |  | ||||||
|  | # Grouping the document tree into Texinfo files. List of tuples | ||||||
|  | # (source start file, target name, title, author, | ||||||
|  | #  dir menu entry, description, category) | ||||||
|  | texinfo_documents = [ | ||||||
|  |   ('index', 'python-escpos', u'python-escpos Documentation', | ||||||
|  |    u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.', | ||||||
|  |    'Miscellaneous'), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | # Documents to append as an appendix to all manuals. | ||||||
|  | #texinfo_appendices = [] | ||||||
|  |  | ||||||
|  | # If false, no module index is generated. | ||||||
|  | #texinfo_domain_indices = True | ||||||
|  |  | ||||||
|  | # How to display URL addresses: 'footnote', 'no', or 'inline'. | ||||||
|  | #texinfo_show_urls = 'footnote' | ||||||
|  |  | ||||||
|  | # If true, do not generate a @detailmenu in the "Top" node's menu. | ||||||
|  | #texinfo_no_detailmenu = False | ||||||
							
								
								
									
										1
									
								
								doc/dev/changelog.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | .. include:: ../../CHANGELOG.rst | ||||||
							
								
								
									
										1
									
								
								doc/dev/contributing.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | .. include:: ../../CONTRIBUTING.rst | ||||||
							
								
								
									
										
											BIN
										
									
								
								doc/download/barcode.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										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 | ||||||
|  |  | ||||||
							
								
								
									
										52
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | |||||||
|  | .. python-escpos documentation master file, created by | ||||||
|  |    sphinx-quickstart on Sat Dec 26 14:28:42 2015. | ||||||
|  |    You can adapt this file completely to your liking, but it should at least | ||||||
|  |    contain the root `toctree` directive. | ||||||
|  |  | ||||||
|  | .. include:: ../README.rst | ||||||
|  |  | ||||||
|  | Content | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | .. toctree:: | ||||||
|  |    :maxdepth: 1 | ||||||
|  |    :caption: User Documentation | ||||||
|  |  | ||||||
|  |    user/installation | ||||||
|  |    user/methods | ||||||
|  |    user/printers | ||||||
|  |    user/raspi | ||||||
|  |    user/todo | ||||||
|  |    user/usage | ||||||
|  |    user/barcode | ||||||
|  |  | ||||||
|  | .. toctree:: | ||||||
|  |    :maxdepth: 1 | ||||||
|  |    :caption: Developer Documentation | ||||||
|  |  | ||||||
|  |    dev/contributing | ||||||
|  |    dev/changelog | ||||||
|  |  | ||||||
|  | .. toctree:: | ||||||
|  |    :maxdepth: 1 | ||||||
|  |    :caption: API Documentation | ||||||
|  |  | ||||||
|  |    api/escpos | ||||||
|  |    api/printer | ||||||
|  |    api/constants | ||||||
|  |    api/exceptions | ||||||
|  |    api/capabilities | ||||||
|  |    api/config | ||||||
|  |    api/image | ||||||
|  |    api/cli | ||||||
|  |    api/magicencode | ||||||
|  |    api/codepages | ||||||
|  |    api/katakana | ||||||
|  |  | ||||||
|  | Indices and tables | ||||||
|  | ================== | ||||||
|  |  | ||||||
|  | * :ref:`genindex` | ||||||
|  | * :ref:`modindex` | ||||||
|  | * :ref:`search` | ||||||
|  |  | ||||||
							
								
								
									
										242
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,242 @@ | |||||||
|  | @ECHO OFF | ||||||
|  |  | ||||||
|  | REM Command file for Sphinx documentation | ||||||
|  |  | ||||||
|  | if "%SPHINXBUILD%" == "" ( | ||||||
|  | 	set SPHINXBUILD=sphinx-build | ||||||
|  | ) | ||||||
|  | set BUILDDIR=_build | ||||||
|  | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | ||||||
|  | set I18NSPHINXOPTS=%SPHINXOPTS% . | ||||||
|  | if NOT "%PAPER%" == "" ( | ||||||
|  | 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | ||||||
|  | 	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "" goto help | ||||||
|  |  | ||||||
|  | if "%1" == "help" ( | ||||||
|  | 	:help | ||||||
|  | 	echo.Please use `make ^<target^>` where ^<target^> is one of | ||||||
|  | 	echo.  html       to make standalone HTML files | ||||||
|  | 	echo.  dirhtml    to make HTML files named index.html in directories | ||||||
|  | 	echo.  singlehtml to make a single large HTML file | ||||||
|  | 	echo.  pickle     to make pickle files | ||||||
|  | 	echo.  json       to make JSON files | ||||||
|  | 	echo.  htmlhelp   to make HTML files and a HTML help project | ||||||
|  | 	echo.  qthelp     to make HTML files and a qthelp project | ||||||
|  | 	echo.  devhelp    to make HTML files and a Devhelp project | ||||||
|  | 	echo.  epub       to make an epub | ||||||
|  | 	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter | ||||||
|  | 	echo.  text       to make text files | ||||||
|  | 	echo.  man        to make manual pages | ||||||
|  | 	echo.  texinfo    to make Texinfo files | ||||||
|  | 	echo.  gettext    to make PO message catalogs | ||||||
|  | 	echo.  changes    to make an overview over all changed/added/deprecated items | ||||||
|  | 	echo.  xml        to make Docutils-native XML files | ||||||
|  | 	echo.  pseudoxml  to make pseudoxml-XML files for display purposes | ||||||
|  | 	echo.  linkcheck  to check all external links for integrity | ||||||
|  | 	echo.  doctest    to run all doctests embedded in the documentation if enabled | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "clean" ( | ||||||
|  | 	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i | ||||||
|  | 	del /q /s %BUILDDIR%\* | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | %SPHINXBUILD% 2> nul | ||||||
|  | if errorlevel 9009 ( | ||||||
|  | 	echo. | ||||||
|  | 	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx | ||||||
|  | 	echo.installed, then set the SPHINXBUILD environment variable to point | ||||||
|  | 	echo.to the full path of the 'sphinx-build' executable. Alternatively you | ||||||
|  | 	echo.may add the Sphinx directory to PATH. | ||||||
|  | 	echo. | ||||||
|  | 	echo.If you don't have Sphinx installed, grab it from | ||||||
|  | 	echo.http://sphinx-doc.org/ | ||||||
|  | 	exit /b 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "html" ( | ||||||
|  | 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The HTML pages are in %BUILDDIR%/html. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "dirhtml" ( | ||||||
|  | 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "singlehtml" ( | ||||||
|  | 	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "pickle" ( | ||||||
|  | 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; now you can process the pickle files. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "json" ( | ||||||
|  | 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; now you can process the JSON files. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "htmlhelp" ( | ||||||
|  | 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; now you can run HTML Help Workshop with the ^ | ||||||
|  | .hhp project file in %BUILDDIR%/htmlhelp. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "qthelp" ( | ||||||
|  | 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; now you can run "qcollectiongenerator" with the ^ | ||||||
|  | .qhcp project file in %BUILDDIR%/qthelp, like this: | ||||||
|  | 	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-escpos.qhcp | ||||||
|  | 	echo.To view the help file: | ||||||
|  | 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-escpos.ghc | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "devhelp" ( | ||||||
|  | 	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "epub" ( | ||||||
|  | 	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The epub file is in %BUILDDIR%/epub. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "latex" ( | ||||||
|  | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "latexpdf" ( | ||||||
|  | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||||
|  | 	cd %BUILDDIR%/latex | ||||||
|  | 	make all-pdf | ||||||
|  | 	cd %BUILDDIR%/.. | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; the PDF files are in %BUILDDIR%/latex. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "latexpdfja" ( | ||||||
|  | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||||
|  | 	cd %BUILDDIR%/latex | ||||||
|  | 	make all-pdf-ja | ||||||
|  | 	cd %BUILDDIR%/.. | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished; the PDF files are in %BUILDDIR%/latex. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "text" ( | ||||||
|  | 	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The text files are in %BUILDDIR%/text. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "man" ( | ||||||
|  | 	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The manual pages are in %BUILDDIR%/man. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "texinfo" ( | ||||||
|  | 	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "gettext" ( | ||||||
|  | 	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The message catalogs are in %BUILDDIR%/locale. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "changes" ( | ||||||
|  | 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.The overview file is in %BUILDDIR%/changes. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "linkcheck" ( | ||||||
|  | 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Link check complete; look for any errors in the above output ^ | ||||||
|  | or in %BUILDDIR%/linkcheck/output.txt. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "doctest" ( | ||||||
|  | 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Testing of doctests in the sources finished, look at the ^ | ||||||
|  | results in %BUILDDIR%/doctest/output.txt. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "xml" ( | ||||||
|  | 	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The XML files are in %BUILDDIR%/xml. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | if "%1" == "pseudoxml" ( | ||||||
|  | 	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml | ||||||
|  | 	if errorlevel 1 exit /b 1 | ||||||
|  | 	echo. | ||||||
|  | 	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. | ||||||
|  | 	goto end | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | :end | ||||||
							
								
								
									
										
											BIN
										
									
								
								doc/pyescpos.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 162 KiB | 
							
								
								
									
										8
									
								
								doc/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | pyusb | ||||||
|  | Pillow>=2.0 | ||||||
|  | qrcode>=4.0 | ||||||
|  | pyserial | ||||||
|  | sphinx-rtd-theme | ||||||
|  | setuptools-scm | ||||||
|  | docutils>=0.12 | ||||||
|  | viivakoodi | ||||||
							
								
								
									
										34
									
								
								doc/user/barcode.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | |||||||
|  | Printing Barcodes | ||||||
|  | ----------------- | ||||||
|  | :Last Reviewed: 2016-07-31 | ||||||
|  |  | ||||||
|  | Most ESC/POS-printers implement barcode-printing. | ||||||
|  | The barcode-commandset is implemented in the barcode-method. | ||||||
|  | For a list of compatible barcodes you should check the manual of your printer. | ||||||
|  | As a rule of thumb: even older Epson-models support most 1D-barcodes. | ||||||
|  | To be sure just try some implementations and have a look at the notices below. | ||||||
|  |  | ||||||
|  | barcode-method | ||||||
|  | ~~~~~~~~~~~~~~ | ||||||
|  | The barcode-method is rather low-level and orients itself on the implementation of ESC/POS. | ||||||
|  | In the future this class could be supplemented by a high-level class that helps the user generating the payload. | ||||||
|  |  | ||||||
|  | .. py:currentmodule:: escpos.escpos | ||||||
|  |  | ||||||
|  | .. automethod:: Escpos.barcode | ||||||
|  |     :noindex: | ||||||
|  |  | ||||||
|  | CODE128 | ||||||
|  | ~~~~~~~ | ||||||
|  | Code128 barcodes need a certain format. | ||||||
|  | For now the user has to make sure that the payload is correct. | ||||||
|  | For alphanumeric CODE128 you have to preface your payload with `{B`. | ||||||
|  |  | ||||||
|  | .. code-block:: Python | ||||||
|  |  | ||||||
|  |    from escpos.printer import Dummy, Serial | ||||||
|  |    p = Serial() | ||||||
|  |    # print CODE128 012ABCDabcd | ||||||
|  |    p.barcode("{B012ABCDabcd", "CODE128", function_type="B") | ||||||
|  |  | ||||||
|  | A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_. | ||||||
							
								
								
									
										48
									
								
								doc/user/installation.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,48 @@ | |||||||
|  | ************ | ||||||
|  | Installation | ||||||
|  | ************ | ||||||
|  |  | ||||||
|  | :Last Reviewed: 2016-07-23 | ||||||
|  |  | ||||||
|  | Installation with PIP | ||||||
|  | --------------------- | ||||||
|  | Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter: | ||||||
|  |  | ||||||
|  |     :: | ||||||
|  |  | ||||||
|  |         pip install python-escpos | ||||||
|  |  | ||||||
|  | This should install all necessary dependencies. Apart from that python-escpos should also be | ||||||
|  | available as a Debian package. If you want to always benefit from the newest stable releases you should probably | ||||||
|  | install from PyPi. | ||||||
|  |  | ||||||
|  | Setup udev for USB-Printers | ||||||
|  | --------------------------- | ||||||
|  | 1. Get the *Product ID* and *Vendor ID* from the lsusb command | ||||||
|  |    ``# lsusb  Bus 002 Device 001: ID 1a2b:1a2b Device name`` | ||||||
|  |  | ||||||
|  | 2. Create a udev rule to let users belonging to *dialout* group use the | ||||||
|  |    printer. You can create the file | ||||||
|  |    ``/etc/udev/rules.d/99-escpos.rules`` and add the following: | ||||||
|  |    ``SUBSYSTEM=="usb", ATTRS{idVendor}=="1a2b", ATTRS{idProduct}=="1a2b", MODE="0664", GROUP="dialout"`` | ||||||
|  |    Replace *idVendor* and *idProduct* hex numbers with the ones that you | ||||||
|  |    got from the previous step. Note that you can either, add yourself to | ||||||
|  |    "dialout" group, or use another group you already belongs instead | ||||||
|  |    "dialout" and set it in the ``GROUP`` parameter in the above rule. | ||||||
|  |  | ||||||
|  | 3. Restart udev ``# sudo service udev restart`` In some new systems it | ||||||
|  |    is done with ``# sudo udevadm control --reload`` | ||||||
|  |  | ||||||
|  | Enabling tab-completion in CLI | ||||||
|  | ------------------------------ | ||||||
|  | python-escpos has a CLI with tab-completion. This is realised with ``argcomplete``. | ||||||
|  | In order for this to work you have to enable tab-completion, which is described in | ||||||
|  | the `manual of argcomplete <https://argcomplete.readthedocs.io>`__. | ||||||
|  |  | ||||||
|  | If you only want to enable it for python-escpos, or global activation does not work, try this: | ||||||
|  |  | ||||||
|  |     :: | ||||||
|  |  | ||||||
|  |         eval "$(register-python-argcomplete python-escpos)" | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								doc/user/methods.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | ******* | ||||||
|  | Methods | ||||||
|  | ******* | ||||||
|  | :Last Reviewed: 2017-01-25 | ||||||
|  |  | ||||||
|  | Escpos class | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | The core part of this libraries API is the Escpos class. | ||||||
|  | You use it by instantiating a  :doc:`printer <printers>` which is a child of Escpos. | ||||||
|  | The following methods are available: | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.escpos.Escpos | ||||||
|  |     :members: | ||||||
|  |     :member-order: bysource | ||||||
|  |     :noindex: | ||||||
|  |  | ||||||
							
								
								
									
										77
									
								
								doc/user/printers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | |||||||
|  | ******** | ||||||
|  | Printers | ||||||
|  | ******** | ||||||
|  | :Last Reviewed: 2017-01-25 | ||||||
|  |  | ||||||
|  | As of now there are 5 different type of printer implementations. | ||||||
|  |  | ||||||
|  | USB | ||||||
|  | --- | ||||||
|  | The USB-class uses pyusb and libusb to communicate with USB-based | ||||||
|  | printers. Note that this driver is not suited for USB-to-Serial-adapters | ||||||
|  | and similiar devices, but only for those implementing native USB. | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.printer.Usb | ||||||
|  |     :members: | ||||||
|  |     :special-members: | ||||||
|  |     :member-order: bysource | ||||||
|  |     :noindex: | ||||||
|  |  | ||||||
|  | Serial | ||||||
|  | ------ | ||||||
|  | This driver uses pyserial in order to communicate with serial devices. | ||||||
|  | If you are using an USB-based adapter to connect to the serial port, | ||||||
|  | then you should also use this driver. | ||||||
|  | The configuration is often based on DIP-switches that you can set on your | ||||||
|  | printer. For the hardware-configuration please refer to your printer's manual. | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.printer.Serial | ||||||
|  |      :members: | ||||||
|  |      :special-members: | ||||||
|  |      :member-order: bysource | ||||||
|  |      :noindex: | ||||||
|  |  | ||||||
|  | Network | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | This driver is based on the socket class. | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.printer.Network | ||||||
|  |       :members: | ||||||
|  |       :special-members: | ||||||
|  |       :member-order: bysource | ||||||
|  |       :noindex: | ||||||
|  |  | ||||||
|  | Troubleshooting | ||||||
|  | ^^^^^^^^^^^^^^^ | ||||||
|  | Problems with a network-attached printer can have numerous causes. Make sure that your device has a proper IP address. | ||||||
|  | Often you can check the IP address by triggering the self-test of the device. As a next step try to send text | ||||||
|  | manually to the device. You could use for example: | ||||||
|  |  | ||||||
|  |     :: | ||||||
|  |  | ||||||
|  |             echo "OK\n" | nc IPADDRESS 9100 | ||||||
|  |             # the port number is often 9100 | ||||||
|  |  | ||||||
|  | As a last resort try to reset the interface of the printer. This should be described in its manual. | ||||||
|  |  | ||||||
|  | File | ||||||
|  | ---- | ||||||
|  | This printer "prints" just into a file-handle. Especially on \*nix-systems this comes very handy. | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.printer.File | ||||||
|  |       :members: | ||||||
|  |       :special-members: | ||||||
|  |       :member-order: bysource | ||||||
|  |       :noindex: | ||||||
|  |  | ||||||
|  | Dummy | ||||||
|  | ----- | ||||||
|  | The Dummy-printer is mainly for testing- and debugging-purposes. It stores | ||||||
|  | all of the "output" as raw ESC/POS in a string and returns that. | ||||||
|  |  | ||||||
|  | .. autoclass:: escpos.printer.Dummy | ||||||
|  |       :members: | ||||||
|  |       :member-order: bysource | ||||||
|  |       :noindex: | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								doc/user/raspi.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | |||||||
|  | ************ | ||||||
|  | Raspberry Pi | ||||||
|  | ************ | ||||||
|  |  | ||||||
|  | :Last Reviewed: 2017-01-05 | ||||||
|  |  | ||||||
|  | This instructions were tested on Raspbian Jessie. | ||||||
|  |  | ||||||
|  | .. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to | ||||||
|  |     a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are | ||||||
|  |     based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would | ||||||
|  |     **thus destroy your interface**. Connect both systems with an appropriate *level shifter*. | ||||||
|  |  | ||||||
|  | Dependencies | ||||||
|  | ------------ | ||||||
|  | First, install the packages available on Raspbian. | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |        sudo apt-get install python3 python3-setuptools python3-pip libjpeg8-dev | ||||||
|  |  | ||||||
|  | Installation | ||||||
|  | ------------ | ||||||
|  | You can install by using pip3. | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     sudo pip3 install --upgrade pip | ||||||
|  |     sudo pip3 install python-escpos | ||||||
|  |  | ||||||
|  | Run | ||||||
|  | --- | ||||||
|  | You need sudo and python3 to run your program. | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     sudo python3 your-program.py | ||||||
|  |  | ||||||
|  | Now you can attach your printer and and test it with the example code in the project's set of examples. | ||||||
|  | You can find that in the `project-repository <https://github.com/python-escpos/python-escpos>`__. | ||||||
|  |  | ||||||
|  | For more details on this check the :doc:`installation-manual <installation>`. | ||||||
							
								
								
									
										43
									
								
								doc/user/todo.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | |||||||
|  | **** | ||||||
|  | TODO | ||||||
|  | **** | ||||||
|  |  | ||||||
|  | Introduction | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | python-escpos is the initial idea, from here we can start to build a | ||||||
|  | robust library to get most of the ESC/POS printers working with this | ||||||
|  | library. | ||||||
|  |  | ||||||
|  | Eventually, this library must be able to cover almost all the defined | ||||||
|  | models detailed in the ESC/POS Command Specification Manual. | ||||||
|  |  | ||||||
|  | Details | ||||||
|  | ------- | ||||||
|  |  | ||||||
|  | What things are planned to work on? | ||||||
|  |  | ||||||
|  | Testing | ||||||
|  | ~~~~~~~ | ||||||
|  |  | ||||||
|  | * Test on many printers as possible (USB, Serial, Network) | ||||||
|  | * automate testing | ||||||
|  |  | ||||||
|  | Design | ||||||
|  | ~~~~~~ | ||||||
|  |  | ||||||
|  | * Add all those sequences which are not common, but part of the ESC/POS | ||||||
|  |   Command Specifications. | ||||||
|  |  | ||||||
|  |   *  Port to Python 3 | ||||||
|  |   *  Windows compatibility (hidapi instead libusb?) | ||||||
|  |   *  PDF417 support | ||||||
|  |  | ||||||
|  | * use something similar to the `capabilities` in escpos-php | ||||||
|  |  | ||||||
|  | Todos in the codebase | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | .. todolist:: | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										287
									
								
								doc/user/usage.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,287 @@ | |||||||
|  | ***** | ||||||
|  | Usage | ||||||
|  | ***** | ||||||
|  | :Last Reviewed: 2017-06-10 | ||||||
|  |  | ||||||
|  | Define your printer | ||||||
|  | ------------------- | ||||||
|  |  | ||||||
|  | USB printer | ||||||
|  | ^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | Before creating your Python ESC/POS printer instance, consult the system to obtain | ||||||
|  | the printer parameters. This is done with the 'lsusb' command. | ||||||
|  |  | ||||||
|  | Run the command and look for the "Vendor ID" and "Product ID" and write | ||||||
|  | down the values. These values are displayed just before the name | ||||||
|  | of the device with the following format: | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     xxxx:xxxx | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     # lsusb | ||||||
|  |     Bus 002 Device 001: ID 04b8:0202 Epson ... | ||||||
|  |  | ||||||
|  | Write down the the values in question, then issue the following command | ||||||
|  | so you can get the "Interface" number and "End Point" | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     # lsusb -vvv -d xxxx:xxxx | grep iInterface | ||||||
|  |         iInterface              0 | ||||||
|  |     # lsusb -vvv -d xxxx:xxxx | grep bEndpointAddress | grep OUT | ||||||
|  |           bEndpointAddress     0x01  EP 1 OUT | ||||||
|  |  | ||||||
|  | The first command will yield the "Interface" number that must be handy | ||||||
|  | to have and the second yields the "Output Endpoint" address. | ||||||
|  |  | ||||||
|  | **USB Printer initialization** | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     Epson = printer.Usb(0x04b8,0x0202) | ||||||
|  |  | ||||||
|  | By default the "Interface" number is "0" and the "Output Endpoint" | ||||||
|  | address is "0x01". If you have other values then you can define them on | ||||||
|  | your instance. So, assuming that we have another printer where in\_ep is | ||||||
|  | on 0x81 and out\_ep=0x02, then the printer definition should look like: | ||||||
|  |  | ||||||
|  | **Generic USB Printer initialization** | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     Generic = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02) | ||||||
|  |  | ||||||
|  | Network printer | ||||||
|  | ^^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | You only need the IP of your printer, either because it is getting its | ||||||
|  | IP by DHCP or you set it manually. | ||||||
|  |  | ||||||
|  | **Network Printer initialization** | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     Epson = printer.Network('192.168.1.99') | ||||||
|  |  | ||||||
|  | Serial printer | ||||||
|  | ^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | Most of the default values set by the DIP switches for the serial | ||||||
|  | printers, have been set as default on the serial printer class, so the | ||||||
|  | only thing you need to know is which serial port the printer is connected | ||||||
|  | to. | ||||||
|  |  | ||||||
|  | **Serial printer initialization** | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     Epson = printer.Serial('/dev/tty0') | ||||||
|  |  | ||||||
|  | Other printers | ||||||
|  | ^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | Some printers under `/dev` can't be used or initialized with any of the | ||||||
|  | methods described above. Usually, those are printers used by printcap, | ||||||
|  | however, if you know the device name, you could try to initialize by | ||||||
|  | passing the device node name. | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     Epson = printer.File('/dev/usb/lp1') | ||||||
|  |  | ||||||
|  | The default is "/dev/usb/lp0", so if the printer is located on that | ||||||
|  | node, then you don't necessary need to pass the node name. | ||||||
|  |  | ||||||
|  | Define your instance | ||||||
|  | -------------------- | ||||||
|  |  | ||||||
|  | The following example demonstrates how to initialize the Epson TM-TI88IV | ||||||
|  | on a USB interface. | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     from escpos import * | ||||||
|  |     """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ | ||||||
|  |     Epson = printer.Usb(0x04b8,0x0202) | ||||||
|  |     # Print text | ||||||
|  |     Epson.text('Hello World\n') | ||||||
|  |     # Print image | ||||||
|  |     Epson.image('logo.gif') | ||||||
|  |     # Print QR Code | ||||||
|  |     Epson.qr('You can readme from your smartphone') | ||||||
|  |     # Print barcode | ||||||
|  |     Epson.barcode('1324354657687','EAN13',64,2,'','') | ||||||
|  |     # Cut paper | ||||||
|  |     Epson.cut() | ||||||
|  |  | ||||||
|  | Configuration File | ||||||
|  | ------------------ | ||||||
|  |  | ||||||
|  | You can create a configuration file for python-escpos. This will | ||||||
|  | allow you to use the CLI, and skip some setup when using the library | ||||||
|  | programmatically. | ||||||
|  |  | ||||||
|  | The default configuration file is named ``config.yaml`` and uses the YAML | ||||||
|  | format. For windows it is probably at:: | ||||||
|  |  | ||||||
|  |     %appdata%/python-escpos/config.yaml | ||||||
|  |  | ||||||
|  | And for linux:: | ||||||
|  |  | ||||||
|  |     $HOME/.config/python-escpos/config.yaml | ||||||
|  |  | ||||||
|  | If you aren't sure, run:: | ||||||
|  |  | ||||||
|  |     from escpos import config | ||||||
|  |     c = config.Config() | ||||||
|  |     c.load() | ||||||
|  |  | ||||||
|  | If it can't find the configuration file in the default location, it will tell | ||||||
|  | you where it's looking. You can always pass a path, or a list of paths, to | ||||||
|  | the ``load()`` method. | ||||||
|  |  | ||||||
|  | To load the configured printer, run:: | ||||||
|  |  | ||||||
|  |     from escpos import config | ||||||
|  |     c = config.Config() | ||||||
|  |     printer = c.printer() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | The printer section | ||||||
|  | ^^^^^^^^^^^^^^^^^^^ | ||||||
|  |  | ||||||
|  | The ``printer`` configuration section defines a default printer to create. | ||||||
|  |  | ||||||
|  | The only required paramter is ``type``. The value of this has to be one of the | ||||||
|  | printers defined in :doc:`/user/printers`. | ||||||
|  |  | ||||||
|  | The rest of the given parameters will be passed on to the initialization of the printer class. | ||||||
|  | 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:: | ||||||
|  |  | ||||||
|  |     printer: | ||||||
|  |             type: File | ||||||
|  |             devfile: /dev/someprinter | ||||||
|  |  | ||||||
|  | And for a network printer:: | ||||||
|  |  | ||||||
|  |     printer: | ||||||
|  |             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 | ||||||
|  | -------------------------------------- | ||||||
|  |  | ||||||
|  | Imagine you have a file with ESC/POS-commands in binary form. This could be useful for testing capabilities of your | ||||||
|  | printer with a known working combination of commands. | ||||||
|  | You can print this data with the following code, using the standard methods of python-escpos. (This is an | ||||||
|  | advantage of the fact that `_raw()` accepts binary strings.) | ||||||
|  |  | ||||||
|  | :: | ||||||
|  |  | ||||||
|  |     from escpos import printer | ||||||
|  |     p = printer.Serial()  # adapt this to your printer model | ||||||
|  |  | ||||||
|  |     file = open('binary-blob.bin', 'rb')  # read in the file containing your commands in binary-mode | ||||||
|  |     data = file.read() | ||||||
|  |     file.close() | ||||||
|  |  | ||||||
|  |     p._raw(data) | ||||||
|  |  | ||||||
|  | That's all, the printer should then print your data. You can also use this technique to let others reproduce an issue | ||||||
|  | that you have found. (Just "print" your commands to a File-printer on your local filesystem.) | ||||||
|  | However, please keep in mind, that often it is easier and better to just supply the code that you are using. | ||||||
|  |  | ||||||
|  | Here you can download an example, that will print a set of common barcodes: | ||||||
|  |  | ||||||
|  |     * :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_ | ||||||
|  |  | ||||||
|  | Advanced Usage: change capabilities-profile | ||||||
|  | ------------------------------------------- | ||||||
|  |  | ||||||
|  | Packaged together with the escpos-code is a capabilities-file. This file in | ||||||
|  | JSON-format describes the capabilities of different printers. It is developed and hosted in | ||||||
|  | `escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_. | ||||||
|  |  | ||||||
|  | Certain applications like the usage of `cx_freeze <https://cx-freeze.readthedocs.io>`_ might change the | ||||||
|  | packaging structure. This leads to the capabilities-profile not being found. | ||||||
|  | In this case you can use the environment-variable `ESCPOS_CAPABILITIES_FILE`. | ||||||
|  | The following code is an example. | ||||||
|  |  | ||||||
|  | .. code-block:: shell | ||||||
|  |  | ||||||
|  |    # use packaged capabilities-profile | ||||||
|  |    python-escpos cut | ||||||
|  |  | ||||||
|  |    # use capabilities-profile that you have put in /usr/python-escpos | ||||||
|  |    export ESCPOS_CAPABILITIES_FILE=/usr/python-escpos/capabilities.json | ||||||
|  |    python-escpos cut | ||||||
|  |  | ||||||
|  |    # use packaged file again | ||||||
|  |    unset ESCPOS_CAPABILITIES_FILE | ||||||
|  |    python-escpos cut | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Hint: preprocess printing | ||||||
|  | ------------------------- | ||||||
|  |  | ||||||
|  | Printing images directly to the printer is rather slow. | ||||||
|  | One factor that slows down the process is the transmission over e.g. serial port. | ||||||
|  |  | ||||||
|  | Apart from configuring your printer to use the maximum baudrate (in the case of serial-printers), there is not much | ||||||
|  | that you can do. | ||||||
|  | However you could use the :py:class:`escpos.printer.Dummy`-printer to preprocess your image. | ||||||
|  | This is probably best explained by an example: | ||||||
|  |  | ||||||
|  | .. code-block:: Python | ||||||
|  |  | ||||||
|  |    from escpos.printer import Serial, Dummy | ||||||
|  |  | ||||||
|  |    p = Serial() | ||||||
|  |    d = Dummy() | ||||||
|  |  | ||||||
|  |    # create ESC/POS for the print job, this should go really fast | ||||||
|  |    d.text('This is my image:\n') | ||||||
|  |    d.image('funny_cat.png') | ||||||
|  |    d.cut() | ||||||
|  |  | ||||||
|  |    # send code to printer | ||||||
|  |    p._raw(d.output) | ||||||
|  |  | ||||||
|  | This way you could also store the code in a file and print it later. | ||||||
|  | You could then for example print the code from another process than your main-program and thus reduce the waiting time. | ||||||
|  | (Of course this will not make the printer print faster.) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| __all__ = ["constants","escpos","exceptions","printer"] |  | ||||||
| @@ -1,75 +0,0 @@ | |||||||
| """ ESC/POS Commands (Constants) """ |  | ||||||
|  |  | ||||||
| # Feed control sequences |  | ||||||
| CTL_LF     = '\x0a'              # Print and line feed |  | ||||||
| CTL_FF     = '\x0c'              # Form feed |  | ||||||
| CTL_CR     = '\x0d'              # Carriage return |  | ||||||
| CTL_HT     = '\x09'              # Horizontal tab |  | ||||||
| CTL_SET_HT = '\x1b\x44'          # Set horizontal tab positions |  | ||||||
| CTL_VT     = '\x1b\x64\x04'      # Vertical tab |  | ||||||
| # Printer hardware |  | ||||||
| HW_INIT    = '\x1b\x40'          # Clear data in buffer and reset modes |  | ||||||
| HW_SELECT  = '\x1b\x3d\x01'      # Printer select |  | ||||||
| HW_RESET   = '\x1b\x3f\x0a\x00'  # Reset printer hardware |  | ||||||
| # Cash Drawer |  | ||||||
| CD_KICK_2  = '\x1b\x70\x00'      # Sends a pulse to pin 2 []  |  | ||||||
| CD_KICK_5  = '\x1b\x70\x01'      # Sends a pulse to pin 5 []  |  | ||||||
| # Paper |  | ||||||
| PAPER_FULL_CUT  = '\x1d\x56\x00' # Full cut paper |  | ||||||
| PAPER_PART_CUT  = '\x1d\x56\x01' # Partial cut paper |  | ||||||
| # Text format    |  | ||||||
| TXT_NORMAL      = '\x1b\x21\x00' # Normal text |  | ||||||
| TXT_2HEIGHT     = '\x1b\x21\x10' # Double height text |  | ||||||
| TXT_2WIDTH      = '\x1b\x21\x20' # Double width text |  | ||||||
| TXT_UNDERL_OFF  = '\x1b\x2d\x00' # Underline font OFF |  | ||||||
| TXT_UNDERL_ON   = '\x1b\x2d\x01' # Underline font 1-dot ON |  | ||||||
| TXT_UNDERL2_ON  = '\x1b\x2d\x02' # Underline font 2-dot ON |  | ||||||
| TXT_BOLD_OFF    = '\x1b\x45\x00' # Bold font OFF |  | ||||||
| TXT_BOLD_ON     = '\x1b\x45\x01' # Bold font ON |  | ||||||
| TXT_FONT_A      = '\x1b\x4d\x00' # Font type A |  | ||||||
| TXT_FONT_B      = '\x1b\x4d\x01' # Font type B |  | ||||||
| TXT_ALIGN_LT    = '\x1b\x61\x00' # Left justification |  | ||||||
| TXT_ALIGN_CT    = '\x1b\x61\x01' # Centering |  | ||||||
| TXT_ALIGN_RT    = '\x1b\x61\x02' # Right justification |  | ||||||
| # Char code table |  | ||||||
| CHARCODE_PC437  = '\x1b\x74\x00' # USA: Standard Europe |  | ||||||
| CHARCODE_JIS    = '\x1b\x74\x01' # Japanese Katakana |  | ||||||
| CHARCODE_PC850  = '\x1b\x74\x02' # Multilingual |  | ||||||
| CHARCODE_PC860  = '\x1b\x74\x03' # Portuguese |  | ||||||
| CHARCODE_PC863  = '\x1b\x74\x04' # Canadian-French |  | ||||||
| CHARCODE_PC865  = '\x1b\x74\x05' # Nordic |  | ||||||
| CHARCODE_WEU    = '\x1b\x74\x06' # Simplified Kanji, Hirakana |  | ||||||
| CHARCODE_GREEK  = '\x1b\x74\x07' # Simplified Kanji |  | ||||||
| CHARCODE_HEBREW = '\x1b\x74\x08' # Simplified Kanji |  | ||||||
| CHARCODE_PC1252 = '\x1b\x74\x11' # Western European Windows Code Set |  | ||||||
| CHARCODE_PC866  = '\x1b\x74\x12' # Cirillic #2 |  | ||||||
| CHARCODE_PC852  = '\x1b\x74\x13' # Latin 2 |  | ||||||
| CHARCODE_PC858  = '\x1b\x74\x14' # Euro |  | ||||||
| CHARCODE_THAI42 = '\x1b\x74\x15' # Thai character code 42 |  | ||||||
| CHARCODE_THAI11 = '\x1b\x74\x16' # Thai character code 11 |  | ||||||
| CHARCODE_THAI13 = '\x1b\x74\x17' # Thai character code 13 |  | ||||||
| CHARCODE_THAI14 = '\x1b\x74\x18' # Thai character code 14 |  | ||||||
| CHARCODE_THAI16 = '\x1b\x74\x19' # Thai character code 16 |  | ||||||
| CHARCODE_THAI17 = '\x1b\x74\x1a' # Thai character code 17 |  | ||||||
| CHARCODE_THAI18 = '\x1b\x74\x1b' # Thai character code 18 |  | ||||||
| # Barcode format |  | ||||||
| BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF |  | ||||||
| BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above |  | ||||||
| BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below |  | ||||||
| BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below |  | ||||||
| BARCODE_FONT_A  = '\x1d\x66\x00' # Font type A for HRI barcode chars |  | ||||||
| BARCODE_FONT_B  = '\x1d\x66\x01' # Font type B for HRI barcode chars |  | ||||||
| BARCODE_HEIGHT  = '\x1d\x68\x64' # Barcode Height [1-255] |  | ||||||
| BARCODE_WIDTH   = '\x1d\x77\x03' # Barcode Width  [2-6] |  | ||||||
| BARCODE_UPC_A   = '\x1d\x6b\x00' # Barcode type UPC-A |  | ||||||
| BARCODE_UPC_E   = '\x1d\x6b\x01' # Barcode type UPC-E |  | ||||||
| BARCODE_EAN13   = '\x1d\x6b\x02' # Barcode type EAN13 |  | ||||||
| BARCODE_EAN8    = '\x1d\x6b\x03' # Barcode type EAN8 |  | ||||||
| BARCODE_CODE39  = '\x1d\x6b\x04' # Barcode type CODE39 |  | ||||||
| BARCODE_ITF     = '\x1d\x6b\x05' # Barcode type ITF |  | ||||||
| BARCODE_NW7     = '\x1d\x6b\x06' # Barcode type NW7 |  | ||||||
| # Image format   |  | ||||||
| S_RASTER_N      = '\x1d\x76\x30\x00' # Set raster image normal size |  | ||||||
| S_RASTER_2W     = '\x1d\x76\x30\x01' # Set raster image double width |  | ||||||
| S_RASTER_2H     = '\x1d\x76\x30\x02' # Set raster image double height |  | ||||||
| S_RASTER_Q      = '\x1d\x76\x30\x03' # Set raster image quadruple |  | ||||||
							
								
								
									
										331
									
								
								escpos/escpos.py
									
									
									
									
									
								
							
							
						
						| @@ -1,331 +0,0 @@ | |||||||
| #!/usr/bin/python |  | ||||||
| """ |  | ||||||
| @author: Manuel F Martinez <manpaz@bashlinux.com> |  | ||||||
| @organization: Bashlinux |  | ||||||
| @copyright: Copyright (c) 2012 Bashlinux |  | ||||||
| @license: GPL |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     import Image |  | ||||||
| except ImportError: |  | ||||||
|     from PIL import Image |  | ||||||
|  |  | ||||||
| import qrcode |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| from constants import * |  | ||||||
| from exceptions import * |  | ||||||
|  |  | ||||||
| class Escpos: |  | ||||||
|     """ ESC/POS Printer object """ |  | ||||||
|     device    = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _check_image_size(self, size): |  | ||||||
|         """ Check and fix the size of the image to 32 bits """ |  | ||||||
|         if size % 32 == 0: |  | ||||||
|             return (0, 0) |  | ||||||
|         else: |  | ||||||
|             image_border = 32 - (size % 32) |  | ||||||
|             if (image_border % 2) == 0: |  | ||||||
|                 return (image_border / 2, image_border / 2) |  | ||||||
|             else: |  | ||||||
|                 return (image_border / 2, (image_border / 2) + 1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _print_image(self, line, size): |  | ||||||
|         """ Print formatted image """ |  | ||||||
|         i = 0 |  | ||||||
|         cont = 0 |  | ||||||
|         buffer = "" |  | ||||||
|         |  | ||||||
|         self._raw(S_RASTER_N) |  | ||||||
|         buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0) |  | ||||||
|         self._raw(buffer.decode('hex')) |  | ||||||
|         buffer = "" |  | ||||||
|  |  | ||||||
|         while i < len(line): |  | ||||||
|             hex_string = int(line[i:i+8],2) |  | ||||||
|             buffer += "%02X" % hex_string |  | ||||||
|             i += 8 |  | ||||||
|             cont += 1 |  | ||||||
|             if cont % 4 == 0: |  | ||||||
|                 self._raw(buffer.decode("hex")) |  | ||||||
|                 buffer = "" |  | ||||||
|                 cont = 0 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _convert_image(self, im): |  | ||||||
|         """ Parse image and prepare it to a printable format """ |  | ||||||
|         pixels   = [] |  | ||||||
|         pix_line = "" |  | ||||||
|         im_left  = "" |  | ||||||
|         im_right = "" |  | ||||||
|         switch   = 0 |  | ||||||
|         img_size = [ 0, 0 ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if im.size[0] > 512: |  | ||||||
|             print  ("WARNING: Image is wider than 512 and could be truncated at print time ") |  | ||||||
|         if im.size[1] > 255: |  | ||||||
|             raise ImageSizeError() |  | ||||||
|  |  | ||||||
|         im_border = self._check_image_size(im.size[0]) |  | ||||||
|         for i in range(im_border[0]): |  | ||||||
|             im_left += "0" |  | ||||||
|         for i in range(im_border[1]): |  | ||||||
|             im_right += "0" |  | ||||||
|  |  | ||||||
|         for y in range(im.size[1]): |  | ||||||
|             img_size[1] += 1 |  | ||||||
|             pix_line += im_left |  | ||||||
|             img_size[0] += im_border[0] |  | ||||||
|             for x in range(im.size[0]): |  | ||||||
|                 img_size[0] += 1 |  | ||||||
|                 RGB = im.getpixel((x, y)) |  | ||||||
|                 im_color = (RGB[0] + RGB[1] + RGB[2]) |  | ||||||
|                 im_pattern = "1X0" |  | ||||||
|                 pattern_len = len(im_pattern) |  | ||||||
|                 switch = (switch - 1 ) * (-1) |  | ||||||
|                 for x in range(pattern_len): |  | ||||||
|                     if im_color <= (255 * 3 / pattern_len * (x+1)): |  | ||||||
|                         if im_pattern[x] == "X": |  | ||||||
|                             pix_line += "%d" % switch |  | ||||||
|                         else: |  | ||||||
|                             pix_line += im_pattern[x] |  | ||||||
|                         break |  | ||||||
|                     elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3): |  | ||||||
|                         pix_line += im_pattern[-1] |  | ||||||
|                         break  |  | ||||||
|             pix_line += im_right |  | ||||||
|             img_size[0] += im_border[1] |  | ||||||
|  |  | ||||||
|         self._print_image(pix_line, img_size) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def image(self,path_img): |  | ||||||
|         """ Open image file """ |  | ||||||
|         im_open = Image.open(path_img) |  | ||||||
|         im = im_open.convert("RGB") |  | ||||||
|         # Convert the RGB image in printable image |  | ||||||
|         self._convert_image(im) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def qr(self,text): |  | ||||||
|         """ Print QR Code for the provided string """ |  | ||||||
|         qr_code = qrcode.QRCode(version=4, box_size=4, border=1) |  | ||||||
|         qr_code.add_data(text) |  | ||||||
|         qr_code.make(fit=True) |  | ||||||
|         qr_img = qr_code.make_image() |  | ||||||
|         im = qr_img._img.convert("RGB") |  | ||||||
|         # Convert the RGB image in printable image |  | ||||||
|         self._convert_image(im) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def charcode(self,code): |  | ||||||
|         """ Set Character Code Table """ |  | ||||||
|         if code.upper() == "USA": |  | ||||||
|             self._raw(CHARCODE_PC437) |  | ||||||
|         elif code.upper() == "JIS": |  | ||||||
|             self._raw(CHARCODE_JIS) |  | ||||||
|         elif code.upper() == "MULTILINGUAL": |  | ||||||
|             self._raw(CHARCODE_PC850) |  | ||||||
|         elif code.upper() == "PORTUGUESE": |  | ||||||
|             self._raw(CHARCODE_PC860) |  | ||||||
|         elif code.upper() == "CA_FRENCH": |  | ||||||
|             self._raw(CHARCODE_PC863) |  | ||||||
|         elif code.upper() == "NORDIC": |  | ||||||
|             self._raw(CHARCODE_PC865) |  | ||||||
|         elif code.upper() == "WEST_EUROPE": |  | ||||||
|             self._raw(CHARCODE_WEU) |  | ||||||
|         elif code.upper() == "GREEK": |  | ||||||
|             self._raw(CHARCODE_GREEK) |  | ||||||
|         elif code.upper() == "HEBREW": |  | ||||||
|             self._raw(CHARCODE_HEBREW) |  | ||||||
|         elif code.upper() == "LATVIAN": |  | ||||||
|             self._raw(CHARCODE_PC755) |  | ||||||
|         elif code.upper() == "WPC1252": |  | ||||||
|             self._raw(CHARCODE_PC1252) |  | ||||||
|         elif code.upper() == "CIRILLIC2": |  | ||||||
|             self._raw(CHARCODE_PC866) |  | ||||||
|         elif code.upper() == "LATIN2": |  | ||||||
|             self._raw(CHARCODE_PC852) |  | ||||||
|         elif code.upper() == "EURO": |  | ||||||
|             self._raw(CHARCODE_PC858) |  | ||||||
|         elif code.upper() == "THAI42": |  | ||||||
|             self._raw(CHARCODE_THAI42) |  | ||||||
|         elif code.upper() == "THAI11": |  | ||||||
|             self._raw(CHARCODE_THAI11) |  | ||||||
|         elif code.upper() == "THAI13": |  | ||||||
|             self._raw(CHARCODE_THAI13) |  | ||||||
|         elif code.upper() == "THAI14": |  | ||||||
|             self._raw(CHARCODE_THAI14) |  | ||||||
|         elif code.upper() == "THAI16": |  | ||||||
|             self._raw(CHARCODE_THAI16) |  | ||||||
|         elif code.upper() == "THAI17": |  | ||||||
|             self._raw(CHARCODE_THAI17) |  | ||||||
|         elif code.upper() == "THAI18": |  | ||||||
|             self._raw(CHARCODE_THAI18) |  | ||||||
|         else: |  | ||||||
|             raise CharCode_error() |  | ||||||
|  |  | ||||||
|     def barcode(self, code, bc, width, height, pos, font): |  | ||||||
|         """ Print Barcode """ |  | ||||||
|         # Align Bar Code() |  | ||||||
|         self._raw(TXT_ALIGN_CT) |  | ||||||
|         # Height |  | ||||||
|         if height >=2 or height <=6: |  | ||||||
|             self._raw(BARCODE_HEIGHT) |  | ||||||
|         else: |  | ||||||
|             raise BarcodeSizeError() |  | ||||||
|         # Width |  | ||||||
|         if width >= 1 or width <=255: |  | ||||||
|             self._raw(BARCODE_WIDTH) |  | ||||||
|         else: |  | ||||||
|             raise BarcodeSizeError() |  | ||||||
|         # Font |  | ||||||
|         if font.upper() == "B": |  | ||||||
|             self._raw(BARCODE_FONT_B) |  | ||||||
|         else: # DEFAULT FONT: A |  | ||||||
|             self._raw(BARCODE_FONT_A) |  | ||||||
|         # Position |  | ||||||
|         if pos.upper() == "OFF": |  | ||||||
|             self._raw(BARCODE_TXT_OFF) |  | ||||||
|         elif pos.upper() == "BOTH": |  | ||||||
|             self._raw(BARCODE_TXT_BTH) |  | ||||||
|         elif pos.upper() == "ABOVE": |  | ||||||
|             self._raw(BARCODE_TXT_ABV) |  | ||||||
|         else:  # DEFAULT POSITION: BELOW  |  | ||||||
|             self._raw(BARCODE_TXT_BLW) |  | ||||||
|         # Type  |  | ||||||
|         if bc.upper() == "UPC-A": |  | ||||||
|             self._raw(BARCODE_UPC_A) |  | ||||||
|         elif bc.upper() == "UPC-E": |  | ||||||
|             self._raw(BARCODE_UPC_E) |  | ||||||
|         elif bc.upper() == "EAN13": |  | ||||||
|             self._raw(BARCODE_EAN13) |  | ||||||
|         elif bc.upper() == "EAN8": |  | ||||||
|             self._raw(BARCODE_EAN8) |  | ||||||
|         elif bc.upper() == "CODE39": |  | ||||||
|             self._raw(BARCODE_CODE39) |  | ||||||
|         elif bc.upper() == "ITF": |  | ||||||
|             self._raw(BARCODE_ITF) |  | ||||||
|         elif bc.upper() == "NW7": |  | ||||||
|             self._raw(BARCODE_NW7) |  | ||||||
|         else: |  | ||||||
|             raise BarcodeTypeError() |  | ||||||
|         # Print Code |  | ||||||
|         if code: |  | ||||||
|             self._raw(code) |  | ||||||
|         else: |  | ||||||
|             raise exception.BarcodeCodeError() |  | ||||||
|  |  | ||||||
|          |  | ||||||
|     def text(self, txt): |  | ||||||
|         """ Print alpha-numeric text """ |  | ||||||
|         if txt: |  | ||||||
|             self._raw(txt) |  | ||||||
|         else: |  | ||||||
|             raise TextError() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set(self, align='left', font='a', type='normal', width=1, height=1): |  | ||||||
|         """ Set text properties """ |  | ||||||
|         # Width |  | ||||||
|         if height == 2 and width == 2: |  | ||||||
|             self._raw(TXT_2WIDTH) |  | ||||||
|             self._raw(TXT_2HEIGHT) |  | ||||||
|         elif height == 2 and width != 2: |  | ||||||
|             self._raw(TXT_NORMAL) |  | ||||||
|             self._raw(TXT_2HEIGHT) |  | ||||||
|         elif width == 2 and height != 2: |  | ||||||
|             self._raw(TXT_NORMAL) |  | ||||||
|             self._raw(TXT_2WIDTH) |  | ||||||
|         else: # DEFAULT SIZE: NORMAL |  | ||||||
|             self._raw(TXT_NORMAL) |  | ||||||
|         # Type |  | ||||||
|         if type.upper() == "B": |  | ||||||
|             self._raw(TXT_BOLD_ON) |  | ||||||
|             self._raw(TXT_UNDERL_OFF) |  | ||||||
|         elif type.upper() == "U": |  | ||||||
|             self._raw(TXT_BOLD_OFF) |  | ||||||
|             self._raw(TXT_UNDERL_ON) |  | ||||||
|         elif type.upper() == "U2": |  | ||||||
|             self._raw(TXT_BOLD_OFF) |  | ||||||
|             self._raw(TXT_UNDERL2_ON) |  | ||||||
|             self._raw(TXT_ITALIC_OFF) |  | ||||||
|         elif type.upper() == "BU": |  | ||||||
|             self._raw(TXT_BOLD_ON) |  | ||||||
|             self._raw(TXT_UNDERL_ON) |  | ||||||
|         elif type.upper() == "BU2": |  | ||||||
|             self._raw(TXT_BOLD_ON) |  | ||||||
|             self._raw(TXT_UNDERL2_ON) |  | ||||||
|         elif 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) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def cut(self, mode=''): |  | ||||||
|         """ Cut paper """ |  | ||||||
|         # Fix the size between last line and cut |  | ||||||
|         # TODO: handle this with a line feed |  | ||||||
|         self._raw("\n\n\n\n\n\n") |  | ||||||
|         if mode.upper() == "PART": |  | ||||||
|             self._raw(PAPER_PART_CUT) |  | ||||||
|         else: # DEFAULT MODE: FULL CUT |  | ||||||
|             self._raw(PAPER_FULL_CUT) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def cashdraw(self, pin): |  | ||||||
|         """ Send pulse to kick the cash drawer """ |  | ||||||
|         if pin == 2: |  | ||||||
|             self._raw(CD_KICK_2) |  | ||||||
|         elif pin == 5: |  | ||||||
|             self._raw(CD_KICK_5) |  | ||||||
|         else: |  | ||||||
|             raise CashDrawerError() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def hw(self, hw): |  | ||||||
|         """ Hardware operations """ |  | ||||||
|         if hw.upper() == "INIT": |  | ||||||
|             self._raw(HW_INIT) |  | ||||||
|         elif hw.upper() == "SELECT": |  | ||||||
|             self._raw(HW_SELECT) |  | ||||||
|         elif hw.upper() == "RESET": |  | ||||||
|             self._raw(HW_RESET) |  | ||||||
|         else: # DEFAULT: DOES NOTHING |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def control(self, ctl, pos=4): |  | ||||||
|         """ Feed control sequences """ |  | ||||||
|         # Set tab positions |  | ||||||
|         if pos < 1 or pos > 16: |  | ||||||
|             raise TabError() |  | ||||||
|         else: |  | ||||||
|             self._raw("".join([CTL_SET_HT,hex(pos)])) |  | ||||||
|         # Set position |  | ||||||
|         if ctl.upper() == "LF": |  | ||||||
|             self._raw(CTL_LF) |  | ||||||
|         elif ctl.upper() == "FF": |  | ||||||
|             self._raw(CTL_FF) |  | ||||||
|         elif ctl.upper() == "CR": |  | ||||||
|             self._raw(CTL_CR) |  | ||||||
|         elif ctl.upper() == "HT": |  | ||||||
|             self._raw(CTL_HT) |  | ||||||
|         elif ctl.upper() == "VT": |  | ||||||
|             self._raw(CTL_VT) |  | ||||||
| @@ -1,102 +0,0 @@ | |||||||
| """ ESC/POS Exceptions classes """ |  | ||||||
|  |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| class Error(Exception): |  | ||||||
|     """ Base class for ESC/POS errors """ |  | ||||||
|     def __init__(self, msg, status=None): |  | ||||||
|         Exception.__init__(self) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 1 |  | ||||||
|         if status is not None: |  | ||||||
|             self.resultcode = status |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.msg |  | ||||||
|  |  | ||||||
| # Result/Exit codes |  | ||||||
| # 0  = success |  | ||||||
| # 10 = No Barcode type defined |  | ||||||
| # 20 = Barcode size values are out of range |  | ||||||
| # 30 = Barcode text not supplied |  | ||||||
| # 40 = Image height is too large |  | ||||||
| # 50 = No string supplied to be printed |  | ||||||
| # 60 = Invalid pin to send Cash Drawer pulse |  | ||||||
| # 70 = Invalid number of tab positions |  | ||||||
| # 80 = Invalid char code |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BarcodeTypeError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 10 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "No Barcode type is defined" |  | ||||||
|  |  | ||||||
| class BarcodeSizeError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 20 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Barcode size is out of range" |  | ||||||
|  |  | ||||||
| class BarcodeCodeError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 30 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Code was not supplied" |  | ||||||
|  |  | ||||||
| class ImageSizeError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 40 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Image height is longer than 255px and can't be printed" |  | ||||||
|  |  | ||||||
| class TextError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 50 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Text string must be supplied to the text() method" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CashDrawerError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 60 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Valid pin must be set to send pulse" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TabError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 70 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Valid tab positions must be in the range 0 to 16" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CharCodeError(Error): |  | ||||||
|     def __init__(self, msg=""): |  | ||||||
|         Error.__init__(self, msg) |  | ||||||
|         self.msg = msg |  | ||||||
|         self.resultcode = 70 |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Valid char code must be set" |  | ||||||
| @@ -1,167 +0,0 @@ | |||||||
| #!/usr/bin/python |  | ||||||
| """ |  | ||||||
| @author: Manuel F Martinez <manpaz@bashlinux.com> |  | ||||||
| @organization: Bashlinux |  | ||||||
| @copyright: Copyright (c) 2012 Bashlinux |  | ||||||
| @license: GPL |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import usb.core |  | ||||||
| import usb.util |  | ||||||
| import serial |  | ||||||
| import socket |  | ||||||
|  |  | ||||||
| from escpos import * |  | ||||||
| from constants import * |  | ||||||
| from exceptions import * |  | ||||||
|  |  | ||||||
| class Usb(Escpos): |  | ||||||
|     """ Define USB printer """ |  | ||||||
|  |  | ||||||
|     def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01): |  | ||||||
|         """ |  | ||||||
|         @param idVendor  : Vendor ID |  | ||||||
|         @param idProduct : Product ID |  | ||||||
|         @param interface : USB device interface |  | ||||||
|         @param in_ep     : Input end point |  | ||||||
|         @param out_ep    : Output end point |  | ||||||
|         """ |  | ||||||
|         self.idVendor  = idVendor |  | ||||||
|         self.idProduct = idProduct |  | ||||||
|         self.interface = interface |  | ||||||
|         self.in_ep     = in_ep |  | ||||||
|         self.out_ep    = out_ep |  | ||||||
| 	self.open() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def open(self): |  | ||||||
|         """ Search device on USB tree and set is as escpos device """ |  | ||||||
|         self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) |  | ||||||
|         if self.device is None: |  | ||||||
|             print "Cable isn't plugged in" |  | ||||||
|  |  | ||||||
|         if self.device.is_kernel_driver_active(0): |  | ||||||
|             try: |  | ||||||
|                 self.device.detach_kernel_driver(0) |  | ||||||
|             except usb.core.USBError as e: |  | ||||||
|                 print "Could not detatch kernel driver: %s" % str(e) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             self.device.set_configuration() |  | ||||||
|             self.device.reset() |  | ||||||
|         except usb.core.USBError as e: |  | ||||||
|             print "Could not set configuration: %s" % str(e) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _raw(self, msg): |  | ||||||
|         """ Print any command sent in raw format """ |  | ||||||
|         self.device.write(self.out_ep, msg, self.interface) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __del__(self): |  | ||||||
|         """ Release USB interface """ |  | ||||||
|         if self.device: |  | ||||||
|             usb.util.dispose_resources(self.device) |  | ||||||
|         self.device = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Serial(Escpos): |  | ||||||
|     """ Define Serial printer """ |  | ||||||
|  |  | ||||||
|     def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1): |  | ||||||
|         """ |  | ||||||
|         @param devfile  : Device file under dev filesystem |  | ||||||
|         @param baudrate : Baud rate for serial transmission |  | ||||||
|         @param bytesize : Serial buffer size |  | ||||||
|         @param timeout  : Read/Write timeout |  | ||||||
|         """ |  | ||||||
|         self.devfile  = devfile |  | ||||||
|         self.baudrate = baudrate |  | ||||||
|         self.bytesize = bytesize |  | ||||||
|         self.timeout  = timeout |  | ||||||
|         self.open() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def open(self): |  | ||||||
|         """ Setup serial port and set is as escpos device """ |  | ||||||
|         self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, bytesize=self.bytesize, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, dsrdtr=True) |  | ||||||
|  |  | ||||||
|         if self.device is not None: |  | ||||||
|             print "Serial printer enabled" |  | ||||||
|         else: |  | ||||||
|             print "Unable to open serial printer on: %s" % self.devfile |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _raw(self, msg): |  | ||||||
|         """ Print any command sent in raw format """ |  | ||||||
|         self.device.write(msg) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __del__(self): |  | ||||||
|         """ Close Serial interface """ |  | ||||||
|         if self.device is not None: |  | ||||||
|             self.device.close() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Network(Escpos): |  | ||||||
|     """ Define Network printer """ |  | ||||||
|  |  | ||||||
|     def __init__(self,host,port=9100): |  | ||||||
|         """ |  | ||||||
|         @param host : Printer's hostname or IP address |  | ||||||
|         @param port : Port to write to |  | ||||||
|         """ |  | ||||||
|         self.host = host |  | ||||||
|         self.port = port |  | ||||||
|         self.open() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def open(self): |  | ||||||
|         """ Open TCP socket and set it as escpos device """ |  | ||||||
|         self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |  | ||||||
|         self.device.connect((self.host, self.port)) |  | ||||||
|  |  | ||||||
|         if self.device is None: |  | ||||||
|             print "Could not open socket for %s" % self.host |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _raw(self, msg): |  | ||||||
|         """ Print any command sent in raw format """ |  | ||||||
|         self.device.send(msg) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __del__(self): |  | ||||||
|         """ Close TCP connection """ |  | ||||||
|         self.device.close() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class File(Escpos): |  | ||||||
|     """ Define Generic file printer """ |  | ||||||
|  |  | ||||||
|     def __init__(self, devfile="/dev/usb/lp0"): |  | ||||||
|         """ |  | ||||||
|         @param devfile : Device file under dev filesystem |  | ||||||
|         """ |  | ||||||
|         self.devfile = devfile |  | ||||||
|         self.open() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def open(self): |  | ||||||
|         """ Open system file """ |  | ||||||
| 	self.device = open(self.devfile, "wb") |  | ||||||
|  |  | ||||||
|         if self.device is None: |  | ||||||
|             print "Could not open the specified file %s" % self.devfile |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _raw(self, msg): |  | ||||||
|         """ Print any command sent in raw format """ |  | ||||||
|         self.device.write(msg); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __del__(self): |  | ||||||
|         """ Close system file """ |  | ||||||
|         self.device.close() |  | ||||||
							
								
								
									
										17
									
								
								examples/barcodes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | 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') | ||||||
							
								
								
									
										67
									
								
								examples/codepage_tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,67 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """Prints code page tables. | ||||||
|  | """ | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from escpos import printer | ||||||
|  | from escpos.constants import CODEPAGE_CHANGE, CTL_CR, CTL_FF, CTL_HT, CTL_LF, CTL_VT, ESC | ||||||
|  |  | ||||||
|  | import six | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | 
							
								
								
									
										25
									
								
								examples/qr_code.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | 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) | ||||||
							
								
								
									
										15
									
								
								examples/software_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | 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') | ||||||
							
								
								
									
										130
									
								
								examples/weather.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,130 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | # 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 absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import calendar | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import urllib | ||||||
|  | from datetime import datetime | ||||||
|  |  | ||||||
|  | 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') | ||||||
							
								
								
									
										7
									
								
								readthedocs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | formats: | ||||||
|  |   - pdf | ||||||
|  |   - epub | ||||||
|  | requirements_file: doc/requirements.txt | ||||||
|  | python: | ||||||
|  |   version: 2 | ||||||
|  |   setup_py_install: true | ||||||
							
								
								
									
										1
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | -e . | ||||||
							
								
								
									
										21
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | [nosetests] | ||||||
|  | verbosity=3 | ||||||
|  | with-doctest=1 | ||||||
|  |  | ||||||
|  | [bdist_wheel] | ||||||
|  | # This flag says that the code is written to work on both Python 2 and Python 3. | ||||||
|  | universal=1 | ||||||
|  |  | ||||||
|  | [flake8] | ||||||
|  | exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py | ||||||
|  | max-line-length = 120 | ||||||
|  | accept-encoding = utf-8, utf-16 | ||||||
|  | require-code = True | ||||||
|  | # FI12 __future__ import "with_statement" missing | ||||||
|  | # FI15 __future__ import "generator_stop" missing | ||||||
|  | # FI16 __future__ import "nested_scopes" missing | ||||||
|  | # FI17 __future__ import "generators" missing | ||||||
|  | # FI5x __future__ import "xxx" present | ||||||
|  | # I101 Imported names are in the wrong order. | ||||||
|  | ignore = FI12,FI15,FI16,FI17,FI5, I101 | ||||||
|  | copyright-check = True | ||||||
							
								
								
									
										125
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						| @@ -1,29 +1,120 @@ | |||||||
| #!/usr/bin/python | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | # from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | from setuptools import find_packages, setup | ||||||
|  |  | ||||||
|  |  | ||||||
|  | base_dir = os.path.dirname(__file__) | ||||||
|  | src_dir = os.path.join(base_dir, 'src') | ||||||
|  |  | ||||||
|  | # When executing the setup.py, we need to be able to import ourselves, this | ||||||
|  | # means that we need to add the src/ directory to the sys.path. | ||||||
|  | sys.path.insert(0, src_dir) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def read(fname): | ||||||
|  |     """read file from same path as setup.py""" | ||||||
|  |     return open(os.path.join(os.path.dirname(__file__), fname)).read() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | setuptools_scm_template = """\ | ||||||
|  | # coding: utf-8 | ||||||
|  | # file generated by setuptools_scm | ||||||
|  | # don't change, don't track in version control | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | version = '{version}' | ||||||
|  | """ | ||||||
|  |  | ||||||
| from distutils.core import setup |  | ||||||
|  |  | ||||||
| setup( | setup( | ||||||
|     name='escpos', |     name='python-escpos', | ||||||
|     version='1.0.4', |     use_scm_version={ | ||||||
|     url='http://code.google.com/p/python-escpos', |         'write_to': 'src/escpos/version.py', | ||||||
|     download_url='http://python-escpos.googlecode.com/files/python-escpos-1.0.zip', |         'write_to_template': setuptools_scm_template, | ||||||
|  |     }, | ||||||
|  |     url='https://github.com/python-escpos/python-escpos', | ||||||
|  |     download_url='https://github.com/python-escpos/python-escpos/archive/master.zip', | ||||||
|     description='Python library to manipulate ESC/POS Printers', |     description='Python library to manipulate ESC/POS Printers', | ||||||
|     license='GNU GPL v3', |     license='MIT', | ||||||
|     long_description=open('README').read(), |     long_description=read('README.rst'), | ||||||
|     author='Manuel F Martinez', |     author='Manuel F Martinez and others', | ||||||
|     author_email='manpaz@bashlinux.com', |     author_email='manpaz@bashlinux.com', | ||||||
|     platforms=['linux'], |     maintainer='Patrick Kanzler', | ||||||
|     packages=[ |     maintainer_email='dev@pkanzler.de', | ||||||
|         'escpos', |     keywords=[ | ||||||
|  |         'ESC/POS', | ||||||
|  |         'thermoprinter', | ||||||
|  |         'voucher printer', | ||||||
|  |         'printing', | ||||||
|  |         'receipt,', | ||||||
|     ], |     ], | ||||||
|     package_data={'': ['COPYING']}, |     platforms='any', | ||||||
|  |     package_dir={'': 'src'}, | ||||||
|  |     packages=find_packages(where='src', exclude=['tests', 'tests.*']), | ||||||
|  |     package_data={'': ['COPYING', 'src/escpos/capabilities.json']}, | ||||||
|  |     include_package_data=True, | ||||||
|     classifiers=[ |     classifiers=[ | ||||||
|         'Development Status :: 1 - Alpha', |         'Development Status :: 4 - Beta', | ||||||
|         'License :: OSI Approved :: GNU GPL v3', |         'Environment :: Console', | ||||||
|         'Operating System :: GNU/Linux', |  | ||||||
|         'Intended Audience :: Developers', |         'Intended Audience :: Developers', | ||||||
|  |         'License :: OSI Approved :: MIT License', | ||||||
|  |         'Operating System :: OS Independent', | ||||||
|         'Programming Language :: Python', |         'Programming Language :: Python', | ||||||
|         'Topic :: System :: Pheripherals', |         'Programming Language :: Python :: 2', | ||||||
|  |         'Programming Language :: Python :: 2.7', | ||||||
|  |         'Programming Language :: Python :: 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', |         'Topic :: Software Development :: Libraries :: Python Modules', | ||||||
|  |         'Topic :: Office/Business :: Financial :: Point-Of-Sale', | ||||||
|     ], |     ], | ||||||
|  |     install_requires=[ | ||||||
|  |         'pyusb>=1.0.0', | ||||||
|  |         'Pillow>=2.0', | ||||||
|  |         'qrcode>=4.0', | ||||||
|  |         'pyserial', | ||||||
|  |         'six', | ||||||
|  |         'appdirs', | ||||||
|  |         'PyYAML', | ||||||
|  |         'argparse', | ||||||
|  |         'argcomplete', | ||||||
|  |         'future', | ||||||
|  |         'viivakoodi>=0.8' | ||||||
|  |     ], | ||||||
|  |     setup_requires=[ | ||||||
|  |         'setuptools_scm', | ||||||
|  |     ], | ||||||
|  |     tests_require=[ | ||||||
|  |         'jaconv', | ||||||
|  |         'tox', | ||||||
|  |         'pytest!=3.2.0,!=3.3.0', | ||||||
|  |         'pytest-cov', | ||||||
|  |         'pytest-mock', | ||||||
|  |         'nose', | ||||||
|  |         'scripttest', | ||||||
|  |         'mock', | ||||||
|  |         'hypothesis!=3.56.9', | ||||||
|  |         'flake8' | ||||||
|  |     ], | ||||||
|  |     entry_points={ | ||||||
|  |         'console_scripts': [ | ||||||
|  |             'python-escpos = escpos.cli:main' | ||||||
|  |         ] | ||||||
|  |     }, | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/escpos/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | """ | ||||||
|  | python-escpos enables you to manipulate escpos-printers | ||||||
|  | """ | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | __all__ = ['constants', 'escpos', 'exceptions', 'printer'] | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from .version import version as __version__  # noqa | ||||||
|  | except ImportError:  # pragma: no cover | ||||||
|  |     raise ImportError( | ||||||
|  |         'Failed to find (autogenerated) version.py. ' | ||||||
|  |         'This might be because you are installing from GitHub\'s tarballs, ' | ||||||
|  |         'use the PyPI ones.' | ||||||
|  |     ) | ||||||
							
								
								
									
										1
									
								
								src/escpos/capabilities.json
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | ../../capabilities-data/dist/capabilities.json | ||||||
							
								
								
									
										159
									
								
								src/escpos/capabilities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,159 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | import pickle | ||||||
|  | import platform | ||||||
|  | import re | ||||||
|  | import time | ||||||
|  | from os import environ, path | ||||||
|  | from tempfile import gettempdir | ||||||
|  |  | ||||||
|  | import six | ||||||
|  |  | ||||||
|  | import yaml | ||||||
|  |  | ||||||
|  | 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.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) | ||||||
							
								
								
									
										584
									
								
								src/escpos/cli.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,584 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | # PYTHON_ARGCOMPLETE_OK | ||||||
|  | """ CLI | ||||||
|  |  | ||||||
|  | This module acts as a command line interface for python-escpos. It mirrors | ||||||
|  | closely the available ESCPOS commands while adding a couple extra ones for convience. | ||||||
|  |  | ||||||
|  | It requires you to have a configuration file. See documentation for details. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import argparse | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     import argcomplete | ||||||
|  | except ImportError: | ||||||
|  |     # this CLI works nevertheless without argcomplete | ||||||
|  |     pass  # noqa | ||||||
|  | import six | ||||||
|  |  | ||||||
|  | from . import config | ||||||
|  | from . import version | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Must be defined before it's used in DEMO_FUNCTIONS | ||||||
|  | def str_to_bool(string): | ||||||
|  |     """ Used as a type in argparse so that we get back a proper | ||||||
|  |     bool instead of always True | ||||||
|  |     """ | ||||||
|  |     return string.lower() in ('y', 'yes', '1', 'true') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # A list of functions that work better with a newline to be sent after them. | ||||||
|  | REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Used in demo method | ||||||
|  | # Key: The name of escpos function and the argument passed on the CLI. Some | ||||||
|  | #   manual translation is done in the case of barcodes_a -> barcode. | ||||||
|  | # Value: A list of dictionaries to pass to the escpos function as arguments. | ||||||
|  | DEMO_FUNCTIONS = { | ||||||
|  |     'text': [ | ||||||
|  |         {'txt': 'Hello, World!\n', } | ||||||
|  |     ], | ||||||
|  |     'qr': [ | ||||||
|  |         {'content': 'This tests a QR code'}, | ||||||
|  |         {'content': 'https://en.wikipedia.org/'} | ||||||
|  |     ], | ||||||
|  |     'barcodes_a': [ | ||||||
|  |         {'bc': 'UPC-A', 'code': '13243546576'}, | ||||||
|  |         {'bc': 'UPC-E', 'code': '132435'}, | ||||||
|  |         {'bc': 'EAN13', 'code': '1324354657687'}, | ||||||
|  |         {'bc': 'EAN8', 'code': '1324354'}, | ||||||
|  |         {'bc': 'CODE39', 'code': 'TEST'}, | ||||||
|  |         {'bc': 'ITF', 'code': '55867492279103'}, | ||||||
|  |         {'bc': 'NW7', 'code': 'A00000000A'}, | ||||||
|  |     ], | ||||||
|  |     'barcodes_b': [ | ||||||
|  |         {'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'}, | ||||||
|  |         {'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'}, | ||||||
|  |     ], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Used to build the CLI | ||||||
|  | # A list of dictionaries. Each dict is a CLI argument. | ||||||
|  | # Keys: | ||||||
|  | # parser: A dict of args for command_parsers.add_parser | ||||||
|  | # defaults: A dict of args for subparser.set_defaults | ||||||
|  | # arguments: A list of dicts of args for subparser.add_argument | ||||||
|  | ESCPOS_COMMANDS = [ | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'qr', | ||||||
|  |             'help': 'Print a QR code', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'qr', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--content',), | ||||||
|  |                 'help': 'Text to print as a qr code', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--size',), | ||||||
|  |                 'help': 'QR code size (1-16) [default:3]', | ||||||
|  |                 'required': False, | ||||||
|  |                 'type': int, | ||||||
|  |             } | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'barcode', | ||||||
|  |             'help': 'Print a barcode', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'barcode', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--code',), | ||||||
|  |                 'help': 'Barcode data to print', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--bc',), | ||||||
|  |                 'help': 'Barcode format', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--height',), | ||||||
|  |                 'help': 'Barcode height in px', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--width',), | ||||||
|  |                 'help': 'Barcode width', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--pos',), | ||||||
|  |                 'help': 'Label position', | ||||||
|  |                 'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--font',), | ||||||
|  |                 'help': 'Label font', | ||||||
|  |                 'choices': ['A', 'B'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--align_ct',), | ||||||
|  |                 'help': 'Align barcode center', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--function_type',), | ||||||
|  |                 'help': 'ESCPOS function type', | ||||||
|  |                 'choices': ['A', 'B'], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'text', | ||||||
|  |             'help': 'Print plain text', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'text', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--txt',), | ||||||
|  |                 'help': 'Plain text to print', | ||||||
|  |                 'required': True, | ||||||
|  |             } | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'block_text', | ||||||
|  |             'help': 'Print wrapped text', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'block_text', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--txt',), | ||||||
|  |                 'help': 'block_text to print', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--columns',), | ||||||
|  |                 'help': 'Number of columns', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'cut', | ||||||
|  |             'help': 'Cut the paper', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'cut', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--mode',), | ||||||
|  |                 'help': 'Type of cut', | ||||||
|  |                 'choices': ['FULL', 'PART'], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'cashdraw', | ||||||
|  |             'help': 'Kick the cash drawer', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'cashdraw', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--pin',), | ||||||
|  |                 'help': 'Which PIN to kick', | ||||||
|  |                 'choices': [2, 5], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'image', | ||||||
|  |             'help': 'Print an image', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'image', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--img_source',), | ||||||
|  |                 'help': 'Path to image', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--impl',), | ||||||
|  |                 'help': 'Implementation to use', | ||||||
|  |                 'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--high_density_horizontal',), | ||||||
|  |                 'help': 'Image density (horizontal)', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--high_density_vertical',), | ||||||
|  |                 'help': 'Image density (vertical)', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |  | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'fullimage', | ||||||
|  |             'help': 'Print a fullimage', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'fullimage', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--img',), | ||||||
|  |                 'help': 'Path to img', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--max_height',), | ||||||
|  |                 'help': 'Max height of image in px', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--width',), | ||||||
|  |                 'help': 'Max width of image in px', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--histeq',), | ||||||
|  |                 'help': 'Equalize the histrogram', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--bandsize',), | ||||||
|  |                 'help': 'Size of bands to divide into when printing', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'charcode', | ||||||
|  |             'help': 'Set character code table', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'charcode', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--code',), | ||||||
|  |                 'help': 'Character code', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'set', | ||||||
|  |             'help': 'Set text properties', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'set', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--align',), | ||||||
|  |                 'help': 'Horizontal alignment', | ||||||
|  |                 'choices': ['left', 'center', 'right'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--font',), | ||||||
|  |                 'help': 'Font choice', | ||||||
|  |                 'choices': ['left', 'center', 'right'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--text_type',), | ||||||
|  |                 'help': 'Text properties', | ||||||
|  |                 'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'], | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--width',), | ||||||
|  |                 'help': 'Width multiplier', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--height',), | ||||||
|  |                 'help': 'Height multiplier', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--density',), | ||||||
|  |                 'help': 'Print density', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--invert',), | ||||||
|  |                 'help': 'White on black printing', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--smooth',), | ||||||
|  |                 'help': 'Text smoothing. Effective on >:  4x4 text', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--flip',), | ||||||
|  |                 'help': 'Text smoothing. Effective on >:  4x4 text', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'hw', | ||||||
|  |             'help': 'Hardware operations', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'hw', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--hw',), | ||||||
|  |                 'help': 'Operation', | ||||||
|  |                 'choices': ['INIT', 'SELECT', 'RESET'], | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'control', | ||||||
|  |             'help': 'Control sequences', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'control', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--ctl',), | ||||||
|  |                 'help': 'Control sequence', | ||||||
|  |                 'choices': ['LF', 'FF', 'CR', 'HT', 'VT'], | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--pos',), | ||||||
|  |                 'help': 'Horizontal tab position (1-4)', | ||||||
|  |                 'type': int, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'panel_buttons', | ||||||
|  |             'help': 'Controls panel buttons', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': 'panel_buttons', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--enable',), | ||||||
|  |                 'help': 'Feed button enabled', | ||||||
|  |                 'type': str_to_bool, | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         'parser': { | ||||||
|  |             'name': 'raw', | ||||||
|  |             'help': 'Raw data', | ||||||
|  |         }, | ||||||
|  |         'defaults': { | ||||||
|  |             'func': '_raw', | ||||||
|  |         }, | ||||||
|  |         'arguments': [ | ||||||
|  |             { | ||||||
|  |                 'option_strings': ('--msg',), | ||||||
|  |                 'help': 'Raw data to send', | ||||||
|  |                 'required': True, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     Handles loading of configuration and creating and processing of command | ||||||
|  |     line arguments. Called when run from a CLI. | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     parser = argparse.ArgumentParser( | ||||||
|  |         description='CLI for python-escpos', | ||||||
|  |         epilog='Printer configuration is defined in the python-escpos config' | ||||||
|  |         'file. See documentation for details.', | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     parser.register('type', 'bool', str_to_bool) | ||||||
|  |  | ||||||
|  |     # Allow config file location to be passed | ||||||
|  |     parser.add_argument( | ||||||
|  |         '-c', '--config', | ||||||
|  |         help='Alternate path to the configuration file', | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # Everything interesting runs off of a subparser so we can use the format | ||||||
|  |     # cli [subparser] -args | ||||||
|  |     command_subparsers = parser.add_subparsers( | ||||||
|  |         title='ESCPOS Command', | ||||||
|  |         dest='parser', | ||||||
|  |     ) | ||||||
|  |     # fix inconsistencies in the behaviour of some versions of argparse | ||||||
|  |     command_subparsers.required = False   # force 'required' testing | ||||||
|  |  | ||||||
|  |     # Build the ESCPOS command arguments | ||||||
|  |     for command in ESCPOS_COMMANDS: | ||||||
|  |         parser_command = command_subparsers.add_parser(**command['parser']) | ||||||
|  |         parser_command.set_defaults(**command['defaults']) | ||||||
|  |         for argument in command['arguments']: | ||||||
|  |             option_strings = argument.pop('option_strings') | ||||||
|  |             parser_command.add_argument(*option_strings, **argument) | ||||||
|  |  | ||||||
|  |     # Build any custom arguments | ||||||
|  |     parser_command_demo = command_subparsers.add_parser('demo', | ||||||
|  |                                                         help='Demonstrates various functions') | ||||||
|  |     parser_command_demo.set_defaults(func='demo') | ||||||
|  |     demo_group = parser_command_demo.add_mutually_exclusive_group() | ||||||
|  |     demo_group.add_argument( | ||||||
|  |         '--barcodes-a', | ||||||
|  |         help='Print demo barcodes for function type A', | ||||||
|  |         action='store_true', | ||||||
|  |     ) | ||||||
|  |     demo_group.add_argument( | ||||||
|  |         '--barcodes-b', | ||||||
|  |         help='Print demo barcodes for function type B', | ||||||
|  |         action='store_true', | ||||||
|  |     ) | ||||||
|  |     demo_group.add_argument( | ||||||
|  |         '--qr', | ||||||
|  |         help='Print some demo QR codes', | ||||||
|  |         action='store_true', | ||||||
|  |     ) | ||||||
|  |     demo_group.add_argument( | ||||||
|  |         '--text', | ||||||
|  |         help='Print some demo text', | ||||||
|  |         action='store_true', | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     parser_command_version = command_subparsers.add_parser('version', | ||||||
|  |                                                            help='Print the version of python-escpos') | ||||||
|  |     parser_command_version.set_defaults(version=True) | ||||||
|  |  | ||||||
|  |     # hook in argcomplete | ||||||
|  |     if 'argcomplete' in globals(): | ||||||
|  |         argcomplete.autocomplete(parser) | ||||||
|  |  | ||||||
|  |     # Get only arguments actually passed | ||||||
|  |     args_dict = vars(parser.parse_args()) | ||||||
|  |     if not args_dict: | ||||||
|  |         parser.print_help() | ||||||
|  |         sys.exit() | ||||||
|  |     command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None) | ||||||
|  |  | ||||||
|  |     # If version should be printed, do this, then exit | ||||||
|  |     print_version = command_arguments.pop('version', None) | ||||||
|  |     if print_version: | ||||||
|  |         print(version.version) | ||||||
|  |         sys.exit() | ||||||
|  |  | ||||||
|  |     # If there was a config path passed, grab it | ||||||
|  |     config_path = command_arguments.pop('config', None) | ||||||
|  |  | ||||||
|  |     # Load the configuration and defined printer | ||||||
|  |     saved_config = config.Config() | ||||||
|  |     saved_config.load(config_path) | ||||||
|  |     printer = saved_config.printer() | ||||||
|  |  | ||||||
|  |     if not printer: | ||||||
|  |         raise Exception('No printers loaded from config') | ||||||
|  |  | ||||||
|  |     target_command = command_arguments.pop('func') | ||||||
|  |  | ||||||
|  |     # remove helper-argument 'parser' from dict | ||||||
|  |     command_arguments.pop('parser', None) | ||||||
|  |  | ||||||
|  |     if hasattr(printer, target_command): | ||||||
|  |         # print command with args | ||||||
|  |         getattr(printer, target_command)(**command_arguments) | ||||||
|  |         if target_command in REQUIRES_NEWLINE: | ||||||
|  |             printer.text('\n') | ||||||
|  |     else: | ||||||
|  |         command_arguments['printer'] = printer | ||||||
|  |         globals()[target_command](**command_arguments) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def demo(printer, **kwargs): | ||||||
|  |     """ | ||||||
|  |     Prints specificed demos. Called when CLI is passed `demo`. This function | ||||||
|  |     uses the DEMO_FUNCTIONS dictionary. | ||||||
|  |  | ||||||
|  |     :param printer: A printer from escpos.printer | ||||||
|  |     :param kwargs: A dict with a key for each function you want to test. It's | ||||||
|  |         in this format since it usually comes from argparse. | ||||||
|  |     """ | ||||||
|  |     for demo_choice in kwargs.keys(): | ||||||
|  |         command = getattr( | ||||||
|  |             printer, | ||||||
|  |             demo_choice | ||||||
|  |             .replace('barcodes_a', 'barcode') | ||||||
|  |             .replace('barcodes_b', 'barcode') | ||||||
|  |         ) | ||||||
|  |         for params in DEMO_FUNCTIONS[demo_choice]: | ||||||
|  |             command(**params) | ||||||
|  |         printer.cut() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
							
								
								
									
										32
									
								
								src/escpos/codepages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | 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']) | ||||||
							
								
								
									
										121
									
								
								src/escpos/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,121 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ ESC/POS configuration manager. | ||||||
|  |  | ||||||
|  | This module contains the implentations of abstract base class :py:class:`Config`. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | import appdirs | ||||||
|  |  | ||||||
|  | import yaml | ||||||
|  |  | ||||||
|  | from . import exceptions | ||||||
|  | from . import printer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Config(object): | ||||||
|  |     """  Configuration handler class. | ||||||
|  |  | ||||||
|  |     This class loads configuration from a default or specificed directory. It | ||||||
|  |     can create your defined printer and return it to you. | ||||||
|  |     """ | ||||||
|  |     _app_name = 'python-escpos' | ||||||
|  |     _config_file = 'config.yaml' | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         """ Initialize configuration. | ||||||
|  |  | ||||||
|  |         Remember to add anything that needs to be reset between configurations | ||||||
|  |         to self._reset_config | ||||||
|  |         """ | ||||||
|  |         self._has_loaded = False | ||||||
|  |         self._printer = None | ||||||
|  |  | ||||||
|  |         self._printer_name = None | ||||||
|  |         self._printer_config = None | ||||||
|  |  | ||||||
|  |     def _reset_config(self): | ||||||
|  |         """ Clear the loaded configuration. | ||||||
|  |  | ||||||
|  |         If we are loading a changed config, we don't want to have leftover | ||||||
|  |         data. | ||||||
|  |         """ | ||||||
|  |         self._has_loaded = False | ||||||
|  |         self._printer = None | ||||||
|  |  | ||||||
|  |         self._printer_name = None | ||||||
|  |         self._printer_config = None | ||||||
|  |  | ||||||
|  |     def load(self, config_path=None): | ||||||
|  |         """ Load and parse the configuration file using pyyaml | ||||||
|  |  | ||||||
|  |         :param config_path: An optional file path, file handle, or byte string | ||||||
|  |             for the configuration file. | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         self._reset_config() | ||||||
|  |  | ||||||
|  |         if not config_path: | ||||||
|  |             config_path = os.path.join( | ||||||
|  |                 appdirs.user_config_dir(self._app_name), | ||||||
|  |                 self._config_file | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             # First check if it's file like. If it is, pyyaml can load it. | ||||||
|  |             # I'm checking type instead of catching exceptions to keep the | ||||||
|  |             # exception handling simple | ||||||
|  |             if hasattr(config_path, 'read'): | ||||||
|  |                 config = yaml.safe_load(config_path) | ||||||
|  |             else: | ||||||
|  |                 # If it isn't, it's a path. We have to open it first, otherwise | ||||||
|  |                 # pyyaml will try to read it as yaml | ||||||
|  |                 with open(config_path, 'rb') as config_file: | ||||||
|  |                     config = yaml.safe_load(config_file) | ||||||
|  |         except EnvironmentError: | ||||||
|  |             raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format( | ||||||
|  |                 config_path=str(config_path), | ||||||
|  |             )) | ||||||
|  |         except yaml.YAMLError: | ||||||
|  |             raise exceptions.ConfigSyntaxError('Error parsing YAML') | ||||||
|  |  | ||||||
|  |         if 'printer' in config: | ||||||
|  |             self._printer_config = config['printer'] | ||||||
|  |             self._printer_name = self._printer_config.pop('type').title() | ||||||
|  |  | ||||||
|  |             if not self._printer_name or not hasattr(printer, self._printer_name): | ||||||
|  |                 raise exceptions.ConfigSyntaxError( | ||||||
|  |                     'Printer type "{printer_name}" is invalid'.format( | ||||||
|  |                         printer_name=self._printer_name, | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |         self._has_loaded = True | ||||||
|  |  | ||||||
|  |     def printer(self): | ||||||
|  |         """ Returns a printer that was defined in the config, or throws an | ||||||
|  |         exception. | ||||||
|  |  | ||||||
|  |         This method loads the default config if one hasn't beeen already loaded. | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |         if not self._has_loaded: | ||||||
|  |             self.load() | ||||||
|  |  | ||||||
|  |         if not self._printer_name: | ||||||
|  |             raise exceptions.ConfigSectionMissingError('printer') | ||||||
|  |  | ||||||
|  |         if not self._printer: | ||||||
|  |             # We could catch init errors and make them a ConfigSyntaxError, | ||||||
|  |             # but I'll just let them pass | ||||||
|  |             self._printer = getattr(printer, self._printer_name)(**self._printer_config) | ||||||
|  |  | ||||||
|  |         return self._printer | ||||||
							
								
								
									
										278
									
								
								src/escpos/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,278 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ Set of ESC/POS Commands (Constants) | ||||||
|  |  | ||||||
|  | This module contains constants that are described in the esc/pos-documentation. | ||||||
|  | Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be | ||||||
|  | moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/escpos-php>`_. | ||||||
|  |  | ||||||
|  | :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||||
|  | :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||||
|  | :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||||
|  | :license: MIT | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import six | ||||||
|  |  | ||||||
|  | # Control characters | ||||||
|  | # as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf | ||||||
|  | NUL = b'\x00' | ||||||
|  | EOT = b'\x04' | ||||||
|  | ENQ = b'\x05' | ||||||
|  | DLE = b'\x10' | ||||||
|  | DC4 = b'\x14' | ||||||
|  | CAN = b'\x18' | ||||||
|  | ESC = b'\x1b' | ||||||
|  | FS  = b'\x1c' | ||||||
|  | GS  = b'\x1d' | ||||||
|  |  | ||||||
|  | # Feed control sequences | ||||||
|  | CTL_LF = b'\n'              # Print and line feed | ||||||
|  | CTL_FF = b'\f'              # Form feed | ||||||
|  | CTL_CR = b'\r'              # Carriage return | ||||||
|  | CTL_HT = b'\t'              # Horizontal tab | ||||||
|  | CTL_SET_HT = ESC + b'\x44'  # Set horizontal tab positions | ||||||
|  | CTL_VT = b'\v'              # Vertical tab | ||||||
|  |  | ||||||
|  | # Printer hardware | ||||||
|  | HW_INIT   = ESC + b'@'             # Clear data in buffer and reset modes | ||||||
|  | HW_SELECT = ESC + b'=\x01'         # Printer select | ||||||
|  |  | ||||||
|  | HW_RESET  = ESC + b'\x3f\x0a\x00'   # Reset printer hardware | ||||||
|  |                                     # (TODO: Where is this specified?) | ||||||
|  |  | ||||||
|  | # Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>) | ||||||
|  | _CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2) | ||||||
|  | CD_KICK_DEC_SEQUENCE = lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) + six.int2byte(p) + six.int2byte(m) + six.int2byte(t1) + six.int2byte(t2) | ||||||
|  | CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50)  # Sends a pulse to pin 2 [] | ||||||
|  | CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50)  # Sends a pulse to pin 5 [] | ||||||
|  |  | ||||||
|  | # Paper Cutter | ||||||
|  | _CUT_PAPER = lambda m: GS + b'V' + m | ||||||
|  | PAPER_FULL_CUT = _CUT_PAPER(b'\x00')  # Full cut paper | ||||||
|  | PAPER_PART_CUT = _CUT_PAPER(b'\x01')  # Partial cut paper | ||||||
|  |  | ||||||
|  | # Beep | ||||||
|  | BEEP = b'\x07' | ||||||
|  |  | ||||||
|  | # Panel buttons (e.g. the FEED button) | ||||||
|  | _PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) | ||||||
|  | PANEL_BUTTON_ON = _PANEL_BUTTON(0)  # enable all panel buttons | ||||||
|  | PANEL_BUTTON_OFF = _PANEL_BUTTON(1)  # disable all panel buttons | ||||||
|  |  | ||||||
|  | # 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 | ||||||
|  |  | ||||||
|  | # Text format | ||||||
|  | # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll | ||||||
|  | #       Printers" and tidy up this stuff too. | ||||||
|  | TXT_SIZE       = GS + b'!' | ||||||
|  |  | ||||||
|  | TXT_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 | ||||||
|  |     }, | ||||||
|  |     'width': {                                  # Custom text width | ||||||
|  |         1: 0x00, | ||||||
|  |         2: 0x10, | ||||||
|  |         3: 0x20, | ||||||
|  |         4: 0x30, | ||||||
|  |         5: 0x40, | ||||||
|  |         6: 0x50, | ||||||
|  |         7: 0x60, | ||||||
|  |         8: 0x70 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Fonts | ||||||
|  | SET_FONT = lambda n: ESC + b'\x4d' + n | ||||||
|  | TXT_FONT_A     = SET_FONT(b'\x00')  # Font type A | ||||||
|  | TXT_FONT_B     = SET_FONT(b'\x01')  # Font type B | ||||||
|  |  | ||||||
|  | # Spacing | ||||||
|  | LINESPACING_RESET = ESC + b'2' | ||||||
|  | LINESPACING_FUNCS = { | ||||||
|  |   60: ESC + b'A',  # line_spacing/60 of an inch, 0 <= line_spacing <= 85 | ||||||
|  |   360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255 | ||||||
|  |   180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Prefix to change the codepage. You need to attach a byte to indicate | ||||||
|  | # the codepage to use. We use escpos-printer-db as the data source. | ||||||
|  | CODEPAGE_CHANGE = ESC + b'\x74' | ||||||
|  |  | ||||||
|  | # Barcode format | ||||||
|  | _SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n | ||||||
|  | BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00')  # HRI barcode chars OFF | ||||||
|  | BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01')  # HRI barcode chars above | ||||||
|  | BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02')  # HRI barcode chars below | ||||||
|  | BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03')  # HRI both above and below | ||||||
|  |  | ||||||
|  | _SET_HRI_FONT = lambda n: GS + b'f' + n | ||||||
|  | BARCODE_FONT_A = _SET_HRI_FONT(b'\x00')  # Font type A for HRI barcode chars | ||||||
|  | BARCODE_FONT_B = _SET_HRI_FONT(b'\x01')  # Font type B for HRI barcode chars | ||||||
|  |  | ||||||
|  | BARCODE_HEIGHT = GS + b'h'  # Barcode Height [1-255] | ||||||
|  | BARCODE_WIDTH  = GS + b'w'  # Barcode Width  [2-6] | ||||||
|  |  | ||||||
|  | # NOTE: This isn't actually an ESC/POS command. It's the common prefix to the | ||||||
|  | #      two "print bar code" commands: | ||||||
|  | #      -  Type A: "GS k <type as integer> <data> NUL" | ||||||
|  | #      -  TYPE B: "GS k <type as letter> <data length> <data>" | ||||||
|  | #      The latter command supports more barcode types | ||||||
|  | _SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m) | ||||||
|  |  | ||||||
|  | # Barcodes for printing function type A | ||||||
|  | BARCODE_TYPE_A = { | ||||||
|  |     'UPC-A':   _SET_BARCODE_TYPE(0), | ||||||
|  |     'UPC-E':   _SET_BARCODE_TYPE(1), | ||||||
|  |     'EAN13':   _SET_BARCODE_TYPE(2), | ||||||
|  |     'EAN8':    _SET_BARCODE_TYPE(3), | ||||||
|  |     'CODE39':  _SET_BARCODE_TYPE(4), | ||||||
|  |     'ITF':     _SET_BARCODE_TYPE(5), | ||||||
|  |     'NW7':     _SET_BARCODE_TYPE(6), | ||||||
|  |     'CODABAR': _SET_BARCODE_TYPE(6),  # Same as NW7 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Barcodes for printing function type B | ||||||
|  | # The first 8 are the same barcodes as type A | ||||||
|  | BARCODE_TYPE_B = { | ||||||
|  |     'UPC-A':                       _SET_BARCODE_TYPE(65), | ||||||
|  |     'UPC-E':                       _SET_BARCODE_TYPE(66), | ||||||
|  |     'EAN13':                       _SET_BARCODE_TYPE(67), | ||||||
|  |     'EAN8':                        _SET_BARCODE_TYPE(68), | ||||||
|  |     'CODE39':                      _SET_BARCODE_TYPE(69), | ||||||
|  |     'ITF':                         _SET_BARCODE_TYPE(70), | ||||||
|  |     'NW7':                         _SET_BARCODE_TYPE(71), | ||||||
|  |     'CODABAR':                     _SET_BARCODE_TYPE(71),  # Same as NW7 | ||||||
|  |     'CODE93':                      _SET_BARCODE_TYPE(72), | ||||||
|  |     'CODE128':                     _SET_BARCODE_TYPE(73), | ||||||
|  |     'GS1-128':                     _SET_BARCODE_TYPE(74), | ||||||
|  |     'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75), | ||||||
|  |     'GS1 DATABAR TRUNCATED':       _SET_BARCODE_TYPE(76), | ||||||
|  |     'GS1 DATABAR LIMITED':         _SET_BARCODE_TYPE(77), | ||||||
|  |     'GS1 DATABAR EXPANDED':        _SET_BARCODE_TYPE(78), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BARCODE_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, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # QRCode error correction levels | ||||||
|  | QR_ECLEVEL_L = 0 | ||||||
|  | QR_ECLEVEL_M = 1 | ||||||
|  | QR_ECLEVEL_Q = 2 | ||||||
|  | QR_ECLEVEL_H = 3 | ||||||
|  |  | ||||||
|  | # QRcode models | ||||||
|  | QR_MODEL_1 = 1 | ||||||
|  | QR_MODEL_2 = 2 | ||||||
|  | QR_MICRO = 3 | ||||||
|  |  | ||||||
|  | # Image format | ||||||
|  | # NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image" | ||||||
|  | #       command. The constants include a fragment of the data's header. | ||||||
|  | _PRINT_RASTER_IMG = lambda data: GS + b'v0' + data | ||||||
|  | S_RASTER_N  = _PRINT_RASTER_IMG(b'\x00')  # Set raster image normal size | ||||||
|  | S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01')  # Set raster image double width | ||||||
|  | S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02')  # Set raster image double height | ||||||
|  | S_RASTER_Q  = _PRINT_RASTER_IMG(b'\x03')  # Set raster image quadruple | ||||||
|  |  | ||||||
|  | # 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 | ||||||
							
								
								
									
										960
									
								
								src/escpos/escpos.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,960 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ Main class | ||||||
|  |  | ||||||
|  | This module contains the abstract base class :py:class:`Escpos`. | ||||||
|  |  | ||||||
|  | :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||||
|  | :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||||
|  | :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||||
|  | :license: MIT | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import textwrap | ||||||
|  | import time | ||||||
|  | from abc import ABCMeta, abstractmethod  # abstract base class support | ||||||
|  | from re import match as re_match | ||||||
|  |  | ||||||
|  | import barcode | ||||||
|  | from barcode.writer import ImageWriter | ||||||
|  |  | ||||||
|  | import qrcode | ||||||
|  |  | ||||||
|  | import six | ||||||
|  |  | ||||||
|  | from .capabilities import BARCODE_B, get_profile | ||||||
|  | from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS | ||||||
|  | from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW | ||||||
|  | from .constants import BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH | ||||||
|  | from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT | ||||||
|  | from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON | ||||||
|  | from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q | ||||||
|  | from .constants import HW_INIT, HW_RESET, HW_SELECT | ||||||
|  | from .constants import LINESPACING_FUNCS, LINESPACING_RESET | ||||||
|  | from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE | ||||||
|  | from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO | ||||||
|  | from .constants import RT_MASK_ONLINE, RT_STATUS_ONLINE | ||||||
|  | from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER | ||||||
|  | from .constants import SET_FONT | ||||||
|  | from .constants import TXT_SIZE, TXT_NORMAL | ||||||
|  | from .constants import TXT_STYLE | ||||||
|  | from .exceptions import BarcodeCodeError, BarcodeSizeError, BarcodeTypeError | ||||||
|  | from .exceptions import CashDrawerError, ImageWidthError | ||||||
|  | from .exceptions import SetVariableError, TabPosError | ||||||
|  | from .image import EscposImage | ||||||
|  | from .magicencode import MagicEncode | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @six.add_metaclass(ABCMeta) | ||||||
|  | class Escpos(object): | ||||||
|  |     """ ESC/POS Printer object | ||||||
|  |  | ||||||
|  |     This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this | ||||||
|  |     class. | ||||||
|  |     """ | ||||||
|  |     device = None | ||||||
|  |  | ||||||
|  |     def __init__(self, profile=None, magic_encode_args=None, **kwargs): | ||||||
|  |         """ Initialize ESCPOS Printer | ||||||
|  |  | ||||||
|  |         :param profile: Printer profile""" | ||||||
|  |         self.profile = get_profile(profile) | ||||||
|  |         self.magic = MagicEncode(self, **(magic_encode_args or {})) | ||||||
|  |  | ||||||
|  |     def __del__(self): | ||||||
|  |         """ call self.close upon deletion """ | ||||||
|  |         self.close() | ||||||
|  |  | ||||||
|  |     @abstractmethod | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Sends raw data to the printer | ||||||
|  |  | ||||||
|  |         This function has to be individually implemented by the implementations. | ||||||
|  |  | ||||||
|  |         :param msg: message string to be sent to the printer | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def _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=960, center=False): | ||||||
|  |         """ Print an image | ||||||
|  |  | ||||||
|  |         You can select whether the printer should print in high density or not. The default value is high density. | ||||||
|  |         When printing in low density, the image will be stretched. | ||||||
|  |  | ||||||
|  |         Esc/Pos supplies several commands for printing. This function supports three of them. Please try to vary the | ||||||
|  |         implementations if you have any problems. For example the printer `IT80-002` will have trouble aligning | ||||||
|  |         images that are not printed in Column-mode. | ||||||
|  |  | ||||||
|  |         The available printing implementations are: | ||||||
|  |  | ||||||
|  |             * `bitImageRaster`: prints with the `GS v 0`-command | ||||||
|  |             * `graphics`: prints with the `GS ( L`-command | ||||||
|  |             * `bitImageColumn`: prints with the `ESC *`-command | ||||||
|  |  | ||||||
|  |         :param img_source: PIL image or filename to load: `jpg`, `gif`, `png` or `bmp` | ||||||
|  |         :param high_density_vertical: print in high density in vertical direction *default:* True | ||||||
|  |         :param high_density_horizontal: print in high density in horizontal direction *default:* True | ||||||
|  |         :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` | ||||||
|  |         :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960 | ||||||
|  |         :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: | ||||||
|  |                 self.image(fragment, | ||||||
|  |                            high_density_vertical=high_density_vertical, | ||||||
|  |                            high_density_horizontal=high_density_horizontal, | ||||||
|  |                            impl=impl, | ||||||
|  |                            fragment_height=fragment_height) | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if impl == 'bitImageRaster': | ||||||
|  |             # GS v 0, raster format bit image | ||||||
|  |             density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2) | ||||||
|  |             header = GS + b'v0' + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\ | ||||||
|  |                 self._int_low_high(im.height, 2) | ||||||
|  |             self._raw(header + im.to_raster_format()) | ||||||
|  |  | ||||||
|  |         if impl == 'graphics': | ||||||
|  |             # GS ( L raster format graphics | ||||||
|  |             img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2) | ||||||
|  |             tone = b'0' | ||||||
|  |             colors = b'1' | ||||||
|  |             ym = six.int2byte(1 if high_density_vertical else 2) | ||||||
|  |             xm = six.int2byte(1 if high_density_horizontal else 2) | ||||||
|  |             header = tone + xm + ym + colors + img_header | ||||||
|  |             raster_data = im.to_raster_format() | ||||||
|  |             self._image_send_graphics_data(b'0', b'p', header + raster_data) | ||||||
|  |             self._image_send_graphics_data(b'0', b'2', b'') | ||||||
|  |  | ||||||
|  |         if impl == 'bitImageColumn': | ||||||
|  |             # ESC *, column format bit image | ||||||
|  |             density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0) | ||||||
|  |             header = ESC + b'*' + six.int2byte(density_byte) + self._int_low_high(im.width, 2) | ||||||
|  |             outp = [ESC + b'3' + six.int2byte(16)]  # Adjust line-feed size | ||||||
|  |             for blob in im.to_column_format(high_density_vertical): | ||||||
|  |                 outp.append(header + blob + b'\n') | ||||||
|  |             outp.append(ESC + b'2')  # Reset line-feed size | ||||||
|  |             self._raw(b''.join(outp)) | ||||||
|  |  | ||||||
|  |     def _image_send_graphics_data(self, m, fn, data): | ||||||
|  |         """ | ||||||
|  |         Wrapper for GS ( L, to calculate and send correct data length. | ||||||
|  |  | ||||||
|  |         :param m: Modifier//variant for function. Usually '0' | ||||||
|  |         :param fn: Function number to use, as byte | ||||||
|  |         :param data: Data to send | ||||||
|  |         """ | ||||||
|  |         header = self._int_low_high(len(data) + 2, 2) | ||||||
|  |         self._raw(GS + b'(L' + header + m + fn + data) | ||||||
|  |  | ||||||
|  |     def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, | ||||||
|  |            native=False, center=False, impl='bitImageRaster'): | ||||||
|  |         """ Print QR Code for the provided string | ||||||
|  |  | ||||||
|  |         :param content: The content of the code. Numeric data will be more efficiently compacted. | ||||||
|  |         :param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or | ||||||
|  |             QR_ECLEVEL_H. | ||||||
|  |             Higher error correction results in a less compact code. | ||||||
|  |         :param size: Pixel size to use. Must be 1-16 (default 3) | ||||||
|  |         :param model: QR code model to use. Must be one of QR_MODEL_1, QR_MODEL_2 (default) or QR_MICRO (not supported | ||||||
|  |             by all printers). | ||||||
|  |         :param native: True to render the code on the printer, False to render the code as an image and send it to the | ||||||
|  |             printer (Default) | ||||||
|  |         :param center: Centers the code *default:* False | ||||||
|  |         """ | ||||||
|  |         # Basic validation | ||||||
|  |         if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: | ||||||
|  |             raise ValueError('Invalid error correction level') | ||||||
|  |         if not 1 <= size <= 16: | ||||||
|  |             raise ValueError('Invalid block size (must be 1-16)') | ||||||
|  |         if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]: | ||||||
|  |             raise ValueError('Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)') | ||||||
|  |         if content == '': | ||||||
|  |             # Handle edge case by printing nothing. | ||||||
|  |             return | ||||||
|  |         if not native: | ||||||
|  |             # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image | ||||||
|  |             if model != QR_MODEL_2: | ||||||
|  |                 raise ValueError('Invalid QR model for qrlib rendering (must be QR_MODEL_2)') | ||||||
|  |             python_qr_ec = { | ||||||
|  |                 QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, | ||||||
|  |                 QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, | ||||||
|  |                 QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M, | ||||||
|  |                 QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q | ||||||
|  |             } | ||||||
|  |             qr_code = qrcode.QRCode(version=None, box_size=size, border=1, error_correction=python_qr_ec[ec]) | ||||||
|  |             qr_code.add_data(content) | ||||||
|  |             qr_code.make(fit=True) | ||||||
|  |             qr_img = qr_code.make_image() | ||||||
|  |             im = qr_img._img.convert('RGB') | ||||||
|  |  | ||||||
|  |             # Convert the RGB image in printable image | ||||||
|  |             self.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. | ||||||
|  |         self._send_2d_code_data(six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0)) | ||||||
|  |         # Set dot size. | ||||||
|  |         self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) | ||||||
|  |         # Set error correction level: L, M, Q, or H | ||||||
|  |         self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) | ||||||
|  |         # Send content & print | ||||||
|  |         self._send_2d_code_data(six.int2byte(80), cn, content.encode('utf-8'), b'0') | ||||||
|  |         self._send_2d_code_data(six.int2byte(81), cn, b'', b'0') | ||||||
|  |  | ||||||
|  |     def _send_2d_code_data(self, fn, cn, data, m=b''): | ||||||
|  |         """ Wrapper for GS ( k, to calculate and send correct data length. | ||||||
|  |  | ||||||
|  |         :param fn: Function to use. | ||||||
|  |         :param cn: Output code type. Affects available data. | ||||||
|  |         :param data: Data to send. | ||||||
|  |         :param m: Modifier/variant for function. Often '0' where used. | ||||||
|  |         """ | ||||||
|  |         if len(m) > 1 or len(cn) != 1 or len(fn) != 1: | ||||||
|  |             raise ValueError('cn and fn must be one byte each.') | ||||||
|  |         header = self._int_low_high(len(data) + len(m) + 2, 2) | ||||||
|  |         self._raw(GS + b'(k' + header + cn + fn + m + data) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _int_low_high(inp_number, out_bytes): | ||||||
|  |         """ Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. | ||||||
|  |  | ||||||
|  |         :param inp_number: Input number | ||||||
|  |         :param out_bytes: The number of bytes to output (1 - 4). | ||||||
|  |         """ | ||||||
|  |         max_input = (256 << (out_bytes * 8) - 1) | ||||||
|  |         if not 1 <= out_bytes <= 4: | ||||||
|  |             raise ValueError('Can only output 1-4 bytes') | ||||||
|  |         if not 0 <= inp_number <= max_input: | ||||||
|  |             raise ValueError('Number too large. Can only output up to {0} in {1} bytes'.format(max_input, out_bytes)) | ||||||
|  |         outp = b'' | ||||||
|  |         for _ in range(0, out_bytes): | ||||||
|  |             outp += six.int2byte(inp_number % 256) | ||||||
|  |             inp_number //= 256 | ||||||
|  |         return outp | ||||||
|  |  | ||||||
|  |     def charcode(self, code='AUTO'): | ||||||
|  |         """ Set Character Code Table | ||||||
|  |  | ||||||
|  |         Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with | ||||||
|  |         the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the | ||||||
|  |         right codepage. (This is the standard behaviour.) | ||||||
|  |  | ||||||
|  |         :param code: Name of CharCode | ||||||
|  |         :raises: :py:exc:`~escpos.exceptions.CharCodeError` | ||||||
|  |         """ | ||||||
|  |         if code.upper() == 'AUTO': | ||||||
|  |             self.magic.force_encoding(False) | ||||||
|  |         else: | ||||||
|  |             self.magic.force_encoding(code) | ||||||
|  |  | ||||||
|  |     @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:func`~escpos.Escpos.barcode` | ||||||
|  |         :param code: alphanumeric data to be printed as bar code, see :py:func`~escpos.Escpos.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. 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 | ||||||
|  |         this information is probably more useful for debugging purposes.) | ||||||
|  |  | ||||||
|  |         .. todo:: On TM-T88II width from 1 to 6 is accepted. Try to acquire command reference and correct the code. | ||||||
|  |         .. todo:: Supplying pos does not have an effect for every barcode type. Check and document for which types this | ||||||
|  |                   is true. | ||||||
|  |  | ||||||
|  |         If you do not want to center the barcode you can call the method with `align_ct=False`, which will disable | ||||||
|  |         automatic centering. Please note that when you use center alignment, then the alignment of text will be changed | ||||||
|  |         automatically to centered. You have to manually restore the alignment if necessary. | ||||||
|  |  | ||||||
|  |         .. todo:: If further barcode-types are needed they could be rendered transparently as an image. (This could also | ||||||
|  |                   be of help if the printer does not support types that others do.) | ||||||
|  |  | ||||||
|  |         :param code: alphanumeric data to be printed as bar code | ||||||
|  |         :param bc: barcode format, possible values are for type A are: | ||||||
|  |  | ||||||
|  |             * UPC-A | ||||||
|  |             * UPC-E | ||||||
|  |             * EAN13 | ||||||
|  |             * EAN8 | ||||||
|  |             * CODE39 | ||||||
|  |             * ITF | ||||||
|  |             * NW7 | ||||||
|  |  | ||||||
|  |             Possible values for type B: | ||||||
|  |  | ||||||
|  |             * All types from function type A | ||||||
|  |             * CODE93 | ||||||
|  |             * CODE128 | ||||||
|  |             * GS1-128 | ||||||
|  |             * GS1 DataBar Omnidirectional | ||||||
|  |             * GS1 DataBar Truncated | ||||||
|  |             * GS1 DataBar Limited | ||||||
|  |             * GS1 DataBar Expanded | ||||||
|  |  | ||||||
|  |             If none is specified, the method raises :py:exc:`~escpos.exceptions.BarcodeTypeError`. | ||||||
|  |         :param height: barcode height, has to be between 1 and 255 | ||||||
|  |             *default*: 64 | ||||||
|  |         :type height: int | ||||||
|  |         :param width: barcode width, has to be between 2 and 6 | ||||||
|  |             *default*: 3 | ||||||
|  |         :type width: int | ||||||
|  |         :param pos: where to place the text relative to the barcode, *default*: BELOW | ||||||
|  |  | ||||||
|  |             * ABOVE | ||||||
|  |             * BELOW | ||||||
|  |             * BOTH | ||||||
|  |             * OFF | ||||||
|  |  | ||||||
|  |         :param font: select font (see ESC/POS-documentation, the device often has two fonts), *default*: A | ||||||
|  |  | ||||||
|  |             * A | ||||||
|  |             * B | ||||||
|  |  | ||||||
|  |         :param align_ct: If this parameter is True the barcode will be centered. Otherwise no alignment command will be | ||||||
|  |                          issued. | ||||||
|  |         :type align_ct: bool | ||||||
|  |  | ||||||
|  |         :param function_type: Choose between ESCPOS function type A or B, | ||||||
|  |             depending on printer support and desired barcode. If not given, | ||||||
|  |             the printer will attempt to automatically choose the correct | ||||||
|  |             function based on the current profile. | ||||||
|  |             *default*: A | ||||||
|  |  | ||||||
|  |         :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:func:`~escpos.Escpos.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_STYLE['align']['center']) | ||||||
|  |         # Height | ||||||
|  |         if 1 <= height <= 255: | ||||||
|  |             self._raw(BARCODE_HEIGHT + six.int2byte(height)) | ||||||
|  |         else: | ||||||
|  |             raise BarcodeSizeError('height = {height}'.format(height=height)) | ||||||
|  |         # Width | ||||||
|  |         if 2 <= width <= 6: | ||||||
|  |             self._raw(BARCODE_WIDTH + six.int2byte(width)) | ||||||
|  |         else: | ||||||
|  |             raise BarcodeSizeError('width = {width}'.format(width=width)) | ||||||
|  |         # Font | ||||||
|  |         if font.upper() == 'B': | ||||||
|  |             self._raw(BARCODE_FONT_B) | ||||||
|  |         else:  # DEFAULT FONT: A | ||||||
|  |             self._raw(BARCODE_FONT_A) | ||||||
|  |         # Position | ||||||
|  |         if pos.upper() == 'OFF': | ||||||
|  |             self._raw(BARCODE_TXT_OFF) | ||||||
|  |         elif pos.upper() == 'BOTH': | ||||||
|  |             self._raw(BARCODE_TXT_BTH) | ||||||
|  |         elif pos.upper() == 'ABOVE': | ||||||
|  |             self._raw(BARCODE_TXT_ABV) | ||||||
|  |         else:  # DEFAULT POSITION: BELOW | ||||||
|  |             self._raw(BARCODE_TXT_BLW) | ||||||
|  |  | ||||||
|  |         self._raw(bc_types[bc.upper()]) | ||||||
|  |  | ||||||
|  |         if function_type.upper() == 'B': | ||||||
|  |             self._raw(six.int2byte(len(code))) | ||||||
|  |  | ||||||
|  |         # Print Code | ||||||
|  |         if code: | ||||||
|  |             self._raw(code.encode()) | ||||||
|  |         else: | ||||||
|  |             raise BarcodeCodeError() | ||||||
|  |  | ||||||
|  |         if function_type.upper() == 'A': | ||||||
|  |             self._raw(NUL) | ||||||
|  |  | ||||||
|  |     def 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 | ||||||
|  |  | ||||||
|  |         The text has to be encoded in the currently selected codepage. | ||||||
|  |         The input text has to be encoded in unicode. | ||||||
|  |  | ||||||
|  |         :param txt: text to be printed | ||||||
|  |         :raises: :py:exc:`~escpos.exceptions.TextError` | ||||||
|  |         """ | ||||||
|  |         txt = six.text_type(txt) | ||||||
|  |         self.magic.write(txt) | ||||||
|  |  | ||||||
|  |     def 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.profile.get_columns(font) if columns is None else columns | ||||||
|  |         self.text(textwrap.fill(txt, col_count)) | ||||||
|  |  | ||||||
|  |     def set(self, align='left', font='a', bold=False, underline=0, width=1, | ||||||
|  |             height=1, density=9, invert=False, smooth=False, flip=False, | ||||||
|  |             double_width=False, double_height=False, custom_size=False): | ||||||
|  |         """ Set text properties by sending them to the printer | ||||||
|  |  | ||||||
|  |         :param align: horizontal position for text, possible values are: | ||||||
|  |  | ||||||
|  |             * 'center' | ||||||
|  |             * 'left' | ||||||
|  |             * 'right' | ||||||
|  |  | ||||||
|  |             *default*: 'left' | ||||||
|  |  | ||||||
|  |         :param font: font given as an index, a name, or one of the | ||||||
|  |             special values 'a' or 'b', 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 | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         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() | ||||||
|  |         else: | ||||||
|  |             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_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. | ||||||
|  |  | ||||||
|  |         If no spacing is given, we reset it to the default. | ||||||
|  |  | ||||||
|  |         There are different commands for setting the line spacing, using | ||||||
|  |         a different denominator: | ||||||
|  |  | ||||||
|  |         '+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255 | ||||||
|  |         '3' line_spacing/180 of an inch, 0 <= line_spacing <= 255 | ||||||
|  |         'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85 | ||||||
|  |  | ||||||
|  |         Some printers may not support all of them. The most commonly | ||||||
|  |         available command (using a divisor of 180) is chosen. | ||||||
|  |         """ | ||||||
|  |         if spacing is None: | ||||||
|  |             self._raw(LINESPACING_RESET) | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if divisor not in LINESPACING_FUNCS: | ||||||
|  |             raise ValueError('divisor must be either 360, 180 or 60') | ||||||
|  |         if (divisor in [360, 180] and (not(0 <= spacing <= 255))): | ||||||
|  |             raise ValueError('spacing must be a int between 0 and 255 when divisor is 360 or 180') | ||||||
|  |         if divisor == 60 and (not(0 <= spacing <= 85)): | ||||||
|  |             raise ValueError('spacing must be a int between 0 and 85 when divisor is 60') | ||||||
|  |  | ||||||
|  |         self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) | ||||||
|  |  | ||||||
|  |     def cut(self, mode='FULL', feed=True): | ||||||
|  |         """ Cut paper. | ||||||
|  |  | ||||||
|  |         Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will | ||||||
|  |         be attempted. Note however, that not all models can do a partial cut. See the documentation of | ||||||
|  |         your printer for details. | ||||||
|  |  | ||||||
|  |         .. todo:: Check this function on TM-T88II. | ||||||
|  |  | ||||||
|  |         :param mode: set to 'PART' for a partial cut. default: 'FULL' | ||||||
|  |         :param feed: print and feed before cutting. default: true | ||||||
|  |         :raises ValueError: if mode not in ('FULL', 'PART') | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         if not feed: | ||||||
|  |             self._raw(GS + b'V' + six.int2byte(66) + b'\x00') | ||||||
|  |             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) | ||||||
|  |             elif self.profile.supports('paperFullCut'): | ||||||
|  |                 self._raw(PAPER_FULL_CUT) | ||||||
|  |         elif mode == 'FULL': | ||||||
|  |             if self.profile.supports('paperFullCut'): | ||||||
|  |                 self._raw(PAPER_FULL_CUT) | ||||||
|  |             elif self.profile.supports('paperPartCut'): | ||||||
|  |                 self._raw(PAPER_PART_CUT) | ||||||
|  |  | ||||||
|  |     def cashdraw(self, pin): | ||||||
|  |         """ Send pulse to kick the cash drawer | ||||||
|  |  | ||||||
|  |         Kick cash drawer on pin 2 or pin 5 according to default parameter. | ||||||
|  |         For non default parameter send a decimal sequence i.e. [27,112,48] or [27,112,0,25,255] | ||||||
|  |  | ||||||
|  |         :param pin: pin number, 2 or 5 or list of decimals | ||||||
|  |         :raises: :py:exc:`~escpos.exceptions.CashDrawerError` | ||||||
|  |         """ | ||||||
|  |         if pin == 2: | ||||||
|  |             self._raw(CD_KICK_2) | ||||||
|  |         elif pin == 5: | ||||||
|  |             self._raw(CD_KICK_5) | ||||||
|  |         else: | ||||||
|  |             try: | ||||||
|  |                 self._raw(CD_KICK_DEC_SEQUENCE(*pin)) | ||||||
|  |             except 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 | ||||||
|  |  | ||||||
|  |         :param hw: hardware action, may be: | ||||||
|  |  | ||||||
|  |             * INIT | ||||||
|  |             * SELECT | ||||||
|  |             * RESET | ||||||
|  |         """ | ||||||
|  |         if hw.upper() == 'INIT': | ||||||
|  |             self._raw(HW_INIT) | ||||||
|  |         elif hw.upper() == 'SELECT': | ||||||
|  |             self._raw(HW_SELECT) | ||||||
|  |         elif hw.upper() == 'RESET': | ||||||
|  |             self._raw(HW_RESET) | ||||||
|  |         else:  # DEFAULT: DOES NOTHING | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     def print_and_feed(self, n=1): | ||||||
|  |         """ Print data in print buffer and feed *n* lines | ||||||
|  |  | ||||||
|  |             if n not in range (0, 255) then ValueError will be raised | ||||||
|  |  | ||||||
|  |             :param n: number of n to feed. 0 <= n <= 255. default: 1 | ||||||
|  |             :raises ValueError: if not 0 <= n <= 255 | ||||||
|  |         """ | ||||||
|  |         if 0 <= n <= 255: | ||||||
|  |             # ESC d n | ||||||
|  |             self._raw(ESC + b'd' + six.int2byte(n)) | ||||||
|  |         else: | ||||||
|  |             raise ValueError('n must be betwen 0 and 255') | ||||||
|  |  | ||||||
|  |     def control(self, ctl, count=5, tab_size=8): | ||||||
|  |         """ Feed control sequences | ||||||
|  |  | ||||||
|  |         :param ctl: string for the following control sequences: | ||||||
|  |  | ||||||
|  |             * LF *for Line Feed* | ||||||
|  |             * FF *for Form Feed* | ||||||
|  |             * CR *for Carriage Return* | ||||||
|  |             * HT *for Horizontal Tab* | ||||||
|  |             * VT *for Vertical Tab* | ||||||
|  |  | ||||||
|  |         :param 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 position | ||||||
|  |         if ctl.upper() == 'LF': | ||||||
|  |             self._raw(CTL_LF) | ||||||
|  |         elif ctl.upper() == 'FF': | ||||||
|  |             self._raw(CTL_FF) | ||||||
|  |         elif ctl.upper() == 'CR': | ||||||
|  |             self._raw(CTL_CR) | ||||||
|  |         elif ctl.upper() == 'HT': | ||||||
|  |             if not (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) | ||||||
|  |  | ||||||
|  |     def panel_buttons(self, enable=True): | ||||||
|  |         """ Controls the panel buttons on the printer (e.g. FEED) | ||||||
|  |  | ||||||
|  |         When enable is set to False the panel buttons on the printer will be disabled. Calling the method with | ||||||
|  |         enable=True or without argument will enable the panel buttons. | ||||||
|  |  | ||||||
|  |         If panel buttons are enabled, the function of the panel button, such as feeding, will be executed upon pressing | ||||||
|  |         the button. If the panel buttons are disabled, pressing them will not have any effect. | ||||||
|  |  | ||||||
|  |         This command is effective until the printer is initialized, reset or power-cycled. The default is enabled panel | ||||||
|  |         buttons. | ||||||
|  |  | ||||||
|  |         Some panel buttons will always work, especially when printer is opened. See for more information the manual | ||||||
|  |         of your printer and the escpos-command-reference. | ||||||
|  |  | ||||||
|  |         :param enable: controls the panel buttons | ||||||
|  |         :rtype: None | ||||||
|  |         """ | ||||||
|  |         if enable: | ||||||
|  |             self._raw(PANEL_BUTTON_ON) | ||||||
|  |         else: | ||||||
|  |             self._raw(PANEL_BUTTON_OFF) | ||||||
|  |  | ||||||
|  |     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 EscposIO class. | ||||||
|  |     This example explains the usage: | ||||||
|  |  | ||||||
|  |     .. code-block:: Python | ||||||
|  |  | ||||||
|  |         with EscposIO(printer.Serial('/dev/ttyUSB0')) as p: | ||||||
|  |             p.set(font='a', height=2, align='center', text_type='bold') | ||||||
|  |             p.printer.set(align='left') | ||||||
|  |             p.printer.image('logo.gif') | ||||||
|  |             p.writelines('Big line\\n', font='b') | ||||||
|  |             p.writelines('Привет') | ||||||
|  |             p.writelines('BIG TEXT', width=2) | ||||||
|  |  | ||||||
|  |     After the `with`-statement the printer automatically cuts the paper if `autocut` is `True`. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, printer, autocut=True, autoclose=True, **kwargs): | ||||||
|  |         """ | ||||||
|  |         :param printer: An EscPos-printer object | ||||||
|  |         :type printer: escpos.Escpos | ||||||
|  |         :param autocut: If True, paper is automatically cut after the `with`-statement *default*: True | ||||||
|  |         :param kwargs: These arguments will be passed to :py:meth:`escpos.Escpos.set()` | ||||||
|  |         """ | ||||||
|  |         self.printer = printer | ||||||
|  |         self.params = kwargs | ||||||
|  |         self.autocut = autocut | ||||||
|  |         self.autoclose = autoclose | ||||||
|  |  | ||||||
|  |     def set(self, **kwargs): | ||||||
|  |         """ Set the printer-parameters | ||||||
|  |  | ||||||
|  |         Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`. | ||||||
|  |         For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods | ||||||
|  |         documentation. These parameters can also be passed with this class' constructor or the | ||||||
|  |         :py:meth:`~escpos.escpos.EscposIO.writelines()`-method. | ||||||
|  |  | ||||||
|  |         :param kwargs: keyword-parameters that will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>` | ||||||
|  |         """ | ||||||
|  |         self.params.update(kwargs) | ||||||
|  |  | ||||||
|  |     def writelines(self, text, **kwargs): | ||||||
|  |         params = dict(self.params) | ||||||
|  |         params.update(kwargs) | ||||||
|  |  | ||||||
|  |         if isinstance(text, six.text_type): | ||||||
|  |             lines = text.split('\n') | ||||||
|  |         elif isinstance(text, list) or isinstance(text, tuple): | ||||||
|  |             lines = text | ||||||
|  |         else: | ||||||
|  |             lines = ['{0}'.format(text), ] | ||||||
|  |  | ||||||
|  |         # TODO check unicode handling | ||||||
|  |         # TODO flush? or on print? (this should prob rather be handled by the _raw-method) | ||||||
|  |         for line in lines: | ||||||
|  |             self.printer.set(**params) | ||||||
|  |             if isinstance(text, six.text_type): | ||||||
|  |                 self.printer.text(u'{0}\n'.format(line)) | ||||||
|  |             else: | ||||||
|  |                 self.printer.text('{0}\n'.format(line)) | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         """ called upon closing the `with`-statement | ||||||
|  |         """ | ||||||
|  |         self.printer.close() | ||||||
|  |  | ||||||
|  |     def __enter__(self, **kwargs): | ||||||
|  |         return self | ||||||
|  |  | ||||||
|  |     def __exit__(self, type, value, traceback): | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor), | ||||||
|  |         then :py:meth:`printer.cut() <escpos.escpos.Escpos.cut()>` will be called here. | ||||||
|  |         """ | ||||||
|  |         if not (type is not None and issubclass(type, Exception)): | ||||||
|  |             if self.autocut: | ||||||
|  |                 self.printer.cut() | ||||||
|  |  | ||||||
|  |         if self.autoclose: | ||||||
|  |             self.close() | ||||||
							
								
								
									
										258
									
								
								src/escpos/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,258 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ ESC/POS Exceptions classes | ||||||
|  |  | ||||||
|  | Result/Exit codes: | ||||||
|  |  | ||||||
|  |     - `0`  = success | ||||||
|  |     - `10` = No Barcode type defined :py:exc:`~escpos.exceptions.BarcodeTypeError` | ||||||
|  |     - `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError` | ||||||
|  |     - `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError` | ||||||
|  |     - `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError` | ||||||
|  |     - `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` | ||||||
|  |     - `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError` | ||||||
|  |     - `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError` | ||||||
|  |     - `100` = Set variable out of range :py:exc:`~escpos.exceptions.SetVariableError` | ||||||
|  |     - `200` = Configuration not found :py:exc:`~escpos.exceptions.ConfigNotFoundError` | ||||||
|  |     - `210` = Configuration syntax error :py:exc:`~escpos.exceptions.ConfigSyntaxError` | ||||||
|  |     - `220` = Configuration section not found :py:exc:`~escpos.exceptions.ConfigSectionMissingError` | ||||||
|  |  | ||||||
|  | :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||||
|  | :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||||
|  | :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||||
|  | :license: MIT | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Error(Exception): | ||||||
|  |     """ Base class for ESC/POS errors """ | ||||||
|  |     def __init__(self, msg, status=None): | ||||||
|  |         Exception.__init__(self) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 1 | ||||||
|  |         if status is not None: | ||||||
|  |             self.resultcode = status | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return self.msg | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BarcodeTypeError(Error): | ||||||
|  |     """ No Barcode type defined. | ||||||
|  |  | ||||||
|  |     This exception indicates that no known barcode-type has been entered. The barcode-type has to be | ||||||
|  |     one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`. | ||||||
|  |     The returned error code is `10`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 10 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'No Barcode type is defined ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BarcodeSizeError(Error): | ||||||
|  |     """ Barcode size is out of range. | ||||||
|  |  | ||||||
|  |     This exception indicates that the values for the barcode size are out of range. | ||||||
|  |     The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`. | ||||||
|  |     The resulting returncode is `20`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 20 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Barcode size is out of range ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BarcodeCodeError(Error): | ||||||
|  |     """ No Barcode code was supplied, or it is incorrect. | ||||||
|  |  | ||||||
|  |     No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter | ||||||
|  |     was True and the check failed. | ||||||
|  |     The returncode for this exception is `30`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 30 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'No Barcode code was supplied ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ImageSizeError(Error): | ||||||
|  |     """ Image height is longer than 255px and can't be printed. | ||||||
|  |  | ||||||
|  |     The returncode for this exception is `40`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 40 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class 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): | ||||||
|  |     """ Text string must be supplied to the `text()` method. | ||||||
|  |  | ||||||
|  |     This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`. | ||||||
|  |     The returncode for this exception is `50`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 50 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Text string must be supplied to the text() method ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CashDrawerError(Error): | ||||||
|  |     """ Valid pin must be set in order to send pulse. | ||||||
|  |  | ||||||
|  |     A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`. | ||||||
|  |     The returncode for this exception is `60`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 60 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Valid pin must be set to send pulse ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TabPosError(Error): | ||||||
|  |     """ Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. | ||||||
|  |     Both values multiplied must not exceed 255, since it is the maximum tab value. | ||||||
|  |  | ||||||
|  |     This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. | ||||||
|  |     The returncode for this exception is `70`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 70 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Valid tab positions must be in the range 0 to 16 ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CharCodeError(Error): | ||||||
|  |     """ Valid char code must be set. | ||||||
|  |  | ||||||
|  |     The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown. | ||||||
|  |     Ths returncode for this exception is `80`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 80 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Valid char code must be set ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class USBNotFoundError(Error): | ||||||
|  |     """ Device wasn't found (probably not plugged in) | ||||||
|  |  | ||||||
|  |     The USB device seems to be not plugged in. | ||||||
|  |     Ths returncode for this exception is `90`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 90 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'USB device not found ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SetVariableError(Error): | ||||||
|  |     """ A set method variable was out of range | ||||||
|  |  | ||||||
|  |     Check set variables against minimum and maximum values | ||||||
|  |     Ths returncode for this exception is `100`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 100 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Set variable out of range ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Configuration errors | ||||||
|  |  | ||||||
|  | class ConfigNotFoundError(Error): | ||||||
|  |     """ The configuration file was not found | ||||||
|  |  | ||||||
|  |     The default or passed configuration file could not be read | ||||||
|  |     Ths returncode for this exception is `200`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 200 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Configuration not found ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConfigSyntaxError(Error): | ||||||
|  |     """ The configuration file is invalid | ||||||
|  |  | ||||||
|  |     The syntax is incorrect | ||||||
|  |     Ths returncode for this exception is `210`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 210 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Configuration syntax is invalid ({msg})'.format(msg=self.msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConfigSectionMissingError(Error): | ||||||
|  |     """ The configuration file is missing a section | ||||||
|  |  | ||||||
|  |     The part of the config asked for doesn't exist in the loaded configuration | ||||||
|  |     Ths returncode for this exception is `220`. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, msg=''): | ||||||
|  |         Error.__init__(self, msg) | ||||||
|  |         self.msg = msg | ||||||
|  |         self.resultcode = 220 | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return 'Configuration section is missing ({msg})'.format(msg=self.msg) | ||||||
							
								
								
									
										135
									
								
								src/escpos/image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,135 @@ | |||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ Image format handling class | ||||||
|  |  | ||||||
|  | This module contains the image format handler :py:class:`EscposImage`. | ||||||
|  |  | ||||||
|  | :author: `Michael Billington <michael.billington@gmail.com>`_ | ||||||
|  | :organization: `python-escpos <https://github.com/python-escpos>`_ | ||||||
|  | :copyright: Copyright (c) 2016 Michael Billington <michael.billington@gmail.com> | ||||||
|  | :license: MIT | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import math | ||||||
|  |  | ||||||
|  | from PIL import Image, ImageOps | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EscposImage(object): | ||||||
|  |     """ | ||||||
|  |     Load images in, and output ESC/POS formats. | ||||||
|  |  | ||||||
|  |     The class is designed to efficiently delegate image processing to | ||||||
|  |     PIL, rather than spend CPU cycles looping over pixels. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, img_source): | ||||||
|  |         """ | ||||||
|  |         Load in an image | ||||||
|  |  | ||||||
|  |         :param img_source: PIL.Image, or filename to load one from. | ||||||
|  |         """ | ||||||
|  |         if isinstance(img_source, Image.Image): | ||||||
|  |             img_original = img_source | ||||||
|  |         else: | ||||||
|  |             img_original = Image.open(img_source) | ||||||
|  |  | ||||||
|  |         # store image for eventual further processing (splitting) | ||||||
|  |         self.img_original = img_original | ||||||
|  |  | ||||||
|  |         # Convert to white RGB background, paste over white background | ||||||
|  |         # to strip alpha. | ||||||
|  |         img_original = img_original.convert('RGBA') | ||||||
|  |         im = Image.new('RGB', img_original.size, (255, 255, 255)) | ||||||
|  |         im.paste(img_original, mask=img_original.split()[3]) | ||||||
|  |         # Convert down to greyscale | ||||||
|  |         im = im.convert('L') | ||||||
|  |         # Invert: Only works on 'L' images | ||||||
|  |         im = ImageOps.invert(im) | ||||||
|  |         # Pure black and white | ||||||
|  |         self._im = im.convert('1') | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def width(self): | ||||||
|  |         """ | ||||||
|  |         Width of image in pixels | ||||||
|  |         """ | ||||||
|  |         width_pixels, _ = self._im.size | ||||||
|  |         return width_pixels | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def width_bytes(self): | ||||||
|  |         """ | ||||||
|  |         Width of image if you use 8 pixels per byte and 0-pad at the end. | ||||||
|  |         """ | ||||||
|  |         return (self.width + 7) >> 3 | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def height(self): | ||||||
|  |         """ | ||||||
|  |         Height of image in pixels | ||||||
|  |         """ | ||||||
|  |         _, height_pixels = self._im.size | ||||||
|  |         return height_pixels | ||||||
|  |  | ||||||
|  |     def to_column_format(self, high_density_vertical=True): | ||||||
|  |         """ | ||||||
|  |         Extract slices of an image as equal-sized blobs of column-format data. | ||||||
|  |  | ||||||
|  |         :param high_density_vertical: Printed line height in dots | ||||||
|  |         """ | ||||||
|  |         im = self._im.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT) | ||||||
|  |         line_height = 24 if high_density_vertical else 8 | ||||||
|  |         width_pixels, height_pixels = im.size | ||||||
|  |         top = 0 | ||||||
|  |         left = 0 | ||||||
|  |         while left < width_pixels: | ||||||
|  |             box = (left, top, left + line_height, top + height_pixels) | ||||||
|  |             im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box) | ||||||
|  |             im_bytes = im_slice.tobytes() | ||||||
|  |             yield(im_bytes) | ||||||
|  |             left += line_height | ||||||
|  |  | ||||||
|  |     def to_raster_format(self): | ||||||
|  |         """ | ||||||
|  |         Convert image to raster-format binary | ||||||
|  |         """ | ||||||
|  |         return self._im.tobytes() | ||||||
|  |  | ||||||
|  |     def split(self, fragment_height): | ||||||
|  |         """ | ||||||
|  |         Split an image into multiple fragments after fragment_height pixels | ||||||
|  |  | ||||||
|  |         :param fragment_height: height of fragment | ||||||
|  |         :return: list of PIL objects | ||||||
|  |         """ | ||||||
|  |         passes = int(math.ceil(self.height // fragment_height)) | ||||||
|  |         fragments = [] | ||||||
|  |         for n in range(0, passes): | ||||||
|  |             left = 0 | ||||||
|  |             right = self.width | ||||||
|  |             upper = n * fragment_height | ||||||
|  |             lower = min((n + 1) * fragment_height, self.height) | ||||||
|  |             box = (left, upper, right, lower) | ||||||
|  |             fragments.append(self.img_original.crop(box)) | ||||||
|  |         return fragments | ||||||
|  |  | ||||||
|  |     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', | ||||||
|  | } | ||||||
							
								
								
									
										298
									
								
								src/escpos/magicencode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,298 @@ | |||||||
|  | #!/usr/bin/env 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 | ||||||
|  |  | ||||||
|  | import six | ||||||
|  |  | ||||||
|  | from .codepages import CodePages | ||||||
|  | from .constants import CODEPAGE_CHANGE | ||||||
|  | from .exceptions import Error | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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)) | ||||||
							
								
								
									
										326
									
								
								src/escpos/printer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,326 @@ | |||||||
|  | #!/usr/bin/python | ||||||
|  | #  -*- coding: utf-8 -*- | ||||||
|  | """ This module contains the implementations of abstract base class :py:class:`Escpos`. | ||||||
|  |  | ||||||
|  | :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others | ||||||
|  | :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ | ||||||
|  | :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos | ||||||
|  | :license: MIT | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import absolute_import | ||||||
|  | from __future__ import division | ||||||
|  | from __future__ import print_function | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import socket | ||||||
|  |  | ||||||
|  | import serial | ||||||
|  |  | ||||||
|  | import usb.core | ||||||
|  | import usb.util | ||||||
|  |  | ||||||
|  | from .escpos import Escpos | ||||||
|  | from .exceptions import USBNotFoundError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Usb(Escpos): | ||||||
|  |     """ USB printer | ||||||
|  |  | ||||||
|  |     This class describes a printer that natively speaks USB. | ||||||
|  |  | ||||||
|  |     inheritance: | ||||||
|  |  | ||||||
|  |     .. inheritance-diagram:: escpos.printer.Usb | ||||||
|  |         :parts: 1 | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs):  # noqa: N803 | ||||||
|  |         """ | ||||||
|  |         :param idVendor: Vendor ID | ||||||
|  |         :param idProduct: Product ID | ||||||
|  |         :param timeout: Is the time limit of the USB operation. Default without timeout. | ||||||
|  |         :param in_ep: Input end point | ||||||
|  |         :param out_ep: Output end point | ||||||
|  |         """ | ||||||
|  |         Escpos.__init__(self, *args, **kwargs) | ||||||
|  |         self.idVendor = idVendor | ||||||
|  |         self.idProduct = idProduct | ||||||
|  |         self.timeout = timeout | ||||||
|  |         self.in_ep = in_ep | ||||||
|  |         self.out_ep = out_ep | ||||||
|  |         self.open() | ||||||
|  |  | ||||||
|  |     def open(self): | ||||||
|  |         """ Search device on USB tree and set it as escpos device """ | ||||||
|  |         self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) | ||||||
|  |         if self.device is None: | ||||||
|  |             raise USBNotFoundError('Device not found or cable not plugged in.') | ||||||
|  |  | ||||||
|  |         check_driver = None | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             check_driver = self.device.is_kernel_driver_active(0) | ||||||
|  |         except NotImplementedError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         if check_driver is None or check_driver: | ||||||
|  |             try: | ||||||
|  |                 self.device.detach_kernel_driver(0) | ||||||
|  |             except usb.core.USBError as e: | ||||||
|  |                 if check_driver is not None: | ||||||
|  |                     print('Could not detatch kernel driver: {0}'.format(str(e))) | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             self.device.set_configuration() | ||||||
|  |             self.device.reset() | ||||||
|  |         except usb.core.USBError as e: | ||||||
|  |             print('Could not set configuration: {0}'.format(str(e))) | ||||||
|  |  | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Print any command sent in raw format | ||||||
|  |  | ||||||
|  |         :param msg: arbitrary code to be printed | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         self.device.write(self.out_ep, msg, self.timeout) | ||||||
|  |  | ||||||
|  |     def _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: | ||||||
|  |             usb.util.dispose_resources(self.device) | ||||||
|  |         self.device = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Serial(Escpos): | ||||||
|  |     """ Serial printer | ||||||
|  |  | ||||||
|  |     This class describes a printer that is connected by serial interface. | ||||||
|  |  | ||||||
|  |     inheritance: | ||||||
|  |  | ||||||
|  |     .. inheritance-diagram:: escpos.printer.Serial | ||||||
|  |         :parts: 1 | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, devfile='/dev/ttyS0', baudrate=9600, bytesize=8, timeout=1, | ||||||
|  |                  parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, | ||||||
|  |                  xonxoff=False, dsrdtr=True, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         :param devfile:  Device file under dev filesystem | ||||||
|  |         :param baudrate: Baud rate for serial transmission | ||||||
|  |         :param bytesize: Serial buffer size | ||||||
|  |         :param timeout:  Read/Write timeout | ||||||
|  |         :param parity:   Parity checking | ||||||
|  |         :param stopbits: Number of stop bits | ||||||
|  |         :param xonxoff:  Software flow control | ||||||
|  |         :param dsrdtr:   Hardware flow control (False to enable RTS/CTS) | ||||||
|  |         """ | ||||||
|  |         Escpos.__init__(self, *args, **kwargs) | ||||||
|  |         self.devfile = devfile | ||||||
|  |         self.baudrate = baudrate | ||||||
|  |         self.bytesize = bytesize | ||||||
|  |         self.timeout = timeout | ||||||
|  |         self.parity = parity | ||||||
|  |         self.stopbits = stopbits | ||||||
|  |         self.xonxoff = xonxoff | ||||||
|  |         self.dsrdtr = dsrdtr | ||||||
|  |  | ||||||
|  |         self.open() | ||||||
|  |  | ||||||
|  |     def open(self): | ||||||
|  |         """ Setup serial port and set is as escpos device """ | ||||||
|  |         if self.device is not None and self.device.is_open: | ||||||
|  |             self.close() | ||||||
|  |         self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, | ||||||
|  |                                     bytesize=self.bytesize, parity=self.parity, | ||||||
|  |                                     stopbits=self.stopbits, timeout=self.timeout, | ||||||
|  |                                     xonxoff=self.xonxoff, dsrdtr=self.dsrdtr) | ||||||
|  |  | ||||||
|  |         if self.device is not None: | ||||||
|  |             print('Serial printer enabled') | ||||||
|  |         else: | ||||||
|  |             print('Unable to open serial printer on: {0}'.format(str(self.devfile))) | ||||||
|  |  | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Print any command sent in raw format | ||||||
|  |  | ||||||
|  |         :param msg: arbitrary code to be printed | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         self.device.write(msg) | ||||||
|  |  | ||||||
|  |     def _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 and self.device.is_open: | ||||||
|  |             self.device.flush() | ||||||
|  |             self.device.close() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Network(Escpos): | ||||||
|  |     """ Network printer | ||||||
|  |  | ||||||
|  |     This class is used to attach to a networked printer. You can also use this in order to attach to a printer that | ||||||
|  |     is forwarded with ``socat``. | ||||||
|  |  | ||||||
|  |     If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with: | ||||||
|  |  | ||||||
|  |     .. code-block:: none | ||||||
|  |  | ||||||
|  |         socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0 | ||||||
|  |  | ||||||
|  |     Then you should be able to attach to port ``4242`` with this class. | ||||||
|  |     Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should | ||||||
|  |     work the same with this class. For the address of the printer check its manuals. | ||||||
|  |  | ||||||
|  |     inheritance: | ||||||
|  |  | ||||||
|  |     .. inheritance-diagram:: escpos.printer.Network | ||||||
|  |         :parts: 1 | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, host, port=9100, timeout=60, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         :param host:    Printer's hostname or IP address | ||||||
|  |         :param port:    Port to write to | ||||||
|  |         :param timeout: timeout in seconds for the socket-library | ||||||
|  |         """ | ||||||
|  |         Escpos.__init__(self, *args, **kwargs) | ||||||
|  |         self.host = host | ||||||
|  |         self.port = port | ||||||
|  |         self.timeout = timeout | ||||||
|  |         self.open() | ||||||
|  |  | ||||||
|  |     def open(self): | ||||||
|  |         """ Open TCP socket with ``socket``-library and set it as escpos device """ | ||||||
|  |         self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||||
|  |         self.device.settimeout(self.timeout) | ||||||
|  |         self.device.connect((self.host, self.port)) | ||||||
|  |  | ||||||
|  |         if self.device is None: | ||||||
|  |             print('Could not open socket for {0}'.format(self.host)) | ||||||
|  |  | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Print any command sent in raw format | ||||||
|  |  | ||||||
|  |         :param msg: arbitrary code to be printed | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         self.device.sendall(msg) | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         """ Close TCP connection """ | ||||||
|  |         if self.device is not None: | ||||||
|  |             self.device.shutdown(socket.SHUT_RDWR) | ||||||
|  |             self.device.close() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class File(Escpos): | ||||||
|  |     """ Generic file printer | ||||||
|  |  | ||||||
|  |     This class is used for parallel port printer or other printers that are directly attached to the filesystem. | ||||||
|  |     Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable | ||||||
|  |     and produce arbitrary errors. | ||||||
|  |  | ||||||
|  |     inheritance: | ||||||
|  |  | ||||||
|  |     .. inheritance-diagram:: escpos.printer.File | ||||||
|  |         :parts: 1 | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, devfile='/dev/usb/lp0', auto_flush=True, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         :param devfile: Device file under dev filesystem | ||||||
|  |         :param auto_flush: automatically call flush after every call of _raw() | ||||||
|  |         """ | ||||||
|  |         Escpos.__init__(self, *args, **kwargs) | ||||||
|  |         self.devfile = devfile | ||||||
|  |         self.auto_flush = auto_flush | ||||||
|  |         self.open() | ||||||
|  |  | ||||||
|  |     def open(self): | ||||||
|  |         """ Open system file """ | ||||||
|  |         self.device = open(self.devfile, 'wb') | ||||||
|  |  | ||||||
|  |         if self.device is None: | ||||||
|  |             print('Could not open the specified file {0}'.format(self.devfile)) | ||||||
|  |  | ||||||
|  |     def flush(self): | ||||||
|  |         """ Flush printing content """ | ||||||
|  |         self.device.flush() | ||||||
|  |  | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Print any command sent in raw format | ||||||
|  |  | ||||||
|  |         :param msg: arbitrary code to be printed | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         self.device.write(msg) | ||||||
|  |         if self.auto_flush: | ||||||
|  |             self.flush() | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         """ Close system file """ | ||||||
|  |         if self.device is not None: | ||||||
|  |             self.device.flush() | ||||||
|  |             self.device.close() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Dummy(Escpos): | ||||||
|  |     """ Dummy printer | ||||||
|  |  | ||||||
|  |     This class is used for saving commands to a variable, for use in situations where | ||||||
|  |     there is no need to send commands to an actual printer. This includes | ||||||
|  |     generating print jobs for later use, or testing output. | ||||||
|  |  | ||||||
|  |     inheritance: | ||||||
|  |  | ||||||
|  |     .. inheritance-diagram:: escpos.printer.Dummy | ||||||
|  |         :parts: 1 | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         """ | ||||||
|  |         Escpos.__init__(self, *args, **kwargs) | ||||||
|  |         self._output_list = [] | ||||||
|  |  | ||||||
|  |     def _raw(self, msg): | ||||||
|  |         """ Print any command sent in raw format | ||||||
|  |  | ||||||
|  |         :param msg: arbitrary code to be printed | ||||||
|  |         :type msg: bytes | ||||||
|  |         """ | ||||||
|  |         self._output_list.append(msg) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def output(self): | ||||||
|  |         """ Get the data that was sent to this printer """ | ||||||
|  |         return b''.join(self._output_list) | ||||||
|  |  | ||||||
|  |     def 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 | ||||||
							
								
								
									
										7
									
								
								test/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | |||||||
|  | import pytest | ||||||
|  | from escpos.printer import Dummy | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def driver(): | ||||||
|  |     return Dummy() | ||||||
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_transparent.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 65 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_transparent.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 167 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 65 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 175 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/black_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 156 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 72 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 160 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 239 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 72 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 160 B | 
							
								
								
									
										
											BIN
										
									
								
								test/resources/canvas_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 239 B |