Compare commits
737 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b826aa834 | ||
![]() |
3560f8b12b | ||
![]() |
83c7f5745c | ||
![]() |
7bf8d4e4b8 | ||
![]() |
6849dd3979 | ||
![]() |
25867f3196 | ||
![]() |
8bbbf7ceac | ||
![]() |
5b72985b0a | ||
![]() |
ef7d518953 | ||
![]() |
d4d3819d55 | ||
![]() |
98ee06a783 | ||
![]() |
de515a86cc | ||
![]() |
2b0d57a9c7 | ||
![]() |
1626af5def | ||
![]() |
e4a73ad5c5 | ||
![]() |
709d90809c | ||
![]() |
90c340163c | ||
![]() |
757fe3328c | ||
![]() |
0a8d8ae6c4 | ||
![]() |
70d4da1364 | ||
![]() |
cbf887ed02 | ||
![]() |
b1a1204bb8 | ||
![]() |
361afe3555 | ||
![]() |
2bc61d9bf8 | ||
![]() |
67827d8e4d | ||
![]() |
2567e0e12a | ||
![]() |
83cccf0cb9 | ||
![]() |
fe9c0573f8 | ||
![]() |
ea61f287cb | ||
![]() |
69e9dca761 | ||
![]() |
49ab3f35a8 | ||
![]() |
84a3912b34 | ||
![]() |
9a3057c8a8 | ||
![]() |
0b695eff79 | ||
![]() |
f0760ddbc0 | ||
![]() |
39826c3286 | ||
![]() |
7f9878282d | ||
![]() |
5e80764ff5 | ||
![]() |
219b43d35a | ||
![]() |
b16f44257e | ||
![]() |
5bb0642b5d | ||
![]() |
8e5c600e20 | ||
![]() |
435f2bba24 | ||
![]() |
109a5d8a92 | ||
![]() |
4a88bacd3f | ||
![]() |
19abf43448 | ||
![]() |
7b6dbc4db4 | ||
![]() |
53aab73ffc | ||
![]() |
a4e15c6ac2 | ||
![]() |
dcfb8fec30 | ||
![]() |
14edc87206 | ||
![]() |
1e3024eaa9 | ||
![]() |
15c18f225e | ||
![]() |
a526be5ddf | ||
![]() |
92fbcabdd2 | ||
![]() |
27b7ae8629 | ||
![]() |
7c82881f65 | ||
![]() |
05f68b22b4 | ||
![]() |
f9ce777057 | ||
![]() |
c2e3b79e5a | ||
![]() |
b5a999a6ea | ||
![]() |
0b2118e146 | ||
![]() |
69e9b9103e | ||
![]() |
620cf97bbf | ||
![]() |
1ad53eb642 | ||
![]() |
ed8ec0788a | ||
![]() |
dd3c768f13 | ||
![]() |
36bbb6690f | ||
![]() |
0758a79e64 | ||
![]() |
9c41cfc54f | ||
![]() |
75006f62da | ||
![]() |
58e1682448 | ||
![]() |
0057a498bb | ||
![]() |
b3e93020d4 | ||
![]() |
3962bc991f | ||
![]() |
b608d59942 | ||
![]() |
e898413464 | ||
![]() |
357558ae10 | ||
![]() |
809b4f47aa | ||
![]() |
b6516e4b42 | ||
![]() |
8a54d59dbc | ||
![]() |
1275bf90d1 | ||
![]() |
d6639d37f3 | ||
![]() |
21f19b7f7e | ||
![]() |
3c479407dc | ||
![]() |
3f04ff352e | ||
![]() |
6c89e93c22 | ||
![]() |
266b64bf2b | ||
![]() |
a2e348d04a | ||
![]() |
40c8399a68 | ||
![]() |
a279ae7152 | ||
![]() |
a2d553efb1 | ||
![]() |
84a97e5b9f | ||
![]() |
7db3d30f4a | ||
![]() |
c6f93e017a | ||
![]() |
c09c10cb2d | ||
![]() |
af4785d52f | ||
![]() |
e68cdcdac2 | ||
![]() |
18cc1a268a | ||
![]() |
5d62be2be9 | ||
![]() |
1dc79baf82 | ||
![]() |
247211d0ce | ||
![]() |
243eb48b38 | ||
![]() |
21b9dba345 | ||
![]() |
a8941b4a3e | ||
![]() |
04e0a0ce47 | ||
![]() |
8ece137789 | ||
![]() |
d390449400 | ||
![]() |
8488fafd75 | ||
![]() |
818c1313e8 | ||
![]() |
596787554b | ||
![]() |
2d798ca7e5 | ||
![]() |
24105226b2 | ||
![]() |
c57b9c35dc | ||
![]() |
0a8477a888 | ||
![]() |
e3cf088d69 | ||
![]() |
323db98318 | ||
![]() |
ee6eef6db3 | ||
![]() |
ef34cca463 | ||
![]() |
4680fb16b1 | ||
![]() |
544bb4f904 | ||
![]() |
d330dd80fd | ||
![]() |
570661b3c8 | ||
![]() |
aa7f2773eb | ||
![]() |
ff39908674 | ||
![]() |
d960eea4a8 | ||
![]() |
f7962576b4 | ||
![]() |
f4e214ad17 | ||
![]() |
9e406efc86 | ||
![]() |
b9e3827867 | ||
![]() |
cbe412cfdb | ||
![]() |
42554d836c | ||
![]() |
8ca682e3ac | ||
![]() |
ab30ef4a8c | ||
![]() |
725f1254aa | ||
![]() |
1e313cefc6 | ||
![]() |
b0ea9aec41 | ||
![]() |
ab210b5996 | ||
![]() |
f3e1db8da2 | ||
![]() |
1e7b43a92d | ||
![]() |
0e41709703 | ||
![]() |
cb30d7a881 | ||
![]() |
142fc4af71 | ||
![]() |
8f71372bb0 | ||
![]() |
81fb7caa07 | ||
![]() |
6a60b6706e | ||
![]() |
c14a414924 | ||
![]() |
faa9414da5 | ||
![]() |
2c4552a528 | ||
![]() |
c9953c2f56 | ||
![]() |
557991d80b | ||
![]() |
fe08fc1469 | ||
![]() |
6c27222aeb | ||
![]() |
6731057456 | ||
![]() |
f0b1a89c48 | ||
![]() |
fd7bd0710e | ||
![]() |
7ea58625e6 | ||
![]() |
baffd98a22 | ||
![]() |
ecbdd43dff | ||
![]() |
5b6b96d2a0 | ||
![]() |
7aa20a60e3 | ||
![]() |
95ec6d5c08 | ||
![]() |
18c3a5f298 | ||
![]() |
4836dcd486 | ||
![]() |
d9d400da6d | ||
![]() |
fe2e1a6d28 | ||
![]() |
c53575a155 | ||
![]() |
cadf448c38 | ||
![]() |
7c05404ac4 | ||
![]() |
e1e1ccb3f2 | ||
![]() |
32c56e78ea | ||
![]() |
4d106e8659 | ||
![]() |
ddab5318cf | ||
![]() |
9b60e2e3ab | ||
![]() |
34cd1ebde1 | ||
![]() |
a2db415559 | ||
![]() |
4e19b0ca51 | ||
![]() |
a3660a6366 | ||
![]() |
9fa551e6e8 | ||
![]() |
ae0a049efa | ||
![]() |
117d286371 | ||
![]() |
a555a651b4 | ||
![]() |
e350a49cad | ||
![]() |
f49c1dcb89 | ||
![]() |
cc67cb1c1e | ||
![]() |
2ee3ff7f87 | ||
![]() |
ca45d25670 | ||
![]() |
f07d5e0610 | ||
![]() |
5eeff1c16b | ||
![]() |
51d1299285 | ||
![]() |
a30c28baa5 | ||
![]() |
0c0e6b9b4c | ||
![]() |
50437cc9d2 | ||
![]() |
7c7d401f31 | ||
![]() |
2110431d4f | ||
![]() |
c0481f3a9a | ||
![]() |
6161b46d57 | ||
![]() |
c86826101d | ||
![]() |
fa140c2df5 | ||
![]() |
8b3076871f | ||
![]() |
46429b1092 | ||
![]() |
99ca096f82 | ||
![]() |
2d97c0bbbd | ||
![]() |
6c6fe9bccf | ||
![]() |
c5e46a888d | ||
![]() |
46942820a5 | ||
![]() |
e50e295acc | ||
![]() |
2d7458fa49 | ||
![]() |
a6f635c0d5 | ||
![]() |
2d0f045457 | ||
![]() |
293b8632ff | ||
![]() |
5ff73595b6 | ||
![]() |
4ecab402b8 | ||
![]() |
c56e43da84 | ||
![]() |
88af26f46e | ||
![]() |
9dd966c2a3 | ||
![]() |
a7d959428f | ||
![]() |
8bf0e08659 | ||
![]() |
5ac5a24b50 | ||
![]() |
63252515b5 | ||
![]() |
29a546821b | ||
![]() |
4ddd18279f | ||
![]() |
de761e96e3 | ||
![]() |
ed7bce6932 | ||
![]() |
edd567785c | ||
![]() |
f1054876da | ||
![]() |
490e0657dd | ||
![]() |
b4c32b5a4a | ||
![]() |
40b30225d3 | ||
![]() |
19e3ec6895 | ||
![]() |
df539da854 | ||
![]() |
4534038b39 | ||
![]() |
adf85f7784 | ||
![]() |
aaa8162967 | ||
![]() |
0461adc212 | ||
![]() |
91ff83e506 | ||
![]() |
29ef88f591 | ||
![]() |
7c01a30d6c | ||
![]() |
035c425581 | ||
![]() |
d20646b2a9 | ||
![]() |
206822ac69 | ||
![]() |
dc08792e72 | ||
![]() |
2886075ce9 | ||
![]() |
73fff6291d | ||
![]() |
d5b9d99093 | ||
![]() |
18c51358aa | ||
![]() |
52719c0b7d | ||
![]() |
0051c876bf | ||
![]() |
854759d312 | ||
![]() |
a0343c66af | ||
![]() |
6c94f88c24 | ||
![]() |
6fb23d6826 | ||
![]() |
f649814091 | ||
![]() |
47b4d41b28 | ||
![]() |
599f4f3ca5 | ||
![]() |
d085e5c467 | ||
![]() |
b418302311 | ||
![]() |
f6acb72bbe | ||
![]() |
0c9856c1f6 | ||
![]() |
a748563395 | ||
![]() |
b84e280efb | ||
![]() |
4390dc4a9c | ||
![]() |
6e09fd1e97 | ||
![]() |
100c6b5e89 | ||
![]() |
26d72a69f0 | ||
![]() |
01e28bbcf6 | ||
![]() |
2a7e2a6a36 | ||
![]() |
3c3dab95f5 | ||
![]() |
d1e7052fa1 | ||
![]() |
10e1dfe1d1 | ||
![]() |
cd1bcb57b4 | ||
![]() |
d6d12f99d4 | ||
![]() |
128221363f | ||
![]() |
6b0b1371e5 | ||
![]() |
44f01a212b | ||
![]() |
456f5b7aa6 | ||
![]() |
d78a6f1699 | ||
![]() |
5e784c060a | ||
![]() |
1439b14686 | ||
![]() |
b648cfd67f | ||
![]() |
50c627fbb0 | ||
![]() |
99034d0575 | ||
![]() |
19663ec574 | ||
![]() |
281eea125f | ||
![]() |
5bed0bfbb4 | ||
![]() |
f12470d3cd | ||
![]() |
fb0e4c28ba | ||
![]() |
af29fcca77 | ||
![]() |
f8b269d859 | ||
![]() |
c259263f26 | ||
![]() |
27c843935f | ||
![]() |
f3da6a9725 | ||
![]() |
b64b534394 | ||
![]() |
81426ab6dc | ||
![]() |
df1193ab35 | ||
![]() |
b494c9a4bd | ||
![]() |
f8a2174108 | ||
![]() |
1f57b04974 | ||
![]() |
c7080165a7 | ||
![]() |
cf0cf127fe | ||
![]() |
82c67aa646 | ||
![]() |
9e47ff2505 | ||
![]() |
9bc3b30a60 | ||
![]() |
5bd6dcf471 | ||
![]() |
89dfb6cf86 | ||
![]() |
662aa30f4b | ||
![]() |
efec3e508c | ||
![]() |
c3e952befa | ||
![]() |
83b426f5fd | ||
![]() |
b963c5668b | ||
![]() |
4882c31531 | ||
![]() |
7c17141fb2 | ||
![]() |
3f9d44ff15 | ||
![]() |
a069009696 | ||
![]() |
024b0df7d2 | ||
![]() |
74ef9aed7f | ||
![]() |
c4dd4f2960 | ||
![]() |
d348712439 | ||
![]() |
22cf6ad00b | ||
![]() |
5bf2636753 | ||
![]() |
1f427953a8 | ||
![]() |
a6e1d0df00 | ||
![]() |
c0b4d03692 | ||
![]() |
a16d6bde06 | ||
![]() |
737cc3176e | ||
![]() |
4b04a5c425 | ||
![]() |
df33945458 | ||
![]() |
c1a7d71fd7 | ||
![]() |
a7ee11a78c | ||
![]() |
43e0a87a74 | ||
![]() |
abbe32f845 | ||
![]() |
29cc8baab7 | ||
![]() |
0f33d68f3a | ||
![]() |
a0ef820947 | ||
![]() |
7b24df6581 | ||
![]() |
5078c49b3a | ||
![]() |
4b81a27407 | ||
![]() |
e8aefd8388 | ||
![]() |
3ee787e8b1 | ||
![]() |
b76fb75702 | ||
![]() |
2bcb1766ae | ||
![]() |
e92f00cdf3 | ||
![]() |
1038844567 | ||
![]() |
1579f05cb7 | ||
![]() |
f885de2f2a | ||
![]() |
fc5ad1673c | ||
![]() |
337e8ee19e | ||
![]() |
81028f9a35 | ||
![]() |
fb18bb34cc | ||
![]() |
94a0f2b94b | ||
![]() |
ce94a1fc18 | ||
![]() |
ca880dd8ec | ||
![]() |
fc69754a21 | ||
![]() |
b4920aafe2 | ||
![]() |
43e30707be | ||
![]() |
c48a0bee51 | ||
![]() |
972c7a2238 | ||
![]() |
c2fc464c55 | ||
![]() |
2ea8e69c66 | ||
![]() |
e4a21e94fc | ||
![]() |
e904500312 | ||
![]() |
efff2cbe43 | ||
![]() |
d3f76a5f6d | ||
![]() |
e595bc2150 | ||
![]() |
7bf6a1791b | ||
![]() |
a15d02b50c | ||
![]() |
938f9890ab | ||
![]() |
1e5a41ba4e | ||
![]() |
f8b7238801 | ||
![]() |
e35f551a56 | ||
![]() |
24731f433e | ||
![]() |
6158ba344f | ||
![]() |
78604573f3 | ||
![]() |
bd57c01794 | ||
![]() |
211e7db22c | ||
![]() |
da0d49c787 | ||
![]() |
46305faf30 | ||
![]() |
94e1944d16 | ||
![]() |
13785a5530 | ||
![]() |
10c589ae8d | ||
![]() |
cd38cdf74e | ||
![]() |
f50910f76e | ||
![]() |
398eb424a9 | ||
![]() |
1b2f509758 | ||
![]() |
b795c02dd4 | ||
![]() |
915adf8fd3 | ||
![]() |
a3ca2c2a16 | ||
![]() |
47fd020abe | ||
![]() |
4f2f1cf520 | ||
![]() |
a82fefb301 | ||
![]() |
7b68d97f5f | ||
![]() |
7a7ea23628 | ||
![]() |
83f926758c | ||
![]() |
9a65945fcd | ||
![]() |
d9a6960f07 | ||
![]() |
b5bf1125db | ||
![]() |
2c8bc1180d | ||
![]() |
4166ee2209 | ||
![]() |
95a84d3673 | ||
![]() |
57ed77e332 | ||
![]() |
2b17e16737 | ||
![]() |
f467cacdd8 | ||
![]() |
9662ca6efe | ||
![]() |
587eee2ef0 | ||
![]() |
f5c706db34 | ||
![]() |
b29ef6df69 | ||
![]() |
a435b66006 | ||
![]() |
ddc93d7369 | ||
![]() |
c3e3ec5808 | ||
![]() |
b543ecea58 | ||
![]() |
c850a726cb | ||
![]() |
d246e945a2 | ||
![]() |
1bd53697b9 | ||
![]() |
73ef8c4c0a | ||
![]() |
9aa1335fd2 | ||
![]() |
2f89f3fe3a | ||
![]() |
40be69347c | ||
![]() |
58ea206c36 | ||
![]() |
68c17f1181 | ||
![]() |
b37f4fc8cc | ||
![]() |
630423d24a | ||
![]() |
b92eeed50b | ||
![]() |
3681c5c7bf | ||
![]() |
4496ea91bd | ||
![]() |
3d8626d17e | ||
![]() |
ea7769f8b2 | ||
![]() |
5fa89ff685 | ||
![]() |
a07f84a5bc | ||
![]() |
216184f43f | ||
![]() |
c7864fd785 | ||
![]() |
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 | ||
![]() |
1a1ed5e7fc | ||
![]() |
e23ce9bfc2 | ||
![]() |
546f47edcc | ||
![]() |
25b650c935 | ||
![]() |
6734864a5b | ||
![]() |
0ef2951c7e | ||
![]() |
dd228c9fda | ||
![]() |
7da2e32e3c | ||
![]() |
b99c076bae | ||
![]() |
afc6834082 |
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:**
|
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
### Description
|
||||
|
||||
|
||||
### Tested with
|
||||
_If applicable, please describe with which device you have tested._
|
23
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/doc"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "gitsubmodule"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
13
.github/workflows/black.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Lint (Black code style)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
black-code-style:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
version: "23.3.0"
|
||||
|
66
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 1 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['python']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
32
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Documentation build
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "docs"
|
||||
docs:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Install packages
|
||||
run:
|
||||
sudo apt-get update -y &&
|
||||
sudo apt-get install -y git python3-sphinx graphviz libenchant-2-2 &&
|
||||
sudo apt-get install -y gcc libcups2-dev python3-dev python3-setuptools &&
|
||||
sudo pip install tox pycups
|
||||
- name: Test doc build
|
||||
run: tox -e docs
|
43
.github/workflows/pythonpackage.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: Python package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest tox tox-gh-actions
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with tox
|
||||
run: |
|
||||
tox
|
||||
env:
|
||||
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
|
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# python temporary files
|
||||
*.pyc
|
||||
|
||||
# editor autosaves, data and file browser files
|
||||
$~
|
||||
.idea/
|
||||
.directory
|
||||
.cache/
|
||||
settings.json
|
||||
|
||||
# temporary data
|
||||
temp
|
||||
|
||||
# packaging and testing
|
||||
.tox/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
*.egg
|
||||
build/
|
||||
dist/
|
||||
.coverage
|
||||
src/escpos/version.py
|
||||
.hypothesis
|
||||
.pytest_cache/
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# testing temporary directories
|
||||
test/test-cli-output/
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
*.swn
|
||||
*.swo
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
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
|
19
.mailmap
Normal file
@@ -0,0 +1,19 @@
|
||||
<dev@pkanzler.de> <patrick.kanzler@fablab.fau.de>
|
||||
<manpaz@gmail.com> <manpaz@bashlinux.com>
|
||||
Manuel F Martinez <manpaz@gmail.com> manpaz <manpaz@bashlinux.com>
|
||||
<emailofdavis@gmail.com> <davis.goglin@oregonicecream.com>
|
||||
Davis Goglin <emailofdavis@gmail.com> davisgoglin <emailofdavis@gmail.com>
|
||||
Michael Billington <michael.billington@gmail.com> Michael <michael.billington@gmail.com>
|
||||
Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.com>
|
||||
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
|
||||
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
|
||||
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
|
||||
Juanmi Taboada <juanmi@juanmitaboada.com> Juanmi Taboada <juanmi@juanmitaboada.com>
|
||||
csoft2k <csoft2k@hotmail.com>
|
||||
Sergio Pulgarin <sergio.pulgarin@gmail.com>
|
||||
reck31 <rakesh.gunduka@gmail.com>
|
||||
Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it>
|
||||
白月秋见心 <ourweijiang@gmail.com>
|
||||
Maximilian Wagenbach <maximilian.wagenbach@native-instruments.de>
|
||||
<belono@users.noreply.github.com> <tiotil.lindeman@gmail.com>
|
||||
belono <belono@users.noreply.github.com> Benito López <belono@users.noreply.github.com>
|
14
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"esbonio.sphinx.confDir": "${workspaceFolder}/doc",
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/*/**": true,
|
||||
"**/.eggs/**": true,
|
||||
"**/.hypothesis/**": true,
|
||||
"**/.tox/**": true,
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"python.formatting.provider": "black",
|
||||
}
|
16
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "test with tox",
|
||||
"type": "shell",
|
||||
"command": "tox",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
48
AUTHORS
Normal file
@@ -0,0 +1,48 @@
|
||||
Ahmed Tahri
|
||||
akeonly
|
||||
Alejandro Hernández
|
||||
Alexander Bougakov
|
||||
Alex Debiasio
|
||||
Asuki Kono
|
||||
belono
|
||||
B. Howell
|
||||
Brian
|
||||
Christoph Heuel
|
||||
Cody (Quantified Code Bot)
|
||||
csoft2k
|
||||
Curtis // mashedkeyboard
|
||||
Davis Goglin
|
||||
Dean Rispin
|
||||
dependabot[bot]
|
||||
Dmytro Katyukha
|
||||
Gerard Marull-Paretas
|
||||
Hark
|
||||
Joel Lehtonen
|
||||
Justin Vieira
|
||||
kennedy
|
||||
Kristi
|
||||
ldos
|
||||
Lucy Linder
|
||||
Manuel F Martinez
|
||||
Mathieu Poussin
|
||||
Maximilian Wagenbach
|
||||
Michael Billington
|
||||
Michael Elsdörfer
|
||||
mrwunderbar666
|
||||
Nathan Bookham
|
||||
Omer Akram
|
||||
Patrick Kanzler
|
||||
primax79
|
||||
Qian Linfeng
|
||||
Ramon Poca
|
||||
reck31
|
||||
Renato Lorenzi
|
||||
Romain Porte
|
||||
Sam Cheng
|
||||
Sergio Pulgarin
|
||||
Stephan Sokolow
|
||||
Thijs Triemstra
|
||||
Thomas van den Berg
|
||||
Yaisel Hurtado
|
||||
ysuolmai
|
||||
白月秋见心
|
43
CHANGELOG
@@ -1,43 +0,0 @@
|
||||
CHANGELOG
|
||||
|
||||
* 2012-11-15 - Version 1.0
|
||||
- Issue #2: Added ethernet support
|
||||
- Issue #3: Added compatibility with libusb-1.0.1
|
||||
- Issue #4: Fixed typo in escpos.py
|
||||
|
||||
* 2013-03-14 - Version 1.0.1
|
||||
- Issue #8: Fixed set font
|
||||
- Added QR support
|
||||
|
||||
* 2013-12-30 - Version 1.0.2
|
||||
- Issue #5: Fixed vertical tab
|
||||
- Issue #9: Fixed identation inconsistence
|
||||
|
||||
* 2014-02-23 - Version 1.0.3
|
||||
- Issue #18: Added quad-area characters (Sent by syncman1x@gmail.com)
|
||||
- Added exception for PIL import
|
||||
|
||||
* 2014-05-20 - Version 1.0.4
|
||||
- Issue #20: Added Density support (Sent by thomas.erbacher@ragapack.de)
|
||||
- Added charcode tables
|
||||
- Fixed Horizontal Tab
|
||||
- Fixed code tabulators
|
||||
|
||||
* 2015-04-21 - Version 1.0.5
|
||||
- Merge pull request #45 from Krispy2009/master
|
||||
. Raising the right error when wrong charcode is used
|
||||
. Sent by Kristi <Krispy2009@gmail.com>
|
||||
|
||||
* 2015-07-06 - Version 1.0.6
|
||||
- Merge pull request #53 from ldos/master
|
||||
. Extended params for serial printers
|
||||
. Sent by ldos <cafeteria.ldosalzira@gmail.com>
|
||||
|
||||
* 2015-08-22 - Version 1.0.7
|
||||
- Issue #57: Fixed transparent images
|
||||
|
||||
* 2015-10-27 - Version 1.0.8
|
||||
- Merge pull request #59 from zouppen/master
|
||||
. Support for images vertically longer than 256 pixels
|
||||
. Sent by Joel Lehtonen <joel.lehtonen@koodilehto.fi>
|
||||
- Updated README
|
399
CHANGELOG.rst
Normal file
@@ -0,0 +1,399 @@
|
||||
*********
|
||||
Changelog
|
||||
*********
|
||||
2023-05-11 - Version 3.0a9 - "Pride Comes Before A Fall"
|
||||
--------------------------------------------------------
|
||||
This release is the 10th alpha release of the new version 3.0.
|
||||
After three years hiatus, a new release is in work in order to
|
||||
finally get a version 3.0 out.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- Include support for CUPS based printer interfaces
|
||||
- Move the build toolchain to GitHub
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- belono
|
||||
- brendanhowell
|
||||
- AlexandroJaez
|
||||
- NullYing
|
||||
- kedare
|
||||
- Foaly
|
||||
- patkan
|
||||
- and others
|
||||
|
||||
2020-05-12 - Version 3.0a8 - "Only Slightly Bent"
|
||||
-------------------------------------------------
|
||||
This release is the ninth alpha release of the new version 3.0.
|
||||
Please be aware that the API is subject to change until v3.0 is
|
||||
released.
|
||||
|
||||
This release drops support for Python 2, requiring at least
|
||||
version 3.5 of Python.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- Drop support for Python 2 and mark in setuptools as only supporting 3.5 and upwards
|
||||
- remove landscape.io badge
|
||||
- replace viivakoodi with python-barcode which is maintained
|
||||
- add configuration for Visual Studio Code
|
||||
- use pkg_resources for the retrieval of the capabilities.json
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Romain Porte
|
||||
- Patrick Kanzler
|
||||
|
||||
2020-05-09 - Version 3.0a7 - "No Fixed Abode"
|
||||
---------------------------------------------
|
||||
This release is the eight alpha release of the new version 3.0.
|
||||
Please be aware that the API is subject to change until v3.0
|
||||
is released.
|
||||
|
||||
This release also marks the point at which the project transitioned
|
||||
to having only a master-branch (and not an additional development branch).
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- add Exception for NotImplementedError in detach_kernel_driver
|
||||
- update installation information
|
||||
- update and improve documentation
|
||||
- add error handling to image centering flag
|
||||
- update and fix tox and CI environment, preparing drop of support for Python 2
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Alexander Bougakov
|
||||
- Brian
|
||||
- Yaisel Hurtado
|
||||
- Maximilan Wagenbach
|
||||
- Patrick Kanzler
|
||||
|
||||
2019-06-19 - Version 3.0a6 - "Mistake not..."
|
||||
---------------------------------------------
|
||||
This release is the seventh alpha release of the new version 3.0.
|
||||
Please be aware that the API is subject to change until v3.0 is
|
||||
released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- fix inclusion of the capabilities-file
|
||||
- execute CI jobs also on Windows and macOS-targets
|
||||
- improve documentation
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Patrick Kanzler
|
||||
|
||||
2019-06-16 - Version 3.0a5 - "Lightly Seared On The Reality Grill"
|
||||
------------------------------------------------------------------
|
||||
This release is the sixth alpha release of the new version 3.0. Please
|
||||
be aware that the API is subject to change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- allow arbitrary USB arguments in USB-class
|
||||
- add Win32Raw-Printer on Windows-platforms
|
||||
- add and improve Windows support of USB-class
|
||||
- use pyyaml safe_load()
|
||||
- improve doc
|
||||
- implement _read method of Network printer class
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Patrick Kanzler
|
||||
- Gerard Marull-Paretas
|
||||
- Ramon Poca
|
||||
- akeonly
|
||||
- Omer Akram
|
||||
- Justin Vieira
|
||||
|
||||
2018-05-15 - Version 3.0a4 - "Kakistocrat"
|
||||
------------------------------------------
|
||||
This release is the fifth alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- raise exception when TypeError occurs in cashdraw (#268)
|
||||
- Feature/clear content in dummy printer (#271)
|
||||
- fix is_online() (#282)
|
||||
- improve documentation
|
||||
- Modified submodule to always pull from master branch (#283)
|
||||
- parameter for implementation of nonnative qrcode (#289)
|
||||
- improve platform independence (#296)
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Christoph Heuel
|
||||
- Patrick Kanzler
|
||||
- kennedy
|
||||
- primax79
|
||||
- reck31
|
||||
- Thijs Triemstra
|
||||
|
||||
2017-10-08 - Version 3.0a3 - "Just Testing"
|
||||
-------------------------------------------
|
||||
This release is the fourth alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- minor changes in documentation, tests and examples
|
||||
- pickle capabilities for faster startup
|
||||
- first implementation of centering images and QR
|
||||
- check barcodes based on regex
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Patrick Kanzler
|
||||
- Lucy Linder
|
||||
- Romain Porte
|
||||
- Sergio Pulgarin
|
||||
|
||||
2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To"
|
||||
-----------------------------------------------------------------------
|
||||
This release is the third alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- refactor of the set-method
|
||||
- preliminary support of POS "line display" printing
|
||||
- improvement of tests
|
||||
- added ImageWidthError
|
||||
- list authors in repository
|
||||
- add support for software-based barcode-rendering
|
||||
- fix SerialException when trying to close device on __del__
|
||||
- added the DLE EOT querying command for USB and Serial
|
||||
- ensure QR codes have a large enough border
|
||||
- make feed for cut optional
|
||||
- fix the behavior of horizontal tabs
|
||||
- added test script for hard an soft barcodes
|
||||
- implemented paper sensor querying command
|
||||
- added weather forecast example script
|
||||
- added a method for simpler newlines
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- csoft2k
|
||||
- Patrick Kanzler
|
||||
- mrwunderbar666
|
||||
- Romain Porte
|
||||
- Ahmed Tahri
|
||||
|
||||
2017-03-29 - Version 3.0a1 - "Headcrash"
|
||||
----------------------------------------
|
||||
This release is the second alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- automatically upload releases to GitHub
|
||||
- add environment variable ESCPOS_CAPABILITIES_FILE
|
||||
- automatically handle cases where full cut or partial cut is not available
|
||||
- add print_and_feed
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Sam Cheng
|
||||
- Patrick Kanzler
|
||||
- Dmytro Katyukha
|
||||
|
||||
2017-01-31 - Version 3.0a - "Grey Area"
|
||||
---------------------------------------
|
||||
This release is the first alpha release of the new version 3.0. Please
|
||||
be aware that the API will still change until v3.0 is released.
|
||||
|
||||
changes
|
||||
^^^^^^^
|
||||
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
|
||||
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
|
||||
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
|
||||
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
|
||||
- as an alternative you can force the codepage with the old API
|
||||
- updated and improved documentation
|
||||
- changed constructor of main class due to introduction of capablities
|
||||
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
|
||||
on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
|
||||
- add support for custom cash drawer sequence
|
||||
- enforce flake8 on the src-files, test py36 and py37 on travis
|
||||
|
||||
contributors
|
||||
^^^^^^^^^^^^
|
||||
- Michael Billington
|
||||
- Michael Elsdörfer
|
||||
- Patrick Kanzler (with code by Frédéric Van der Essen)
|
||||
- Asuki Kono
|
||||
- Benito López
|
||||
- Curtis // mashedkeyboard
|
||||
- Thijs Triemstra
|
||||
- ysuolmai
|
||||
|
||||
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
|
||||
------------------------------------------------------
|
||||
|
||||
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
|
69
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,69 @@
|
||||
************
|
||||
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 <https://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.
|
||||
|
||||
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 the main development of the project. A release to PyPi is marked with a tag
|
||||
corresponding to the version. Issues are closed when they have been resolved by merging into the master-branch.
|
||||
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/master`.
|
||||
|
||||
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 <https://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
|
||||
and the linked one, please stick to this text.
|
||||
Aside from that feel free to create an issue or write an email if anything is unclear.
|
||||
|
||||
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>.
|
23
INSTALL
@@ -1,23 +1,10 @@
|
||||
python-escpos
|
||||
=============
|
||||
|
||||
Ensure the library is installed on ${lib_arch}/${python_ver}/site-packages/escpos
|
||||
This library is available over pypi. So for most of the use-cases it should be sufficient to run
|
||||
|
||||
On CLi you must run:
|
||||
# python setup.py build
|
||||
# sudo python setup.py install
|
||||
```
|
||||
pip install python-escpos --user # add --pre if you want to install pre-releases
|
||||
```
|
||||
|
||||
On Linux, ensure you belongs to the proper group so you can have access to the printer.
|
||||
This can be done, by adding yourself to 'dialout' group, this might require to re-login
|
||||
so the changes make effect.
|
||||
|
||||
Then, add the following rule to /etc/udev/rules.d/99-escpos.rules
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="04b8", ATTRS{idProduct}=="0202", MODE="0664", GROUP="dialout"
|
||||
|
||||
and restar udev rules.
|
||||
# sudo service udev restart
|
||||
|
||||
Enjoy !!!
|
||||
And please, don't forget to ALWAYS add Epson.cut() at the end of your printing :)
|
||||
|
||||
Manuel F Martinez <manpaz@bashlinux.com>
|
||||
For more information please read the documentation at https://python-escpos.readthedocs.io/en/latest/user/installation.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 LICENSE
|
||||
include INSTALL
|
||||
include tox.ini
|
||||
include capabilities-data/dist/capabilities.json
|
||||
include src/escpos/capabilities.json
|
||||
recursive-include doc *.bat
|
||||
recursive-include doc *.ico
|
||||
recursive-include doc *.py
|
||||
recursive-include doc *.rst
|
||||
recursive-include doc *.txt
|
||||
recursive-include doc Makefile
|
45
README
@@ -1,45 +0,0 @@
|
||||
ESCPOS
|
||||
======
|
||||
|
||||
Python library to manipulate ESC/POS Printers.
|
||||
|
||||
------------------------------------------------------------------
|
||||
1. Description
|
||||
|
||||
Python ESC/POS is a library which lets the user have access to all
|
||||
those printers handled by ESC/POS commands, as defined by Epson,
|
||||
from a Python application.
|
||||
|
||||
The standard usage is send raw text to the printer, but in also
|
||||
helps the user to enhance the experience with those printers by
|
||||
facilitating the bar code printing in many different standards,
|
||||
as well as manipulating images so they can be printed as brand
|
||||
logo or any other usage images migh have.
|
||||
|
||||
Text can be aligned/justified and fonts can be changed by size,
|
||||
type and weight.
|
||||
|
||||
Also, this module handles some hardware functionalities like, cut
|
||||
paper, carrier return, printer reset and others concerned to the
|
||||
carriage alignment.
|
||||
|
||||
------------------------------------------------------------------
|
||||
2. Documentation
|
||||
|
||||
Please visit project documentation at:
|
||||
https://github.com/manpaz/python-escpos/wiki
|
||||
|
||||
------------------------------------------------------------------
|
||||
3. Donations
|
||||
|
||||
There are some different prints I'd like to acquire, but unfortunately
|
||||
not all, even used, are cheaper and easy to get.
|
||||
|
||||
If you want to help funding money to get more printers or just want to
|
||||
donate because you like the project, please be in touch and I'll be
|
||||
sending my PayPal info so you can donate.
|
||||
|
||||
Thank you!
|
||||
|
||||
Manuel F Martinez <manpaz@bashlinux.com>
|
||||
|
108
README.rst
Normal file
@@ -0,0 +1,108 @@
|
||||
#############################################################
|
||||
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://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=latest
|
||||
:target: https://python-escpos.readthedocs.io/en/latest/?badge=latest
|
||||
: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
|
||||
* `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ 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()
|
||||
|
||||
|
||||
Another example based on the Network printer class:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from escpos.printer import Network
|
||||
|
||||
kitchen = Network("192.168.1.100") #Printer IP Address
|
||||
kitchen.text("Hello World\n")
|
||||
kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '')
|
||||
kitchen.cut()
|
||||
|
||||
Another example based on the Serial printer class:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from escpos.printer import Serial
|
||||
|
||||
""" 9600 Baud, 8N1, Flow Control Enabled """
|
||||
p = Serial(devfile='/dev/tty.usbserial',
|
||||
baudrate=9600,
|
||||
bytesize=8,
|
||||
parity='N',
|
||||
stopbits=1,
|
||||
timeout=1.00,
|
||||
dsrdtr=True)
|
||||
|
||||
p.text("Hello World\n")
|
||||
p.qr("You can readme from your smartphone")
|
||||
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 <https://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
12
codecov.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
codecov:
|
||||
bot: patkan
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
changes: off
|
||||
range: "60...100"
|
||||
|
||||
comment: off
|
||||
|
1
doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_build/
|
183
doc/Makefile
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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 https://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 spelling
|
||||
|
||||
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)"
|
||||
@echo " spelling to run the spellchecker"
|
||||
|
||||
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."
|
||||
|
||||
spelling:
|
||||
$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
|
||||
@echo
|
||||
@echo "Spellchecker finished."
|
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
|
310
doc/conf.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# -*- 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
|
||||
|
||||
from importlib.metadata import version as imp_version
|
||||
|
||||
on_rtd = os.getenv("READTHEDOCS") == "True"
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath("../src"))
|
||||
root = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
# -- 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",
|
||||
"sphinxcontrib.spelling",
|
||||
]
|
||||
|
||||
# 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 = "python-escpos"
|
||||
copyright = "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.
|
||||
#
|
||||
release = imp_version("python-escpos")
|
||||
# The short X.Y version.
|
||||
version = ".".join(release.split(".")[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# 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",
|
||||
"python-escpos Documentation",
|
||||
"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",
|
||||
"python-escpos Documentation",
|
||||
["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",
|
||||
"python-escpos Documentation",
|
||||
"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
|
||||
|
||||
# spellchecker
|
||||
spelling_ignore_pypi_package_names = True
|
||||
spelling_ignore_wiki_words = True
|
||||
spelling_ignore_python_builtins = True
|
||||
spelling_ignore_importable_modules = True
|
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
22
doc/generate_authors.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
|
||||
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
|
||||
GENLIST_W_MAIL=$(git shortlog -s -e -n | cut -f2 | sort -f)
|
||||
AUTHORSFILE="$(dirname $0)/../AUTHORS"
|
||||
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
|
||||
|
||||
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 --suppress-common-lines -b --from-file $AUTHORSFILE $TEMPAUTHORSFILE
|
||||
echo "Authors with mail addresses:\n"
|
||||
echo "$GENLIST_W_MAIL"
|
||||
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.https://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 |
11
doc/requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
pyusb
|
||||
Pillow>=2.0
|
||||
qrcode>=4.0
|
||||
pyserial
|
||||
sphinx-rtd-theme
|
||||
setuptools
|
||||
setuptools-scm
|
||||
docutils>=0.12
|
||||
sphinxcontrib-spelling>=7.2.0
|
||||
python-barcode>=0.11.0,<1
|
||||
importlib-metadata
|
7
doc/spelling_wordlist.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Raspbian
|
||||
ESC
|
||||
POS
|
||||
Escpos
|
||||
Escpos
|
||||
baudrate
|
||||
lsusb
|
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:
|
||||
|
100
doc/user/printers.rst
Normal file
@@ -0,0 +1,100 @@
|
||||
********
|
||||
Printers
|
||||
********
|
||||
:Last Reviewed: 2022-11-25
|
||||
|
||||
As of now there are 7 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:
|
||||
|
||||
CUPS
|
||||
----
|
||||
This driver uses `pycups` in order to communicate with a CUPS server.
|
||||
Supports both local and remote CUPS printers and servers.
|
||||
The printer must be properly configured in CUPS administration.
|
||||
The connector generates a print job that is added to the CUPS queue.
|
||||
|
||||
.. todo:: fix import in documentation
|
||||
|
||||
LP
|
||||
----
|
||||
This driver uses the UNIX command `lp` in order to communicate with a CUPS server.
|
||||
Supports local and remote CUPS printers.
|
||||
The printer must be properly configured in CUPS administration.
|
||||
The connector spawns a new sub-process where the command lp is executed.
|
||||
|
||||
No dependencies required, but somehow the print queue will affect some print job such as barcode.
|
||||
|
||||
.. autoclass:: escpos.printer.LP
|
||||
:members:
|
||||
:special-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>`.
|
14
doc/user/todo.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
****
|
||||
TODO
|
||||
****
|
||||
|
||||
Open points and issues of the project are tracked in the GitHub issues.
|
||||
Some annotations still remain in the code and should be moved over time
|
||||
into the issue tracker.
|
||||
|
||||
Todos in the codebase
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. todolist::
|
||||
|
||||
|
311
doc/user/usage.rst
Normal file
@@ -0,0 +1,311 @@
|
||||
*****
|
||||
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**
|
||||
|
||||
::
|
||||
|
||||
p = printer.Usb(0x04b8,0x0202)
|
||||
|
||||
By default the "Interface" number is "0" and the "Output Endpoint"
|
||||
address is "0x01". If you have other values then you can define them on
|
||||
your instance. So, assuming that we have another printer, CT-S2000,
|
||||
manufactured by Citizen (with "Vendor ID" of 2730 and "Product ID" of 0fff)
|
||||
where in\_ep is on 0x81 and out\_ep=0x02, then the printer definition should
|
||||
look like:
|
||||
|
||||
**Generic USB Printer initialization**
|
||||
|
||||
::
|
||||
|
||||
p = printer.Usb(0x2730, 0x0fff, 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**
|
||||
|
||||
::
|
||||
|
||||
p = 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**
|
||||
|
||||
::
|
||||
|
||||
p = printer.Serial("/dev/tty0")
|
||||
|
||||
# on a Windows OS serial devices are typically accessible as COM
|
||||
p = printer.Serial("COM1")
|
||||
|
||||
Other printers
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Some printers under `/dev` can't be used or initialized with any of the
|
||||
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.
|
||||
|
||||
::
|
||||
|
||||
p = printer.File("/dev/usb/lp1")
|
||||
|
||||
The default is "/dev/usb/lp0", so if the printer is located on that
|
||||
node, then you don't necessary need to pass the node name.
|
||||
|
||||
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) """
|
||||
p = printer.Usb(0x04b8,0x0202)
|
||||
# Print text
|
||||
p.text("Hello World\n")
|
||||
# Print image
|
||||
p.image("logo.gif")
|
||||
# Print QR Code
|
||||
p.qr("You can readme from your smartphone")
|
||||
# Print barcode
|
||||
p.barcode('1324354657687','EAN13',64,2,'','')
|
||||
# Cut paper
|
||||
p.cut()
|
||||
|
||||
Standard python constraints on libraries apply. This means especially
|
||||
that you should not name the script in which you implement these lines
|
||||
should not be named ``escpos`` as this would collide with the name of
|
||||
the library.
|
||||
|
||||
Configuration File
|
||||
------------------
|
||||
|
||||
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 parameter is ``type``. The value of this has to be one of the
|
||||
printers defined in :doc:`/user/printers`.
|
||||
|
||||
The rest of the given parameters will be passed on to the initialization of the printer class.
|
||||
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.
|
||||
|
||||
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.)
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
This section gathers various hints on troubleshooting.
|
||||
|
||||
Print with STAR TSP100 family
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Printer of the STAR TSP100 family do not have a native ESC/POS mode, which
|
||||
is why you will not be able to directly print with this library to the printer.
|
||||
|
||||
More information on this topic can be found in the online documentation of
|
||||
`Star Micronics <https://www.starmicronics.com/help-center/knowledge-base/configure-tsp100-series-printers-esc-pos-mode/>`_
|
||||
and the `discussion in the python-escpos project <https://github.com/python-escpos/python-escpos/issues/410>`_.
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
__all__ = ["constants","escpos","exceptions","printer"]
|
@@ -1,86 +0,0 @@
|
||||
""" ESC/POS Commands (Constants) """
|
||||
|
||||
# Feed control sequences
|
||||
CTL_LF = '\x0a' # Print and line feed
|
||||
CTL_FF = '\x0c' # Form feed
|
||||
CTL_CR = '\x0d' # Carriage return
|
||||
CTL_HT = '\x09' # Horizontal tab
|
||||
CTL_SET_HT = '\x1b\x44' # Set horizontal tab positions
|
||||
CTL_VT = '\x1b\x64\x04' # Vertical tab
|
||||
# Printer hardware
|
||||
HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
|
||||
HW_SELECT = '\x1b\x3d\x01' # Printer select
|
||||
HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
|
||||
# Cash Drawer
|
||||
CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
|
||||
CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
|
||||
# Paper
|
||||
PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
|
||||
PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
|
||||
# Text format
|
||||
TXT_NORMAL = '\x1b\x21\x00' # Normal text
|
||||
TXT_2HEIGHT = '\x1b\x21\x10' # Double height text
|
||||
TXT_2WIDTH = '\x1b\x21\x20' # Double width text
|
||||
TXT_4SQUARE = '\x1b\x21\x30' # Quad area text
|
||||
TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF
|
||||
TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON
|
||||
TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON
|
||||
TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF
|
||||
TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON
|
||||
TXT_FONT_A = '\x1b\x4d\x00' # Font type A
|
||||
TXT_FONT_B = '\x1b\x4d\x01' # Font type B
|
||||
TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification
|
||||
TXT_ALIGN_CT = '\x1b\x61\x01' # Centering
|
||||
TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification
|
||||
# Char code table
|
||||
CHARCODE_PC437 = '\x1b\x74\x00' # USA: Standard Europe
|
||||
CHARCODE_JIS = '\x1b\x74\x01' # Japanese Katakana
|
||||
CHARCODE_PC850 = '\x1b\x74\x02' # Multilingual
|
||||
CHARCODE_PC860 = '\x1b\x74\x03' # Portuguese
|
||||
CHARCODE_PC863 = '\x1b\x74\x04' # Canadian-French
|
||||
CHARCODE_PC865 = '\x1b\x74\x05' # Nordic
|
||||
CHARCODE_WEU = '\x1b\x74\x06' # Simplified Kanji, Hirakana
|
||||
CHARCODE_GREEK = '\x1b\x74\x07' # Simplified Kanji
|
||||
CHARCODE_HEBREW = '\x1b\x74\x08' # Simplified Kanji
|
||||
CHARCODE_PC1252 = '\x1b\x74\x11' # Western European Windows Code Set
|
||||
CHARCODE_PC866 = '\x1b\x74\x12' # Cirillic #2
|
||||
CHARCODE_PC852 = '\x1b\x74\x13' # Latin 2
|
||||
CHARCODE_PC858 = '\x1b\x74\x14' # Euro
|
||||
CHARCODE_THAI42 = '\x1b\x74\x15' # Thai character code 42
|
||||
CHARCODE_THAI11 = '\x1b\x74\x16' # Thai character code 11
|
||||
CHARCODE_THAI13 = '\x1b\x74\x17' # Thai character code 13
|
||||
CHARCODE_THAI14 = '\x1b\x74\x18' # Thai character code 14
|
||||
CHARCODE_THAI16 = '\x1b\x74\x19' # Thai character code 16
|
||||
CHARCODE_THAI17 = '\x1b\x74\x1a' # Thai character code 17
|
||||
CHARCODE_THAI18 = '\x1b\x74\x1b' # Thai character code 18
|
||||
# Barcode format
|
||||
BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF
|
||||
BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above
|
||||
BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below
|
||||
BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below
|
||||
BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars
|
||||
BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars
|
||||
BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255]
|
||||
BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6]
|
||||
BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A
|
||||
BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E
|
||||
BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13
|
||||
BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8
|
||||
BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39
|
||||
BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF
|
||||
BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7
|
||||
# Image format
|
||||
S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size
|
||||
S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width
|
||||
S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height
|
||||
S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple
|
||||
# Printing Density
|
||||
PD_N50 = '\x1d\x7c\x00' # Printing Density -50%
|
||||
PD_N37 = '\x1d\x7c\x01' # Printing Density -37.5%
|
||||
PD_N25 = '\x1d\x7c\x02' # Printing Density -25%
|
||||
PD_N12 = '\x1d\x7c\x03' # Printing Density -12.5%
|
||||
PD_0 = '\x1d\x7c\x04' # Printing Density 0%
|
||||
PD_P50 = '\x1d\x7c\x08' # Printing Density +50%
|
||||
PD_P37 = '\x1d\x7c\x07' # Printing Density +37.5%
|
||||
PD_P25 = '\x1d\x7c\x06' # Printing Density +25%
|
||||
PD_P12 = '\x1d\x7c\x05' # Printing Density +12.5%
|
360
escpos/escpos.py
@@ -1,360 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
|
||||
@organization: Bashlinux
|
||||
@copyright: Copyright (c) 2012 Bashlinux
|
||||
@license: GNU GPL v3
|
||||
"""
|
||||
|
||||
try:
|
||||
import Image
|
||||
except ImportError:
|
||||
from PIL import Image
|
||||
|
||||
import qrcode
|
||||
import time
|
||||
|
||||
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]&0xff, size[1]>>8)
|
||||
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] > 0xffff:
|
||||
raise ImageSizeError()
|
||||
|
||||
im_border = self._check_image_size(im.size[0])
|
||||
for i in range(im_border[0]):
|
||||
im_left += "0"
|
||||
for i in range(im_border[1]):
|
||||
im_right += "0"
|
||||
|
||||
for y in range(im.size[1]):
|
||||
img_size[1] += 1
|
||||
pix_line += im_left
|
||||
img_size[0] += im_border[0]
|
||||
for x in range(im.size[0]):
|
||||
img_size[0] += 1
|
||||
RGB = im.getpixel((x, y))
|
||||
im_color = (RGB[0] + RGB[1] + RGB[2])
|
||||
im_pattern = "1X0"
|
||||
pattern_len = len(im_pattern)
|
||||
switch = (switch - 1 ) * (-1)
|
||||
for x in range(pattern_len):
|
||||
if im_color <= (255 * 3 / pattern_len * (x+1)):
|
||||
if im_pattern[x] == "X":
|
||||
pix_line += "%d" % switch
|
||||
else:
|
||||
pix_line += im_pattern[x]
|
||||
break
|
||||
elif 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)
|
||||
|
||||
# Remove the alpha channel on transparent images
|
||||
if im_open.mode == 'RGBA':
|
||||
im_open.load()
|
||||
im = Image.new("RGB", im_open.size, (255, 255, 255))
|
||||
im.paste(im_open, mask=im_open.split()[3])
|
||||
else:
|
||||
im = im_open.convert("RGB")
|
||||
|
||||
# Convert the RGB image in printable image
|
||||
self._convert_image(im)
|
||||
|
||||
|
||||
def 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 CharCodeError()
|
||||
|
||||
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, density=9):
|
||||
""" Set text properties """
|
||||
# Width
|
||||
if height == 2 and width == 2:
|
||||
self._raw(TXT_NORMAL)
|
||||
self._raw(TXT_4SQUARE)
|
||||
elif height == 2 and width != 2:
|
||||
self._raw(TXT_NORMAL)
|
||||
self._raw(TXT_2HEIGHT)
|
||||
elif width == 2 and height != 2:
|
||||
self._raw(TXT_NORMAL)
|
||||
self._raw(TXT_2WIDTH)
|
||||
else: # DEFAULT SIZE: NORMAL
|
||||
self._raw(TXT_NORMAL)
|
||||
# Type
|
||||
if 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)
|
||||
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)
|
||||
# Density
|
||||
if density == 0:
|
||||
self._raw(PD_N50)
|
||||
elif density == 1:
|
||||
self._raw(PD_N37)
|
||||
elif density == 2:
|
||||
self._raw(PD_N25)
|
||||
elif density == 3:
|
||||
self._raw(PD_N12)
|
||||
elif density == 4:
|
||||
self._raw(PD_0)
|
||||
elif density == 5:
|
||||
self._raw(PD_P12)
|
||||
elif density == 6:
|
||||
self._raw(PD_P25)
|
||||
elif density == 7:
|
||||
self._raw(PD_P37)
|
||||
elif density == 8:
|
||||
self._raw(PD_P50)
|
||||
else:# DEFAULT: DOES NOTHING
|
||||
pass
|
||||
|
||||
|
||||
def cut(self, mode=''):
|
||||
""" Cut paper """
|
||||
# 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,191 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
@author: Manuel F Martinez <manpaz@bashlinux.com>
|
||||
@organization: Bashlinux
|
||||
@copyright: Copyright (c) 2012 Bashlinux
|
||||
@license: GNU GPL v3
|
||||
"""
|
||||
|
||||
import usb.core
|
||||
import usb.util
|
||||
import serial
|
||||
import socket
|
||||
|
||||
from escpos import *
|
||||
from constants import *
|
||||
from exceptions import *
|
||||
|
||||
class Usb(Escpos):
|
||||
""" Define USB printer """
|
||||
|
||||
def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01):
|
||||
"""
|
||||
@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"
|
||||
|
||||
check_driver = None
|
||||
|
||||
try:
|
||||
check_driver = self.device.is_kernel_driver_active(0)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
if check_driver is None or check_driver:
|
||||
try:
|
||||
self.device.detach_kernel_driver(0)
|
||||
except usb.core.USBError as e:
|
||||
if check_driver is not None:
|
||||
print "Could not detatch kernel driver: %s" % str(e)
|
||||
|
||||
try:
|
||||
self.device.set_configuration()
|
||||
self.device.reset()
|
||||
except usb.core.USBError as e:
|
||||
print "Could not set configuration: %s" % str(e)
|
||||
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format """
|
||||
self.device.write(self.out_ep, msg, self.interface)
|
||||
|
||||
|
||||
def __del__(self):
|
||||
""" Release USB interface """
|
||||
if self.device:
|
||||
usb.util.dispose_resources(self.device)
|
||||
self.device = None
|
||||
|
||||
|
||||
|
||||
class Serial(Escpos):
|
||||
""" Define Serial printer """
|
||||
|
||||
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
|
||||
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
|
||||
xonxoff=False , dsrdtr=True):
|
||||
"""
|
||||
@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)
|
||||
"""
|
||||
self.devfile = devfile
|
||||
self.baudrate = baudrate
|
||||
self.bytesize = bytesize
|
||||
self.timeout = timeout
|
||||
|
||||
self.parity = parity
|
||||
self.stopbits = stopbits
|
||||
self.xonxoff = xonxoff
|
||||
self.dsrdtr = dsrdtr
|
||||
|
||||
self.open()
|
||||
|
||||
|
||||
def open(self):
|
||||
""" Setup serial port and set is as escpos device """
|
||||
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
|
||||
bytesize=self.bytesize, parity=self.parity,
|
||||
stopbits=self.stopbits, timeout=self.timeout,
|
||||
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
|
||||
|
||||
if self.device is not None:
|
||||
print "Serial printer enabled"
|
||||
else:
|
||||
print "Unable to open serial printer on: %s" % self.devfile
|
||||
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format """
|
||||
self.device.write(msg)
|
||||
|
||||
|
||||
def __del__(self):
|
||||
""" Close Serial interface """
|
||||
if self.device is not None:
|
||||
self.device.close()
|
||||
|
||||
|
||||
|
||||
class Network(Escpos):
|
||||
""" Define Network printer """
|
||||
|
||||
def __init__(self,host,port=9100):
|
||||
"""
|
||||
@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()
|
11
examples/barcodes.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
|
||||
# Print software and then hardware barcode with the same content
|
||||
p.soft_barcode("code39", "123456")
|
||||
p.text("\n")
|
||||
p.text("\n")
|
||||
p.barcode("123456", "CODE39")
|
70
examples/codepage_tables.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Prints code page tables.
|
||||
"""
|
||||
|
||||
|
||||
import six
|
||||
import sys
|
||||
|
||||
from escpos import printer
|
||||
from escpos.constants import (
|
||||
CODEPAGE_CHANGE,
|
||||
ESC,
|
||||
CTL_LF,
|
||||
CTL_FF,
|
||||
CTL_CR,
|
||||
CTL_HT,
|
||||
CTL_VT,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
dummy = printer.Dummy()
|
||||
|
||||
dummy.hw("init")
|
||||
|
||||
for codepage in sys.argv[1:] or ["USA"]:
|
||||
dummy.set(height=2, width=2)
|
||||
dummy._raw(codepage + "\n\n\n")
|
||||
print_codepage(dummy, codepage)
|
||||
dummy._raw("\n\n")
|
||||
|
||||
dummy.cut()
|
||||
|
||||
print(dummy.output)
|
||||
|
||||
|
||||
def print_codepage(printer, codepage):
|
||||
if codepage.isdigit():
|
||||
codepage = int(codepage)
|
||||
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
|
||||
printer._raw("after")
|
||||
else:
|
||||
printer.charcode(codepage)
|
||||
|
||||
sep = ""
|
||||
|
||||
# Table header
|
||||
printer.set(font="b")
|
||||
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
|
||||
printer.set()
|
||||
|
||||
# The table
|
||||
for x in range(0, 16):
|
||||
# First column
|
||||
printer.set(font="b")
|
||||
printer._raw("{} ".format(hex(x)[2:]))
|
||||
printer.set()
|
||||
|
||||
for y in range(0, 16):
|
||||
byte = six.int2byte(x * 16 + y)
|
||||
|
||||
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
|
||||
byte = " "
|
||||
|
||||
printer._raw(byte)
|
||||
printer._raw(sep)
|
||||
printer._raw("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
BIN
examples/graphics/climacons/clear-day.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/clear-night.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
examples/graphics/climacons/cloudy.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
examples/graphics/climacons/fog.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
examples/graphics/climacons/partly-cloudy-day.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
examples/graphics/climacons/partly-cloudy-night.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/rain.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
10
examples/graphics/climacons/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Climacons by Adam Whitcroft
|
||||
|
||||
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](https://www.twitter.com/#!/adamwhitcroft).
|
||||
|
||||
Visit the [Climacons](https://adamwhitcroft.com/climacons/) website for more information.
|
||||
|
||||
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
|
||||
|
||||
## License
|
||||
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.
|
BIN
examples/graphics/climacons/sleet.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
examples/graphics/climacons/snow.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
examples/graphics/climacons/wind.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
19
examples/qr_code.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import sys
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
def usage():
|
||||
print("usage: qr_code.py <content>")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
usage()
|
||||
sys.exit(1)
|
||||
|
||||
content = sys.argv[1]
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
p.qr(content, center=True)
|
9
examples/software_barcode.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from escpos.printer import Usb
|
||||
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
|
||||
# Some software barcodes
|
||||
p.soft_barcode("code128", "Hello")
|
||||
p.soft_barcode("code39", "1234")
|
128
examples/weather.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
|
||||
# Adapted script from Adafruit
|
||||
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
|
||||
# Retrieves data from DarkSky.net's API, prints current conditions and
|
||||
# forecasts for next two days.
|
||||
# Weather example using nice bitmaps.
|
||||
# Written by Adafruit Industries. MIT license.
|
||||
# Adapted and enhanced for escpos library by MrWunderbar666
|
||||
|
||||
# Icons taken from https://adamwhitcroft.com/climacons/
|
||||
# Check out his github: https://github.com/AdamWhitcroft/climacons
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
import calendar
|
||||
import urllib
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
|
||||
from escpos.printer import Usb
|
||||
|
||||
""" Setting up the main pathing """
|
||||
this_dir, this_filename = os.path.split(__file__)
|
||||
GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/")
|
||||
|
||||
# Adapt to your needs
|
||||
printer = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
|
||||
# You can get your API Key on www.darksky.net and register a dev account.
|
||||
# Technically you can use any other weather service, of course :)
|
||||
API_KEY = "YOUR API KEY"
|
||||
|
||||
LAT = "22.345490" # Your Location
|
||||
LONG = "114.189945" # Your Location
|
||||
|
||||
|
||||
def forecast_icon(idx):
|
||||
icon = data["daily"]["data"][idx]["icon"]
|
||||
image = GRAPHICS_PATH + icon + ".png"
|
||||
return image
|
||||
|
||||
|
||||
# Dumps one forecast line to the printer
|
||||
def forecast(idx):
|
||||
date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"]))
|
||||
day = calendar.day_name[date.weekday()]
|
||||
lo = data["daily"]["data"][idx]["temperatureMin"]
|
||||
hi = data["daily"]["data"][idx]["temperatureMax"]
|
||||
cond = data["daily"]["data"][idx]["summary"]
|
||||
print(date)
|
||||
print(day)
|
||||
print(lo)
|
||||
print(hi)
|
||||
print(cond)
|
||||
time.sleep(1)
|
||||
printer.set(font="a", height=2, align="left", bold=False, double_height=False)
|
||||
printer.text(day + " \n ")
|
||||
time.sleep(5) # Sleep to prevent printer buffer overflow
|
||||
printer.text("\n")
|
||||
printer.image(forecast_icon(idx))
|
||||
printer.text("low " + str(lo))
|
||||
printer.text(deg)
|
||||
printer.text("\n")
|
||||
printer.text(" high " + str(hi))
|
||||
printer.text(deg)
|
||||
printer.text("\n")
|
||||
# take care of pesky unicode dash
|
||||
printer.text(cond.replace("\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")
|
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[tool.black]
|
||||
extend-exclude = 'capabilities-data'
|
||||
|
7
readthedocs.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
formats:
|
||||
- pdf
|
||||
- epub
|
||||
requirements_file: doc/requirements.txt
|
||||
python:
|
||||
version: 3
|
||||
setup_py_install: true
|
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
-e .
|
72
setup.cfg
Normal file
@@ -0,0 +1,72 @@
|
||||
[metadata]
|
||||
name = python-escpos
|
||||
url = https://github.com/python-escpos/python-escpos
|
||||
description = Python library to manipulate ESC/POS Printers
|
||||
long_description = file: README.rst
|
||||
license = MIT
|
||||
license_file = LICENSE
|
||||
author = Manuel F Martinez and others
|
||||
author_email = dev@pkanzler.de
|
||||
maintainer = Patrick Kanzler
|
||||
maintainer_email = dev@pkanzler.de
|
||||
keywords = ESC/POS, thermoprinter, voucher printer, printing, receipt
|
||||
classifiers =
|
||||
Development Status :: 4 - Beta
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Topic :: Software Development :: Libraries :: Python Modules
|
||||
Topic :: Office/Business :: Financial :: Point-Of-Sale
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/python-escpos/python-escpos/issues
|
||||
Documentation = https://python-escpos.readthedocs.io/en/latest/
|
||||
Release Notes = https://github.com/python-escpos/python-escpos/releases
|
||||
|
||||
[options]
|
||||
python_requires = >=3.6
|
||||
zip_safe = false
|
||||
include_package_data = true
|
||||
install_requires =
|
||||
pyusb>=1.0.0
|
||||
Pillow>=2.0
|
||||
qrcode>=4.0
|
||||
pyserial
|
||||
python-barcode>=0.9.1,<1
|
||||
setuptools
|
||||
six
|
||||
appdirs
|
||||
PyYAML
|
||||
argparse
|
||||
argcomplete
|
||||
future
|
||||
setup_requires = setuptools_scm
|
||||
tests_require =
|
||||
jaconv
|
||||
tox
|
||||
pytest!=3.2.0,!=3.3.0
|
||||
pytest-cov
|
||||
pytest-mock
|
||||
nose
|
||||
scripttest
|
||||
mock
|
||||
hypothesis>4
|
||||
flake8
|
||||
sphinxcontrib-spelling>=7.2.0
|
||||
|
||||
[nosetests]
|
||||
verbosity=3
|
||||
with-doctest=1
|
||||
|
||||
[flake8]
|
||||
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
|
||||
max-line-length = 120
|
||||
# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet
|
61
setup.py
@@ -1,29 +1,40 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
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
|
||||
|
||||
version = '{version}'
|
||||
"""
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name='escpos',
|
||||
version='1.0.8',
|
||||
url='https://github.com/manpaz/python-escpos',
|
||||
download_url='https://github.com/manpaz/python-escpos.git',
|
||||
description='Python library to manipulate ESC/POS Printers',
|
||||
license='GNU GPL v3',
|
||||
long_description=open('README').read(),
|
||||
author='Manuel F Martinez',
|
||||
author_email='manpaz@bashlinux.com',
|
||||
platforms=['linux'],
|
||||
packages=[
|
||||
'escpos',
|
||||
],
|
||||
package_data={'': ['COPYING']},
|
||||
classifiers=[
|
||||
'Development Status :: 1 - Alpha',
|
||||
'License :: OSI Approved :: GNU GPL v3',
|
||||
'Operating System :: GNU/Linux',
|
||||
'Intended Audience :: Developers',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: System :: Pheripherals',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
use_scm_version={
|
||||
"write_to": "src/escpos/version.py",
|
||||
"write_to_template": setuptools_scm_template,
|
||||
},
|
||||
platforms="any",
|
||||
package_dir={"": "src"},
|
||||
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
|
||||
package_data={"escpos": ["capabilities.json"]},
|
||||
entry_points={"console_scripts": ["python-escpos = escpos.cli:main"]},
|
||||
)
|
||||
|
15
src/escpos/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
python-escpos enables you to manipulate escpos-printers
|
||||
"""
|
||||
|
||||
__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
|
156
src/escpos/capabilities.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import re
|
||||
from os import environ, path
|
||||
import pkg_resources
|
||||
import pickle
|
||||
import logging
|
||||
import time
|
||||
|
||||
import six
|
||||
import yaml
|
||||
|
||||
from tempfile import gettempdir
|
||||
import platform
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir())
|
||||
pickle_path = path.join(
|
||||
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
|
||||
)
|
||||
# get a temporary file from pkg_resources if no file is specified in env
|
||||
capabilities_path = environ.get(
|
||||
"ESCPOS_CAPABILITIES_FILE",
|
||||
pkg_resources.resource_filename(__name__, "capabilities.json"),
|
||||
)
|
||||
|
||||
# Load external printer database
|
||||
t0 = time.time()
|
||||
logger.debug("Using capabilities from file: %s", capabilities_path)
|
||||
if path.exists(pickle_path):
|
||||
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
|
||||
logger.debug("Found a more recent capabilities file")
|
||||
full_load = True
|
||||
else:
|
||||
full_load = False
|
||||
logger.debug("Loading capabilities from pickle in %s", pickle_path)
|
||||
with open(pickle_path, "rb") as cf:
|
||||
CAPABILITIES = pickle.load(cf)
|
||||
else:
|
||||
logger.debug("Capabilities pickle file not found: %s", pickle_path)
|
||||
full_load = True
|
||||
|
||||
if full_load:
|
||||
logger.debug("Loading and pickling capabilities")
|
||||
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
|
||||
CAPABILITIES = yaml.safe_load(cp)
|
||||
pickle.dump(CAPABILITIES, pp, protocol=2)
|
||||
|
||||
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
|
||||
|
||||
|
||||
PROFILES: Dict[str, Any] = 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 represents a printer profile.
|
||||
|
||||
A printer profile knows about the number of columns, supported
|
||||
features, colors and more.
|
||||
"""
|
||||
|
||||
profile_data: Dict[str, Any] = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.profile_data[name]
|
||||
|
||||
def get_font(self, font) -> int:
|
||||
"""Return the escpos index for `font`. Makes sure that
|
||||
the requested `font` is valid.
|
||||
"""
|
||||
font = {"a": 0, "b": 1}.get(font, font)
|
||||
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: str = None, **kwargs):
|
||||
"""Get the profile by name; if no name is given, return the
|
||||
default profile.
|
||||
"""
|
||||
if isinstance(name, Profile):
|
||||
return name
|
||||
|
||||
clazz = get_profile_class(name or "default")
|
||||
return clazz(**kwargs)
|
||||
|
||||
|
||||
CLASS_CACHE = {}
|
||||
|
||||
|
||||
def get_profile_class(name: str):
|
||||
"""For the given profile name, load the data from the external
|
||||
database, then generate dynamically a class.
|
||||
"""
|
||||
if name not in CLASS_CACHE:
|
||||
profile_data = PROFILES[name]
|
||||
profile_name = clean(name)
|
||||
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
|
||||
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
|
||||
CLASS_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)
|
588
src/escpos/cli.py
Normal file
@@ -0,0 +1,588 @@
|
||||
#!/usr/bin/env python
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
""" CLI
|
||||
|
||||
This module acts as a command line interface for python-escpos. It mirrors
|
||||
closely the available ESCPOS commands while adding a couple extra ones for convenience.
|
||||
|
||||
It requires you to have a configuration file. See documentation for details.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
except ImportError:
|
||||
# this CLI works nevertheless without argcomplete
|
||||
pass # noqa
|
||||
import sys
|
||||
import six
|
||||
from . import config
|
||||
from . import version
|
||||
|
||||
|
||||
# Must be defined before it's used in DEMO_FUNCTIONS
|
||||
def str_to_bool(string):
|
||||
"""Used as a type in argparse so that we get back a proper
|
||||
bool instead of always True
|
||||
"""
|
||||
return string.lower() in ("y", "yes", "1", "true")
|
||||
|
||||
|
||||
# A list of functions that work better with a newline to be sent after them.
|
||||
REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
|
||||
|
||||
|
||||
# Used in demo method
|
||||
# Key: The name of escpos function and the argument passed on the CLI. Some
|
||||
# manual translation is done in the case of barcodes_a -> barcode.
|
||||
# Value: A list of dictionaries to pass to the escpos function as arguments.
|
||||
DEMO_FUNCTIONS = {
|
||||
"text": [
|
||||
{
|
||||
"txt": "Hello, World!\n",
|
||||
}
|
||||
],
|
||||
"qr": [
|
||||
{"content": "This tests a QR code"},
|
||||
{"content": "https://en.wikipedia.org/"},
|
||||
],
|
||||
"barcodes_a": [
|
||||
{"bc": "UPC-A", "code": "13243546576"},
|
||||
{"bc": "UPC-E", "code": "132435"},
|
||||
{"bc": "EAN13", "code": "1324354657687"},
|
||||
{"bc": "EAN8", "code": "1324354"},
|
||||
{"bc": "CODE39", "code": "TEST"},
|
||||
{"bc": "ITF", "code": "55867492279103"},
|
||||
{"bc": "NW7", "code": "A00000000A"},
|
||||
],
|
||||
"barcodes_b": [
|
||||
{"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
|
||||
{"bc": "UPC-E", "code": "132435", "function_type": "B"},
|
||||
{"bc": "EAN13", "code": "1324354657687", "function_type": "B"},
|
||||
{"bc": "EAN8", "code": "1324354", "function_type": "B"},
|
||||
{"bc": "CODE39", "code": "TEST", "function_type": "B"},
|
||||
{"bc": "ITF", "code": "55867492279103", "function_type": "B"},
|
||||
{"bc": "NW7", "code": "A00000000A", "function_type": "B"},
|
||||
{"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
|
||||
{"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
|
||||
{"bc": "CODE128A", "code": "TEST", "function_type": "B"},
|
||||
{"bc": "CODE128B", "code": "TEST", "function_type": "B"},
|
||||
{"bc": "CODE128C", "code": "TEST", "function_type": "B"},
|
||||
{"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"},
|
||||
{
|
||||
"bc": "GS1 DataBar Omnidirectional",
|
||||
"code": "0000000000000",
|
||||
"function_type": "B",
|
||||
},
|
||||
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
|
||||
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
|
||||
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
|
||||
],
|
||||
}
|
||||
|
||||
# Used to build the CLI
|
||||
# A list of dictionaries. Each dict is a CLI argument.
|
||||
# Keys:
|
||||
# parser: A dict of args for command_parsers.add_parser
|
||||
# defaults: A dict of args for subparser.set_defaults
|
||||
# arguments: A list of dicts of args for subparser.add_argument
|
||||
ESCPOS_COMMANDS = [
|
||||
{
|
||||
"parser": {
|
||||
"name": "qr",
|
||||
"help": "Print a QR code",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "qr",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--content",),
|
||||
"help": "Text to print as a qr code",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--size",),
|
||||
"help": "QR code size (1-16) [default:3]",
|
||||
"required": False,
|
||||
"type": int,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "barcode",
|
||||
"help": "Print a barcode",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "barcode",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--code",),
|
||||
"help": "Barcode data to print",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--bc",),
|
||||
"help": "Barcode format",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--height",),
|
||||
"help": "Barcode height in px",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--width",),
|
||||
"help": "Barcode width",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--pos",),
|
||||
"help": "Label position",
|
||||
"choices": ["BELOW", "ABOVE", "BOTH", "OFF"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--font",),
|
||||
"help": "Label font",
|
||||
"choices": ["A", "B"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--align_ct",),
|
||||
"help": "Align barcode center",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--function_type",),
|
||||
"help": "ESCPOS function type",
|
||||
"choices": ["A", "B"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "text",
|
||||
"help": "Print plain text",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "text",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--txt",),
|
||||
"help": "Plain text to print",
|
||||
"required": True,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "block_text",
|
||||
"help": "Print wrapped text",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "block_text",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--txt",),
|
||||
"help": "block_text to print",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--columns",),
|
||||
"help": "Number of columns",
|
||||
"type": int,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "cut",
|
||||
"help": "Cut the paper",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "cut",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--mode",),
|
||||
"help": "Type of cut",
|
||||
"choices": ["FULL", "PART"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "cashdraw",
|
||||
"help": "Kick the cash drawer",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "cashdraw",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--pin",),
|
||||
"help": "Which PIN to kick",
|
||||
"choices": [2, 5],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "image",
|
||||
"help": "Print an image",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "image",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--img_source",),
|
||||
"help": "Path to image",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--impl",),
|
||||
"help": "Implementation to use",
|
||||
"choices": ["bitImageRaster", "bitImageColumn", "graphics"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--high_density_horizontal",),
|
||||
"help": "Image density (horizontal)",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--high_density_vertical",),
|
||||
"help": "Image density (vertical)",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "fullimage",
|
||||
"help": "Print a fullimage",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "fullimage",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--img",),
|
||||
"help": "Path to img",
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--max_height",),
|
||||
"help": "Max height of image in px",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--width",),
|
||||
"help": "Max width of image in px",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--histeq",),
|
||||
"help": "Equalize the histrogram",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--bandsize",),
|
||||
"help": "Size of bands to divide into when printing",
|
||||
"type": int,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "charcode",
|
||||
"help": "Set character code table",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "charcode",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--code",),
|
||||
"help": "Character code",
|
||||
"required": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "set",
|
||||
"help": "Set text properties",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "set",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--align",),
|
||||
"help": "Horizontal alignment",
|
||||
"choices": ["left", "center", "right"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--font",),
|
||||
"help": "Font choice",
|
||||
"choices": ["left", "center", "right"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--text_type",),
|
||||
"help": "Text properties",
|
||||
"choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--width",),
|
||||
"help": "Width multiplier",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--height",),
|
||||
"help": "Height multiplier",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--density",),
|
||||
"help": "Print density",
|
||||
"type": int,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--invert",),
|
||||
"help": "White on black printing",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--smooth",),
|
||||
"help": "Text smoothing. Effective on >: 4x4 text",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--flip",),
|
||||
"help": "Text smoothing. Effective on >: 4x4 text",
|
||||
"type": str_to_bool,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "hw",
|
||||
"help": "Hardware operations",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "hw",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--hw",),
|
||||
"help": "Operation",
|
||||
"choices": ["INIT", "SELECT", "RESET"],
|
||||
"required": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "control",
|
||||
"help": "Control sequences",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "control",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--ctl",),
|
||||
"help": "Control sequence",
|
||||
"choices": ["LF", "FF", "CR", "HT", "VT"],
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"option_strings": ("--pos",),
|
||||
"help": "Horizontal tab position (1-4)",
|
||||
"type": int,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "panel_buttons",
|
||||
"help": "Controls panel buttons",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "panel_buttons",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--enable",),
|
||||
"help": "Feed button enabled",
|
||||
"type": str_to_bool,
|
||||
"required": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"parser": {
|
||||
"name": "raw",
|
||||
"help": "Raw data",
|
||||
},
|
||||
"defaults": {
|
||||
"func": "_raw",
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"option_strings": ("--msg",),
|
||||
"help": "Raw data to send",
|
||||
"required": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
|
||||
Handles loading of configuration and creating and processing of command
|
||||
line arguments. Called when run from a CLI.
|
||||
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="CLI for python-escpos",
|
||||
epilog="Printer configuration is defined in the python-escpos config"
|
||||
"file. See documentation for details.",
|
||||
)
|
||||
|
||||
parser.register("type", "bool", str_to_bool)
|
||||
|
||||
# Allow config file location to be passed
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
help="Alternate path to the configuration file",
|
||||
)
|
||||
|
||||
# Everything interesting runs off of a subparser so we can use the format
|
||||
# cli [subparser] -args
|
||||
command_subparsers = parser.add_subparsers(
|
||||
title="ESCPOS Command",
|
||||
dest="parser",
|
||||
)
|
||||
# fix inconsistencies in the behaviour of some versions of argparse
|
||||
command_subparsers.required = False # force 'required' testing
|
||||
|
||||
# Build the ESCPOS command arguments
|
||||
for command in ESCPOS_COMMANDS:
|
||||
parser_command = command_subparsers.add_parser(**command["parser"])
|
||||
parser_command.set_defaults(**command["defaults"])
|
||||
for argument in command["arguments"]:
|
||||
option_strings = argument.pop("option_strings")
|
||||
parser_command.add_argument(*option_strings, **argument)
|
||||
|
||||
# Build any custom arguments
|
||||
parser_command_demo = command_subparsers.add_parser(
|
||||
"demo", help="Demonstrates various functions"
|
||||
)
|
||||
parser_command_demo.set_defaults(func="demo")
|
||||
demo_group = parser_command_demo.add_mutually_exclusive_group()
|
||||
demo_group.add_argument(
|
||||
"--barcodes-a",
|
||||
help="Print demo barcodes for function type A",
|
||||
action="store_true",
|
||||
)
|
||||
demo_group.add_argument(
|
||||
"--barcodes-b",
|
||||
help="Print demo barcodes for function type B",
|
||||
action="store_true",
|
||||
)
|
||||
demo_group.add_argument(
|
||||
"--qr",
|
||||
help="Print some demo QR codes",
|
||||
action="store_true",
|
||||
)
|
||||
demo_group.add_argument(
|
||||
"--text",
|
||||
help="Print some demo text",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser_command_version = command_subparsers.add_parser(
|
||||
"version", help="Print the version of python-escpos"
|
||||
)
|
||||
parser_command_version.set_defaults(version=True)
|
||||
|
||||
# hook in argcomplete
|
||||
if "argcomplete" in globals():
|
||||
argcomplete.autocomplete(parser)
|
||||
|
||||
# Get only arguments actually passed
|
||||
args_dict = vars(parser.parse_args())
|
||||
if not args_dict:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
command_arguments = dict(
|
||||
[k, v] for k, v in six.iteritems(args_dict) if v is not None
|
||||
)
|
||||
|
||||
# If version should be printed, do this, then exit
|
||||
print_version = command_arguments.pop("version", None)
|
||||
if print_version:
|
||||
print(version.version)
|
||||
sys.exit()
|
||||
|
||||
# If there was a config path passed, grab it
|
||||
config_path = command_arguments.pop("config", None)
|
||||
|
||||
# Load the configuration and defined printer
|
||||
saved_config = config.Config()
|
||||
saved_config.load(config_path)
|
||||
printer = saved_config.printer()
|
||||
|
||||
if not printer:
|
||||
raise Exception("No printers loaded from config")
|
||||
|
||||
target_command = command_arguments.pop("func")
|
||||
|
||||
# remove helper-argument 'parser' from dict
|
||||
command_arguments.pop("parser", None)
|
||||
|
||||
if hasattr(printer, target_command):
|
||||
# print command with args
|
||||
getattr(printer, target_command)(**command_arguments)
|
||||
if target_command in REQUIRES_NEWLINE:
|
||||
printer.text("\n")
|
||||
else:
|
||||
command_arguments["printer"] = printer
|
||||
globals()[target_command](**command_arguments)
|
||||
|
||||
|
||||
def demo(printer, **kwargs):
|
||||
"""
|
||||
Prints demos. Called when CLI is passed `demo`. This function
|
||||
uses the DEMO_FUNCTIONS dictionary.
|
||||
|
||||
:param printer: A printer from escpos.printer
|
||||
:param kwargs: A dict with a key for each function you want to test. It's
|
||||
in this format since it usually comes from argparse.
|
||||
"""
|
||||
for demo_choice in kwargs.keys():
|
||||
command = getattr(
|
||||
printer,
|
||||
demo_choice.replace("barcodes_a", "barcode").replace(
|
||||
"barcodes_b", "barcode"
|
||||
),
|
||||
)
|
||||
for params in DEMO_FUNCTIONS[demo_choice]:
|
||||
command(**params)
|
||||
printer.cut()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
24
src/escpos/codepages.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from .capabilities import CAPABILITIES
|
||||
|
||||
|
||||
class CodePageManager:
|
||||
"""Holds information about all the code pages (as defined
|
||||
in escpos-printer-db).
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def get_all(self):
|
||||
return self.data.values()
|
||||
|
||||
@staticmethod
|
||||
def get_encoding_name(encoding):
|
||||
# TODO resolve the encoding alias
|
||||
return encoding.upper()
|
||||
|
||||
def get_encoding(self, encoding):
|
||||
return self.data[encoding]
|
||||
|
||||
|
||||
CodePages = CodePageManager(CAPABILITIES["encodings"])
|
116
src/escpos/config.py
Normal file
@@ -0,0 +1,116 @@
|
||||
""" ESC/POS configuration manager.
|
||||
|
||||
This module contains the implementations of abstract base class :py:class:`Config`.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import appdirs
|
||||
import yaml
|
||||
|
||||
from . import printer
|
||||
from . import exceptions
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""Configuration handler class.
|
||||
|
||||
This class loads configuration from a default or specificed directory. It
|
||||
can create your defined printer and return it to you.
|
||||
"""
|
||||
|
||||
_app_name = "python-escpos"
|
||||
_config_file = "config.yaml"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize configuration.
|
||||
|
||||
Remember to add anything that needs to be reset between configurations
|
||||
to self._reset_config
|
||||
"""
|
||||
self._has_loaded = False
|
||||
self._printer = None
|
||||
|
||||
self._printer_name = None
|
||||
self._printer_config = None
|
||||
|
||||
def _reset_config(self):
|
||||
"""Clear the loaded configuration.
|
||||
|
||||
If we are loading a changed config, we don't want to have leftover
|
||||
data.
|
||||
"""
|
||||
self._has_loaded = False
|
||||
self._printer = None
|
||||
|
||||
self._printer_name = None
|
||||
self._printer_config = None
|
||||
|
||||
def load(self, config_path=None):
|
||||
"""Load and parse the configuration file using pyyaml
|
||||
|
||||
:param config_path: An optional file path, file handle, or byte string
|
||||
for the configuration file.
|
||||
|
||||
"""
|
||||
|
||||
self._reset_config()
|
||||
|
||||
if not config_path:
|
||||
config_path = os.path.join(
|
||||
appdirs.user_config_dir(self._app_name), self._config_file
|
||||
)
|
||||
|
||||
try:
|
||||
# First check if it's file like. If it is, pyyaml can load it.
|
||||
# I'm checking type instead of catching exceptions to keep the
|
||||
# exception handling simple
|
||||
if hasattr(config_path, "read"):
|
||||
config = yaml.safe_load(config_path)
|
||||
else:
|
||||
# If it isn't, it's a path. We have to open it first, otherwise
|
||||
# pyyaml will try to read it as yaml
|
||||
with open(config_path, "rb") as config_file:
|
||||
config = yaml.safe_load(config_file)
|
||||
except EnvironmentError:
|
||||
raise exceptions.ConfigNotFoundError(
|
||||
"Couldn't read config at {config_path}".format(
|
||||
config_path=str(config_path),
|
||||
)
|
||||
)
|
||||
except yaml.YAMLError:
|
||||
raise exceptions.ConfigSyntaxError("Error parsing YAML")
|
||||
|
||||
if "printer" in config:
|
||||
self._printer_config = config["printer"]
|
||||
self._printer_name = self._printer_config.pop("type").title()
|
||||
|
||||
if not self._printer_name or not hasattr(printer, self._printer_name):
|
||||
raise exceptions.ConfigSyntaxError(
|
||||
'Printer type "{printer_name}" is invalid'.format(
|
||||
printer_name=self._printer_name,
|
||||
)
|
||||
)
|
||||
|
||||
self._has_loaded = True
|
||||
|
||||
def printer(self):
|
||||
"""Returns a printer that was defined in the config, or throws an
|
||||
exception.
|
||||
|
||||
This method loads the default config if one hasn't beeen already loaded.
|
||||
|
||||
"""
|
||||
if not self._has_loaded:
|
||||
self.load()
|
||||
|
||||
if not self._printer_name:
|
||||
raise exceptions.ConfigSectionMissingError("printer")
|
||||
|
||||
if not self._printer:
|
||||
# We could catch init errors and make them a ConfigSyntaxError,
|
||||
# but I'll just let them pass
|
||||
self._printer = getattr(printer, self._printer_name)(**self._printer_config)
|
||||
|
||||
return self._printer
|
292
src/escpos/constants.py
Normal file
@@ -0,0 +1,292 @@
|
||||
# -*- 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
|
||||
"""
|
||||
|
||||
|
||||
import six
|
||||
|
||||
# Control characters
|
||||
# as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
|
||||
NUL = b"\x00"
|
||||
EOT = b"\x04"
|
||||
ENQ = b"\x05"
|
||||
DLE = b"\x10"
|
||||
DC4 = b"\x14"
|
||||
CAN = b"\x18"
|
||||
ESC = b"\x1b"
|
||||
FS = b"\x1c"
|
||||
GS = b"\x1d"
|
||||
|
||||
# Feed control sequences
|
||||
CTL_LF = b"\n" # Print and line feed
|
||||
CTL_FF = b"\f" # Form feed
|
||||
CTL_CR = b"\r" # Carriage return
|
||||
CTL_HT = b"\t" # Horizontal tab
|
||||
CTL_SET_HT = ESC + b"\x44" # Set horizontal tab positions
|
||||
CTL_VT = b"\v" # Vertical tab
|
||||
|
||||
# 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 (please note that the actual beep sequence may differ between devices)
|
||||
BEEP = b"\x07"
|
||||
|
||||
# Panel buttons (e.g. the FEED button)
|
||||
_PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n)
|
||||
PANEL_BUTTON_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
|
||||
|
||||
# Slip specific codes
|
||||
SLIP_EJECT = ESC + b"\x4b\xc0" # Eject the slip or cheque
|
||||
SLIP_SELECT = FS # Select the slip station as default station
|
||||
SLIP_SET_WAIT_TIME = (
|
||||
ESC + b"\x1b\x66"
|
||||
) # Set timeout waiting for a slip/cheque to be inserted
|
||||
SLIP_PRINT_AND_EJECT = (
|
||||
b"\x0c" # Print the buffer and eject (after waiting for the paper to be inserted)
|
||||
)
|
||||
|
||||
# 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", False: ESC + b"\x7b\x00"}, # Flip ON # Flip OFF
|
||||
"density": {
|
||||
0: GS + b"\x7c\x00", # Printing Density -50%
|
||||
1: GS + b"\x7c\x01", # Printing Density -37.5%
|
||||
2: GS + b"\x7c\x02", # Printing Density -25%
|
||||
3: GS + b"\x7c\x03", # Printing Density -12.5%
|
||||
4: GS + b"\x7c\x04", # Printing Density 0%
|
||||
5: GS + b"\x7c\x08", # Printing Density +50%
|
||||
6: GS + b"\x7c\x07", # Printing Density +37.5%
|
||||
7: GS + b"\x7c\x06", # Printing Density +25%
|
||||
8: GS + b"\x7c\x05", # Printing Density +12.5%
|
||||
},
|
||||
"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
|
1139
src/escpos/escpos.py
Normal file
275
src/escpos/exceptions.py
Normal file
@@ -0,0 +1,275 @@
|
||||
# -*- 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
|
||||
"""
|
||||
|
||||
|
||||
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)
|
129
src/escpos/image.py
Normal file
@@ -0,0 +1,129 @@
|
||||
""" 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
|
||||
"""
|
||||
|
||||
|
||||
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
|
101
src/escpos/katakana.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Helpers to encode Japanese characters.
|
||||
|
||||
I doubt that this currently works correctly.
|
||||
"""
|
||||
|
||||
|
||||
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",
|
||||
}
|
302
src/escpos/magicencode.py
Normal file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Magic Encode
|
||||
|
||||
This module tries to convert an UTF-8 string to an encoded string for the printer.
|
||||
It uses trial and error in order to guess the right codepage.
|
||||
The code is based on the encoding-code in py-xml-escpos by @fvdsn.
|
||||
|
||||
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
||||
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||
:copyright: Copyright (c) 2016 Patrick Kanzler and Frédéric van der Essen
|
||||
:license: MIT
|
||||
"""
|
||||
|
||||
|
||||
from builtins import bytes
|
||||
from .constants import CODEPAGE_CHANGE
|
||||
from .exceptions import Error
|
||||
from .codepages import CodePages
|
||||
import six
|
||||
import re
|
||||
|
||||
|
||||
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 = [" "] * 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 beginning 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
|
||||
|
||||
if re.findall(r"[\u4e00-\u9fa5]", text):
|
||||
self.driver._raw(text.encode("GB18030"))
|
||||
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))
|
602
src/escpos/printer.py
Normal file
@@ -0,0 +1,602 @@
|
||||
#!/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
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import serial
|
||||
import usb.core
|
||||
import usb.util
|
||||
|
||||
from .escpos import Escpos
|
||||
from .exceptions import USBNotFoundError
|
||||
|
||||
_WIN32PRINT = False
|
||||
try:
|
||||
import win32print
|
||||
|
||||
_WIN32PRINT = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
_CUPSPRINT = False
|
||||
try:
|
||||
import cups
|
||||
import tempfile
|
||||
|
||||
_CUPSPRINT = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
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,
|
||||
usb_args=None,
|
||||
timeout=0,
|
||||
in_ep=0x82,
|
||||
out_ep=0x01,
|
||||
*args,
|
||||
**kwargs
|
||||
): # noqa: N803
|
||||
"""
|
||||
:param idVendor: Vendor ID
|
||||
:param idProduct: Product ID
|
||||
:param usb_args: Optional USB arguments (e.g. custom_match)
|
||||
:param timeout: Is the time limit of the USB operation. Default without timeout.
|
||||
:param in_ep: Input end point
|
||||
:param out_ep: Output end point
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
self.timeout = timeout
|
||||
self.in_ep = in_ep
|
||||
self.out_ep = out_ep
|
||||
|
||||
usb_args = usb_args or {}
|
||||
if idVendor:
|
||||
usb_args["idVendor"] = idVendor
|
||||
if idProduct:
|
||||
usb_args["idProduct"] = idProduct
|
||||
self.open(usb_args)
|
||||
|
||||
def open(self, usb_args):
|
||||
"""Search device on USB tree and set it as escpos device.
|
||||
|
||||
:param usb_args: USB arguments
|
||||
"""
|
||||
self.device = usb.core.find(**usb_args)
|
||||
if self.device is None:
|
||||
raise USBNotFoundError("Device not found or cable not plugged in.")
|
||||
|
||||
self.idVendor = self.device.idVendor
|
||||
self.idProduct = self.device.idProduct
|
||||
|
||||
# pyusb has three backends: libusb0, libusb1 and openusb but
|
||||
# only libusb1 backend implements the methods is_kernel_driver_active()
|
||||
# and detach_kernel_driver().
|
||||
# This helps enable this library to work on Windows.
|
||||
if self.device.backend.__module__.endswith("libusb1"):
|
||||
check_driver = None
|
||||
|
||||
try:
|
||||
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 NotImplementedError:
|
||||
pass
|
||||
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 _read(self):
|
||||
"""Read data from the TCP socket"""
|
||||
|
||||
return self.device.recv(16)
|
||||
|
||||
def close(self):
|
||||
"""Close TCP connection"""
|
||||
if self.device is not None:
|
||||
try:
|
||||
self.device.shutdown(socket.SHUT_RDWR)
|
||||
except socket.error:
|
||||
pass
|
||||
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
|
||||
|
||||
|
||||
if _WIN32PRINT:
|
||||
|
||||
class Win32Raw(Escpos):
|
||||
def __init__(self, printer_name=None, *args, **kwargs):
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
if printer_name is not None:
|
||||
self.printer_name = printer_name
|
||||
else:
|
||||
self.printer_name = win32print.GetDefaultPrinter()
|
||||
self.hPrinter = None
|
||||
self.open()
|
||||
|
||||
def open(self, job_name="python-escpos"):
|
||||
if self.printer_name is None:
|
||||
raise Exception("Printer not found")
|
||||
self.hPrinter = win32print.OpenPrinter(self.printer_name)
|
||||
self.current_job = win32print.StartDocPrinter(
|
||||
self.hPrinter, 1, (job_name, None, "RAW")
|
||||
)
|
||||
win32print.StartPagePrinter(self.hPrinter)
|
||||
|
||||
def close(self):
|
||||
if not self.hPrinter:
|
||||
return
|
||||
win32print.EndPagePrinter(self.hPrinter)
|
||||
win32print.EndDocPrinter(self.hPrinter)
|
||||
win32print.ClosePrinter(self.hPrinter)
|
||||
self.hPrinter = None
|
||||
|
||||
def _raw(self, msg):
|
||||
"""Print any command sent in raw format
|
||||
|
||||
:param msg: arbitrary code to be printed
|
||||
:type msg: bytes
|
||||
"""
|
||||
if self.printer_name is None:
|
||||
raise Exception("Printer not found")
|
||||
if self.hPrinter is None:
|
||||
raise Exception("Printer job not opened")
|
||||
win32print.WritePrinter(self.hPrinter, msg)
|
||||
|
||||
|
||||
if _CUPSPRINT:
|
||||
|
||||
class CupsPrinter(Escpos):
|
||||
"""Simple CUPS printer connector.
|
||||
|
||||
.. note::
|
||||
Requires _pycups_ which in turn needs the cups development library package:
|
||||
- Ubuntu/Debian: _libcups2-dev_
|
||||
- OpenSuse/Fedora: _cups-devel_
|
||||
"""
|
||||
|
||||
def __init__(self, printer_name=None, *args, **kwargs):
|
||||
"""CupsPrinter class constructor.
|
||||
|
||||
:param printer_name: CUPS printer name (Optional)
|
||||
:type printer_name: str
|
||||
:param host: CUPS server host/ip (Optional)
|
||||
:type host: str
|
||||
:param port: CUPS server port (Optional)
|
||||
:type port: int
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
host, port = args or (
|
||||
kwargs.get("host", cups.getServer()),
|
||||
kwargs.get("port", cups.getPort()),
|
||||
)
|
||||
cups.setServer(host)
|
||||
cups.setPort(port)
|
||||
self.conn = cups.Connection()
|
||||
self.tmpfile = None
|
||||
self.printer_name = printer_name
|
||||
self.job_name = ""
|
||||
self.pending_job = False
|
||||
self.open()
|
||||
|
||||
@property
|
||||
def printers(self):
|
||||
"""Available CUPS printers."""
|
||||
return self.conn.getPrinters()
|
||||
|
||||
def open(self, job_name="python-escpos"):
|
||||
"""Setup a new print job and target printer.
|
||||
|
||||
A call to this method is required to send new jobs to
|
||||
the same CUPS connection.
|
||||
|
||||
Defaults to default CUPS printer.
|
||||
Creates a new temporary file buffer.
|
||||
"""
|
||||
self.job_name = job_name
|
||||
if self.printer_name not in self.printers:
|
||||
self.printer_name = self.conn.getDefault()
|
||||
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
|
||||
|
||||
def _raw(self, msg):
|
||||
"""Append any command sent in raw format to temporary file
|
||||
|
||||
:param msg: arbitrary code to be printed
|
||||
:type msg: bytes
|
||||
"""
|
||||
self.pending_job = True
|
||||
try:
|
||||
self.tmpfile.write(msg)
|
||||
except ValueError:
|
||||
self.pending_job = False
|
||||
raise ValueError("Printer job not opened")
|
||||
|
||||
def send(self):
|
||||
"""Send the print job to the printer."""
|
||||
if self.pending_job:
|
||||
# Rewind tempfile
|
||||
self.tmpfile.seek(0)
|
||||
# Print temporary file via CUPS printer.
|
||||
self.conn.printFile(
|
||||
self.printer_name,
|
||||
self.tmpfile.name,
|
||||
self.job_name,
|
||||
{"document-format": cups.CUPS_FORMAT_RAW},
|
||||
)
|
||||
self._clear()
|
||||
|
||||
def _clear(self):
|
||||
"""Finish the print job.
|
||||
|
||||
Remove temporary file.
|
||||
"""
|
||||
self.tmpfile.close()
|
||||
self.pending_job = False
|
||||
|
||||
def _read(self):
|
||||
"""Return a single-item array with the accepting state of the print queue.
|
||||
|
||||
states: idle = [3], printing a job = [4], stopped = [5]
|
||||
"""
|
||||
printer = self.printers.get(self.printer_name, {})
|
||||
state = printer.get("printer-state")
|
||||
if not state:
|
||||
return []
|
||||
return [state]
|
||||
|
||||
def close(self):
|
||||
"""Close CUPS connection.
|
||||
|
||||
Send pending job to the printer if needed.
|
||||
"""
|
||||
if self.pending_job:
|
||||
self.send()
|
||||
if self.conn:
|
||||
print("Closing CUPS connection to printer {}".format(self.printer_name))
|
||||
self.conn = None
|
||||
|
||||
|
||||
if not sys.platform.startswith("win"):
|
||||
|
||||
class LP(Escpos):
|
||||
"""Simple UNIX lp command raw printing.
|
||||
|
||||
Thanks to `Oyami-Srk comment <https://github.com/python-escpos/python-escpos/pull/348#issuecomment-549558316>`_.
|
||||
"""
|
||||
|
||||
def __init__(self, printer_name: str, *args, **kwargs):
|
||||
"""LP class constructor.
|
||||
|
||||
:param printer_name: CUPS printer name (Optional)
|
||||
:type printer_name: str
|
||||
:param auto_flush: Automatic flush after every _raw() (Optional)
|
||||
:type auto_flush: bool
|
||||
"""
|
||||
Escpos.__init__(self, *args, **kwargs)
|
||||
self.printer_name = printer_name
|
||||
self.auto_flush = kwargs.get("auto_flush", True)
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
"""Invoke _lp_ in a new subprocess and wait for commands."""
|
||||
self.lp = subprocess.Popen(
|
||||
["lp", "-d", self.printer_name, "-o", "raw"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=open(os.devnull, "w"),
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""Stop the subprocess."""
|
||||
self.lp.terminate()
|
||||
|
||||
def flush(self):
|
||||
"""End line and wait for new commands"""
|
||||
if self.lp.stdin.writable():
|
||||
self.lp.stdin.write(b"\n")
|
||||
if self.lp.stdin.closed is False:
|
||||
self.lp.stdin.close()
|
||||
self.lp.wait()
|
||||
self.open()
|
||||
|
||||
def _raw(self, msg):
|
||||
"""Write raw command(s) to the printer.
|
||||
|
||||
:param msg: arbitrary code to be printed
|
||||
:type msg: bytes
|
||||
"""
|
||||
if self.lp.stdin.writable():
|
||||
self.lp.stdin.write(msg)
|
||||
else:
|
||||
raise Exception("Not a valid pipe for lp process")
|
||||
if self.auto_flush:
|
||||
self.flush()
|
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 |