1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-10-23 09:30:00 +00:00

5 Commits

Author SHA1 Message Date
Christoph Heuel
83185bf2f3 Change doc 2017-04-22 01:09:24 +02:00
Christoph Heuel
57ce8389d7 Merge branch 'master' into debian/jessie 2017-04-01 01:03:06 +02:00
Christoph Heuel
b2ce102ca5 Release 1.0.8-3 2015-12-13 14:31:07 +01:00
Christoph Heuel
d50c407edb Merge branch 'master' into debian/jessie 2015-12-13 14:25:19 +01:00
Christoph Heuel
2a2ba9a0e2 Debian packaging
* Add Debian packaging file
* Use pybuild system to generate Python packages
2015-12-12 21:50:56 +01:00
94 changed files with 1853 additions and 3700 deletions

View File

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

View File

@@ -1,23 +0,0 @@
# 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"

View File

@@ -1,13 +0,0 @@
name: Lint (Black code style)
on: [push, pull_request]
jobs:
black-code-style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable
with:
version: "23.3.0"

View File

@@ -1,66 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 1 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,32 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: Documentation build
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "docs"
docs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Install packages
run:
sudo apt-get update -y &&
sudo apt-get install -y git python3-sphinx graphviz libenchant-2-2 &&
sudo apt-get install -y gcc libcups2-dev python3-dev python3-setuptools &&
sudo pip install tox pycups
- name: Test doc build
run: tox -e docs

View File

@@ -1,43 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest tox tox-gh-actions
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with tox
run: |
tox
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json

12
.gitignore vendored
View File

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

1
.gitmodules vendored
View File

@@ -1,4 +1,3 @@
[submodule "capabilities-data"]
path = capabilities-data
url = https://github.com/receipt-print-hq/escpos-printer-db.git
branch = master

View File

@@ -1,19 +0,0 @@
<dev@pkanzler.de> <patrick.kanzler@fablab.fau.de>
<manpaz@gmail.com> <manpaz@bashlinux.com>
Manuel F Martinez <manpaz@gmail.com> manpaz <manpaz@bashlinux.com>
<emailofdavis@gmail.com> <davis.goglin@oregonicecream.com>
Davis Goglin <emailofdavis@gmail.com> davisgoglin <emailofdavis@gmail.com>
Michael Billington <michael.billington@gmail.com> Michael <michael.billington@gmail.com>
Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.com>
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
Juanmi Taboada <juanmi@juanmitaboada.com> Juanmi Taboada <juanmi@juanmitaboada.com>
csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com>
reck31 <rakesh.gunduka@gmail.com>
Alex Debiasio <alex.debiasio@thinkin.io> <alex.debiasio@studenti.unitn.it>
白月秋见心 <ourweijiang@gmail.com>
Maximilian Wagenbach <maximilian.wagenbach@native-instruments.de>
<belono@users.noreply.github.com> <tiotil.lindeman@gmail.com>
belono <belono@users.noreply.github.com> Benito López <belono@users.noreply.github.com>

59
.travis.yml Normal file
View File

@@ -0,0 +1,59 @@
language: python
sudo: false
cache: pip
addons:
apt:
packages:
- graphviz
matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.3
env: TOXENV=py33
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- python: 3.6-dev
env: TOXENV=py36
- python: nightly
env: TOXENV=py37
- python: pypy
env: TOXENV=pypy
- python: pypy3
env: TOXENV=pypy3
- python: 2.7
env: TOXENV=docs
- python: 2.7
env: TOXENV=flake8
- python: 3.6
env: TOXENV=flake8
allow_failures:
- python: 3.6-dev
- python: nightly
- python: pypy3
before_install:
- pip install tox codecov 'sphinx>=1.5.1'
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.5"

14
.vscode/settings.json vendored
View File

