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

214 Commits

Author SHA1 Message Date
Patrick Kanzler
90c89e66f0 filter for ValueError too in dpi logic 2023-11-17 00:48:31 +01:00
Patrick Kanzler
8f44ae7480 update changelog 2023-11-17 00:48:31 +01:00
Patrick Kanzler
ff5631c989 update authors 2023-11-17 00:48:31 +01:00
Patrick Kanzler
ecf3fbcc25 update capabilities data 2023-11-17 00:48:31 +01:00
Patrick Kanzler
678f8d2451 Merge pull request #579 from python-escpos:update-changelog
update changelog for new v3 release
2023-11-16 23:39:30 +01:00
Patrick Kanzler
a127c7b9c4 Merge branch 'master' into update-changelog 2023-11-16 23:32:22 +01:00
Patrick Kanzler
72d26879a0 Apply suggestions from code review 2023-11-01 19:37:38 +01:00
Benito López
3b6551004f Restore types-pywin32 (#591)
* Restore types-pywin32 dependency

* Restore pywin32 type annotations in win32raw

* Add return type to _raw()
2023-10-31 13:55:48 +01:00
Patrick Kanzler
5d3d2ca34b import TypedDict from typing, not from extensions (#589)
TypedDict is in the supported python versions
available in typing. Therefore an import from
potentially uninstalled typing_extensions is
not necessary.

fixes #560
2023-10-28 21:11:38 +02:00
Patrick Kanzler
d2ef5159c5 Merge branch 'master' into update-changelog 2023-10-28 20:53:21 +02:00
Benito López
a50a3b7167 Separate method open() and constructor, enhance consistency between connectors: Rework printer tests (#587)
* Add fixtures
* Add test_printer_file.py
* Remove old broken printer tests
* Include a close_on_reopen test
* Add test_printer_network.py
* Add test_printer_serial.py
* Add test_printer_usb.py
* Add test_printer_lp.py
* Add test_printer_cups.py
* Add test_printer_win32raw.py
* Test the 'printers' property
* Fix conftest import formatting
* Fix failing LP tests
* Cancel close only if literal False|None _device
* Fix win32raw failing tests (maybe)
* Include win32raw close_on_reopen test
* Include test _raw methods to win32raw
* Replace general exceptions in win32raw
* Replace wrong exception in cups
* Include more tests to cups
* Extend cups tests
2023-10-28 20:52:59 +02:00
dependabot[bot]
e7dd97554c Bump werkzeug from 2.3.4 to 3.0.1 in /examples/docker-flask (#588)
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 2.3.4 to 3.0.1.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/werkzeug/compare/2.3.4...3.0.1)

---
updated-dependencies:
- dependency-name: werkzeug
  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-10-26 18:55:56 +02:00
Benito López
425d3d209f Update capabilities data (#586) 2023-10-18 13:20:08 +02:00
Benito López
a00b98937b Separate method open() and constructor, enhance consistency between connectors. (#584)
* Add self-open mechanism
* self-open mechanism through the 'device' property
* Separate open() and store connection in 'device'
* Restore device status to False on close()
* Add default value to all params + type annotations
* Add generic DeviceNotFoundError exception
* Update USBNotFoundError return code
* Enhance connectors consistency
* Fix LP printer stall
* Fix LP waste of paper due to auto-flush + flush on close
* Move platform dependent printers' guard to init

---------

Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
2023-10-16 11:36:07 +02:00
Patrick Kanzler
a8d789fe64 update change log 2023-10-15 23:03:10 +02:00
Patrick Kanzler
246c4ea6c5 add template for new release 2023-10-09 00:16:38 +02:00
Patrick Kanzler
3a8af8a6f5 switch to python 3.12 (#582)
* switch to python 3.12
* 3.11 on RTD
* fix SyntaxWarning (regex strings were invalid partially)
2023-10-09 00:13:39 +02:00
Patrick Kanzler
e4f4844983 add action for windows build (#581)
* copy action for windows
* exclude cups on windows
* update changelog
2023-10-08 23:27:37 +02:00
Patrick Kanzler
a70e1604d6 Magic encoder: fix codepage usage (#580)
* add unit test for issue pointed out by @scott-r in #570

* swap order of encoder search

As described by @scott-r in
Magic encoder does not use
previously used code pages
when possible #570
Thank you!
2023-10-05 14:55:12 +02:00
Patrick Kanzler
5018f7f377 add checkpoint to release checklist for changelog (#578) 2023-10-05 14:22:21 +02:00
Patrick Kanzler
ecfeeb9b13 Improve diagnostic output (#577)
* add extended version information

* autodocument argparser

* add spelling exception

* fix docstrings

* add annotations

* use typing types

* add test
2023-10-05 14:15:19 +02:00
dependabot[bot]
ba0167f1fd Bump pillow from 9.5.0 to 10.0.1 in /examples/docker-flask (#576)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.5.0 to 10.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.1)

---
updated-dependencies:
- dependency-name: pillow
  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-10-04 08:44:44 +02:00
dependabot[bot]
87e488ce11 Bump actions/setup-python from 4.7.0 to 4.7.1 (#575)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.7.0...v4.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 20:10:41 +02:00
Patrick Kanzler
6d0c475b9a refactor set method (#547)
* add type annotations
* Update setup.cfg
* improve mypy and test config
* get_profile_class returns a baseProfile
* mute mypy type issue
* add set_with_default
* docstring
* add test for empty call
* correct type annotations in set_with_default
* improve tests
* test for exception
* sort with isort
* add default value to get call
* empty string has the same effect: will not be found --> None
* add mypy test to workflow
* explicitely call mypy
* update spelling
2023-09-19 07:51:39 +02:00
Patrick Kanzler
adcde8abda Install types-pywin32 for mypy (#568) 2023-09-17 23:39:16 +02:00
Patrick Kanzler
89aaae0186 get mypy configuration change from #547 (#567) 2023-09-17 23:24:16 +02:00
Patrick Kanzler
f3ee049ee1 Update spelling_wordlist.txt (#566)
* Update spelling_wordlist.txt

* update apt
2023-09-17 23:00:38 +02:00
Patrick Kanzler
cfa9ecf16d Enable spell checking (#563)
* add spellchecking

* improve spelling

* improve spelling config

* extend word list

* improve spelling

* improve spelling

* escalate warning in spell check to failure

* fix spelling

* fix spelling

* add plural

* Update doc/spelling_wordlist.txt

* do not stop on warning

* require newest sphinxcontrib spelling

* remove old comment

* add authors as single line entry to spelling list

* reenable stop on warning
2023-09-07 22:08:31 +02:00
Patrick Kanzler
8f07c1da0f isort imports on save (#564) 2023-09-06 23:42:01 +02:00
Patrick Kanzler
24217756f7 558 improve capabilities handling (#562)
* add handling for missing capabilities file

* improve documentation

* Update doc/user/installation.rst
2023-09-06 00:05:38 +02:00
dependabot[bot]
e3e1500d35 Bump actions/checkout from 3 to 4 (#561)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 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/v3...v4)

---
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>
2023-09-05 13:07:02 +02:00
Patrick Kanzler
44444f3c51 Maintenance: fix read the docs and some annotations (#557) 2023-09-03 09:57:56 +02:00
Patrick Kanzler
c7c01cdbff fix rtd theme (#553)
* use rtd_theme

* add types for serial

* annotate types where possible

* fix imports
2023-08-25 01:19:23 +02:00
dependabot[bot]
6c49e4a057 Bump sphinx-rtd-theme from 1.2.2 to 1.3.0 (#550) 2023-08-24 21:30:22 +00:00
Benito López
f23bcacabe fix typo in lp connector (#548) 2023-08-24 23:17:34 +02:00
Patrick Kanzler
c11d192e65 Fix import issues with qrcode in sphinx 7.2 (#552)
* 7.2 introduces the issue

* mock module qrcode for documentation build

This prevents issues with sphinx 7.2
2023-08-24 23:02:33 +02:00
Patrick Kanzler
3177c8d411 split off dependencies for optional installation (#546)
* add inheritance diagrams to all printers and exceptions
* split off printer implementations into separate files
* add wrapper that thros RuntimeError if not importable
* add dependency check for lp
* add dependency check for pyserial
* added check for usability
* import Win32Raw
* include WIn32Raw in documentation
* enable all extras on tox
* update github workflow
2023-08-17 01:37:50 +02:00
Patrick Kanzler
fbabd8ed88 Drop Py37, improve typing and docstrings (#544)
Drops Py3.7, improves typing and adds a mypy config, improves the docstrings and isorts the imports.

* configure isort
* sort with isort
* add github action
* enable flake8-docstrings
* fix docstrings
* add mypy env
* no implicit optional
* add type for raw
* add some type hints
2023-08-15 01:03:36 +02:00
Patrick Kanzler
2b62c8e28d modernize and cleanup documentation (#542)
* disable system packages on rtd

* install pycups on rtd

* enable cups binding in documentation

* document CupsPrinter

* fix formatting

* revise methods and installation

* revise user/printers

* revise raspi section

* further revise
2023-08-10 01:38:47 +02:00
Patrick Kanzler
4c2dcdfac6 Clean up tests and migrate (#540)
* migrate
  * abstract base class test
  * remove assert_equal in test_cli
  * remove nose from test_cli
  * remove nose dependencies
  * use tempfile
* configure coverage
  * flag python version in name
  * enable comment
* drop EOL py37
2023-08-10 00:18:02 +02:00
Patrick Kanzler
31daabcbea Improve documentation build (#539)
* use imgconverter
* enable pdf build with xelatex
* change heading levels
* restructure headings
* add chapter intro
* remove extensions from index
2023-08-09 00:52:01 +02:00
Patrick Kanzler
4a0f5855ef disable pdf build for now (#538) 2023-07-29 21:05:18 +02:00
Patrick Kanzler
60c4f481ae Add printer profile list to documentation (#536)
* add first draft of printer profile listing

* add todos

* Update doc/capability_templates/capabilities-template.jinja

* Update doc/capability_templates/capabilities-template.jinja

* restructure documentation

* add encoding list

* add color and encoding table

* add notes on usage

* add feature table
2023-07-29 02:02:13 +02:00
Patrick Kanzler
09a598883c 351 ean example fix (#537)
* bug/doc: Fix invalid EAN-13 barcode in examples

In the various examples and python-escpos CLI there are a number of uses
of `13243546557687` as an EAN-13 example.  This EAN barcode is invalid
as the checksum should be `0` and not `7`.

```
$ python test_print.py
Traceback (most recent call last):
  File "test_print.py", line 5, in <module>
    p.barcode('13243546557687', 'EAN13', 64, 2, '', '')
  File "/home/pi/fatt-display/lib/python3.7/site-packages/escpos/escpos.py", line 433, in barcode
    bc=bc,
escpos.exceptions.BarcodeCodeError: No Barcode code was supplied (Barcode '13243546557687' not in a valid format for type 'EAN13')
```

This patch set removes `13243546557687` and replaces it with the valid
number `40063813339310`.

In researching the list of [assigned prefixes issued by
G1][g1-prefixes] there seemed to be no "officially" defined test  prefix, so
this change was made to be minimally invasive using the number from the
existing test cases.

Resolves #350

Affects #176

[g1-prefixes]: https://www.gs1.org/standards/id-keys/company-prefix
[test-code]: https://www.barcodelookup.com/4006381333931

---------

Co-authored-by: Brian 'Redbeard' Harrington <redbeard@dead-city.org>
2023-07-28 18:23:18 +02:00
Alfredo orozco
df9e8ff394 Feature(escpos) Add buzzer function (#535)
* Add buzzer function
* Add `buzzer(time, duration)` function to control the buzzer on supported
   printers.
* Add unit tests for buzzer function.
* Update test_function_buzzer.py to pass black
* Run black in tests files
---------

Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
2023-07-27 19:10:19 +02:00
Benito López
9ff327d967 Remove sleep when quering status. Fixes #407 (#534)
* Remove sleep when quering status
* Remove unused import time
2023-07-23 23:22:27 +02:00
Patrick Kanzler
e9e8b10582 Fixes from ci (#533)
* break line

* remove unused imports

* remove unused os import

* make flake8 more strict

* configure flake for black

* fix action

* use importlib_resources

* rename deprecated methods
2023-07-21 23:03:46 +02:00
Florent de Labarre
cb3f4e856b [FIX] doesn't work in multi user env (#532)
1/ launch code with user A, a temp dir is create with user A
2/ launch code with user B --> permission denied
2023-07-21 22:10:29 +02:00
dependabot[bot]
756bf2c6c2 Bump actions/setup-python from 4.6.1 to 4.7.0 (#531)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.1 to 4.7.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.6.1...v4.7.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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-17 17:59:00 +02:00
Benito López
3c11c1b9ab New high level barcode method. Closes #245, #244. (#527)
* Merge software and hardware barcodes to one method

* Fix wrong sw barcode heigh/width

* Add missing param to _sw_barcode call

* Make barcode() smarter, improvements and clean up

* Use param font_size in sw_barcode()

* Update docstrings

* Update barcode examples and docs

* Add --force_software option to CLI

* Attempt to match the sw and hw barcode sizes

* Better approximation to native font size

* Fix docs build

* Update tests at test_function_softbarcode

* Fix exception

* Move image dpi setting to writter_options

* Fix _sw_barcode() docstring param

* Fix wrong default param in docstring

* improve linkage in documentation

---------

Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
2023-07-12 20:45:41 +02:00
dependabot[bot]
676d2840de Bump sphinx-rtd-theme from 1.2.1 to 1.2.2 (#528)
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.1 to 1.2.2.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.1...1.2.2)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-09 01:24:17 +02:00
dependabot[bot]
e72ebc7986 Bump actions/setup-python from 4.6.0 to 4.6.1 (#526)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.6.0...v4.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-25 14:03:26 +02:00
dependabot[bot]
c67c077a4d Bump sphinx-rtd-theme from 1.2.0 to 1.2.1 (#524)
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.0 to 1.2.1.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.0...1.2.1)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 15:22:50 +02:00
Patrick Kanzler
fc17391b63 remove unused badges (#522) 2023-05-18 16:38:40 +02:00
Patrick Kanzler
905e37e52e update read the docs config (#520)
* update read the docs config

* add hints on black code style

* add link to black

* fix path to requirements

* include submodules in build

* add submodule capabilities-data

* format as list

* post on execution on RTD

* use sphinx rtd theme 1.2.0

* add apt dependencies
2023-05-18 16:33:20 +02:00
Ricardo Sánchez Alba
66348ccc0a Added example with Docker and Flask (#519)
* Added example with Docker and Flask

* set flask debug flag to False

* new line at the end of file

* format with black

---------

Co-authored-by: Ricardo Sanchez Alba <r2sanchezalba@gmail.com>
2023-05-18 15:06:06 +02:00
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
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
Benito López
a30c28baa5 Merge branch 'development' into development 2019-07-02 18:06:20 +02: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
112 changed files with 6539 additions and 2737 deletions

View File

@@ -18,7 +18,8 @@ I have:
**Printer:** Manufacturer Model XVI
<!-- since version 2.0.1 you can type 'python-escpos version' in your shell.
Alternatively you could use '__version__' in module escpos. -->
Starting with python-escpos version 3.0, please replace the information below
with the output of `python-escpos version_extended`. -->
**python-escpos version:** 0.0.0
**python version:** 0.0

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
### 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@v4
- 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@v4
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@v4
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

View File

@@ -0,0 +1,55 @@
name: Python package on Windows
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
python-version: ['3.11', '3.12']
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.1
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 (Test-Path .\requirements.txt) { pip install -r .\requirements.txt }
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E,F,W --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: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
- name: Test mypy with tox
run: |
tox -e mypy
env:
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
directory: ./coverage/reports/
env_vars: OS,PYTHON
fail_ci_if_error: true
files: ./coverage.xml,!./cache
flags: unittests
name: coverage-tox-${{ matrix.python-version }}
verbose: true

View File

@@ -15,25 +15,32 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v4.7.1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y graphviz libenchant-2-2 gcc libcups2-dev python3-dev xindy
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: Check sorting of imports
uses: isort/isort-action@master
with:
requirementsFiles: "requirements.txt doc/requirements.txt"
sortPaths: "./doc ./src ./examples ./test ./setup.py"
- 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
flake8 . --count --select=E,F,W --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
@@ -41,3 +48,18 @@ jobs:
tox
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Test mypy with tox
run: |
tox -e mypy
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
directory: ./coverage/reports/
env_vars: OS,PYTHON
fail_ci_if_error: true
files: ./coverage.xml,!./cache
flags: unittests
name: coverage-tox-${{ matrix.python-version }}
verbose: true

12
.gitignore vendored
View File

@@ -6,6 +6,7 @@ $~
.idea/
.directory
.cache/
settings.json
# temporary data
temp
@@ -21,6 +22,10 @@ dist/
src/escpos/version.py
.hypothesis
.pytest_cache/
coverage.xml
# pyenv
.python-version
# testing temporary directories
test/test-cli-output/
@@ -31,4 +36,9 @@ test/test-cli-output/
*.swo
# vscode
.vscode/settings.json
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@@ -13,3 +13,8 @@ csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com>
reck31 <rakesh.gunduka@gmail.com>
Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it>
白月秋见心 <ourweijiang@gmail.com>
Maximilian Wagenbach <maximilian.wagenbach@native-instruments.de>
<belono@users.noreply.github.com> <tiotil.lindeman@gmail.com>
belono <belono@users.noreply.github.com> Benito López <belono@users.noreply.github.com>
Alfredo Orozco <alfredoopa@gmail.com> Alfredo orozco <alfreedom@users.noreply.github.com>

27
.readthedocs.yml Normal file
View File

@@ -0,0 +1,27 @@
version: 2
formats:
- epub
- pdf
build:
os: ubuntu-22.04
tools:
python: "3.11"
apt_packages:
- graphviz
- libenchant-2-2
- gcc
- libcups2-dev
- python3-dev
- xindy
sphinx:
configuration: doc/conf.py
submodules:
include:
- capabilities-data
recursive: true
python:
install:
- requirements: doc/requirements.txt
- method: pip
path: .

View File

@@ -1,81 +0,0 @@
language: python
sudo: false
cache: pip
dist: bionic
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: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.7
env: TOXENV=py37
- python: 3.7-dev
env: TOXENV=py37
- python: 3.8
env: TOXENV=py38
- python: 3.8-dev
env: TOXENV=py38
- python: nightly
env: TOXENV=py38
- python: pypy3
env: TOXENV=pypy3
- python: 3.8
env: TOXENV=docs
- python: 3.8
env: TOXENV=flake8
allow_failures:
- 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.8"

17
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"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",
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
}

View File

@@ -1,9 +1,12 @@
Ahmed Tahri
akeonly
Alejandro Hernández
Alexander Bougakov
Alex Debiasio
Alfredo Orozco
Asuki Kono
belono
B. Howell
Brian
Christoph Heuel
Cody (Quantified Code Bot)
@@ -11,7 +14,9 @@ csoft2k
Curtis // mashedkeyboard
Davis Goglin
Dean Rispin
dependabot[bot]
Dmytro Katyukha
Florent de Labarre
Gerard Marull-Paretas
Hark
Joel Lehtonen
@@ -21,6 +26,7 @@ Kristi
ldos
Lucy Linder
Manuel F Martinez
Mathieu Poussin
Maximilian Wagenbach
Michael Billington
Michael Elsdörfer
@@ -33,11 +39,14 @@ Qian Linfeng
Ramon Poca
reck31
Renato Lorenzi
Ricardo Sánchez Alba
Romain Porte
Sam Cheng
Sergio Pulgarin
Stephan Sokolow
Thijs Triemstra
Thomas van den Berg
vendryan
Yaisel Hurtado
ysuolmai
白月秋见心

View File

@@ -1,6 +1,138 @@
*********
Changelog
*********
=========
2023-11-17 - Version 3.0 - "Quietly Confident"
----------------------------------------------
This is the major release of the new version 3.0.
A big thank you to @belono for their many contributions
for the finalization of v3!
The third major release of this library drops support for
Python 2 and requires a Python version of at least 3.8.
The API has been reworked to be more consistent and two
new concepts have been introduced:
`Capabilities` allow the library to know which features
the currently used printer implements and send fitting
commands.
`Magic Encode` is a new feature that uses the
capability information and encodes Unicode automatically
with the correct code page while sending also the
code page change commands.
The license of the project has been changed to MIT
in accordance with its contributors.
The changes listed here are a summary of the changes
of the previous alpha releases. For details please
read the changelog of the alpha releases.
changes
^^^^^^^
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the codepage with the old API
- fix the encoding search so that lower encodings are found first
- automatically handle cases where full cut or partial cut is not available
- refactor of the set-method
- preliminary support of POS "line display" printing
- add support for software-based barcode-rendering
- make feed for cut optional
- implemented paper sensor querying command
- Include support for CUPS based printer interfaces
- add Win32Raw-Printer on Windows-platforms
- add and improve Windows support of USB-class
- pickle capabilities for faster startup
contributors
^^^^^^^^^^^^
This is a list of contributors since the last v2 release.
- Ahmed Tahri
- akeonly
- Alexander Bougakov
- AlexandroJaez
- Asuki Kono
- belono
- brendanhowell
- Brian
- Christoph Heuel
- csoft2k
- Curtis // mashedkeyboard
- Dmytro Katyukha
- Foaly
- Gerard Marull-Paretas
- Justin Vieira
- kedare
- kennedy
- Lucy Linder
- Maximilian Wagenbach
- Michael Billington
- Michael Elsdörfer
- mrwunderbar666
- NullYing
- Omer Akram
- Patrick Kanzler
- primax79
- Ramon Poca
- reck31
- Romain Porte
- Sam Cheng
- Scott Rotondo
- Sergio Pulgarin
- Thijs Triemstra
- Yaisel Hurtado
- ysuolmai
and others
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 tool chain 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.
@@ -23,7 +155,7 @@ contributors
- Alexander Bougakov
- Brian
- Yaisel Hurtado
- Maximilan Wagenbach
- Maximilian Wagenbach
- Patrick Kanzler
2019-06-19 - Version 3.0a6 - "Mistake not..."
@@ -35,7 +167,7 @@ released.
changes
^^^^^^^
- fix inclusion of the capabilities-file
- execute CI jobs also on Windows and macOS-targets
- execute CI jobs also on Windows and MacOS-targets
- improve documentation
contributors
@@ -170,7 +302,7 @@ changes
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the codepage with the old API
- updated and improved documentation
- changed constructor of main class due to introduction of capablities
- changed constructor of main class due to introduction of capabilities
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
- add support for custom cash drawer sequence
@@ -195,7 +327,7 @@ changes
- fix improper API-use in qrcode()
- change setup.py shebang to make it compatible with virtualenvs.
- add constants for sheet mode and colors
- support changing the linespacing
- support changing the line spacing
contributors
^^^^^^^^^^^^
@@ -249,8 +381,8 @@ changes
- packaging: configured the coverage-analysis codecov.io
- GitHub: improved issues-template
- documentation: add troubleshooting tip to network-interface
- the module, cli and documentation is now aware of the version of python-escpos
- the cli does now support basic tabcompletion
- the module, CLI and documentation is now aware of the version of python-escpos
- the CLI does now support basic tab completion
contributors
^^^^^^^^^^^^
@@ -268,7 +400,7 @@ changes
- refactor complete code in order to be compatible with Python 2 and 3
- modernize packaging
- add testing and CI
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cashdraw and structure
- merge various forks into codebase, fixing multiple issues with barcode-, QR-printing, cash-draw and structure
- improve the documentation
- extend support of barcode-codes to type B
- add function to disable panel-buttons
@@ -339,7 +471,7 @@ contributors
--------------------------
- Issue #5: Fixed vertical tab
- Issue #9: Fixed identation inconsistence
- Issue #9: Fixed indentation inconsistency
2013-03-14 - Version 1.0.1
--------------------------
@@ -350,6 +482,6 @@ contributors
2012-11-15 - Version 1.0
------------------------
- Issue #2: Added ethernet support
- Issue #2: Added Ethernet support
- Issue #3: Added compatibility with libusb-1.0.1
- Issue #4: Fixed typo in escpos.py

View File

@@ -1,87 +1,83 @@
************
Contributing
************
============
This project is open to any kind of contribution. You can help with improving the documentation, adding fixes to the
code, providing test cases in code or as a description or just spreading the word. Please feel free to create an
issue or pull request.
In order to reduce the amount of work for everyone please try to adhere to good practice.
:Last Reviewed: 2023-08-10
The pull requests and issues will be prefilled with templates. Please fill in your information where applicable.
This project is open to any kind of contribution.
You can help with improving the documentation, adding fixes to the
code, providing test cases in code or as a description or just
spreading the word.
Please feel free to create an issue or pull request.
In order to reduce the amount of work for everyone please try
to adhere to good practice.
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as
well as possible.
The pull requests and issues will be prefilled with templates.
Please fill in your information where applicable.
This project uses `semantic versioning <https://semver.org/>`_
and tries to adhere to the proposed rules as well as possible.
Author-list
-----------
This project keeps a list of authors. This can be auto-generated by calling `./doc/generate-authors.sh`.
When contributing the first time, please include a commit with the output of this script in place.
Otherwise the integration-check will fail.
This project keeps a list of authors.
This can be auto-generated by calling `./doc/generate-authors.sh`.
When contributing the first time, please include a commit with
the output of this script in place.
When you change your username or mail-address, please also update the `.mailmap` and the authors-list.
You can find a good documentation on the mapping-feature in the `documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
When you change your username or mail-address, please also
update the `.mailmap` and the authors-list.
You can find a good documentation on the mapping-feature in the
`documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
Style-Guide
-----------
When writing code please try to stick to these rules.
Python 2 and 3
^^^^^^^^^^^^^^
We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3.
In order to ensure that we do not miss any accidental degradation, please add these imports to the top
of every file of code:
.. code-block:: Python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
Furthermore please be aware of the differences between Python 2 and 3. For
example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful.
Special care has to be taken when dealing with strings and byte-strings. Please note
that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings.
Often you can achieve compatibility quite easily with a tool from the `six`-package.
PEP8
^^^^
The entire codebase adheres to the rules of PEP8.
These rules are enforced by running `flake8` in the integration-checks.
Please adhere to these rules as your contribution can only be merged if the check succeeds.
You can use flake8 or similar tools locally in order to check your code.
Apart from that the travis-log and the check by Landscape will provide you with hints.
Black Code Style
^^^^^^^^^^^^^^^^
This project is formatted with the auto formatter `black <https://github.com/psf/black>`_.
Please format your contributions with black, otherwise they will be rejected.
GIT
^^^
The master-branch contains the main development of the project. A release to PyPi is marked with a tag
corresponding to the version. Issues are closed when they have been resolved by merging into the master-branch.
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/master`.
The master-branch contains the main development of the project.
A release to PyPi is marked with a tag corresponding to the version.
Issues are closed when they have been resolved by merging
into the master-branch.
When you have a change to make, begin by creating a new branch
from the HEAD of `python-escpos/master`.
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a
git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
easier to review, and improves the chance of your code being merged.
Please also make sure that before creating your PR, your branch is rebased on a recent commit or you merged a recent
commit into your branch. This way you can ensure that your PR is without merge conflicts.
Please try to group your commits into logical units.
If you need to tidy up your branch, you can make use of a
git feature called an 'interactive rebase' before making a pull request.
A small, self-contained change-set is easier to review, and
improves the chance of your code being merged.
Please also make sure that before creating your PR, your branch
is rebased on a recent commit or you merged a recent
commit into your branch.
This way you can ensure that your PR is without merge conflicts.
Docstrings
^^^^^^^^^^
This project tries to have a good documentation.
Please add a docstring to every method and class. Have a look at existing methods and classes for the style.
Please add a docstring to every method and class.
Have a look at existing methods and classes for the style.
We use basically standard rst-docstrings for Sphinx.
Test
^^^^
Try to write tests whenever possible. Our goal for the future is 100% coverage.
We are currently using `nose` but might change in the future.
You can copy the structure from other testcases. Please remember to adapt the docstrings.
Try to write tests whenever possible.
Our goal for the future is 100% coverage.
You can copy the structure from other testcases.
Please remember to adapt the docstrings.
Further reading
^^^^^^^^^^^^^^^
For further best practices and hints on contributing please see the
`contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
`contribution-guide <https://www.contribution-guide.org/>`_.
Should there be any contradictions between this guide
and the linked one, please stick to this text.
Aside from that feel free to create an issue or write an email if anything is unclear.

View File

@@ -4,7 +4,7 @@ python-escpos
This library is available over pypi. So for most of the use-cases it should be sufficient to run
```
pip install python-escpos --user # add --pre if you want to install pre-releases
pip install python-escpos[all] --user # add --pre if you want to install pre-releases
```
For more information please read the documentation at https://python-escpos.readthedocs.io/en/latest/user/installation.html

View File

@@ -2,30 +2,17 @@
python-escpos - Python library to manipulate ESC/POS Printers
#############################################################
.. image:: https://travis-ci.org/python-escpos/python-escpos.svg?branch=master
:target: https://travis-ci.org/python-escpos/python-escpos
:alt: Continous Integration
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
:target: https://landscape.io/github/python-escpos/python-escpos/master
:alt: Code Health
.. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master
:target: https://codecov.io/github/python-escpos/python-escpos?branch=master
:alt: Code Coverage
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable
:target: http://python-escpos.readthedocs.io/en/latest/?badge=stable
:alt: Documentation Status
Description
-----------
===========
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=latest
:target: https://python-escpos.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
Python ESC/POS is a library which lets the user have access to all those printers handled
by ESC/POS commands, as defined by Epson, from a Python application.
The library tries to implement the functions provided by the ESC/POS-commandset and supports sending text, images,
The library tries to implement the functions provided by the ESC/POS-command-set and supports sending text, images,
barcodes and qr-codes to the printer.
Text can be aligned/justified and fonts can be changed by size, type and weight.
@@ -47,7 +34,7 @@ This library makes use of:
* `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing
* `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes
* `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers
* `viivakoodi <https://github.com/kxepal/viivakoodi>`_ for the generation of barcodes
* `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ for the generation of barcodes
Documentation and Usage
-----------------------
@@ -62,7 +49,7 @@ The basic usage is:
p = Usb(0x04b8, 0x0202, 0, profile="TM-T88III")
p.text("Hello World\n")
p.image("logo.gif")
p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
p.barcode('4006381333931', 'EAN13', 64, 2, '', '')
p.cut()
@@ -74,7 +61,7 @@ Another example based on the Network printer class:
kitchen = Network("192.168.1.100") #Printer IP Address
kitchen.text("Hello World\n")
kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '')
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '')
kitchen.cut()
Another example based on the Serial printer class:
@@ -97,18 +84,20 @@ Another example based on the Serial printer class:
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
------------
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
----------
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.

View File

@@ -3,11 +3,15 @@ codecov:
coverage:
status:
project:
default: # status context
target: auto
threshold: "1%"
project: off
patch: off
changes: off
range: "60...100"
comment: off
comment:
layout: " diff, flags, files"
behavior: default
require_changes: false # if true: only post the comment if coverage changes
require_base: false # [true :: must have a base report to post]
require_head: true # [true :: must have a head report to post]

View File

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

View File

@@ -0,0 +1,28 @@
{% for item in data.encodings %}
{% set encoding = data.encodings[item] %}
{% macro draw_with_underline(text, symbol='-') -%}
{{ escape_rst(text) }}
{{ escape_rst(text) | length * symbol }}
{%- endmacro %}
{{ '.. _encoding-label-' + item + ':' }}
{{ draw_with_underline(encoding.name) }}
{{ escape_rst(encoding.notes) }}
Mapping Information
^^^^^^^^^^^^^^^^^^^
====================== ================================================================
identifier {{ escape_rst(item) }}
Name {{ escape_rst(encoding.name|default('Unknown')) }}
Iconv Name {{ escape_rst(encoding.iconv|default('Unknown')) }}
``python_encode`` Name {{ escape_rst(encoding.python_encode|default('Unknown')) }}
====================== ================================================================
{% if encoding.data is defined %}
{{ draw_with_underline('Code page data', symbol='^') }}
{{ encoding.data }}
{% endif %}
{% endfor %}

View File

@@ -0,0 +1,83 @@
{% for item in data.profiles %}
{% set printer = data.profiles[item] %}
{% macro draw_with_underline(text, symbol='-') -%}
{{ escape_rst(text) }}
{{ escape_rst(text) | length * symbol }}
{%- endmacro %}
{% macro abort(error) %}
{{ None['[ERROR] ' ~ error][0] }}
{% endmacro %}
{% macro fill_line(text, total, symbol=' ') -%}
{%- if total < text|length -%}
{{- abort("Line cannot be filled: must be longer") -}}
{%- endif -%}
{{- text + ((total - text|length ) * symbol ) -}}
{%- endmacro %}
{{ '.. _printer-label-' + item + ':' }}
{{ draw_with_underline(printer.name) }}
{{ escape_rst(printer.notes) }}
You can select this profile in python-escpos with this identifier: ``{{ item }}``.
(Set parameter to `profile='{{ item }}'`.)
Basic information
^^^^^^^^^^^^^^^^^
====================== ================================================================
Name {{ escape_rst(printer.name|default('Unknown')) }}
Vendor {{ escape_rst(printer.vendor|default('Unknown')) }}
Media width (mm) {{ escape_rst(printer.media.width.mm|default('Unknown')|string) }}
Media width (pixels) {{ escape_rst(printer.media.width.pixels|default('Unknown')|string) }}
DPI {{ escape_rst(printer.media.dpi|default('Unknown')|string) }}
====================== ================================================================
Fonts
^^^^^
+------------------+------------------------------+-----------------------+
| ID | Name | Columns |
+==================+==============================+=======================+
{% for id in printer.fonts -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(escape_rst(printer.fonts[id].name), 28) }} | {{ fill_line(printer.fonts[id].columns|string, 21) }} |
+------------------+------------------------------+-----------------------+
{% endfor %}
Colors
^^^^^^
+------------------+----------------------------------------------------------------+
| ID | Color |
+==================+================================================================+
{% for id in printer.colors -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(escape_rst(printer.colors[id]), 62) }} |
+------------------+----------------------------------------------------------------+
{% endfor %}
Feature support
^^^^^^^^^^^^^^^
+-----------------------------------+----------------------------+
| Feature | Supported |
+===================================+============================+
{% for feature in printer.features -%}
| {{ fill_line(escape_rst(feature), 33) }} | {{ fill_line(escape_rst(printer.features[feature]|string), 26) }} |
+-----------------------------------+----------------------------+
{% endfor %}
Text code pages
^^^^^^^^^^^^^^^
+------------------+----------------------------------------------------------------+
| ID | Encoding |
+==================+================================================================+
{% for id in printer.codePages -%}
| {{ fill_line(escape_rst(id), 16) }} | {{ fill_line(':ref:`encoding-label-'+printer.codePages[id]+'`', 62) }} |
+------------------+----------------------------------------------------------------+
{% endfor %}
{% endfor %}

View File

@@ -12,113 +12,114 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
on_rtd = os.getenv('READTHEDOCS') == 'True'
if on_rtd:
import escpos
else:
from setuptools_scm import get_version
import sys
from importlib.metadata import version as imp_version
on_rtd = os.getenv("READTHEDOCS") == "True"
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../src'))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, os.path.abspath("../src"))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
'sphinx.ext.graphviz',
'sphinx.ext.inheritance_diagram',
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.doctest",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.viewcode",
"sphinx.ext.todo",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.imgconverter",
"sphinxarg.ext",
"sphinxcontrib.datatemplates",
"sphinxcontrib.spelling",
]
# mock the following modules for autodoc
autodoc_mock_imports = ["qrcode"]
# supress warnings for external images
suppress_warnings = [
'image.nonlocal_uri',
"image.nonlocal_uri",
]
# enable todos
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates", "capability_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'python-escpos'
copyright = u'2016, Manuel F Martinez and others'
project = "python-escpos"
copyright = "2023, python-escpos developers"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
if on_rtd:
# The full version, including alpha/beta/rc tags.
release = escpos.__version__
else:
# locally setuptools_scm should work
release = get_version(root=root)
release = imp_version("python-escpos")
# 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
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# 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
# 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
# documents.
#default_role = None
# default_role = None
# 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
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# 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.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
@@ -126,135 +127,142 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
if on_rtd:
html_theme = 'default'
html_theme = "sphinx_rtd_theme"
print("recognized execution on RTD")
else:
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
print("no sphinx_rtd_theme found, switching to nature")
html_theme = 'default'
html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# 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
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# 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
# 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
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = 'pyescpos.ico'
html_favicon = "pyescpos.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# 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
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# 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.
#html_split_index = False
# html_split_index = False
# 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.
#html_show_sphinx = True
# html_show_sphinx = 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
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# 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.
htmlhelp_basename = 'python-escposdoc'
htmlhelp_basename = "python-escposdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
latex_engine = "xelatex"
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'python-escpos.tex', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'manual'),
(
"index",
"python-escpos.tex",
"python-escpos Documentation",
"python-escpos developers",
"manual",
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# 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.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
@@ -262,12 +270,17 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'python-escpos', u'python-escpos Documentation',
[u'Manuel F Martinez and others'], 1)
(
"index",
"python-escpos",
"python-escpos Documentation",
["python-escpos developers"],
1,
)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -276,19 +289,43 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'python-escpos', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.',
'Miscellaneous'),
(
"index",
"python-escpos",
"python-escpos Documentation",
"python-escpos developers",
"python-escpos",
"One line description of project.",
"Miscellaneous",
),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# 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.
#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
spelling_ignore_contributor_names = True
spelling_word_list_filename = ["spelling_wordlist.txt", "../AUTHORS"]
spelling_show_suggestions = True
spelling_suggestion_limit = 3
spelling_warning = True
spelling_exclude_patterns = [
"**/capabilities.json",
"../../capabilities-data/dist/capabilities.json",
"**/available-encodings.rst",
"**/available-profiles.rst",
"dev/todo.rst",
]

View File

@@ -0,0 +1,11 @@
Release process
===============
:Last Reviewed: 2023-08-10
* Update authors file
* Update changelog
* Set annotated tag for release and push to public github
* Build wheel
* Load wheel to PyPi
* Prepare project for next release with an empty changelog entry

17
doc/dev/repository.rst Normal file
View File

@@ -0,0 +1,17 @@
.. _developer-manual-repository:
Repository
==========
:Last Reviewed: 2023-09-05
This project uses sub-projects and retrieves its versioning
information from version control.
Therefore it is crucial that you follow these rules when
working with the project (e.g. for packaging a
development version).
* Make sure that the git project is complete. A call to git status for example should succeed.
* Make sure that you have checked out all available sub-projects.
* Proper initialization of submodules can be ensured with ``git submodule update --init --recursive``

View File

@@ -1,6 +1,7 @@
****
TODO
****
====
:Last Reviewed: 2023-08-10
Open points and issues of the project are tracked in the GitHub issues.
Some annotations still remain in the code and should be moved over time

View File

@@ -1,6 +1,7 @@
#!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
GENLIST_W_MAIL=$(git shortlog -s -e -n | cut -f2 | sort -f)
AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
@@ -12,7 +13,9 @@ if [ "$#" -eq 1 ]
echo "\nNew authorsfile:\n"
cat $TEMPAUTHORSFILE
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
echo "$GENLIST">$AUTHORSFILE
fi

View File

@@ -5,8 +5,15 @@
.. include:: ../README.rst
#######
Content
-------
#######
User Documentation
==================
This chapter describes the central points that
are relevant to the user of this library.
.. toctree::
:maxdepth: 1
@@ -16,16 +23,46 @@ Content
user/methods
user/printers
user/raspi
user/todo
user/usage
user/cli-user
user/barcode
Printer profiles
================
This chapter gives a listing of the available
printer profiles. Details are described in
:ref:`capabilities-profile-intro`.
.. toctree::
:maxdepth: 1
:caption: Printer profiles
printer_profiles/capabilities
printer_profiles/available-profiles
printer_profiles/available-encodings
Developer Documentation
=======================
This chapter summarizes information for
developers of this library.
.. toctree::
:maxdepth: 1
:caption: Developer Documentation
dev/release-process
dev/contributing
dev/repository
dev/changelog
dev/todo
API Documentation
=================
This chapter contains an auto-generated
documentation of the API of this library.
.. toctree::
:maxdepth: 1
@@ -43,8 +80,9 @@ Content
api/codepages
api/katakana
##################
Indices and tables
==================
##################
* :ref:`genindex`
* :ref:`modindex`

View File

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

View File

@@ -0,0 +1,11 @@
Available Encodings
-------------------
:Last Reviewed: 2023-08-10
If you find any issues with the described encodings,
please open an issue in the
`ESC/POS printer database <https://github.com/receipt-print-hq/escpos-printer-db>`_.
The data shown here is directly taken from there.
.. datatemplate:json:: ../../capabilities-data/dist/capabilities.json
:template: capabilities-template-encoding.jinja

View File

@@ -0,0 +1,18 @@
.. _available-profiles:
Available Profiles
------------------
:Last Reviewed: 2023-08-10
The following list describes which printer profiles are
available in this release.
The existence of a profile is a hint, but no guarantee
that this printer actually can be controlled by this library.
If you find any issues with the described capabilities,
please open an issue in the
`ESC/POS printer database <https://github.com/receipt-print-hq/escpos-printer-db>`_.
The data shown here is directly taken from there.
.. datatemplate:json:: ../../capabilities-data/dist/capabilities.json
:template: capabilities-template.jinja

View File

@@ -0,0 +1,26 @@
.. _capabilities-profile-intro:
Capabilities
------------
:Last Reviewed: 2023-08-10
Since the used command set often differs between printers,
a model for supporting different printers is implemented.
This feature is called `capabilities`.
The `capabilities`-feature allows this library to know
which features are supported.
If no further information is specified, python-escpos will
try to automatically use features based on the supplied information.
In order to use the `capabilities`-database, the printer instance
simply has to be created with the parameter `profile` set to the
relevant identifier.
The identifier can be found in :ref:`available-profiles`.
This documentation describes the profiles in the database file that
is bundled with this release.
If another configuration is to be used, this chapter can be followed
for information on how to side-load another `capabilities`-database:
:ref:`advanced-usage-change-capabilities-profile`.

View File

@@ -2,7 +2,15 @@ pyusb
Pillow>=2.0
qrcode>=4.0
pyserial
sphinx-rtd-theme
sphinx-rtd-theme==1.3.0
setuptools
setuptools-scm
docutils>=0.12
viivakoodi
sphinxcontrib-spelling>=7.2.0
python-barcode>=0.11.0,<1
importlib-metadata
importlib_resources
sphinxcontrib.datatemplates
sphinx-argparse
sphinx-autodoc-typehints
pycups

128
doc/spelling_wordlist.txt Normal file
View File

@@ -0,0 +1,128 @@
ESC
Esc
POS
Pos
Escpos
Escpos
escpos
bitImageColumn
bitImageRaster
ep
pc
cp
csoft
Frédéric
Headcrash
Krispy
primax
Tahri
reck
mrwunderbar
zouppen
kedare
Foaly
brendanhowell
der
fvdsn
Marull
Paretas
Kakistocrat
Billington
patkan
Romain
Bougakov
Yaisel
Hurtado
Wagenbach
Poca
Akram
Vieira
Christoph
Heuel
Thijs
Triemstra
Linder
Romain
Pulgarin
Romain
Cheng
Dmytro
Katyukha
Elsdörfer
Asuki
Kono
López
mashedkeyboard
Thijs
Triemstra
Elsdörfer
Renato
Lorenzi
Bookham
Goglin
Christoph
Heuel
Qian
Lehtonen
Kanzler
Rotondo
barcode
barcodes
baudrate
Bashlinux
capabilities
cashdraw
charcode
changelog
cheque
codebase
codecov
codepages
config
del
dev
dialout
docstrings
ean
Ean
encodable
fff
fullimage
io
json
latin
libusb
lp
lsusb
natively
php
pre
prefilled
printcap
programmatically
py
pypy
pyyaml
px
qrcode
Raspbian
rebase
rebased
resetted
rst
submodule
submodules
src
testcases
th
Todo
traceback
udev
usb
usec
virtualenvs
whitespaces
xml

View File

@@ -1,17 +1,29 @@
Printing Barcodes
-----------------
:Last Reviewed: 2016-07-31
Most ESC/POS-printers implement barcode-printing.
The barcode-commandset is implemented in the barcode-method.
For a list of compatible barcodes you should check the manual of your printer.
As a rule of thumb: even older Epson-models support most 1D-barcodes.
To be sure just try some implementations and have a look at the notices below.
:Last Reviewed: 2023-08-10
Many printers implement barcode printing natively.
These hardware rendered barcodes are fast but the supported
formats are limited by the printer itself and different between models.
However, almost all printers support printing images, so barcode
rendering can be performed externally by software and then sent
to the printer as an image.
As a drawback, this operation is much slower and the user needs
to know and choose the image implementation method supported by
the printer's command-set.
barcode-method
~~~~~~~~~~~~~~
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
Since version 3.0, the ``barcode`` method unifies the previous
``barcode`` (hardware) and ``soft_barcode`` (software) methods.
It is able to choose automatically the best printer implementation
for barcode printing based on the capabilities of the printer
and the type of barcode desired.
To achieve this, it relies on the information contained in the
escpos-printer-db profiles.
The chosen profile needs to match the capabilities of the printer
as closely as possible.
.. py:currentmodule:: escpos.escpos
@@ -31,4 +43,5 @@ For alphanumeric CODE128 you have to preface your payload with `{B`.
# print CODE128 012ABCDabcd
p.barcode("{B012ABCDabcd", "CODE128", function_type="B")
A very good description on CODE128 is also on `Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.
A very good description on CODE128 is also on
`Wikipedia <https://en.wikipedia.org/wiki/Code_128>`_.

10
doc/user/cli-user.rst Normal file
View File

@@ -0,0 +1,10 @@
CLI
===
:Last Reviewed: 2023-09-25
Documentation of the command line interface, callable with ``python-escpos``.
.. argparse::
:ref: escpos.cli.generate_parser
:prog: python-escpos

View File

@@ -1,25 +1,53 @@
************
Installation
************
.. _user_installation:
:Last Reviewed: 2016-07-23
Installation
============
:Last Reviewed: 2023-08-10
Installation with PIP
---------------------
Installation should be rather straight-forward. python-escpos is on PyPi, so you can simply enter:
Installation should be rather straight-forward. python-escpos is on PyPi,
so you can simply enter:
::
pip install python-escpos
pip install python-escpos[all]
This should install all necessary dependencies. Apart from that python-escpos should also be
available as a Debian package. If you want to always benefit from the newest stable releases you should probably
install from PyPi.
This should install all necessary dependencies. Apart from that
python-escpos is for some versions also available as a Debian package.
If you want to always benefit from the newest stable releases you should
always install from PyPi.
If you use the ``--pre`` parameter for ``pip``, you will get the latest
pre-release.
The following installation options exist:
* `all`: install all packages available for this platform
* `usb`: install packages required for USB printers
* `serial`: install packages required for serial printers
* `win32`: install packages required for win32 printing (only Windows)
* `cups`: install packages required for CUPS printing
Other installation methods
--------------------------
Officially, no other installation methods are supplied.
If you want to install nevertheless from another source,
please make sure that you have received the correct package
and that the capabilities data is properly integrated.
When packaging from source please read the developer
information in :ref:`developer-manual-repository`.
If your packaging method breaks the resource system from setuptools,
it might be necessary to supply the path to the capabilities file:
:ref:`advanced-usage-change-capabilities-profile`.
Setup udev for USB-Printers
---------------------------
1. Get the *Product ID* and *Vendor ID* from the lsusb command
``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name``
``# lsusb Bus 002 Device 001: ID 1a2b:1a2b Device name``.
(Or whichever way your system supplies to get the PID and VID.)
2. Create a udev rule to let users belonging to *dialout* group use the
printer. You can create the file
@@ -35,7 +63,8 @@ Setup udev for USB-Printers
Enabling tab-completion in CLI
------------------------------
python-escpos has a CLI with tab-completion. This is realised with ``argcomplete``.
python-escpos has a CLI with tab-completion.
This is realized with ``argcomplete``.
In order for this to work you have to enable tab-completion, which is described in
the `manual of argcomplete <https://argcomplete.readthedocs.io>`__.

View File

@@ -1,13 +1,13 @@
*******
Methods
*******
:Last Reviewed: 2017-01-25
=======
:Last Reviewed: 2023-08-10
Escpos class
------------
The core part of this libraries API is the Escpos class.
You use it by instantiating a :doc:`printer <printers>` which is a child of Escpos.
The core part of the API of this library is the Escpos class.
You use it by instantiating a :doc:`printer <printers>` which is a child of Escpos.
The following methods are available:
.. autoclass:: escpos.escpos.Escpos

View File

@@ -1,15 +1,18 @@
********
Printers
********
:Last Reviewed: 2017-01-25
========
As of now there are 5 different type of printer implementations.
:Last Reviewed: 2023-08-23
As of now there are 8 different types of printer implementations.
USB
---
The USB-class uses pyusb and libusb to communicate with USB-based
printers. Note that this driver is not suited for USB-to-Serial-adapters
and similiar devices, but only for those implementing native USB.
printers.
.. note::
This driver is not suited for USB-to-Serial-adapters
and similar devices, but only for those implementing native USB.
.. autoclass:: escpos.printer.Usb
:members:
@@ -44,20 +47,24 @@ This driver is based on the socket class.
Troubleshooting
^^^^^^^^^^^^^^^
Problems with a network-attached printer can have numerous causes. Make sure that your device has a proper IP address.
Often you can check the IP address by triggering the self-test of the device. As a next step try to send text
manually to the device. You could use for example:
Problems with a network-attached printer can have numerous causes.
Make sure that your device has a proper IP address.
Often you can check the IP address by triggering the self-test of the device.
As a next step try to send text manually to the device.
You could use for example:
::
echo "OK\n" | nc IPADDRESS 9100
# the port number is often 9100
As a last resort try to reset the interface of the printer. This should be described in its manual.
As a last resort try to reset the interface of the printer.
This should be described in its manual.
File
----
This printer "prints" just into a file-handle. Especially on \*nix-systems this comes very handy.
This printer "prints" just into a file-handle.
Especially on \*nix-systems this comes very handy.
.. autoclass:: escpos.printer.File
:members:
@@ -67,11 +74,50 @@ This printer "prints" just into a file-handle. Especially on \*nix-systems this
Dummy
-----
The Dummy-printer is mainly for testing- and debugging-purposes. It stores
all of the "output" as raw ESC/POS in a string and returns that.
The Dummy-printer is mainly for testing- and debugging-purposes.
It stores all of the "output" as raw ESC/POS in a string and returns that.
.. autoclass:: escpos.printer.Dummy
:members:
:member-order: bysource
:noindex:
CUPS
----
This driver uses `pycups` in order to communicate with a CUPS server.
Supports both local and remote CUPS printers and servers.
The printer must be properly configured in CUPS administration.
The connector generates a print job that is added to the CUPS queue.
.. autoclass:: escpos.printer.CupsPrinter
:members:
:member-order: bysource
:noindex:
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:
Win32Raw
--------
This driver uses a native WIN32 interface of Windows in order to print.
Please refer to the code for documentation as this driver is currently
not included in the documentation build.
.. autoclass:: escpos.printer.Win32Raw
:members:
:special-members:
:member-order: bysource
:noindex:

View File

@@ -1,42 +1,26 @@
************
Raspberry Pi
************
============
:Last Reviewed: 2017-01-05
:Last Reviewed: 2023-08-10
This instructions were tested on Raspbian Jessie.
.. warning:: You should **never** directly connect an printer with RS232-interface (serial port) directly to
a Raspberry PI or similar interface (e.g. those simple USB-sticks without encasing). Those interfaces are
based on 5V- or 3,3V-logic (the latter in the case of Raspberry PI). Classical RS232 uses 12V-logic and would
**thus destroy your interface**. Connect both systems with an appropriate *level shifter*.
Dependencies
------------
First, install the packages available on Raspbian.
::
sudo apt-get install python3 python3-setuptools python3-pip libjpeg8-dev
.. warning:: You should **never** directly connect an printer with RS232-interface
(serial port) directly to a Raspberry PI or similar interface
(e.g. those simple USB-sticks without encasing).
Those interfaces are based on 5V- or 3,3V-logic
(the latter in the case of Raspberry PI).
Classical RS232 uses 12V-logic and would **thus destroy your interface**.
Connect both systems with an appropriate *level shifter*.
Installation
------------
You can install by using pip3.
::
sudo pip3 install --upgrade pip
sudo pip3 install python-escpos
The installation should be performed as described in :ref:`user_installation`.
Run
---
You need sudo and python3 to run your program.
You can run this software as on any other Linux system.
::
sudo python3 your-program.py
Now you can attach your printer and and test it with the example code in the project's set of examples.
You can find that in the `project-repository <https://github.com/python-escpos/python-escpos>`__.
Attach your printer and test it with the example code in the project's set of examples.
You can find that in the
`project-repository <https://github.com/python-escpos/python-escpos>`__.
For more details on this check the :doc:`installation-manual <installation>`.

View File

@@ -1,7 +1,7 @@
*****
Usage
*****
:Last Reviewed: 2017-06-10
=====
:Last Reviewed: 2023-08-10
Define your printer
-------------------
@@ -121,10 +121,15 @@ on a USB interface.
# Print QR Code
p.qr("You can readme from your smartphone")
# Print barcode
p.barcode('1324354657687','EAN13',64,2,'','')
p.barcode('4006381333931','EAN13',64,2,'','')
# Cut paper
p.cut()
Standard python constraints on libraries apply. This means especially
that you should not name the script in which you implement these lines
should not be named ``escpos`` as this would collide with the name of
the library.
Configuration File
------------------
@@ -141,7 +146,7 @@ And for linux::
$HOME/.config/python-escpos/config.yaml
If you aren't sure, run::
If you are not sure, run::
from escpos import config
c = config.Config()
@@ -163,7 +168,7 @@ The printer section
The ``printer`` configuration section defines a default printer to create.
The only required paramter is ``type``. The value of this has to be one of the
The only required parameter is ``type``. The value of this has to be one of the
printers defined in :doc:`/user/printers`.
The rest of the given parameters will be passed on to the initialization of the printer class.
@@ -194,17 +199,19 @@ An USB-printer could be defined by::
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
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
any key-value that is in ``CHARCODE``. If something is wrong, an ``CharCodeError`` will be raised.
After you have manually set the codepage the printer won't change it anymore. You can revert to normal behaviour
by setting charcode to ``AUTO``.
If you want or need to you can manually set the codepage.
For this please use the ``charcode()``-function.
You can set any key-value that is in ``CHARCODE``.
If something is wrong, an ``CharCodeError`` will be raised.
After you have manually set the codepage the printer won't change it anymore.
You can revert to normal behavior by setting charcode to ``AUTO``.
Advanced Usage: Print from binary blob
--------------------------------------
@@ -233,6 +240,8 @@ Here you can download an example, that will print a set of common barcodes:
* :download:`barcode.bin </download/barcode.bin>` by `@mike42 <https://github.com/mike42>`_
.. _advanced-usage-change-capabilities-profile:
Advanced Usage: change capabilities-profile
-------------------------------------------
@@ -289,4 +298,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.
(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

@@ -1,11 +1,11 @@
"""Example for printing barcodes."""
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
p = Usb(0x0416, 0x5011, profile="TM-T88II")
# Print software and then hardware barcode with the same content
p.soft_barcode('code39', '123456')
p.text('\n')
p.text('\n')
p.barcode('123456', 'CODE39')
p.barcode("123456", "CODE39", width=2, force_software=True)
p.text("\n")
p.text("\n")
p.barcode("123456", "CODE39")

View File

@@ -1,21 +1,29 @@
"""Prints code page tables.
"""
"""Prints code page tables."""
from __future__ import print_function
import six
import sys
import six
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,
CTL_CR,
CTL_FF,
CTL_HT,
CTL_LF,
CTL_VT,
ESC,
)
def main():
"""Init printer and print codepage tables."""
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._raw(codepage + "\n\n\n")
print_codepage(dummy, codepage)
@@ -27,6 +35,7 @@ def main():
def print_codepage(printer, codepage):
"""Print a codepage."""
if codepage.isdigit():
codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
@@ -37,14 +46,14 @@ def print_codepage(printer, codepage):
sep = ""
# 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.set()
# The table
for x in range(0, 16):
# First column
printer.set(font='b')
printer.set(font="b")
printer._raw("{} ".format(hex(x)[2:]))
printer.set()
@@ -52,12 +61,12 @@ def print_codepage(printer, codepage):
byte = six.int2byte(x * 16 + y)
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
byte = ' '
byte = " "
printer._raw(byte)
printer._raw(sep)
printer._raw('\n')
printer._raw("\n")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,28 @@
# Use the official Python image as the base image
FROM python:3.9-slim
# Set the working directory
WORKDIR /app
# Copy the requirements file
COPY requirements.txt .
#Install the libcups library
RUN apt-get update -y && apt-get install libcups2-dev -y
# Install the Python packages
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install python-escpos --pre
# Install Git
RUN apt-get update && \
apt-get install -y git && \
rm -rf /var/lib/apt/lists/*
# Copy the Flask app
COPY app.py .
# Expose the port the Flask app will run on
EXPOSE 8080
# Run the Flask app
CMD ["python", "app.py"]

View File

@@ -0,0 +1,6 @@
Simple example on how to use it inside a web service
```
docker build . -t escpos-web
docker run --network=host -p 9999:9999 escpos
```

View File

@@ -0,0 +1,22 @@
"""Example for a flask application."""
from flask import Flask
from escpos.printer import CupsPrinter
# Initialize Flask app
app = Flask(__name__)
@app.route("/", methods=["GET"])
def do_print():
"""Print."""
# p = Usb(0x04b8, 0x0e28, 0)
p = CupsPrinter(host="localhost", port=631, printer_name="TM-T20III")
p.text("Hello World\n")
p.cut()
p.close()
return "OK"
if __name__ == "__main__":
app.run(debug=False, host="0.0.0.0", port=9999)

View File

@@ -0,0 +1,20 @@
appdirs==1.4.4
argcomplete==3.0.8
blinker==1.6.2
click==8.1.3
Flask==2.3.2
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
Pillow==10.0.1
pycups==2.0.1
pypng==0.20220715.0
pyserial==3.5
python-barcode==0.14.0
python-escpos==3.0a9
pyusb==1.2.1
PyYAML==6.0
qrcode==7.4.2
six==1.16.0
typing_extensions==4.5.0
Werkzeug==3.0.1

View File

@@ -1,8 +1,8 @@
# 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)

View File

@@ -1,13 +1,15 @@
"""Print example QR codes."""
import sys
from escpos.printer import Usb
def usage():
"""Print information on usage."""
print("usage: qr_code.py <content>")
if __name__ == '__main__':
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
sys.exit(1)

View File

@@ -1,9 +1,9 @@
"""Example file for software barcodes."""
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Some software barcodes
p.soft_barcode('code128', 'Hello')
p.soft_barcode('code39', '123456')
p.barcode("Hello", "code128", width=2, force_software="bitImageRaster")
p.barcode("1234", "code39", width=2, force_software=True)

View File

@@ -1,29 +1,29 @@
#!/usr/bin/python
"""Weather forecast example.
Adapted script from Adafruit
Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
Retrieves data from DarkSky.net's API, prints current conditions and
forecasts for next two days.
Weather example using nice bitmaps.
Written by Adafruit Industries. MIT license.
Adapted and enhanced for escpos library by MrWunderbar666
Icons taken from https://adamwhitcroft.com/climacons/
Check out his github: https://github.com/AdamWhitcroft/climacons
"""
# Adapted script from Adafruit
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
# Retrieves data from DarkSky.net's API, prints current conditions and
# forecasts for next two days.
# Weather example using nice bitmaps.
# Written by Adafruit Industries. MIT license.
# Adapted and enhanced for escpos library by MrWunderbar666
# Icons taken from http://adamwhitcroft.com/climacons/
# Check out his github: https://github.com/AdamWhitcroft/climacons
from __future__ import print_function
from datetime import datetime
import calendar
import urllib
import json
import time
import os
import time
import urllib
from datetime import datetime
from escpos.printer import Usb
""" Setting up the main pathing """
"""Set up the main pathing."""
this_dir, this_filename = os.path.split(__file__)
GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/")
@@ -34,93 +34,97 @@ printer = Usb(0x0416, 0x5011, profile="POS-5890")
# Technically you can use any other weather service, of course :)
API_KEY = "YOUR API KEY"
LAT = "22.345490" # Your Location
LONG = "114.189945" # Your Location
LAT = "22.345490" # Your Location
LONG = "114.189945" # Your Location
def forecast_icon(idx):
icon = data['daily']['data'][idx]['icon']
"""Get right icon for forecast."""
icon = data["daily"]["data"][idx]["icon"]
image = GRAPHICS_PATH + icon + ".png"
return image
# Dumps one forecast line to the printer
def forecast(idx):
date = datetime.fromtimestamp(int(data['daily']['data'][idx]['time']))
"""Dump one forecast line to the printer."""
date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"]))
day = calendar.day_name[date.weekday()]
lo = data['daily']['data'][idx]['temperatureMin']
hi = data['daily']['data'][idx]['temperatureMax']
cond = data['daily']['data'][idx]['summary']
lo = data["daily"]["data"][idx]["temperatureMin"]
hi = data["daily"]["data"][idx]["temperatureMax"]
cond = data["daily"]["data"][idx]["summary"]
print(date)
print(day)
print(lo)
print(hi)
print(cond)
time.sleep(1)
printer.set(
font='a',
height=2,
align='left',
bold=False,
double_height=False)
printer.text(day + ' \n ')
time.sleep(5) # Sleep to prevent printer buffer overflow
printer.text('\n')
printer.set(font="a", height=2, align="left", bold=False, double_height=False)
printer.text(day + " \n ")
time.sleep(5) # Sleep to prevent printer buffer overflow
printer.text("\n")
printer.image(forecast_icon(idx))
printer.text('low ' + str(lo))
printer.text("low " + str(lo))
printer.text(deg)
printer.text('\n')
printer.text(' high ' + str(hi))
printer.text("\n")
printer.text(" high " + str(hi))
printer.text(deg)
printer.text('\n')
printer.text("\n")
# take care of pesky unicode dash
printer.text(cond.replace(u'\u2013', '-').encode('utf-8'))
printer.text('\n \n')
printer.text(cond.replace("\u2013", "-").encode("utf-8"))
printer.text("\n \n")
def icon():
icon = data['currently']['icon']
"""Get icon."""
icon = data["currently"]["icon"]
image = GRAPHICS_PATH + icon + ".png"
return image
deg = ' C' # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
deg = " C" # Degree symbol on thermal printer, need to find a better way to use a proper degree symbol
# if you want Fahrenheit change units= to 'us'
url = "https://api.darksky.net/forecast/" + API_KEY + "/" + LAT + "," + LONG + \
"?exclude=[alerts,minutely,hourly,flags]&units=si" # change last bit to 'us' for Fahrenheit
url = (
"https://api.darksky.net/forecast/"
+ API_KEY
+ "/"
+ LAT
+ ","
+ LONG
+ "?exclude=[alerts,minutely,hourly,flags]&units=si"
) # change last bit to 'us' for Fahrenheit
response = urllib.urlopen(url)
data = json.loads(response.read())
printer.print_and_feed(n=1)
printer.control("LF")
printer.set(font='a', height=2, align='center', bold=True, double_height=True)
printer.set(font="a", height=2, align="center", bold=True, double_height=True)
printer.text("Weather Forecast")
printer.text("\n")
printer.set(align='center')
printer.set(align="center")
# Print current conditions
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
printer.text('Current conditions: \n')
printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text("Current conditions: \n")
printer.image(icon())
printer.text("\n")
printer.set(font='a', height=2, align='left', bold=False, double_height=False)
temp = data['currently']['temperature']
cond = data['currently']['summary']
printer.set(font="a", height=2, align="left", bold=False, double_height=False)
temp = data["currently"]["temperature"]
cond = data["currently"]["summary"]
printer.text(temp)
printer.text(' ')
printer.text(" ")
printer.text(deg)
printer.text(' ')
printer.text('\n')
printer.text('Sky: ' + cond)
printer.text('\n')
printer.text('\n')
printer.text(" ")
printer.text("\n")
printer.text("Sky: " + cond)
printer.text("\n")
printer.text("\n")
# Print forecast
printer.set(font='a', height=2, align='center', bold=True, double_height=False)
printer.text('Forecast: \n')
printer.set(font="a", height=2, align="center", bold=True, double_height=False)
printer.text("Forecast: \n")
forecast(0)
forecast(1)
printer.cut()

27
pyproject.toml Normal file
View File

@@ -0,0 +1,27 @@
[tool.black]
extend-exclude = 'capabilities-data'
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "--doctest-modules --cov escpos --cov-report=xml"
testpaths = [
"test",
"src",
"src/escpos",
"escpos",
]
[[tool.mypy.overrides]]
module = ["pytest",
"jaconv",
"scripttest",
"barcode.*",
"qrcode",
"usb.*",
"cups",
"win32print"
]
ignore_missing_imports = true

View File

@@ -1,7 +0,0 @@
formats:
- pdf
- epub
requirements_file: doc/requirements.txt
python:
version: 3
setup_py_install: true

View File

@@ -1,12 +1,80 @@
[nosetests]
verbosity=3
with-doctest=1
[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 = python-escpos developers
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.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
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
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python 3.
universal=1
[options]
python_requires = >=3.8
zip_safe = false
include_package_data = true
install_requires =
Pillow>=2.0
qrcode>=4.0
python-barcode>=0.9.1,<1
setuptools
six
appdirs
PyYAML
argparse
argcomplete
importlib_resources
setup_requires = setuptools_scm
tests_require =
jaconv
tox>=4.11
pytest>=7.4
pytest-cov
pytest-mock
scripttest
mock
hypothesis>=6.83
flake8
sphinxcontrib-spelling>=8.0.0
[options.extras_require]
usb =
pyusb>=1.0.0
serial =
pyserial
cups =
pycups; platform_system!='Windows'
win32 =
pywin32; platform_system=='Windows'
all =
pyusb>=1.0.0
pyserial
pycups; platform_system!='Windows'
pywin32; platform_system=='Windows'
[flake8]
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120
# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet
extend-ignore = E203, W503

View File

@@ -1,9 +1,10 @@
#!/usr/bin/env python
"""Setup script for python package."""
import os
import sys
from setuptools import find_packages, setup
from setuptools import find_packages, setup
base_dir = os.path.dirname(__file__)
src_dir = os.path.join(base_dir, "src")
@@ -14,97 +15,31 @@ sys.path.insert(0, src_dir)
def read(fname):
"""read file from same path as setup.py"""
"""Read file from same path as setup.py."""
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setuptools_scm_template = """\
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
#!/usr/bin/python
# -*- coding: utf-8 -*-
\"\"\"Version identifier.
file generated by setuptools_scm
don't change, don't track in version control
\"\"\"
version = '{version}'
"""
setup(
name='python-escpos',
use_scm_version={
'write_to': 'src/escpos/version.py',
'write_to_template': setuptools_scm_template,
"write_to": "src/escpos/version.py",
"write_to_template": setuptools_scm_template,
},
url='https://github.com/python-escpos/python-escpos',
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
description='Python library to manipulate ESC/POS Printers',
license='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',
platforms="any",
package_dir={"": "src"},
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={'escpos': ['capabilities.json']},
include_package_data=True,
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.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'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>4',
'flake8'
],
entry_points={
'console_scripts': [
'python-escpos = escpos.cli:main'
]
},
package_data={"escpos": ["capabilities.json"]},
entry_points={"console_scripts": ["python-escpos = escpos.cli:main"]},
)

View File

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

View File

@@ -1,150 +1,198 @@
import re
from os import environ, path
import pickle
"""Handler for capabilities data."""
import atexit
import logging
import pickle
import platform
import re
import time
from contextlib import ExitStack
from os import environ, path
from tempfile import mkdtemp
from typing import Any, Dict, Optional, Type
import importlib_resources
import six
import yaml
from tempfile import gettempdir
import platform
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir())
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version()))
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", mkdtemp())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
# get a temporary file from importlib_resources if no file is specified in env
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files(__name__) / "capabilities.json"
capabilities_path = environ.get(
'ESCPOS_CAPABILITIES_FILE',
path.join(path.dirname(__file__), 'capabilities.json'))
"ESCPOS_CAPABILITIES_FILE",
file_manager.enter_context(importlib_resources.as_file(ref)),
)
# Load external printer database
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.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
else:
full_load = False
logger.debug('Loading capabilities from pickle in %s', pickle_path)
with open(pickle_path, 'rb') as cf:
logger.debug("Loading capabilities from pickle in %s", pickle_path)
with open(pickle_path, "rb") as cf:
CAPABILITIES = pickle.load(cf)
else:
logger.debug('Capabilities pickle file not found: %s', pickle_path)
logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True
if full_load:
logger.debug('Loading and pickling capabilities')
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp:
logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp)
if not CAPABILITIES:
# yaml could not be loaded
print(
f"Capabilities yaml from {capabilities_path} could not be loaded.\n"
"This python package seems to be broken. If it has been installed "
"from official sources, please report an issue on GitHub.\n"
"Currently loaded capabilities:\n"
f"{CAPABILITIES}"
)
CAPABILITIES = {
"profiles": {
"default": {
"name": "BrokenDefault",
"notes": "The integrated capabilities file could not be found and has been replaced.",
"codePages": {"0": "Broken"},
"features": {},
},
},
"encodings": {
"Broken": {
"name": "Broken",
"notes": "The configuration is broken.",
}
},
}
print(
"Created a minimal backup profile, "
"many functionalities of the library will not work:\n"
f"{CAPABILITIES}"
)
pickle.dump(CAPABILITIES, pp, protocol=2)
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0)
PROFILES = CAPABILITIES['profiles']
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
class NotSupported(Exception):
"""Raised if a requested feature is not supported by the
printer profile.
"""
"""Raised if a requested feature is not supported by the printer profile."""
pass
BARCODE_B = 'barcodeB'
BARCODE_B = "barcodeB"
class BaseProfile(object):
"""This respresents a printer profile.
"""This represents a printer profile.
A printer profile knows about the number of columns, supported
features, colors and more.
"""
profile_data = {}
profile_data: Dict[str, Any] = {}
def __getattr__(self, name):
"""Get a data element from the profile."""
return self.profile_data[name]
def get_font(self, font):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
def get_font(self, font) -> int:
"""Return the escpos index for `font`.
Makes sure that the requested `font` is valid.
"""
font = {'a': 0, 'b': 1}.get(font, font)
font = {"a": 0, "b": 1}.get(font, font)
if not six.text_type(font) in self.fonts:
raise NotSupported(
'"{}" is not a valid font in the current profile'.format(font))
'"{}" is not a valid font in the current profile'.format(font)
)
return font
def get_columns(self, font):
""" Return the number of columns for the given font.
"""
"""Return the number of columns for the given 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):
"""Return true/false for the given feature.
"""
"""Return true/false for the given feature."""
return self.features.get(feature)
def get_code_pages(self):
"""Return the support code pages as a ``{name: index}`` dict.
"""
"""Return the support code pages as a ``{name: index}`` dict."""
return {v: k for k, v in self.codePages.items()}
def get_profile(name=None, **kwargs):
"""Get the profile by name; if no name is given, return the
default profile.
def get_profile(name: Optional[str] = None, **kwargs):
"""Get a profile by name.
If no name is given, return the default profile.
"""
if isinstance(name, Profile):
return name
clazz = get_profile_class(name or 'default')
clazz = get_profile_class(name or "default")
return clazz(**kwargs)
CLASS_CACHE = {}
def get_profile_class(name):
"""For the given profile name, load the data from the external
def get_profile_class(name: str) -> Type[BaseProfile]:
"""Load a profile class.
For the given profile name, load the data from the external
database, then generate dynamically a class.
"""
if name not in CLASS_CACHE:
profile_data = PROFILES[name]
profiles: Dict[str, Any] = CAPABILITIES["profiles"]
profile_data = profiles[name]
profile_name = clean(name)
class_name = '{}{}Profile'.format(
profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
CLASS_CACHE[name] = new_class
return CLASS_CACHE[name]
def clean(s):
"""Clean profile name."""
# 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
s = re.sub('^[^a-zA-Z_]+', '', s)
s = re.sub("^[^a-zA-Z_]+", "", s)
return str(s)
class Profile(get_profile_class('default')):
"""
For users, who want to provide their profile
# mute the mypy type issue with this dynamic base class function for now (: Any)
ProfileBaseClass: Any = get_profile_class("default")
class Profile(ProfileBaseClass):
"""Profile class for user usage.
For users, who want to provide their own profile.
"""
def __init__(self, columns=None, features=None):
"""Initialize profile."""
super(Profile, self).__init__()
self.columns = columns
self.features = features or {}
def get_columns(self, font):
"""Get column count of printer."""
if self.columns is not None:
return self.columns

View File

@@ -1,41 +1,44 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
""" CLI
"""CLI.
This module acts as a command line interface for python-escpos. It mirrors
closely the available ESCPOS commands while adding a couple extra ones for convience.
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.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import platform
from typing import Any, Dict, List
try:
import argcomplete
except ImportError:
# this CLI works nevertheless without argcomplete
pass # noqa
import sys
import six
from . import config
from . import printer as escpos_printer_module
from . import version
# Must be defined before it's used in DEMO_FUNCTIONS
def str_to_bool(string):
""" Used as a type in argparse so that we get back a proper
bool instead of always True
"""Convert string to bool.
Used as a type in argparse so that we get back a proper
bool instead of always True.
"""
return string.lower() in ('y', 'yes', '1', 'true')
return string.lower() in ("y", "yes", "1", "true")
# A list of functions that work better with a newline to be sent after them.
REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
REQUIRES_NEWLINE = ("qr", "barcode", "text", "block_text")
# Used in demo method
@@ -43,40 +46,46 @@ REQUIRES_NEWLINE = ('qr', 'barcode', 'text', 'block_text')
# manual translation is done in the case of barcodes_a -> barcode.
# Value: A list of dictionaries to pass to the escpos function as arguments.
DEMO_FUNCTIONS = {
'text': [
{'txt': 'Hello, World!\n', }
"text": [
{
"txt": "Hello, World!\n",
}
],
'qr': [
{'content': 'This tests a QR code'},
{'content': 'https://en.wikipedia.org/'}
"qr": [
{"content": "This tests a QR code"},
{"content": "https://en.wikipedia.org/"},
],
'barcodes_a': [
{'bc': 'UPC-A', 'code': '13243546576'},
{'bc': 'UPC-E', 'code': '132435'},
{'bc': 'EAN13', 'code': '1324354657687'},
{'bc': 'EAN8', 'code': '1324354'},
{'bc': 'CODE39', 'code': 'TEST'},
{'bc': 'ITF', 'code': '55867492279103'},
{'bc': 'NW7', 'code': 'A00000000A'},
"barcodes_a": [
{"bc": "UPC-A", "code": "13243546576"},
{"bc": "UPC-E", "code": "132435"},
{"bc": "EAN13", "code": "4006381333931"},
{"bc": "EAN8", "code": "1324354"},
{"bc": "CODE39", "code": "TEST"},
{"bc": "ITF", "code": "55867492279103"},
{"bc": "NW7", "code": "A00000000A"},
],
'barcodes_b': [
{'bc': 'UPC-A', 'code': '13243546576', 'function_type': 'B'},
{'bc': 'UPC-E', 'code': '132435', 'function_type': 'B'},
{'bc': 'EAN13', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'EAN8', 'code': '1324354', 'function_type': 'B'},
{'bc': 'CODE39', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'ITF', 'code': '55867492279103', 'function_type': 'B'},
{'bc': 'NW7', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': 'A00000000A', 'function_type': 'B'},
{'bc': 'CODE93', 'code': '1324354657687', 'function_type': 'B'},
{'bc': 'CODE128A', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128B', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'CODE128C', 'code': 'TEST', 'function_type': 'B'},
{'bc': 'GS1-128', 'code': '00123456780000000001', 'function_type': 'B'},
{'bc': 'GS1 DataBar Omnidirectional', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Truncated', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Limited', 'code': '0000000000000', 'function_type': 'B'},
{'bc': 'GS1 DataBar Expanded', 'code': '00AAAAAAA', 'function_type': 'B'},
"barcodes_b": [
{"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
{"bc": "UPC-E", "code": "132435", "function_type": "B"},
{"bc": "EAN13", "code": "4006381333931", "function_type": "B"},
{"bc": "EAN8", "code": "1324354", "function_type": "B"},
{"bc": "CODE39", "code": "TEST", "function_type": "B"},
{"bc": "ITF", "code": "55867492279103", "function_type": "B"},
{"bc": "NW7", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
{"bc": "CODE128A", "code": "TEST", "function_type": "B"},
{"bc": "CODE128B", "code": "TEST", "function_type": "B"},
{"bc": "CODE128C", "code": "TEST", "function_type": "B"},
{"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"},
{
"bc": "GS1 DataBar Omnidirectional",
"code": "0000000000000",
"function_type": "B",
},
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
],
}
@@ -86,435 +95,484 @@ DEMO_FUNCTIONS = {
# parser: A dict of args for command_parsers.add_parser
# defaults: A dict of args for subparser.set_defaults
# arguments: A list of dicts of args for subparser.add_argument
ESCPOS_COMMANDS = [
ESCPOS_COMMANDS: List[Dict[str, Any]] = [
{
'parser': {
'name': 'qr',
'help': 'Print a QR code',
"parser": {
"name": "qr",
"help": "Print a QR code",
},
'defaults': {
'func': 'qr',
"defaults": {
"func": "qr",
},
'arguments': [
"arguments": [
{
'option_strings': ('--content',),
'help': 'Text to print as a qr code',
'required': True,
"option_strings": ("--content",),
"help": "Text to print as a qr code",
"required": True,
},
{
'option_strings': ('--size',),
'help': 'QR code size (1-16) [default:3]',
'required': False,
'type': int,
"option_strings": ("--size",),
"help": "QR code size (1-16) [default:3]",
"required": False,
"type": int,
},
],
},
{
"parser": {
"name": "barcode",
"help": "Print a barcode",
},
"defaults": {
"func": "barcode",
},
"arguments": [
{
"option_strings": ("--code",),
"help": "Barcode data to print",
"required": True,
},
{
"option_strings": ("--bc",),
"help": "Barcode format",
"required": True,
},
{
"option_strings": ("--height",),
"help": "Barcode height in px",
"type": int,
},
{
"option_strings": ("--width",),
"help": "Barcode width",
"type": int,
},
{
"option_strings": ("--pos",),
"help": "Label position",
"choices": ["BELOW", "ABOVE", "BOTH", "OFF"],
},
{
"option_strings": ("--font",),
"help": "Label font",
"choices": ["A", "B"],
},
{
"option_strings": ("--align_ct",),
"help": "Align barcode center",
"type": str_to_bool,
},
{
"option_strings": ("--function_type",),
"help": "ESCPOS function type",
"choices": ["A", "B"],
},
{
"option_strings": ("--force_software",),
"help": "Force render and print barcode as an image",
"choices": ["graphics", "bitImageColumn", "bitImageRaster"],
},
],
},
{
"parser": {
"name": "text",
"help": "Print plain text",
},
"defaults": {
"func": "text",
},
"arguments": [
{
"option_strings": ("--txt",),
"help": "Plain text to print",
"required": True,
}
],
},
{
'parser': {
'name': 'barcode',
'help': 'Print a barcode',
"parser": {
"name": "block_text",
"help": "Print wrapped text",
},
'defaults': {
'func': 'barcode',
"defaults": {
"func": "block_text",
},
'arguments': [
"arguments": [
{
'option_strings': ('--code',),
'help': 'Barcode data to print',
'required': True,
"option_strings": ("--txt",),
"help": "block_text 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'],
"option_strings": ("--columns",),
"help": "Number of columns",
"type": int,
},
],
},
{
'parser': {
'name': 'text',
'help': 'Print plain text',
"parser": {
"name": "cut",
"help": "Cut the paper",
},
'defaults': {
'func': 'text',
"defaults": {
"func": "cut",
},
'arguments': [
"arguments": [
{
'option_strings': ('--txt',),
'help': 'Plain text to print',
'required': True,
}
],
},
{
'parser': {
'name': 'block_text',
'help': 'Print wrapped text',
},
'defaults': {
'func': 'block_text',
},
'arguments': [
{
'option_strings': ('--txt',),
'help': 'block_text to print',
'required': True,
},
{
'option_strings': ('--columns',),
'help': 'Number of columns',
'type': int,
"option_strings": ("--mode",),
"help": "Type of cut",
"choices": ["FULL", "PART"],
},
],
},
{
'parser': {
'name': 'cut',
'help': 'Cut the paper',
"parser": {
"name": "cashdraw",
"help": "Kick the cash drawer",
},
'defaults': {
'func': 'cut',
"defaults": {
"func": "cashdraw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--mode',),
'help': 'Type of cut',
'choices': ['FULL', 'PART'],
"option_strings": ("--pin",),
"help": "Which PIN to kick",
"choices": [2, 5],
},
],
},
{
'parser': {
'name': 'cashdraw',
'help': 'Kick the cash drawer',
"parser": {
"name": "image",
"help": "Print an image",
},
'defaults': {
'func': 'cashdraw',
"defaults": {
"func": "image",
},
'arguments': [
"arguments": [
{
'option_strings': ('--pin',),
'help': 'Which PIN to kick',
'choices': [2, 5],
"option_strings": ("--img_source",),
"help": "Path to image",
"required": True,
},
{
"option_strings": ("--impl",),
"help": "Implementation to use",
"choices": ["bitImageRaster", "bitImageColumn", "graphics"],
},
{
"option_strings": ("--high_density_horizontal",),
"help": "Image density (horizontal)",
"type": str_to_bool,
},
{
"option_strings": ("--high_density_vertical",),
"help": "Image density (vertical)",
"type": str_to_bool,
},
],
},
{
'parser': {
'name': 'image',
'help': 'Print an image',
"parser": {
"name": "fullimage",
"help": "Print a fullimage",
},
'defaults': {
'func': 'image',
"defaults": {
"func": "fullimage",
},
'arguments': [
"arguments": [
{
'option_strings': ('--img_source',),
'help': 'Path to image',
'required': True,
"option_strings": ("--img",),
"help": "Path to img",
"required": True,
},
{
'option_strings': ('--impl',),
'help': 'Implementation to use',
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'],
"option_strings": ("--max_height",),
"help": "Max height of image in px",
"type": int,
},
{
'option_strings': ('--high_density_horizontal',),
'help': 'Image density (horizontal)',
'type': str_to_bool,
"option_strings": ("--width",),
"help": "Max width of image in px",
"type": int,
},
{
'option_strings': ('--high_density_vertical',),
'help': 'Image density (vertical)',
'type': str_to_bool,
},
],
},
{
'parser': {
'name': 'fullimage',
'help': 'Print a fullimage',
},
'defaults': {
'func': 'fullimage',
},
'arguments': [
{
'option_strings': ('--img',),
'help': 'Path to img',
'required': True,
"option_strings": ("--histeq",),
"help": "Equalize the histogram",
"type": str_to_bool,
},
{
'option_strings': ('--max_height',),
'help': 'Max height of image in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Max width of image in px',
'type': int,
},
{
'option_strings': ('--histeq',),
'help': 'Equalize the histrogram',
'type': str_to_bool,
},
{
'option_strings': ('--bandsize',),
'help': 'Size of bands to divide into when printing',
'type': int,
"option_strings": ("--bandsize",),
"help": "Size of bands to divide into when printing",
"type": int,
},
],
},
{
'parser': {
'name': 'charcode',
'help': 'Set character code table',
"parser": {
"name": "charcode",
"help": "Set character code table",
},
'defaults': {
'func': 'charcode',
"defaults": {
"func": "charcode",
},
'arguments': [
"arguments": [
{
'option_strings': ('--code',),
'help': 'Character code',
'required': True,
"option_strings": ("--code",),
"help": "Character code",
"required": True,
},
],
},
{
'parser': {
'name': 'set',
'help': 'Set text properties',
"parser": {
"name": "set",
"help": "Set text properties",
},
'defaults': {
'func': 'set',
"defaults": {
"func": "set",
},
'arguments': [
"arguments": [
{
'option_strings': ('--align',),
'help': 'Horizontal alignment',
'choices': ['left', 'center', 'right'],
"option_strings": ("--align",),
"help": "Horizontal alignment",
"choices": ["left", "center", "right"],
},
{
'option_strings': ('--font',),
'help': 'Font choice',
'choices': ['left', 'center', 'right'],
"option_strings": ("--font",),
"help": "Font choice",
"choices": ["left", "center", "right"],
},
{
'option_strings': ('--text_type',),
'help': 'Text properties',
'choices': ['B', 'U', 'U2', 'BU', 'BU2', 'NORMAL'],
"option_strings": ("--text_type",),
"help": "Text properties",
"choices": ["B", "U", "U2", "BU", "BU2", "NORMAL"],
},
{
'option_strings': ('--width',),
'help': 'Width multiplier',
'type': int,
"option_strings": ("--width",),
"help": "Width multiplier",
"type": int,
},
{
'option_strings': ('--height',),
'help': 'Height multiplier',
'type': int,
"option_strings": ("--height",),
"help": "Height multiplier",
"type": int,
},
{
'option_strings': ('--density',),
'help': 'Print density',
'type': int,
"option_strings": ("--density",),
"help": "Print density",
"type": int,
},
{
'option_strings': ('--invert',),
'help': 'White on black printing',
'type': str_to_bool,
"option_strings": ("--invert",),
"help": "White on black printing",
"type": str_to_bool,
},
{
'option_strings': ('--smooth',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
"option_strings": ("--smooth",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
},
{
'option_strings': ('--flip',),
'help': 'Text smoothing. Effective on >: 4x4 text',
'type': str_to_bool,
"option_strings": ("--flip",),
"help": "Text smoothing. Effective on >: 4x4 text",
"type": str_to_bool,
},
],
},
{
'parser': {
'name': 'hw',
'help': 'Hardware operations',
"parser": {
"name": "hw",
"help": "Hardware operations",
},
'defaults': {
'func': 'hw',
"defaults": {
"func": "hw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--hw',),
'help': 'Operation',
'choices': ['INIT', 'SELECT', 'RESET'],
'required': True,
"option_strings": ("--hw",),
"help": "Operation",
"choices": ["INIT", "SELECT", "RESET"],
"required": True,
},
],
},
{
'parser': {
'name': 'control',
'help': 'Control sequences',
"parser": {
"name": "control",
"help": "Control sequences",
},
'defaults': {
'func': 'control',
"defaults": {
"func": "control",
},
'arguments': [
"arguments": [
{
'option_strings': ('--ctl',),
'help': 'Control sequence',
'choices': ['LF', 'FF', 'CR', 'HT', 'VT'],
'required': True,
"option_strings": ("--ctl",),
"help": "Control sequence",
"choices": ["LF", "FF", "CR", "HT", "VT"],
"required": True,
},
{
'option_strings': ('--pos',),
'help': 'Horizontal tab position (1-4)',
'type': int,
"option_strings": ("--pos",),
"help": "Horizontal tab position (1-4)",
"type": int,
},
],
},
{
'parser': {
'name': 'panel_buttons',
'help': 'Controls panel buttons',
"parser": {
"name": "panel_buttons",
"help": "Controls panel buttons",
},
'defaults': {
'func': 'panel_buttons',
"defaults": {
"func": "panel_buttons",
},
'arguments': [
"arguments": [
{
'option_strings': ('--enable',),
'help': 'Feed button enabled',
'type': str_to_bool,
'required': True,
"option_strings": ("--enable",),
"help": "Feed button enabled",
"type": str_to_bool,
"required": True,
},
],
},
{
'parser': {
'name': 'raw',
'help': 'Raw data',
"parser": {
"name": "raw",
"help": "Raw data",
},
'defaults': {
'func': '_raw',
"defaults": {
"func": "_raw",
},
'arguments': [
"arguments": [
{
'option_strings': ('--msg',),
'help': 'Raw data to send',
'required': True,
"option_strings": ("--msg",),
"help": "Raw data to send",
"required": True,
},
],
},
]
def main():
"""
Handles loading of configuration and creating and processing of command
line arguments. Called when run from a CLI.
"""
parser = argparse.ArgumentParser(
description='CLI for python-escpos',
epilog='Printer configuration is defined in the python-escpos config'
'file. See documentation for details.',
def print_extended_information() -> None:
"""Print diagnostic information for bug reports."""
print(f"* python-escpos version: `{version.version}`")
print(
f"* python version: `{platform.python_implementation()} v{platform.python_version()}`"
)
print(f"* platform: `{platform.platform()}`")
print(
f"* printer driver `USB` is usable: `{escpos_printer_module.Usb.is_usable()}`"
)
print(
f"* printer driver `File` is usable: `{escpos_printer_module.File.is_usable()}`"
)
print(
f"* printer driver `Network` is usable: `{escpos_printer_module.Network.is_usable()}`"
)
print(
f"* printer driver `Serial` is usable: `{escpos_printer_module.Serial.is_usable()}`"
)
print(f"* printer driver `LP` is usable: `{escpos_printer_module.LP.is_usable()}`")
print(
f"* printer driver `Dummy` is usable: `{escpos_printer_module.Dummy.is_usable()}`"
)
print(
f"* printer driver `CupsPrinter` is usable: `{escpos_printer_module.CupsPrinter.is_usable()}`"
)
print(
f"* printer driver `Win32Raw` is usable: `{escpos_printer_module.Win32Raw.is_usable()}`"
)
parser.register('type', 'bool', str_to_bool)
def generate_parser() -> argparse.ArgumentParser:
"""Generate an argparse parser."""
parser = argparse.ArgumentParser(
description="CLI for python-escpos",
epilog="Printer configuration is defined in the python-escpos config"
"file. See documentation for details.",
)
parser.register("type", "bool", str_to_bool)
# Allow config file location to be passed
parser.add_argument(
'-c', '--config',
help='Alternate path to the configuration file',
"-c",
"--config",
help="Alternate path to the configuration file",
)
# Everything interesting runs off of a subparser so we can use the format
# cli [subparser] -args
command_subparsers = parser.add_subparsers(
title='ESCPOS Command',
dest='parser',
title="ESCPOS Command",
dest="parser",
)
# 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
for command in ESCPOS_COMMANDS:
parser_command = command_subparsers.add_parser(**command['parser'])
parser_command.set_defaults(**command['defaults'])
for argument in command['arguments']:
option_strings = argument.pop('option_strings')
parser_command = command_subparsers.add_parser(**command["parser"])
parser_command.set_defaults(**command["defaults"])
for argument in command["arguments"]:
option_strings = argument.pop("option_strings")
parser_command.add_argument(*option_strings, **argument)
# Build any custom arguments
parser_command_demo = command_subparsers.add_parser('demo',
help='Demonstrates various functions')
parser_command_demo.set_defaults(func='demo')
parser_command_demo = command_subparsers.add_parser(
"demo", help="Demonstrates various functions"
)
parser_command_demo.set_defaults(func="demo")
demo_group = parser_command_demo.add_mutually_exclusive_group()
demo_group.add_argument(
'--barcodes-a',
help='Print demo barcodes for function type A',
action='store_true',
"--barcodes-a",
help="Print demo barcodes for function type A",
action="store_true",
)
demo_group.add_argument(
'--barcodes-b',
help='Print demo barcodes for function type B',
action='store_true',
"--barcodes-b",
help="Print demo barcodes for function type B",
action="store_true",
)
demo_group.add_argument(
'--qr',
help='Print some demo QR codes',
action='store_true',
"--qr",
help="Print some demo QR codes",
action="store_true",
)
demo_group.add_argument(
'--text',
help='Print some demo text',
action='store_true',
"--text",
help="Print some demo text",
action="store_true",
)
parser_command_version = command_subparsers.add_parser('version',
help='Print the version of python-escpos')
parser_command_version = command_subparsers.add_parser(
"version", help="Print the version information of python-escpos"
)
parser_command_version.set_defaults(version=True)
parser_command_version_extended = command_subparsers.add_parser(
"version_extended",
help="Print the extended version information of python-escpos (for bug reports)",
)
parser_command_version_extended.set_defaults(version_extended=True)
return parser
def main():
"""Handle main entry point of CLI script.
Handles loading of configuration and creating and processing of command
line arguments. Called when run from a CLI.
"""
parser = generate_parser()
# hook in argcomplete
if 'argcomplete' in globals():
if "argcomplete" in globals():
argcomplete.autocomplete(parser)
# Get only arguments actually passed
@@ -522,16 +580,23 @@ def main():
if not args_dict:
parser.print_help()
sys.exit()
command_arguments = dict([k, v] for k, v in six.iteritems(args_dict) if v is not None)
command_arguments = dict(
[k, v] for k, v in six.iteritems(args_dict) if v is not None
)
# If version should be printed, do this, then exit
print_version = command_arguments.pop('version', None)
print_version = command_arguments.pop("version", None)
if print_version:
print(version.version)
sys.exit()
print_version_extended = command_arguments.pop("version_extended", None)
if print_version_extended:
print_extended_information()
sys.exit()
# 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
saved_config = config.Config()
@@ -539,12 +604,12 @@ def main():
printer = saved_config.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
command_arguments.pop('parser', None)
command_arguments.pop("parser", None)
if hasattr(printer, target_command):
# print command with args
@@ -552,13 +617,14 @@ def main():
if target_command in REQUIRES_NEWLINE:
printer.text("\n")
else:
command_arguments['printer'] = printer
command_arguments["printer"] = printer
globals()[target_command](**command_arguments)
def demo(printer, **kwargs):
"""
Prints specificed demos. Called when CLI is passed `demo`. This function
"""Print demos.
Called when CLI is passed `demo`. This function
uses the DEMO_FUNCTIONS dictionary.
:param printer: A printer from escpos.printer
@@ -568,14 +634,14 @@ def demo(printer, **kwargs):
for demo_choice in kwargs.keys():
command = getattr(
printer,
demo_choice
.replace('barcodes_a', 'barcode')
.replace('barcodes_b', 'barcode')
demo_choice.replace("barcodes_a", "barcode").replace(
"barcodes_b", "barcode"
),
)
for params in DEMO_FUNCTIONS[demo_choice]:
command(**params)
printer.cut()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -1,24 +1,32 @@
"""Helper module for codepage handling."""
from .capabilities import CAPABILITIES
class CodePageManager:
"""Holds information about all the code pages (as defined
in escpos-printer-db).
"""Holds information about all the code pages.
Information as defined in escpos-printer-db.
"""
def __init__(self, data):
"""Initialize codepage manager."""
self.data = data
def get_all(self):
"""Get list of all codepages."""
return self.data.values()
@staticmethod
def get_encoding_name(encoding):
# TODO resolve the encoding alias
"""Get encoding name.
.. todo:: Resolve the encoding alias.
"""
return encoding.upper()
def get_encoding(self, encoding):
"""Return the encoding data."""
return self.data[encoding]
CodePages = CodePageManager(CAPABILITIES['encodings'])
CodePages = CodePageManager(CAPABILITIES["encodings"])

View File

@@ -1,33 +1,27 @@
""" ESC/POS configuration manager.
This module contains the implentations of abstract base class :py:class:`Config`.
"""ESC/POS configuration manager.
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 appdirs
import yaml
from . import printer
from . import exceptions
from . import exceptions, printer
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 specified directory. It
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):
""" Initialize configuration.
"""Initialize configuration.
Remember to add anything that needs to be reset between configurations
to self._reset_config
@@ -39,7 +33,7 @@ class Config(object):
self._printer_config = None
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
data.
@@ -51,42 +45,41 @@ class Config(object):
self._printer_config = 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
for the configuration file.
"""
self._reset_config()
if not config_path:
config_path = os.path.join(
appdirs.user_config_dir(self._app_name),
self._config_file
appdirs.user_config_dir(self._app_name), self._config_file
)
try:
# First check if it's file like. If it is, pyyaml can load it.
# I'm checking type instead of catching exceptions to keep the
# exception handling simple
if hasattr(config_path, 'read'):
if hasattr(config_path, "read"):
config = yaml.safe_load(config_path)
else:
# If it isn't, it's a path. We have to open it first, otherwise
# pyyaml will try to read it as yaml
with open(config_path, 'rb') as config_file:
with open(config_path, "rb") as config_file:
config = yaml.safe_load(config_file)
except EnvironmentError:
raise exceptions.ConfigNotFoundError('Couldn\'t read config at {config_path}'.format(
config_path=str(config_path),
))
raise exceptions.ConfigNotFoundError(
"Couldn't read config at {config_path}".format(
config_path=str(config_path),
)
)
except yaml.YAMLError:
raise exceptions.ConfigSyntaxError('Error parsing YAML')
raise exceptions.ConfigSyntaxError("Error parsing YAML")
if 'printer' in config:
self._printer_config = config['printer']
self._printer_name = self._printer_config.pop('type').title()
if "printer" in config:
self._printer_config = config["printer"]
self._printer_name = self._printer_config.pop("type").title()
if not self._printer_name or not hasattr(printer, self._printer_name):
raise exceptions.ConfigSyntaxError(
@@ -98,17 +91,18 @@ class Config(object):
self._has_loaded = True
def printer(self):
""" Returns a printer that was defined in the config, or throws an
exception.
"""Return a printer that was defined in the config.
This method loads the default config if one hasn't beeen already loaded.
Throw an exception on error.
This method loads the default config if one has not been already loaded.
"""
if not self._has_loaded:
self.load()
if not self._printer_name:
raise exceptions.ConfigSectionMissingError('printer')
raise exceptions.ConfigSectionMissingError("printer")
if not self._printer:
# We could catch init errors and make them a ConfigSyntaxError,

View File

@@ -1,139 +1,161 @@
# -*- coding: utf-8 -*-
""" Set of ESC/POS Commands (Constants)
This module contains constants that are described in the esc/pos-documentation.
Since there is no definitive and unified specification for all esc/pos-like printers the constants could later be
This module contains constants that are described in the Esc/Pos-documentation.
Since there is no definitive and unified specification for all Esc/Pos-like printers the constants could later be
moved to `capabilities` as in `escpos-php by @mike42 <https://github.com/mike42/escpos-php>`_.
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:author: python-escpos developers
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from typing import Dict
import six
from .types import ConstTxtStyleClass
# Control characters
# as labelled in http://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL = b'\x00'
EOT = b'\x04'
ENQ = b'\x05'
DLE = b'\x10'
DC4 = b'\x14'
CAN = b'\x18'
ESC = b'\x1b'
FS = b'\x1c'
GS = b'\x1d'
# as labelled in https://www.novopos.ch/client/EPSON/TM-T20/TM-T20_eng_qr.pdf
NUL: bytes = b"\x00"
EOT: bytes = b"\x04"
ENQ: bytes = b"\x05"
DLE: bytes = b"\x10"
DC4: bytes = b"\x14"
CAN: bytes = b"\x18"
ESC: bytes = b"\x1b"
FS: bytes = b"\x1c"
GS: bytes = b"\x1d"
# Feed control sequences
CTL_LF = b'\n' # Print and line feed
CTL_FF = b'\f' # Form feed
CTL_CR = b'\r' # Carriage return
CTL_HT = b'\t' # Horizontal tab
CTL_SET_HT = ESC + b'\x44' # Set horizontal tab positions
CTL_VT = b'\v' # Vertical tab
CTL_LF: bytes = b"\n" #: Print and line feed
CTL_FF: bytes = b"\f" #: Form feed
CTL_CR: bytes = b"\r" #: Carriage return
CTL_HT: bytes = b"\t" #: Horizontal tab
CTL_SET_HT: bytes = ESC + b"\x44" #: Set horizontal tab positions
CTL_VT: bytes = b"\v" #: Vertical tab
# Printer hardware
HW_INIT = ESC + b'@' # Clear data in buffer and reset modes
HW_SELECT = ESC + b'=\x01' # Printer select
HW_INIT: bytes = ESC + b"@" # Clear data in buffer and reset modes
HW_SELECT: bytes = ESC + b"=\x01" # Printer select
HW_RESET = ESC + b'\x3f\x0a\x00' # Reset printer hardware
# (TODO: Where is this specified?)
HW_RESET: bytes = ESC + b"\x3f\x0a\x00" # Reset printer hardware
# (TODO: Where is this specified?)
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
_CASH_DRAWER = lambda m, t1='', t2='': ESC + b'p' + m + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_DEC_SEQUENCE = lambda esc, p, m, t1=50, t2=50: six.int2byte(esc) + six.int2byte(p) + six.int2byte(m) + six.int2byte(t1) + six.int2byte(t2)
CD_KICK_2 = _CASH_DRAWER(b'\x00', 50, 50) # Sends a pulse to pin 2 []
CD_KICK_5 = _CASH_DRAWER(b'\x01', 50, 50) # Sends a pulse to pin 5 []
_CASH_DRAWER = (
lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2)
)
#: decimal cash drawer kick sequence
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)
)
#: Sends a pulse to pin 2 []
CD_KICK_2: bytes = _CASH_DRAWER(b"\x00", 50, 50)
#: Sends a pulse to pin 5 []
CD_KICK_5: bytes = _CASH_DRAWER(b"\x01", 50, 50)
# Paper Cutter
_CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
_CUT_PAPER = lambda m: GS + b"V" + m
PAPER_FULL_CUT: bytes = _CUT_PAPER(b"\x00") #: Full cut paper
PAPER_PART_CUT: bytes = _CUT_PAPER(b"\x01") #: Partial cut paper
# Beep
BEEP = b'\x07'
# Beep (please note that the actual beep sequence may differ between devices)
BEEP: bytes = b"\x07"
# Internal buzzer (only supported printers)
BUZZER: bytes = ESC + b"\x42"
# Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
_PANEL_BUTTON = lambda n: ESC + b"c5" + six.int2byte(n)
PANEL_BUTTON_ON: bytes = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF: bytes = _PANEL_BUTTON(1) # disable all panel buttons
# Line display printing
LINE_DISPLAY_OPEN = ESC + b'\x3d\x02'
LINE_DISPLAY_CLEAR = ESC + b'\x40'
LINE_DISPLAY_CLOSE = ESC + b'\x3d\x01'
LINE_DISPLAY_OPEN: bytes = ESC + b"\x3d\x02"
LINE_DISPLAY_CLEAR: bytes = ESC + b"\x40"
LINE_DISPLAY_CLOSE: bytes = ESC + b"\x3d\x01"
# Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
SHEET_SLIP_MODE: bytes = ESC + b"\x63\x30\x04" # slip paper
SHEET_ROLL_MODE: bytes = ESC + b"\x63\x30\x01" # paper roll
# Slip specific codes
SLIP_EJECT: bytes = ESC + b"\x4b\xc0" # Eject the slip or cheque
SLIP_SELECT: bytes = FS # Select the slip station as default station
SLIP_SET_WAIT_TIME: bytes = (
ESC + b"\x1b\x66"
) # Set timeout waiting for a slip/cheque to be inserted
SLIP_PRINT_AND_EJECT: bytes = (
b"\x0c" # Print the buffer and eject (after waiting for the paper to be inserted)
)
# Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too.
TXT_SIZE = GS + b'!'
TXT_SIZE: bytes = GS + b"!"
TXT_NORMAL = ESC + b'!\x00' # Normal text
TXT_NORMAL: bytes = ESC + b"!\x00" # Normal text
TXT_STYLE = {
'bold': {
False: ESC + b'\x45\x00', # Bold font OFF
True: ESC + b'\x45\x01' # Bold font ON
#: text style dictionary for :py:meth:`escpos.escpos.Escpos.set`
TXT_STYLE: ConstTxtStyleClass = {
"bold": {
False: ESC + b"\x45\x00", # Bold font OFF
True: ESC + b"\x45\x01", # Bold font ON
},
'underline': {
0: ESC + b'\x2d\x00', # Underline font OFF
1: ESC + b'\x2d\x01', # Underline font 1-dot ON
2: ESC + b'\x2d\x02' # Underline font 2-dot ON
"underline": {
0: ESC + b"\x2d\x00", # Underline font OFF
1: ESC + b"\x2d\x01", # Underline font 1-dot ON
2: ESC + b"\x2d\x02", # Underline font 2-dot ON
},
'size': {
'normal': TXT_NORMAL + ESC + b'!\x00', # Normal text
'2h': TXT_NORMAL + ESC + b'!\x10', # Double height text
'2w': TXT_NORMAL + ESC + b'!\x20', # Double width text
'2x': TXT_NORMAL + ESC + b'!\x30' # Quad area text
"size": {
"normal": TXT_NORMAL + ESC + b"!\x00", # Normal text
"2h": TXT_NORMAL + ESC + b"!\x10", # Double height text
"2w": TXT_NORMAL + ESC + b"!\x20", # Double width text
"2x": TXT_NORMAL + ESC + b"!\x30", # Quad area text
},
'font': {
'a': ESC + b'\x4d\x00', # Font type A
'b': ESC + b'\x4d\x00' # Font type B
"font": {
"a": ESC + b"\x4d\x00", # Font type A
"b": ESC + b"\x4d\x00", # Font type B
},
'align': {
'left': ESC + b'\x61\x00', # Left justification
'center': ESC + b'\x61\x01', # Centering
'right': ESC + b'\x61\x02' # Right justification
"align": {
"left": ESC + b"\x61\x00", # Left justification
"center": ESC + b"\x61\x01", # Centering
"right": ESC + b"\x61\x02", # Right justification
},
'invert': {
True: GS + b'\x42\x01', # Inverse Printing ON
False: GS + b'\x42\x00' # Inverse Printing OFF
"invert": {
True: GS + b"\x42\x01", # Inverse Printing ON
False: GS + b"\x42\x00", # Inverse Printing OFF
},
'color': {
'black': ESC + b'\x72\x00', # Default Color
'red': ESC + b'\x72\x01' # Alternative Color, Usually Red
"color": {
"black": ESC + b"\x72\x00", # Default Color
"red": ESC + b"\x72\x01", # Alternative Color, Usually Red
},
'flip': {
True: ESC + b'\x7b\x01', # Flip ON
False: ESC + b'\x7b\x00' # Flip OFF
"flip": {True: ESC + b"\x7b\x01", False: ESC + b"\x7b\x00"}, # Flip ON # Flip OFF
"density": {
0: GS + b"\x7c\x00", # Printing Density -50%
1: GS + b"\x7c\x01", # Printing Density -37.5%
2: GS + b"\x7c\x02", # Printing Density -25%
3: GS + b"\x7c\x03", # Printing Density -12.5%
4: GS + b"\x7c\x04", # Printing Density 0%
5: GS + b"\x7c\x08", # Printing Density +50%
6: GS + b"\x7c\x07", # Printing Density +37.5%
7: GS + b"\x7c\x06", # Printing Density +25%
8: GS + b"\x7c\x05", # Printing Density +12.5%
},
'density': {
0: GS + b'\x7c\x00', # Printing Density -50%
1: GS + b'\x7c\x01', # Printing Density -37.5%
2: GS + b'\x7c\x02', # Printing Density -25%
3: GS + b'\x7c\x03', # Printing Density -12.5%
4: GS + b'\x7c\x04', # Printing Density 0%
5: GS + b'\x7c\x08', # Printing Density +50%
6: GS + b'\x7c\x07', # Printing Density +37.5%
7: GS + b'\x7c\x06', # Printing Density +25%
8: GS + b'\x7c\x05' # Printing Density +12.5%
"smooth": {
True: GS + b"\x62\x01", # Smooth ON
False: GS + b"\x62\x00", # Smooth OFF
},
'smooth': {
True: GS + b'\x62\x01', # Smooth ON
False: GS + b'\x62\x00' # Smooth OFF
},
'height': { # Custom text height
"height": { # Custom text height
1: 0x00,
2: 0x01,
3: 0x02,
@@ -141,9 +163,9 @@ TXT_STYLE = {
5: 0x04,
6: 0x05,
7: 0x06,
8: 0x07
8: 0x07,
},
'width': { # Custom text width
"width": { # Custom text width
1: 0x00,
2: 0x10,
3: 0x20,
@@ -151,128 +173,135 @@ TXT_STYLE = {
5: 0x40,
6: 0x50,
7: 0x60,
8: 0x70
}
8: 0x70,
},
}
# Fonts
SET_FONT = lambda n: ESC + b'\x4d' + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B
SET_FONT = lambda n: ESC + b"\x4d" + n
TXT_FONT_A: bytes = SET_FONT(b"\x00") #: Font type A
TXT_FONT_B: bytes = SET_FONT(b"\x01") #: Font type B
# Spacing
LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
LINESPACING_RESET = ESC + b"2"
LINESPACING_FUNCS: Dict[int, bytes] = {
60: ESC + b"A", # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b"+", # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
# Prefix to change the codepage. You need to attach a byte to indicate
# the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE = ESC + b'\x74'
#: Prefix to change the codepage. You need to attach a byte to indicate
#: the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE: bytes = ESC + b"\x74"
# Barcode format
_SET_BARCODE_TXT_POS = lambda n: GS + b'H' + n
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b'\x00') # HRI barcode chars OFF
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b'\x01') # HRI barcode chars above
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b'\x02') # HRI barcode chars below
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b'\x03') # HRI both above and below
_SET_BARCODE_TXT_POS = lambda n: GS + b"H" + n
BARCODE_TXT_OFF: bytes = _SET_BARCODE_TXT_POS(b"\x00") #: HRI barcode chars OFF
BARCODE_TXT_ABV: bytes = _SET_BARCODE_TXT_POS(b"\x01") #: HRI barcode chars above
BARCODE_TXT_BLW: bytes = _SET_BARCODE_TXT_POS(b"\x02") #: HRI barcode chars below
BARCODE_TXT_BTH: bytes = _SET_BARCODE_TXT_POS(b"\x03") #: HRI both above and below
_SET_HRI_FONT = lambda n: GS + b'f' + n
BARCODE_FONT_A = _SET_HRI_FONT(b'\x00') # Font type A for HRI barcode chars
BARCODE_FONT_B = _SET_HRI_FONT(b'\x01') # Font type B for HRI barcode chars
_SET_HRI_FONT = lambda n: GS + b"f" + n
BARCODE_FONT_A: bytes = _SET_HRI_FONT(b"\x00") #: Font type A for HRI barcode chars
BARCODE_FONT_B: bytes = _SET_HRI_FONT(b"\x01") #: Font type B for HRI barcode chars
BARCODE_HEIGHT = GS + b'h' # Barcode Height [1-255]
BARCODE_WIDTH = GS + b'w' # Barcode Width [2-6]
BARCODE_HEIGHT: bytes = GS + b"h" #: Barcode Height [1-255]
BARCODE_WIDTH: bytes = GS + b"w" #: Barcode Width [2-6]
# NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
# two "print bar code" commands:
# - Type A: "GS k <type as integer> <data> NUL"
# - TYPE B: "GS k <type as letter> <data length> <data>"
# The latter command supports more barcode types
_SET_BARCODE_TYPE = lambda m: GS + b'k' + six.int2byte(m)
_SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m)
# Barcodes for printing function type A
BARCODE_TYPE_A = {
'UPC-A': _SET_BARCODE_TYPE(0),
'UPC-E': _SET_BARCODE_TYPE(1),
'EAN13': _SET_BARCODE_TYPE(2),
'EAN8': _SET_BARCODE_TYPE(3),
'CODE39': _SET_BARCODE_TYPE(4),
'ITF': _SET_BARCODE_TYPE(5),
'NW7': _SET_BARCODE_TYPE(6),
'CODABAR': _SET_BARCODE_TYPE(6), # Same as NW7
#: Barcodes for printing function type A
BARCODE_TYPE_A: Dict[str, bytes] = {
"UPC-A": _SET_BARCODE_TYPE(0),
"UPC-E": _SET_BARCODE_TYPE(1),
"EAN13": _SET_BARCODE_TYPE(2),
"EAN8": _SET_BARCODE_TYPE(3),
"CODE39": _SET_BARCODE_TYPE(4),
"ITF": _SET_BARCODE_TYPE(5),
"NW7": _SET_BARCODE_TYPE(6),
"CODABAR": _SET_BARCODE_TYPE(6), # Same as NW7
}
# Barcodes for printing function type B
# The first 8 are the same barcodes as type A
BARCODE_TYPE_B = {
'UPC-A': _SET_BARCODE_TYPE(65),
'UPC-E': _SET_BARCODE_TYPE(66),
'EAN13': _SET_BARCODE_TYPE(67),
'EAN8': _SET_BARCODE_TYPE(68),
'CODE39': _SET_BARCODE_TYPE(69),
'ITF': _SET_BARCODE_TYPE(70),
'NW7': _SET_BARCODE_TYPE(71),
'CODABAR': _SET_BARCODE_TYPE(71), # Same as NW7
'CODE93': _SET_BARCODE_TYPE(72),
'CODE128': _SET_BARCODE_TYPE(73),
'GS1-128': _SET_BARCODE_TYPE(74),
'GS1 DATABAR OMNIDIRECTIONAL': _SET_BARCODE_TYPE(75),
'GS1 DATABAR TRUNCATED': _SET_BARCODE_TYPE(76),
'GS1 DATABAR LIMITED': _SET_BARCODE_TYPE(77),
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
#: Barcodes for printing function type B
#: The first 8 are the same barcodes as type A
BARCODE_TYPE_B: Dict[str, bytes] = {
"UPC-A": _SET_BARCODE_TYPE(65),
"UPC-E": _SET_BARCODE_TYPE(66),
"EAN13": _SET_BARCODE_TYPE(67),
"EAN8": _SET_BARCODE_TYPE(68),
"CODE39": _SET_BARCODE_TYPE(69),
"ITF": _SET_BARCODE_TYPE(70),
"NW7": _SET_BARCODE_TYPE(71),
"CODABAR": _SET_BARCODE_TYPE(71), # Same as NW7
"CODE93": _SET_BARCODE_TYPE(72),
"CODE128": _SET_BARCODE_TYPE(73),
"GS1-128": _SET_BARCODE_TYPE(74),
"GS1 DATABAR OMNIDIRECTIONAL": _SET_BARCODE_TYPE(75),
"GS1 DATABAR TRUNCATED": _SET_BARCODE_TYPE(76),
"GS1 DATABAR LIMITED": _SET_BARCODE_TYPE(77),
"GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78),
}
#: supported barcode formats
BARCODE_FORMATS = {
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"),
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
'EAN13': ([(12, 13)], "^[0-9]{12,13}$"),
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"),
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
'ITF': ([(2, 255)], "^([0-9]{2})+$"),
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"),
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"),
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"),
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"),
"UPC-A": ([(11, 12)], r"^[0-9]{11,12}$"),
"UPC-E": ([(7, 8), (11, 12)], r"^([0-9]{7,8}|[0-9]{11,12})$"),
"EAN13": ([(12, 13)], r"^[0-9]{12,13}$"),
"EAN8": ([(7, 8)], r"^[0-9]{7,8}$"),
"CODE39": ([(1, 255)], r"^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
"ITF": ([(2, 255)], r"^([0-9]{2})+$"),
"NW7": ([(1, 255)], r"^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
"CODABAR": ([(1, 255)], r"^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
"CODE93": ([(1, 255)], r"^[\x00-\x7F]+$"),
"CODE128": ([(2, 255)], r"^\{[A-C][\x00-\x7F]+$"),
"GS1-128": ([(2, 255)], r"^\{[A-C][\x00-\x7F]+$"), # same as CODE128
"GS1 DATABAR OMNIDIRECTIONAL": ([(13, 13)], r"^[0-9]{13}$"),
"GS1 DATABAR TRUNCATED": (
[(13, 13)],
r"^[0-9]{13}$",
), # same as GS1 omnidirectional
"GS1 DATABAR LIMITED": ([(13, 13)], r"^[01][0-9]{12}$"),
"GS1 DATABAR EXPANDED": (
[(2, 255)],
r"^\([0-9][A-Za-z0-9 \!\"\%\&'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$",
),
}
BARCODE_TYPES = {
'A': BARCODE_TYPE_A,
'B': BARCODE_TYPE_B,
BARCODE_TYPES: Dict[str, Dict[str, bytes]] = {
"A": BARCODE_TYPE_A,
"B": BARCODE_TYPE_B,
}
# QRCode error correction levels
QR_ECLEVEL_L = 0
QR_ECLEVEL_M = 1
QR_ECLEVEL_Q = 2
QR_ECLEVEL_H = 3
QR_ECLEVEL_L: int = 0
QR_ECLEVEL_M: int = 1
QR_ECLEVEL_Q: int = 2
QR_ECLEVEL_H: int = 3
# QRcode models
QR_MODEL_1 = 1
QR_MODEL_2 = 2
QR_MICRO = 3
QR_MODEL_1: int = 1
QR_MODEL_2: int = 2
QR_MICRO: int = 3
# Image format
# NOTE: _PRINT_RASTER_IMG is the obsolete ESC/POS "print raster bit image"
# command. The constants include a fragment of the data's header.
_PRINT_RASTER_IMG = lambda data: GS + b'v0' + data
S_RASTER_N = _PRINT_RASTER_IMG(b'\x00') # Set raster image normal size
S_RASTER_2W = _PRINT_RASTER_IMG(b'\x01') # Set raster image double width
S_RASTER_2H = _PRINT_RASTER_IMG(b'\x02') # Set raster image double height
S_RASTER_Q = _PRINT_RASTER_IMG(b'\x03') # Set raster image quadruple
_PRINT_RASTER_IMG = lambda data: GS + b"v0" + data
S_RASTER_N: bytes = _PRINT_RASTER_IMG(b"\x00") # Set raster image normal size
S_RASTER_2W: bytes = _PRINT_RASTER_IMG(b"\x01") # Set raster image double width
S_RASTER_2H: bytes = _PRINT_RASTER_IMG(b"\x02") # Set raster image double height
S_RASTER_Q: bytes = _PRINT_RASTER_IMG(b"\x03") # Set raster image quadruple
# Status Command
RT_STATUS = DLE + EOT
RT_STATUS_ONLINE = RT_STATUS + b'\x01'
RT_STATUS_PAPER = RT_STATUS + b'\x04'
RT_MASK_ONLINE = 8
RT_MASK_PAPER = 18
RT_MASK_LOWPAPER = 30
RT_MASK_NOPAPER = 114
RT_STATUS: bytes = DLE + EOT
RT_STATUS_ONLINE: bytes = RT_STATUS + b"\x01"
RT_STATUS_PAPER: bytes = RT_STATUS + b"\x04"
RT_MASK_ONLINE: int = 8
RT_MASK_PAPER: int = 18
RT_MASK_LOWPAPER: int = 30
RT_MASK_NOPAPER: int = 114

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
""" ESC/POS Exceptions classes
"""ESC/POS Exceptions classes.
Result/Exit codes:
@@ -13,27 +13,32 @@ Result/Exit codes:
- `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
- `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
- `80` = Invalid char code :py:exc:`~escpos.exceptions.CharCodeError`
- `90` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
- `90` = Device not found :py:exc:`~escpos.exceptions.DeviceNotFoundError`
- `91` = USB device not found :py:exc:`~escpos.exceptions.USBNotFoundError`
- `100` = Set variable out of range :py:exc:`~escpos.exceptions.SetVariableError`
- `200` = Configuration not found :py:exc:`~escpos.exceptions.ConfigNotFoundError`
- `210` = Configuration syntax error :py:exc:`~escpos.exceptions.ConfigSyntaxError`
- `220` = Configuration section not found :py:exc:`~escpos.exceptions.ConfigSectionMissingError`
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:author: python-escpos developers
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
class Error(Exception):
""" Base class for ESC/POS errors """
"""Base class for ESC/POS errors.
inheritance:
.. inheritance-diagram:: escpos.exceptions.Error
:parts: 1
"""
def __init__(self, msg, status=None):
"""Initialize Error object."""
Exception.__init__(self)
self.msg = msg
self.resultcode = 1
@@ -41,218 +46,378 @@ class Error(Exception):
self.resultcode = status
def __str__(self):
"""Return string representation of Error."""
return self.msg
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
one of those specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The returned error code is `10`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeTypeError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize BarcodeTypeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 10
def __str__(self):
"""Return string representation of BarcodeTypeError."""
return "No Barcode type is defined ({msg})".format(msg=self.msg)
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.
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 return code is `20`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeSizeError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize BarcodeSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 20
def __str__(self):
"""Return string representation of BarcodeSizeError."""
return "Barcode size is out of range ({msg})".format(msg=self.msg)
class BarcodeCodeError(Error):
""" No Barcode code was supplied, or it is incorrect.
"""No Barcode code was supplied, or it is incorrect.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter
was True and the check failed.
The returncode for this exception is `30`.
The return code for this exception is `30`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.BarcodeCodeError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize BarcodeCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 30
def __str__(self):
"""Return string representation of BarcodeCodeError."""
return "No Barcode code was supplied ({msg})".format(msg=self.msg)
class ImageSizeError(Error):
""" Image height is longer than 255px and can't be printed.
"""Image height is longer than 255px and can't be printed.
The return code for this exception is `40`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ImageSizeError
:parts: 1
The returncode for this exception is `40`.
"""
def __init__(self, msg=""):
"""Initialize ImageSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 40
def __str__(self):
return "Image height is longer than 255px and can't be printed ({msg})".format(msg=self.msg)
"""Return string representation of ImageSizeError."""
return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
class ImageWidthError(Error):
""" Image width is too large.
"""Image width is too large.
The return code for this exception is `41`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ImageWidthError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize ImageWidthError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 41
def __str__(self):
"""Return string representation of ImageWidthError."""
return "Image width is too large ({msg})".format(msg=self.msg)
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`.
The returncode for this exception is `50`.
The return code for this exception is `50`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.TextError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize TextError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 50
def __str__(self):
return "Text string must be supplied to the text() method ({msg})".format(msg=self.msg)
"""Return string representation of TextError."""
return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg
)
class CashDrawerError(Error):
""" Valid pin must be set in order to send pulse.
"""Valid pin must be set in order to send pulse.
A valid pin number has to be passed onto the method :py:meth:`escpos.escpos.Escpos.cashdraw`.
The returncode for this exception is `60`.
The return code for this exception is `60`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.CashDrawerError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize CashDrawerError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 60
def __str__(self):
"""Return string representation of CashDrawerError."""
return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
class TabPosError(Error):
""" Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
"""Tab position is invalid.
Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The returncode for this exception is `70`.
The return code for this exception is `70`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.TabPosError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize TabPosError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(msg=self.msg)
"""Return string representation of TabPosError."""
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg
)
class CharCodeError(Error):
""" Valid char code must be set.
"""Valid char code must be set.
The supplied charcode-name in :py:meth:`escpos.escpos.Escpos.charcode` is unknown.
Ths returncode for this exception is `80`.
The return code for this exception is `80`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.CharCodeError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize CharCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 80
def __str__(self):
"""Return string representation of CharCodeError."""
return "Valid char code must be set ({msg})".format(msg=self.msg)
class USBNotFoundError(Error):
""" Device wasn't found (probably not plugged in)
class DeviceNotFoundError(Error):
"""Device was not found.
The device seems to be not accessible.
The return code for this exception is `90`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.Error
:parts: 1
The USB device seems to be not plugged in.
Ths returncode for this exception is `90`.
"""
def __init__(self, msg=""):
"""Initialize DeviceNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 90
def __str__(self):
return "USB device not found ({msg})".format(msg=self.msg)
"""Return string representation of DeviceNotFoundError."""
return f"Device not found ({self.msg})"
class USBNotFoundError(DeviceNotFoundError):
"""USB device was not found (probably not plugged in).
The USB device seems to be not plugged in.
The return code for this exception is `91`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.USBNotFoundError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize USBNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 91
def __str__(self):
"""Return string representation of USBNotFoundError."""
return f"USB device not found ({self.msg})"
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
Ths returncode for this exception is `100`.
The return code for this exception is `100`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.SetVariableError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize SetVariableError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 100
def __str__(self):
"""Return string representation of SetVariableError."""
return "Set variable out of range ({msg})".format(msg=self.msg)
# Configuration errors
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
Ths returncode for this exception is `200`.
The return code for this exception is `200`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigNotFoundError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize ConfigNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 200
def __str__(self):
"""Return string representation of ConfigNotFoundError."""
return "Configuration not found ({msg})".format(msg=self.msg)
class ConfigSyntaxError(Error):
""" The configuration file is invalid
"""The configuration file is invalid.
The syntax is incorrect
Ths returncode for this exception is `210`.
The return code for this exception is `210`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigSyntaxError
:parts: 1
"""
def __init__(self, msg=""):
"""Initialize ConfigSyntaxError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 210
def __str__(self):
"""Return string representation of ConfigSyntaxError."""
return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
class ConfigSectionMissingError(Error):
""" The configuration file is missing a section
"""The configuration file is missing a section.
The part of the config asked for does not exist in the loaded configuration
The return code for this exception is `220`.
inheritance:
.. inheritance-diagram:: escpos.exceptions.ConfigSectionMissingError
:parts: 1
The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`.
"""
def __init__(self, msg=""):
"""Initialize ConfigSectionMissingError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 220
def __str__(self):
"""Return string representation of ConfigSectionMissingError."""
return "Configuration section is missing ({msg})".format(msg=self.msg)

View File

@@ -1,4 +1,4 @@
""" Image format handling class
"""Image format handling class.
This module contains the image format handler :py:class:`EscposImage`.
@@ -8,12 +8,9 @@ This module contains the image format handler :py:class:`EscposImage`.
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import math
from PIL import Image, ImageOps
@@ -26,8 +23,7 @@ class EscposImage(object):
"""
def __init__(self, img_source):
"""
Load in an image
"""Load in an image.
:param img_source: PIL.Image, or filename to load one from.
"""
@@ -41,7 +37,7 @@ class EscposImage(object):
# Convert to white RGB background, paste over white background
# 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.paste(img_original, mask=img_original.split()[3])
# Convert down to greyscale
@@ -53,30 +49,23 @@ class EscposImage(object):
@property
def width(self):
"""
Width of image in pixels
"""
"""Return width of image in pixels."""
width_pixels, _ = self._im.size
return width_pixels
@property
def width_bytes(self):
"""
Width of image if you use 8 pixels per byte and 0-pad at the end.
"""
"""Return width of image if you use 8 pixels per byte and 0-pad at the end."""
return (self.width + 7) >> 3
@property
def height(self):
"""
Height of image in pixels
"""
"""Height of image in pixels."""
_, height_pixels = self._im.size
return height_pixels
def to_column_format(self, high_density_vertical=True):
"""
Extract slices of an image as equal-sized blobs of column-format data.
"""Extract slices of an image as equal-sized blobs of column-format data.
:param high_density_vertical: Printed line height in dots
"""
@@ -89,23 +78,20 @@ class EscposImage(object):
box = (left, top, left + line_height, top + height_pixels)
im_slice = im.transform((line_height, height_pixels), Image.EXTENT, box)
im_bytes = im_slice.tobytes()
yield(im_bytes)
yield (im_bytes)
left += line_height
def to_raster_format(self):
"""
Convert image to raster-format binary
"""
"""Convert image to raster-format binary."""
return self._im.tobytes()
def split(self, fragment_height):
"""
Split an image into multiple fragments after fragment_height pixels
"""Split an image into multiple fragments after fragment_height pixels.
:param fragment_height: height of fragment
:return: list of PIL objects
"""
passes = int(math.ceil(self.height/fragment_height))
passes = int(math.ceil(self.height / fragment_height))
fragments = []
for n in range(0, passes):
left = 0
@@ -117,7 +103,7 @@ class EscposImage(object):
return fragments
def center(self, max_width):
"""In-place image centering
"""Center image in place.
:param: Maximum width in order to deduce x offset for centering
:return: None

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" Magic Encode
"""Magic Encode.
This module tries to convert an UTF-8 string to an encoded string for the printer.
It uses trial and error in order to guess the right codepage.
@@ -12,78 +12,78 @@ The code is based on the encoding-code in py-xml-escpos by @fvdsn.
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import re
from builtins import bytes
import six
from .codepages import CodePages
from .constants import CODEPAGE_CHANGE
from .exceptions import Error
from .codepages import CodePages
import six
class Encoder(object):
"""Takes a list of available code spaces. Picks the right one for a
given character.
"""Take available code spaces and pick the right one for a given character.
Note: To determine the code page, it needs to do the conversion, and
thus already knows what the final byte in the target encoding would
be. Nevertheless, the API of this class doesn't return the byte.
be. Nevertheless, the API of this class does not return the byte.
The caller use to do the character conversion itself.
$ python -m timeit -s "{u'ö':'a'}.get(u'ö')"
100000000 loops, best of 3: 0.0133 usec per loop
$ python -m timeit -s "u'ö'.encode('latin1')"
100000000 loops, best of 3: 0.0141 usec per loop
"""
def __init__(self, codepage_map):
"""Initialize encoder."""
self.codepages = codepage_map
self.available_encodings = set(codepage_map.keys())
self.available_characters = {}
self.used_encodings = set()
def get_sequence(self, encoding):
"""Get a sequence."""
return int(self.codepages[encoding])
def get_encoding_name(self, encoding):
"""Given an encoding provided by the user, will return a
"""Return a canonical encoding name.
Given an encoding provided by the user, will return a
canonical encoding name; and also validate that the encoding
is supported.
TODO: Support encoding aliases: pc437 instead of cp437.
.. todo:: Support encoding aliases: pc437 instead of cp437.
"""
encoding = CodePages.get_encoding_name(encoding)
if encoding not in self.codepages:
raise ValueError((
'Encoding "{}" cannot be used for the current profile. '
'Valid encodings are: {}'
).format(encoding, ','.join(self.codepages.keys())))
raise ValueError(
(
'Encoding "{}" cannot be used for the current profile. '
"Valid encodings are: {}"
).format(encoding, ",".join(self.codepages.keys()))
)
return encoding
@staticmethod
def _get_codepage_char_list(encoding):
"""Get codepage character list
"""Get codepage character list.
Gets characters 128-255 for a given code page, as an array.
:param encoding: The name of the encoding. This must appear in the CodePage list
"""
codepage = CodePages.get_encoding(encoding)
if 'data' in codepage:
encodable_chars = list("".join(codepage['data']))
assert(len(encodable_chars) == 128)
if "data" in codepage:
encodable_chars = list("".join(codepage["data"]))
assert len(encodable_chars) == 128
return encodable_chars
elif 'python_encode' in codepage:
encodable_chars = [u" "] * 128
elif "python_encode" in codepage:
encodable_chars = [" "] * 128
for i in range(0, 128):
codepoint = i + 128
try:
encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode'])
encodable_chars[i] = bytes([codepoint]).decode(
codepage["python_encode"]
)
except UnicodeDecodeError:
# Non-encodable character, just skip it
pass
@@ -91,7 +91,7 @@ class Encoder(object):
raise LookupError("Can't find a known encoding for {}".format(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
in this encoding.
@@ -104,12 +104,14 @@ class Encoder(object):
if encoding in self.available_characters:
return self.available_characters[encoding]
codepage_char_list = self._get_codepage_char_list(encoding)
codepage_char_map = dict((utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list))
codepage_char_map = dict(
(utf8, i + 128) for (i, utf8) in enumerate(codepage_char_list)
)
self.available_characters[encoding] = codepage_char_map
return codepage_char_map
def can_encode(self, encoding, char):
"""Determine if a character is encodeable in the given code page.
"""Determine if a character is encodable in the given code page.
:param encoding: The name of the encoding.
:param char: The character to attempt to encode.
@@ -127,7 +129,7 @@ class Encoder(object):
@staticmethod
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 charmap: dictionary for mapping characters in this code page
@@ -138,31 +140,33 @@ class Encoder(object):
return charmap[char]
return ord(defaultchar)
def encode(self, text, encoding, defaultchar='?'):
""" Encode text under the given encoding
def encode(self, text, encoding, defaultchar="?"):
"""Encode text under the given encoding.
:param text: Text to encode
:param encoding: Encoding name to use (must be defined in capabilities)
:param defaultchar: Fallback for non-encodable characters
"""
codepage_char_map = self._get_codepage_char_map(encoding)
output_bytes = bytes([self._encode_char(char, codepage_char_map, defaultchar) for char in text])
output_bytes = bytes(
[self._encode_char(char, codepage_char_map, defaultchar) for char in text]
)
return output_bytes
def __encoding_sort_func(self, item):
key, index = item
return (
key in self.used_encodings,
index
)
used = key in self.used_encodings
return (not used, index)
def find_suitable_encoding(self, char):
"""The order of our search is a specific one:
"""Search in a specific order for a suitable encoding.
It is the following order:
1. code pages that we already tried before; there is a good
chance they might work again, reducing the search space,
and by re-using already used encodings we might also
reduce the number of codepage change instructiosn we have
reduce the number of codepage change instruction we have
to send. Still, any performance gains will presumably be
fairly minor.
@@ -172,9 +176,7 @@ class Encoder(object):
that the code page we pick for this character is actually
supported.
"""
sorted_encodings = sorted(
self.codepages.items(),
key=self.__encoding_sort_func)
sorted_encodings = sorted(self.codepages.items(), key=self.__encoding_sort_func)
for encoding, _ in sorted_encodings:
if self.can_encode(encoding, char):
@@ -184,7 +186,9 @@ class Encoder(object):
def split_writable_text(encoder, text, encoding):
"""Splits off as many characters from the begnning of text as
"""Split up the writable text.
Splits off as many characters from the beginning of text as
are writable with "encoding". Returns a 2-tuple (writable, rest).
"""
if not encoding:
@@ -199,7 +203,9 @@ def split_writable_text(encoder, text, encoding):
class MagicEncode(object):
"""A helper that helps us to automatically switch to the right
"""Help switching to the right code page.
A helper that helps us to automatically switch to the right
code page to encode any given Unicode character.
This will consider the printers supported codepages, according
@@ -209,9 +215,11 @@ class MagicEncode(object):
If the printer does not support a suitable code page, it can
insert an error character.
"""
def __init__(self, driver, encoding=None, disabled=False,
defaultsymbol='?', encoder=None):
"""
def __init__(
self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None
):
"""Initialize magic encode.
:param driver:
:param encoding: If you know the current encoding of the printer
@@ -223,7 +231,7 @@ class MagicEncode(object):
:param encoder:
"""
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.encoder = encoder or Encoder(driver.profile.get_code_pages())
@@ -233,7 +241,7 @@ class MagicEncode(object):
self.disabled = disabled
def force_encoding(self, encoding):
"""Sets a fixed encoding. The change is emitted right away.
"""Set a fixed encoding. The change is emitted right away.
From now one, this buffer will switch the code page anymore.
However, it will still keep track of the current code page.
@@ -245,13 +253,15 @@ class MagicEncode(object):
self.disabled = True
def write(self, text):
"""Write the text, automatically switching encodings.
"""
"""Write the text, automatically switching encodings."""
if self.disabled:
self.write_with_encoding(self.encoding, text)
return
if re.findall(r"[\u4e00-\u9fa5]", text):
self.driver._raw(text.encode("GB18030"))
return
# See how far we can go into the text with the current encoding
to_write, text = split_writable_text(self.encoder, text, self.encoding)
if to_write:
@@ -272,25 +282,30 @@ class MagicEncode(object):
self.write_with_encoding(encoding, to_write)
def _handle_character_failed(self, char):
"""Called when no codepage was found to render a character.
"""Write a default symbol.
Called when no codepage was found to render a character.
"""
# Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches.
self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text):
"""Write the text and inject necessary codepage switches."""
if text is not None and type(text) is not six.text_type:
raise Error("The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
))
raise Error(
"The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
)
)
# We always know the current code page; if the new codepage
# is different, emit a change command.
if encoding != self.encoding:
self.encoding = encoding
self.driver._raw(
CODEPAGE_CHANGE +
six.int2byte(self.encoder.get_sequence(encoding)))
CODEPAGE_CHANGE + six.int2byte(self.encoder.get_sequence(encoding))
)
if text:
self.driver._raw(self.encoder.encode(text, encoding))

View File

@@ -1,391 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" This module contains the implementations of abstract base class :py:class:`Escpos`.
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import serial
import socket
import usb.core
import usb.util
from .escpos import Escpos
from .exceptions import USBNotFoundError
class Usb(Escpos):
""" USB printer
This class describes a printer that natively speaks USB.
inheritance:
.. inheritance-diagram:: escpos.printer.Usb
:parts: 1
"""
def __init__(self, idVendor, idProduct, usb_args=None, timeout=0, in_ep=0x82, out_ep=0x01,
*args, **kwargs): # noqa: N803
"""
:param idVendor: Vendor ID
:param idProduct: Product ID
:param usb_args: Optional USB arguments (e.g. custom_match)
:param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point
:param out_ep: Output end point
"""
Escpos.__init__(self, *args, **kwargs)
self.timeout = timeout
self.in_ep = in_ep
self.out_ep = out_ep
usb_args = usb_args or {}
if idVendor:
usb_args['idVendor'] = idVendor
if idProduct:
usb_args['idProduct'] = idProduct
self.open(usb_args)
def open(self, usb_args):
""" Search device on USB tree and set it as escpos device.
:param usb_args: USB arguments
"""
self.device = usb.core.find(**usb_args)
if self.device is None:
raise USBNotFoundError("Device not found or cable not plugged in.")
self.idVendor = self.device.idVendor
self.idProduct = self.device.idProduct
# pyusb has three backends: libusb0, libusb1 and openusb but
# only libusb1 backend implements the methods is_kernel_driver_active()
# and detach_kernel_driver().
# This helps enable this library to work on Windows.
if self.device.backend.__module__.endswith("libusb1"):
check_driver = None
try:
check_driver = self.device.is_kernel_driver_active(0)
except NotImplementedError:
pass
if check_driver is None or check_driver:
try:
self.device.detach_kernel_driver(0)
except NotImplementedError:
pass
except usb.core.USBError as e:
if check_driver is not None:
print("Could not detatch kernel driver: {0}".format(str(e)))
try:
self.device.set_configuration()
self.device.reset()
except usb.core.USBError as e:
print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(self.out_ep, msg, self.timeout)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
return self.device.read(self.in_ep, 16)
def close(self):
""" Release USB interface """
if self.device:
usb.util.dispose_resources(self.device)
self.device = None
class Serial(Escpos):
""" Serial printer
This class describes a printer that is connected by serial interface.
inheritance:
.. inheritance-diagram:: escpos.printer.Serial
:parts: 1
"""
def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1,
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE,
xonxoff=False, dsrdtr=True, *args, **kwargs):
"""
:param devfile: Device file under dev filesystem
:param baudrate: Baud rate for serial transmission
:param bytesize: Serial buffer size
:param timeout: Read/Write timeout
:param parity: Parity checking
:param stopbits: Number of stop bits
:param xonxoff: Software flow control
:param dsrdtr: Hardware flow control (False to enable RTS/CTS)
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
self.parity = parity
self.stopbits = stopbits
self.xonxoff = xonxoff
self.dsrdtr = dsrdtr
self.open()
def open(self):
""" Setup serial port and set is as escpos device """
if self.device is not None and self.device.is_open:
self.close()
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
bytesize=self.bytesize, parity=self.parity,
stopbits=self.stopbits, timeout=self.timeout,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
if self.device is not None:
print("Serial printer enabled")
else:
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
def _read(self):
""" Reads a data buffer and returns it to the caller. """
return self.device.read(16)
def close(self):
""" Close Serial interface """
if self.device is not None and self.device.is_open:
self.device.flush()
self.device.close()
class Network(Escpos):
""" Network printer
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
is forwarded with ``socat``.
If you have a local printer on parallel port ``/dev/usb/lp0`` then you could start ``socat`` with:
.. code-block:: none
socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
Then you should be able to attach to port ``4242`` with this class.
Otherwise the normal usecase would be to have a printer with ethernet interface. This type of printer should
work the same with this class. For the address of the printer check its manuals.
inheritance:
.. inheritance-diagram:: escpos.printer.Network
:parts: 1
"""
def __init__(self, host, port=9100, timeout=60, *args, **kwargs):
"""
:param host: Printer's hostname or IP address
:param port: Port to write to
:param timeout: timeout in seconds for the socket-library
"""
Escpos.__init__(self, *args, **kwargs)
self.host = host
self.port = port
self.timeout = timeout
self.open()
def open(self):
""" Open TCP socket with ``socket``-library and set it as escpos device """
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
if self.device is None:
print("Could not open socket for {0}".format(self.host))
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.sendall(msg)
def _read(self):
""" Read data from the TCP socket """
return self.device.recv(16)
def close(self):
""" Close TCP connection """
if self.device is not None:
self.device.shutdown(socket.SHUT_RDWR)
self.device.close()
class File(Escpos):
""" Generic file printer
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
and produce arbitrary errors.
inheritance:
.. inheritance-diagram:: escpos.printer.File
:parts: 1
"""
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
"""
:param devfile: Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.auto_flush = auto_flush
self.open()
def open(self):
""" Open system file """
self.device = open(self.devfile, "wb")
if self.device is None:
print("Could not open the specified file {0}".format(self.devfile))
def flush(self):
""" Flush printing content """
self.device.flush()
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self):
""" Close system file """
if self.device is not None:
self.device.flush()
self.device.close()
class Dummy(Escpos):
""" Dummy printer
This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes
generating print jobs for later use, or testing output.
inheritance:
.. inheritance-diagram:: escpos.printer.Dummy
:parts: 1
"""
def __init__(self, *args, **kwargs):
"""
"""
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self._output_list.append(msg)
@property
def output(self):
""" Get the data that was sent to this printer """
return b''.join(self._output_list)
def clear(self):
""" Clear the buffer of the printer
This method can be called if you send the contents to a physical printer
and want to use the Dummy printer for new output.
"""
del self._output_list[:]
def close(self):
pass
_WIN32PRINT = False
try:
import win32print
_WIN32PRINT = True
except ImportError:
pass
if _WIN32PRINT:
class Win32Raw(Escpos):
def __init__(self, printer_name=None, *args, **kwargs):
Escpos.__init__(self, *args, **kwargs)
if printer_name is not None:
self.printer_name = printer_name
else:
self.printer_name = win32print.GetDefaultPrinter()
self.hPrinter = None
def open(self, job_name="python-escpos"):
if self.printer_name is None:
raise Exception("Printer not found")
self.hPrinter = win32print.OpenPrinter(self.printer_name)
self.current_job = win32print.StartDocPrinter(self.hPrinter, 1, (job_name, None, "RAW"))
win32print.StartPagePrinter(self.hPrinter)
def close(self):
if not self.hPrinter:
return
win32print.EndPagePrinter(self.hPrinter)
win32print.EndDocPrinter(self.hPrinter)
win32print.ClosePrinter(self.hPrinter)
self.hPrinter = None
def _raw(self, msg):
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.printer_name is None:
raise Exception("Printer not found")
if self.hPrinter is None:
raise Exception("Printer job not opened")
win32print.WritePrinter(self.hPrinter, msg)

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""printer implementations."""
from .cups import CupsPrinter
from .dummy import Dummy
from .file import File
from .lp import LP
from .network import Network
from .serial import Serial
from .usb import Usb
from .win32raw import Win32Raw
__all__ = [
"Usb",
"File",
"Network",
"Serial",
"LP",
"Dummy",
"CupsPrinter",
"Win32Raw",
]

222
src/escpos/printer/cups.py Normal file
View File

@@ -0,0 +1,222 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the CupsPrinter printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
import tempfile
from typing import Literal, Optional, Type, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the pycups dependency could be loaded (:py:class:`escpos.printer.CupsPrinter`)
_DEP_PYCUPS = False
try:
import cups
_DEP_PYCUPS = True
# Store server defaults before further configuration
DEFAULT_HOST = cups.getServer()
DEFAULT_PORT = cups.getPort()
except ImportError:
pass
# TODO: dev build mode that let's the wrapper bypass?
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_PYCUPS:
usable = True
return usable
def dependency_pycups(func):
"""Indicate dependency on pycups."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if pycups is not imported."""
if not is_usable():
raise RuntimeError(
"Printing with PyCups requires the pycups library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for pycups."
)
return func(*args, **kwargs)
return wrapper
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``
inheritance:
.. inheritance-diagram:: escpos.printer.CupsPrinter
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_pycups
def __init__(self, printer_name: str = "", *args, **kwargs):
"""Class constructor for CupsPrinter.
:param printer_name: CUPS printer name (Optional)
:param host: CUPS server host/ip (Optional)
:type host: str
:param port: CUPS server port (Optional)
:type port: int
"""
Escpos.__init__(self, *args, **kwargs)
self.host, self.port = args or (
kwargs.get("host", DEFAULT_HOST),
kwargs.get("port", DEFAULT_PORT),
)
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
self.printer_name = printer_name
self.job_name = ""
self.pending_job = False
self._device: Union[
Literal[False], Literal[None], Type[cups.Connection]
] = False
@property
def printers(self) -> dict:
"""Available CUPS printers."""
if self.device:
return self.device.getPrinters()
return {}
def open(
self, job_name: str = "python-escpos", raise_not_found: bool = True
) -> None:
"""Set up a new print job and target the printer.
A call to this method is required to send new jobs to
the CUPS connection after close.
Defaults to default CUPS printer.
Creates a new temporary file buffer.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
cups.setServer(self.host)
cups.setPort(self.port)
self.job_name = job_name
if self.tmpfile.closed:
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
try:
# Open device
self.device: Optional[Type[cups.Connection]] = cups.Connection()
if self.device:
# Name validation, set default if no given name
self.printer_name = self.printer_name or self.device.getDefault()
assert self.printer_name in self.printers, "Incorrect printer name"
except (RuntimeError, AssertionError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error(
"CupsPrinter printing %s not available", self.printer_name
)
return
logging.info("CupsPrinter printer enabled")
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 TypeError:
self.pending_job = False
raise TypeError("Bytes required. 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.device.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 not self._device:
return
if self.pending_job:
self.send()
logging.info("Closing CUPS connection to printer %s", self.printer_name)
self._device = False

View File

@@ -0,0 +1,70 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the CupsPrinter printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
from ..escpos import Escpos
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class Dummy(Escpos):
"""Dummy printer.
This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes
generating print jobs for later use, or testing output.
inheritance:
.. inheritance-diagram:: escpos.printer.Dummy
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(self, *args, **kwargs):
"""Init with empty output list."""
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self._output_list.append(msg)
@property
def output(self):
"""Get the data that was sent to this printer."""
return b"".join(self._output_list)
def clear(self):
"""Clear the buffer of the printer.
This method can be called if you send the contents to a physical printer
and want to use the Dummy printer for new output.
"""
del self._output_list[:]
def close(self):
"""Close not implemented for Dummy printer."""
pass

109
src/escpos/printer/file.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the File printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import logging
from typing import IO, Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class File(Escpos):
"""Generic file printer.
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
and produce arbitrary errors.
inheritance:
.. inheritance-diagram:: escpos.printer.File
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(self, devfile: str = "", auto_flush: bool = True, *args, **kwargs):
"""Initialize file printer with device file.
:param devfile: Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.auto_flush = auto_flush
self._device: Union[Literal[False], Literal[None], IO[bytes]] = False
def open(self, raise_not_found: bool = True) -> None:
"""Open system file.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
try:
# Open device
self.device: Optional[IO[bytes]] = open(self.devfile, "wb")
except OSError as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Could not open the specified file {self.devfile}:\n{e}"
)
else:
logging.error("File printer %s not found", self.devfile)
return
logging.info("File printer enabled")
def flush(self) -> None:
"""Flush printing content."""
if self.device:
self.device.flush()
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
if self.auto_flush:
self.flush()
def close(self) -> None:
"""Close system file."""
if not self._device:
return
logging.info("Closing File connection to printer %s", self.devfile)
if not self.auto_flush:
self.flush()
self._device.close()
self._device = False

197
src/escpos/printer/lp.py Normal file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the LP printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
import subprocess
import sys
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if not sys.platform.startswith("win"):
usable = True
return usable
def dependency_linux_lp(func):
"""Indicate dependency on non Windows."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if not on a non-Windows system."""
if not is_usable():
raise RuntimeError(
"This printer driver depends on LP which is not"
"available on Windows systems."
)
return func(*args, **kwargs)
return wrapper
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>`_.
inheritance:
.. inheritance-diagram:: escpos.printer.LP
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_linux_lp
def __init__(self, printer_name: str = "", *args, **kwargs):
"""LP class constructor.
:param printer_name: CUPS printer name (Optional)
:param auto_flush: Automatic flush after every _raw() (Optional)
:type auto_flush: bool (Defaults False)
"""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
self.auto_flush = kwargs.get("auto_flush", False)
self._flushed = False
self._device: Union[Literal[False], Literal[None], subprocess.Popen] = False
@property
def printers(self) -> dict:
"""Available CUPS printers."""
p_names = subprocess.run(
["lpstat", "-e"], # Get printer names
capture_output=True,
text=True,
)
p_devs = subprocess.run(
["lpstat", "-v"], # Get attached devices
capture_output=True,
text=True,
)
# List and trim output lines
names = [name for name in p_names.stdout.split("\n") if name]
devs = [dev for dev in p_devs.stdout.split("\n") if dev]
# return a dict of {printer name: attached device} pairs
return {name: dev.split()[-1] for name in names for dev in devs if name in dev}
def _get_system_default_printer(self) -> str:
"""Return the system's default printer name."""
p_name = subprocess.run(
["lpstat", "-d"],
capture_output=True,
text=True,
)
name = p_name.stdout.split()[-1]
if name not in self.printers:
return ""
return name
def open(
self,
job_name: str = "python-escpos",
raise_not_found: bool = True,
_close_opened: bool = True,
) -> None:
"""Invoke _lp_ in a new subprocess and wait for commands.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device and _close_opened:
self.close()
self._is_closing = False
self.job_name = job_name
try:
# Name validation, set default if no given name
self.printer_name = self.printer_name or self._get_system_default_printer()
assert self.printer_name in self.printers, "Incorrect printer name"
# Open device
self.device: Optional[subprocess.Popen] = subprocess.Popen(
["lp", "-d", self.printer_name, "-t", self.job_name, "-o", "raw"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except (AssertionError, subprocess.SubprocessError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error("LP printing %s not available", self.printer_name)
return
logging.info("LP printer enabled")
def close(self) -> None:
"""Stop the subprocess."""
if not self._device:
return
logging.info("Closing LP connection to printer %s", self.printer_name)
self._is_closing = True
if not self.auto_flush:
self.flush()
self._device.terminate()
self._device = False
def flush(self) -> None:
"""End line and wait for new commands."""
if not self.device or not self.device.stdin:
return
if self._flushed:
return
if self.device.stdin.writable():
self.device.stdin.write(b"\n")
if self.device.stdin.closed is False:
self.device.stdin.close()
self.device.wait()
self._flushed = True
if not self._is_closing:
self.open(_close_opened=False)
def _raw(self, msg):
"""Write raw command(s) to the printer.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.device.stdin.writable():
self.device.stdin.write(msg)
else:
raise subprocess.SubprocessError("Not a valid pipe for lp process")
self._flushed = False
if self.auto_flush:
self.flush()

View File

@@ -0,0 +1,135 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Network printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import logging
import socket
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
return True
class Network(Escpos):
"""Network printer.
This class is used to attach to a networked printer.
You can also use this in order to attach to a printer that
is forwarded with ``socat``.
If you have a local printer on parallel port ``/dev/usb/lp0``
then you could start ``socat`` with:
.. code-block:: none
socat -u TCP4-LISTEN:4242,reuseaddr,fork OPEN:/dev/usb/lp0
Then you should be able to attach to port ``4242`` with this class.
Otherwise the normal use case would be to have a printer with
Ethernet interface.
This type of printer should work the same with this class.
For the address of the printer check its manuals.
inheritance:
.. inheritance-diagram:: escpos.printer.Network
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(
self,
host: str = "",
port: int = 9100,
timeout: Union[int, float] = 60,
*args,
**kwargs,
):
"""Initialize network printer.
:param host: Printer's host name or IP address
:param port: Port to write to
:param timeout: timeout in seconds for the socket-library
"""
Escpos.__init__(self, *args, **kwargs)
self.host = host
self.port = port
self.timeout = timeout
self._device: Union[Literal[False], Literal[None], socket.socket] = False
def open(self, raise_not_found: bool = True) -> None:
"""Open TCP socket with ``socket``-library and set it as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
try:
# Open device
self.device: Optional[socket.socket] = socket.socket(
socket.AF_INET, socket.SOCK_STREAM
)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
except OSError as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Could not open socket for {self.host}:\n{e}"
)
else:
logging.error("Network device %s not found", self.host)
return
logging.info("Network printer enabled")
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.sendall(msg)
def _read(self):
"""Read data from the TCP socket."""
return self.device.recv(16)
def close(self) -> None:
"""Close TCP connection."""
if not self._device:
return
logging.info("Closing Network connection to printer %s", self.host)
try:
self._device.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
self._device.close()
self._device = False

View File

@@ -0,0 +1,178 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Serial printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the pyserial dependency could be loaded (:py:class:`escpos.printer.Serial`)
_DEP_PYSERIAL = False
try:
import serial
_DEP_PYSERIAL = True
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_PYSERIAL:
usable = True
return usable
def dependency_pyserial(func):
"""Indicate dependency on pyserial."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if pyserial not installed."""
if not is_usable():
raise RuntimeError(
"Printing with Serial requires the pyserial library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for pyserial."
)
return func(*args, **kwargs)
return wrapper
class Serial(Escpos):
"""Serial printer.
This class describes a printer that is connected by serial interface.
inheritance:
.. inheritance-diagram:: escpos.printer.Serial
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_pyserial
def __init__(
self,
devfile: str = "",
baudrate: int = 9600,
bytesize: int = 8,
timeout: Union[int, float] = 1,
parity: Optional[str] = None,
stopbits: Optional[int] = None,
xonxoff: bool = False,
dsrdtr: bool = True,
*args,
**kwargs,
):
"""Initialize serial printer.
:param devfile: Device file under dev filesystem
:param baudrate: Baud rate for serial transmission
:param bytesize: Serial buffer size
:param timeout: Read/Write timeout
:param parity: Parity checking
:param stopbits: Number of stop bits
:param xonxoff: Software flow control
:param dsrdtr: Hardware flow control (False to enable RTS/CTS)
"""
Escpos.__init__(self, *args, **kwargs)
self.devfile = devfile
self.baudrate = baudrate
self.bytesize = bytesize
self.timeout = timeout
if parity:
self.parity = parity
else:
self.parity = serial.PARITY_NONE
if stopbits:
self.stopbits = stopbits
else:
self.stopbits = serial.STOPBITS_ONE
self.xonxoff = xonxoff
self.dsrdtr = dsrdtr
self._device: Union[Literal[False], Literal[None], serial.Serial] = False
@dependency_pyserial
def open(self, raise_not_found: bool = True) -> None:
"""Set up serial port and set is as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
if self.device and self.device.is_open:
self.close()
try:
# Open device
self.device: Optional[serial.Serial] = serial.Serial(
port=self.devfile,
baudrate=self.baudrate,
bytesize=self.bytesize,
parity=self.parity,
stopbits=self.stopbits,
timeout=self.timeout,
xonxoff=self.xonxoff,
dsrdtr=self.dsrdtr,
)
except (ValueError, serial.SerialException) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to open serial printer on {self.devfile}:\n{e}"
)
else:
logging.error("Serial device %s not found", self.devfile)
return
logging.info("Serial printer enabled")
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(msg)
def _read(self):
"""Read the data buffer and return it to the caller."""
return self.device.read(16)
def close(self) -> None:
"""Close Serial interface."""
if not self._device:
return
logging.info("Closing Serial connection to printer %s", self.devfile)
if self._device and self._device.is_open:
self._device.flush()
self._device.close()
self._device = False

205
src/escpos/printer/usb.py Normal file
View File

@@ -0,0 +1,205 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the USB printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Dict, Literal, Optional, Type, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError, USBNotFoundError
#: keeps track if the usb dependency could be loaded (:py:class:`escpos.printer.Usb`)
_DEP_USB = False
try:
import usb.core
import usb.util
_DEP_USB = True
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_USB:
usable = True
return usable
def dependency_usb(func):
"""Indicate dependency on usb."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if usb not installed."""
if not is_usable():
raise RuntimeError(
"Printing with USB connection requires a usb library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for USB."
)
return func(*args, **kwargs)
return wrapper
class Usb(Escpos):
"""USB printer.
This class describes a printer that natively speaks USB.
inheritance:
.. inheritance-diagram:: escpos.printer.Usb
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
def __init__(
self,
idVendor: str = "",
idProduct: str = "",
usb_args: Dict[str, str] = {},
timeout: Union[int, float] = 0,
in_ep: int = 0x82,
out_ep: int = 0x01,
*args,
**kwargs,
):
"""Initialize USB printer.
:param idVendor: Vendor ID
:param idProduct: Product ID
:param usb_args: Optional USB arguments (e.g. custom_match)
:param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point
:param out_ep: Output end point
"""
Escpos.__init__(self, *args, **kwargs)
self.timeout = timeout
self.in_ep = in_ep
self.out_ep = out_ep
self.usb_args = usb_args or {}
if idVendor:
self.usb_args["idVendor"] = idVendor
if idProduct:
self.usb_args["idProduct"] = idProduct
self._device: Union[
Literal[False], Literal[None], Type[usb.core.Device]
] = False
@dependency_usb
def open(self, raise_not_found: bool = True) -> None:
"""Search device on USB tree and set it as escpos device.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
:raises: :py:exc:`~escpos.exceptions.USBNotFoundError`
"""
if self._device:
self.close()
# Open device
try:
self.device: Optional[Type[usb.core.Device]] = usb.core.find(
**self.usb_args
)
assert self.device, USBNotFoundError(
f"Device {tuple(self.usb_args.values())} not found"
+ " or cable not plugged in."
)
self._check_driver()
self._configure_usb()
except (AssertionError, usb.core.USBError) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to open USB printer on {tuple(self.usb_args.values())}:"
+ f"\n{e}"
)
else:
logging.error("USB device %s not found", tuple(self.usb_args.values()))
return
logging.info("USB printer enabled")
def _check_driver(self) -> None:
"""Check the driver.
pyusb has three backends: libusb0, libusb1 and openusb but
only libusb1 backend implements the methods is_kernel_driver_active()
and detach_kernel_driver().
This helps enable this library to work on Windows.
"""
if self.device and self.device.backend.__module__.endswith("libusb1"):
check_driver: Optional[bool] = None
try:
check_driver = self.device.is_kernel_driver_active(0)
except NotImplementedError:
pass
if check_driver is None or check_driver:
try:
self.device.detach_kernel_driver(0)
except NotImplementedError:
pass
except usb.core.USBError as e:
if check_driver is not None:
logging.error("Could not detatch kernel driver: %s", str(e))
def _configure_usb(self) -> None:
"""Configure USB."""
if not self.device:
return
try:
self.device.set_configuration()
self.device.reset()
except usb.core.USBError as e:
logging.error("Could not set configuration: %s", str(e))
def _raw(self, msg):
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.device.write(self.out_ep, msg, self.timeout)
def _read(self):
"""Read a data buffer and return it to the caller."""
return self.device.read(self.in_ep, 16)
@dependency_usb
def close(self) -> None:
"""Release USB interface."""
if not self._device:
return
logging.info(
"Closing Usb connection to printer %s", tuple(self.usb_args.values())
)
usb.util.dispose_resources(self._device)
self._device = False

View File

@@ -0,0 +1,161 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""This module contains the implementation of the Win32Raw printer driver.
:author: python-escpos developers
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
import functools
import logging
from typing import Any, Literal, Optional, Union
from ..escpos import Escpos
from ..exceptions import DeviceNotFoundError
#: keeps track if the win32print dependency could be loaded (:py:class:`escpos.printer.Win32Raw`)
_DEP_WIN32PRINT = False
try:
import pywintypes
import win32print
_DEP_WIN32PRINT = True
PyPrinterHANDLE: Any = win32print.OpenPrinter
except ImportError:
pass
def is_usable() -> bool:
"""Indicate whether this component can be used due to dependencies."""
usable = False
if _DEP_WIN32PRINT:
usable = True
return usable
def dependency_win32print(func):
"""Indicate dependency on win32print."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Throw a RuntimeError if win32print not installed."""
if not is_usable():
raise RuntimeError(
"Printing with Win32Raw requires a win32print library to"
"be installed. Please refer to the documentation on"
"what to install and install the dependencies for win32print."
)
return func(*args, **kwargs)
return wrapper
class Win32Raw(Escpos):
"""Printer binding for win32 API.
Uses the module pywin32 for printing.
inheritance:
.. inheritance-diagram:: escpos.printer.Win32Raw
:parts: 1
"""
@staticmethod
def is_usable() -> bool:
"""Indicate whether this printer class is usable.
Will return True if dependencies are available.
Will return False if not.
"""
return is_usable()
@dependency_win32print
def __init__(self, printer_name: str = "", *args, **kwargs):
"""Initialize default printer."""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
self.job_name = ""
self._device: Union[
Literal[False],
Literal[None],
"PyPrinterHANDLE",
] = False
@property
def printers(self) -> dict:
"""Available Windows printers."""
return {
printer["pPrinterName"]: printer
for printer in win32print.EnumPrinters(win32print.PRINTER_ENUM_NAME, "", 4)
}
def open(
self, job_name: str = "python-escpos", raise_not_found: bool = True
) -> None:
"""Open connection to default printer.
By default raise an exception if device is not found.
:param raise_not_found: Default True.
False to log error but do not raise exception.
:raises: :py:exc:`~escpos.exceptions.DeviceNotFoundError`
"""
if self._device:
self.close()
self.job_name = job_name
try:
# Name validation, set default if no given name
self.printer_name = self.printer_name or win32print.GetDefaultPrinter()
assert self.printer_name in self.printers, "Incorrect printer name"
# Open device
self.device: Optional["PyPrinterHANDLE"] = win32print.OpenPrinter(
self.printer_name
)
if self.device:
self.current_job = win32print.StartDocPrinter(
self.device, 1, (job_name, "", "RAW")
)
win32print.StartPagePrinter(self.device)
except (AssertionError, pywintypes.error) as e:
# Raise exception or log error and cancel
self.device = None
if raise_not_found:
raise DeviceNotFoundError(
f"Unable to start a print job for the printer {self.printer_name}:"
+ f"\n{e}"
)
else:
logging.error("Win32Raw printing %s not available", self.printer_name)
return
logging.info("Win32Raw printer enabled")
def close(self) -> None:
"""Close connection to default printer."""
if self._device is False or self._device is None: # Literal False | None
return
logging.info("Closing Win32Raw connection to printer %s", self.printer_name)
win32print.EndPagePrinter(self._device)
win32print.EndDocPrinter(self._device)
win32print.ClosePrinter(self._device)
self._device = False
def _raw(self, msg) -> None:
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.printer_name is None:
raise DeviceNotFoundError("Printer not found")
if not self.device:
raise DeviceNotFoundError("Printer job not opened")
win32print.WritePrinter(self.device, msg)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""Custom types."""
from typing import Dict, TypedDict
class ConstTxtStyleClass(TypedDict):
"""Describe type of :py:data:`escpos.constants.TXT_STYLES`."""
bold: Dict[bool, bytes]
underline: Dict[int, bytes]
size: Dict[str, bytes]
font: Dict[str, bytes]
align: Dict[str, bytes]
invert: Dict[bool, bytes]
color: Dict[str, bytes]
flip: Dict[bool, bytes]
density: Dict[int, bytes]
smooth: Dict[bool, bytes]
height: Dict[int, int]
width: Dict[int, int]

View File

@@ -1,7 +1,49 @@
import pytest
from escpos.printer import Dummy
from escpos.exceptions import DeviceNotFoundError
from escpos.printer import LP, CupsPrinter, Dummy, File, Network, Serial, Usb, Win32Raw
@pytest.fixture
def driver():
return Dummy()
@pytest.fixture
def usbprinter():
return Usb()
@pytest.fixture
def serialprinter():
return Serial()
@pytest.fixture
def networkprinter():
return Network()
@pytest.fixture
def fileprinter():
return File()
@pytest.fixture
def lpprinter():
return LP()
@pytest.fixture
def win32rawprinter():
return Win32Raw()
@pytest.fixture
def cupsprinter():
return CupsPrinter()
@pytest.fixture
def devicenotfounderror():
return DeviceNotFoundError

View File

@@ -1,30 +1,27 @@
#!/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 <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 Patrick Kanzler
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from nose.tools import raises
import escpos.escpos as escpos
from abc import ABCMeta
import pytest
import escpos.escpos as escpos
@raises(TypeError)
def test_abstract_base_class_raises():
"""test whether the abstract base class raises an exception for Escpos"""
escpos.Escpos() # This call should raise TypeError because of abstractmethod _raw()
"""test whether the abstract base class raises an exception for ESC/POS"""
with pytest.raises(TypeError):
# This call should raise TypeError because of abstractmethod _raw()
escpos.Escpos()
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 type(escpos.Escpos) is ABCMeta

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
import pytest
import six
from escpos import printer
from escpos.constants import BUZZER
def test_buzzer_function_with_default_params():
instance = printer.Dummy()
instance.buzzer()
expected = BUZZER + six.int2byte(2) + six.int2byte(4)
assert instance.output == expected
@pytest.mark.parametrize(
"times, duration",
[
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
[7, 7],
[8, 8],
[9, 9],
],
)
def test_buzzer_function(times, duration):
instance = printer.Dummy()
instance.buzzer(times, duration)
expected = BUZZER + six.int2byte(times) + six.int2byte(duration)
assert instance.output == expected
@pytest.mark.parametrize(
"times, duration, expected_message",
[
[0, 0, "times must be between 1 and 9"],
[-1, 0, "times must be between 1 and 9"],
[10, 0, "times must be between 1 and 9"],
[11, 0, "times must be between 1 and 9"],
[3, 0, "duration must be between 1 and 9"],
[3, -1, "duration must be between 1 and 9"],
[3, 10, "duration must be between 1 and 9"],
[3, 11, "duration must be between 1 and 9"],
],
)
def test_buzzer_fuction_with_outrange_values(times, duration, expected_message):
instance = printer.Dummy()
with pytest.raises(ValueError) as e:
instance.buzzer(times, duration)
assert str(e.value) == expected_message

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,12 @@
#!/usr/bin/python
"""tests for line display
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
@@ -19,17 +15,18 @@ def test_function_linedisplay_select_on():
"""test the linedisplay_select function (activate)"""
instance = printer.Dummy()
instance.linedisplay_select(select_display=True)
assert(instance.output == b'\x1B\x3D\x02')
assert instance.output == b"\x1B\x3D\x02"
def test_function_linedisplay_select_off():
"""test the linedisplay_select function (deactivate)"""
instance = printer.Dummy()
instance.linedisplay_select(select_display=False)
assert(instance.output == b'\x1B\x3D\x01')
assert instance.output == b"\x1B\x3D\x01"
def test_function_linedisplay_clear():
"""test the linedisplay_clear function"""
instance = printer.Dummy()
instance.linedisplay_clear()
assert(instance.output == b'\x1B\x40')
assert instance.output == b"\x1B\x40"

View File

@@ -1,16 +1,12 @@
#!/usr/bin/python
"""tests for panel button function
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
@@ -19,11 +15,11 @@ def test_function_panel_button_on():
"""test the panel button function (enabling) by comparing output"""
instance = printer.Dummy()
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():
"""test the panel button function (disabling) by comparing output"""
instance = printer.Dummy()
instance.panel_buttons(False)
assert(instance.output == b'\x1B\x63\x35\x01')
assert instance.output == b"\x1B\x63\x35\x01"

View File

@@ -7,12 +7,7 @@
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from nose.tools import raises
import pytest
import escpos.printer as printer
@@ -23,63 +18,72 @@ def test_defaults():
"""Test QR code with defaults"""
instance = printer.Dummy()
instance.qr("1234", native=True)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected)
expected = (
b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d"
b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_empty():
"""Test QR printing blank code"""
instance = printer.Dummy()
instance.qr("", native=True)
assert(instance.output == b'')
assert instance.output == b""
def test_ec():
"""Test QR error correction setting"""
instance = printer.Dummy()
instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d' \
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected)
expected = (
b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E3\x1d"
b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_size():
"""Test QR box size"""
instance = printer.Dummy()
instance.qr("1234", native=True, size=7)
expected = b'\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d' \
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected)
expected = (
b"\x1d(k\x04\x001A2\x00\x1d(k\x03\x001C\x07\x1d(k\x03\x001E0\x1d"
b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
def test_model():
"""Test QR model"""
instance = printer.Dummy()
instance.qr("1234", native=True, model=QR_MODEL_1)
expected = b'\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d' \
b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected)
expected = (
b"\x1d(k\x04\x001A1\x00\x1d(k\x03\x001C\x03\x1d(k\x03\x001E0\x1d"
b"(k\x07\x001P01234\x1d(k\x03\x001Q0"
)
assert instance.output == expected
@raises(ValueError)
def test_invalid_ec():
"""Test invalid QR error correction"""
instance = printer.Dummy()
instance.qr("1234", native=True, ec=-1)
with pytest.raises(ValueError):
instance.qr("1234", native=True, ec=-1)
@raises(ValueError)
def test_invalid_size():
"""Test invalid QR size"""
instance = printer.Dummy()
instance.qr("1234", native=True, size=0)
with pytest.raises(ValueError):
instance.qr("1234", native=True, size=0)
@raises(ValueError)
def test_invalid_model():
"""Test invalid QR model"""
instance = printer.Dummy()
instance.qr("1234", native=True, model="Hello")
with pytest.raises(ValueError):
instance.qr("1234", native=True, model="Hello")
@pytest.mark.skip("this test has to be debugged")
@@ -88,19 +92,21 @@ def test_image():
instance = printer.Dummy()
instance.qr("1", native=False, size=1)
print(instance.output)
expected = b'\x1bt\x00\n' \
b'\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et' \
b']ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00' \
b'i(\x7f<\xa8A \xd8]\'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00' \
b'\n\n'
assert(instance.output == expected)
expected = (
b"\x1bt\x00\n"
b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et"
b"]ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00"
b"i(\x7f<\xa8A \xd8]'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00"
b"\n\n"
)
assert instance.output == expected
@raises(ValueError)
def test_image_invalid_model():
"""Test unsupported QR model as image"""
instance = printer.Dummy()
instance.qr("1234", native=False, model=QR_MODEL_1)
with pytest.raises(ValueError):
instance.qr("1234", native=False, model=QR_MODEL_1)
@pytest.fixture

View File

@@ -2,25 +2,21 @@
# -*- coding: utf-8 -*-
"""tests for the non-native part of qr()
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest
import mock
from escpos.printer import Dummy
import pytest
from PIL import Image
from escpos.printer import Dummy
@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):
"""
Test the type of object that is passed to the image function during non-native qr-printing.

View File

@@ -1,145 +1,227 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest
import six
import escpos.printer as printer
from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT
from escpos.constants import TXT_SIZE
from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
from escpos.exceptions import SetVariableError
# Default test, please copy and paste this block to test set method calls
def test_default_values_with_default():
"""Default test, please copy and paste this block to test set method calls"""
instance = printer.Dummy()
instance.set_with_default()
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_default_values():
"""Default test"""
instance = printer.Dummy()
instance.set()
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
assert instance.output == b""
assert(instance.output == b''.join(expected_sequence))
# Size tests
def test_set_size_2h():
instance = printer.Dummy()
instance.set_with_default(double_height=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2h"], # Double height text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2h_no_default():
instance = printer.Dummy()
instance.set(double_height=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2h'], # Double height text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["2h"], # Double height text size
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_set_size_2w():
instance = printer.Dummy()
instance.set_with_default(double_width=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2w"], # Double width text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2w_no_default():
instance = printer.Dummy()
instance.set(double_width=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2w'], # Double width text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["2w"], # Double width text size
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_set_size_2x():
instance = printer.Dummy()
instance.set(double_height=True, double_width=True)
instance.set_with_default(double_height=True, double_width=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['2x'], # Double text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["2x"], # Double text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_set_size_2x_no_default():
instance = printer.Dummy()
instance.set(double_width=True, double_height=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2x"], # Quad area text size
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_custom():
instance = printer.Dummy()
instance.set(custom_size=True, width=8, height=7)
instance.set_with_default(custom_size=True, width=8, height=7)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE['width'][8] + TXT_STYLE['height'][7]),
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]),
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
@pytest.mark.parametrize("width", [1, 8])
@pytest.mark.parametrize("height", [1, 8])
def test_set_size_custom_no_default(width, height):
instance = printer.Dummy()
instance.set(custom_size=True, width=width, height=height)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE["width"][width] + TXT_STYLE["height"][height]),
)
assert instance.output == b"".join(expected_sequence)
@pytest.mark.parametrize("width", [None, 0, 9, 10, 4444])
@pytest.mark.parametrize("height", [None, 0, 9, 10, 4444])
def test_set_size_custom_invalid_input(width, height):
instance = printer.Dummy()
with pytest.raises(SetVariableError):
instance.set(custom_size=True, width=width, height=height)
# Flip
def test_set_flip():
instance = printer.Dummy()
instance.set_with_default(flip=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][True], # Flip ON
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_flip_no_default():
instance = printer.Dummy()
instance.set(flip=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][True], # Flip ON
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
)
expected_sequence = (TXT_STYLE["flip"][True],) # Flip ON
assert instance.output == b"".join(expected_sequence)
assert (instance.output == b''.join(expected_sequence))
# Smooth
def test_smooth():
instance = printer.Dummy()
instance.set(smooth=True)
instance.set_with_default(smooth=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][True], # Smooth ON
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][True], # Smooth ON
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
# Type
@@ -147,134 +229,143 @@ def test_smooth():
def test_set_bold():
instance = printer.Dummy()
instance.set(bold=True)
instance.set_with_default(bold=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][True], # Bold ON
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][True], # Bold ON
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_set_underline():
instance = printer.Dummy()
instance.set(underline=1)
instance.set_with_default(underline=1)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][1], # Underline ON, type 1
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][1], # Underline ON, type 1
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_set_underline2():
instance = printer.Dummy()
instance.set(underline=2)
instance.set_with_default(underline=2)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][2], # Underline ON, type 2
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][2], # Underline ON, type 2
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert (instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
# Align
def test_align_center():
instance = printer.Dummy()
instance.set(align='center')
instance.set_with_default(align="center")
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['center'], # Align center
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["center"], # Align center
TXT_STYLE["invert"][False], # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
def test_align_right():
instance = printer.Dummy()
instance.set(align='right')
instance.set_with_default(align="right")
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['right'], # Align right
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["right"], # Align right
TXT_STYLE["invert"][False], # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
# Densities
def test_densities():
def test_densities():
for density in range(8):
instance = printer.Dummy()
instance.set(density=density)
instance.set_with_default(density=density)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['density'][density], # Custom density from 0 to 8
TXT_STYLE['invert'][False] # Inverted OFF
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["density"][density], # Custom density from 0 to 8
TXT_STYLE["invert"][False], # Inverted OFF
)
assert(instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)
# Invert
def test_invert():
instance = printer.Dummy()
instance.set(invert=True)
instance.set_with_default(invert=True)
expected_sequence = (
TXT_NORMAL, TXT_STYLE['size']['normal'], # Normal text size
TXT_STYLE['flip'][False], # Flip OFF
TXT_STYLE['smooth'][False], # Smooth OFF
TXT_STYLE['bold'][False], # Bold OFF
TXT_STYLE['underline'][0], # Underline OFF
SET_FONT(b'\x00'), # Default font
TXT_STYLE['align']['left'], # Align left
TXT_STYLE['invert'][True] # Inverted ON
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][True], # Inverted ON
)
assert(instance.output == b''.join(expected_sequence))
assert instance.output == b"".join(expected_sequence)

View File

@@ -1,16 +1,26 @@
#!/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 barcode.errors
import pytest
import escpos.printer as printer
def test_soft_barcode():
"""just execute soft_barcode
"""
instance = printer.Dummy()
instance.soft_barcode("ean8", "1234")
@pytest.fixture
def instance():
return printer.Dummy()
def test_soft_barcode_ean8_invalid(instance):
"""test with an invalid barcode"""
with pytest.raises(barcode.errors.BarcodeError):
instance.barcode("1234", "ean8", force_software=True)
def test_soft_barcode_ean8(instance):
"""test with a valid ean8 barcode"""
instance.barcode("1234567", "ean8", force_software=True)
def test_soft_barcode_ean8_nocenter(instance):
instance.barcode("1234567", "ean8", align_ct=False, force_software=True)

View File

@@ -1,32 +1,28 @@
#!/usr/bin/python
"""tests for the text printing function
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest
import mock
from hypothesis import given, assume
import hypothesis.strategies as st
import mock
import pytest
from hypothesis import assume, given
from escpos.printer import Dummy
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())
def test_text(text):
"""Test that text() calls the MagicEncode object.
"""
"""Test that text() calls the MagicEncode object."""
instance = get_printer()
instance.magic.write = mock.Mock()
instance.text(text)
@@ -36,30 +32,32 @@ def test_text(text):
def test_block_text():
printer = get_printer()
printer.block_text(
"All the presidents men were eating falafel for breakfast.", font='a')
assert printer.output == \
b'All the presidents men were eating falafel\nfor breakfast.'
"All the presidents men were eating falafel for breakfast.", font="a"
)
assert (
printer.output == b"All the presidents men were eating falafel\nfor breakfast."
)
def test_textln():
printer = get_printer()
printer.textln('hello, world')
assert printer.output == b'hello, world\n'
printer.textln("hello, world")
assert printer.output == b"hello, world\n"
def test_textln_empty():
printer = get_printer()
printer.textln()
assert printer.output == b'\n'
assert printer.output == b"\n"
def test_ln():
printer = get_printer()
printer.ln()
assert printer.output == b'\n'
assert printer.output == b"\n"
def test_multiple_ln():
printer = get_printer()
printer.ln(3)
assert printer.output == b'\n\n\n'
assert printer.output == b"\n\n\n"

View File

@@ -1,26 +1,27 @@
from nose.tools import assert_raises
import pytest
from escpos.printer import Dummy
def test_line_spacing_code_gen():
printer = Dummy()
printer.line_spacing(10)
assert printer.output == b'\x1b3\n'
assert printer.output == b"\x1b3\n"
def test_line_spacing_rest():
printer = Dummy()
printer.line_spacing()
assert printer.output == b'\x1b2'
assert printer.output == b"\x1b2"
def test_line_spacing_error_handling():
printer = Dummy()
with assert_raises(ValueError):
with pytest.raises(ValueError):
printer.line_spacing(99, divisor=44)
with assert_raises(ValueError):
with pytest.raises(ValueError):
printer.line_spacing(divisor=80, spacing=86)
with assert_raises(ValueError):
with pytest.raises(ValueError):
printer.line_spacing(divisor=360, spacing=256)
with assert_raises(ValueError):
with pytest.raises(ValueError):
printer.line_spacing(divisor=180, spacing=256)

View File

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

View File

@@ -1,16 +1,12 @@
#!/usr/bin/python
"""very basic test cases that load the classes
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
@@ -18,4 +14,4 @@ import escpos.printer as printer
def test_instantiation():
"""test the instantiation of a escpos-printer class and basic printing"""
instance = printer.Dummy()
instance.text('This is a test\n')
instance.text("This is a test\n")

View File

@@ -0,0 +1,29 @@
#!/usr/bin/python
"""basic test case that simulates an empty capability file
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
import os
import tempfile
import pytest
def test_instantiation():
"""test the instantiation of a escpos-printer class"""
# inject an environment variable that points to an empty capabilities file
os.environ["ESCPOS_CAPABILITIES_FILE"] = tempfile.NamedTemporaryFile().name
import escpos.printer as printer
from escpos.exceptions import BarcodeTypeError
# remove again the variable (so that no other tests are affected)
os.environ.pop("ESCPOS_CAPABILITIES_FILE")
instance = printer.Dummy()
with pytest.raises(BarcodeTypeError):
instance.barcode("bc", "code")

View File

@@ -2,24 +2,20 @@
# -*- coding: utf-8 -*-
"""tests for the magic encode module
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import pytest
from nose.tools import raises, assert_raises
from hypothesis import given, example
import hypothesis.strategies as st
from escpos.magicencode import MagicEncode, Encoder
from escpos.katakana import encode_katakana
import pytest
from hypothesis import example, given
from escpos.exceptions import CharCodeError, Error
from escpos.katakana import encode_katakana
from escpos.magicencode import Encoder, MagicEncode
class TestEncoder:
@@ -28,17 +24,24 @@ class TestEncoder:
"""
def test_can_encode(self):
assert not Encoder({'CP437': 1}).can_encode('CP437', u'')
assert Encoder({'CP437': 1}).can_encode('CP437', u'á')
assert not Encoder({'foobar': 1}).can_encode('foobar', 'a')
assert not Encoder({"CP437": 1}).can_encode("CP437", "")
assert Encoder({"CP437": 1}).can_encode("CP437", "á")
assert not Encoder({"foobar": 1}).can_encode("foobar", "a")
def test_find_suitable_encoding(self):
assert not Encoder({'CP437': 1}).find_suitable_encoding(u'')
assert Encoder({'CP858': 1}).find_suitable_encoding(u'') == 'CP858'
assert not Encoder({"CP437": 1}).find_suitable_encoding("")
assert Encoder({"CP858": 1}).find_suitable_encoding("") == "CP858"
def test_find_suitable_encoding_unnecessary_codepage_swap(self):
enc = Encoder({"CP857": 1, "CP437": 2, "CP1252": 3, "CP852": 4, "CP858": 5})
# desired behavior would be that the encoder always stays in the lower
# available codepages if possible
for character in ("Á", "É", "Í", "Ó", "Ú"):
assert enc.find_suitable_encoding(character) == "CP857"
@raises(ValueError)
def test_get_encoding(self):
Encoder({}).get_encoding_name('latin1')
with pytest.raises(ValueError):
Encoder({}).get_encoding_name("latin1")
class TestMagicEncode:
@@ -61,50 +64,50 @@ class TestMagicEncode:
MagicEncode(driver, disabled=True)
class TestWriteWithEncoding:
def test_init_from_none(self, driver):
encode = MagicEncode(driver, encoding=None)
encode.write_with_encoding('CP858', '€ ist teuro.')
assert driver.output == b'\x1bt\x13\xd5 ist teuro.'
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_change_from_another(self, driver):
encode = MagicEncode(driver, encoding='CP437')
encode.write_with_encoding('CP858', '€ ist teuro.')
assert driver.output == b'\x1bt\x13\xd5 ist teuro.'
encode = MagicEncode(driver, encoding="CP437")
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_no_change(self, driver):
encode = MagicEncode(driver, encoding='CP858')
encode.write_with_encoding('CP858', '€ ist teuro.')
assert driver.output == b'\xd5 ist teuro.'
encode = MagicEncode(driver, encoding="CP858")
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\xd5 ist teuro."
class TestWrite:
def test_write(self, driver):
encode = MagicEncode(driver)
encode.write('€ ist teuro.')
assert driver.output == b'\x1bt\x0f\xa4 ist teuro.'
encode.write("€ ist teuro.")
assert driver.output == b"\x1bt\x0f\xa4 ist teuro."
def test_write_disabled(self, driver):
encode = MagicEncode(driver, encoding='CP437', disabled=True)
encode.write('€ ist teuro.')
assert driver.output == b'? ist teuro.'
encode = MagicEncode(driver, encoding="CP437", disabled=True)
encode.write("€ ist teuro.")
assert driver.output == b"? ist teuro."
def test_write_no_codepage(self, driver):
encode = MagicEncode(
driver, defaultsymbol="_", encoder=Encoder({'CP437': 1}),
encoding='CP437')
encode.write(u'€ ist teuro.')
assert driver.output == b'_ ist teuro.'
driver,
defaultsymbol="_",
encoder=Encoder({"CP437": 1}),
encoding="CP437",
)
encode.write("€ ist teuro.")
assert driver.output == b"_ ist teuro."
class TestForceEncoding:
def test(self, driver):
encode = MagicEncode(driver)
encode.force_encoding('CP437')
assert driver.output == b'\x1bt\x00'
encode.force_encoding("CP437")
assert driver.output == b"\x1bt\x00"
encode.write('€ ist teuro.')
assert driver.output == b'\x1bt\x00? ist teuro.'
encode.write("€ ist teuro.")
assert driver.output == b"\x1bt\x00? ist teuro."
try:
@@ -123,5 +126,5 @@ class TestKatakana:
encode_katakana(text)
def test_result(self):
assert encode_katakana('カタカナ') == b'\xb6\xc0\xb6\xc5'
assert encode_katakana("あいうえお") == b'\xb1\xb2\xb3\xb4\xb5'
assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5"

Some files were not shown because too many files have changed in this diff Show More