1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-09-13 09:09:58 +00:00

198 Commits

Author SHA1 Message Date
Patrick Kanzler
2b826aa834 add changelog (#516) 2023-05-11 23:37:09 +02:00
Patrick Kanzler
3560f8b12b housekeeping: fix-build for doc (#515)
* disable git checkout head²

* update pull request template

* add importlib metadata to requirements for doc build

* update capabilities data
2023-05-11 23:12:46 +02:00
dependabot[bot]
83c7f5745c Bump capabilities-data from 190a96d to 5fb5cb7 (#514)
Bumps [capabilities-data](https://github.com/receipt-print-hq/escpos-printer-db) from `190a96d` to `5fb5cb7`.
- [Commits](190a96db4b...5fb5cb7254)

---
updated-dependencies:
- dependency-name: capabilities-data
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 15:15:16 +02:00
Mathieu Poussin
7bf8d4e4b8 Add support for slip/cheque dot matrix printers (#485)
* Add support for slip/cheque dot matrix printers

* format

* fix comments

---------

Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
2023-05-09 01:25:48 +02:00
白月秋见心
6849dd3979 Add Chinese support (#356)
* Add Chinese support

* Add author

* Add newline

* Revert "Add Chinese support"

This reverts commit 110200dde6.

* Add Chinese support

* Add Chinese support

* update mailmap file

* update formatting

---------

Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
2023-05-09 01:18:00 +02:00
Patrick Kanzler
25867f3196 update to most recent printer database (#513) 2023-05-09 01:15:34 +02:00
B. Howell
8bbbf7ceac soft_barcode to render directly (#469)
Rendering to /dev/null by a call to .write causes an error. Calling .render directly is simpler and fixes the error.

Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
2023-05-09 01:07:56 +02:00
Patrick Kanzler
5b72985b0a Merge pull request #512 from python-escpos/dependabot/github_actions/actions/setup-python-4.6.0
Bump actions/setup-python from 4.5.0 to 4.6.0
2023-04-21 17:27:24 +02:00
dependabot[bot]
ef7d518953 Bump actions/setup-python from 4.5.0 to 4.6.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-21 10:56:42 +00:00
Patrick Kanzler
d4d3819d55 Merge pull request #348 from belono/development
Add new CUPS printer connector
2023-04-19 22:49:05 +02:00
Patrick Kanzler
98ee06a783 preliminarily deactivate cups printer documentation 2023-04-19 22:42:55 +02:00
Patrick Kanzler
de515a86cc add && 2023-04-19 22:36:20 +02:00
Patrick Kanzler
2b0d57a9c7 install pycups packages in doc build 2023-04-19 22:34:08 +02:00
Patrick Kanzler
1626af5def Merge branch 'master' into development 2023-04-19 22:27:56 +02:00
Patrick Kanzler
e4a73ad5c5 install pycups on docbuild 2023-04-19 22:27:03 +02:00
Patrick Kanzler
709d90809c fixed formatting with black 2023-04-19 22:23:23 +02:00
Patrick Kanzler
90c340163c Merge branch 'master' into development 2023-04-19 22:19:27 +02:00
Patrick Kanzler
757fe3328c fix passenv 2023-04-19 22:18:25 +02:00
Patrick Kanzler
0a8d8ae6c4 py311 in tests 2023-04-19 22:15:47 +02:00
Patrick Kanzler
70d4da1364 format with black 2023-04-19 22:11:09 +02:00
Patrick Kanzler
cbf887ed02 adapt confDir config for sphinx integration 2023-04-19 20:37:08 +02:00
belono
b1a1204bb8 Add _read() method to the CupsPrinter() connector 2023-04-14 12:36:15 +02:00
Benito López
361afe3555 Merge branch 'master' into development 2023-02-17 00:17:20 +01:00
Patrick Kanzler
2bc61d9bf8 Merge pull request #482 from vendryan/fix-typo
Fix typo
2023-02-05 22:43:28 +01:00
Patrick Kanzler
67827d8e4d Merge pull request #502 from python-escpos/dependabot/github_actions/actions/setup-python-4.5.0
Bump actions/setup-python from 3.1.1 to 4.5.0
2023-02-05 22:42:53 +01:00
dependabot[bot]
2567e0e12a Bump actions/setup-python from 3.1.1 to 4.5.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3.1.1 to 4.5.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3.1.1...v4.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-05 21:36:52 +00:00
dependabot[bot]
83cccf0cb9 Bump github/codeql-action from 1 to 2 (#481)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-05 22:36:00 +01:00
belono
fe9c0573f8 Fix docs build 2022-12-11 18:41:05 +01:00
belono
ea61f287cb Add/Improve documentation of the new connectors 2022-12-11 16:29:33 +01:00
belono
69e9dca761 Fix black linter checks - Remove u"" strings 2022-11-24 20:23:21 +01:00
belono
49ab3f35a8 Fix build version targets for ubuntu-latest 22.04 2022-11-24 12:09:04 +01:00
belono
84a3912b34 Fix docs build - libenchant1c2a -> libenchant-2-2 2022-11-24 11:50:22 +01:00
belono
9a3057c8a8 Add LP Connector - UNIX printing via lp command 2022-11-24 00:11:01 +01:00
Benito López
0b695eff79 Merge branch 'master' into development 2022-08-28 21:45:21 +02:00
belono
f0760ddbc0 Add support for remote CUPS server 2022-08-28 21:39:01 +02:00
belono
39826c3286 Fix .mailmap 2022-08-15 22:22:44 +02:00
vendryan
7f9878282d Fix typo 2022-05-13 08:52:27 +07:00
dependabot[bot]
5e80764ff5 Bump actions/checkout from 2 to 3 (#477)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-07 19:51:19 +02:00
dependabot[bot]
219b43d35a Bump actions/setup-python from 2.3.1 to 3.1.1 (#479)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.1 to 3.1.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.3.1...v3.1.1)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-07 19:40:09 +02:00
dependabot[bot]
b16f44257e Bump actions/setup-python from 2.2.2 to 2.3.1 (#468)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.2.2 to 2.3.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.2.2...v2.3.1)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-30 22:52:04 +01:00
Patrick Kanzler
5bb0642b5d housekeeping tasks (#464)
* update settings to use black

* update python versions

* enquote version numbers

* update dependency

* set explicit build command

* add newline

* add command to checkout

* add run step

* chain

* sudo

* test

* newer sphinx version

* update sphinx

* clean up setuptools usage

* use tox

* install tox

* tox
2021-10-30 22:21:34 +02:00
Patrick Kanzler
8e5c600e20 Merge pull request #463 from python-escpos/add-black
add black as lint step
2021-10-30 18:22:44 +02:00
Patrick Kanzler
435f2bba24 reformat codebase 2021-10-30 18:15:22 +02:00
Patrick Kanzler
109a5d8a92 add config for black 2021-10-30 18:14:59 +02:00
Patrick Kanzler
4a88bacd3f add black as lint step 2021-10-30 18:07:21 +02:00
Patrick Kanzler
19abf43448 Merge pull request #455 from python-escpos/dependabot/github_actions/actions/setup-python-2.2.2
Bump actions/setup-python from 2.2.1 to 2.2.2
2021-10-30 17:59:10 +02:00
Patrick Kanzler
7b6dbc4db4 Merge pull request #461 from python-escpos/dependabot/github_actions/actions/checkout-2.3.5
Bump actions/checkout from 2.3.4 to 2.3.5
2021-10-30 17:58:57 +02:00
Patrick Kanzler
53aab73ffc Merge pull request #435 from brendanhowell/patch-1
Update escpos.py
2021-10-30 17:58:35 +02:00
Patrick Kanzler
a4e15c6ac2 Merge branch 'master' into patch-1 2021-10-30 17:58:24 +02:00
Patrick Kanzler
dcfb8fec30 Merge branch 'master' into dependabot/github_actions/actions/setup-python-2.2.2 2021-10-30 17:58:03 +02:00
Patrick Kanzler
14edc87206 Merge branch 'master' into dependabot/github_actions/actions/checkout-2.3.5 2021-10-30 17:57:50 +02:00
Patrick Kanzler
1e3024eaa9 Merge pull request #462 from AlejandroJaez/patch-1
add call to self.open() to get physical printer works.
2021-10-30 17:57:35 +02:00
Alejandro Hernández
15c18f225e Update printer.py
without the call to open function, the printer isn't initialize to print, i tested on a generic and epson physical printers.
2021-10-27 17:35:53 -05:00
dependabot[bot]
a526be5ddf Bump actions/checkout from 2.3.4 to 2.3.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 10:04:25 +00:00
dependabot[bot]
92fbcabdd2 Bump actions/setup-python from 2.2.1 to 2.2.2
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.2.1...v2.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-12 09:45:29 +00:00
Patrick Kanzler
27b7ae8629 Merge pull request #454 from python-escpos/dependabot/github_actions/actions/checkout-2.3.4
Bump actions/checkout from 2 to 2.3.4
2021-09-12 11:44:54 +02:00
dependabot[bot]
7c82881f65 Bump actions/checkout from 2 to 2.3.4
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 2.3.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v2.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-12 05:16:05 +00:00
B. Howell
05f68b22b4 Merge branch 'master' into patch-1 2021-01-25 18:25:07 +01:00
Patrick Kanzler
f9ce777057 Merge pull request #441 from python-escpos/dependabot/github_actions/actions/setup-python-v2.2.1
Bump actions/setup-python from v2.2.0 to v2.2.1
2020-12-21 10:32:23 +01:00
dependabot[bot]
c2e3b79e5a Bump actions/setup-python from v2.2.0 to v2.2.1
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.2.0 to v2.2.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.2.0...3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 07:27:38 +00:00
Patrick Kanzler
b5a999a6ea Merge pull request #439 from python-escpos/dependabot/github_actions/actions/setup-python-v2.2.0
Bump actions/setup-python from v2.1.4 to v2.2.0
2020-12-18 09:19:10 +01:00
dependabot[bot]
0b2118e146 Bump actions/setup-python from v2.1.4 to v2.2.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.1.4 to v2.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.1.4...8c5ea631b2b2d5d8840cf4a2b183a8a0edc1e40d)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-18 06:30:08 +00:00
B. Howell
69e9b9103e Update escpos.py
Set block_text to use the default font. This means calls will wrap to full paper width.
2020-11-28 00:41:11 +01:00
Patrick Kanzler
620cf97bbf Merge pull request #433 from python-escpos/drop-py35
drop support for python 3.5
2020-11-08 23:55:52 +01:00
Patrick Kanzler
1ad53eb642 drop support for python 3.5 2020-11-08 23:51:57 +01:00
Patrick Kanzler
ed8ec0788a start with type annotation 2020-11-08 23:45:25 +01:00
Patrick Kanzler
dd3c768f13 Merge pull request #431 from python-escpos/handle-exception-on-network-close
Handle exception on network close
2020-11-08 22:37:13 +01:00
Patrick Kanzler
36bbb6690f handle socket error when closing 2020-11-08 22:34:57 +01:00
Patrick Kanzler
0758a79e64 test for graceful closing of socket 2020-11-08 22:33:51 +01:00
Patrick Kanzler
9c41cfc54f Merge pull request #422 from python-escpos/patkan-patch-1
Test for python 3.9
2020-11-08 22:16:10 +01:00
Patrick Kanzler
75006f62da Merge branch 'master' into patkan-patch-1 2020-11-08 22:14:19 +01:00
dependabot[bot]
58e1682448 Merge pull request #425 from python-escpos/dependabot/github_actions/actions/setup-python-v2.1.4 2020-11-08 21:13:36 +00:00
dependabot[bot]
0057a498bb Bump actions/setup-python from v2.1.3 to v2.1.4
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.1.3 to v2.1.4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.1.3...41b7212b1668f5de9d65e9c82aa777e6bbedb3a8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-08 21:08:33 +00:00
Patrick Kanzler
b3e93020d4 Merge pull request #430 from python-escpos/clean-up-broken-dependencies
Clean up broken dependencies
2020-11-08 22:07:10 +01:00
Patrick Kanzler
3962bc991f improve test for soft barcode 2020-11-08 22:04:27 +01:00
Patrick Kanzler
b608d59942 remove viivakoodi from dependencies 2020-11-08 21:34:07 +01:00
Patrick Kanzler
e898413464 change useage of assert_equal(s) 2020-11-08 21:29:18 +01:00
Patrick Kanzler
357558ae10 Merge pull request #429 from python-escpos/update-capabilities-db
update capabilities-data
2020-11-08 19:49:15 +01:00
Patrick Kanzler
809b4f47aa update capabilities-data 2020-11-08 19:45:08 +01:00
Patrick Kanzler
b6516e4b42 Test for python 3.9
#421
2020-10-08 23:56:05 +02:00
Patrick Kanzler
8a54d59dbc Merge pull request #420 from python-escpos/dependabot/github_actions/actions/setup-python-v2.1.3
Bump actions/setup-python from v1 to v2.1.3
2020-10-06 00:12:34 +02:00
dependabot[bot]
1275bf90d1 Bump actions/setup-python from v1 to v2.1.3
Bumps [actions/setup-python](https://github.com/actions/setup-python) from v1 to v2.1.3.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v1...c181ffa198a1248f902bc2f7965d2f9a36c2d7f6)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-05 22:10:17 +00:00
Patrick Kanzler
d6639d37f3 set directories 2020-10-06 00:04:11 +02:00
Patrick Kanzler
21f19b7f7e use standard directories 2020-10-06 00:01:59 +02:00
Patrick Kanzler
3c479407dc fix github-actions config 2020-10-05 23:58:35 +02:00
Patrick Kanzler
3f04ff352e Merge pull request #419 from python-escpos/add-dependabot
create dependabot.yml
2020-10-05 23:56:56 +02:00
Patrick Kanzler
6c89e93c22 create dependabot.yml 2020-10-05 23:51:27 +02:00
Patrick Kanzler
266b64bf2b Merge pull request #416 from python-escpos/410-troubleshoot-startsp100
410 troubleshoot startsp100
2020-10-04 15:12:47 +02:00
Patrick Kanzler
a2e348d04a fix typos 2020-10-04 14:30:03 +02:00
Patrick Kanzler
40c8399a68 update documentation on STAR TSP100
fixes #410 and #403
2020-10-04 14:28:09 +02:00
Patrick Kanzler
a279ae7152 Merge pull request #414 from python-escpos/add-codeql
Create codeql-analysis.yml
2020-10-04 14:10:13 +02:00
Patrick Kanzler
a2d553efb1 Create codeql-analysis.yml 2020-10-01 13:46:23 +02:00
Patrick Kanzler
84a97e5b9f Merge pull request #412 from python-escpos/365-enhance-documentation
add disclaimer to beep sound
2020-09-28 22:42:21 +02:00
Patrick Kanzler
7db3d30f4a Merge branch 'master' into 365-enhance-documentation 2020-09-28 22:37:36 +02:00
Patrick Kanzler
c6f93e017a Merge pull request #411 from python-escpos/404-enhance-documentation-on-naming
add hint on proper script names
2020-09-28 22:37:02 +02:00
Patrick Kanzler
c09c10cb2d Merge remote-tracking branch 'origin/master' into 404-enhance-documentation-on-naming 2020-09-28 22:35:13 +02:00
Patrick Kanzler
af4785d52f Merge remote-tracking branch 'origin/master' into 365-enhance-documentation 2020-09-28 22:34:50 +02:00
Patrick Kanzler
e68cdcdac2 Merge pull request #413 from python-escpos/documentation-build-action
Build documentation in Github action
2020-09-28 22:33:51 +02:00
Patrick Kanzler
18cc1a268a use corrct name for sphinx package 2020-09-28 22:31:48 +02:00
Patrick Kanzler
5d62be2be9 install graphviz and libenchant 2020-09-28 22:28:27 +02:00
Patrick Kanzler
1dc79baf82 rename job 2020-09-28 21:33:16 +02:00
Patrick Kanzler
247211d0ce install sphinx 2020-09-28 21:31:29 +02:00
Patrick Kanzler
243eb48b38 install git in container 2020-09-28 21:26:42 +02:00
Patrick Kanzler
21b9dba345 have a look around before building doc 2020-09-28 21:22:54 +02:00
Patrick Kanzler
a8941b4a3e check out submodules 2020-09-28 21:15:33 +02:00
Patrick Kanzler
04e0a0ce47 Revert "tr< checkout v1"
This reverts commit 8ece137789.
2020-09-28 21:14:56 +02:00
Patrick Kanzler
8ece137789 tr< checkout v1 2020-09-28 21:12:44 +02:00
Patrick Kanzler
d390449400 change documenatation action 2020-09-28 21:06:45 +02:00
Patrick Kanzler
8488fafd75 remove travis config
fixes #389
2020-09-28 20:59:51 +02:00
Patrick Kanzler
818c1313e8 Build documentation in Github action 2020-09-28 20:55:13 +02:00
Patrick Kanzler
596787554b add disclaimer to beep sound
fixes #365
2020-09-28 20:44:22 +02:00
Patrick Kanzler
2d798ca7e5 add hint on proper script names
fixes #404
2020-09-28 19:51:49 +02:00
Patrick Kanzler
24105226b2 Merge pull request #402 from Foaly/bugfix/https
Use https links.
2020-05-31 16:40:26 +02:00
Maximilian Wagenbach
c57b9c35dc Use https links. 2020-05-31 16:21:42 +02:00
Patrick Kanzler
0a8477a888 Merge pull request #401 from Foaly/feature/cleanup
Removed a todo.
2020-05-26 21:34:34 +02:00
Patrick Kanzler
e3cf088d69 improve output of authors script 2020-05-26 21:29:16 +02:00
Patrick Kanzler
323db98318 make gen authors more robust 2020-05-26 20:43:24 +02:00
Maximilian Wagenbach
ee6eef6db3 I verified that this works. Both modes cut the paper 95% of the way through. 2020-05-26 01:38:57 +02:00
Patrick Kanzler
ef34cca463 update changelog 2020-05-12 19:32:22 +02:00
Patrick Kanzler
4680fb16b1 Merge pull request #399 from python-escpos/omit-capabilities-env-on-travis
omit env for capabilities on travis
2020-05-12 19:00:06 +02:00
Patrick Kanzler
544bb4f904 omit env for capabilities on travis 2020-05-12 18:39:38 +02:00
Patrick Kanzler
d330dd80fd Merge pull request #398 from python-escpos/390-add-spellcheck-to-sphinx
add sphinxcontrib-spelling
2020-05-12 18:29:48 +02:00
Patrick Kanzler
570661b3c8 install libenchant1c2a on travis 2020-05-12 18:17:22 +02:00
Patrick Kanzler
aa7f2773eb install spelling on travis 2020-05-12 00:06:07 +02:00
Patrick Kanzler
ff39908674 Merge branch 'master' into 390-add-spellcheck-to-sphinx 2020-05-11 23:45:34 +02:00
Patrick Kanzler
d960eea4a8 Merge pull request #397 from python-escpos/use-pkg-resources-for-capabilities-access
use pkg_resources
2020-05-11 23:44:53 +02:00
Patrick Kanzler
f7962576b4 fix tox config 2020-05-11 23:44:10 +02:00
Patrick Kanzler
f4e214ad17 add spellchecker with first conf, fix some errors 2020-05-11 23:39:56 +02:00
Patrick Kanzler
9e406efc86 add sphinxcontrib-spelling 2020-05-11 23:22:40 +02:00
Patrick Kanzler
b9e3827867 use pkg_resources
This change uses if no path for e capabilities-file is supplied a
temporary file created by pkg_resources, which should be more robust
than directly accessing the file. (This failed sometimes, for example
in zipped distributions or uncommon structures)
2020-05-11 22:54:22 +02:00
Patrick Kanzler
cbe412cfdb fix gitignore for vscode 2020-05-11 01:15:41 +02:00
Patrick Kanzler
42554d836c Merge pull request #395 from MicroJoe/python-barcode
replace viivakoodi with python-barcode, center soft barcodes by default
2020-05-10 18:42:04 +02:00
Romain Porte
8ca682e3ac soft_barcode: add new center=True option 2020-05-10 14:08:50 +02:00
Romain Porte
ab30ef4a8c test_function_softbarcode: use pytest fixture 2020-05-10 14:05:33 +02:00
Romain Porte
725f1254aa examples: software_barcode: fix too long code39 for 5890 printer 2020-05-10 14:04:25 +02:00
Romain Porte
1e313cefc6 test_function_barcode.py: remove unused imports 2020-05-10 14:03:43 +02:00
Romain Porte
b0ea9aec41 barcodes: replace viivakoodi with python-barcode
python-barcode is yet another clone of the PyPI barcode library, but
which is still developped compared to viivakoodi.

Signed-off-by: Romain Porte <microjoe@microjoe.org>
2020-05-10 13:36:16 +02:00
Patrick Kanzler
ab210b5996 Merge pull request #392 from MicroJoe/python3-only
drop Python 2.7 support
2020-05-10 13:10:04 +02:00
Patrick Kanzler
f3e1db8da2 Merge branch 'master' into python3-only 2020-05-10 12:45:03 +02:00
Patrick Kanzler
1e7b43a92d Merge pull request #394 from python-escpos/disable-codecov-status
disable codecov status
2020-05-10 12:44:50 +02:00
Patrick Kanzler
0e41709703 disable codecov status 2020-05-10 12:35:36 +02:00
Romain Porte
cb30d7a881 drop Python 2.7 support
Python 2.7 EOL is arriving on 2020-01-01: https://pythonclock.org/

This will allow us to use Python 3 only libraries, like python-barcode,
which can maintain a reduced, simpler codebase, due to only one version
to support.

Closes #371.

Signed-off-by: Romain Porte <microjoe@microjoe.org>
2020-05-10 10:55:44 +02:00
Patrick Kanzler
142fc4af71 fix readthedocs badge 2020-05-09 03:05:58 +02:00
Patrick Kanzler
8f71372bb0 remove landscape io badge 2020-05-09 03:02:35 +02:00
Patrick Kanzler
81fb7caa07 Merge branch 'master' into development 2020-05-09 02:57:46 +02:00
Patrick Kanzler
6a60b6706e apply black to setup.py 2020-05-09 02:48:24 +02:00
Patrick Kanzler
c14a414924 transition setup.py to setup.cfg 2020-05-09 02:45:34 +02:00
Patrick Kanzler
faa9414da5 move marker down to other requires 2020-05-09 02:15:13 +02:00
Patrick Kanzler
2c4552a528 mark as requiring at least python 3.5 2020-05-09 02:14:23 +02:00
Patrick Kanzler
c9953c2f56 Merge branch 'master' into development 2020-05-09 01:56:22 +02:00
Patrick Kanzler
557991d80b Merge pull request #388 from python-escpos/development
v3.0a7
2020-05-09 01:45:25 +02:00
Patrick Kanzler
fe08fc1469 drop pypy 2020-05-09 01:35:00 +02:00
Patrick Kanzler
6c27222aeb use bionic on travis 2020-05-09 01:30:38 +02:00
Patrick Kanzler
6731057456 checkout submodules on github 2020-05-09 01:25:28 +02:00
Patrick Kanzler
f0b1a89c48 fix syntax 2020-05-09 01:19:32 +02:00
Patrick Kanzler
fd7bd0710e set path to capabilities file 2020-05-09 01:11:38 +02:00
Patrick Kanzler
7ea58625e6 use tox plugin for github 2020-05-09 01:08:16 +02:00
Patrick Kanzler
baffd98a22 use focal on travis 2020-05-09 01:05:35 +02:00
Patrick Kanzler
ecbdd43dff install tox in github ci 2020-05-09 01:03:45 +02:00
Patrick Kanzler
5b6b96d2a0 update changelog 2020-05-09 00:58:54 +02:00
Patrick Kanzler
7aa20a60e3 update capabilities 2020-05-09 00:40:05 +02:00
Patrick Kanzler
95ec6d5c08 update hypothesis 2020-05-09 00:39:41 +02:00
Patrick Kanzler
18c3a5f298 add capabilities to manifest 2020-05-09 00:37:35 +02:00
Patrick Kanzler
4836dcd486 change pytest call in tox 2020-05-09 00:27:58 +02:00
Patrick Kanzler
d9d400da6d ignore vscode settings 2020-05-09 00:07:35 +02:00
Patrick Kanzler
fe2e1a6d28 add tox task for vscode 2020-05-09 00:07:02 +02:00
Patrick Kanzler
c53575a155 update tox config 2020-05-09 00:02:28 +02:00
Patrick Kanzler
cadf448c38 update travis 2020-05-08 23:53:35 +02:00
Patrick Kanzler
7c05404ac4 build docs with python 3 2020-05-08 23:51:06 +02:00
Patrick Kanzler
e1e1ccb3f2 update trove identifiers 2020-05-08 23:50:28 +02:00
Patrick Kanzler
32c56e78ea simplify branching model 2020-05-08 23:48:48 +02:00
Patrick Kanzler
4d106e8659 Create pythonpackage.yml 2020-05-08 23:34:24 +02:00
Patrick Kanzler
ddab5318cf Merge pull request #380 from mofosyne/development
Add to readme Serial usage example
2020-05-08 23:21:59 +02:00
Patrick Kanzler
9b60e2e3ab Merge pull request #381 from Foaly/feature/image-center-doc
Added some documentation and error handling to the image center flag.
2020-05-08 23:21:17 +02:00
Patrick Kanzler
34cd1ebde1 readd authors 2020-05-08 23:19:47 +02:00
Patrick Kanzler
a2db415559 remove authors 2020-05-08 23:19:31 +02:00
Patrick Kanzler
4e19b0ca51 Merge branch 'development' into feature/image-center-doc 2020-05-08 23:15:35 +02:00
Patrick Kanzler
a3660a6366 fix authors file 2020-05-08 23:14:05 +02:00
Patrick Kanzler
9fa551e6e8 Merge branch 'development' into development 2020-05-08 22:44:07 +02:00
Patrick Kanzler
ae0a049efa fix authors file 2020-05-08 22:43:47 +02:00
Patrick Kanzler
117d286371 Merge branch 'development' into development 2020-05-08 22:41:56 +02:00
Patrick Kanzler
a555a651b4 Merge pull request #385 from Bougakov/patch-1
Clarify the positions of vendor_id and product_id
2020-05-08 22:38:48 +02:00
Alexander Bougakov
e350a49cad Clarify the positions of vendor_id and product_id
An existing example uses same value, `0x1a2b`  in both `Vendor id` and `Product id` fields, which can confuse a new user.
2020-03-22 13:36:49 +03:00
Maximilian Wagenbach
f49c1dcb89 Updating AUTHORS. 2020-03-11 15:56:51 +01:00
Maximilian Wagenbach
cc67cb1c1e Added some documentation and error handling to the image center flag. 2020-03-11 15:51:16 +01:00
Brian
2ee3ff7f87 Update README.rst 2020-03-09 23:19:11 +11:00
Brian
ca45d25670 Update README.rst
Added example on serial.
2020-03-09 23:18:36 +11:00
belono
f07d5e0610 Force send job unfiltered (raw) 2019-11-10 16:35:52 +01:00
Benito López
5eeff1c16b Merge branch 'development' into development 2019-10-04 11:18:32 +02:00
Patrick Kanzler
51d1299285 update installation information
INSTALL has been outdated

fixes #357
2019-08-08 11:00:27 +02:00
Benito López
a30c28baa5 Merge branch 'development' into development 2019-07-02 18:06:20 +02:00
Patrick Kanzler
0c0e6b9b4c Merge pull request #349 from hurta2yaisel/development
Adding except NotImplementedError for 'detach_kernel_driver' in order…
2019-06-30 17:29:48 +02:00
Yaisel Hurtado
50437cc9d2 Generating AUTHORS 2019-06-28 10:10:45 -04:00
Yaisel Hurtado
7c7d401f31 Adding except NotImplementedError for 'detach_kernel_driver' in order to avoid the exception NotImplementedError: Operation not supported or unimplemented on this platform. 2019-06-27 19:28:49 -04:00
belono
2110431d4f Fix whitespace 2019-06-27 00:26:49 +02:00
belono
c0481f3a9a Add user to mailmap 2019-06-27 00:18:41 +02:00
belono
6161b46d57 Update gitignore 2019-06-24 23:19:37 +02:00
belono
c86826101d Add CUPS printer connector 2019-06-24 23:09:43 +02:00
74 changed files with 2554 additions and 1840 deletions

View File

@@ -1,10 +1,5 @@
### Contributor checklist
<!-- mark with x between the brackets -->
- [ ] I have read the CONTRIBUTING.rst
- [ ] I have tested my contribution on these devices:
* e.g. Epson TM-T88II
- [ ] My contribution is ready to be merged as is
----------
### Description ### Description
### Tested with
_If applicable, please describe with which device you have tested._

23
.github/dependabot.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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

11
.gitignore vendored
View File

@@ -6,6 +6,7 @@ $~
.idea/ .idea/
.directory .directory
.cache/ .cache/
settings.json
# temporary data # temporary data
temp temp
@@ -22,6 +23,9 @@ src/escpos/version.py
.hypothesis .hypothesis
.pytest_cache/ .pytest_cache/
# pyenv
.python-version
# testing temporary directories # testing temporary directories
test/test-cli-output/ test/test-cli-output/
@@ -29,3 +33,10 @@ test/test-cli-output/
*.swp *.swp
*.swn *.swn
*.swo *.swo
# vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@@ -13,4 +13,7 @@ csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com> Sergio Pulgarin <sergio.pulgarin@gmail.com>
reck31 <rakesh.gunduka@gmail.com> reck31 <rakesh.gunduka@gmail.com>
Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it> Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it>
Brian 'Redbeard' Harrington <redbeard@dead-city.org> 白月秋见心 <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>

View File

@@ -1,89 +0,0 @@
language: python
sudo: false
cache: pip
dist: xenial
git:
depth: 100000
addons:
apt:
packages:
- graphviz
env:
global:
- ESCPOS_CAPABILITIES_FILE=/home/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
matrix:
fast_finish: true
include:
- name: "Python 3.7 on Windows"
os: windows
language: shell
before_install:
- choco install python
- pip install tox codecov 'sphinx>=1.5.1'
env:
- TOXENV=py37
- PATH=/c/Python37:/c/Python37/Scripts:$PATH
- ESCPOS_CAPABILITIES_FILE=C:/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: "Python 3.7 on macOS"
os: osx
osx_image: xcode10.2
language: shell
env: TOXENV=py37 ESCPOS_CAPABILITIES_FILE=/Users/travis/build/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- python: 2.7
env: TOXENV=py27
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.6-dev
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
- python: 3.7-dev
env: TOXENV=py37
- python: 3.8-dev
env: TOXENV=py38
- python: nightly
env: TOXENV=py38
- python: pypy
env: TOXENV=pypy
- python: pypy3
env: TOXENV=pypy3
- python: 3.7
env: TOXENV=docs
- python: 3.7
env: TOXENV=flake8
allow_failures:
- python: 2.7
- python: 3.6-dev
- python: 3.7-dev
- python: 3.8-dev
- python: nightly
- python: pypy3
- os: windows
- os: osx
before_install:
- pip install tox codecov 'sphinx>=1.5.1'
- ./doc/generate_authors.sh --check
script:
- tox
- codecov
notifications:
email:
on_success: never
on_failure: change
deploy:
# Github deployment
- provider: releases
api_key:
secure: oiR3r5AIx9ENIRtbUKIxorRx8GMv4BxgVIZcieXbgSTN4DBZdRWdzs1Xxngu/90Xf79G0X+XGxZyXrYN7eFFNp0kUYj8kwZ1aS/dyR88scskumERWi1Hv5WUJrYGrDe7PcjNGsJ2jw0nNnRPKG87Y84aR4lQygyGBSlDcdrOBnBv0sHYJMxRvHSRkGgWpur06QIOGOk4oOipTXR/7E9cg3YQC5nvZAf2QiprwTa8IcOSFlZQPykEVRYSiAgXrgqBYcZzpX0hAGuIBv7DmPI2ORTF+t79Wbhxhnho3gGJleDv7Z96//sf1vQNCG6qOgeIc9ZY08Jm1AwXQoW0p6F1/XcEPxeyPDkXJzlojE9rjYNLCPL4gxb/LESEuUafm0U4JGMsZ6hnsBOw583yTuAdfQuJ9M+QaSyem6OVNkky3+DKAD3z0WJnl9jmGXIXigNSIxD25XhpvY+j9P0XTLBG1GT2Q+wXCIjSYJc2XnYcdgVJcLoxSWk1fKj/KPi7buAWtqwnL3tjeldpMMOZMliPUTWMM14zoGskHztt0JCkAtcotm9AQtvL8eZ2LHLDK/jyLzjv0wAwU5vzSVp14XHLZl7Q0AIoNc20p1EYGa9C/gSPd9CkrWZoG4lMOiAu3tp2PRLVrdXH3ZWSPQq4Ek5MczrUTkmB82XErNbOa8QB1Dw=
file: .tox/dist/python-escpos*.zip
file_glob: true
skip_cleanup: true
on:
tags: true
repo: python-escpos/python-escpos
branch: master
condition: $TRAVIS_PYTHON_VERSION = "3.7"

14
.vscode/settings.json vendored Normal file
View 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
View 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
}
}
]
}

10
AUTHORS
View File

@@ -1,15 +1,19 @@
Ahmed Tahri Ahmed Tahri
akeonly akeonly
Alejandro Hernández
Alexander Bougakov
Alex Debiasio Alex Debiasio
Asuki Kono Asuki Kono
belono belono
Brian 'Redbeard' Harrington B. Howell
Brian
Christoph Heuel Christoph Heuel
Cody (Quantified Code Bot) Cody (Quantified Code Bot)
csoft2k csoft2k
Curtis // mashedkeyboard Curtis // mashedkeyboard
Davis Goglin Davis Goglin
Dean Rispin Dean Rispin
dependabot[bot]
Dmytro Katyukha Dmytro Katyukha
Gerard Marull-Paretas Gerard Marull-Paretas
Hark Hark
@@ -20,6 +24,8 @@ Kristi
ldos ldos
Lucy Linder Lucy Linder
Manuel F Martinez Manuel F Martinez
Mathieu Poussin
Maximilian Wagenbach
Michael Billington Michael Billington
Michael Elsdörfer Michael Elsdörfer
mrwunderbar666 mrwunderbar666
@@ -37,4 +43,6 @@ Sergio Pulgarin
Stephan Sokolow Stephan Sokolow
Thijs Triemstra Thijs Triemstra
Thomas van den Berg Thomas van den Berg
Yaisel Hurtado
ysuolmai ysuolmai
白月秋见心

View File

@@ -1,10 +1,79 @@
********* *********
Changelog 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..." 2019-06-19 - Version 3.0a6 - "Mistake not..."
--------------------------------------------- ---------------------------------------------
This release is the seventh alpha release of the new version 3.0. This release is the seventh alpha release of the new version 3.0.
Please be aware the the API is subject to change until v3.0 is Please be aware that the API is subject to change until v3.0 is
released. released.
changes changes

View File

@@ -9,7 +9,7 @@ In order to reduce the amount of work for everyone please try to adhere to good
The pull requests and issues will be prefilled with templates. Please fill in your information where applicable. The pull requests and issues will be prefilled with templates. Please fill in your information where applicable.
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as This project uses `semantic versioning <https://semver.org/>`_ and tries to adhere to the proposed rules as
well as possible. well as possible.
Author-list Author-list
@@ -27,25 +27,6 @@ Style-Guide
When writing code please try to stick to these rules. When writing code please try to stick to these rules.
Python 2 and 3
^^^^^^^^^^^^^^
We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3.
In order to ensure that we do not miss any accidental degradation, please add these imports to the top
of every file of code:
.. code-block:: Python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
Furthermore please be aware of the differences between Python 2 and 3. For
example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful.
Special care has to be taken when dealing with strings and byte-strings. Please note
that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings.
Often you can achieve compatibility quite easily with a tool from the `six`-package.
PEP8 PEP8
^^^^ ^^^^
The entire codebase adheres to the rules of PEP8. The entire codebase adheres to the rules of PEP8.
@@ -56,12 +37,9 @@ Apart from that the travis-log and the check by Landscape will provide you with
GIT GIT
^^^ ^^^
The master-branch contains code that has been released to PyPi. A release is marked with a tag 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 in the development-branch. 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`.
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`.
Name your branch to indicate what you are trying to achieve. Good branch names might
be `improve/text-handling`, `feature/enable-color-printing`.
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a 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 git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
@@ -84,7 +62,7 @@ You can copy the structure from other testcases. Please remember to adapt the do
Further reading Further reading
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
For further best practices and hints on contributing please see the For further best practices and hints on contributing please see the
`contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide `contribution-guide <https://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
and the linked one, please stick to this text. 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. Aside from that feel free to create an issue or write an email if anything is unclear.

23
INSTALL
View File

@@ -1,23 +1,10 @@
python-escpos 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 pip install python-escpos --user # add --pre if you want to install pre-releases
# sudo python setup.py install ```
On Linux, ensure you belongs to the proper group so you can have access to the printer. For more information please read the documentation at https://python-escpos.readthedocs.io/en/latest/user/installation.html
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>

View File

@@ -4,6 +4,7 @@ include LICENSE
include INSTALL include INSTALL
include tox.ini include tox.ini
include capabilities-data/dist/capabilities.json include capabilities-data/dist/capabilities.json
include src/escpos/capabilities.json
recursive-include doc *.bat recursive-include doc *.bat
recursive-include doc *.ico recursive-include doc *.ico
recursive-include doc *.py recursive-include doc *.py

View File

@@ -6,16 +6,12 @@ python-escpos - Python library to manipulate ESC/POS Printers
:target: https://travis-ci.org/python-escpos/python-escpos :target: https://travis-ci.org/python-escpos/python-escpos
:alt: Continous Integration :alt: Continous Integration
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
:target: https://landscape.io/github/python-escpos/python-escpos/master
:alt: Code Health
.. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master .. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master
:target: https://codecov.io/github/python-escpos/python-escpos?branch=master :target: https://codecov.io/github/python-escpos/python-escpos?branch=master
:alt: Code Coverage :alt: Code Coverage
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable .. image:: https://readthedocs.org/projects/python-escpos/badge/?version=latest
:target: http://python-escpos.readthedocs.io/en/latest/?badge=stable :target: https://python-escpos.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status :alt: Documentation Status
@@ -47,7 +43,7 @@ This library makes use of:
* `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing * `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing
* `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes * `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes
* `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers * `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers
* `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes * `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ for the generation of barcodes
Documentation and Usage Documentation and Usage
----------------------- -----------------------
@@ -62,7 +58,7 @@ The basic usage is:
p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III") p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
p.text("Hello World\n") p.text("Hello World\n")
p.image("logo.gif") p.image("logo.gif")
p.barcode('4006381333931', 'EAN13', 64, 2, '', '') p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
p.cut() p.cut()
@@ -71,19 +67,38 @@ Another example based on the Network printer class:
.. code:: python .. code:: python
from escpos.printer import Network from escpos.printer import Network
kitchen = Network("192.168.1.100") #Printer IP Address kitchen = Network("192.168.1.100") #Printer IP Address
kitchen.text("Hello World\n") kitchen.text("Hello World\n")
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '') kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '')
kitchen.cut() 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>`_. The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
Contributing Contributing
------------ ------------
This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information. This project is open for any contribution! Please see `CONTRIBUTING.rst <https://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information.
Disclaimer Disclaimer
@@ -91,5 +106,3 @@ Disclaimer
None of the vendors cited in this project agree or endorse any of the patterns or implementations. 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. Its names are used only to maintain context.

View File

@@ -3,10 +3,9 @@ codecov:
coverage: coverage:
status: status:
project: project: off
default: # status context patch: off
target: auto changes: off
threshold: "1%"
range: "60...100" range: "60...100"
comment: off comment: off

View File

@@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build # User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/)
endif endif
# Internal variables. # Internal variables.
@@ -19,7 +19,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others # the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext spelling
help: help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@@ -45,6 +45,7 @@ help:
@echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity" @echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " spelling to run the spellchecker"
clean: clean:
rm -rf $(BUILDDIR)/* rm -rf $(BUILDDIR)/*
@@ -175,3 +176,8 @@ pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo @echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
spelling:
$(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
@echo
@echo "Spellchecker finished."

View File

@@ -14,111 +14,106 @@
import sys import sys
import os import os
on_rtd = os.getenv('READTHEDOCS') == 'True'
if on_rtd: from importlib.metadata import version as imp_version
import escpos
else: on_rtd = os.getenv("READTHEDOCS") == "True"
from setuptools_scm import get_version
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../src')) sys.path.insert(0, os.path.abspath("../src"))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..')) root = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.doctest', "sphinx.ext.doctest",
'sphinx.ext.todo', "sphinx.ext.todo",
'sphinx.ext.coverage', "sphinx.ext.coverage",
'sphinx.ext.viewcode', "sphinx.ext.viewcode",
'sphinx.ext.todo', "sphinx.ext.todo",
'sphinx.ext.graphviz', "sphinx.ext.graphviz",
'sphinx.ext.inheritance_diagram', "sphinx.ext.inheritance_diagram",
"sphinxcontrib.spelling",
] ]
# supress warnings for external images # supress warnings for external images
suppress_warnings = [ suppress_warnings = [
'image.nonlocal_uri', "image.nonlocal_uri",
] ]
# enable todos # enable todos
todo_include_todos = True todo_include_todos = True
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'python-escpos' project = "python-escpos"
copyright = u'2016, Manuel F Martinez and others' copyright = "2016, Manuel F Martinez and others"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
if on_rtd: release = imp_version("python-escpos")
# The full version, including alpha/beta/rc tags.
release = escpos.__version__
else:
# locally setuptools_scm should work
release = get_version(root=root)
# The short X.Y version. # The short X.Y version.
version = '.'.join(release.split('.')[:2]) # The short X.Y version. version = ".".join(release.split(".")[:2])
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y' # today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build'] exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all # The reST default role (used for this markup: `text`) to use for all
# documents. # documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True # add_function_parentheses = True
# If true, the current module name will be prepended to all description # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] # modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents. # If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False # keep_warnings = False
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------
@@ -126,135 +121,139 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
if on_rtd: if on_rtd:
html_theme = 'default' html_theme = "default"
else: else:
try: try:
import sphinx_rtd_theme import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError: except ImportError:
print("no sphinx_rtd_theme found, switching to nature") print("no sphinx_rtd_theme found, switching to nature")
html_theme = 'default' html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = [] # html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None # html_short_title = None
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
#html_logo = None # html_logo = None
# The name of an image file (within the static path) to use as favicon of the # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
html_favicon = 'pyescpos.ico' html_favicon = "pyescpos.ico"
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation. # directly to the root of the documentation.
#html_extra_path = [] # html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y' # html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} # html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # If false, no index is generated.
#html_use_index = True # html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
#html_split_index = False # html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True # html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True # html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True # html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will # 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 # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # base URL from which the finished HTML is served.
#html_use_opensearch = '' # html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml"). # This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'python-escposdoc' htmlhelp_basename = "python-escposdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt',
#'pointsize': '10pt', # Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
('index', 'python-escpos.tex', u'python-escpos Documentation', (
u'Manuel F Martinez and others', 'manual'), "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 name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#latex_show_urls = False # latex_show_urls = False
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output --------------------------------------- # -- Options for manual page output ---------------------------------------
@@ -262,12 +261,17 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'python-escpos', u'python-escpos Documentation', (
[u'Manuel F Martinez and others'], 1) "index",
"python-escpos",
"python-escpos Documentation",
["Manuel F Martinez and others"],
1,
)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ------------------------------------------- # -- Options for Texinfo output -------------------------------------------
@@ -276,19 +280,31 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'python-escpos', u'python-escpos Documentation', (
u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.', "index",
'Miscellaneous'), "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. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote' # texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu. # If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False # texinfo_no_detailmenu = False
# spellchecker
spelling_ignore_pypi_package_names = True
spelling_ignore_wiki_words = True
spelling_ignore_python_builtins = True
spelling_ignore_importable_modules = True

View File

@@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f) 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" AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
@@ -12,7 +13,9 @@ if [ "$#" -eq 1 ]
echo "\nNew authorsfile:\n" echo "\nNew authorsfile:\n"
cat $TEMPAUTHORSFILE cat $TEMPAUTHORSFILE
echo "\nUsing diff on files...\n" echo "\nUsing diff on files...\n"
diff -q --from-file $AUTHORSFILE $TEMPAUTHORSFILE diff --suppress-common-lines -b --from-file $AUTHORSFILE $TEMPAUTHORSFILE
echo "Authors with mail addresses:\n"
echo "$GENLIST_W_MAIL"
else else
echo "$GENLIST">$AUTHORSFILE echo "$GENLIST">$AUTHORSFILE
fi fi

View File

@@ -56,7 +56,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH. echo.may add the Sphinx directory to PATH.
echo. echo.
echo.If you don't have Sphinx installed, grab it from echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/ echo.https://sphinx-doc.org/
exit /b 1 exit /b 1
) )

View File

@@ -3,6 +3,9 @@ Pillow>=2.0
qrcode>=4.0 qrcode>=4.0
pyserial pyserial
sphinx-rtd-theme sphinx-rtd-theme
setuptools
setuptools-scm setuptools-scm
docutils>=0.12 docutils>=0.12
viivakoodi sphinxcontrib-spelling>=7.2.0
python-barcode>=0.11.0,<1
importlib-metadata

View File

@@ -0,0 +1,7 @@
Raspbian
ESC
POS
Escpos
Escpos
baudrate
lsusb

View File

@@ -1,9 +1,9 @@
******** ********
Printers Printers
******** ********
:Last Reviewed: 2017-01-25 :Last Reviewed: 2022-11-25
As of now there are 5 different type of printer implementations. As of now there are 7 different type of printer implementations.
USB USB
--- ---
@@ -75,3 +75,26 @@ all of the "output" as raw ESC/POS in a string and returns that.
:member-order: bysource :member-order: bysource
:noindex: :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:

View File

@@ -48,14 +48,16 @@ to have and the second yields the "Output Endpoint" address.
By default the "Interface" number is "0" and the "Output Endpoint" 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 address is "0x01". If you have other values then you can define them on
your instance. So, assuming that we have another printer where in\_ep is your instance. So, assuming that we have another printer, CT-S2000,
on 0x81 and out\_ep=0x02, then the printer definition should look like: 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** **Generic USB Printer initialization**
:: ::
p = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02) p = printer.Usb(0x2730, 0x0fff, 0, 0x81, 0x02)
Network printer Network printer
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
@@ -82,7 +84,7 @@ to.
:: ::
p = printer.Serial("/dev/tty0") p = printer.Serial("/dev/tty0")
# on a Windows OS serial devices are typically accessible as COM # on a Windows OS serial devices are typically accessible as COM
p = printer.Serial("COM1") p = printer.Serial("COM1")
@@ -119,10 +121,15 @@ on a USB interface.
# Print QR Code # Print QR Code
p.qr("You can readme from your smartphone") p.qr("You can readme from your smartphone")
# Print barcode # Print barcode
p.barcode('4006381333931','EAN13',64,2,'','') p.barcode('1324354657687','EAN13',64,2,'','')
# Cut paper # Cut paper
p.cut() 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 Configuration File
------------------ ------------------
@@ -161,7 +168,7 @@ The printer section
The ``printer`` configuration section defines a default printer to create. The ``printer`` configuration section defines a default printer to create.
The only required paramter is ``type``. The value of this has to be one of the The only required parameter is ``type``. The value of this has to be one of the
printers defined in :doc:`/user/printers`. printers defined in :doc:`/user/printers`.
The rest of the given parameters will be passed on to the initialization of the printer class. The rest of the given parameters will be passed on to the initialization of the printer class.
@@ -192,12 +199,12 @@ An USB-printer could be defined by::
Printing text right Printing text right
------------------- -------------------
Python-escpos is designed to accept unicode. So make sure that you use ``u'strings'`` or import ``unicode_literals``
from ``__future__`` if you are on Python 2. On Python 3 you should be fine. Python-escpos is designed to accept unicode.
For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess 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 the right codepage and then send the encoded data to the printer. If this feature does not work, please try to
isolate the error and then create an issue on the Github project page. isolate the error and then create an issue on the GitHub project page.
If you want or need to you can manually set the codepage. For this please use the ``charcode()``-function. You can set 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. any key-value that is in ``CHARCODE``. If something is wrong, an ``CharCodeError`` will be raised.
@@ -287,4 +294,18 @@ This way you could also store the code in a file and print it later.
You could then for example print the code from another process than your main-program and thus reduce the waiting time. 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.) (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>`_.

View File

@@ -5,7 +5,7 @@ from escpos.printer import Usb
p = Usb(0x0416, 0x5011, profile="POS-5890") p = Usb(0x0416, 0x5011, profile="POS-5890")
# Print software and then hardware barcode with the same content # Print software and then hardware barcode with the same content
p.soft_barcode('code39', '123456') p.soft_barcode("code39", "123456")
p.text('\n') p.text("\n")
p.text('\n') p.text("\n")
p.barcode('123456', 'CODE39') p.barcode("123456", "CODE39")

View File

@@ -1,21 +1,28 @@
"""Prints code page tables. """Prints code page tables.
""" """
from __future__ import print_function
import six import six
import sys import sys
from escpos import printer from escpos import printer
from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT from escpos.constants import (
CODEPAGE_CHANGE,
ESC,
CTL_LF,
CTL_FF,
CTL_CR,
CTL_HT,
CTL_VT,
)
def main(): def main():
dummy = printer.Dummy() dummy = printer.Dummy()
dummy.hw('init') dummy.hw("init")
for codepage in sys.argv[1:] or ['USA']: for codepage in sys.argv[1:] or ["USA"]:
dummy.set(height=2, width=2) dummy.set(height=2, width=2)
dummy._raw(codepage + "\n\n\n") dummy._raw(codepage + "\n\n\n")
print_codepage(dummy, codepage) print_codepage(dummy, codepage)
@@ -37,14 +44,14 @@ def print_codepage(printer, codepage):
sep = "" sep = ""
# Table header # Table header
printer.set(font='b') printer.set(font="b")
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
printer.set() printer.set()
# The table # The table
for x in range(0, 16): for x in range(0, 16):
# First column # First column
printer.set(font='b') printer.set(font="b")
printer._raw("{} ".format(hex(x)[2:])) printer._raw("{} ".format(hex(x)[2:]))
printer.set() printer.set()
@@ -52,12 +59,12 @@ def print_codepage(printer, codepage):
byte = six.int2byte(x * 16 + y) byte = six.int2byte(x * 16 + y)
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT): if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
byte = ' ' byte = " "
printer._raw(byte) printer._raw(byte)
printer._raw(sep) printer._raw(sep)
printer._raw('\n') printer._raw("\n")
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -1,10 +1,10 @@
# Climacons by Adam Whitcroft # Climacons by Adam Whitcroft
75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](http://www.twitter.com/#!/adamwhitcroft). 75 climatically categorised pictographs for web and UI design by [@adamwhitcroft](https://www.twitter.com/#!/adamwhitcroft).
Visit the [Climacons](http://adamwhitcroft.com/climacons/) website for more information. Visit the [Climacons](https://adamwhitcroft.com/climacons/) website for more information.
Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft) Visit [Adam Whitcroft on GitHub](https://github.com/AdamWhitcroft)
## License ## License
You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons. You are free to use any of the Climacons Icons (the "icons") in any personal or commercial work without obligation of payment (monetary or otherwise) or attribution, however a credit for the work would be appreciated. **Do not** redistribute or sell and **do not** claim creative credit. Intellectual property rights are not transferred with the download of the icons.

View File

@@ -7,7 +7,7 @@ def usage():
print("usage: qr_code.py <content>") print("usage: qr_code.py <content>")
if __name__ == '__main__': if __name__ == "__main__":
if len(sys.argv) != 2: if len(sys.argv) != 2:
usage() usage()
sys.exit(1) sys.exit(1)

View File

@@ -5,5 +5,5 @@ from escpos.printer import Usb
p = Usb(0x0416, 0x5011, profile="POS-5890") p = Usb(0x0416, 0x5011, profile="POS-5890")
# Some software barcodes # Some software barcodes
p.soft_barcode('code128', 'Hello') p.soft_barcode("code128", "Hello")
p.soft_barcode('code39', '123456') p.soft_barcode("code39", "1234")

View File

@@ -9,11 +9,10 @@
# Written by Adafruit Industries. MIT license. # Written by Adafruit Industries. MIT license.
# Adapted and enhanced for escpos library by MrWunderbar666 # Adapted and enhanced for escpos library by MrWunderbar666
# Icons taken from http://adamwhitcroft.com/climacons/ # Icons taken from https://adamwhitcroft.com/climacons/
# Check out his github: https://github.com/AdamWhitcroft/climacons # Check out his github: https://github.com/AdamWhitcroft/climacons
from __future__ import print_function
from datetime import datetime from datetime import datetime
import calendar import calendar
import urllib import urllib
@@ -34,93 +33,95 @@ printer = Usb(0x0416, 0x5011, profile="POS-5890")
# Technically you can use any other weather service, of course :) # Technically you can use any other weather service, of course :)
API_KEY = "YOUR API KEY" API_KEY = "YOUR API KEY"
LAT = "22.345490" # Your Location LAT = "22.345490" # Your Location
LONG = "114.189945" # Your Location LONG = "114.189945" # Your Location
def forecast_icon(idx): def forecast_icon(idx):
icon = data['daily']['data'][idx]['icon'] icon = data["daily"]["data"][idx]["icon"]
image = GRAPHICS_PATH + icon + ".png" image = GRAPHICS_PATH + icon + ".png"
return image return image
# Dumps one forecast line to the printer # Dumps one forecast line to the printer
def forecast(idx): def forecast(idx):
date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time'])) date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"]))
day = calendar.day_name[date.weekday()] day = calendar.day_name[date.weekday()]
lo = data['daily']['data'][idx]['temperatureMin'] lo = data["daily"]["data"][idx]["temperatureMin"]
hi = data['daily']['data'][idx]['temperatureMax'] hi = data["daily"]["data"][idx]["temperatureMax"]
cond = data['daily']['data'][idx]['summary'] cond = data["daily"]["data"][idx]["summary"]
print(date) print(date)
print(day) print(day)
print(lo) print(lo)
print(hi) print(hi)
print(cond) print(cond)
time.sleep(1) time.sleep(1)
printer.set( printer.set(font="a", height=2, align="left", bold=False, double_height=False)
font='a', printer.text(day + " \n ")
height=2, time.sleep(5) # Sleep to prevent printer buffer overflow
align='left', printer.text("\n")
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.image(forecast_icon(idx))
printer.text('low ' + str(lo)) printer.text("low " + str(lo))
printer.text(deg) printer.text(deg)
printer.text('\n') printer.text("\n")
printer.text(' high ' + str(hi)) printer.text(" high " + str(hi))
printer.text(deg) printer.text(deg)
printer.text('\n') printer.text("\n")
# take care of pesky unicode dash # take care of pesky unicode dash
printer.text(cond.replace(u'\u2013', '-').encode('utf-8')) printer.text(cond.replace("\u2013", "-").encode("utf-8"))
printer.text('\n \n') printer.text("\n \n")
def icon(): def icon():
icon = data['currently']['icon'] icon = data["currently"]["icon"]
image = GRAPHICS_PATH + icon + ".png" image = GRAPHICS_PATH + icon + ".png"
return image return image
deg = ' C' # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol deg = " C" # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
# if you want Fahrenheit change units= to 'us' # if you want Fahrenheit change units= to 'us'
url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \ url = (
"?exclude=[alerts,minutely,hourly,flags]&units=si" # change last bit to 'us' for Fahrenheit "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) response = urllib.urlopen(url)
data = json.loads(response.read()) data = json.loads(response.read())
printer.print_and_feed(n=1) printer.print_and_feed(n=1)
printer.control("LF") printer.control("LF")
printer.set(font='a', height=2, align='center', bold=True, double_height=True) printer.set(font="a", height=2, align="center", bold=True, double_height=True)
printer.text("Weather Forecast") printer.text("Weather Forecast")
printer.text("\n") printer.text("\n")
printer.set(align='center') printer.set(align="center")
# Print current conditions # Print current conditions
printer.set(font='a', height=2, align='center', bold=True, double_height=False) printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text('Current conditions: \n') printer.text("Current conditions: \n")
printer.image(icon()) printer.image(icon())
printer.text("\n") printer.text("\n")
printer.set(font='a', height=2, align='left', bold=False, double_height=False) printer.set(font="a", height=2, align="left", bold=False, double_height=False)
temp = data['currently']['temperature'] temp = data["currently"]["temperature"]
cond = data['currently']['summary'] cond = data["currently"]["summary"]
printer.text(temp) printer.text(temp)
printer.text(' ') printer.text(" ")
printer.text(deg) printer.text(deg)
printer.text(' ') printer.text(" ")
printer.text('\n') printer.text("\n")
printer.text('Sky: ' + cond) printer.text("Sky: " + cond)
printer.text('\n') printer.text("\n")
printer.text('\n') printer.text("\n")
# Print forecast # Print forecast
printer.set(font='a', height=2, align='center', bold=True, double_height=False) printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text('Forecast: \n') printer.text("Forecast: \n")
forecast(0) forecast(0)
forecast(1) forecast(1)
printer.cut() printer.cut()

3
pyproject.toml Normal file
View File

@@ -0,0 +1,3 @@
[tool.black]
extend-exclude = 'capabilities-data'

View File

@@ -3,5 +3,5 @@ formats:
- epub - epub
requirements_file: doc/requirements.txt requirements_file: doc/requirements.txt
python: python:
version: 2 version: 3
setup_py_install: true setup_py_install: true

View File

@@ -1,11 +1,71 @@
[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] [nosetests]
verbosity=3 verbosity=3
with-doctest=1 with-doctest=1
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python 3.
universal=1
[flake8] [flake8]
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120 max-line-length = 120

View File

@@ -22,90 +22,19 @@ setuptools_scm_template = """\
# coding: utf-8 # coding: utf-8
# file generated by setuptools_scm # file generated by setuptools_scm
# don't change, don't track in version control # don't change, don't track in version control
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
version = '{version}' version = '{version}'
""" """
setup( setup(
name='python-escpos',
use_scm_version={ use_scm_version={
'write_to': 'src/escpos/version.py', "write_to": "src/escpos/version.py",
'write_to_template': setuptools_scm_template, "write_to_template": setuptools_scm_template,
}, },
url='https://github.com/python-escpos/python-escpos', platforms="any",
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
description='Python library to manipulate ESC/POS Printers',
license='MIT',
long_description=read('README.rst'),
author='Manuel F Martinez and others',
author_email='manpaz@bashlinux.com',
maintainer='Patrick Kanzler',
maintainer_email='dev@pkanzler.de',
keywords=[
'ESC/POS',
'thermoprinter',
'voucher printer',
'printing',
'receipt,',
],
platforms='any',
package_dir={"": "src"}, package_dir={"": "src"},
packages=find_packages(where="src", exclude=["tests", "tests.*"]), packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={'escpos': ['capabilities.json']}, package_data={"escpos": ["capabilities.json"]},
include_package_data=True, entry_points={"console_scripts": ["python-escpos = escpos.cli:main"]},
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.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Office/Business :: Financial :: Point-Of-Sale',
],
install_requires=[
'pyusb>=1.0.0',
'Pillow>=2.0',
'qrcode>=4.0',
'pyserial',
'six',
'appdirs',
'PyYAML',
'argparse',
'argcomplete',
'future',
'viivakoodi>=0.8'
],
setup_requires=[
'setuptools_scm',
],
tests_require=[
'jaconv',
'tox',
'pytest!=3.2.0,!=3.3.0',
'pytest-cov',
'pytest-mock',
'nose',
'scripttest',
'mock',
'hypothesis!=3.56.9,<4',
'flake8'
],
entry_points={
'console_scripts': [
'python-escpos = escpos.cli:main'
]
},
) )

View File

@@ -2,10 +2,6 @@
""" """
python-escpos enables you to manipulate escpos-printers python-escpos enables you to manipulate escpos-printers
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
__all__ = ["constants", "escpos", "exceptions", "printer"] __all__ = ["constants", "escpos", "exceptions", "printer"]
@@ -13,7 +9,7 @@ try:
from .version import version as __version__ # noqa from .version import version as __version__ # noqa
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
raise ImportError( raise ImportError(
'Failed to find (autogenerated) version.py. ' "Failed to find (autogenerated) version.py. "
'This might be because you are installing from GitHub\'s tarballs, ' "This might be because you are installing from GitHub's tarballs, "
'use the PyPI ones.' "use the PyPI ones."
) )

View File

@@ -1,5 +1,6 @@
import re import re
from os import environ, path from os import environ, path
import pkg_resources
import pickle import pickle
import logging import logging
import time import time
@@ -10,116 +11,120 @@ import yaml
from tempfile import gettempdir from tempfile import gettempdir
import platform import platform
from typing import Any, Dict
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir()) pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir())
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version())) 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( capabilities_path = environ.get(
'ESCPOS_CAPABILITIES_FILE', "ESCPOS_CAPABILITIES_FILE",
path.join(path.dirname(__file__), 'capabilities.json')) pkg_resources.resource_filename(__name__, "capabilities.json"),
)
# Load external printer database # Load external printer database
t0 = time.time() t0 = time.time()
logger.debug('Using capabilities from file: %s', capabilities_path) logger.debug("Using capabilities from file: %s", capabilities_path)
if path.exists(pickle_path): if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path): if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
logger.debug('Found a more recent capabilities file') logger.debug("Found a more recent capabilities file")
full_load = True full_load = True
else: else:
full_load = False full_load = False
logger.debug('Loading capabilities from pickle in %s', pickle_path) logger.debug("Loading capabilities from pickle in %s", pickle_path)
with open(pickle_path, 'rb') as cf: with open(pickle_path, "rb") as cf:
CAPABILITIES = pickle.load(cf) CAPABILITIES = pickle.load(cf)
else: else:
logger.debug('Capabilities pickle file not found: %s', pickle_path) logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True full_load = True
if full_load: if full_load:
logger.debug('Loading and pickling capabilities') logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp: with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp) CAPABILITIES = yaml.safe_load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2) pickle.dump(CAPABILITIES, pp, protocol=2)
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0) logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
PROFILES = CAPABILITIES['profiles'] PROFILES: Dict[str, Any] = CAPABILITIES["profiles"]
class NotSupported(Exception): class NotSupported(Exception):
"""Raised if a requested feature is not supported by the """Raised if a requested feature is not supported by the
printer profile. printer profile.
""" """
pass pass
BARCODE_B = 'barcodeB' BARCODE_B = "barcodeB"
class BaseProfile(object): class BaseProfile(object):
"""This respresents a printer profile. """This represents a printer profile.
A printer profile knows about the number of columns, supported A printer profile knows about the number of columns, supported
features, colors and more. features, colors and more.
""" """
profile_data = {} profile_data: Dict[str, Any] = {}
def __getattr__(self, name): def __getattr__(self, name):
return self.profile_data[name] return self.profile_data[name]
def get_font(self, font): def get_font(self, font) -> int:
"""Return the escpos index for `font`. Makes sure that """Return the escpos index for `font`. Makes sure that
the requested `font` is valid. the requested `font` is valid.
""" """
font = {'a': 0, 'b': 1}.get(font, font) font = {"a": 0, "b": 1}.get(font, font)
if not six.text_type(font) in self.fonts: if not six.text_type(font) in self.fonts:
raise NotSupported( raise NotSupported(
'"{}" is not a valid font in the current profile'.format(font)) '"{}" is not a valid font in the current profile'.format(font)
)
return font return font
def get_columns(self, font): def get_columns(self, font):
""" Return the number of columns for the given font. """Return the number of columns for the given font."""
"""
font = self.get_font(font) font = self.get_font(font)
return self.fonts[six.text_type(font)]['columns'] return self.fonts[six.text_type(font)]["columns"]
def supports(self, feature): def supports(self, feature):
"""Return true/false for the given feature. """Return true/false for the given feature."""
"""
return self.features.get(feature) return self.features.get(feature)
def get_code_pages(self): def get_code_pages(self):
"""Return the support code pages as a ``{name: index}`` dict. """Return the support code pages as a ``{name: index}`` dict."""
"""
return {v: k for k, v in self.codePages.items()} return {v: k for k, v in self.codePages.items()}
def get_profile(name=None, **kwargs): def get_profile(name: str = None, **kwargs):
"""Get the profile by name; if no name is given, return the """Get the profile by name; if no name is given, return the
default profile. default profile.
""" """
if isinstance(name, Profile): if isinstance(name, Profile):
return name return name
clazz = get_profile_class(name or 'default') clazz = get_profile_class(name or "default")
return clazz(**kwargs) return clazz(**kwargs)
CLASS_CACHE = {} CLASS_CACHE = {}
def get_profile_class(name): def get_profile_class(name: str):
"""For the given profile name, load the data from the external """For the given profile name, load the data from the external
database, then generate dynamically a class. database, then generate dynamically a class.
""" """
if name not in CLASS_CACHE: if name not in CLASS_CACHE:
profile_data = PROFILES[name] profile_data = PROFILES[name]
profile_name = clean(name) profile_name = clean(name)
class_name = '{}{}Profile'.format( class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
profile_name[0].upper(), profile_name[1:]) new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
CLASS_CACHE[name] = new_class CLASS_CACHE[name] = new_class
return CLASS_CACHE[name] return CLASS_CACHE[name]
@@ -127,13 +132,13 @@ def get_profile_class(name):
def clean(s): def clean(s):
# Remove invalid characters # Remove invalid characters
s = re.sub('[^0-9a-zA-Z_]', '', s) s = re.sub("[^0-9a-zA-Z_]", "", s)
# Remove leading characters until we find a letter or underscore # Remove leading characters until we find a letter or underscore
s = re.sub('^[^a-zA-Z_]+', '', s) s = re.sub("^[^a-zA-Z_]+", "", s)
return str(s) return str(s)
class Profile(get_profile_class('default')): class Profile(get_profile_class("default")):
""" """
For users, who want to provide their profile For users, who want to provide their profile
""" """

View File

@@ -3,18 +3,15 @@
""" CLI """ CLI
This module acts as a command line interface for python-escpos. It mirrors This module acts as a command line interface for python-escpos. It mirrors
closely the available ESCPOS commands while adding a couple extra ones for convience. closely the available ESCPOS commands while adding a couple extra ones for convenience.
It requires you to have a configuration file. See documentation for details. It requires you to have a configuration file. See documentation for details.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse import argparse
try: try:
import argcomplete import argcomplete
except ImportError: except ImportError:
@@ -28,14 +25,14 @@ from . import version
# Must be defined before it's used in DEMO_FUNCTIONS # Must be defined before it's used in DEMO_FUNCTIONS
def str_to_bool(string): def str_to_bool(string):
""" Used as a type in argparse so that we get back a proper """Used as a type in argparse so that we get back a proper
bool instead of always True bool instead of always True
""" """
return string.lower() in ('y', 'yes', '1', 'true') return string.lower() in ("y", "yes", "1", "true")
# A list of functions that work better with a newline to be sent after them. # A list of functions that work better with a newline to be sent after them.
REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text') REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
# Used in demo method # Used in demo method
@@ -43,40 +40,46 @@ REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
# manual translation is done in the case of barcodes_a -> barcode. # manual translation is done in the case of barcodes_a -> barcode.
# Value: A list of dictionaries to pass to the escpos function as arguments. # Value: A list of dictionaries to pass to the escpos function as arguments.
DEMO_FUNCTIONS = { DEMO_FUNCTIONS = {
'text': [ "text": [
{'txt': 'Hello, World!\n', } {
"txt": "Hello, World!\n",
}
], ],
'qr': [ "qr": [
{'content': 'This tests a QR code'}, {"content": "This tests a QR code"},
{'content': 'https://en.wikipedia.org/'} {"content": "https://en.wikipedia.org/"},
], ],
'barcodes_a': [ "barcodes_a": [
{'bc': 'UPC-A', 'code': '13243546576'}, {"bc": "UPC-A", "code": "13243546576"},
{'bc': 'UPC-E', 'code': '132435'}, {"bc": "UPC-E", "code": "132435"},
{'bc': 'EAN13', 'code': '4006381333931'}, {"bc": "EAN13", "code": "1324354657687"},
{'bc': 'EAN8', 'code': '1324354'}, {"bc": "EAN8", "code": "1324354"},
{'bc': 'CODE39', 'code': 'TEST'}, {"bc": "CODE39", "code": "TEST"},
{'bc': 'ITF', 'code': '55867492279103'}, {"bc": "ITF", "code": "55867492279103"},
{'bc': 'NW7', 'code': 'A00000000A'}, {"bc": "NW7", "code": "A00000000A"},
], ],
'barcodes_b': [ "barcodes_b": [
{'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'}, {"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
{'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'}, {"bc": "UPC-E", "code": "132435", "function_type": "B"},
{'bc': 'EAN13', 'code': '4006381333931', 'function_type': 'B'}, {"bc": "EAN13", "code": "1324354657687", "function_type": "B"},
{'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'}, {"bc": "EAN8", "code": "1324354", "function_type": "B"},
{'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'}, {"bc": "CODE39", "code": "TEST", "function_type": "B"},
{'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'}, {"bc": "ITF", "code": "55867492279103", "function_type": "B"},
{'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'}, {"bc": "NW7", "code": "A00000000A", "function_type": "B"},
{'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'}, {"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
{'bc': 'CODE93', 'code': '4006381333931', 'function_type': 'B'}, {"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
{'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'}, {"bc": "CODE128A", "code": "TEST", "function_type": "B"},
{'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'}, {"bc": "CODE128B", "code": "TEST", "function_type": "B"},
{'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'}, {"bc": "CODE128C", "code": "TEST", "function_type": "B"},
{'bc': 'GS1-128', 'code': '00123456780000000001', '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 Omnidirectional",
{'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'}, "code": "0000000000000",
{'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'}, "function_type": "B",
},
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
], ],
} }
@@ -88,356 +91,355 @@ DEMO_FUNCTIONS = {
# arguments: A list of dicts of args for subparser.add_argument # arguments: A list of dicts of args for subparser.add_argument
ESCPOS_COMMANDS = [ ESCPOS_COMMANDS = [
{ {
'parser': { "parser": {
'name': 'qr', "name": "qr",
'help': 'Print a QR code', "help": "Print a QR code",
}, },
'defaults': { "defaults": {
'func': 'qr', "func": "qr",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--content',), "option_strings": ("--content",),
'help': 'Text to print as a qr code', "help": "Text to print as a qr code",
'required': True, "required": True,
}, },
{ {
'option_strings': ('--size',), "option_strings": ("--size",),
'help': 'QR code size (1-16) [default:3]', "help": "QR code size (1-16) [default:3]",
'required': False, "required": False,
'type': int, "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': { "parser": {
'name': 'barcode', "name": "block_text",
'help': 'Print a barcode', "help": "Print wrapped text",
}, },
'defaults': { "defaults": {
'func': 'barcode', "func": "block_text",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--code',), "option_strings": ("--txt",),
'help': 'Barcode data to print', "help": "block_text to print",
'required': True, "required": True,
}, },
{ {
'option_strings': ('--bc',), "option_strings": ("--columns",),
'help': 'Barcode format', "help": "Number of columns",
'required': True, "type": int,
},
{
'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': { "parser": {
'name': 'text', "name": "cut",
'help': 'Print plain text', "help": "Cut the paper",
}, },
'defaults': { "defaults": {
'func': 'text', "func": "cut",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--txt',), "option_strings": ("--mode",),
'help': 'Plain text to print', "help": "Type of cut",
'required': True, "choices": ["FULL", "PART"],
}
],
},
{
'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': { "parser": {
'name': 'cut', "name": "cashdraw",
'help': 'Cut the paper', "help": "Kick the cash drawer",
}, },
'defaults': { "defaults": {
'func': 'cut', "func": "cashdraw",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--mode',), "option_strings": ("--pin",),
'help': 'Type of cut', "help": "Which PIN to kick",
'choices': ['FULL', 'PART'], "choices": [2, 5],
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'cashdraw', "name": "image",
'help': 'Kick the cash drawer', "help": "Print an image",
}, },
'defaults': { "defaults": {
'func': 'cashdraw', "func": "image",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--pin',), "option_strings": ("--img_source",),
'help': 'Which PIN to kick', "help": "Path to image",
'choices': [2, 5], "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': { "parser": {
'name': 'image', "name": "fullimage",
'help': 'Print an image', "help": "Print a fullimage",
}, },
'defaults': { "defaults": {
'func': 'image', "func": "fullimage",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--img_source',), "option_strings": ("--img",),
'help': 'Path to image', "help": "Path to img",
'required': True, "required": True,
}, },
{ {
'option_strings': ('--impl',), "option_strings": ("--max_height",),
'help': 'Implementation to use', "help": "Max height of image in px",
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'], "type": int,
}, },
{ {
'option_strings': ('--high_density_horizontal',), "option_strings": ("--width",),
'help': 'Image density (horizontal)', "help": "Max width of image in px",
'type': str_to_bool, "type": int,
}, },
{ {
'option_strings': ('--high_density_vertical',), "option_strings": ("--histeq",),
'help': 'Image density (vertical)', "help": "Equalize the histrogram",
'type': str_to_bool, "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',), "option_strings": ("--bandsize",),
'help': 'Max height of image in px', "help": "Size of bands to divide into when printing",
'type': int, "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': { "parser": {
'name': 'charcode', "name": "charcode",
'help': 'Set character code table', "help": "Set character code table",
}, },
'defaults': { "defaults": {
'func': 'charcode', "func": "charcode",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--code',), "option_strings": ("--code",),
'help': 'Character code', "help": "Character code",
'required': True, "required": True,
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'set', "name": "set",
'help': 'Set text properties', "help": "Set text properties",
}, },
'defaults': { "defaults": {
'func': 'set', "func": "set",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--align',), "option_strings": ("--align",),
'help': 'Horizontal alignment', "help": "Horizontal alignment",
'choices': ['left', 'center', 'right'], "choices": ["left", "center", "right"],
}, },
{ {
'option_strings': ('--font',), "option_strings": ("--font",),
'help': 'Font choice', "help": "Font choice",
'choices': ['left', 'center', 'right'], "choices": ["left", "center", "right"],
}, },
{ {
'option_strings': ('--text_type',), "option_strings": ("--text_type",),
'help': 'Text properties', "help": "Text properties",
'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'], "choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"],
}, },
{ {
'option_strings': ('--width',), "option_strings": ("--width",),
'help': 'Width multiplier', "help": "Width multiplier",
'type': int, "type": int,
}, },
{ {
'option_strings': ('--height',), "option_strings": ("--height",),
'help': 'Height multiplier', "help": "Height multiplier",
'type': int, "type": int,
}, },
{ {
'option_strings': ('--density',), "option_strings": ("--density",),
'help': 'Print density', "help": "Print density",
'type': int, "type": int,
}, },
{ {
'option_strings': ('--invert',), "option_strings": ("--invert",),
'help': 'White on black printing', "help": "White on black printing",
'type': str_to_bool, "type": str_to_bool,
}, },
{ {
'option_strings': ('--smooth',), "option_strings": ("--smooth",),
'help': 'Text smoothing. Effective on >: 4x4 text', "help": "Text smoothing. Effective on >: 4x4 text",
'type': str_to_bool, "type": str_to_bool,
}, },
{ {
'option_strings': ('--flip',), "option_strings": ("--flip",),
'help': 'Text smoothing. Effective on >: 4x4 text', "help": "Text smoothing. Effective on >: 4x4 text",
'type': str_to_bool, "type": str_to_bool,
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'hw', "name": "hw",
'help': 'Hardware operations', "help": "Hardware operations",
}, },
'defaults': { "defaults": {
'func': 'hw', "func": "hw",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--hw',), "option_strings": ("--hw",),
'help': 'Operation', "help": "Operation",
'choices': ['INIT', 'SELECT', 'RESET'], "choices": ["INIT", "SELECT", "RESET"],
'required': True, "required": True,
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'control', "name": "control",
'help': 'Control sequences', "help": "Control sequences",
}, },
'defaults': { "defaults": {
'func': 'control', "func": "control",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--ctl',), "option_strings": ("--ctl",),
'help': 'Control sequence', "help": "Control sequence",
'choices': ['LF', 'FF', 'CR', 'HT', 'VT'], "choices": ["LF", "FF", "CR", "HT", "VT"],
'required': True, "required": True,
}, },
{ {
'option_strings': ('--pos',), "option_strings": ("--pos",),
'help': 'Horizontal tab position (1-4)', "help": "Horizontal tab position (1-4)",
'type': int, "type": int,
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'panel_buttons', "name": "panel_buttons",
'help': 'Controls panel buttons', "help": "Controls panel buttons",
}, },
'defaults': { "defaults": {
'func': 'panel_buttons', "func": "panel_buttons",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--enable',), "option_strings": ("--enable",),
'help': 'Feed button enabled', "help": "Feed button enabled",
'type': str_to_bool, "type": str_to_bool,
'required': True, "required": True,
}, },
], ],
}, },
{ {
'parser': { "parser": {
'name': 'raw', "name": "raw",
'help': 'Raw data', "help": "Raw data",
}, },
'defaults': { "defaults": {
'func': '_raw', "func": "_raw",
}, },
'arguments': [ "arguments": [
{ {
'option_strings': ('--msg',), "option_strings": ("--msg",),
'help': 'Raw data to send', "help": "Raw data to send",
'required': True, "required": True,
}, },
], ],
}, },
@@ -453,68 +455,71 @@ def main():
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='CLI for python-escpos', description="CLI for python-escpos",
epilog='Printer configuration is defined in the python-escpos config' epilog="Printer configuration is defined in the python-escpos config"
'file. See documentation for details.', "file. See documentation for details.",
) )
parser.register('type', 'bool', str_to_bool) parser.register("type", "bool", str_to_bool)
# Allow config file location to be passed # Allow config file location to be passed
parser.add_argument( parser.add_argument(
'-c', '--config', "-c",
help='Alternate path to the configuration file', "--config",
help="Alternate path to the configuration file",
) )
# Everything interesting runs off of a subparser so we can use the format # Everything interesting runs off of a subparser so we can use the format
# cli [subparser] -args # cli [subparser] -args
command_subparsers = parser.add_subparsers( command_subparsers = parser.add_subparsers(
title='ESCPOS Command', title="ESCPOS Command",
dest='parser', dest="parser",
) )
# fix inconsistencies in the behaviour of some versions of argparse # fix inconsistencies in the behaviour of some versions of argparse
command_subparsers.required = False # force 'required' testing command_subparsers.required = False # force 'required' testing
# Build the ESCPOS command arguments # Build the ESCPOS command arguments
for command in ESCPOS_COMMANDS: for command in ESCPOS_COMMANDS:
parser_command = command_subparsers.add_parser(**command['parser']) parser_command = command_subparsers.add_parser(**command["parser"])
parser_command.set_defaults(**command['defaults']) parser_command.set_defaults(**command["defaults"])
for argument in command['arguments']: for argument in command["arguments"]:
option_strings = argument.pop('option_strings') option_strings = argument.pop("option_strings")
parser_command.add_argument(*option_strings, **argument) parser_command.add_argument(*option_strings, **argument)
# Build any custom arguments # Build any custom arguments
parser_command_demo = command_subparsers.add_parser('demo', parser_command_demo = command_subparsers.add_parser(
help='Demonstrates various functions') "demo", help="Demonstrates various functions"
parser_command_demo.set_defaults(func='demo') )
parser_command_demo.set_defaults(func="demo")
demo_group = parser_command_demo.add_mutually_exclusive_group() demo_group = parser_command_demo.add_mutually_exclusive_group()
demo_group.add_argument( demo_group.add_argument(
'--barcodes-a', "--barcodes-a",
help='Print demo barcodes for function type A', help="Print demo barcodes for function type A",
action='store_true', action="store_true",
) )
demo_group.add_argument( demo_group.add_argument(
'--barcodes-b', "--barcodes-b",
help='Print demo barcodes for function type B', help="Print demo barcodes for function type B",
action='store_true', action="store_true",
) )
demo_group.add_argument( demo_group.add_argument(
'--qr', "--qr",
help='Print some demo QR codes', help="Print some demo QR codes",
action='store_true', action="store_true",
) )
demo_group.add_argument( demo_group.add_argument(
'--text', "--text",
help='Print some demo text', help="Print some demo text",
action='store_true', action="store_true",
) )
parser_command_version = command_subparsers.add_parser('version', parser_command_version = command_subparsers.add_parser(
help='Print the version of python-escpos') "version", help="Print the version of python-escpos"
)
parser_command_version.set_defaults(version=True) parser_command_version.set_defaults(version=True)
# hook in argcomplete # hook in argcomplete
if 'argcomplete' in globals(): if "argcomplete" in globals():
argcomplete.autocomplete(parser) argcomplete.autocomplete(parser)
# Get only arguments actually passed # Get only arguments actually passed
@@ -522,16 +527,18 @@ def main():
if not args_dict: if not args_dict:
parser.print_help() parser.print_help()
sys.exit() sys.exit()
command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None) command_arguments = dict(
[k, v] for k, v in six.iteritems(args_dict) if v is not None
)
# If version should be printed, do this, then exit # If version should be printed, do this, then exit
print_version = command_arguments.pop('version', None) print_version = command_arguments.pop("version", None)
if print_version: if print_version:
print(version.version) print(version.version)
sys.exit() sys.exit()
# If there was a config path passed, grab it # If there was a config path passed, grab it
config_path = command_arguments.pop('config', None) config_path = command_arguments.pop("config", None)
# Load the configuration and defined printer # Load the configuration and defined printer
saved_config = config.Config() saved_config = config.Config()
@@ -539,12 +546,12 @@ def main():
printer = saved_config.printer() printer = saved_config.printer()
if not printer: if not printer:
raise Exception('No printers loaded from config') raise Exception("No printers loaded from config")
target_command = command_arguments.pop('func') target_command = command_arguments.pop("func")
# remove helper-argument 'parser' from dict # remove helper-argument 'parser' from dict
command_arguments.pop('parser', None) command_arguments.pop("parser", None)
if hasattr(printer, target_command): if hasattr(printer, target_command):
# print command with args # print command with args
@@ -552,13 +559,13 @@ def main():
if target_command in REQUIRES_NEWLINE: if target_command in REQUIRES_NEWLINE:
printer.text("\n") printer.text("\n")
else: else:
command_arguments['printer'] = printer command_arguments["printer"] = printer
globals()[target_command](**command_arguments) globals()[target_command](**command_arguments)
def demo(printer, **kwargs): def demo(printer, **kwargs):
""" """
Prints specificed demos. Called when CLI is passed `demo`. This function Prints demos. Called when CLI is passed `demo`. This function
uses the DEMO_FUNCTIONS dictionary. uses the DEMO_FUNCTIONS dictionary.
:param printer: A printer from escpos.printer :param printer: A printer from escpos.printer
@@ -568,14 +575,14 @@ def demo(printer, **kwargs):
for demo_choice in kwargs.keys(): for demo_choice in kwargs.keys():
command = getattr( command = getattr(
printer, printer,
demo_choice demo_choice.replace("barcodes_a", "barcode").replace(
.replace('barcodes_a', 'barcode') "barcodes_b", "barcode"
.replace('barcodes_b', 'barcode') ),
) )
for params in DEMO_FUNCTIONS[demo_choice]: for params in DEMO_FUNCTIONS[demo_choice]:
command(**params) command(**params)
printer.cut() printer.cut()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -21,4 +21,4 @@ class CodePageManager:
return self.data[encoding] return self.data[encoding]
CodePages = CodePageManager(CAPABILITIES['encodings']) CodePages = CodePageManager(CAPABILITIES["encodings"])

View File

@@ -1,13 +1,9 @@
""" ESC/POS configuration manager. """ ESC/POS configuration manager.
This module contains the implentations of abstract base class :py:class:`Config`. This module contains the implementations of abstract base class :py:class:`Config`.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os import os
import appdirs import appdirs
@@ -18,16 +14,17 @@ from . import exceptions
class Config(object): class Config(object):
""" Configuration handler class. """Configuration handler class.
This class loads configuration from a default or specificed directory. It This class loads configuration from a default or specificed directory. It
can create your defined printer and return it to you. can create your defined printer and return it to you.
""" """
_app_name = 'python-escpos'
_config_file = 'config.yaml' _app_name = "python-escpos"
_config_file = "config.yaml"
def __init__(self): def __init__(self):
""" Initialize configuration. """Initialize configuration.
Remember to add anything that needs to be reset between configurations Remember to add anything that needs to be reset between configurations
to self._reset_config to self._reset_config
@@ -39,7 +36,7 @@ class Config(object):
self._printer_config = None self._printer_config = None
def _reset_config(self): def _reset_config(self):
""" Clear the loaded configuration. """Clear the loaded configuration.
If we are loading a changed config, we don't want to have leftover If we are loading a changed config, we don't want to have leftover
data. data.
@@ -51,7 +48,7 @@ class Config(object):
self._printer_config = None self._printer_config = None
def load(self, config_path=None): def load(self, config_path=None):
""" Load and parse the configuration file using pyyaml """Load and parse the configuration file using pyyaml
:param config_path: An optional file path, file handle, or byte string :param config_path: An optional file path, file handle, or byte string
for the configuration file. for the configuration file.
@@ -62,31 +59,32 @@ class Config(object):
if not config_path: if not config_path:
config_path = os.path.join( config_path = os.path.join(
appdirs.user_config_dir(self._app_name), appdirs.user_config_dir(self._app_name), self._config_file
self._config_file
) )
try: try:
# First check if it's file like. If it is, pyyaml can load it. # 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 # I'm checking type instead of catching exceptions to keep the
# exception handling simple # exception handling simple
if hasattr(config_path, 'read'): if hasattr(config_path, "read"):
config = yaml.safe_load(config_path) config = yaml.safe_load(config_path)
else: else:
# If it isn't, it's a path. We have to open it first, otherwise # If it isn't, it's a path. We have to open it first, otherwise
# pyyaml will try to read it as yaml # pyyaml will try to read it as yaml
with open(config_path, 'rb') as config_file: with open(config_path, "rb") as config_file:
config = yaml.safe_load(config_file) config = yaml.safe_load(config_file)
except EnvironmentError: except EnvironmentError:
raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format( raise exceptions.ConfigNotFoundError(
config_path=str(config_path), "Couldn't read config at {config_path}".format(
)) config_path=str(config_path),
)
)
except yaml.YAMLError: except yaml.YAMLError:
raise exceptions.ConfigSyntaxError('Error parsing YAML') raise exceptions.ConfigSyntaxError("Error parsing YAML")
if 'printer' in config: if "printer" in config:
self._printer_config = config['printer'] self._printer_config = config["printer"]
self._printer_name = self._printer_config.pop('type').title() self._printer_name = self._printer_config.pop("type").title()
if not self._printer_name or not hasattr(printer, self._printer_name): if not self._printer_name or not hasattr(printer, self._printer_name):
raise exceptions.ConfigSyntaxError( raise exceptions.ConfigSyntaxError(
@@ -98,7 +96,7 @@ class Config(object):
self._has_loaded = True self._has_loaded = True
def printer(self): def printer(self):
""" Returns a printer that was defined in the config, or throws an """Returns a printer that was defined in the config, or throws an
exception. exception.
This method loads the default config if one hasn't beeen already loaded. This method loads the default config if one hasn't beeen already loaded.
@@ -108,7 +106,7 @@ class Config(object):
self.load() self.load()
if not self._printer_name: if not self._printer_name:
raise exceptions.ConfigSectionMissingError('printer') raise exceptions.ConfigSectionMissingError("printer")
if not self._printer: if not self._printer:
# We could catch init errors and make them a ConfigSyntaxError, # We could catch init errors and make them a ConfigSyntaxError,

View File

@@ -11,129 +11,140 @@ moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six import six
# Control characters # Control characters
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf # as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = b'\x00' NUL = b"\x00"
EOT = b'\x04' EOT = b"\x04"
ENQ = b'\x05' ENQ = b"\x05"
DLE = b'\x10' DLE = b"\x10"
DC4 = b'\x14' DC4 = b"\x14"
CAN = b'\x18' CAN = b"\x18"
ESC = b'\x1b' ESC = b"\x1b"
FS = b'\x1c' FS = b"\x1c"
GS = b'\x1d' GS = b"\x1d"
# Feed control sequences # Feed control sequences
CTL_LF = b'\n' # Print and line feed CTL_LF = b"\n" # Print and line feed
CTL_FF = b'\f' # Form feed CTL_FF = b"\f" # Form feed
CTL_CR = b'\r' # Carriage return CTL_CR = b"\r" # Carriage return
CTL_HT = b'\t' # Horizontal tab CTL_HT = b"\t" # Horizontal tab
CTL_SET_HT = ESC + b'\x44' # Set horizontal tab positions CTL_SET_HT = ESC + b"\x44" # Set horizontal tab positions
CTL_VT = b'\v' # Vertical tab CTL_VT = b"\v" # Vertical tab
# Printer hardware # Printer hardware
HW_INIT = ESC + b'@' # Clear data in buffer and reset modes HW_INIT = ESC + b"@" # Clear data in buffer and reset modes
HW_SELECT = ESC + b'=\x01' # Printer select HW_SELECT = ESC + b"=\x01" # Printer select
HW_RESET = ESC + b'\x3f\x0a\x00' # Reset printer hardware HW_RESET = ESC + b"\x3f\x0a\x00" # Reset printer hardware
# (TODO: Where is this specified?) # (TODO: Where is this specified?)
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>) # 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) _CASH_DRAWER = (
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) lambda m, t1="", t2="": ESC + b"p" + 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 [] 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 # Paper Cutter
_CUT_PAPER = lambda m: GS + b'V' + m _CUT_PAPER = lambda m: GS + b"V" + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper PAPER_FULL_CUT = _CUT_PAPER(b"\x00") # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper PAPER_PART_CUT = _CUT_PAPER(b"\x01") # Partial cut paper
# Beep # Beep (please note that the actual beep sequence may differ between devices)
BEEP = b'\x07' BEEP = b"\x07"
# Panel buttons (e.g. the FEED button) # Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) _PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Line display printing # Line display printing
LINE_DISPLAY_OPEN = ESC + b'\x3d\x02' LINE_DISPLAY_OPEN = ESC + b"\x3d\x02"
LINE_DISPLAY_CLEAR = ESC + b'\x40' LINE_DISPLAY_CLEAR = ESC + b"\x40"
LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01' LINE_DISPLAY_CLOSE = ESC + b"\x3d\x01"
# Sheet modes # Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper SHEET_SLIP_MODE = ESC + b"\x63\x30\x04" # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll 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 # Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too. # Printers" and tidy up this stuff too.
TXT_SIZE = GS + b'!' TXT_SIZE = GS + b"!"
TXT_NORMAL = ESC + b'!\x00' # Normal text TXT_NORMAL = ESC + b"!\x00" # Normal text
TXT_STYLE = { TXT_STYLE = {
'bold': { "bold": {
False: ESC + b'\x45\x00', # Bold font OFF False: ESC + b"\x45\x00", # Bold font OFF
True: ESC + b'\x45\x01' # Bold font ON True: ESC + b"\x45\x01", # Bold font ON
}, },
'underline': { "underline": {
0: ESC + b'\x2d\x00', # Underline font OFF 0: ESC + b"\x2d\x00", # Underline font OFF
1: ESC + b'\x2d\x01', # Underline font 1-dot ON 1: ESC + b"\x2d\x01", # Underline font 1-dot ON
2: ESC + b'\x2d\x02' # Underline font 2-dot ON 2: ESC + b"\x2d\x02", # Underline font 2-dot ON
}, },
'size': { "size": {
'normal': TXT_NORMAL + ESC + b'!\x00', # Normal text "normal": TXT_NORMAL + ESC + b"!\x00", # Normal text
'2h': TXT_NORMAL + ESC + b'!\x10', # Double height text "2h": TXT_NORMAL + ESC + b"!\x10", # Double height text
'2w': TXT_NORMAL + ESC + b'!\x20', # Double width text "2w": TXT_NORMAL + ESC + b"!\x20", # Double width text
'2x': TXT_NORMAL + ESC + b'!\x30' # Quad area text "2x": TXT_NORMAL + ESC + b"!\x30", # Quad area text
}, },
'font': { "font": {
'a': ESC + b'\x4d\x00', # Font type A "a": ESC + b"\x4d\x00", # Font type A
'b': ESC + b'\x4d\x00' # Font type B "b": ESC + b"\x4d\x00", # Font type B
}, },
'align': { "align": {
'left': ESC + b'\x61\x00', # Left justification "left": ESC + b"\x61\x00", # Left justification
'center': ESC + b'\x61\x01', # Centering "center": ESC + b"\x61\x01", # Centering
'right': ESC + b'\x61\x02' # Right justification "right": ESC + b"\x61\x02", # Right justification
}, },
'invert': { "invert": {
True: GS + b'\x42\x01', # Inverse Printing ON True: GS + b"\x42\x01", # Inverse Printing ON
False: GS + b'\x42\x00' # Inverse Printing OFF False: GS + b"\x42\x00", # Inverse Printing OFF
}, },
'color': { "color": {
'black': ESC + b'\x72\x00', # Default Color "black": ESC + b"\x72\x00", # Default Color
'red': ESC + b'\x72\x01' # Alternative Color, Usually Red "red": ESC + b"\x72\x01", # Alternative Color, Usually Red
}, },
'flip': { "flip": {True: ESC + b"\x7b\x01", False: ESC + b"\x7b\x00"}, # Flip ON # Flip OFF
True: ESC + b'\x7b\x01', # Flip ON "density": {
False: ESC + b'\x7b\x00' # Flip OFF 0: GS + b"\x7c\x00", # Printing Density -50%
1: GS + b"\x7c\x01", # Printing Density -37.5%
2: GS + b"\x7c\x02", # Printing Density -25%
3: GS + b"\x7c\x03", # Printing Density -12.5%
4: GS + b"\x7c\x04", # Printing Density 0%
5: GS + b"\x7c\x08", # Printing Density +50%
6: GS + b"\x7c\x07", # Printing Density +37.5%
7: GS + b"\x7c\x06", # Printing Density +25%
8: GS + b"\x7c\x05", # Printing Density +12.5%
}, },
'density': { "smooth": {
0: GS + b'\x7c\x00', # Printing Density -50% True: GS + b"\x62\x01", # Smooth ON
1: GS + b'\x7c\x01', # Printing Density -37.5% False: GS + b"\x62\x00", # Smooth OFF
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': { "height": { # Custom text height
True: GS + b'\x62\x01', # Smooth ON
False: GS + b'\x62\x00' # Smooth OFF
},
'height': { # Custom text height
1: 0x00, 1: 0x00,
2: 0x01, 2: 0x01,
3: 0x02, 3: 0x02,
@@ -141,9 +152,9 @@ TXT_STYLE = {
5: 0x04, 5: 0x04,
6: 0x05, 6: 0x05,
7: 0x06, 7: 0x06,
8: 0x07 8: 0x07,
}, },
'width': { # Custom text width "width": { # Custom text width
1: 0x00, 1: 0x00,
2: 0x10, 2: 0x10,
3: 0x20, 3: 0x20,
@@ -151,101 +162,104 @@ TXT_STYLE = {
5: 0x40, 5: 0x40,
6: 0x50, 6: 0x50,
7: 0x60, 7: 0x60,
8: 0x70 8: 0x70,
} },
} }
# Fonts # Fonts
SET_FONT = lambda n: ESC + b'\x4d' + n SET_FONT = lambda n: ESC + b"\x4d" + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A TXT_FONT_A = SET_FONT(b"\x00") # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B TXT_FONT_B = SET_FONT(b"\x01") # Font type B
# Spacing # Spacing
LINESPACING_RESET = ESC + b'2' LINESPACING_RESET = ESC + b"2"
LINESPACING_FUNCS = { LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85 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 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 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 # 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. # the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE = ESC + b'\x74' CODEPAGE_CHANGE = ESC + b"\x74"
# Barcode format # Barcode format
_SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n _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_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_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_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 BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b"\x03") # HRI both above and below
_SET_HRI_FONT = lambda n: GS + b'f' + n _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_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_FONT_B = _SET_HRI_FONT(b"\x01") # Font type B for HRI barcode chars
BARCODE_HEIGHT = GS + b'h' # Barcode Height [1-255] BARCODE_HEIGHT = GS + b"h" # Barcode Height [1-255]
BARCODE_WIDTH = GS + b'w' # Barcode Width [2-6] 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 # NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
# two "print bar code" commands: # two "print bar code" commands:
# - Type A: "GS k <type as integer> <data> NUL" # - Type A: "GS k <type as integer> <data> NUL"
# - TYPE B: "GS k <type as letter> <data length> <data>" # - TYPE B: "GS k <type as letter> <data length> <data>"
# The latter command supports more barcode types # The latter command supports more barcode types
_SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m) _SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m)
# Barcodes for printing function type A # Barcodes for printing function type A
BARCODE_TYPE_A = { BARCODE_TYPE_A = {
'UPC-A': _SET_BARCODE_TYPE(0), "UPC-A": _SET_BARCODE_TYPE(0),
'UPC-E': _SET_BARCODE_TYPE(1), "UPC-E": _SET_BARCODE_TYPE(1),
'EAN13': _SET_BARCODE_TYPE(2), "EAN13": _SET_BARCODE_TYPE(2),
'EAN8': _SET_BARCODE_TYPE(3), "EAN8": _SET_BARCODE_TYPE(3),
'CODE39': _SET_BARCODE_TYPE(4), "CODE39": _SET_BARCODE_TYPE(4),
'ITF': _SET_BARCODE_TYPE(5), "ITF": _SET_BARCODE_TYPE(5),
'NW7': _SET_BARCODE_TYPE(6), "NW7": _SET_BARCODE_TYPE(6),
'CODABAR': _SET_BARCODE_TYPE(6), # Same as NW7 "CODABAR": _SET_BARCODE_TYPE(6), # Same as NW7
} }
# Barcodes for printing function type B # Barcodes for printing function type B
# The first 8 are the same barcodes as type A # The first 8 are the same barcodes as type A
BARCODE_TYPE_B = { BARCODE_TYPE_B = {
'UPC-A': _SET_BARCODE_TYPE(65), "UPC-A": _SET_BARCODE_TYPE(65),
'UPC-E': _SET_BARCODE_TYPE(66), "UPC-E": _SET_BARCODE_TYPE(66),
'EAN13': _SET_BARCODE_TYPE(67), "EAN13": _SET_BARCODE_TYPE(67),
'EAN8': _SET_BARCODE_TYPE(68), "EAN8": _SET_BARCODE_TYPE(68),
'CODE39': _SET_BARCODE_TYPE(69), "CODE39": _SET_BARCODE_TYPE(69),
'ITF': _SET_BARCODE_TYPE(70), "ITF": _SET_BARCODE_TYPE(70),
'NW7': _SET_BARCODE_TYPE(71), "NW7": _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7 "CODABAR": _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72), "CODE93": _SET_BARCODE_TYPE(72),
'CODE128': _SET_BARCODE_TYPE(73), "CODE128": _SET_BARCODE_TYPE(73),
'GS1-128': _SET_BARCODE_TYPE(74), "GS1-128": _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75), "GS1 DATABAR OMNIDIRECTIONAL": _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76), "GS1 DATABAR TRUNCATED": _SET_BARCODE_TYPE(76),
'GS1 DATABAR LIMITED': _SET_BARCODE_TYPE(77), "GS1 DATABAR LIMITED": _SET_BARCODE_TYPE(77),
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78), "GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78),
} }
BARCODE_FORMATS = { BARCODE_FORMATS = {
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"), "UPC-A": ([(11, 12)], "^[0-9]{11,12}$"),
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[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}$"), "EAN13": ([(12, 13)], "^[0-9]{12,13}$"),
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"), "EAN8": ([(7, 8)], "^[0-9]{7,8}$"),
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"), "CODE39": ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
'ITF': ([(2, 255)], "^([0-9]{2})+$"), "ITF": ([(2, 255)], "^([0-9]{2})+$"),
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), "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 "CODABAR": ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"), "CODE93": ([(1, 255)], "^[\\x00-\\x7F]+$"),
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), "CODE128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128 "GS1-128": ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"), "GS1 DATABAR OMNIDIRECTIONAL": ([(13, 13)], "^[0-9]{13}$"),
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional "GS1 DATABAR TRUNCATED": ([(13, 13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"), "GS1 DATABAR LIMITED": ([(13, 13)], "^[01][0-9]{12}$"),
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"), "GS1 DATABAR EXPANDED": (
[(2, 255)],
"^\([0-9][A-Za-z0-9 \!\"\%\&'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$",
),
} }
BARCODE_TYPES = { BARCODE_TYPES = {
'A': BARCODE_TYPE_A, "A": BARCODE_TYPE_A,
'B': BARCODE_TYPE_B, "B": BARCODE_TYPE_B,
} }
# QRCode error correction levels # QRCode error correction levels
@@ -262,17 +276,17 @@ QR_MICRO = 3
# Image format # Image format
# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image" # NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
# command. The constants include a fragment of the data's header. # command. The constants include a fragment of the data's header.
_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data _PRINT_RASTER_IMG = lambda data: GS + b"v0" + data
S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size 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_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_2H = _PRINT_RASTER_IMG(b"\x02") # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple S_RASTER_Q = _PRINT_RASTER_IMG(b"\x03") # Set raster image quadruple
# Status Command # Status Command
RT_STATUS = DLE + EOT RT_STATUS = DLE + EOT
RT_STATUS_ONLINE = RT_STATUS + b'\x01' RT_STATUS_ONLINE = RT_STATUS + b"\x01"
RT_STATUS_PAPER = RT_STATUS + b'\x04' RT_STATUS_PAPER = RT_STATUS + b"\x04"
RT_MASK_ONLINE = 8 RT_MASK_ONLINE = 8
RT_MASK_PAPER = 18 RT_MASK_PAPER = 18
RT_MASK_LOWPAPER = 30 RT_MASK_LOWPAPER = 30
RT_MASK_NOPAPER = 114 RT_MASK_NOPAPER = 114

View File

@@ -10,10 +10,6 @@ This module contains the abstract base class :py:class:`Escpos`.
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import qrcode import qrcode
import textwrap import textwrap
@@ -26,17 +22,56 @@ from barcode.writer import ImageWriter
import os import os
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q from .constants import (
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH ESC,
GS,
NUL,
QR_ECLEVEL_L,
QR_ECLEVEL_M,
QR_ECLEVEL_H,
QR_ECLEVEL_Q,
SHEET_ROLL_MODE,
SHEET_SLIP_MODE,
SLIP_PRINT_AND_EJECT,
SLIP_SELECT,
SLIP_EJECT,
)
from .constants import (
QR_MODEL_1,
QR_MODEL_2,
QR_MICRO,
BARCODE_TYPES,
BARCODE_HEIGHT,
BARCODE_WIDTH,
)
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW from .constants import (
BARCODE_TXT_OFF,
BARCODE_TXT_BTH,
BARCODE_TXT_ABV,
BARCODE_TXT_BLW,
)
from .constants import TXT_SIZE, TXT_NORMAL from .constants import TXT_SIZE, TXT_NORMAL
from .constants import SET_FONT from .constants import SET_FONT
from .constants import LINESPACING_FUNCS, LINESPACING_RESET from .constants import LINESPACING_FUNCS, LINESPACING_RESET
from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE
from .constants import CD_KICK_DEC_SEQUENCE, CD_KICK_5, CD_KICK_2, PAPER_FULL_CUT, PAPER_PART_CUT from .constants import (
CD_KICK_DEC_SEQUENCE,
CD_KICK_5,
CD_KICK_2,
PAPER_FULL_CUT,
PAPER_PART_CUT,
)
from .constants import HW_RESET, HW_SELECT, HW_INIT from .constants import HW_RESET, HW_SELECT, HW_INIT
from .constants import CTL_VT, CTL_CR, CTL_FF, CTL_LF, CTL_SET_HT, PANEL_BUTTON_OFF, PANEL_BUTTON_ON from .constants import (
CTL_VT,
CTL_CR,
CTL_FF,
CTL_LF,
CTL_SET_HT,
PANEL_BUTTON_OFF,
PANEL_BUTTON_ON,
)
from .constants import TXT_STYLE from .constants import TXT_STYLE
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
@@ -54,27 +89,28 @@ from escpos.capabilities import get_profile, BARCODE_B
@six.add_metaclass(ABCMeta) @six.add_metaclass(ABCMeta)
class Escpos(object): class Escpos(object):
""" ESC/POS Printer object """ESC/POS Printer object
This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this
class. class.
""" """
device = None device = None
def __init__(self, profile=None, magic_encode_args=None, **kwargs): def __init__(self, profile=None, magic_encode_args=None, **kwargs):
""" Initialize ESCPOS Printer """Initialize ESCPOS Printer
:param profile: Printer profile""" :param profile: Printer profile"""
self.profile = get_profile(profile) self.profile = get_profile(profile)
self.magic = MagicEncode(self, **(magic_encode_args or {})) self.magic = MagicEncode(self, **(magic_encode_args or {}))
def __del__(self): def __del__(self):
""" call self.close upon deletion """ """call self.close upon deletion"""
self.close() self.close()
@abstractmethod @abstractmethod
def _raw(self, msg): def _raw(self, msg):
""" Sends raw data to the printer """Sends raw data to the printer
This function has to be individually implemented by the implementations. This function has to be individually implemented by the implementations.
@@ -84,14 +120,21 @@ class Escpos(object):
pass pass
def _read(self): def _read(self):
""" Returns a NotImplementedError if the instance of the class doesn't override this method. """Returns a NotImplementedError if the instance of the class doesn't override this method.
:raises NotImplementedError :raises NotImplementedError
""" """
raise NotImplementedError() raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", def image(
fragment_height=960, center=False): self,
""" Print an image img_source,
high_density_vertical=True,
high_density_horizontal=True,
impl="bitImageRaster",
fragment_height=960,
center=False,
):
"""Print an image
You can select whether the printer should print in high density or not. The default value is high density. You can select whether the printer should print in high density or not. The default value is high density.
When printing in low density, the image will be stretched. When printing in low density, the image will be stretched.
@@ -106,6 +149,9 @@ class Escpos(object):
* `graphics`: prints with the `GS ( L`-command * `graphics`: prints with the `GS ( L`-command
* `bitImageColumn`: prints with the `ESC *`-command * `bitImageColumn`: prints with the `ESC *`-command
When trying to center an image make sure you have initialized the printer with a valid profile, that
contains a media width pixel field. Otherwise the centering will have no effect.
:param img_source: PIL image or filename to load: `jpg`, `gif`, `png` or `bmp` :param img_source: PIL image or filename to load: `jpg`, `gif`, `png` or `bmp`
:param high_density_vertical: print in high density in vertical direction *default:* True :param high_density_vertical: print in high density in vertical direction *default:* True
:param high_density_horizontal: print in high density in horizontal direction *default:* True :param high_density_horizontal: print in high density in horizontal direction *default:* True
@@ -117,10 +163,16 @@ class Escpos(object):
im = EscposImage(img_source) im = EscposImage(img_source)
try: try:
max_width = int(self.profile.profile_data['media']['width']['pixels']) if self.profile.profile_data["media"]["width"]["pixels"] == "Unknown":
print(
"The media.width.pixel field of the printer profile is not set. "
+ "The center flag will have no effect."
)
max_width = int(self.profile.profile_data["media"]["width"]["pixels"])
if im.width > max_width: if im.width > max_width:
raise ImageWidthError('{} > {}'.format(im.width, max_width)) raise ImageWidthError("{} > {}".format(im.width, max_width))
if center: if center:
im.center(max_width) im.center(max_width)
@@ -134,41 +186,59 @@ class Escpos(object):
if im.height > fragment_height: if im.height > fragment_height:
fragments = im.split(fragment_height) fragments = im.split(fragment_height)
for fragment in fragments: for fragment in fragments:
self.image(fragment, self.image(
high_density_vertical=high_density_vertical, fragment,
high_density_horizontal=high_density_horizontal, high_density_vertical=high_density_vertical,
impl=impl, high_density_horizontal=high_density_horizontal,
fragment_height=fragment_height) impl=impl,
fragment_height=fragment_height,
)
return return
if impl == "bitImageRaster": if impl == "bitImageRaster":
# GS v 0, raster format bit image # GS v 0, raster format bit image
density_byte = (0 if high_density_horizontal else 1) + (0 if high_density_vertical else 2) density_byte = (0 if high_density_horizontal else 1) + (
header = GS + b"v0" + six.int2byte(density_byte) + self._int_low_high(im.width_bytes, 2) +\ 0 if high_density_vertical else 2
self._int_low_high(im.height, 2) )
header = (
GS
+ b"v0"
+ six.int2byte(density_byte)
+ self._int_low_high(im.width_bytes, 2)
+ self._int_low_high(im.height, 2)
)
self._raw(header + im.to_raster_format()) self._raw(header + im.to_raster_format())
if impl == "graphics": if impl == "graphics":
# GS ( L raster format graphics # GS ( L raster format graphics
img_header = self._int_low_high(im.width, 2) + self._int_low_high(im.height, 2) img_header = self._int_low_high(im.width, 2) + self._int_low_high(
tone = b'0' im.height, 2
colors = b'1' )
tone = b"0"
colors = b"1"
ym = six.int2byte(1 if high_density_vertical else 2) ym = six.int2byte(1 if high_density_vertical else 2)
xm = six.int2byte(1 if high_density_horizontal else 2) xm = six.int2byte(1 if high_density_horizontal else 2)
header = tone + xm + ym + colors + img_header header = tone + xm + ym + colors + img_header
raster_data = im.to_raster_format() raster_data = im.to_raster_format()
self._image_send_graphics_data(b'0', b'p', header + raster_data) self._image_send_graphics_data(b"0", b"p", header + raster_data)
self._image_send_graphics_data(b'0', b'2', b'') self._image_send_graphics_data(b"0", b"2", b"")
if impl == "bitImageColumn": if impl == "bitImageColumn":
# ESC *, column format bit image # ESC *, column format bit image
density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0) density_byte = (1 if high_density_horizontal else 0) + (
header = ESC + b"*" + six.int2byte(density_byte) + self._int_low_high(im.width, 2) 32 if high_density_vertical else 0
)
header = (
ESC
+ b"*"
+ six.int2byte(density_byte)
+ self._int_low_high(im.width, 2)
)
outp = [ESC + b"3" + six.int2byte(16)] # Adjust line-feed size outp = [ESC + b"3" + six.int2byte(16)] # Adjust line-feed size
for blob in im.to_column_format(high_density_vertical): for blob in im.to_column_format(high_density_vertical):
outp.append(header + blob + b"\n") outp.append(header + blob + b"\n")
outp.append(ESC + b"2") # Reset line-feed size outp.append(ESC + b"2") # Reset line-feed size
self._raw(b''.join(outp)) self._raw(b"".join(outp))
def _image_send_graphics_data(self, m, fn, data): def _image_send_graphics_data(self, m, fn, data):
""" """
@@ -179,11 +249,19 @@ class Escpos(object):
:param data: Data to send :param data: Data to send
""" """
header = self._int_low_high(len(data) + 2, 2) header = self._int_low_high(len(data) + 2, 2)
self._raw(GS + b'(L' + header + m + fn + data) self._raw(GS + b"(L" + header + m + fn + data)
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, def qr(
native=False, center=False, impl="bitImageRaster"): self,
""" Print QR Code for the provided string content,
ec=QR_ECLEVEL_L,
size=3,
model=QR_MODEL_2,
native=False,
center=False,
impl="bitImageRaster",
):
"""Print QR Code for the provided string
:param content: The content of the code. Numeric data will be more efficiently compacted. :param content: The content of the code. Numeric data will be more efficiently compacted.
:param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or :param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or
@@ -203,50 +281,60 @@ class Escpos(object):
if not 1 <= size <= 16: if not 1 <= size <= 16:
raise ValueError("Invalid block size (must be 1-16)") raise ValueError("Invalid block size (must be 1-16)")
if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]: if model not in [QR_MODEL_1, QR_MODEL_2, QR_MICRO]:
raise ValueError("Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)") raise ValueError(
"Invalid QR model (must be one of QR_MODEL_1, QR_MODEL_2, QR_MICRO)"
)
if content == "": if content == "":
# Handle edge case by printing nothing. # Handle edge case by printing nothing.
return return
if not native: if not native:
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image # Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
if model != QR_MODEL_2: if model != QR_MODEL_2:
raise ValueError("Invalid QR model for qrlib rendering (must be QR_MODEL_2)") raise ValueError(
"Invalid QR model for qrlib rendering (must be QR_MODEL_2)"
)
python_qr_ec = { python_qr_ec = {
QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H, QR_ECLEVEL_H: qrcode.constants.ERROR_CORRECT_H,
QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L, QR_ECLEVEL_L: qrcode.constants.ERROR_CORRECT_L,
QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M, QR_ECLEVEL_M: qrcode.constants.ERROR_CORRECT_M,
QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q QR_ECLEVEL_Q: qrcode.constants.ERROR_CORRECT_Q,
} }
qr_code = qrcode.QRCode(version=None, box_size=size, border=1, error_correction=python_qr_ec[ec]) qr_code = qrcode.QRCode(
version=None, box_size=size, border=1, error_correction=python_qr_ec[ec]
)
qr_code.add_data(content) qr_code.add_data(content)
qr_code.make(fit=True) qr_code.make(fit=True)
qr_img = qr_code.make_image() qr_img = qr_code.make_image()
im = qr_img._img.convert("RGB") im = qr_img._img.convert("RGB")
# Convert the RGB image in printable image # Convert the RGB image in printable image
self.text('\n') self.text("\n")
self.image(im, center=center, impl=impl) self.image(im, center=center, impl=impl)
self.text('\n') self.text("\n")
self.text('\n') self.text("\n")
return return
if center: if center:
raise NotImplementedError("Centering not implemented for native QR rendering") raise NotImplementedError(
"Centering not implemented for native QR rendering"
)
# Native 2D code printing # Native 2D code printing
cn = b'1' # Code type for QR code cn = b"1" # Code type for QR code
# Select model: 1, 2 or micro. # Select model: 1, 2 or micro.
self._send_2d_code_data(six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0)) self._send_2d_code_data(
six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0)
)
# Set dot size. # Set dot size.
self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size))
# Set error correction level: L, M, Q, or H # Set error correction level: L, M, Q, or H
self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec))
# Send content & print # Send content & print
self._send_2d_code_data(six.int2byte(80), cn, content.encode('utf-8'), b'0') self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0")
self._send_2d_code_data(six.int2byte(81), cn, b'', b'0') self._send_2d_code_data(six.int2byte(81), cn, b"", b"0")
def _send_2d_code_data(self, fn, cn, data, m=b''): def _send_2d_code_data(self, fn, cn, data, m=b""):
""" Wrapper for GS ( k, to calculate and send correct data length. """Wrapper for GS ( k, to calculate and send correct data length.
:param fn: Function to use. :param fn: Function to use.
:param cn: Output code type. Affects available data. :param cn: Output code type. Affects available data.
@@ -256,28 +344,32 @@ class Escpos(object):
if len(m) > 1 or len(cn) != 1 or len(fn) != 1: if len(m) > 1 or len(cn) != 1 or len(fn) != 1:
raise ValueError("cn and fn must be one byte each.") raise ValueError("cn and fn must be one byte each.")
header = self._int_low_high(len(data) + len(m) + 2, 2) header = self._int_low_high(len(data) + len(m) + 2, 2)
self._raw(GS + b'(k' + header + cn + fn + m + data) self._raw(GS + b"(k" + header + cn + fn + m + data)
@staticmethod @staticmethod
def _int_low_high(inp_number, out_bytes): def _int_low_high(inp_number, out_bytes):
""" Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. """Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
:param inp_number: Input number :param inp_number: Input number
:param out_bytes: The number of bytes to output (1 - 4). :param out_bytes: The number of bytes to output (1 - 4).
""" """
max_input = (256 << (out_bytes * 8) - 1) max_input = 256 << (out_bytes * 8) - 1
if not 1 <= out_bytes <= 4: if not 1 <= out_bytes <= 4:
raise ValueError("Can only output 1-4 bytes") raise ValueError("Can only output 1-4 bytes")
if not 0 <= inp_number <= max_input: if not 0 <= inp_number <= max_input:
raise ValueError("Number too large. Can only output up to {0} in {1} bytes".format(max_input, out_bytes)) raise ValueError(
outp = b'' "Number too large. Can only output up to {0} in {1} bytes".format(
max_input, out_bytes
)
)
outp = b""
for _ in range(0, out_bytes): for _ in range(0, out_bytes):
outp += six.int2byte(inp_number % 256) outp += six.int2byte(inp_number % 256)
inp_number //= 256 inp_number //= 256
return outp return outp
def charcode(self, code="AUTO"): def charcode(self, code="AUTO"):
""" Set Character Code Table """Set Character Code Table
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with
the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the
@@ -315,11 +407,23 @@ class Escpos(object):
return False return False
bounds, regex = BARCODE_FORMATS[bc] bounds, regex = BARCODE_FORMATS[bc]
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code) return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(
regex, code
)
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", def barcode(
align_ct=True, function_type=None, check=True): self,
""" Print Barcode code,
bc,
height=64,
width=3,
pos="BELOW",
font="A",
align_ct=True,
function_type=None,
check=True,
):
"""Print Barcode
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
be supported by the unit. By default, this method will check whether your barcode text is correct, that is be supported by the unit. By default, this method will check whether your barcode text is correct, that is
@@ -395,7 +499,7 @@ class Escpos(object):
*default*: A *default*: A
:param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc :param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc
requirements as defigned in the esc/pos documentation. See :py:meth:`.check_barcode()` requirements as definged in the ESC/POS documentation. See :py:meth:`.check_barcode()`
for more information. *default*: True. for more information. *default*: True.
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
@@ -404,38 +508,46 @@ class Escpos(object):
""" """
if function_type is None: if function_type is None:
# Choose the function type automatically. # Choose the function type automatically.
if bc in BARCODE_TYPES['A']: if bc in BARCODE_TYPES["A"]:
function_type = 'A' function_type = "A"
else: else:
if bc in BARCODE_TYPES['B']: if bc in BARCODE_TYPES["B"]:
if not self.profile.supports(BARCODE_B): if not self.profile.supports(BARCODE_B):
raise BarcodeTypeError(( raise BarcodeTypeError(
"Barcode type '{bc} not supported for " (
"the current printer profile").format(bc=bc)) "Barcode type '{bc} not supported for "
function_type = 'B' "the current printer profile"
).format(bc=bc)
)
function_type = "B"
else: else:
raise BarcodeTypeError(( raise BarcodeTypeError(
"Barcode type '{bc} is not valid").format(bc=bc)) ("Barcode type '{bc} is not valid").format(bc=bc)
)
bc_types = BARCODE_TYPES[function_type.upper()] bc_types = BARCODE_TYPES[function_type.upper()]
if bc.upper() not in bc_types.keys(): if bc.upper() not in bc_types.keys():
raise BarcodeTypeError(( raise BarcodeTypeError(
"Barcode '{bc}' not valid for barcode function type " (
"{function_type}").format( "Barcode '{bc}' not valid for barcode function type "
"{function_type}"
).format(
bc=bc, bc=bc,
function_type=function_type, function_type=function_type,
)) )
)
if check and not self.check_barcode(bc, code): if check and not self.check_barcode(bc, code):
raise BarcodeCodeError(( raise BarcodeCodeError(
"Barcode '{code}' not in a valid format for type '{bc}'").format( ("Barcode '{code}' not in a valid format for type '{bc}'").format(
code=code, code=code,
bc=bc, bc=bc,
)) )
)
# Align Bar Code() # Align Bar Code()
if align_ct: if align_ct:
self._raw(TXT_STYLE['align']['center']) self._raw(TXT_STYLE["align"]["center"])
# Height # Height
if 1 <= height <= 255: if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT + six.int2byte(height)) self._raw(BARCODE_HEIGHT + six.int2byte(height))
@@ -475,34 +587,43 @@ class Escpos(object):
if function_type.upper() == "A": if function_type.upper() == "A":
self._raw(NUL) self._raw(NUL)
def soft_barcode(self, barcode_type, data, impl='bitImageColumn', def soft_barcode(
module_height=5, module_width=0.2, text_distance=1): self,
barcode_type,
data,
impl="bitImageColumn",
module_height=5,
module_width=0.2,
text_distance=1,
center=True,
):
image_writer = ImageWriter() image_writer = ImageWriter()
# Check if barcode type exists # Check if barcode type exists
if barcode_type not in barcode.PROVIDED_BARCODES: if barcode_type not in barcode.PROVIDED_BARCODES:
raise BarcodeTypeError( raise BarcodeTypeError(
'Barcode type {} not supported by software barcode renderer' "Barcode type {} not supported by software barcode renderer".format(
.format(barcode_type)) barcode_type
)
)
# Render the barcode to a fake file # Render the barcode
barcode_class = barcode.get_barcode_class(barcode_type) barcode_class = barcode.get_barcode_class(barcode_type)
my_code = barcode_class(data, writer=image_writer) my_code = barcode_class(data, writer=image_writer)
my_code.render(
with open(os.devnull, "wb") as nullfile: writer_options={
my_code.write(nullfile, { "module_height": module_height,
'module_height': module_height, "module_width": module_width,
'module_width': module_width, "text_distance": text_distance,
'text_distance': text_distance }
}) )
# Retrieve the Pillow image and print it # Retrieve the Pillow image and print it
image = my_code.writer._image image = my_code.writer._image
self.image(image, impl=impl) self.image(image, impl=impl, center=center)
def text(self, txt): def text(self, txt):
""" Print alpha-numeric text """Print alpha-numeric text
The text has to be encoded in the currently selected codepage. The text has to be encoded in the currently selected codepage.
The input text has to be encoded in unicode. The input text has to be encoded in unicode.
@@ -513,7 +634,7 @@ class Escpos(object):
txt = six.text_type(txt) txt = six.text_type(txt)
self.magic.write(txt) self.magic.write(txt)
def textln(self, txt=''): def textln(self, txt=""):
"""Print alpha-numeric text with a newline """Print alpha-numeric text with a newline
The text has to be encoded in the currently selected codepage. The text has to be encoded in the currently selected codepage.
@@ -522,7 +643,7 @@ class Escpos(object):
:param txt: text to be printed with a newline :param txt: text to be printed with a newline
:raises: :py:exc:`~escpos.exceptions.TextError` :raises: :py:exc:`~escpos.exceptions.TextError`
""" """
self.text('{}\n'.format(txt)) self.text("{}\n".format(txt))
def ln(self, count=1): def ln(self, count=1):
"""Print a newline or more """Print a newline or more
@@ -531,12 +652,12 @@ class Escpos(object):
:raises: :py:exc:`ValueError` if count < 0 :raises: :py:exc:`ValueError` if count < 0
""" """
if count < 0: if count < 0:
raise ValueError('Count cannot be lesser than 0') raise ValueError("Count cannot be lesser than 0")
if count > 0: if count > 0:
self.text('\n' * count) self.text("\n" * count)
def block_text(self, txt, font=None, columns=None): def block_text(self, txt, font="0", columns=None):
""" Text is printed wrapped to specified columns """Text is printed wrapped to specified columns
Text has to be encoded in unicode. Text has to be encoded in unicode.
@@ -548,10 +669,23 @@ class Escpos(object):
col_count = self.profile.get_columns(font) if columns is None else columns col_count = self.profile.get_columns(font) if columns is None else columns
self.text(textwrap.fill(txt, col_count)) self.text(textwrap.fill(txt, col_count))
def set(self, align='left', font='a', bold=False, underline=0, width=1, def set(
height=1, density=9, invert=False, smooth=False, flip=False, self,
double_width=False, double_height=False, custom_size=False): align="left",
""" Set text properties by sending them to the printer font="a",
bold=False,
underline=0,
width=1,
height=1,
density=9,
invert=False,
smooth=False,
flip=False,
double_width=False,
double_height=False,
custom_size=False,
):
"""Set text properties by sending them to the printer
:param align: horizontal position for text, possible values are: :param align: horizontal position for text, possible values are:
@@ -592,37 +726,41 @@ class Escpos(object):
""" """
if custom_size: if custom_size:
if 1 <= width <= 8 and 1 <= height <= 8 and isinstance(width, int) and\ if (
isinstance(height, int): 1 <= width <= 8
size_byte = TXT_STYLE['width'][width] + TXT_STYLE['height'][height] and 1 <= height <= 8
and isinstance(width, int)
and isinstance(height, int)
):
size_byte = TXT_STYLE["width"][width] + TXT_STYLE["height"][height]
self._raw(TXT_SIZE + six.int2byte(size_byte)) self._raw(TXT_SIZE + six.int2byte(size_byte))
else: else:
raise SetVariableError() raise SetVariableError()
else: else:
self._raw(TXT_NORMAL) self._raw(TXT_NORMAL)
if double_width and double_height: if double_width and double_height:
self._raw(TXT_STYLE['size']['2x']) self._raw(TXT_STYLE["size"]["2x"])
elif double_width: elif double_width:
self._raw(TXT_STYLE['size']['2w']) self._raw(TXT_STYLE["size"]["2w"])
elif double_height: elif double_height:
self._raw(TXT_STYLE['size']['2h']) self._raw(TXT_STYLE["size"]["2h"])
else: else:
self._raw(TXT_STYLE['size']['normal']) self._raw(TXT_STYLE["size"]["normal"])
self._raw(TXT_STYLE['flip'][flip]) self._raw(TXT_STYLE["flip"][flip])
self._raw(TXT_STYLE['smooth'][smooth]) self._raw(TXT_STYLE["smooth"][smooth])
self._raw(TXT_STYLE['bold'][bold]) self._raw(TXT_STYLE["bold"][bold])
self._raw(TXT_STYLE['underline'][underline]) self._raw(TXT_STYLE["underline"][underline])
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font)))) self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_STYLE['align'][align]) self._raw(TXT_STYLE["align"][align])
if density != 9: if density != 9:
self._raw(TXT_STYLE['density'][density]) self._raw(TXT_STYLE["density"][density])
self._raw(TXT_STYLE['invert'][invert]) self._raw(TXT_STYLE["invert"][invert])
def line_spacing(self, spacing=None, divisor=180): def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing. """Set line character spacing.
If no spacing is given, we reset it to the default. If no spacing is given, we reset it to the default.
@@ -642,51 +780,52 @@ class Escpos(object):
if divisor not in LINESPACING_FUNCS: if divisor not in LINESPACING_FUNCS:
raise ValueError("divisor must be either 360, 180 or 60") raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 180] if divisor in [360, 180] and (not (0 <= spacing <= 255)):
and (not(0 <= spacing <= 255))): raise ValueError(
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180") "spacing must be a int between 0 and 255 when divisor is 360 or 180"
if divisor == 60 and (not(0 <= spacing <= 85)): )
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60") if divisor == 60 and (not (0 <= spacing <= 85)):
raise ValueError(
"spacing must be a int between 0 and 85 when divisor is 60"
)
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing)) self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode='FULL', feed=True): def cut(self, mode="FULL", feed=True):
""" Cut paper. """Cut paper.
Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will Without any arguments the paper will be cut completely. With 'mode=PART' a partial cut will
be attempted. Note however, that not all models can do a partial cut. See the documentation of be attempted. Note however, that not all models can do a partial cut. See the documentation of
your printer for details. your printer for details.
.. todo:: Check this function on TM-T88II.
:param mode: set to 'PART' for a partial cut. default: 'FULL' :param mode: set to 'PART' for a partial cut. default: 'FULL'
:param feed: print and feed before cutting. default: true :param feed: print and feed before cutting. default: true
:raises ValueError: if mode not in ('FULL', 'PART') :raises ValueError: if mode not in ('FULL', 'PART')
""" """
if not feed: if not feed:
self._raw(GS + b'V' + six.int2byte(66) + b'\x00') self._raw(GS + b"V" + six.int2byte(66) + b"\x00")
return return
self.print_and_feed(6) self.print_and_feed(6)
mode = mode.upper() mode = mode.upper()
if mode not in ('FULL', 'PART'): if mode not in ("FULL", "PART"):
raise ValueError("Mode must be one of ('FULL', 'PART')") raise ValueError("Mode must be one of ('FULL', 'PART')")
if mode == "PART": if mode == "PART":
if self.profile.supports('paperPartCut'): if self.profile.supports("paperPartCut"):
self._raw(PAPER_PART_CUT) self._raw(PAPER_PART_CUT)
elif self.profile.supports('paperFullCut'): elif self.profile.supports("paperFullCut"):
self._raw(PAPER_FULL_CUT) self._raw(PAPER_FULL_CUT)
elif mode == "FULL": elif mode == "FULL":
if self.profile.supports('paperFullCut'): if self.profile.supports("paperFullCut"):
self._raw(PAPER_FULL_CUT) self._raw(PAPER_FULL_CUT)
elif self.profile.supports('paperPartCut'): elif self.profile.supports("paperPartCut"):
self._raw(PAPER_PART_CUT) self._raw(PAPER_PART_CUT)
def cashdraw(self, pin): def cashdraw(self, pin):
""" Send pulse to kick the cash drawer """Send pulse to kick the cash drawer
Kick cash drawer on pin 2 or pin 5 according to default parameter. Kick cash drawer on pin 2 or pin 5 according to default parameter.
For non default parameter send a decimal sequence i.e. [27,112,48] or [27,112,0,25,255] For non default parameter send a decimal sequence i.e. [27,112,48] or [27,112,0,25,255]
@@ -705,7 +844,7 @@ class Escpos(object):
raise CashDrawerError(err) raise CashDrawerError(err)
def linedisplay_select(self, select_display=False): def linedisplay_select(self, select_display=False):
""" Selects the line display or the printer """Selects the line display or the printer
This method is used for line displays that are daisy-chained between your computer and printer. This method is used for line displays that are daisy-chained between your computer and printer.
If you set `select_display` to true, only the display is selected and if you set it to false, If you set `select_display` to true, only the display is selected and if you set it to false,
@@ -720,7 +859,7 @@ class Escpos(object):
self._raw(LINE_DISPLAY_CLOSE) self._raw(LINE_DISPLAY_CLOSE)
def linedisplay_clear(self): def linedisplay_clear(self):
""" Clears the line display and resets the cursor """Clears the line display and resets the cursor
This method is used for line displays that are daisy-chained between your computer and printer. This method is used for line displays that are daisy-chained between your computer and printer.
""" """
@@ -741,7 +880,7 @@ class Escpos(object):
self.linedisplay_select(select_display=False) self.linedisplay_select(select_display=False)
def hw(self, hw): def hw(self, hw):
""" Hardware operations """Hardware operations
:param hw: hardware action, may be: :param hw: hardware action, may be:
@@ -759,12 +898,12 @@ class Escpos(object):
pass pass
def print_and_feed(self, n=1): def print_and_feed(self, n=1):
""" Print data in print buffer and feed *n* lines """Print data in print buffer and feed *n* lines
if n not in range (0, 255) then ValueError will be raised if n not in range (0, 255) then ValueError will be raised
:param n: number of n to feed. 0 <= n <= 255. default: 1 :param n: number of n to feed. 0 <= n <= 255. default: 1
:raises ValueError: if not 0 <= n <= 255 :raises ValueError: if not 0 <= n <= 255
""" """
if 0 <= n <= 255: if 0 <= n <= 255:
# ESC d n # ESC d n
@@ -773,7 +912,7 @@ class Escpos(object):
raise ValueError("n must be betwen 0 and 255") raise ValueError("n must be betwen 0 and 255")
def control(self, ctl, count=5, tab_size=8): def control(self, ctl, count=5, tab_size=8):
""" Feed control sequences """Feed control sequences
:param ctl: string for the following control sequences: :param ctl: string for the following control sequences:
@@ -795,9 +934,9 @@ class Escpos(object):
elif ctl.upper() == "CR": elif ctl.upper() == "CR":
self._raw(CTL_CR) self._raw(CTL_CR)
elif ctl.upper() == "HT": elif ctl.upper() == "HT":
if not (0 <= count <= 32 and if not (
1 <= tab_size <= 255 and 0 <= count <= 32 and 1 <= tab_size <= 255 and count * tab_size < 256
count * tab_size < 256): ):
raise TabPosError() raise TabPosError()
else: else:
# Set tab positions # Set tab positions
@@ -809,7 +948,7 @@ class Escpos(object):
self._raw(CTL_VT) self._raw(CTL_VT)
def panel_buttons(self, enable=True): def panel_buttons(self, enable=True):
""" Controls the panel buttons on the printer (e.g. FEED) """Controls the panel buttons on the printer (e.g. FEED)
When enable is set to False the panel buttons on the printer will be disabled. Calling the method with When enable is set to False the panel buttons on the printer will be disabled. Calling the method with
enable=True or without argument will enable the panel buttons. enable=True or without argument will enable the panel buttons.
@@ -870,13 +1009,48 @@ class Escpos(object):
status = self.query_status(RT_STATUS_PAPER) status = self.query_status(RT_STATUS_PAPER)
if len(status) == 0: if len(status) == 0:
return 2 return 2
if (status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER): if status[0] & RT_MASK_NOPAPER == RT_MASK_NOPAPER:
return 0 return 0
if (status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER): if status[0] & RT_MASK_LOWPAPER == RT_MASK_LOWPAPER:
return 1 return 1
if (status[0] & RT_MASK_PAPER == RT_MASK_PAPER): if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
return 2 return 2
def target(self, type="ROLL"):
"""Select where to print to
Print to the thermal printer by default (ROLL) or
print to the slip dot matrix printer if supported (SLIP)
"""
if type.upper() == "ROLL":
self._raw(SHEET_ROLL_MODE)
elif type.upper() == "SLIP":
self._raw(SHEET_SLIP_MODE)
else:
raise ValueError("Unsupported target")
def eject_slip(self):
"""Eject the slip/cheque"""
self._raw(SLIP_EJECT)
def print_and_eject_slip(self):
"""Print and eject
Prints data from the buffer to the slip station and if the paper
sensor is covered, reverses the slip out the front of the printer
far enough to be accessible to the operator.
The impact station opens the platen in all cases.
"""
self._raw(SLIP_PRINT_AND_EJECT)
def use_slip_only(self):
"""Selects the Slip Station for all functions.
The receipt station is the default setting after the printer
is initialized or the Clear Printer (0x10) command is received
"""
self._raw(SLIP_SELECT)
class EscposIO(object): class EscposIO(object):
"""ESC/POS Printer IO object """ESC/POS Printer IO object
@@ -911,7 +1085,7 @@ class EscposIO(object):
self.autoclose = autoclose self.autoclose = autoclose
def set(self, **kwargs): def set(self, **kwargs):
""" Set the printer-parameters """Set the printer-parameters
Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`. Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`.
For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods
@@ -927,24 +1101,25 @@ class EscposIO(object):
params.update(kwargs) params.update(kwargs)
if isinstance(text, six.text_type): if isinstance(text, six.text_type):
lines = text.split('\n') lines = text.split("\n")
elif isinstance(text, list) or isinstance(text, tuple): elif isinstance(text, list) or isinstance(text, tuple):
lines = text lines = text
else: else:
lines = ["{0}".format(text), ] lines = [
"{0}".format(text),
]
# TODO check unicode handling # TODO check unicode handling
# TODO flush? or on print? (this should prob rather be handled by the _raw-method) # TODO flush? or on print? (this should prob rather be handled by the _raw-method)
for line in lines: for line in lines:
self.printer.set(**params) self.printer.set(**params)
if isinstance(text, six.text_type): if isinstance(text, six.text_type):
self.printer.text(u"{0}\n".format(line)) self.printer.text("{0}\n".format(line))
else: else:
self.printer.text("{0}\n".format(line)) self.printer.text("{0}\n".format(line))
def close(self): def close(self):
""" called upon closing the `with`-statement """called upon closing the `with`-statement"""
"""
self.printer.close() self.printer.close()
def __enter__(self, **kwargs): def __enter__(self, **kwargs):

View File

@@ -25,14 +25,10 @@ Result/Exit codes:
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
class Error(Exception): class Error(Exception):
""" Base class for ESC/POS errors """ """Base class for ESC/POS errors"""
def __init__(self, msg, status=None): def __init__(self, msg, status=None):
Exception.__init__(self) Exception.__init__(self)
self.msg = msg self.msg = msg
@@ -45,12 +41,13 @@ class Error(Exception):
class BarcodeTypeError(Error): class BarcodeTypeError(Error):
""" No Barcode type defined. """No Barcode type defined.
This exception indicates that no known barcode-type has been entered. The barcode-type has to be 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`. one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The returned error code is `10`. The returned error code is `10`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -61,12 +58,13 @@ class BarcodeTypeError(Error):
class BarcodeSizeError(Error): class BarcodeSizeError(Error):
""" Barcode size is out of range. """Barcode size is out of range.
This exception indicates that the values for the barcode size are out of range. 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 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`. The resulting returncode is `20`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -77,12 +75,13 @@ class BarcodeSizeError(Error):
class BarcodeCodeError(Error): class BarcodeCodeError(Error):
""" No Barcode code was supplied, or it is incorrect. """No Barcode code was supplied, or it is incorrect.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter 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. was True and the check failed.
The returncode for this exception is `30`. The returncode for this exception is `30`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -93,24 +92,28 @@ class BarcodeCodeError(Error):
class ImageSizeError(Error): class ImageSizeError(Error):
""" Image height is longer than 255px and can't be printed. """Image height is longer than 255px and can't be printed.
The returncode for this exception is `40`. The returncode for this exception is `40`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 40 self.resultcode = 40
def __str__(self): def __str__(self):
return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg) return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
class ImageWidthError(Error): class ImageWidthError(Error):
""" Image width is too large. """Image width is too large.
The return code for this exception is `41`. The return code for this exception is `41`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -121,26 +124,30 @@ class ImageWidthError(Error):
class TextError(Error): class TextError(Error):
""" Text string must be supplied to the `text()` method. """Text string must be supplied to the `text()` method.
This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`. This exception is raised when an empty string is passed to :py:meth:`escpos.escpos.Escpos.text`.
The returncode for this exception is `50`. The returncode for this exception is `50`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 50 self.resultcode = 50
def __str__(self): def __str__(self):
return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg) return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg
)
class CashDrawerError(Error): class CashDrawerError(Error):
""" Valid pin must be set in order to send pulse. """Valid pin must be set in order to send pulse.
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`. A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
The returncode for this exception is `60`. The returncode for this exception is `60`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -151,27 +158,31 @@ class CashDrawerError(Error):
class TabPosError(Error): class TabPosError(Error):
""" Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. """Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value. 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`. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The returncode for this exception is `70`. The returncode for this exception is `70`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 70 self.resultcode = 70
def __str__(self): def __str__(self):
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg) return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg
)
class CharCodeError(Error): class CharCodeError(Error):
""" Valid char code must be set. """Valid char code must be set.
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown. The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
Ths returncode for this exception is `80`. Ths returncode for this exception is `80`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -182,11 +193,12 @@ class CharCodeError(Error):
class USBNotFoundError(Error): class USBNotFoundError(Error):
""" Device wasn't found (probably not plugged in) """Device wasn't found (probably not plugged in)
The USB device seems to be not plugged in. The USB device seems to be not plugged in.
Ths returncode for this exception is `90`. Ths returncode for this exception is `90`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -197,11 +209,12 @@ class USBNotFoundError(Error):
class SetVariableError(Error): class SetVariableError(Error):
""" A set method variable was out of range """A set method variable was out of range
Check set variables against minimum and maximum values Check set variables against minimum and maximum values
Ths returncode for this exception is `100`. Ths returncode for this exception is `100`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -213,12 +226,14 @@ class SetVariableError(Error):
# Configuration errors # Configuration errors
class ConfigNotFoundError(Error): class ConfigNotFoundError(Error):
""" The configuration file was not found """The configuration file was not found
The default or passed configuration file could not be read The default or passed configuration file could not be read
Ths returncode for this exception is `200`. Ths returncode for this exception is `200`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -229,11 +244,12 @@ class ConfigNotFoundError(Error):
class ConfigSyntaxError(Error): class ConfigSyntaxError(Error):
""" The configuration file is invalid """The configuration file is invalid
The syntax is incorrect The syntax is incorrect
Ths returncode for this exception is `210`. Ths returncode for this exception is `210`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
@@ -244,11 +260,12 @@ class ConfigSyntaxError(Error):
class ConfigSectionMissingError(Error): class ConfigSectionMissingError(Error):
""" The configuration file is missing a section """The configuration file is missing a section
The part of the config asked for doesn't exist in the loaded configuration The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`. Ths returncode for this exception is `220`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg

View File

@@ -8,10 +8,6 @@ This module contains the image format handler :py:class:`EscposImage`.
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import math import math
from PIL import Image, ImageOps from PIL import Image, ImageOps
@@ -41,7 +37,7 @@ class EscposImage(object):
# Convert to white RGB background, paste over white background # Convert to white RGB background, paste over white background
# to strip alpha. # to strip alpha.
img_original = img_original.convert('RGBA') img_original = img_original.convert("RGBA")
im = Image.new("RGB", img_original.size, (255, 255, 255)) im = Image.new("RGB", img_original.size, (255, 255, 255))
im.paste(img_original, mask=img_original.split()[3]) im.paste(img_original, mask=img_original.split()[3])
# Convert down to greyscale # Convert down to greyscale
@@ -89,7 +85,7 @@ class EscposImage(object):
box = (left, top, left + line_height, top + height_pixels) box = (left, top, left + line_height, top + height_pixels)
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box) im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
im_bytes = im_slice.tobytes() im_bytes = im_slice.tobytes()
yield(im_bytes) yield (im_bytes)
left += line_height left += line_height
def to_raster_format(self): def to_raster_format(self):
@@ -105,7 +101,7 @@ class EscposImage(object):
:param fragment_height: height of fragment :param fragment_height: height of fragment
:return: list of PIL objects :return: list of PIL objects
""" """
passes = int(math.ceil(self.height/fragment_height)) passes = int(math.ceil(self.height / fragment_height))
fragments = [] fragments = []
for n in range(0, passes): for n in range(0, passes):
left = 0 left = 0

View File

@@ -4,11 +4,6 @@
I doubt that this currently works correctly. I doubt that this currently works correctly.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
try: try:
import jaconv import jaconv
@@ -39,69 +34,68 @@ def encode_katakana(text):
TXT_ENC_KATAKANA_MAP = { TXT_ENC_KATAKANA_MAP = {
# Maps UTF-8 Katakana symbols to KATAKANA Page Codes # Maps UTF-8 Katakana symbols to KATAKANA Page Codes
# TODO: has this really to be hardcoded? # TODO: has this really to be hardcoded?
# Half-Width Katakanas # Half-Width Katakanas
'': b'\xa1', "": b"\xa1",
'': b'\xa2', "": b"\xa2",
'': b'\xa3', "": b"\xa3",
'': b'\xa4', "": b"\xa4",
'': b'\xa5', "": b"\xa5",
'': b'\xa6', "": b"\xa6",
'': b'\xa7', "": b"\xa7",
'': b'\xa8', "": b"\xa8",
'': b'\xa9', "": b"\xa9",
'': b'\xaa', "": b"\xaa",
'': b'\xab', "": b"\xab",
'': b'\xac', "": b"\xac",
'': b'\xad', "": b"\xad",
'': b'\xae', "": b"\xae",
'': b'\xaf', "": b"\xaf",
'': b'\xb0', "": b"\xb0",
'': b'\xb1', "": b"\xb1",
'': b'\xb2', "": b"\xb2",
'': b'\xb3', "": b"\xb3",
'': b'\xb4', "": b"\xb4",
'': b'\xb5', "": b"\xb5",
'': b'\xb6', "": b"\xb6",
'': b'\xb7', "": b"\xb7",
'': b'\xb8', "": b"\xb8",
'': b'\xb9', "": b"\xb9",
'': b'\xba', "": b"\xba",
'': b'\xbb', "": b"\xbb",
'': b'\xbc', "": b"\xbc",
'': b'\xbd', "": b"\xbd",
'': b'\xbe', "": b"\xbe",
'ソ': b'\xbf', "ソ": b"\xbf",
'': b'\xc0', "": b"\xc0",
'': b'\xc1', "": b"\xc1",
'': b'\xc2', "": b"\xc2",
'': b'\xc3', "": b"\xc3",
'': b'\xc4', "": b"\xc4",
'': b'\xc5', "": b"\xc5",
'': b'\xc6', "": b"\xc6",
'': b'\xc7', "": b"\xc7",
'': b'\xc8', "": b"\xc8",
'': b'\xc9', "": b"\xc9",
'': b'\xca', "": b"\xca",
'': b'\xcb', "": b"\xcb",
'': b'\xcc', "": b"\xcc",
'': b'\xcd', "": b"\xcd",
'': b'\xce', "": b"\xce",
'': b'\xcf', "": b"\xcf",
'': b'\xd0', "": b"\xd0",
'': b'\xd1', "": b"\xd1",
'': b'\xd2', "": b"\xd2",
'': b'\xd3', "": b"\xd3",
'': b'\xd4', "": b"\xd4",
'': b'\xd5', "": b"\xd5",
'': b'\xd6', "": b"\xd6",
'': b'\xd7', "": b"\xd7",
'': b'\xd8', "": b"\xd8",
'': b'\xd9', "": b"\xd9",
'': b'\xda', "": b"\xda",
'': b'\xdb', "": b"\xdb",
'': b'\xdc', "": b"\xdc",
'': b'\xdd', "": b"\xdd",
'': b'\xde', "": b"\xde",
'': b'\xdf', "": b"\xdf",
} }

View File

@@ -12,16 +12,13 @@ The code is based on the encoding-code in py-xml-escpos by @fvdsn.
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from builtins import bytes from builtins import bytes
from .constants import CODEPAGE_CHANGE from .constants import CODEPAGE_CHANGE
from .exceptions import Error from .exceptions import Error
from .codepages import CodePages from .codepages import CodePages
import six import six
import re
class Encoder(object): class Encoder(object):
@@ -59,10 +56,12 @@ class Encoder(object):
""" """
encoding = CodePages.get_encoding_name(encoding) encoding = CodePages.get_encoding_name(encoding)
if encoding not in self.codepages: if encoding not in self.codepages:
raise ValueError(( raise ValueError(
'Encoding "{}" cannot be used for the current profile. ' (
'Valid encodings are: {}' 'Encoding "{}" cannot be used for the current profile. '
).format(encoding, ','.join(self.codepages.keys()))) "Valid encodings are: {}"
).format(encoding, ",".join(self.codepages.keys()))
)
return encoding return encoding
@staticmethod @staticmethod
@@ -74,16 +73,18 @@ class Encoder(object):
:param encoding: The name of the encoding. This must appear in the CodePage list :param encoding: The name of the encoding. This must appear in the CodePage list
""" """
codepage = CodePages.get_encoding(encoding) codepage = CodePages.get_encoding(encoding)
if 'data' in codepage: if "data" in codepage:
encodable_chars = list("".join(codepage['data'])) encodable_chars = list("".join(codepage["data"]))
assert(len(encodable_chars) == 128) assert len(encodable_chars) == 128
return encodable_chars return encodable_chars
elif 'python_encode' in codepage: elif "python_encode" in codepage:
encodable_chars = [u" "] * 128 encodable_chars = [" "] * 128
for i in range(0, 128): for i in range(0, 128):
codepoint = i + 128 codepoint = i + 128
try: try:
encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode']) encodable_chars[i] = bytes([codepoint]).decode(
codepage["python_encode"]
)
except UnicodeDecodeError: except UnicodeDecodeError:
# Non-encodable character, just skip it # Non-encodable character, just skip it
pass pass
@@ -91,7 +92,7 @@ class Encoder(object):
raise LookupError("Can't find a known encoding for {}".format(encoding)) raise LookupError("Can't find a known encoding for {}".format(encoding))
def _get_codepage_char_map(self, encoding): def _get_codepage_char_map(self, encoding):
""" Get codepage character map """Get codepage character map
Process an encoding and return a map of UTF-characters to code points Process an encoding and return a map of UTF-characters to code points
in this encoding. in this encoding.
@@ -104,7 +105,9 @@ class Encoder(object):
if encoding in self.available_characters: if encoding in self.available_characters:
return self.available_characters[encoding] return self.available_characters[encoding]
codepage_char_list = self._get_codepage_char_list(encoding) codepage_char_list = self._get_codepage_char_list(encoding)
codepage_char_map = dict((utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)) codepage_char_map = dict(
(utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)
)
self.available_characters[encoding] = codepage_char_map self.available_characters[encoding] = codepage_char_map
return codepage_char_map return codepage_char_map
@@ -127,7 +130,7 @@ class Encoder(object):
@staticmethod @staticmethod
def _encode_char(char, charmap, defaultchar): def _encode_char(char, charmap, defaultchar):
""" Encode a single character with the given encoding map """Encode a single character with the given encoding map
:param char: char to encode :param char: char to encode
:param charmap: dictionary for mapping characters in this code page :param charmap: dictionary for mapping characters in this code page
@@ -138,23 +141,22 @@ class Encoder(object):
return charmap[char] return charmap[char]
return ord(defaultchar) return ord(defaultchar)
def encode(self, text, encoding, defaultchar='?'): def encode(self, text, encoding, defaultchar="?"):
""" Encode text under the given encoding """Encode text under the given encoding
:param text: Text to encode :param text: Text to encode
:param encoding: Encoding name to use (must be defined in capabilities) :param encoding: Encoding name to use (must be defined in capabilities)
:param defaultchar: Fallback for non-encodable characters :param defaultchar: Fallback for non-encodable characters
""" """
codepage_char_map = self._get_codepage_char_map(encoding) codepage_char_map = self._get_codepage_char_map(encoding)
output_bytes = bytes([self._encode_char(char, codepage_char_map, defaultchar) for char in text]) output_bytes = bytes(
[self._encode_char(char, codepage_char_map, defaultchar) for char in text]
)
return output_bytes return output_bytes
def __encoding_sort_func(self, item): def __encoding_sort_func(self, item):
key, index = item key, index = item
return ( return (key in self.used_encodings, index)
key in self.used_encodings,
index
)
def find_suitable_encoding(self, char): def find_suitable_encoding(self, char):
"""The order of our search is a specific one: """The order of our search is a specific one:
@@ -172,9 +174,7 @@ class Encoder(object):
that the code page we pick for this character is actually that the code page we pick for this character is actually
supported. supported.
""" """
sorted_encodings = sorted( sorted_encodings = sorted(self.codepages.items(), key=self.__encoding_sort_func)
self.codepages.items(),
key=self.__encoding_sort_func)
for encoding, _ in sorted_encodings: for encoding, _ in sorted_encodings:
if self.can_encode(encoding, char): if self.can_encode(encoding, char):
@@ -184,7 +184,7 @@ class Encoder(object):
def split_writable_text(encoder, text, encoding): def split_writable_text(encoder, text, encoding):
"""Splits off as many characters from the begnning of text as """Splits off as many characters from the beginning of text as
are writable with "encoding". Returns a 2-tuple (writable, rest). are writable with "encoding". Returns a 2-tuple (writable, rest).
""" """
if not encoding: if not encoding:
@@ -209,8 +209,10 @@ class MagicEncode(object):
If the printer does not support a suitable code page, it can If the printer does not support a suitable code page, it can
insert an error character. insert an error character.
""" """
def __init__(self, driver, encoding=None, disabled=False,
defaultsymbol='?', encoder=None): def __init__(
self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None
):
""" """
:param driver: :param driver:
@@ -223,7 +225,7 @@ class MagicEncode(object):
:param encoder: :param encoder:
""" """
if disabled and not encoding: if disabled and not encoding:
raise Error('If you disable magic encode, you need to define an encoding!') raise Error("If you disable magic encode, you need to define an encoding!")
self.driver = driver self.driver = driver
self.encoder = encoder or Encoder(driver.profile.get_code_pages()) self.encoder = encoder or Encoder(driver.profile.get_code_pages())
@@ -245,13 +247,16 @@ class MagicEncode(object):
self.disabled = True self.disabled = True
def write(self, text): def write(self, text):
"""Write the text, automatically switching encodings. """Write the text, automatically switching encodings."""
"""
if self.disabled: if self.disabled:
self.write_with_encoding(self.encoding, text) self.write_with_encoding(self.encoding, text)
return 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 # See how far we can go into the text with the current encoding
to_write, text = split_writable_text(self.encoder, text, self.encoding) to_write, text = split_writable_text(self.encoder, text, self.encoding)
if to_write: if to_write:
@@ -272,25 +277,26 @@ class MagicEncode(object):
self.write_with_encoding(encoding, to_write) self.write_with_encoding(encoding, to_write)
def _handle_character_failed(self, char): def _handle_character_failed(self, char):
"""Called when no codepage was found to render a character. """Called when no codepage was found to render a character."""
"""
# Writing the default symbol via write() allows us to avoid # Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches. # unnecesary codepage switches.
self.write(self.defaultsymbol) self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text): def write_with_encoding(self, encoding, text):
if text is not None and type(text) is not six.text_type: 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( raise Error(
type=type(text) "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 # We always know the current code page; if the new codepage
# is different, emit a change command. # is different, emit a change command.
if encoding != self.encoding: if encoding != self.encoding:
self.encoding = encoding self.encoding = encoding
self.driver._raw( self.driver._raw(
CODEPAGE_CHANGE + CODEPAGE_CHANGE + six.int2byte(self.encoder.get_sequence(encoding))
six.int2byte(self.encoder.get_sequence(encoding))) )
if text: if text:
self.driver._raw(self.encoder.encode(text, encoding)) self.driver._raw(self.encoder.encode(text, encoding))

View File

@@ -8,22 +8,39 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import socket
import subprocess
import sys
import serial
import usb.core import usb.core
import usb.util import usb.util
import serial
import socket
from .escpos import Escpos from .escpos import Escpos
from .exceptions import USBNotFoundError 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): class Usb(Escpos):
""" USB printer """USB printer
This class describes a printer that natively speaks USB. This class describes a printer that natively speaks USB.
@@ -34,8 +51,17 @@ class Usb(Escpos):
""" """
def __init__(self, idVendor, idProduct, usb_args=None, timeout=0, in_ep=0x82, out_ep=0x01, def __init__(
*args, **kwargs): # noqa: N803 self,
idVendor,
idProduct,
usb_args=None,
timeout=0,
in_ep=0x82,
out_ep=0x01,
*args,
**kwargs
): # noqa: N803
""" """
:param idVendor: Vendor ID :param idVendor: Vendor ID
:param idProduct: Product ID :param idProduct: Product ID
@@ -51,13 +77,13 @@ class Usb(Escpos):
usb_args = usb_args or {} usb_args = usb_args or {}
if idVendor: if idVendor:
usb_args['idVendor'] = idVendor usb_args["idVendor"] = idVendor
if idProduct: if idProduct:
usb_args['idProduct'] = idProduct usb_args["idProduct"] = idProduct
self.open(usb_args) self.open(usb_args)
def open(self, usb_args): def open(self, usb_args):
""" Search device on USB tree and set it as escpos device. """Search device on USB tree and set it as escpos device.
:param usb_args: USB arguments :param usb_args: USB arguments
""" """
@@ -83,6 +109,8 @@ class Usb(Escpos):
if check_driver is None or check_driver: if check_driver is None or check_driver:
try: try:
self.device.detach_kernel_driver(0) self.device.detach_kernel_driver(0)
except NotImplementedError:
pass
except usb.core.USBError as e: except usb.core.USBError as e:
if check_driver is not None: if check_driver is not None:
print("Could not detatch kernel driver: {0}".format(str(e))) print("Could not detatch kernel driver: {0}".format(str(e)))
@@ -94,7 +122,7 @@ class Usb(Escpos):
print("Could not set configuration: {0}".format(str(e))) print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -102,18 +130,18 @@ class Usb(Escpos):
self.device.write(self.out_ep, msg, self.timeout) self.device.write(self.out_ep, msg, self.timeout)
def _read(self): def _read(self):
""" Reads a data buffer and returns it to the caller. """ """Reads a data buffer and returns it to the caller."""
return self.device.read(self.in_ep, 16) return self.device.read(self.in_ep, 16)
def close(self): def close(self):
""" Release USB interface """ """Release USB interface"""
if self.device: if self.device:
usb.util.dispose_resources(self.device) usb.util.dispose_resources(self.device)
self.device = None self.device = None
class Serial(Escpos): class Serial(Escpos):
""" Serial printer """Serial printer
This class describes a printer that is connected by serial interface. This class describes a printer that is connected by serial interface.
@@ -124,9 +152,19 @@ class Serial(Escpos):
""" """
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1, def __init__(
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, self,
xonxoff=False, dsrdtr=True, *args, **kwargs): 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 devfile: Device file under dev filesystem
@@ -151,13 +189,19 @@ class Serial(Escpos):
self.open() self.open()
def open(self): def open(self):
""" Setup serial port and set is as escpos device """ """Setup serial port and set is as escpos device"""
if self.device is not None and self.device.is_open: if self.device is not None and self.device.is_open:
self.close() self.close()
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, self.device = serial.Serial(
bytesize=self.bytesize, parity=self.parity, port=self.devfile,
stopbits=self.stopbits, timeout=self.timeout, baudrate=self.baudrate,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr) bytesize=self.bytesize,
parity=self.parity,
stopbits=self.stopbits,
timeout=self.timeout,
xonxoff=self.xonxoff,
dsrdtr=self.dsrdtr,
)
if self.device is not None: if self.device is not None:
print("Serial printer enabled") print("Serial printer enabled")
@@ -165,7 +209,7 @@ class Serial(Escpos):
print("Unable to open serial printer on: {0}".format(str(self.devfile))) print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -173,18 +217,18 @@ class Serial(Escpos):
self.device.write(msg) self.device.write(msg)
def _read(self): def _read(self):
""" Reads a data buffer and returns it to the caller. """ """Reads a data buffer and returns it to the caller."""
return self.device.read(16) return self.device.read(16)
def close(self): def close(self):
""" Close Serial interface """ """Close Serial interface"""
if self.device is not None and self.device.is_open: if self.device is not None and self.device.is_open:
self.device.flush() self.device.flush()
self.device.close() self.device.close()
class Network(Escpos): class Network(Escpos):
""" Network printer """Network printer
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that 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``. is forwarded with ``socat``.
@@ -220,7 +264,7 @@ class Network(Escpos):
self.open() self.open()
def open(self): def open(self):
""" Open TCP socket with ``socket``-library and set it as escpos device """ """Open TCP socket with ``socket``-library and set it as escpos device"""
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout) self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port)) self.device.connect((self.host, self.port))
@@ -229,7 +273,7 @@ class Network(Escpos):
print("Could not open socket for {0}".format(self.host)) print("Could not open socket for {0}".format(self.host))
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -237,19 +281,22 @@ class Network(Escpos):
self.device.sendall(msg) self.device.sendall(msg)
def _read(self): def _read(self):
""" Read data from the TCP socket """ """Read data from the TCP socket"""
return self.device.recv(16) return self.device.recv(16)
def close(self): def close(self):
""" Close TCP connection """ """Close TCP connection"""
if self.device is not None: if self.device is not None:
self.device.shutdown(socket.SHUT_RDWR) try:
self.device.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
self.device.close() self.device.close()
class File(Escpos): class File(Escpos):
""" Generic file printer """Generic file printer
This class is used for parallel port printer or other printers that are directly attached to the filesystem. 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 Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
@@ -274,18 +321,18 @@ class File(Escpos):
self.open() self.open()
def open(self): def open(self):
""" Open system file """ """Open system file"""
self.device = open(self.devfile, "wb") self.device = open(self.devfile, "wb")
if self.device is None: if self.device is None:
print("Could not open the specified file {0}".format(self.devfile)) print("Could not open the specified file {0}".format(self.devfile))
def flush(self): def flush(self):
""" Flush printing content """ """Flush printing content"""
self.device.flush() self.device.flush()
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -295,14 +342,14 @@ class File(Escpos):
self.flush() self.flush()
def close(self): def close(self):
""" Close system file """ """Close system file"""
if self.device is not None: if self.device is not None:
self.device.flush() self.device.flush()
self.device.close() self.device.close()
class Dummy(Escpos): class Dummy(Escpos):
""" Dummy printer """Dummy printer
This class is used for saving commands to a variable, for use in situations where 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 there is no need to send commands to an actual printer. This includes
@@ -316,13 +363,12 @@ class Dummy(Escpos):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """ """
"""
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self._output_list = [] self._output_list = []
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -331,11 +377,11 @@ class Dummy(Escpos):
@property @property
def output(self): def output(self):
""" Get the data that was sent to this printer """ """Get the data that was sent to this printer"""
return b''.join(self._output_list) return b"".join(self._output_list)
def clear(self): def clear(self):
""" Clear the buffer of the printer """Clear the buffer of the printer
This method can be called if you send the contents to a physical printer This method can be called if you send the contents to a physical printer
and want to use the Dummy printer for new output. and want to use the Dummy printer for new output.
@@ -346,14 +392,8 @@ class Dummy(Escpos):
pass pass
_WIN32PRINT = False
try:
import win32print
_WIN32PRINT = True
except ImportError:
pass
if _WIN32PRINT: if _WIN32PRINT:
class Win32Raw(Escpos): class Win32Raw(Escpos):
def __init__(self, printer_name=None, *args, **kwargs): def __init__(self, printer_name=None, *args, **kwargs):
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
@@ -362,12 +402,15 @@ if _WIN32PRINT:
else: else:
self.printer_name = win32print.GetDefaultPrinter() self.printer_name = win32print.GetDefaultPrinter()
self.hPrinter = None self.hPrinter = None
self.open()
def open(self, job_name="python-escpos"): def open(self, job_name="python-escpos"):
if self.printer_name is None: if self.printer_name is None:
raise Exception("Printer not found") raise Exception("Printer not found")
self.hPrinter = win32print.OpenPrinter(self.printer_name) self.hPrinter = win32print.OpenPrinter(self.printer_name)
self.current_job = win32print.StartDocPrinter(self.hPrinter, 1, (job_name, None, "RAW")) self.current_job = win32print.StartDocPrinter(
self.hPrinter, 1, (job_name, None, "RAW")
)
win32print.StartPagePrinter(self.hPrinter) win32print.StartPagePrinter(self.hPrinter)
def close(self): def close(self):
@@ -379,7 +422,7 @@ if _WIN32PRINT:
self.hPrinter = None self.hPrinter = None
def _raw(self, msg): def _raw(self, msg):
""" Print any command sent in raw format """Print any command sent in raw format
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes :type msg: bytes
@@ -389,3 +432,171 @@ if _WIN32PRINT:
if self.hPrinter is None: if self.hPrinter is None:
raise Exception("Printer job not opened") raise Exception("Printer job not opened")
win32print.WritePrinter(self.hPrinter, msg) 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()

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
"""verifies that the metaclass abc is properly used by Escpos """verifies that the metaclass abc is properly used by ESC/POS
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from nose.tools import raises from nose.tools import raises
@@ -20,11 +16,11 @@ from abc import ABCMeta
@raises(TypeError) @raises(TypeError)
def test_abstract_base_class_raises(): def test_abstract_base_class_raises():
"""test whether the abstract base class raises an exception for Escpos""" """test whether the abstract base class raises an exception for ESC/POS"""
escpos.Escpos() # This call should raise TypeError because of abstractmethod _raw() escpos.Escpos() # This call should raise TypeError because of abstractmethod _raw()
def test_abstract_base_class(): def test_abstract_base_class():
""" test whether Escpos has the metaclass ABCMeta """ """test whether Escpos has the metaclass ABCMeta"""
assert issubclass(escpos.Escpos, object) assert issubclass(escpos.Escpos, object)
assert type(escpos.Escpos) is ABCMeta assert type(escpos.Escpos) is ABCMeta

View File

@@ -2,51 +2,46 @@
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os import os
import sys import sys
from scripttest import TestFileEnvironment from scripttest import TestFileEnvironment
from nose.tools import assert_equals, nottest from nose.tools import assert_equal, nottest
import escpos import escpos
TEST_DIR = os.path.abspath('test/test-cli-output') TEST_DIR = os.path.abspath("test/test-cli-output")
DEVFILE_NAME = 'testfile' DEVFILE_NAME = "testfile"
DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME) DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME)
CONFIGFILE = 'testconfig.yaml' CONFIGFILE = "testconfig.yaml"
CONFIG_YAML = ''' CONFIG_YAML = """
--- ---
printer: printer:
type: file type: file
devfile: {testfile} devfile: {testfile}
'''.format( """.format(
testfile=DEVFILE, testfile=DEVFILE,
) )
class TestCLI: class TestCLI:
""" Contains setups, teardowns, and tests for CLI """Contains setups, teardowns, and tests for CLI"""
"""
@classmethod @classmethod
def setup_class(cls): def setup_class(cls):
""" Create a config file to read from """ """Create a config file to read from"""
with open(CONFIGFILE, 'w') as config: with open(CONFIGFILE, "w") as config:
config.write(CONFIG_YAML) config.write(CONFIG_YAML)
@classmethod @classmethod
def teardown_class(cls): def teardown_class(cls):
""" Remove config file """ """Remove config file"""
os.remove(CONFIGFILE) os.remove(CONFIGFILE)
def setup(self): def setup(self):
""" Create a file to print to and set up env""" """Create a file to print to and set up env"""
self.env = None self.env = None
self.default_args = None self.default_args = None
@@ -56,63 +51,59 @@ class TestCLI:
) )
self.default_args = ( self.default_args = (
'python-escpos', "python-escpos",
'-c', "-c",
CONFIGFILE, CONFIGFILE,
) )
fhandle = open(DEVFILE, 'a') fhandle = open(DEVFILE, "a")
try: try:
os.utime(DEVFILE, None) os.utime(DEVFILE, None)
finally: finally:
fhandle.close() fhandle.close()
def teardown(self): def teardown(self):
""" Destroy printer file and env """ """Destroy printer file and env"""
os.remove(DEVFILE) os.remove(DEVFILE)
self.env.clear() self.env.clear()
def test_cli_help(self): def test_cli_help(self):
""" Test getting help from cli """ """Test getting help from cli"""
result = self.env.run('python-escpos', '-h') result = self.env.run("python-escpos", "-h")
assert not result.stderr assert not result.stderr
assert 'usage' in result.stdout assert "usage" in result.stdout
def test_cli_version(self): def test_cli_version(self):
""" Test the version string """ """Test the version string"""
result = self.env.run('python-escpos', 'version') result = self.env.run("python-escpos", "version")
assert not result.stderr assert not result.stderr
assert_equals(escpos.__version__, result.stdout.strip()) assert_equal(escpos.__version__, result.stdout.strip())
@nottest # disable this test as it is not that easy anymore to predict the outcome of this call @nottest # disable this test as it is not that easy anymore to predict the outcome of this call
def test_cli_text(self): def test_cli_text(self):
""" Make sure text returns what we sent it """ """Make sure text returns what we sent it"""
test_text = 'this is some text' test_text = "this is some text"
result = self.env.run( result = self.env.run(
*(self.default_args + ( *(
'text', self.default_args
'--txt', + (
test_text, "text",
)) "--txt",
test_text,
)
)
) )
assert not result.stderr assert not result.stderr
assert DEVFILE_NAME in result.files_updated.keys() assert DEVFILE_NAME in result.files_updated.keys()
assert_equals( assert_equals(result.files_updated[DEVFILE_NAME].bytes, test_text + "\n")
result.files_updated[DEVFILE_NAME].bytes,
test_text + '\n'
)
def test_cli_text_inavlid_args(self): def test_cli_text_inavlid_args(self):
""" Test a failure to send valid arguments """ """Test a failure to send valid arguments"""
result = self.env.run( result = self.env.run(
*(self.default_args + ( *(self.default_args + ("text", "--invalid-param", "some data")),
'text',
'--invalid-param',
'some data'
)),
expect_error=True, expect_error=True,
expect_stderr=True expect_stderr=True
) )
assert_equals(result.returncode, 2) assert_equal(result.returncode, 2)
assert 'error:' in result.stderr assert "error:" in result.stderr
assert not result.files_updated assert not result.files_updated

View File

@@ -1,52 +1,56 @@
#!/usr/bin/python #!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B
from escpos.capabilities import Profile, BARCODE_B from escpos.capabilities import Profile, BARCODE_B
from escpos.exceptions import BarcodeTypeError, BarcodeCodeError from escpos.exceptions import BarcodeTypeError, BarcodeCodeError
import pytest import pytest
@pytest.mark.parametrize("bctype,data,expected", [ @pytest.mark.parametrize(
('EAN13', '4006381333931', "bctype,data,expected",
b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00') [
]) (
"EAN13",
"4006381333931",
b"\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00",
)
],
)
def test_barcode(bctype, data, expected): def test_barcode(bctype, data, expected):
"""should generate different barcode types correctly. """should generate different barcode types correctly."""
"""
instance = printer.Dummy() instance = printer.Dummy()
instance.barcode(data, bctype) instance.barcode(data, bctype)
assert instance.output == expected assert instance.output == expected
@pytest.mark.parametrize("bctype,supports_b", [ @pytest.mark.parametrize(
('invalid', True), "bctype,supports_b",
('CODE128', False), [
]) ("invalid", True),
("CODE128", False),
],
)
def test_lacks_support(bctype, supports_b): def test_lacks_support(bctype, supports_b):
"""should raise an error if the barcode type is not supported. """should raise an error if the barcode type is not supported."""
"""
profile = Profile(features={BARCODE_B: supports_b}) profile = Profile(features={BARCODE_B: supports_b})
instance = printer.Dummy(profile=profile) instance = printer.Dummy(profile=profile)
with pytest.raises(BarcodeTypeError): with pytest.raises(BarcodeTypeError):
instance.barcode('test', bctype) instance.barcode("test", bctype)
assert instance.output == b'' assert instance.output == b""
@pytest.mark.parametrize("bctype,data", [ @pytest.mark.parametrize(
('EAN13', 'AA'), "bctype,data",
('CODE128', '{D2354AA'), [
]) ("EAN13", "AA"),
("CODE128", "{D2354AA"),
],
)
def test_code_check(bctype, data): def test_code_check(bctype, data):
"""should raise an error if the barcode code is invalid. """should raise an error if the barcode code is invalid."""
"""
instance = printer.Dummy() instance = printer.Dummy()
with pytest.raises(BarcodeCodeError): with pytest.raises(BarcodeCodeError):
instance.barcode(data, bctype) instance.barcode(data, bctype)
assert instance.output == b'' assert instance.output == b""

View File

@@ -1,8 +1,4 @@
#!/usr/bin/python #!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
from escpos.exceptions import CashDrawerError from escpos.exceptions import CashDrawerError
@@ -10,10 +6,8 @@ import pytest
def test_raise_CashDrawerError(): def test_raise_CashDrawerError():
"""should raise an error if the sequence is invalid. """should raise an error if the sequence is invalid."""
"""
instance = printer.Dummy() instance = printer.Dummy()
with pytest.raises(CashDrawerError): with pytest.raises(CashDrawerError):
# call with sequence that is too long # call with sequence that is too long
instance.cashdraw([1,1,1,1,1,1]) instance.cashdraw([1, 1, 1, 1, 1, 1])

View File

@@ -1,104 +1,106 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
import pytest import pytest
@pytest.mark.parametrize("bctype,data", [ @pytest.mark.parametrize(
('UPC-A', '01234567890'), "bctype,data",
('UPC-A', '012345678905'), [
('UPC-E', '01234567'), ("UPC-A", "01234567890"),
('UPC-E', '0123456'), ("UPC-A", "012345678905"),
('UPC-E', '012345678905'), ("UPC-E", "01234567"),
('EAN13', '0123456789012'), ("UPC-E", "0123456"),
('EAN13', '012345678901'), ("UPC-E", "012345678905"),
('EAN8', '01234567'), ("EAN13", "0123456789012"),
('EAN8', '0123456'), ("EAN13", "012345678901"),
('CODE39', 'ABC-1234'), ("EAN8", "01234567"),
('CODE39', 'ABC-1234-$$-+A'), ("EAN8", "0123456"),
('CODE39', '*WIKIPEDIA*'), ("CODE39", "ABC-1234"),
('ITF', '010203040506070809'), ("CODE39", "ABC-1234-$$-+A"),
('ITF', '11221133113344556677889900'), ("CODE39", "*WIKIPEDIA*"),
('CODABAR', 'A2030405060B'), ("ITF", "010203040506070809"),
('CODABAR', 'C11221133113344556677889900D'), ("ITF", "11221133113344556677889900"),
('CODABAR', 'D0D'), ("CODABAR", "A2030405060B"),
('NW7', 'A2030405060B'), ("CODABAR", "C11221133113344556677889900D"),
('NW7', 'C11221133113344556677889900D'), ("CODABAR", "D0D"),
('NW7', 'D0D'), ("NW7", "A2030405060B"),
('CODE93', 'A2030405060B'), ("NW7", "C11221133113344556677889900D"),
('CODE93', '+:$&23-7@$'), ("NW7", "D0D"),
('CODE93', 'D0D'), ("CODE93", "A2030405060B"),
('CODE128', '{A2030405060B'), ("CODE93", "+:$&23-7@$"),
('CODE128', '{C+:$&23-7@$'), ("CODE93", "D0D"),
('CODE128', '{B0D'), ("CODE128", "{A2030405060B"),
('GS1-128', '{A2030405060B'), ("CODE128", "{C+:$&23-7@$"),
('GS1-128', '{C+:$&23-7@$'), ("CODE128", "{B0D"),
('GS1-128', '{B0D'), ("GS1-128", "{A2030405060B"),
('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'), ("GS1-128", "{C+:$&23-7@$"),
('GS1 DATABAR TRUNCATED', '0123456789123'), ("GS1-128", "{B0D"),
('GS1 DATABAR LIMITED', '0123456789123'), ("GS1 DATABAR OMNIDIRECTIONAL", "0123456789123"),
('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'), ("GS1 DATABAR TRUNCATED", "0123456789123"),
('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'), ("GS1 DATABAR LIMITED", "0123456789123"),
('GS1 DATABAR EXPANDED', '(00000001234567678'), ("GS1 DATABAR EXPANDED", "(9A{A20304+-%&06a0B"),
]) ("GS1 DATABAR EXPANDED", "(1 {C+:&23-7%"),
("GS1 DATABAR EXPANDED", "(00000001234567678"),
],
)
def test_check_valid_barcode(bctype, data): def test_check_valid_barcode(bctype, data):
assert (printer.Escpos.check_barcode(bctype, data)) assert printer.Escpos.check_barcode(bctype, data)
@pytest.mark.parametrize("bctype,data", [ @pytest.mark.parametrize(
('UPC-A', '01234567890123'), # too long "bctype,data",
('UPC-A', '0123456789'), # too short [
('UPC-A', '72527273-711'), # invalid '-' ("UPC-A", "01234567890123"), # too long
('UPC-A', 'A12345678901'), # invalid 'A' ("UPC-A", "0123456789"), # too short
('UPC-E', '01234567890123'), # too long ("UPC-A", "72527273-711"), # invalid '-'
('UPC-E', '012345'), # too short ("UPC-A", "A12345678901"), # invalid 'A'
('UPC-E', '72527-2'), # invalid '-' ("UPC-E", "01234567890123"), # too long
('UPC-E', 'A123456'), # invalid 'A' ("UPC-E", "012345"), # too short
('EAN13', '0123456789'), # too short ("UPC-E", "72527-2"), # invalid '-'
('EAN13', 'A123456789012'), # invalid 'A' ("UPC-E", "A123456"), # invalid 'A'
('EAN13', '012345678901234'), # too long ("EAN13", "0123456789"), # too short
('EAN8', '012345'), # too short ("EAN13", "A123456789012"), # invalid 'A'
('EAN8', 'A123456789012'), # invalid 'A' ("EAN13", "012345678901234"), # too long
('EAN8', '012345678901234'), # too long ("EAN8", "012345"), # too short
('CODE39', 'ALKJ_34'), # invalid '_' ("EAN8", "A123456789012"), # invalid 'A'
('CODE39', 'A' * 256), # too long ("EAN8", "012345678901234"), # too long
('ITF', '010203040'), # odd length ("CODE39", "ALKJ_34"), # invalid '_'
('ITF', '0' * 256), # too long ("CODE39", "A" * 256), # too long
('ITF', 'AB01'), # invalid 'A' ("ITF", "010203040"), # odd length
('CODABAR', '010203040'), # no start/stop ("ITF", "0" * 256), # too long
('CODABAR', '0' * 256), # too long ("ITF", "AB01"), # invalid 'A'
('CODABAR', 'AB-01F'), # invalid 'B' ("CODABAR", "010203040"), # no start/stop
('NW7', '010203040'), # no start/stop ("CODABAR", "0" * 256), # too long
('NW7', '0' * 256), # too long ("CODABAR", "AB-01F"), # invalid 'B'
('NW7', 'AB-01F'), # invalid 'B' ("NW7", "010203040"), # no start/stop
('CODE93', 'é010203040'), # invalid 'é' ("NW7", "0" * 256), # too long
('CODE93', '0' * 256), # too long ("NW7", "AB-01F"), # invalid 'B'
('CODE128', '010203040'), # missing leading { ("CODE93", "é010203040"), # invalid 'é'
('CODE128', '{D2354AA'), # second char not between A-C ("CODE93", "0" * 256), # too long
('CODE128', '0' * 256), # too long ("CODE128", "010203040"), # missing leading {
('GS1-128', '010203040'), # missing leading { ("CODE128", "{D2354AA"), # second char not between A-C
('GS1-128', '{D2354AA'), # second char not between A-C ("CODE128", "0" * 256), # too long
('GS1-128', '0' * 256), # too long ("GS1-128", "010203040"), # missing leading {
('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'), # too long ("GS1-128", "{D2354AA"), # second char not between A-C
('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'), # too short ("GS1-128", "0" * 256), # too long
('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'), # invalid 'A' ("GS1 DATABAR OMNIDIRECTIONAL", "01234567891234"), # too long
('GS1 DATABAR TRUNCATED', '01234567891234'), # too long ("GS1 DATABAR OMNIDIRECTIONAL", "012345678912"), # too short
('GS1 DATABAR TRUNCATED', '012345678912'), # too short ("GS1 DATABAR OMNIDIRECTIONAL", "012345678A1234"), # invalid 'A'
('GS1 DATABAR TRUNCATED', '012345678A1234'), # invalid 'A' ("GS1 DATABAR TRUNCATED", "01234567891234"), # too long
('GS1 DATABAR LIMITED', '01234567891234'), # too long ("GS1 DATABAR TRUNCATED", "012345678912"), # too short
('GS1 DATABAR LIMITED', '012345678912'), # too short ("GS1 DATABAR TRUNCATED", "012345678A1234"), # invalid 'A'
('GS1 DATABAR LIMITED', '012345678A1234'), # invalid 'A' ("GS1 DATABAR LIMITED", "01234567891234"), # too long
('GS1 DATABAR LIMITED', '02345678912341'), # invalid start (should be 01) ("GS1 DATABAR LIMITED", "012345678912"), # too short
('GS1 DATABAR EXPANDED', '010203040'), # missing leading ( ("GS1 DATABAR LIMITED", "012345678A1234"), # invalid 'A'
('GS1-128', '(' + ('0' * 256)), # too long ("GS1 DATABAR LIMITED", "02345678912341"), # invalid start (should be 01)
('GS1 DATABAR EXPANDED', '(a{D2354AA'), # second char not between 0-9 ("GS1 DATABAR EXPANDED", "010203040"), # missing leading (
('GS1 DATABAR EXPANDED', 'IT will fail'), # first char not '(' ("GS1-128", "(" + ("0" * 256)), # too long
]) ("GS1 DATABAR EXPANDED", "(a{D2354AA"), # second char not between 0-9
("GS1 DATABAR EXPANDED", "IT will fail"), # first char not '('
],
)
def test_check_invalid_barcode(bctype, data): def test_check_invalid_barcode(bctype, data):
assert (not printer.Escpos.check_barcode(bctype, data)) assert not printer.Escpos.check_barcode(bctype, data)

View File

@@ -1,8 +1,3 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six import six
import escpos.printer as printer import escpos.printer as printer
@@ -13,5 +8,5 @@ def test_cut_without_feed():
"""Test cut without feeding paper""" """Test cut without feeding paper"""
instance = printer.Dummy() instance = printer.Dummy()
instance.cut(feed=False) instance.cut(feed=False)
expected = GS + b'V' + six.int2byte(66) + b'\x00' expected = GS + b"V" + six.int2byte(66) + b"\x00"
assert(instance.output == expected) assert instance.output == expected

View File

@@ -1,8 +1,9 @@
from nose.tools import assert_raises from nose.tools import assert_raises
from escpos.printer import Dummy from escpos.printer import Dummy
def test_printer_dummy_clear(): def test_printer_dummy_clear():
printer = Dummy() printer = Dummy()
printer.text("Hello") printer.text("Hello")
printer.clear() printer.clear()
assert(printer.output == b'') assert printer.output == b""

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest import pytest
@@ -26,13 +22,13 @@ def test_bit_image_black():
Test printing solid black bit image (raster) Test printing solid black bit image (raster)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_black.png', impl="bitImageRaster") instance.image("test/resources/canvas_black.png", impl="bitImageRaster")
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80') assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80"
# Same thing w/ object created on the fly, rather than a filename # Same thing w/ object created on the fly, rather than a filename
instance = printer.Dummy() instance = printer.Dummy()
im = Image.new("RGB", (1, 1), (0, 0, 0)) im = Image.new("RGB", (1, 1), (0, 0, 0))
instance.image(im, impl="bitImageRaster") instance.image(im, impl="bitImageRaster")
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x80') assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80"
def test_bit_image_white(): def test_bit_image_white():
@@ -40,8 +36,8 @@ def test_bit_image_white():
Test printing solid white bit image (raster) Test printing solid white bit image (raster)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_white.png', impl="bitImageRaster") instance.image("test/resources/canvas_white.png", impl="bitImageRaster")
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\x00') assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00"
def test_bit_image_both(): def test_bit_image_both():
@@ -49,8 +45,8 @@ def test_bit_image_both():
Test printing black/white bit image (raster) Test printing black/white bit image (raster)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="bitImageRaster") instance.image("test/resources/black_white.png", impl="bitImageRaster")
assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00') assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00"
def test_bit_image_transparent(): def test_bit_image_transparent():
@@ -58,8 +54,8 @@ def test_bit_image_transparent():
Test printing black/transparent bit image (raster) Test printing black/transparent bit image (raster)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_transparent.png', impl="bitImageRaster") instance.image("test/resources/black_transparent.png", impl="bitImageRaster")
assert(instance.output == b'\x1dv0\x00\x01\x00\x02\x00\xc0\x00') assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00"
# Column format print # Column format print
@@ -68,8 +64,8 @@ def test_bit_image_colfmt_black():
Test printing solid black bit image (column format) Test printing solid black bit image (column format)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_black.png', impl="bitImageColumn") instance.image("test/resources/canvas_black.png", impl="bitImageColumn")
assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2') assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2"
def test_bit_image_colfmt_white(): def test_bit_image_colfmt_white():
@@ -77,8 +73,8 @@ def test_bit_image_colfmt_white():
Test printing solid white bit image (column format) Test printing solid white bit image (column format)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_white.png', impl="bitImageColumn") instance.image("test/resources/canvas_white.png", impl="bitImageColumn")
assert(instance.output == b'\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2') assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2"
def test_bit_image_colfmt_both(): def test_bit_image_colfmt_both():
@@ -86,8 +82,10 @@ def test_bit_image_colfmt_both():
Test printing black/white bit image (column format) Test printing black/white bit image (column format)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="bitImageColumn") instance.image("test/resources/black_white.png", impl="bitImageColumn")
assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2') assert (
instance.output == b"\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2"
)
def test_bit_image_colfmt_transparent(): def test_bit_image_colfmt_transparent():
@@ -95,8 +93,10 @@ def test_bit_image_colfmt_transparent():
Test printing black/transparent bit image (column format) Test printing black/transparent bit image (column format)
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_transparent.png', impl="bitImageColumn") instance.image("test/resources/black_transparent.png", impl="bitImageColumn")
assert(instance.output == b'\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2') assert (
instance.output == b"\x1b3\x10\x1b*!\x02\x00\x80\x00\x00\x80\x00\x00\x0a\x1b2"
)
# Graphics print # Graphics print
@@ -105,8 +105,11 @@ def test_graphics_black():
Test printing solid black graphics Test printing solid black graphics
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_black.png', impl="graphics") instance.image("test/resources/canvas_black.png", impl="graphics")
assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002') assert (
instance.output
== b"\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x80\x1d(L\x02\x0002"
)
def test_graphics_white(): def test_graphics_white():
@@ -114,8 +117,11 @@ def test_graphics_white():
Test printing solid white graphics Test printing solid white graphics
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/canvas_white.png', impl="graphics") instance.image("test/resources/canvas_white.png", impl="graphics")
assert(instance.output == b'\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002') assert (
instance.output
== b"\x1d(L\x0b\x000p0\x01\x011\x01\x00\x01\x00\x00\x1d(L\x02\x0002"
)
def test_graphics_both(): def test_graphics_both():
@@ -123,8 +129,11 @@ def test_graphics_both():
Test printing black/white graphics Test printing black/white graphics
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="graphics") instance.image("test/resources/black_white.png", impl="graphics")
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') assert (
instance.output
== b"\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002"
)
def test_graphics_transparent(): def test_graphics_transparent():
@@ -132,8 +141,11 @@ def test_graphics_transparent():
Test printing black/transparent graphics Test printing black/transparent graphics
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_transparent.png', impl="graphics") instance.image("test/resources/black_transparent.png", impl="graphics")
assert(instance.output == b'\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002') assert (
instance.output
== b"\x1d(L\x0c\x000p0\x01\x011\x02\x00\x02\x00\xc0\x00\x1d(L\x02\x0002"
)
def test_large_graphics(): def test_large_graphics():
@@ -141,20 +153,19 @@ def test_large_graphics():
Test whether 'large' graphics that induce a fragmentation are handled correctly. Test whether 'large' graphics that induce a fragmentation are handled correctly.
""" """
instance = printer.Dummy() instance = printer.Dummy()
instance.image('test/resources/black_white.png', impl="bitImageRaster", fragment_height=1) instance.image(
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') "test/resources/black_white.png", impl="bitImageRaster", fragment_height=1
)
assert (
instance.output
== b"\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00"
)
@pytest.fixture @pytest.fixture
def dummy_with_width(): def dummy_with_width():
instance = printer.Dummy() instance = printer.Dummy()
instance.profile.profile_data = { instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
'media': {
'width': {
'pixels': 384
}
}
}
return instance return instance

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
@@ -19,17 +15,18 @@ def test_function_linedisplay_select_on():
"""test the linedisplay_select function (activate)""" """test the linedisplay_select function (activate)"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_select(select_display=True) instance.linedisplay_select(select_display=True)
assert(instance.output == b'\x1B\x3D\x02') assert instance.output == b"\x1B\x3D\x02"
def test_function_linedisplay_select_off(): def test_function_linedisplay_select_off():
"""test the linedisplay_select function (deactivate)""" """test the linedisplay_select function (deactivate)"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_select(select_display=False) instance.linedisplay_select(select_display=False)
assert(instance.output == b'\x1B\x3D\x01') assert instance.output == b"\x1B\x3D\x01"
def test_function_linedisplay_clear(): def test_function_linedisplay_clear():
"""test the linedisplay_clear function""" """test the linedisplay_clear function"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_clear() instance.linedisplay_clear()
assert(instance.output == b'\x1B\x40') assert instance.output == b"\x1B\x40"

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
@@ -19,11 +15,11 @@ def test_function_panel_button_on():
"""test the panel button function (enabling) by comparing output""" """test the panel button function (enabling) by comparing output"""
instance = printer.Dummy() instance = printer.Dummy()
instance.panel_buttons() instance.panel_buttons()
assert(instance.output == b'\x1B\x63\x35\x00') assert instance.output == b"\x1B\x63\x35\x00"
def test_function_panel_button_off(): def test_function_panel_button_off():
"""test the panel button function (disabling) by comparing output""" """test the panel button function (disabling) by comparing output"""
instance = printer.Dummy() instance = printer.Dummy()
instance.panel_buttons(False) instance.panel_buttons(False)
assert(instance.output == b'\x1B\x63\x35\x01') assert instance.output == b"\x1B\x63\x35\x01"

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from nose.tools import raises from nose.tools import raises
import pytest import pytest
@@ -23,42 +19,51 @@ def test_defaults():
"""Test QR code with defaults""" """Test QR code with defaults"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=True) instance.qr("1234", native=True)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \ expected = (
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d"
assert(instance.output == expected) b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_empty(): def test_empty():
"""Test QR printing blank code""" """Test QR printing blank code"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("", native=True) instance.qr("", native=True)
assert(instance.output == b'') assert instance.output == b""
def test_ec(): def test_ec():
"""Test QR error correction setting""" """Test QR error correction setting"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=True, ec=QR_ECLEVEL_H) instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d' \ expected = (
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d"
assert(instance.output == expected) b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_size(): def test_size():
"""Test QR box size""" """Test QR box size"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=True, size=7) instance.qr("1234", native=True, size=7)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d' \ expected = (
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d"
assert(instance.output == expected) b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_model(): def test_model():
"""Test QR model""" """Test QR model"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=True, model=QR_MODEL_1) instance.qr("1234", native=True, model=QR_MODEL_1)
expected = b'\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \ expected = (
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b"\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d"
assert(instance.output == expected) b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
@raises(ValueError) @raises(ValueError)
@@ -88,12 +93,14 @@ def test_image():
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1", native=False, size=1) instance.qr("1", native=False, size=1)
print(instance.output) print(instance.output)
expected = b'\x1bt\x00\n' \ expected = (
b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \ b"\x1bt\x00\n"
b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \ b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et"
b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \ b"]ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00"
b'\n\n' b"i(\x7f<\xa8A \xd8]'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00"
assert(instance.output == expected) b"\n\n"
)
assert instance.output == expected
@raises(ValueError) @raises(ValueError)

View File

@@ -8,10 +8,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest import pytest
import mock import mock
@@ -20,7 +16,7 @@ from escpos.printer import Dummy
from PIL import Image from PIL import Image
@mock.patch('escpos.printer.Dummy.image', spec=Dummy) @mock.patch("escpos.printer.Dummy.image", spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function): def test_type_of_object_passed_to_image_function(img_function):
""" """
Test the type of object that is passed to the image function during non-native qr-printing. Test the type of object that is passed to the image function during non-native qr-printing.

View File

@@ -1,8 +1,3 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six import six
import escpos.printer as printer import escpos.printer as printer
@@ -12,41 +7,46 @@ from escpos.constants import TXT_SIZE
# Default test, please copy and paste this block to test set method calls # Default test, please copy and paste this block to test set method calls
def test_default_values(): def test_default_values():
instance = printer.Dummy() instance = printer.Dummy()
instance.set() instance.set()
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Size tests # Size tests
def test_set_size_2h(): def test_set_size_2h():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_height=True) instance.set(double_height=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2h'], # Double height text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["2h"], # Double height text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_set_size_2w(): def test_set_size_2w():
@@ -54,17 +54,18 @@ def test_set_size_2w():
instance.set(double_width=True) instance.set(double_width=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2w'], # Double width text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["2w"], # Double width text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_set_size_2x(): def test_set_size_2x():
@@ -72,17 +73,18 @@ def test_set_size_2x():
instance.set(double_height=True, double_width=True) instance.set(double_height=True, double_width=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2x'], # Double text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["2x"], # Double text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_set_size_custom(): def test_set_size_custom():
@@ -91,55 +93,61 @@ def test_set_size_custom():
expected_sequence = ( expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]), six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]),
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b'\x00'), # Default font SET_FONT(b"\x00"), # Default font
TXT_STYLE['align']['left'], # Align left TXT_STYLE["align"]["left"], # Align left
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Flip # Flip
def test_set_flip(): def test_set_flip():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(flip=True) instance.set(flip=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][True], # Flip ON TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][True], # Flip ON
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Smooth # Smooth
def test_smooth(): def test_smooth():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(smooth=True) instance.set(smooth=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][True], # Smooth ON TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][True], # Smooth ON
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Type # Type
@@ -150,17 +158,18 @@ def test_set_bold():
instance.set(bold=True) instance.set(bold=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][True], # Bold ON TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][True], # Bold ON
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_set_underline(): def test_set_underline():
@@ -168,17 +177,18 @@ def test_set_underline():
instance.set(underline=1) instance.set(underline=1)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][1], # Underline ON, type 1 TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][1], # Underline ON, type 1
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_set_underline2(): def test_set_underline2():
@@ -186,95 +196,102 @@ def test_set_underline2():
instance.set(underline=2) instance.set(underline=2)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][2], # Underline ON, type 2 TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][2], # Underline ON, type 2
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert (instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Align # Align
def test_align_center(): def test_align_center():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(align='center') instance.set(align="center")
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['center'], # Align center SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["center"], # Align center
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
def test_align_right(): def test_align_right():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(align='right') instance.set(align="right")
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['right'], # Align right SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["align"]["right"], # Align right
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Densities # Densities
def test_densities():
def test_densities():
for density in range(8): for density in range(8):
instance = printer.Dummy() instance = printer.Dummy()
instance.set(density=density) instance.set(density=density)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['density'][density], # Custom density from 0 to 8 TXT_STYLE["align"]["left"], # Align left
TXT_STYLE['invert'][False] # Inverted OFF TXT_STYLE["density"][density], # Custom density from 0 to 8
TXT_STYLE["invert"][False], # Inverted OFF
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)
# Invert # Invert
def test_invert(): def test_invert():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(invert=True) instance.set(invert=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size TXT_NORMAL,
TXT_STYLE['flip'][False], # Flip OFF TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE['smooth'][False], # Smooth OFF TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE['bold'][False], # Bold OFF TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE['underline'][0], # Underline OFF TXT_STYLE["bold"][False], # Bold OFF
SET_FONT(b'\x00'), # Default font TXT_STYLE["underline"][0], # Underline OFF
TXT_STYLE['align']['left'], # Align left SET_FONT(b"\x00"), # Default font
TXT_STYLE['invert'][True] # Inverted ON TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][True], # Inverted ON
) )
assert(instance.output == b''.join(expected_sequence)) assert instance.output == b"".join(expected_sequence)

View File

@@ -1,16 +1,25 @@
#!/usr/bin/python #!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
import barcode.errors
import pytest import pytest
def test_soft_barcode(): @pytest.fixture
"""just execute soft_barcode def instance():
""" return printer.Dummy()
instance = printer.Dummy()
instance.soft_barcode("ean8", "1234")
def test_soft_barcode_ean8_invalid(instance):
"""test with an invalid barcode"""
with pytest.raises(barcode.errors.BarcodeError):
instance.soft_barcode("ean8", "1234")
def test_soft_barcode_ean8(instance):
"""test with a valid ean8 barcode"""
instance.soft_barcode("ean8", "1234567")
def test_soft_barcode_ean8_nocenter(instance):
instance.soft_barcode("ean8", "1234567", center=False)

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest import pytest
import mock import mock
@@ -20,13 +16,12 @@ from escpos.printer import Dummy
def get_printer(): def get_printer():
return Dummy(magic_encode_args={'disabled': True, 'encoding': 'CP437'}) return Dummy(magic_encode_args={"disabled": True, "encoding": "CP437"})
@given(text=st.text()) @given(text=st.text())
def test_text(text): def test_text(text):
"""Test that text() calls the MagicEncode object. """Test that text() calls the MagicEncode object."""
"""
instance = get_printer() instance = get_printer()
instance.magic.write = mock.Mock() instance.magic.write = mock.Mock()
instance.text(text) instance.text(text)
@@ -36,30 +31,32 @@ def test_text(text):
def test_block_text(): def test_block_text():
printer = get_printer() printer = get_printer()
printer.block_text( printer.block_text(
"All the presidents men were eating falafel for breakfast.", font='a') "All the presidents men were eating falafel for breakfast.", font="a"
assert printer.output == \ )
b'All the presidents men were eating falafel\nfor breakfast.' assert (
printer.output == b"All the presidents men were eating falafel\nfor breakfast."
)
def test_textln(): def test_textln():
printer = get_printer() printer = get_printer()
printer.textln('hello, world') printer.textln("hello, world")
assert printer.output == b'hello, world\n' assert printer.output == b"hello, world\n"
def test_textln_empty(): def test_textln_empty():
printer = get_printer() printer = get_printer()
printer.textln() printer.textln()
assert printer.output == b'\n' assert printer.output == b"\n"
def test_ln(): def test_ln():
printer = get_printer() printer = get_printer()
printer.ln() printer.ln()
assert printer.output == b'\n' assert printer.output == b"\n"
def test_multiple_ln(): def test_multiple_ln():
printer = get_printer() printer = get_printer()
printer.ln(3) printer.ln(3)
assert printer.output == b'\n\n\n' assert printer.output == b"\n\n\n"

View File

@@ -5,13 +5,13 @@ from escpos.printer import Dummy
def test_line_spacing_code_gen(): def test_line_spacing_code_gen():
printer = Dummy() printer = Dummy()
printer.line_spacing(10) printer.line_spacing(10)
assert printer.output == b'\x1b3\n' assert printer.output == b"\x1b3\n"
def test_line_spacing_rest(): def test_line_spacing_rest():
printer = Dummy() printer = Dummy()
printer.line_spacing() printer.line_spacing()
assert printer.output == b'\x1b2' assert printer.output == b"\x1b2"
def test_line_spacing_error_handling(): def test_line_spacing_error_handling():

View File

@@ -15,57 +15,67 @@ def test_image_black():
""" """
Test rendering solid black image Test rendering solid black image
""" """
for img_format in ['png', 'jpg', 'gif']: for img_format in ["png", "jpg", "gif"]:
_load_and_check_img('canvas_black.' + img_format, 1, 1, b'\x80', [b'\x80']) _load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"])
def test_image_black_transparent(): def test_image_black_transparent():
""" """
Test rendering black/transparent image Test rendering black/transparent image
""" """
for img_format in ['png', 'gif']: for img_format in ["png", "gif"]:
_load_and_check_img('black_transparent.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80']) _load_and_check_img(
"black_transparent." + img_format, 2, 2, b"\xc0\x00", [b"\x80\x80"]
)
def test_image_black_white(): def test_image_black_white():
""" """
Test rendering black/white image Test rendering black/white image
""" """
for img_format in ['png', 'jpg', 'gif']: for img_format in ["png", "jpg", "gif"]:
_load_and_check_img('black_white.' + img_format, 2, 2, b'\xc0\x00', [b'\x80\x80']) _load_and_check_img(
"black_white." + img_format, 2, 2, b"\xc0\x00", [b"\x80\x80"]
)
def test_image_white(): def test_image_white():
""" """
Test rendering solid white image Test rendering solid white image
""" """
for img_format in ['png', 'jpg', 'gif']: for img_format in ["png", "jpg", "gif"]:
_load_and_check_img('canvas_white.' + img_format, 1, 1, b'\x00', [b'\x00']) _load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"])
def test_split(): def test_split():
""" """
test whether the split-function works as expected test whether the split-function works as expected
""" """
im = EscposImage('test/resources/black_white.png') im = EscposImage("test/resources/black_white.png")
(upper_part, lower_part) = im.split(1) (upper_part, lower_part) = im.split(1)
upper_part = EscposImage(upper_part) upper_part = EscposImage(upper_part)
lower_part = EscposImage(lower_part) lower_part = EscposImage(lower_part)
assert(upper_part.width == lower_part.width == 2) assert upper_part.width == lower_part.width == 2
assert(upper_part.height == lower_part.height == 1) assert upper_part.height == lower_part.height == 1
assert(upper_part.to_raster_format() == b'\xc0') assert upper_part.to_raster_format() == b"\xc0"
assert(lower_part.to_raster_format() == b'\x00') assert lower_part.to_raster_format() == b"\x00"
def _load_and_check_img(filename, width_expected, height_expected, raster_format_expected, column_format_expected): def _load_and_check_img(
filename,
width_expected,
height_expected,
raster_format_expected,
column_format_expected,
):
""" """
Load an image, and test whether raster & column formatted output, sizes, etc match expectations. Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
""" """
im = EscposImage('test/resources/' + filename) im = EscposImage("test/resources/" + filename)
assert(im.width == width_expected) assert im.width == width_expected
assert(im.height == height_expected) assert im.height == height_expected
assert(im.to_raster_format() == raster_format_expected) assert im.to_raster_format() == raster_format_expected
i = 0 i = 0
for row in im.to_column_format(False): for row in im.to_column_format(False):
assert(row == column_format_expected[i]) assert row == column_format_expected[i]
i += 1 i += 1

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
@@ -18,4 +14,4 @@ import escpos.printer as printer
def test_instantiation(): def test_instantiation():
"""test the instantiation of a escpos-printer class and basic printing""" """test the instantiation of a escpos-printer class and basic printing"""
instance = printer.Dummy() instance = printer.Dummy()
instance.text('This is a test\n') instance.text("This is a test\n")

View File

@@ -8,10 +8,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest import pytest
from nose.tools import raises, assert_raises from nose.tools import raises, assert_raises
@@ -28,17 +24,17 @@ class TestEncoder:
""" """
def test_can_encode(self): def test_can_encode(self):
assert not Encoder({'CP437': 1}).can_encode('CP437', u'') assert not Encoder({"CP437": 1}).can_encode("CP437", "")
assert Encoder({'CP437': 1}).can_encode('CP437', u'á') assert Encoder({"CP437": 1}).can_encode("CP437", "á")
assert not Encoder({'foobar': 1}).can_encode('foobar', 'a') assert not Encoder({"foobar": 1}).can_encode("foobar", "a")
def test_find_suitable_encoding(self): def test_find_suitable_encoding(self):
assert not Encoder({'CP437': 1}).find_suitable_encoding(u'') assert not Encoder({"CP437": 1}).find_suitable_encoding("")
assert Encoder({'CP858': 1}).find_suitable_encoding(u'') == 'CP858' assert Encoder({"CP858": 1}).find_suitable_encoding("") == "CP858"
@raises(ValueError) @raises(ValueError)
def test_get_encoding(self): def test_get_encoding(self):
Encoder({}).get_encoding_name('latin1') Encoder({}).get_encoding_name("latin1")
class TestMagicEncode: class TestMagicEncode:
@@ -61,50 +57,50 @@ class TestMagicEncode:
MagicEncode(driver, disabled=True) MagicEncode(driver, disabled=True)
class TestWriteWithEncoding: class TestWriteWithEncoding:
def test_init_from_none(self, driver): def test_init_from_none(self, driver):
encode = MagicEncode(driver, encoding=None) encode = MagicEncode(driver, encoding=None)
encode.write_with_encoding('CP858', '€ ist teuro.') encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b'\x1bt\x13\xd5 ist teuro.' assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_change_from_another(self, driver): def test_change_from_another(self, driver):
encode = MagicEncode(driver, encoding='CP437') encode = MagicEncode(driver, encoding="CP437")
encode.write_with_encoding('CP858', '€ ist teuro.') encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b'\x1bt\x13\xd5 ist teuro.' assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_no_change(self, driver): def test_no_change(self, driver):
encode = MagicEncode(driver, encoding='CP858') encode = MagicEncode(driver, encoding="CP858")
encode.write_with_encoding('CP858', '€ ist teuro.') encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b'\xd5 ist teuro.' assert driver.output == b"\xd5 ist teuro."
class TestWrite: class TestWrite:
def test_write(self, driver): def test_write(self, driver):
encode = MagicEncode(driver) encode = MagicEncode(driver)
encode.write('€ ist teuro.') encode.write("€ ist teuro.")
assert driver.output == b'\x1bt\x0f\xa4 ist teuro.' assert driver.output == b"\x1bt\x0f\xa4 ist teuro."
def test_write_disabled(self, driver): def test_write_disabled(self, driver):
encode = MagicEncode(driver, encoding='CP437', disabled=True) encode = MagicEncode(driver, encoding="CP437", disabled=True)
encode.write('€ ist teuro.') encode.write("€ ist teuro.")
assert driver.output == b'? ist teuro.' assert driver.output == b"? ist teuro."
def test_write_no_codepage(self, driver): def test_write_no_codepage(self, driver):
encode = MagicEncode( encode = MagicEncode(
driver, defaultsymbol="_", encoder=Encoder({'CP437': 1}), driver,
encoding='CP437') defaultsymbol="_",
encode.write(u'€ ist teuro.') encoder=Encoder({"CP437": 1}),
assert driver.output == b'_ ist teuro.' encoding="CP437",
)
encode.write("€ ist teuro.")
assert driver.output == b"_ ist teuro."
class TestForceEncoding: class TestForceEncoding:
def test(self, driver): def test(self, driver):
encode = MagicEncode(driver) encode = MagicEncode(driver)
encode.force_encoding('CP437') encode.force_encoding("CP437")
assert driver.output == b'\x1bt\x00' assert driver.output == b"\x1bt\x00"
encode.write('€ ist teuro.') encode.write("€ ist teuro.")
assert driver.output == b'\x1bt\x00? ist teuro.' assert driver.output == b"\x1bt\x00? ist teuro."
try: try:
@@ -123,5 +119,5 @@ class TestKatakana:
encode_katakana(text) encode_katakana(text)
def test_result(self): def test_result(self):
assert encode_katakana('カタカナ') == b'\xb6\xc0\xb6\xc5' assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
assert encode_katakana("あいうえお") == b'\xb1\xb2\xb3\xb4\xb5' assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5"

View File

@@ -8,10 +8,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import six import six
@@ -22,17 +18,16 @@ from hypothesis.strategies import text
import escpos.printer as printer import escpos.printer as printer
if six.PY3: if six.PY3:
mock_open_call = 'builtins.open' mock_open_call = "builtins.open"
else: else:
mock_open_call = '__builtin__.open' mock_open_call = "__builtin__.open"
@pytest.mark.skip("this test is broken and has to be fixed or discarded") @pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(use_coverage=False)
@given(path=text()) @given(path=text())
def test_load_file_printer(mocker, path): def test_load_file_printer(mocker, path):
"""test the loading of the file-printer""" """test the loading of the file-printer"""
mock_escpos = mocker.patch('escpos.escpos.Escpos.__init__') mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__")
mock_open = mocker.patch(mock_open_call) mock_open = mocker.patch(mock_open_call)
printer.File(devfile=path) printer.File(devfile=path)
assert mock_escpos.called assert mock_escpos.called
@@ -40,13 +35,12 @@ def test_load_file_printer(mocker, path):
@pytest.mark.skip("this test is broken and has to be fixed or discarded") @pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_auto_flush(mocker, txt): def test_auto_flush(mocker, txt):
"""test auto_flush in file-printer""" """test auto_flush in file-printer"""
mock_escpos = mocker.patch('escpos.escpos.Escpos.__init__') mock_escpos = mocker.patch("escpos.escpos.Escpos.__init__")
mock_open = mocker.patch(mock_open_call) mock_open = mocker.patch(mock_open_call)
mock_device = mocker.patch.object(printer.File, 'device') mock_device = mocker.patch.object(printer.File, "device")
p = printer.File(auto_flush=False) p = printer.File(auto_flush=False)
# inject the mocked device-object # inject the mocked device-object
@@ -62,12 +56,11 @@ def test_auto_flush(mocker, txt):
@pytest.mark.skip("this test is broken and has to be fixed or discarded") @pytest.mark.skip("this test is broken and has to be fixed or discarded")
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_flush_on_close(mocker, txt): def test_flush_on_close(mocker, txt):
"""test flush on close in file-printer""" """test flush on close in file-printer"""
mock_open = mocker.patch(mock_open_call) mock_open = mocker.patch(mock_open_call)
mock_device = mocker.patch.object(printer.File, 'device') mock_device = mocker.patch.object(printer.File, "device")
p = printer.File(auto_flush=False) p = printer.File(auto_flush=False)
# inject the mocked device-object # inject the mocked device-object

View File

@@ -0,0 +1,24 @@
#!/usr/bin/python
import escpos.printer as printer
import pytest
import mock
import socket
@pytest.fixture
def instance():
socket.socket.connect = mock.Mock()
return printer.Network("localhost")
def test_close_without_open(instance):
"""try to close without opening (should fail gracefully)
Currently we never open from our fixture, so calling close once
should be enough. In the future this might not be enough,
therefore we have to close twice in order to provoke an error
(if possible, this should not raise)
"""
instance.close()
instance.close()

View File

@@ -4,35 +4,33 @@ from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile
@pytest.fixture @pytest.fixture
def profile(): def profile():
return get_profile('default') return get_profile("default")
class TestBaseProfile: class TestBaseProfile:
"""Test the `BaseProfile` class. """Test the `BaseProfile` class."""
"""
def test_get_font(self, profile): def test_get_font(self, profile):
with pytest.raises(NotSupported): with pytest.raises(NotSupported):
assert profile.get_font('3') assert profile.get_font("3")
assert profile.get_font(1) == 1 assert profile.get_font(1) == 1
assert profile.get_font('a') == 0 assert profile.get_font("a") == 0
def test_supports(self, profile): def test_supports(self, profile):
assert not profile.supports('asdf asdf') assert not profile.supports("asdf asdf")
assert profile.supports(BARCODE_B) assert profile.supports(BARCODE_B)
def test_get_columns(self, profile): def test_get_columns(self, profile):
assert profile.get_columns('a') > 5 assert profile.get_columns("a") > 5
with pytest.raises(NotSupported): with pytest.raises(NotSupported):
assert profile.get_columns('asdfasdf') assert profile.get_columns("asdfasdf")
class TestCustomProfile: class TestCustomProfile:
"""Test custom profile options with the `Profile` class. """Test custom profile options with the `Profile` class."""
"""
def test_columns(self): def test_columns(self):
assert Profile(columns=10).get_columns('sdfasdf') == 10 assert Profile(columns=10).get_columns("sdfasdf") == 10
def test_features(self): def test_features(self):
assert Profile(features={'foo': True}).supports('foo') assert Profile(features={"foo": True}).supports("foo")

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest import pytest
import escpos import escpos

View File

@@ -7,10 +7,6 @@
:license: MIT :license: MIT
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
import escpos.escpos as escpos import escpos.escpos as escpos
@@ -20,5 +16,5 @@ def test_with_statement():
"""Use with statement""" """Use with statement"""
dummy_printer = printer.Dummy() dummy_printer = printer.Dummy()
with escpos.EscposIO(dummy_printer) as p: with escpos.EscposIO(dummy_printer) as p:
p.writelines('Some text.\n') p.writelines("Some text.\n")
# TODO extend these tests as they don't really do anything at the moment # TODO extend these tests as they don't really do anything at the moment

26
tox.ini
View File

@@ -1,5 +1,15 @@
[tox] [tox]
envlist = py27, py34, py35, py36, py37, docs, flake8 envlist = py37, py38, py39, py310, py311, docs, flake8
[gh-actions]
python =
2.7: py27
3.6: py36
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv] [testenv]
deps = nose deps = nose
@@ -10,17 +20,19 @@ deps = nose
pytest!=3.2.0,!=3.3.0 pytest!=3.2.0,!=3.3.0
pytest-cov pytest-cov
pytest-mock pytest-mock
hypothesis!=3.56.9,<4 hypothesis>4
viivakoodi python-barcode
commands = py.test --cov escpos commands = pytest --cov escpos
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR ESCPOS_CAPABILITIES_FILE CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* CODECOV_* passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_*
[testenv:docs] [testenv:docs]
basepython = python basepython = python
changedir = doc changedir = doc
deps = sphinx>=1.5.1 deps = sphinx>=3.0.0
setuptools_scm setuptools_scm
viivakoodi python-barcode
sphinxcontrib-spelling>=7.2.0
sphinx_rtd_theme
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
[testenv:flake8] [testenv:flake8]