@@ -1,14 +0,0 @@
{
"esbonio.sphinx.confDir": "${workspaceFolder}/doc",
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/*/**": true,
"**/.eggs/**": true,
"**/.hypothesis/**": true,
"**/.tox/**": true,
},
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"python.formatting.provider": "black",
}

16
.vscode/tasks.json vendored
View File

@@ -1,16 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "test with tox",
"type": "shell",
"command": "tox",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}

48
AUTHORS
View File

@@ -1,48 +0,0 @@
Ahmed Tahri
akeonly
Alejandro Hernández
Alexander Bougakov
Alex Debiasio
Asuki Kono
belono
B. Howell
Brian
Christoph Heuel
Cody (Quantified Code Bot)
csoft2k
Curtis // mashedkeyboard
Davis Goglin
Dean Rispin
dependabot[bot]
Dmytro Katyukha
Gerard Marull-Paretas
Hark
Joel Lehtonen
Justin Vieira
kennedy
Kristi
ldos
Lucy Linder
Manuel F Martinez
Mathieu Poussin
Maximilian Wagenbach
Michael Billington
Michael Elsdörfer
mrwunderbar666
Nathan Bookham
Omer Akram
Patrick Kanzler
primax79
Qian Linfeng
Ramon Poca
reck31
Renato Lorenzi
Romain Porte
Sam Cheng
Sergio Pulgarin
Stephan Sokolow
Thijs Triemstra
Thomas van den Berg
Yaisel Hurtado
ysuolmai
白月秋见心

View File

@@ -1,187 +1,6 @@
*********
Changelog
*********
2023-05-11 - Version 3.0a9 - "Pride Comes Before A Fall"
--------------------------------------------------------
This release is the 10th alpha release of the new version 3.0.
After three years hiatus, a new release is in work in order to
finally get a version 3.0 out.
changes
^^^^^^^
- Include support for CUPS based printer interfaces
- Move the build toolchain to GitHub
contributors
^^^^^^^^^^^^
- belono
- brendanhowell
- AlexandroJaez
- NullYing
- kedare
- Foaly
- patkan
- and others
2020-05-12 - Version 3.0a8 - "Only Slightly Bent"
-------------------------------------------------
This release is the ninth alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0 is
released.
This release drops support for Python 2, requiring at least
version 3.5 of Python.
changes
^^^^^^^
- Drop support for Python 2 and mark in setuptools as only supporting 3.5 and upwards
- remove landscape.io badge
- replace viivakoodi with python-barcode which is maintained
- add configuration for Visual Studio Code
- use pkg_resources for the retrieval of the capabilities.json
contributors
^^^^^^^^^^^^
- Romain Porte
- Patrick Kanzler
2020-05-09 - Version 3.0a7 - "No Fixed Abode"
---------------------------------------------
This release is the eight alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0
is released.
This release also marks the point at which the project transitioned
to having only a master-branch (and not an additional development branch).
changes
^^^^^^^
- add Exception for NotImplementedError in detach_kernel_driver
- update installation information
- update and improve documentation
- add error handling to image centering flag
- update and fix tox and CI environment, preparing drop of support for Python 2
contributors
^^^^^^^^^^^^
- Alexander Bougakov
- Brian
- Yaisel Hurtado
- Maximilan Wagenbach
- Patrick Kanzler
2019-06-19 - Version 3.0a6 - "Mistake not..."
---------------------------------------------
This release is the seventh alpha release of the new version 3.0.
Please be aware that the API is subject to change until v3.0 is
released.
changes
^^^^^^^
- fix inclusion of the capabilities-file
- execute CI jobs also on Windows and macOS-targets
- improve documentation
contributors
^^^^^^^^^^^^
- Patrick Kanzler
2019-06-16 - Version 3.0a5 - "Lightly Seared On The Reality Grill"
------------------------------------------------------------------
This release is the sixth alpha release of the new version 3.0. Please
be aware that the API is subject to change until v3.0 is released.
changes
^^^^^^^
- allow arbitrary USB arguments in USB-class
- add Win32Raw-Printer on Windows-platforms
- add and improve Windows support of USB-class
- use pyyaml safe_load()
- improve doc
- implement _read method of Network printer class
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Gerard Marull-Paretas
- Ramon Poca
- akeonly
- Omer Akram
- Justin Vieira
2018-05-15 - Version 3.0a4 - "Kakistocrat"
------------------------------------------
This release is the fifth alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- raise exception when TypeError occurs in cashdraw (#268)
- Feature/clear content in dummy printer (#271)
- fix is_online() (#282)
- improve documentation
- Modified submodule to always pull from master branch (#283)
- parameter for implementation of nonnative qrcode (#289)
- improve platform independence (#296)
contributors
^^^^^^^^^^^^
- Christoph Heuel
- Patrick Kanzler
- kennedy
- primax79
- reck31
- Thijs Triemstra
2017-10-08 - Version 3.0a3 - "Just Testing"
-------------------------------------------
This release is the fourth alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- minor changes in documentation, tests and examples
- pickle capabilities for faster startup
- first implementation of centering images and QR
- check barcodes based on regex
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Lucy Linder
- Romain Porte
- Sergio Pulgarin
2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To"
-----------------------------------------------------------------------
This release is the third alpha release of the new version 3.0. Please
be aware that the API will still change until v3.0 is released.
changes
^^^^^^^
- refactor of the set-method
- preliminary support of POS "line display" printing
- improvement of tests
- added ImageWidthError
- list authors in repository
- add support for software-based barcode-rendering
- fix SerialException when trying to close device on __del__
- added the DLE EOT querying command for USB and Serial
- ensure QR codes have a large enough border
- make feed for cut optional
- fix the behavior of horizontal tabs
- added test script for hard an soft barcodes
- implemented paper sensor querying command
- added weather forecast example script
- added a method for simpler newlines
contributors
^^^^^^^^^^^^
- csoft2k
- Patrick Kanzler
- mrwunderbar666
- Romain Porte
- Ahmed Tahri
2017-03-29 - Version 3.0a1 - "Headcrash"
----------------------------------------

View File

@@ -9,37 +9,47 @@ In order to reduce the amount of work for everyone please try to adhere to good
The pull requests and issues will be prefilled with templates. Please fill in your information where applicable.
This project uses `semantic versioning <https://semver.org/>`_ and tries to adhere to the proposed rules as
This project uses `semantic versioning <http://semver.org/>`_ and tries to adhere to the proposed rules as
well as possible.
Author-list
-----------
This project keeps a list of authors. This can be auto-generated by calling `./doc/generate-authors.sh`.
When contributing the first time, please include a commit with the output of this script in place.
Otherwise the integration-check will fail.
When you change your username or mail-address, please also update the `.mailmap` and the authors-list.
You can find a good documentation on the mapping-feature in the `documentation of git-shortlog <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
Style-Guide
-----------
When writing code please try to stick to these rules.
Python 2 and 3
^^^^^^^^^^^^^^
We have rewritten the code in order to maintain compatibility with both Python 2 and Python 3.
In order to ensure that we do not miss any accidental degradation, please add these imports to the top
of every file of code:
.. code-block:: Python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
Furthermore please be aware of the differences between Python 2 and 3. For
example `this guide <https://docs.python.org/3/howto/pyporting.html>`_ is helpful.
Special care has to be taken when dealing with strings and byte-strings. Please note
that the :py:meth:`~escpos.escpos.Escpos._raw`-method only accepts byte-strings.
Often you can achieve compatibility quite easily with a tool from the `six`-package.
PEP8
^^^^
The entire codebase adheres to the rules of PEP8.
These rules are enforced by running `flake8` in the integration-checks.
Please adhere to these rules as your contribution can only be merged if the check succeeds.
You can use flake8 or similar tools locally in order to check your code.
Apart from that the travis-log and the check by Landscape will provide you with hints.
This is not yet consequently done in every piece of code, but please try to ensure
that your code honors PEP8.
The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints.
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 code that has been released to PyPi. A release is marked with a tag
corresponding to the version. Issues are closed when they have been resolved in the development-branch.
When you have a change to make, begin by creating a new branch from the HEAD of `python-escpos/development`.
Name your branch to indicate what you are trying to achieve. Good branch names might
be `improve/text-handling`, `feature/enable-color-printing`.
Please try to group your commits into logical units. If you need to tidy up your branch, you can make use of a
git feature called an 'interactive rebase' before making a pull request. A small, self-contained change-set is
@@ -62,7 +72,7 @@ You can copy the structure from other testcases. Please remember to adapt the do
Further reading
^^^^^^^^^^^^^^^
For further best practices and hints on contributing please see the
`contribution-guide <https://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
`contribution-guide <http://www.contribution-guide.org/>`_. Should there be any contradictions between this guide
and the linked one, please stick to this text.
Aside from that feel free to create an issue or write an email if anything is unclear.

23
INSTALL
View File

@@ -1,10 +1,23 @@
python-escpos
=============
This library is available over pypi. So for most of the use-cases it should be sufficient to run
Ensure the library is installed on ${lib_arch}/${python_ver}/site-packages/escpos
```
pip install python-escpos --user # add --pre if you want to install pre-releases
```
On CLi you must run:
# python setup.py build
# sudo python setup.py install
For more information please read the documentation at https://python-escpos.readthedocs.io/en/latest/user/installation.html
On Linux, ensure you belongs to the proper group so you can have access to the printer.
This can be done, by adding yourself to 'dialout' group, this might require to re-login
so the changes make effect.
Then, add the following rule to /etc/udev/rules.d/99-escpos.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="04b8", ATTRS{idProduct}=="0202", MODE="0664", GROUP="dialout"
and restar udev rules.
# sudo service udev restart
Enjoy !!!
And please, don't forget to ALWAYS add Epson.cut() at the end of your printing :)
Manuel F Martinez <manpaz@bashlinux.com>

View File

@@ -1,10 +1,10 @@
include *.rst
include *.txt
include COPYING
include LICENSE
include INSTALL
include tox.ini
include capabilities-data/dist/capabilities.json
include src/escpos/capabilities.json
recursive-include doc *.bat
recursive-include doc *.ico
recursive-include doc *.py

View File

@@ -6,12 +6,20 @@ python-escpos - Python library to manipulate ESC/POS Printers
:target: https://travis-ci.org/python-escpos/python-escpos
:alt: Continous Integration
.. image:: https://www.quantifiedcode.com/api/v1/project/95748b89a3974700800b85e4ed3d32c4/badge.svg
:target: https://www.quantifiedcode.com/app/project/95748b89a3974700800b85e4ed3d32c4
:alt: Code issues
.. image:: https://landscape.io/github/python-escpos/python-escpos/master/landscape.svg?style=flat
:target: https://landscape.io/github/python-escpos/python-escpos/master
:alt: Code Health
.. image:: https://codecov.io/github/python-escpos/python-escpos/coverage.svg?branch=master
:target: https://codecov.io/github/python-escpos/python-escpos?branch=master
:alt: Code Coverage
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=latest
:target: https://python-escpos.readthedocs.io/en/latest/?badge=latest
.. image:: https://readthedocs.org/projects/python-escpos/badge/?version=stable
:target: http://python-escpos.readthedocs.io/en/latest/?badge=stable
:alt: Documentation Status
@@ -39,11 +47,10 @@ Dependencies
This library makes use of:
* `pyusb <https://github.com/walac/pyusb>`_ for USB-printers
* `Pillow <https://github.com/python-pillow/Pillow>`_ for image printing
* `qrcode <https://github.com/lincolnloop/python-qrcode>`_ for the generation of QR-codes
* `pyserial <https://github.com/pyserial/pyserial>`_ for serial printers
* `python-barcode <https://github.com/WhyNotHugo/python-barcode>`_ for the generation of barcodes
* pyusb for USB-printers
* Pillow for image printing
* qrcode for the generation of QR-codes
* pyserial for serial printers
Documentation and Usage
-----------------------
@@ -61,48 +68,9 @@ The basic usage is:
p.barcode('1324354657687', 'EAN13', 64, 2, '', '')
p.cut()
Another example based on the Network printer class:
.. code:: python
from escpos.printer import Network
kitchen = Network("192.168.1.100") #Printer IP Address
kitchen.text("Hello World\n")
kitchen.barcode('1324354657687', 'EAN13', 64, 2, '', '')
kitchen.cut()
Another example based on the Serial printer class:
.. code:: python
from escpos.printer import Serial
""" 9600 Baud, 8N1, Flow Control Enabled """
p = Serial(devfile='/dev/tty.usbserial',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=1.00,
dsrdtr=True)
p.text("Hello World\n")
p.qr("You can readme from your smartphone")
p.cut()
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.
Contributing
------------
This project is open for any contribution! Please see `CONTRIBUTING.rst <https://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ for more information.
Disclaimer
----------
None of the vendors cited in this project agree or endorse any of the patterns or implementations.
Its names are used only to maintain context.
This project is open for any contribution! Please see CONTRIBUTING.rst for more information.

View File

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

94
debian/changelog vendored Normal file
View File

@@ -0,0 +1,94 @@
python-escpos (1.0.8-3) unstable; urgency=medium
[ Christoph Heuel ]
* Fix text wrapping error after image
[ Patrick Kanzler ]
* moved .hgignore to .gitignore
* REFACTOR chained boolean expression in escpos
* REFACTOR use new-style class for Escpos
* REFACTOR style and PEP8, fixes #66
* REFACTOR do not shadow built-ins
* ADD requirements.txt and requirements to setup.py
* DOC, IMPROVE improve docstrings and add abstract method _raw to Escpos
* FIX constant definition for PC1252
-- Christoph Heuel <mail@christoph-heuel.net> Sun, 13 Dec 2015 14:27:27 +0100
python-escpos (1.0.8-2) unstable; urgency=low
* Imported source
* First debianization
-- Christoph Heuel <mail@christoph-heuel.net> Sat, 12 Dec 2015 19:13:33 +0100
python-escpos (1.0.8-1) unstable; urgency=low
[ Manuel F Martinez ]
* Added donation message
[ Joel Lehtonen ]
* Support for images vertically longer than 256 pixels
[ Christoph Heuel ]
* Fix mixed tabs/space error
[ Hark ]
* Prevent crash when using libusb0 printers
[ Manuel F Martinez ]
* Updated README and documentation
[ Christoph Heuel ]
* Add flush function
* Debian packaging
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100
python-escpos (1.0.7-1) unstable; urgency=low
[ Kristi ]
* Raising the right error when wrong charcode is used
[ Christoph Heuel ]
* After running 2to3 tool
* Fix for string operation
* Integer is needed, not float
* Add text wrapping
[ Manuel F Martinez ]
* Updated URL for the documentation
* Updated documentation URL to local wiki
* Updated setup URLs
[ Christoph Heuel ]
* Introduce new direct_image
[ Manuel F Martinez ]
* Fixed License version mismatch
[ Christoph Heuel ]
* Use unhexlify
* Hexlify text
[ Manuel F Martinez ]
* Updated accordingly to the wiki
[ ldos ]
* Extended params for serial printers
[ Manuel F Martinez ]
* Fixed issues with transparent images
* Updated project version
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100
python-escpos (1.0.4-1) unstable; urgency=medium
[ Manuel F Martinez ]
* Added density support
* Added quad support
* fixed code tabulators
* Updated version
-- Manuel F Martinez <manpaz@gmail.com> Sat, 12 Dec 2015 20:59:53 +0100

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

55
debian/control vendored Normal file
View File

@@ -0,0 +1,55 @@
Source: python-escpos
Section: unknown
Priority: optional
Maintainer: Christoph Heuel <mail@christoph-heuel.net>
Build-Depends: debhelper (>= 9), dh-python
Standards-Version: 3.9.5
Homepage: https://github.com/braveheuel/python-escpos
#Vcs-Git: git@github.com:braveheuel/python-escpos.git
#Vcs-Browser: https://github.com/braveheuel/python-escpos
Package: python-escpos
Architecture: all
Depends: ${misc:Depends}
Description: Python library to manipulate ESC/POS Printers (Python 2)
Python ESC/POS is a library which lets the user have access to all
those printers handled by ESC/POS commands, as defined by Epson,
from a Python application.
.
The standard usage is send raw text to the printer, but in also
helps the user to enhance the experience with those printers by
facilitating the bar code printing in many different standards,
as well as manipulating images so they can be printed as brand
logo or any other usage images migh have.
.
Text can be aligned/justified and fonts can be changed by size,
type and weight.
.
Also, this module handles some hardware functionalities like, cut
paper, carrier return, printer reset and others concerned to the
carriage alignment.
.
This package covers Python 2 code.
Package: python3-escpos
Architecture: all
Depends: ${misc:Depends}
Description: Python library to manipulate ESC/POS Printers (Python 3)
Python ESC/POS is a library which lets the user have access to all
those printers handled by ESC/POS commands, as defined by Epson,
from a Python application.
.
The standard usage is send raw text to the printer, but in also
helps the user to enhance the experience with those printers by
facilitating the bar code printing in many different standards,
as well as manipulating images so they can be printed as brand
logo or any other usage images migh have.
.
Text can be aligned/justified and fonts can be changed by size,
type and weight.
.
Also, this module handles some hardware functionalities like, cut
paper, carrier return, printer reset and others concerned to the
carriage alignment.
.
This package covers Python 3 code.

26
debian/copyright vendored Normal file
View File

@@ -0,0 +1,26 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: python-escpos
Source: https://github.com/manpaz/python-escpos
Files: *
Copyright: 2015 Manuel F Martinez <manpaz@bashlinux.com>
License: GPL v3
Files: debian/*
Copyright: 2015 Christoph Heuel <mail@christoph-heuel.net>
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

1
debian/docs vendored Normal file
View File

@@ -0,0 +1 @@
README.rst

6
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,6 @@
[DEFAULT]
upstream-branch=master
upstream-tree = tag
debian-branch = debian/jessie
upstream-tag = v%(version)s
debian-tag = v%(version)s

28
debian/rules vendored Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
#DH_VERBOSE = 1
# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk
# see FEATURE AREAS in dpkg-buildflags(1)
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# see ENVIRONMENT in dpkg-buildflags(1)
# package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
# main packaging script based on dh7 syntax
%:
dh $@ --with python2,python3 --buildsystem=pybuild
# debmake generated override targets
# This is example for Cmake (See http://bugs.debian.org/641051 )
#override_dh_auto_configure:
# dh_auto_configure -- \
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (quilt)

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 https://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 http://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 spelling
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@@ -45,7 +45,6 @@ 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)/*
@@ -176,8 +175,3 @@ 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

@@ -14,106 +14,111 @@
import sys
import os
from importlib.metadata import version as imp_version
on_rtd = os.getenv("READTHEDOCS") == "True"
on_rtd = os.getenv('READTHEDOCS') == 'True'
if on_rtd:
import escpos
else:
from setuptools_scm import get_version
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath("../src"))
root = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
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",
"sphinxcontrib.spelling",
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
'sphinx.ext.graphviz',
'sphinx.ext.inheritance_diagram',
]
# supress warnings for external images
suppress_warnings = [
"image.nonlocal_uri",
'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']
# 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 = "python-escpos"
copyright = "2016, Manuel F Martinez and others"
project = u'python-escpos'
copyright = u'2016, Manuel F Martinez and others'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
release = imp_version("python-escpos")
if on_rtd:
# The full version, including alpha/beta/rc tags.
release = escpos.__version__
else:
# locally setuptools_scm should work
release = get_version(root=root)
# The short X.Y version.
version = ".".join(release.split(".")[:2])
version = '.'.join(release.split('.')[:2]) # The short X.Y version.
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
#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 ----------------------------------------------
@@ -121,139 +126,135 @@ 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 = 'default'
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': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
"index",
"python-escpos.tex",
"python-escpos Documentation",
"Manuel F Martinez and others",
"manual",
),
('index', 'python-escpos.tex', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
#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 ---------------------------------------
@@ -261,17 +262,12 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
"index",
"python-escpos",
"python-escpos Documentation",
["Manuel F Martinez and others"],
1,
)
('index', 'python-escpos', u'python-escpos Documentation',
[u'Manuel F Martinez and others'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -280,31 +276,19 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"python-escpos",
"python-escpos Documentation",
"Manuel F Martinez and others",
"python-escpos",
"One line description of project.",
"Miscellaneous",
),
('index', 'python-escpos', u'python-escpos Documentation',
u'Manuel F Martinez and others', 'python-escpos', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
#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
# spellchecker
spelling_ignore_pypi_package_names = True
spelling_ignore_wiki_words = True
spelling_ignore_python_builtins = True
spelling_ignore_importable_modules = True
#texinfo_no_detailmenu = False

View File

@@ -1,22 +0,0 @@
#!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
GENLIST_W_MAIL=$(git shortlog -s -e -n | cut -f2 | sort -f)
AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"
if [ "$#" -eq 1 ]
then
echo "$GENLIST">$TEMPAUTHORSFILE
echo "\nAuthorsfile in version control:\n"
cat $AUTHORSFILE
echo "\nNew authorsfile:\n"
cat $TEMPAUTHORSFILE
echo "\nUsing diff on files...\n"
diff --suppress-common-lines -b --from-file $AUTHORSFILE $TEMPAUTHORSFILE
echo "Authors with mail addresses:\n"
echo "$GENLIST_W_MAIL"
else
echo "$GENLIST">$AUTHORSFILE
fi

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.https://sphinx-doc.org/
echo.http://sphinx-doc.org/
exit /b 1
)

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
********
Printers
********
:Last Reviewed: 2022-11-25
:Last Reviewed: 2017-01-25
As of now there are 7 different type of printer implementations.
As of now there are 5 different type of printer implementations.
USB
---
@@ -75,26 +75,3 @@ all of the "output" as raw ESC/POS in a string and returns that.
:member-order: bysource
:noindex:
CUPS
----
This driver uses `pycups` in order to communicate with a CUPS server.
Supports both local and remote CUPS printers and servers.
The printer must be properly configured in CUPS administration.
The connector generates a print job that is added to the CUPS queue.
.. todo:: fix import in documentation
LP
----
This driver uses the UNIX command `lp` in order to communicate with a CUPS server.
Supports local and remote CUPS printers.
The printer must be properly configured in CUPS administration.
The connector spawns a new sub-process where the command lp is executed.
No dependencies required, but somehow the print queue will affect some print job such as barcode.
.. autoclass:: escpos.printer.LP
:members:
:special-members:
:member-order: bysource
:noindex:

View File

@@ -2,9 +2,38 @@
TODO
****
Open points and issues of the project are tracked in the GitHub issues.
Some annotations still remain in the code and should be moved over time
into the issue tracker.
Introduction
------------
python-escpos is the initial idea, from here we can start to build a
robust library to get most of the ESC/POS printers working with this
library.
Eventually, this library must be able to cover almost all the defined
models detailed in the ESC/POS Command Specification Manual.
Details
-------
What things are planned to work on?
Testing
~~~~~~~
* Test on many printers as possible (USB, Serial, Network)
* automate testing
Design
~~~~~~
* Add all those sequences which are not common, but part of the ESC/POS
Command Specifications.
* Port to Python 3
* Windows compatibility (hidapi instead libusb?)
* PDF417 support
* use something similar to the `capabilities` in escpos-php
Todos in the codebase
~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,7 +1,6 @@
*****
Usage
*****
:Last Reviewed: 2017-06-10
Define your printer
-------------------
@@ -44,20 +43,18 @@ to have and the second yields the "Output Endpoint" address.
::
p = printer.Usb(0x04b8,0x0202)
Epson = printer.Usb(0x04b8,0x0202)
By default the "Interface" number is "0" and the "Output Endpoint"
address is "0x01". If you have other values then you can define them on
your instance. So, assuming that we have another printer, CT-S2000,
manufactured by Citizen (with "Vendor ID" of 2730 and "Product ID" of 0fff)
where in\_ep is on 0x81 and out\_ep=0x02, then the printer definition should
look like:
your instance. So, assuming that we have another printer where in\_ep is
on 0x81 and out\_ep=0x02, then the printer definition should look like:
**Generic USB Printer initialization**
::
p = printer.Usb(0x2730, 0x0fff, 0, 0x81, 0x02)
Generic = printer.Usb(0x1a2b,0x1a2b,0,0x81,0x02)
Network printer
^^^^^^^^^^^^^^^
@@ -69,7 +66,7 @@ IP by DHCP or you set it manually.
::
p = printer.Network("192.168.1.99")
Epson = printer.Network("192.168.1.99")
Serial printer
^^^^^^^^^^^^^^
@@ -83,10 +80,7 @@ to.
::
p = printer.Serial("/dev/tty0")
# on a Windows OS serial devices are typically accessible as COM
p = printer.Serial("COM1")
Epson = printer.Serial("/dev/tty0")
Other printers
^^^^^^^^^^^^^^
@@ -98,7 +92,7 @@ passing the device node name.
::
p = printer.File("/dev/usb/lp1")
Epson = printer.File("/dev/usb/lp1")
The default is "/dev/usb/lp0", so if the printer is located on that
node, then you don't necessary need to pass the node name.
@@ -113,22 +107,17 @@ on a USB interface.
from escpos import *
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
p = printer.Usb(0x04b8,0x0202)
Epson = printer.Usb(0x04b8,0x0202)
# Print text
p.text("Hello World\n")
Epson.text("Hello World\n")
# Print image
p.image("logo.gif")
Epson.image("logo.gif")
# Print QR Code
p.qr("You can readme from your smartphone")
Epson.qr("You can readme from your smartphone")
# Print barcode
p.barcode('1324354657687','EAN13',64,2,'','')
Epson.barcode('1324354657687','EAN13',64,2,'','')
# Cut paper
p.cut()
Standard python constraints on libraries apply. This means especially
that you should not name the script in which you implement these lines
should not be named ``escpos`` as this would collide with the name of
the library.
Epson.cut()
Configuration File
------------------
@@ -144,13 +133,13 @@ format. For windows it is probably at::
And for linux::
$HOME/.config/python-escpos/config.yaml
$HOME/.config/python-escpos/config.yaml
If you aren't sure, run::
from escpos import config
c = config.Config()
c.load()
from escpos import config
c = config.Config()
c.load()
If it can't find the configuration file in the default location, it will tell
you where it's looking. You can always pass a path, or a list of paths, to
@@ -158,9 +147,9 @@ the ``load()`` method.
To load the configured printer, run::
from escpos import config
c = config.Config()
printer = c.printer()
from escpos import config
c = config.Config()
printer = c.printer()
The printer section
@@ -168,43 +157,32 @@ The printer section
The ``printer`` configuration section defines a default printer to create.
The only required parameter is ``type``. The value of this has to be one of the
The only required paramter is ``type``. The value of this should be one of the
printers defined in :doc:`/user/printers`.
The rest of the given parameters will be passed on to the initialization of the printer class.
Use these to overwrite the default values as specified in :doc:`/user/printers`.
This implies that the parameters have to match the parameter-names of the respective printer class.
The rest of the parameters are whatever you want to pass to the printer.
An example file printer::
printer:
type: File
devfile: /dev/someprinter
printer:
type: File
devfile: /dev/someprinter
And for a network printer::
printer:
type: Network
host: 127.0.0.1
port: 9000
An USB-printer could be defined by::
printer:
type: Usb
idVendor: 0x1234
idProduct: 0x5678
in_ep: 0x66
out_ep: 0x01
printer:
type: network
host: 127.0.0.1
port: 9000
Printing text right
-------------------
Python-escpos is designed to accept unicode.
Python-escpos is designed to accept unicode. So make sure that you use ``u'strings'`` or import ``unicode_literals``
from ``__future__`` if you are on Python 2. On Python 3 you should be fine.
For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess
the right codepage and then send the encoded data to the printer. If this feature does not work, please try to
isolate the error and then create an issue on the GitHub project page.
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.
@@ -294,18 +272,4 @@ 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 +0,0 @@
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Print software and then hardware barcode with the same content
p.soft_barcode("code39", "123456")
p.text("\n")
p.text("\n")
p.barcode("123456", "CODE39")

View File

@@ -1,28 +1,21 @@
"""Prints code page tables.
"""
from __future__ import print_function
import six
import sys
from escpos import printer
from escpos.constants import (
CODEPAGE_CHANGE,
ESC,
CTL_LF,
CTL_FF,
CTL_CR,
CTL_HT,
CTL_VT,
)
from escpos.constants import CODEPAGE_CHANGE, ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT
def main():
dummy = printer.Dummy()
dummy.hw("init")
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)
@@ -44,14 +37,14 @@ def print_codepage(printer, codepage):
sep = ""
# Table header
printer.set(font="b")
printer.set(text_type='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(text_type='B')
printer._raw("{} ".format(hex(x)[2:]))
printer.set()
@@ -59,12 +52,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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,19 +0,0 @@
import sys
from escpos.printer import Usb
def usage():
print("usage: qr_code.py <content>")
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
sys.exit(1)
content = sys.argv[1]
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
p.qr(content, center=True)

View File

@@ -1,9 +0,0 @@
from escpos.printer import Usb
# Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890")
# Some software barcodes
p.soft_barcode("code128", "Hello")
p.soft_barcode("code39", "1234")

View File

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

View File

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

View File

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

View File

@@ -1,71 +1,11 @@
[metadata]
name = python-escpos
url = https://github.com/python-escpos/python-escpos
description = Python library to manipulate ESC/POS Printers
long_description = file: README.rst
license = MIT
license_file = LICENSE
author = Manuel F Martinez and others
author_email = dev@pkanzler.de
maintainer = Patrick Kanzler
maintainer_email = dev@pkanzler.de
keywords = ESC/POS, thermoprinter, voucher printer, printing, receipt
classifiers =
Development Status :: 4 - Beta
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development :: Libraries :: Python Modules
Topic :: Office/Business :: Financial :: Point-Of-Sale
project_urls =
Bug Tracker = https://github.com/python-escpos/python-escpos/issues
Documentation = https://python-escpos.readthedocs.io/en/latest/
Release Notes = https://github.com/python-escpos/python-escpos/releases
[options]
python_requires = >=3.6
zip_safe = false
include_package_data = true
install_requires =
pyusb>=1.0.0
Pillow>=2.0
qrcode>=4.0
pyserial
python-barcode>=0.9.1,<1
setuptools
six
appdirs
PyYAML
argparse
argcomplete
future
setup_requires = setuptools_scm
tests_require =
jaconv
tox
pytest!=3.2.0,!=3.3.0
pytest-cov
pytest-mock
nose
scripttest
mock
hypothesis>4
flake8
sphinxcontrib-spelling>=7.2.0
[nosetests]
verbosity=3
with-doctest=1
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python 3.
universal=1
[flake8]
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120

111
setup.py
View File

@@ -3,6 +3,7 @@
import os
import sys
from setuptools import find_packages, setup
from setuptools.command.test import test as test_command
base_dir = os.path.dirname(__file__)
@@ -18,23 +19,123 @@ def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
class Tox(test_command):
"""proxy class that enables tox to be run with setup.py test"""
user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
def initialize_options(self):
"""initialize the user-options"""
test_command.initialize_options(self)
self.tox_args = None
def finalize_options(self):
"""finalize user-options"""
test_command.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
"""run tox and pass on user-options"""
# import here, cause outside the eggs aren't loaded
import tox
import shlex
args = self.tox_args
if args:
args = shlex.split(self.tox_args)
errno = tox.cmdline(args=args)
sys.exit(errno)
setuptools_scm_template = """\
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
version = '{version}'
"""
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,
},
platforms="any",
url='https://github.com/python-escpos/python-escpos',
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
description='Python library to manipulate ESC/POS Printers',
bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
license='MIT',
long_description=read('README.rst'),
author='Manuel F Martinez and others',
author_email='manpaz@bashlinux.com',
maintainer='Patrick Kanzler',
maintainer_email='dev@pkanzler.de',
keywords=[
'ESC/POS',
'thermoprinter',
'voucher printer',
'printing',
'receipt,',
],
platforms='any',
package_dir={"": "src"},
packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={"escpos": ["capabilities.json"]},
entry_points={"console_scripts": ["python-escpos = escpos.cli:main"]},
package_data={'': ['COPYING', 'src/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 :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Office/Business :: Financial :: Point-Of-Sale',
],
install_requires=[
'pyusb>=1.0.0',
'Pillow>=2.0',
'qrcode>=4.0',
'pyserial',
'six',
'appdirs',
'pyyaml',
'argparse',
'argcomplete',
'future'
],
setup_requires=[
'setuptools_scm',
],
tests_require=[
'jaconv',
'tox',
'pytest',
'pytest-cov',
'pytest-mock',
'nose',
'scripttest',
'mock',
'hypothesis',
'flake8'
],
cmdclass={'test': Tox},
entry_points={
'console_scripts': [
'python-escpos = escpos.cli:main'
]
},
)

View File

@@ -2,6 +2,10 @@
"""
python-escpos enables you to manipulate escpos-printers
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
__all__ = ["constants", "escpos", "exceptions", "printer"]
@@ -9,7 +13,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,130 +1,93 @@
import re
from os import environ, path
import pkg_resources
import pickle
import logging
import time
import six
from os import environ, path
import yaml
from tempfile import gettempdir
import platform
from typing import Any, Dict
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
# get a temporary file from pkg_resources if no file is specified in env
capabilities_path = environ.get(
"ESCPOS_CAPABILITIES_FILE",
pkg_resources.resource_filename(__name__, "capabilities.json"),
)
# Load external printer database
t0 = time.time()
logger.debug("Using capabilities from file: %s", capabilities_path)
if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
logger.debug("Found a more recent capabilities file")
full_load = True
else:
full_load = False
logger.debug("Loading capabilities from pickle in %s", pickle_path)
with open(pickle_path, "rb") as cf:
CAPABILITIES = pickle.load(cf)
if 'ESCPOS_CAPABILITIES_FILE' in environ:
file_path = environ['ESCPOS_CAPABILITIES_FILE']
else:
logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True
file_path = path.join(path.dirname(__file__), 'capabilities.json')
if full_load:
logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2)
with open(file_path) as f:
CAPABILITIES = yaml.load(f)
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
PROFILES: Dict[str, Any] = CAPABILITIES["profiles"]
PROFILES = CAPABILITIES['profiles']
class NotSupported(Exception):
"""Raised if a requested feature is not supported by the
"""Raised if a requested feature is not suppored by the
printer profile.
"""
pass
BARCODE_B = "barcodeB"
BARCODE_B = 'barcodeB'
class BaseProfile(object):
"""This represents a printer profile.
"""This respresents a printer profile.
A printer profile knows about the number of columns, supported
features, colors and more.
"""
profile_data: Dict[str, Any] = {}
profile_data = {}
def __getattr__(self, name):
return self.profile_data[name]
def get_font(self, font) -> int:
def get_font(self, font):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
font = {"a": 0, "b": 1}.get(font, font)
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: str = None, **kwargs):
def get_profile(name=None, **kwargs):
"""Get the profile by name; if no name is given, return the
default profile.
"""
if isinstance(name, Profile):
return name
clazz = get_profile_class(name or "default")
clazz = get_profile_class(name or 'default')
return clazz(**kwargs)
CLASS_CACHE = {}
def get_profile_class(name: str):
def get_profile_class(name):
"""For the given profile name, load the data from the external
database, then generate dynamically a class.
"""
if name not in CLASS_CACHE:
profile_data = PROFILES[name]
profile_name = clean(name)
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
class_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]
@@ -132,13 +95,13 @@ def get_profile_class(name: str):
def clean(s):
# 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")):
class Profile(get_profile_class('default')):
"""
For users, who want to provide their profile
"""

View File

@@ -3,15 +3,18 @@
""" CLI
This module acts as a command line interface for python-escpos. It mirrors
closely the available ESCPOS commands while adding a couple extra ones for convenience.
closely the available ESCPOS commands while adding a couple extra ones for convience.
It requires you to have a configuration file. See documentation for details.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
try:
import argcomplete
except ImportError:
@@ -25,14 +28,14 @@ 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
""" 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
@@ -40,46 +43,40 @@ 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': '1324354657687'},
{'bc': 'EAN8', 'code': '1324354'},
{'bc': 'CODE39', 'code': 'TEST'},
{'bc': 'ITF', 'code': '55867492279103'},
{'bc': 'NW7', 'code': 'A00000000A'},
],
"barcodes_b": [
{"bc": "UPC-A", "code": "13243546576", "function_type": "B"},
{"bc": "UPC-E", "code": "132435", "function_type": "B"},
{"bc": "EAN13", "code": "1324354657687", "function_type": "B"},
{"bc": "EAN8", "code": "1324354", "function_type": "B"},
{"bc": "CODE39", "code": "TEST", "function_type": "B"},
{"bc": "ITF", "code": "55867492279103", "function_type": "B"},
{"bc": "NW7", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "A00000000A", "function_type": "B"},
{"bc": "CODE93", "code": "1324354657687", "function_type": "B"},
{"bc": "CODE128A", "code": "TEST", "function_type": "B"},
{"bc": "CODE128B", "code": "TEST", "function_type": "B"},
{"bc": "CODE128C", "code": "TEST", "function_type": "B"},
{"bc": "GS1-128", "code": "00123456780000000001", "function_type": "B"},
{
"bc": "GS1 DataBar Omnidirectional",
"code": "0000000000000",
"function_type": "B",
},
{"bc": "GS1 DataBar Truncated", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Limited", "code": "0000000000000", "function_type": "B"},
{"bc": "GS1 DataBar Expanded", "code": "00AAAAAAA", "function_type": "B"},
'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'},
],
}
@@ -91,355 +88,356 @@ DEMO_FUNCTIONS = {
# arguments: A list of dicts of args for subparser.add_argument
ESCPOS_COMMANDS = [
{
"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,
},
],
},
{
"parser": {
"name": "barcode",
"help": "Print a barcode",
},
"defaults": {
"func": "barcode",
},
"arguments": [
{
"option_strings": ("--code",),
"help": "Barcode data to print",
"required": True,
},
{
"option_strings": ("--bc",),
"help": "Barcode format",
"required": True,
},
{
"option_strings": ("--height",),
"help": "Barcode height in px",
"type": int,
},
{
"option_strings": ("--width",),
"help": "Barcode width",
"type": int,
},
{
"option_strings": ("--pos",),
"help": "Label position",
"choices": ["BELOW", "ABOVE", "BOTH", "OFF"],
},
{
"option_strings": ("--font",),
"help": "Label font",
"choices": ["A", "B"],
},
{
"option_strings": ("--align_ct",),
"help": "Align barcode center",
"type": str_to_bool,
},
{
"option_strings": ("--function_type",),
"help": "ESCPOS function type",
"choices": ["A", "B"],
},
],
},
{
"parser": {
"name": "text",
"help": "Print plain text",
},
"defaults": {
"func": "text",
},
"arguments": [
{
"option_strings": ("--txt",),
"help": "Plain text to print",
"required": True,
'option_strings': ('--size',),
'help': 'QR code size (1-16) [default:3]',
'required': False,
'type': int,
}
],
},
{
"parser": {
"name": "block_text",
"help": "Print wrapped text",
'parser': {
'name': 'barcode',
'help': 'Print a barcode',
},
"defaults": {
"func": "block_text",
'defaults': {
'func': 'barcode',
},
"arguments": [
'arguments': [
{
"option_strings": ("--txt",),
"help": "block_text to print",
"required": True,
'option_strings': ('--code',),
'help': 'Barcode data to print',
'required': True,
},
{
"option_strings": ("--columns",),
"help": "Number of columns",
"type": int,
'option_strings': ('--bc',),
'help': 'Barcode format',
'required': True,
},
{
'option_strings': ('--height',),
'help': 'Barcode height in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Barcode width',
'type': int,
},
{
'option_strings': ('--pos',),
'help': 'Label position',
'choices': ['BELOW', 'ABOVE', 'BOTH', 'OFF'],
},
{
'option_strings': ('--font',),
'help': 'Label font',
'choices': ['A', 'B'],
},
{
'option_strings': ('--align_ct',),
'help': 'Align barcode center',
'type': str_to_bool,
},
{
'option_strings': ('--function_type',),
'help': 'ESCPOS function type',
'choices': ['A', 'B'],
},
],
},
{
"parser": {
"name": "cut",
"help": "Cut the paper",
'parser': {
'name': 'text',
'help': 'Print plain text',
},
"defaults": {
"func": "cut",
'defaults': {
'func': 'text',
},
"arguments": [
'arguments': [
{
"option_strings": ("--mode",),
"help": "Type of cut",
"choices": ["FULL", "PART"],
'option_strings': ('--txt',),
'help': 'Plain text to print',
'required': True,
}
],
},
{
'parser': {
'name': 'block_text',
'help': 'Print wrapped text',
},
'defaults': {
'func': 'block_text',
},
'arguments': [
{
'option_strings': ('--txt',),
'help': 'block_text to print',
'required': True,
},
{
'option_strings': ('--columns',),
'help': 'Number of columns',
'type': int,
},
],
},
{
"parser": {
"name": "cashdraw",
"help": "Kick the cash drawer",
'parser': {
'name': 'cut',
'help': 'Cut the paper',
},
"defaults": {
"func": "cashdraw",
'defaults': {
'func': 'cut',
},
"arguments": [
'arguments': [
{
"option_strings": ("--pin",),
"help": "Which PIN to kick",
"choices": [2, 5],
'option_strings': ('--mode',),
'help': 'Type of cut',
'choices': ['FULL', 'PART'],
},
],
},
{
"parser": {
"name": "image",
"help": "Print an image",
'parser': {
'name': 'cashdraw',
'help': 'Kick the cash drawer',
},
"defaults": {
"func": "image",
'defaults': {
'func': 'cashdraw',
},
"arguments": [
'arguments': [
{
"option_strings": ("--img_source",),
"help": "Path to image",
"required": True,
},
{
"option_strings": ("--impl",),
"help": "Implementation to use",
"choices": ["bitImageRaster", "bitImageColumn", "graphics"],
},
{
"option_strings": ("--high_density_horizontal",),
"help": "Image density (horizontal)",
"type": str_to_bool,
},
{
"option_strings": ("--high_density_vertical",),
"help": "Image density (vertical)",
"type": str_to_bool,
'option_strings': ('--pin',),
'help': 'Which PIN to kick',
'choices': [2, 5],
},
],
},
{
"parser": {
"name": "fullimage",
"help": "Print a fullimage",
'parser': {
'name': 'image',
'help': 'Print an image',
},
"defaults": {
"func": "fullimage",
'defaults': {
'func': 'image',
},
"arguments": [
'arguments': [
{
"option_strings": ("--img",),
"help": "Path to img",
"required": True,
'option_strings': ('--img_source',),
'help': 'Path to image',
'required': True,
},
{
"option_strings": ("--max_height",),
"help": "Max height of image in px",
"type": int,
'option_strings': ('--impl',),
'help': 'Implementation to use',
'choices': ['bitImageRaster', 'bitImageColumn', 'graphics'],
},
{
"option_strings": ("--width",),
"help": "Max width of image in px",
"type": int,
'option_strings': ('--high_density_horizontal',),
'help': 'Image density (horizontal)',
'type': str_to_bool,
},
{
"option_strings": ("--histeq",),
"help": "Equalize the histrogram",
"type": str_to_bool,
'option_strings': ('--high_density_vertical',),
'help': 'Image density (vertical)',
'type': str_to_bool,
},
],
},
{
'parser': {
'name': 'fullimage',
'help': 'Print a fullimage',
},
'defaults': {
'func': 'fullimage',
},
'arguments': [
{
'option_strings': ('--img',),
'help': 'Path to img',
'required': True,
},
{
"option_strings": ("--bandsize",),
"help": "Size of bands to divide into when printing",
"type": int,
'option_strings': ('--max_height',),
'help': 'Max height of image in px',
'type': int,
},
{
'option_strings': ('--width',),
'help': 'Max width of image in px',
'type': int,
},
{
'option_strings': ('--histeq',),
'help': 'Equalize the histrogram',
'type': str_to_bool,
},
{
'option_strings': ('--bandsize',),
'help': 'Size of bands to divide into when printing',
'type': int,
},
],
},
{
"parser": {
"name": "charcode",
"help": "Set character code table",
'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,
},
],
},
@@ -455,71 +453,68 @@ def main():
"""
parser = argparse.ArgumentParser(
description="CLI for python-escpos",
epilog="Printer configuration is defined in the python-escpos config"
"file. See documentation for details.",
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)
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 of python-escpos')
parser_command_version.set_defaults(version=True)
# hook in argcomplete
if "argcomplete" in globals():
if 'argcomplete' in globals():
argcomplete.autocomplete(parser)
# Get only arguments actually passed
@@ -527,18 +522,16 @@ 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()
# 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()
@@ -546,12 +539,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
@@ -559,13 +552,13 @@ 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 demos. Called when CLI is passed `demo`. This function
Prints specificed demos. Called when CLI is passed `demo`. This function
uses the DEMO_FUNCTIONS dictionary.
:param printer: A printer from escpos.printer
@@ -575,14 +568,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

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

View File

@@ -1,9 +1,13 @@
""" ESC/POS configuration manager.
This module contains the implementations of abstract base class :py:class:`Config`.
This module contains the implentations of abstract base class :py:class:`Config`.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import appdirs
@@ -14,17 +18,16 @@ from . import exceptions
class Config(object):
"""Configuration handler class.
""" Configuration handler class.
This class loads configuration from a default or specificed directory. It
can create your defined printer and return it to you.
"""
_app_name = "python-escpos"
_config_file = "config.yaml"
_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
@@ -36,7 +39,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.
@@ -48,7 +51,7 @@ 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.
@@ -59,32 +62,31 @@ class Config(object):
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(
@@ -96,7 +98,7 @@ class Config(object):
self._has_loaded = True
def printer(self):
"""Returns a printer that was defined in the config, or throws an
""" Returns a printer that was defined in the config, or throws an
exception.
This method loads the default config if one hasn't beeen already loaded.
@@ -106,7 +108,7 @@ class Config(object):
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

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

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ Result/Exit codes:
- `20` = Barcode size values are out of range :py:exc:`~escpos.exceptions.BarcodeSizeError`
- `30` = Barcode text not supplied :py:exc:`~escpos.exceptions.BarcodeCodeError`
- `40` = Image height is too large :py:exc:`~escpos.exceptions.ImageSizeError`
- `41` = Image width is too large :py:exc:`~escpos.exceptions.ImageWidthError`
- `50` = No string supplied to be printed :py:exc:`~escpos.exceptions.TextError`
- `60` = Invalid pin to send Cash Drawer pulse :py:exc:`~escpos.exceptions.CashDrawerError`
- `70` = Invalid number of tab positions :py:exc:`~escpos.exceptions.TabPosError`
@@ -25,10 +24,14 @@ Result/Exit codes:
: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 """
def __init__(self, msg, status=None):
Exception.__init__(self)
self.msg = msg
@@ -41,13 +44,12 @@ class Error(Exception):
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`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -58,13 +60,12 @@ class BarcodeTypeError(Error):
class BarcodeSizeError(Error):
"""Barcode size is out of range.
""" Barcode size is out of range.
This exception indicates that the values for the barcode size are out of range.
The size of the barcode has to be in the range that is specified in :py:meth:`escpos.escpos.Escpos.barcode`.
The resulting returncode is `20`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -75,13 +76,11 @@ class BarcodeSizeError(Error):
class BarcodeCodeError(Error):
"""No Barcode code was supplied, or it is incorrect.
""" No Barcode code was supplied.
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.
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
The returncode for this exception is `30`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -92,62 +91,40 @@ class BarcodeCodeError(Error):
class ImageSizeError(Error):
"""Image height is longer than 255px and can't be printed.
""" Image height is longer than 255px and can't be printed.
The returncode for this exception is `40`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 40
def __str__(self):
return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
class ImageWidthError(Error):
"""Image width is too large.
The return code for this exception is `41`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 41
def __str__(self):
return "Image width is too large ({msg})".format(msg=self.msg)
return "Image height is longer than 255px and can't be printed ({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`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 50
def __str__(self):
return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg
)
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`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -158,31 +135,26 @@ class CashDrawerError(Error):
class TabPosError(Error):
"""Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value.
""" Valid tab positions must be in the range 0 to 16.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
The returncode for this exception is `70`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg
)
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`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -193,12 +165,11 @@ class CharCodeError(Error):
class USBNotFoundError(Error):
"""Device wasn't found (probably not plugged in)
""" Device wasn't found (probably not plugged in)
The USB device seems to be not plugged in.
Ths returncode for this exception is `90`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -209,12 +180,11 @@ class USBNotFoundError(Error):
class SetVariableError(Error):
"""A set method variable was out of range
""" A set method variable was out of range
Check set variables against minimum and maximum values
Ths returncode for this exception is `100`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -226,14 +196,12 @@ class SetVariableError(Error):
# 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`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -244,12 +212,11 @@ class ConfigNotFoundError(Error):
class ConfigSyntaxError(Error):
"""The configuration file is invalid
""" The configuration file is invalid
The syntax is incorrect
Ths returncode for this exception is `210`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg
@@ -260,12 +227,11 @@ class ConfigSyntaxError(Error):
class ConfigSectionMissingError(Error):
"""The configuration file is missing a section
""" The configuration file is missing a section
The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`.
"""
def __init__(self, msg=""):
Error.__init__(self, msg)
self.msg = msg

View File

@@ -8,6 +8,10 @@ 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
@@ -37,7 +41,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
@@ -85,7 +89,7 @@ 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):
@@ -101,7 +105,7 @@ class EscposImage(object):
: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
@@ -111,19 +115,3 @@ class EscposImage(object):
box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box))
return fragments
def center(self, max_width):
"""In-place image centering
:param: Maximum width in order to deduce x offset for centering
:return: None
"""
old_width, height = self._im.size
new_size = (max_width, height)
new_im = Image.new("1", new_size)
paste_x = int((max_width - old_width) / 2)
new_im.paste(self._im, (paste_x, 0))
self._im = new_im

View File

@@ -4,6 +4,11 @@
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
@@ -34,68 +39,69 @@ 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

@@ -12,13 +12,16 @@ 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
from builtins import bytes
from .constants import CODEPAGE_CHANGE
from .exceptions import Error
from .codepages import CodePages
import six
import re
class Encoder(object):
@@ -56,12 +59,10 @@ class Encoder(object):
"""
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
@@ -73,18 +74,16 @@ class Encoder(object):
: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 = [" "] * 128
elif 'python_encode' in codepage:
encodable_chars = [u" "] * 128
for i in range(0, 128):
codepoint = i + 128
try:
encodable_chars[i] = bytes([codepoint]).decode(
codepage["python_encode"]
)
encodable_chars[i] = bytes([codepoint]).decode(codepage['python_encode'])
except UnicodeDecodeError:
# Non-encodable character, just skip it
pass
@@ -92,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.
@@ -105,9 +104,7 @@ 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
@@ -130,7 +127,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
@@ -141,22 +138,23 @@ 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)
return (
key in self.used_encodings,
index
)
def find_suitable_encoding(self, char):
"""The order of our search is a specific one:
@@ -174,7 +172,9 @@ 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 +184,7 @@ class Encoder(object):
def split_writable_text(encoder, text, encoding):
"""Splits off as many characters from the beginning of text as
"""Splits off as many characters from the begnning of text as
are writable with "encoding". Returns a 2-tuple (writable, rest).
"""
if not encoding:
@@ -209,10 +209,8 @@ 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):
"""
:param driver:
@@ -225,7 +223,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())
@@ -247,16 +245,13 @@ 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:
@@ -277,26 +272,25 @@ 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."""
"""Called when no codepage was found to render a character.
"""
# Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches.
self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text):
if text is not None and type(text) is not six.text_type:
raise Error(
"The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
)
)
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

@@ -8,39 +8,22 @@
:license: MIT
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
import socket
import subprocess
import sys
import serial
import usb.core
import usb.util
import serial
import socket
from .escpos import Escpos
from .exceptions import USBNotFoundError
_WIN32PRINT = False
try:
import win32print
_WIN32PRINT = True
except ImportError:
pass
_CUPSPRINT = False
try:
import cups
import tempfile
_CUPSPRINT = True
except ImportError:
pass
class Usb(Escpos):
"""USB printer
""" USB printer
This class describes a printer that natively speaks USB.
@@ -51,69 +34,41 @@ class Usb(Escpos):
"""
def __init__(
self,
idVendor,
idProduct,
usb_args=None,
timeout=0,
in_ep=0x82,
out_ep=0x01,
*args,
**kwargs
): # noqa: N803
def __init__(self, idVendor, idProduct, timeout=0, in_ep=0x82, out_ep=0x01, *args, **kwargs): # noqa: N803
"""
:param idVendor: Vendor ID
:param idProduct: Product ID
:param usb_args: Optional USB arguments (e.g. custom_match)
:param timeout: Is the time limit of the USB operation. Default without timeout.
:param in_ep: Input end point
:param out_ep: Output end point
"""
Escpos.__init__(self, *args, **kwargs)
self.idVendor = idVendor
self.idProduct = idProduct
self.timeout = timeout
self.in_ep = in_ep
self.out_ep = out_ep
self.open()
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)
def open(self):
""" Search device on USB tree and set it as escpos device """
self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct)
if self.device is None:
raise USBNotFoundError("Device not found or cable not plugged in.")
self.idVendor = self.device.idVendor
self.idProduct = self.device.idProduct
check_driver = None
# 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:
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)))
self.device.detach_kernel_driver(0)
except usb.core.USBError as e:
if check_driver is not None:
print("Could not detatch kernel driver: {0}".format(str(e)))
try:
self.device.set_configuration()
@@ -122,26 +77,22 @@ class Usb(Escpos):
print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg):
"""Print any command sent in raw format
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
: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"""
""" Release USB interface """
if self.device:
usb.util.dispose_resources(self.device)
self.device = None
class Serial(Escpos):
"""Serial printer
""" Serial printer
This class describes a printer that is connected by serial interface.
@@ -152,19 +103,9 @@ class Serial(Escpos):
"""
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
):
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
@@ -189,19 +130,11 @@ class Serial(Escpos):
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,
)
""" Setup serial port and set is as escpos device """
self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate,
bytesize=self.bytesize, parity=self.parity,
stopbits=self.stopbits, timeout=self.timeout,
xonxoff=self.xonxoff, dsrdtr=self.dsrdtr)
if self.device is not None:
print("Serial printer enabled")
@@ -209,26 +142,22 @@ class Serial(Escpos):
print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg):
"""Print any command sent in raw format
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
: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:
""" Close Serial interface """
if self.device is not None:
self.device.flush()
self.device.close()
class Network(Escpos):
"""Network printer
""" Network printer
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
is forwarded with ``socat``.
@@ -264,7 +193,7 @@ class Network(Escpos):
self.open()
def open(self):
"""Open TCP socket with ``socket``-library and set it as escpos device"""
""" Open TCP socket with ``socket``-library and set it as escpos device """
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port))
@@ -273,30 +202,22 @@ class Network(Escpos):
print("Could not open socket for {0}".format(self.host))
def _raw(self, msg):
"""Print any command sent in raw format
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
: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"""
""" Close TCP connection """
if self.device is not None:
try:
self.device.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
self.device.shutdown(socket.SHUT_RDWR)
self.device.close()
class File(Escpos):
"""Generic file printer
""" Generic file printer
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
@@ -312,7 +233,7 @@ class File(Escpos):
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
"""
:param devfile: Device file under dev filesystem
:param devfile : Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw()
"""
Escpos.__init__(self, *args, **kwargs)
@@ -321,18 +242,18 @@ class File(Escpos):
self.open()
def open(self):
"""Open system file"""
""" 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"""
""" Flush printing content """
self.device.flush()
def _raw(self, msg):
"""Print any command sent in raw format
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -342,14 +263,14 @@ class File(Escpos):
self.flush()
def close(self):
"""Close system file"""
""" Close system file """
if self.device is not None:
self.device.flush()
self.device.close()
class Dummy(Escpos):
"""Dummy printer
""" 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
@@ -363,12 +284,13 @@ class Dummy(Escpos):
"""
def __init__(self, *args, **kwargs):
""" """
"""
"""
Escpos.__init__(self, *args, **kwargs)
self._output_list = []
def _raw(self, msg):
"""Print any command sent in raw format
""" Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
@@ -377,226 +299,8 @@ class Dummy(Escpos):
@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[:]
""" Get the data that was sent to this printer """
return b''.join(self._output_list)
def close(self):
pass
if _WIN32PRINT:
class Win32Raw(Escpos):
def __init__(self, printer_name=None, *args, **kwargs):
Escpos.__init__(self, *args, **kwargs)
if printer_name is not None:
self.printer_name = printer_name
else:
self.printer_name = win32print.GetDefaultPrinter()
self.hPrinter = None
self.open()
def open(self, job_name="python-escpos"):
if self.printer_name is None:
raise Exception("Printer not found")
self.hPrinter = win32print.OpenPrinter(self.printer_name)
self.current_job = win32print.StartDocPrinter(
self.hPrinter, 1, (job_name, None, "RAW")
)
win32print.StartPagePrinter(self.hPrinter)
def close(self):
if not self.hPrinter:
return
win32print.EndPagePrinter(self.hPrinter)
win32print.EndDocPrinter(self.hPrinter)
win32print.ClosePrinter(self.hPrinter)
self.hPrinter = None
def _raw(self, msg):
"""Print any command sent in raw format
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.printer_name is None:
raise Exception("Printer not found")
if self.hPrinter is None:
raise Exception("Printer job not opened")
win32print.WritePrinter(self.hPrinter, msg)
if _CUPSPRINT:
class CupsPrinter(Escpos):
"""Simple CUPS printer connector.
.. note::
Requires _pycups_ which in turn needs the cups development library package:
- Ubuntu/Debian: _libcups2-dev_
- OpenSuse/Fedora: _cups-devel_
"""
def __init__(self, printer_name=None, *args, **kwargs):
"""CupsPrinter class constructor.
:param printer_name: CUPS printer name (Optional)
:type printer_name: str
:param host: CUPS server host/ip (Optional)
:type host: str
:param port: CUPS server port (Optional)
:type port: int
"""
Escpos.__init__(self, *args, **kwargs)
host, port = args or (
kwargs.get("host", cups.getServer()),
kwargs.get("port", cups.getPort()),
)
cups.setServer(host)
cups.setPort(port)
self.conn = cups.Connection()
self.tmpfile = None
self.printer_name = printer_name
self.job_name = ""
self.pending_job = False
self.open()
@property
def printers(self):
"""Available CUPS printers."""
return self.conn.getPrinters()
def open(self, job_name="python-escpos"):
"""Setup a new print job and target printer.
A call to this method is required to send new jobs to
the same CUPS connection.
Defaults to default CUPS printer.
Creates a new temporary file buffer.
"""
self.job_name = job_name
if self.printer_name not in self.printers:
self.printer_name = self.conn.getDefault()
self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
def _raw(self, msg):
"""Append any command sent in raw format to temporary file
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self.pending_job = True
try:
self.tmpfile.write(msg)
except ValueError:
self.pending_job = False
raise ValueError("Printer job not opened")
def send(self):
"""Send the print job to the printer."""
if self.pending_job:
# Rewind tempfile
self.tmpfile.seek(0)
# Print temporary file via CUPS printer.
self.conn.printFile(
self.printer_name,
self.tmpfile.name,
self.job_name,
{"document-format": cups.CUPS_FORMAT_RAW},
)
self._clear()
def _clear(self):
"""Finish the print job.
Remove temporary file.
"""
self.tmpfile.close()
self.pending_job = False
def _read(self):
"""Return a single-item array with the accepting state of the print queue.
states: idle = [3], printing a job = [4], stopped = [5]
"""
printer = self.printers.get(self.printer_name, {})
state = printer.get("printer-state")
if not state:
return []
return [state]
def close(self):
"""Close CUPS connection.
Send pending job to the printer if needed.
"""
if self.pending_job:
self.send()
if self.conn:
print("Closing CUPS connection to printer {}".format(self.printer_name))
self.conn = None
if not sys.platform.startswith("win"):
class LP(Escpos):
"""Simple UNIX lp command raw printing.
Thanks to `Oyami-Srk comment <https://github.com/python-escpos/python-escpos/pull/348#issuecomment-549558316>`_.
"""
def __init__(self, printer_name: str, *args, **kwargs):
"""LP class constructor.
:param printer_name: CUPS printer name (Optional)
:type printer_name: str
:param auto_flush: Automatic flush after every _raw() (Optional)
:type auto_flush: bool
"""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
self.auto_flush = kwargs.get("auto_flush", True)
self.open()
def open(self):
"""Invoke _lp_ in a new subprocess and wait for commands."""
self.lp = subprocess.Popen(
["lp", "-d", self.printer_name, "-o", "raw"],
stdin=subprocess.PIPE,
stdout=open(os.devnull, "w"),
)
def close(self):
"""Stop the subprocess."""
self.lp.terminate()
def flush(self):
"""End line and wait for new commands"""
if self.lp.stdin.writable():
self.lp.stdin.write(b"\n")
if self.lp.stdin.closed is False:
self.lp.stdin.close()
self.lp.wait()
self.open()
def _raw(self, msg):
"""Write raw command(s) to the printer.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.lp.stdin.writable():
self.lp.stdin.write(msg)
else:
raise Exception("Not a valid pipe for lp process")
if self.auto_flush:
self.flush()

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python
"""verifies that the metaclass abc is properly used by ESC/POS
"""verifies that the metaclass abc is properly used by Escpos
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
@@ -7,6 +7,10 @@
: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
@@ -16,11 +20,11 @@ from abc import ABCMeta
@raises(TypeError)
def test_abstract_base_class_raises():
"""test whether the abstract base class raises an exception for ESC/POS"""
"""test whether the abstract base class raises an exception for Escpos"""
escpos.Escpos() # This call should raise TypeError because of abstractmethod _raw()
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,46 +2,51 @@
"""
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_equal, nottest
from nose.tools import assert_equals, nottest
import escpos
TEST_DIR = os.path.abspath("test/test-cli-output")
TEST_DIR = os.path.abspath('test/test-cli-output')
DEVFILE_NAME = "testfile"
DEVFILE_NAME = 'testfile'
DEVFILE = os.path.join(TEST_DIR, DEVFILE_NAME)
CONFIGFILE = "testconfig.yaml"
CONFIG_YAML = """
CONFIGFILE = 'testconfig.yaml'
CONFIG_YAML = '''
---
printer:
type: file
devfile: {testfile}
""".format(
'''.format(
testfile=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)
def setup(self):
"""Create a file to print to and set up env"""
""" Create a file to print to and set up env"""
self.env = None
self.default_args = None
@@ -51,59 +56,63 @@ class TestCLI:
)
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"""
""" 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_equal(escpos.__version__, result.stdout.strip())
assert_equals(escpos.__version__, result.stdout.strip())
@nottest # disable this test as it is not that easy anymore to predict the outcome of this call
def test_cli_text(self):
"""Make sure text returns what we sent it"""
test_text = "this is some text"
""" 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_equals(
result.files_updated[DEVFILE_NAME].bytes,
test_text + '\n'
)
def test_cli_text_inavlid_args(self):
"""Test a failure to send valid arguments"""
""" Test a failure to send valid arguments """
result = self.env.run(
*(self.default_args + ("text", "--invalid-param", "some data")),
*(self.default_args + (
'text',
'--invalid-param',
'some data'
)),
expect_error=True,
expect_stderr=True
)
assert_equal(result.returncode, 2)
assert "error:" in result.stderr
assert_equals(result.returncode, 2)
assert 'error:' in result.stderr
assert not result.files_updated

View File

@@ -1,56 +1,38 @@
#!/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
from escpos.exceptions import BarcodeTypeError
import pytest
@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""
@pytest.mark.parametrize(
"bctype,data",
[
("EAN13", "AA"),
("CODE128", "{D2354AA"),
],
)
def test_code_check(bctype, data):
"""should raise an error if the barcode code is invalid."""
instance = printer.Dummy()
with pytest.raises(BarcodeCodeError):
instance.barcode(data, bctype)
assert instance.output == b""
assert instance.output == b''

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
import six
import escpos.printer as printer
from escpos.constants import GS
def test_cut_without_feed():
"""Test cut without feeding paper"""
instance = printer.Dummy()
instance.cut(feed=False)
expected = GS + b"V" + six.int2byte(66) + b"\x00"
assert instance.output == expected

View File

@@ -1,9 +0,0 @@
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""

View File

@@ -7,13 +7,13 @@
:license: MIT
"""
import pytest
from PIL import Image
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import escpos.printer as printer
from escpos.exceptions import ImageWidthError
from PIL import Image
# Raster format print
@@ -22,13 +22,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():
@@ -36,8 +36,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():
@@ -45,8 +45,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():
@@ -54,8 +54,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
@@ -64,8 +64,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():
@@ -73,8 +73,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():
@@ -82,10 +82,8 @@ 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():
@@ -93,10 +91,8 @@ 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,11 +101,8 @@ 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():
@@ -117,11 +110,8 @@ 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():
@@ -129,11 +119,8 @@ 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():
@@ -141,11 +128,8 @@ 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():
@@ -153,38 +137,5 @@ 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"
)
@pytest.fixture
def dummy_with_width():
instance = printer.Dummy()
instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
return instance
def test_width_too_large(dummy_with_width):
"""
Test printing an image that is too large in width.
"""
instance = dummy_with_width
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)))
instance.image(Image.new("RGB", (384, 200)))
def test_center_image(dummy_with_width):
instance = dummy_with_width
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)), center=True)
instance.image(Image.new("RGB", (384, 200)), center=True)
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')

View File

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

View File

@@ -7,19 +7,48 @@
: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 with_setup
import escpos.printer as printer
import os
devfile = 'testfile'
def setup_testfile():
"""create a testfile as devfile"""
fhandle = open(devfile, 'a')
try:
os.utime(devfile, None)
finally:
fhandle.close()
def teardown_testfile():
"""destroy testfile again"""
os.remove(devfile)
@with_setup(setup_testfile, teardown_testfile)
def test_function_panel_button_on():
"""test the panel button function (enabling) by comparing output"""
instance = printer.Dummy()
instance = printer.File(devfile=devfile)
instance.panel_buttons()
assert instance.output == b"\x1B\x63\x35\x00"
instance.flush()
with open(devfile, "rb") as f:
assert(f.read() == b'\x1B\x63\x35\x00')
@with_setup(setup_testfile, teardown_testfile)
def test_function_panel_button_off():
"""test the panel button function (disabling) by comparing output"""
instance = printer.Dummy()
instance = printer.File(devfile=devfile)
instance.panel_buttons(False)
assert instance.output == b"\x1B\x63\x35\x01"
instance.flush()
with open(devfile, "rb") as f:
assert(f.read() == b'\x1B\x63\x35\x01')

View File

@@ -7,10 +7,12 @@
: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
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
@@ -19,51 +21,43 @@ 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)
@@ -87,20 +81,15 @@ def test_invalid_model():
instance.qr("1234", native=True, model="Hello")
@pytest.mark.skip("this test has to be debugged")
def test_image():
"""Test QR as image"""
instance = printer.Dummy()
instance.qr("1", native=False, size=1)
print(instance.output)
expected = (
b"\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'\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'
assert(instance.output == expected)
@raises(ValueError)
@@ -108,13 +97,3 @@ def test_image_invalid_model():
"""Test unsupported QR model as image"""
instance = printer.Dummy()
instance.qr("1234", native=False, model=QR_MODEL_1)
@pytest.fixture
def instance():
return printer.Dummy()
def test_center_not_implementer(instance):
with pytest.raises(NotImplementedError):
instance.qr("test", center=True, native=True)

View File

@@ -8,15 +8,18 @@
: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
from PIL import Image
@mock.patch("escpos.printer.Dummy.image", spec=Dummy)
@mock.patch('escpos.printer.Dummy.image', spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function):
"""
Test the type of object that is passed to the image function during non-native qr-printing.
@@ -27,12 +30,3 @@ def test_type_of_object_passed_to_image_function(img_function):
d.qr("LoremIpsum")
args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image)
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
instance.qr("LoremIpsum", center=True)

View File

@@ -1,297 +0,0 @@
import six
import escpos.printer as printer
from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT
from escpos.constants import TXT_SIZE
# Default test, please copy and paste this block to test set method calls
def test_default_values():
instance = printer.Dummy()
instance.set()
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Size tests
def test_set_size_2h():
instance = printer.Dummy()
instance.set(double_height=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2h"], # Double height text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2w():
instance = printer.Dummy()
instance.set(double_width=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2w"], # Double width text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2x():
instance = printer.Dummy()
instance.set(double_height=True, double_width=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2x"], # Double text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_custom():
instance = printer.Dummy()
instance.set(custom_size=True, width=8, height=7)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]),
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Flip
def test_set_flip():
instance = printer.Dummy()
instance.set(flip=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][True], # Flip ON
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Smooth
def test_smooth():
instance = printer.Dummy()
instance.set(smooth=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][True], # Smooth ON
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Type
def test_set_bold():
instance = printer.Dummy()
instance.set(bold=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][True], # Bold ON
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_underline():
instance = printer.Dummy()
instance.set(underline=1)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][1], # Underline ON, type 1
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_set_underline2():
instance = printer.Dummy()
instance.set(underline=2)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][2], # Underline ON, type 2
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Align
def test_align_center():
instance = printer.Dummy()
instance.set(align="center")
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["center"], # Align center
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
def test_align_right():
instance = printer.Dummy()
instance.set(align="right")
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["right"], # Align right
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Densities
def test_densities():
for density in range(8):
instance = printer.Dummy()
instance.set(density=density)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["density"][density], # Custom density from 0 to 8
TXT_STYLE["invert"][False], # Inverted OFF
)
assert instance.output == b"".join(expected_sequence)
# Invert
def test_invert():
instance = printer.Dummy()
instance.set(invert=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["normal"], # Normal text size
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
TXT_STYLE["underline"][0], # Underline OFF
SET_FONT(b"\x00"), # Default font
TXT_STYLE["align"]["left"], # Align left
TXT_STYLE["invert"][True], # Inverted ON
)
assert instance.output == b"".join(expected_sequence)

View File

@@ -1,25 +0,0 @@
#!/usr/bin/python
import escpos.printer as printer
import barcode.errors
import pytest
@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.soft_barcode("ean8", "1234")
def test_soft_barcode_ean8(instance):
"""test with a valid ean8 barcode"""
instance.soft_barcode("ean8", "1234567")
def test_soft_barcode_ean8_nocenter(instance):
instance.soft_barcode("ean8", "1234567", center=False)

View File

@@ -7,6 +7,10 @@
: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
@@ -16,12 +20,13 @@ 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)
@@ -31,32 +36,6 @@ 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."
)
def test_textln():
printer = get_printer()
printer.textln("hello, world")
assert printer.output == b"hello, world\n"
def test_textln_empty():
printer = get_printer()
printer.textln()
assert printer.output == b"\n"
def test_ln():
printer = get_printer()
printer.ln()
assert printer.output == b"\n"
def test_multiple_ln():
printer = get_printer()
printer.ln(3)
assert printer.output == b"\n\n\n"
"All the presidents men were eating falafel for breakfast.", font='a')
assert printer.output == \
b'All the presidents men were eating falafel\nfor breakfast.'

View File

@@ -5,13 +5,13 @@ 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():

View File

@@ -15,67 +15,57 @@ 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
i = 0
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

@@ -7,11 +7,35 @@
: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 with_setup
import escpos.printer as printer
import os
devfile = 'testfile'
def setup_testfile():
"""create a testfile as devfile"""
fhandle = open(devfile, 'a')
try:
os.utime(devfile, None)
finally:
fhandle.close()
def teardown_testfile():
"""destroy testfile again"""
os.remove(devfile)
@with_setup(setup_testfile, teardown_testfile)
def test_instantiation():
"""test the instantiation of a escpos-printer class and basic printing"""
instance = printer.Dummy()
instance.text("This is a test\n")
instance = printer.File(devfile=devfile)
instance.text('This is a test\n')

View File

@@ -8,6 +8,10 @@
: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
@@ -24,17 +28,17 @@ class TestEncoder:
"""
def test_can_encode(self):
assert not Encoder({"CP437": 1}).can_encode("CP437", "")
assert Encoder({"CP437": 1}).can_encode("CP437", "á")
assert not Encoder({"foobar": 1}).can_encode("foobar", "a")
assert not Encoder({'CP437': 1}).can_encode('CP437', u'')
assert Encoder({'CP437': 1}).can_encode('CP437', u'á')
assert not Encoder({'foobar': 1}).can_encode('foobar', 'a')
def test_find_suitable_encoding(self):
assert not Encoder({"CP437": 1}).find_suitable_encoding("")
assert Encoder({"CP858": 1}).find_suitable_encoding("") == "CP858"
assert not Encoder({'CP437': 1}).find_suitable_encoding(u'')
assert Encoder({'CP858': 1}).find_suitable_encoding(u'') == 'CP858'
@raises(ValueError)
def test_get_encoding(self):
Encoder({}).get_encoding_name("latin1")
Encoder({}).get_encoding_name('latin1')
class TestMagicEncode:
@@ -57,50 +61,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("€ ist teuro.")
assert driver.output == b"_ ist teuro."
driver, defaultsymbol="_", encoder=Encoder({'CP437': 1}),
encoding='CP437')
encode.write(u'€ ist teuro.')
assert driver.output == b'_ ist teuro.'
class TestForceEncoding:
def test(self, driver):
encode = MagicEncode(driver)
encode.force_encoding("CP437")
assert driver.output == b"\x1bt\x00"
encode.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:
@@ -119,5 +123,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'

View File

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

View File

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

View File

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

View File

@@ -1,28 +0,0 @@
#!/usr/bin/python
"""test the raising of errors with the error module
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
import pytest
import escpos
import escpos.exceptions
def test_raise_error_wrongly():
"""raise error the wrong way
should reproduce https://github.com/python-escpos/python-escpos/issues/257
"""
with pytest.raises(AttributeError):
raise escpos.Error("This should raise an AttributeError.")
def tests_raise_error():
"""raise error the right way"""
with pytest.raises(escpos.exceptions.Error):
raise escpos.exceptions.Error("This should raise an error.")

View File

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

25
tox.ini
View File

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