mirror of
https://github.com/python-escpos/python-escpos
synced 2025-09-13 09:09:58 +00:00
Compare commits
57 Commits
v3.0
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1349ad85bf | ||
![]() |
11d46fdb66 | ||
![]() |
1a780e8f80 | ||
![]() |
c702204231 | ||
![]() |
00d3a1301f | ||
![]() |
ebd6f88acf | ||
![]() |
b85d5b907d | ||
![]() |
3dfbf15fa5 | ||
![]() |
9b695d698b | ||
![]() |
7ec59c41a2 | ||
![]() |
a394826d69 | ||
![]() |
e383b7a397 | ||
![]() |
e39ec9e50d | ||
![]() |
dc0d9e6bf6 | ||
![]() |
5d943566c9 | ||
![]() |
3e8525673b | ||
![]() |
0a1d3841f1 | ||
![]() |
27def759ba | ||
![]() |
8c44d8e64e | ||
![]() |
7d42f11716 | ||
![]() |
5cdff0b56e | ||
![]() |
f42410603d | ||
![]() |
22982fbd12 | ||
![]() |
82386f7496 | ||
![]() |
99501cc2c1 | ||
![]() |
3aaf203ceb | ||
![]() |
a8753a1121 | ||
![]() |
5af01641d9 | ||
![]() |
fe3cdde424 | ||
![]() |
4c02881fe7 | ||
![]() |
82f5a00b8d | ||
![]() |
1e9adb80f3 | ||
![]() |
640f6089ac | ||
![]() |
a8a9d0f0ad | ||
![]() |
f781e28a69 | ||
![]() |
62c234f6f1 | ||
![]() |
4ba98c0017 | ||
![]() |
a865b715f3 | ||
![]() |
776b8a26ad | ||
![]() |
9b0d126da2 | ||
![]() |
0d22896689 | ||
![]() |
b66dafc90e | ||
![]() |
0c824cf295 | ||
![]() |
66a2e78e16 | ||
![]() |
06bdb56937 | ||
![]() |
91cbc264fc | ||
![]() |
dcc71ce47d | ||
![]() |
d2b213dcdc | ||
![]() |
c91eec544e | ||
![]() |
a9e49c909d | ||
![]() |
815f247df1 | ||
![]() |
5914c7c560 | ||
![]() |
86a715cf02 | ||
![]() |
ac23c083b6 | ||
![]() |
9a1699ab94 | ||
![]() |
8274833255 | ||
![]() |
33d17615a0 |
4
.github/workflows/black.yml
vendored
4
.github/workflows/black.yml
vendored
@@ -6,8 +6,8 @@ jobs:
|
|||||||
black-code-style:
|
black-code-style:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: psf/black@stable
|
- uses: psf/black@stable
|
||||||
with:
|
with:
|
||||||
version: "23.3.0"
|
version: "23.12.0"
|
||||||
|
|
||||||
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -63,4 +63,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
@@ -27,6 +27,6 @@ jobs:
|
|||||||
sudo apt-get update -y &&
|
sudo apt-get update -y &&
|
||||||
sudo apt-get install -y git python3-sphinx graphviz libenchant-2-2 &&
|
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 apt-get install -y gcc libcups2-dev python3-dev python3-setuptools &&
|
||||||
sudo pip install tox pycups
|
sudo pip install --ignore-installed tox pycups
|
||||||
- name: Test doc build
|
- name: Test doc build
|
||||||
run: tox -e docs
|
run: tox -e docs
|
||||||
|
14
.github/workflows/pythonpackage-windows.yml
vendored
14
.github/workflows/pythonpackage-windows.yml
vendored
@@ -12,14 +12,14 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.11', '3.12']
|
python-version: ['3.11', '3.12', '3.13']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4.7.1
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -44,12 +44,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
|
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
with:
|
with:
|
||||||
directory: ./coverage/reports/
|
|
||||||
env_vars: OS,PYTHON
|
env_vars: OS,PYTHON
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
files: ./coverage.xml,!./cache
|
files: ./coverage.xml
|
||||||
|
exclude: "**/.mypy_cache"
|
||||||
flags: unittests
|
flags: unittests
|
||||||
name: coverage-tox-${{ matrix.python-version }}
|
name: coverage-tox-${{ matrix.python-version }}
|
||||||
verbose: true
|
verbose: true
|
||||||
|
14
.github/workflows/pythonpackage.yml
vendored
14
.github/workflows/pythonpackage.yml
vendored
@@ -15,14 +15,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4.7.1
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -54,12 +54,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
|
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
with:
|
with:
|
||||||
directory: ./coverage/reports/
|
|
||||||
env_vars: OS,PYTHON
|
env_vars: OS,PYTHON
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
files: ./coverage.xml,!./cache
|
files: ./coverage.xml
|
||||||
|
exclude: "**/.mypy_cache"
|
||||||
flags: unittests
|
flags: unittests
|
||||||
name: coverage-tox-${{ matrix.python-version }}
|
name: coverage-tox-${{ matrix.python-version }}
|
||||||
verbose: true
|
verbose: true
|
||||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -12,6 +12,8 @@
|
|||||||
"editor.formatOnPaste": true,
|
"editor.formatOnPaste": true,
|
||||||
"python.formatting.provider": "black",
|
"python.formatting.provider": "black",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": true
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
}
|
}
|
2
AUTHORS
2
AUTHORS
@@ -2,6 +2,7 @@ Ahmed Tahri
|
|||||||
akeonly
|
akeonly
|
||||||
Alejandro Hernández
|
Alejandro Hernández
|
||||||
Alexander Bougakov
|
Alexander Bougakov
|
||||||
|
Alexandre Detiste
|
||||||
Alex Debiasio
|
Alex Debiasio
|
||||||
Alfredo Orozco
|
Alfredo Orozco
|
||||||
Asuki Kono
|
Asuki Kono
|
||||||
@@ -46,6 +47,7 @@ Sergio Pulgarin
|
|||||||
Stephan Sokolow
|
Stephan Sokolow
|
||||||
Thijs Triemstra
|
Thijs Triemstra
|
||||||
Thomas van den Berg
|
Thomas van den Berg
|
||||||
|
tuxmaster
|
||||||
vendryan
|
vendryan
|
||||||
Yaisel Hurtado
|
Yaisel Hurtado
|
||||||
ysuolmai
|
ysuolmai
|
||||||
|
@@ -1,6 +1,42 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
202x-xx-xx - Version 3.x - ""
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
changes
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
contributors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
||||||
|
2023-12-17 - Version 3.1 - "Rubric Of Ruin"
|
||||||
|
-------------------------------------------
|
||||||
|
This is the minor release of the new version 3.1.
|
||||||
|
It adds a modification of the API of the qr-method,
|
||||||
|
hence the minor release.
|
||||||
|
|
||||||
|
changes
|
||||||
|
^^^^^^^
|
||||||
|
- extend API of the qr-method to allow passing image
|
||||||
|
parameters in non-native mode
|
||||||
|
- use version 0.15 and upwards of python-barcode
|
||||||
|
- fix an issue in the config provider that prevented
|
||||||
|
config files to be found when only a path was supplied
|
||||||
|
- Improve type annotations, usage of six and other
|
||||||
|
packaging relevant parts.
|
||||||
|
|
||||||
|
contributors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
- Patrick Kanzler
|
||||||
|
- Alexandre Detiste
|
||||||
|
- tuxmaster
|
||||||
|
- belono
|
||||||
|
|
||||||
|
|
||||||
2023-11-17 - Version 3.0 - "Quietly Confident"
|
2023-11-17 - Version 3.0 - "Quietly Confident"
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
This is the major release of the new version 3.0.
|
This is the major release of the new version 3.0.
|
||||||
@@ -33,8 +69,8 @@ changes
|
|||||||
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
|
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
|
||||||
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
|
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
|
||||||
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
|
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
|
||||||
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
|
- feature: the driver tries now to guess the appropriate code page and sets it automatically (called "magic encode")
|
||||||
- as an alternative you can force the codepage with the old API
|
- as an alternative you can force the code page with the old API
|
||||||
- fix the encoding search so that lower encodings are found first
|
- fix the encoding search so that lower encodings are found first
|
||||||
- automatically handle cases where full cut or partial cut is not available
|
- automatically handle cases where full cut or partial cut is not available
|
||||||
- refactor of the set-method
|
- refactor of the set-method
|
||||||
@@ -299,14 +335,14 @@ changes
|
|||||||
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
|
- change the project's license to MIT in accordance with the contributors (see python-escpos/python-escpos#171)
|
||||||
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
|
- feature: add "capabilities" which are shared with escpos-php, capabilities are stored in
|
||||||
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
|
`escpos-printer-db <https://github.com/receipt-print-hq/escpos-printer-db>`_
|
||||||
- feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
|
- feature: the driver tries now to guess the appropriate code page and sets it automatically (called "magic encode")
|
||||||
- as an alternative you can force the codepage with the old API
|
- as an alternative you can force the code page with the old API
|
||||||
- updated and improved documentation
|
- updated and improved documentation
|
||||||
- changed constructor of main class due to introduction of capabilities
|
- changed constructor of main class due to introduction of capabilities
|
||||||
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
|
- changed interface of method `blocktext`, changed behavior of multiple methods, for details refer to the documentation
|
||||||
on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
|
on `python-escpos.readthedocs.io <https://python-escpos.readthedocs.io>`_
|
||||||
- add support for custom cash drawer sequence
|
- add support for custom cash drawer sequence
|
||||||
- enforce flake8 on the src-files, test py36 and py37 on travis
|
- enforce flake8 on the src-files, test py36 and py37 on Travis
|
||||||
|
|
||||||
contributors
|
contributors
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
@@ -339,7 +375,7 @@ contributors
|
|||||||
|
|
||||||
changes
|
changes
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
- configure readthedocs and travis
|
- configure readthedocs and Travis
|
||||||
- update doc with hint on image preprocessing
|
- update doc with hint on image preprocessing
|
||||||
- add fix for printing large images (by splitting them into multiple images)
|
- add fix for printing large images (by splitting them into multiple images)
|
||||||
|
|
||||||
@@ -404,8 +440,8 @@ changes
|
|||||||
- improve the documentation
|
- improve the documentation
|
||||||
- extend support of barcode-codes to type B
|
- extend support of barcode-codes to type B
|
||||||
- add function to disable panel-buttons
|
- add function to disable panel-buttons
|
||||||
- the text-functions are now intended for unicode, the driver will automatically encode the string based on the selected
|
- the text-functions are now intended for Unicode, the driver will automatically encode the string based on the selected
|
||||||
codepage
|
code page
|
||||||
- the image-functions are now much more flexible
|
- the image-functions are now much more flexible
|
||||||
- added a CLI
|
- added a CLI
|
||||||
- restructured the constants
|
- restructured the constants
|
||||||
@@ -443,7 +479,7 @@ contributors
|
|||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
- Merge pull request #53 from ldos/master
|
- Merge pull request #53 from ldos/master
|
||||||
- Extended params for serial printers
|
- Extended parameters for serial printers
|
||||||
- Sent by ldos <cafeteria.ldosalzira@gmail.com>
|
- Sent by ldos <cafeteria.ldosalzira@gmail.com>
|
||||||
|
|
||||||
2015-04-21 - Version 1.0.5
|
2015-04-21 - Version 1.0.5
|
||||||
|
22
README.rst
22
README.rst
@@ -59,7 +59,7 @@ Another example based on the Network printer class:
|
|||||||
|
|
||||||
from escpos.printer import Network
|
from escpos.printer import Network
|
||||||
|
|
||||||
kitchen = Network("192.168.1.100") #Printer IP Address
|
kitchen = Network("192.168.1.100", profile="TM-T88III") #Printer IP Address
|
||||||
kitchen.text("Hello World\n")
|
kitchen.text("Hello World\n")
|
||||||
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '')
|
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '')
|
||||||
kitchen.cut()
|
kitchen.cut()
|
||||||
@@ -71,18 +71,22 @@ Another example based on the Serial printer class:
|
|||||||
from escpos.printer import Serial
|
from escpos.printer import Serial
|
||||||
|
|
||||||
""" 9600 Baud, 8N1, Flow Control Enabled """
|
""" 9600 Baud, 8N1, Flow Control Enabled """
|
||||||
p = Serial(devfile='/dev/tty.usbserial',
|
p = Serial(
|
||||||
baudrate=9600,
|
devfile='/dev/tty.usbserial',
|
||||||
bytesize=8,
|
baudrate=9600,
|
||||||
parity='N',
|
bytesize=8,
|
||||||
stopbits=1,
|
parity='N',
|
||||||
timeout=1.00,
|
stopbits=1,
|
||||||
dsrdtr=True)
|
timeout=1.00,
|
||||||
|
dsrdtr=True,
|
||||||
|
profile="TM-T88III"
|
||||||
|
)
|
||||||
|
|
||||||
p.text("Hello World\n")
|
p.text("Hello World\n")
|
||||||
p.qr("You can readme from your smartphone")
|
p.qr("You can readme from your smartphone")
|
||||||
p.cut()
|
p.cut()
|
||||||
|
|
||||||
|
.. note:: It is highly recommended to include a matching profile to inform python-escpos about the printer's capabilities.
|
||||||
|
|
||||||
The full project-documentation is available on
|
The full project-documentation is available on
|
||||||
`Read the Docs <https://python-escpos.readthedocs.io>`_.
|
`Read the Docs <https://python-escpos.readthedocs.io>`_.
|
||||||
@@ -100,4 +104,4 @@ Disclaimer
|
|||||||
|
|
||||||
None of the vendors cited in this project agree or endorse any of the
|
None of the vendors cited in this project agree or endorse any of the
|
||||||
patterns or implementations.
|
patterns or implementations.
|
||||||
Its names are used only to maintain context.
|
Their names are used only to maintain context.
|
||||||
|
Submodule capabilities-data updated: 9a760e6f93...e3bf6056ee
13
doc/conf.py
13
doc/conf.py
@@ -73,7 +73,7 @@ master_doc = "index"
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = "python-escpos"
|
project = "python-escpos"
|
||||||
copyright = "2023, python-escpos developers"
|
copyright = "2024, python-escpos developers"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
@@ -134,7 +134,6 @@ else:
|
|||||||
import sphinx_rtd_theme
|
import sphinx_rtd_theme
|
||||||
|
|
||||||
html_theme = "sphinx_rtd_theme"
|
html_theme = "sphinx_rtd_theme"
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("no sphinx_rtd_theme found, switching to nature")
|
print("no sphinx_rtd_theme found, switching to nature")
|
||||||
html_theme = "default"
|
html_theme = "default"
|
||||||
@@ -144,6 +143,14 @@ else:
|
|||||||
# documentation.
|
# documentation.
|
||||||
# html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Show a 'Edit on GitHub' link instead of 'View page source'
|
||||||
|
html_context = {
|
||||||
|
"display_github": True,
|
||||||
|
"github_user": "python-escpos",
|
||||||
|
"github_repo": "python-escpos",
|
||||||
|
"github_version": "master/doc/",
|
||||||
|
}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
# html_theme_path = []
|
# html_theme_path = []
|
||||||
|
|
||||||
@@ -313,7 +320,7 @@ texinfo_documents = [
|
|||||||
# texinfo_no_detailmenu = False
|
# texinfo_no_detailmenu = False
|
||||||
|
|
||||||
# spellchecker
|
# spellchecker
|
||||||
spelling_ignore_pypi_package_names = True
|
spelling_ignore_pypi_package_names = False
|
||||||
spelling_ignore_wiki_words = True
|
spelling_ignore_wiki_words = True
|
||||||
spelling_ignore_python_builtins = True
|
spelling_ignore_python_builtins = True
|
||||||
spelling_ignore_importable_modules = True
|
spelling_ignore_importable_modules = True
|
||||||
|
@@ -5,7 +5,7 @@ Release process
|
|||||||
|
|
||||||
* Update authors file
|
* Update authors file
|
||||||
* Update changelog
|
* Update changelog
|
||||||
* Set annotated tag for release and push to public github
|
* Set annotated tag for release and push to public GitHub
|
||||||
* Build wheel
|
* Build wheel
|
||||||
* Load wheel to PyPi
|
* Load wheel to PyPi
|
||||||
* Prepare project for next release with an empty changelog entry
|
* Prepare project for next release with an empty changelog entry
|
||||||
|
@@ -2,12 +2,12 @@ pyusb
|
|||||||
Pillow>=2.0
|
Pillow>=2.0
|
||||||
qrcode>=4.0
|
qrcode>=4.0
|
||||||
pyserial
|
pyserial
|
||||||
sphinx-rtd-theme==1.3.0
|
sphinx-rtd-theme==3.0.2
|
||||||
setuptools
|
setuptools
|
||||||
setuptools-scm
|
setuptools-scm
|
||||||
docutils>=0.12
|
docutils>=0.12
|
||||||
sphinxcontrib-spelling>=7.2.0
|
sphinxcontrib-spelling>=8.0.0
|
||||||
python-barcode>=0.11.0,<1
|
python-barcode>=0.15.0,<1
|
||||||
importlib-metadata
|
importlib-metadata
|
||||||
importlib_resources
|
importlib_resources
|
||||||
sphinxcontrib.datatemplates
|
sphinxcontrib.datatemplates
|
||||||
|
@@ -68,6 +68,8 @@ Qian
|
|||||||
Lehtonen
|
Lehtonen
|
||||||
Kanzler
|
Kanzler
|
||||||
Rotondo
|
Rotondo
|
||||||
|
Alexandre
|
||||||
|
Detiste
|
||||||
|
|
||||||
barcode
|
barcode
|
||||||
barcodes
|
barcodes
|
||||||
@@ -85,13 +87,18 @@ config
|
|||||||
del
|
del
|
||||||
dev
|
dev
|
||||||
dialout
|
dialout
|
||||||
|
docstring
|
||||||
docstrings
|
docstrings
|
||||||
ean
|
ean
|
||||||
Ean
|
Ean
|
||||||
encodable
|
encodable
|
||||||
|
Errno
|
||||||
fff
|
fff
|
||||||
|
formatter
|
||||||
fullimage
|
fullimage
|
||||||
|
hw
|
||||||
io
|
io
|
||||||
|
img
|
||||||
json
|
json
|
||||||
latin
|
latin
|
||||||
libusb
|
libusb
|
||||||
@@ -101,16 +108,23 @@ natively
|
|||||||
php
|
php
|
||||||
pre
|
pre
|
||||||
prefilled
|
prefilled
|
||||||
|
preprocess
|
||||||
|
preprocessing
|
||||||
printcap
|
printcap
|
||||||
programmatically
|
programmatically
|
||||||
py
|
py
|
||||||
pypy
|
pypy
|
||||||
|
pyserial
|
||||||
|
pyusb
|
||||||
pyyaml
|
pyyaml
|
||||||
|
pywin
|
||||||
px
|
px
|
||||||
qrcode
|
qrcode
|
||||||
Raspbian
|
Raspbian
|
||||||
|
readthedocs
|
||||||
rebase
|
rebase
|
||||||
rebased
|
rebased
|
||||||
|
renderer
|
||||||
resetted
|
resetted
|
||||||
rst
|
rst
|
||||||
submodule
|
submodule
|
||||||
@@ -119,10 +133,13 @@ src
|
|||||||
testcases
|
testcases
|
||||||
th
|
th
|
||||||
Todo
|
Todo
|
||||||
|
tox
|
||||||
traceback
|
traceback
|
||||||
udev
|
udev
|
||||||
usb
|
usb
|
||||||
|
USBTimeoutError
|
||||||
usec
|
usec
|
||||||
virtualenvs
|
virtualenvs
|
||||||
|
viivakoodi
|
||||||
whitespaces
|
whitespaces
|
||||||
xml
|
xml
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
Usage
|
Usage
|
||||||
=====
|
=====
|
||||||
|
|
||||||
:Last Reviewed: 2023-08-10
|
:Last Reviewed: 2025-02-16
|
||||||
|
|
||||||
Define your printer
|
Define your printer
|
||||||
-------------------
|
-------------------
|
||||||
@@ -113,7 +113,7 @@ on a USB interface.
|
|||||||
|
|
||||||
from escpos import *
|
from escpos import *
|
||||||
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
|
||||||
p = printer.Usb(0x04b8,0x0202)
|
p = printer.Usb(0x04b8,0x0202, profile="TM-T88IV")
|
||||||
# Print text
|
# Print text
|
||||||
p.text("Hello World\n")
|
p.text("Hello World\n")
|
||||||
# Print image
|
# Print image
|
||||||
@@ -142,7 +142,7 @@ format. For windows it is probably at::
|
|||||||
|
|
||||||
%appdata%/python-escpos/config.yaml
|
%appdata%/python-escpos/config.yaml
|
||||||
|
|
||||||
And for linux::
|
And for Linux::
|
||||||
|
|
||||||
$HOME/.config/python-escpos/config.yaml
|
$HOME/.config/python-escpos/config.yaml
|
||||||
|
|
||||||
@@ -180,6 +180,7 @@ An example file printer::
|
|||||||
printer:
|
printer:
|
||||||
type: File
|
type: File
|
||||||
devfile: /dev/someprinter
|
devfile: /dev/someprinter
|
||||||
|
profile: TM-U220
|
||||||
|
|
||||||
And for a network printer::
|
And for a network printer::
|
||||||
|
|
||||||
@@ -187,6 +188,7 @@ And for a network printer::
|
|||||||
type: Network
|
type: Network
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 9000
|
port: 9000
|
||||||
|
profile: TM-U220
|
||||||
|
|
||||||
An USB-printer could be defined by::
|
An USB-printer could be defined by::
|
||||||
|
|
||||||
@@ -196,23 +198,32 @@ An USB-printer could be defined by::
|
|||||||
idProduct: 0x5678
|
idProduct: 0x5678
|
||||||
in_ep: 0x66
|
in_ep: 0x66
|
||||||
out_ep: 0x01
|
out_ep: 0x01
|
||||||
|
profile: TM-U220
|
||||||
|
|
||||||
Printing text right
|
Printing text right
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Python-escpos is designed to accept unicode.
|
Python-escpos is designed to accept Unicode.
|
||||||
|
|
||||||
For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess
|
For normal usage you can simply pass your text to the printers ``text()``-function. It will automatically guess
|
||||||
the right codepage and then send the encoded data to the printer. If this feature does not work, please try to
|
the right code page 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.
|
If you want or need to you can manually set the code page.
|
||||||
For this please use the ``charcode()``-function.
|
For this please use the ``charcode()``-function.
|
||||||
You can set any key-value that is in ``CHARCODE``.
|
You can set any key-value that is in ``CHARCODE``.
|
||||||
If something is wrong, an ``CharCodeError`` will be raised.
|
If something is wrong, an ``CharCodeError`` will be raised.
|
||||||
After you have manually set the codepage the printer won't change it anymore.
|
After you have manually set the code page the printer won't change it anymore.
|
||||||
You can revert to normal behavior by setting charcode to ``AUTO``.
|
You can revert to normal behavior by setting charcode to ``AUTO``.
|
||||||
|
|
||||||
|
Resolving bus timeout issues during printing images
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If an error message such as "USBTimeoutError: [Errno 110] Operation timed out" occurs,
|
||||||
|
setting a sleep time between printing fragments can help.
|
||||||
|
|
||||||
|
This can be done with the :meth:`.set_sleep_in_fragment()` method.
|
||||||
|
|
||||||
Advanced Usage: Print from binary blob
|
Advanced Usage: Print from binary blob
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
@@ -233,7 +244,7 @@ advantage of the fact that `_raw()` accepts binary strings.)
|
|||||||
p._raw(data)
|
p._raw(data)
|
||||||
|
|
||||||
That's all, the printer should then print your data. You can also use this technique to let others reproduce an issue
|
That's all, the printer should then print your data. You can also use this technique to let others reproduce an issue
|
||||||
that you have found. (Just "print" your commands to a File-printer on your local filesystem.)
|
that you have found. (Just "print" your commands to a File-printer on your local file system.)
|
||||||
However, please keep in mind, that often it is easier and better to just supply the code that you are using.
|
However, please keep in mind, that often it is easier and better to just supply the code that you are using.
|
||||||
|
|
||||||
Here you can download an example, that will print a set of common barcodes:
|
Here you can download an example, that will print a set of common barcodes:
|
||||||
@@ -242,8 +253,8 @@ Here you can download an example, that will print a set of common barcodes:
|
|||||||
|
|
||||||
.. _advanced-usage-change-capabilities-profile:
|
.. _advanced-usage-change-capabilities-profile:
|
||||||
|
|
||||||
Advanced Usage: change capabilities-profile
|
Advanced Usage: change where is the capabilities-profile
|
||||||
-------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
||||||
Packaged together with the escpos-code is a capabilities-file. This file in
|
Packaged together with the escpos-code is a capabilities-file. This file in
|
||||||
JSON-format describes the capabilities of different printers. It is developed and hosted in
|
JSON-format describes the capabilities of different printers. It is developed and hosted in
|
||||||
|
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from escpos import printer
|
from escpos import printer
|
||||||
from escpos.constants import (
|
from escpos.constants import (
|
||||||
CODEPAGE_CHANGE,
|
CODEPAGE_CHANGE,
|
||||||
@@ -35,10 +33,10 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
def print_codepage(printer, codepage):
|
def print_codepage(printer, codepage):
|
||||||
"""Print a codepage."""
|
"""Print a code page."""
|
||||||
if codepage.isdigit():
|
if codepage.isdigit():
|
||||||
codepage = int(codepage)
|
codepage = int(codepage)
|
||||||
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
|
printer._raw(CODEPAGE_CHANGE + bytes((codepage,)))
|
||||||
printer._raw("after")
|
printer._raw("after")
|
||||||
else:
|
else:
|
||||||
printer.charcode(codepage)
|
printer.charcode(codepage)
|
||||||
@@ -47,18 +45,20 @@ def print_codepage(printer, codepage):
|
|||||||
|
|
||||||
# Table header
|
# Table header
|
||||||
printer.set(font="b")
|
printer.set(font="b")
|
||||||
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
|
printer._raw(f" {sep.join(map(lambda s: hex(s)[2:], range(0, 16)))}\n")
|
||||||
printer.set()
|
printer.set()
|
||||||
|
|
||||||
# The table
|
# The table
|
||||||
for x in range(0, 16):
|
for x in range(0, 16):
|
||||||
# First column
|
# First column
|
||||||
printer.set(font="b")
|
printer.set(font="b")
|
||||||
printer._raw("{} ".format(hex(x)[2:]))
|
printer._raw(f"{hex(x)[2:]} ")
|
||||||
printer.set()
|
printer.set()
|
||||||
|
|
||||||
for y in range(0, 16):
|
for y in range(0, 16):
|
||||||
byte = six.int2byte(x * 16 + y)
|
byte = bytes(
|
||||||
|
(x * 16 + y),
|
||||||
|
)
|
||||||
|
|
||||||
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
|
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
|
||||||
byte = " "
|
byte = " "
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
appdirs==1.4.4
|
platformdirs==4.3.8
|
||||||
argcomplete==3.0.8
|
argcomplete==3.0.8
|
||||||
blinker==1.6.2
|
blinker==1.6.2
|
||||||
click==8.1.3
|
click==8.1.3
|
||||||
Flask==2.3.2
|
Flask==2.3.2
|
||||||
itsdangerous==2.1.2
|
itsdangerous==2.1.2
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.6
|
||||||
MarkupSafe==2.1.2
|
MarkupSafe==2.1.2
|
||||||
Pillow==10.0.1
|
Pillow==10.3.0
|
||||||
pycups==2.0.1
|
pycups==2.0.1
|
||||||
pypng==0.20220715.0
|
pypng==0.20220715.0
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
@@ -17,4 +17,4 @@ PyYAML==6.0
|
|||||||
qrcode==7.4.2
|
qrcode==7.4.2
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
typing_extensions==4.5.0
|
typing_extensions==4.5.0
|
||||||
Werkzeug==3.0.1
|
Werkzeug==3.0.6
|
25
examples/software_columns.py
Normal file
25
examples/software_columns.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
""" Example for software_columns: Print text arranged into columns."""
|
||||||
|
|
||||||
|
from escpos import printer
|
||||||
|
|
||||||
|
p = printer.Dummy(profile="TM-U220")
|
||||||
|
|
||||||
|
font = "a"
|
||||||
|
p.set(font=font)
|
||||||
|
|
||||||
|
# Default: Automatic column width given the characters per line of the printer.
|
||||||
|
text_list = ["col1", "col2", "col3"]
|
||||||
|
charsxline = p.profile.get_columns(font)
|
||||||
|
p.software_columns(text_list=text_list, widths=charsxline, align="center")
|
||||||
|
|
||||||
|
# Tuning some columns:
|
||||||
|
text_list = ["col1", "col2", "col3"]
|
||||||
|
widths = [5, 20] # col1 = 5 chars width, col2 + col3 = 20 chars width
|
||||||
|
align = ["left", "center"] # col1 = left aligned, col2 + col3 = center aligned
|
||||||
|
p.software_columns(text_list=text_list, widths=widths, align=align)
|
||||||
|
|
||||||
|
# Tuning them all:
|
||||||
|
text_list = ["col1", "col2", "col3"]
|
||||||
|
widths = [5, 20, 15]
|
||||||
|
align = ["left", "center", "right"]
|
||||||
|
p.software_columns(text_list=text_list, widths=widths, align=align)
|
@@ -18,8 +18,8 @@ import calendar
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import urllib
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from escpos.printer import Usb
|
from escpos.printer import Usb
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ def forecast(idx):
|
|||||||
printer.text(" high " + str(hi))
|
printer.text(" high " + str(hi))
|
||||||
printer.text(deg)
|
printer.text(deg)
|
||||||
printer.text("\n")
|
printer.text("\n")
|
||||||
# take care of pesky unicode dash
|
# take care of pesky Unicode dash
|
||||||
printer.text(cond.replace("\u2013", "-").encode("utf-8"))
|
printer.text(cond.replace("\u2013", "-").encode("utf-8"))
|
||||||
printer.text("\n \n")
|
printer.text("\n \n")
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ url = (
|
|||||||
+ LONG
|
+ LONG
|
||||||
+ "?exclude=[alerts,minutely,hourly,flags]&units=si"
|
+ "?exclude=[alerts,minutely,hourly,flags]&units=si"
|
||||||
) # change last bit to 'us' for Fahrenheit
|
) # change last bit to 'us' for Fahrenheit
|
||||||
response = urllib.urlopen(url)
|
response = urlopen(url)
|
||||||
data = json.loads(response.read())
|
data = json.loads(response.read())
|
||||||
|
|
||||||
printer.print_and_feed(n=1)
|
printer.print_and_feed(n=1)
|
||||||
|
@@ -3,6 +3,7 @@ name = python-escpos
|
|||||||
url = https://github.com/python-escpos/python-escpos
|
url = https://github.com/python-escpos/python-escpos
|
||||||
description = Python library to manipulate ESC/POS Printers
|
description = Python library to manipulate ESC/POS Printers
|
||||||
long_description = file: README.rst
|
long_description = file: README.rst
|
||||||
|
long_description_content_type = text/x-rst
|
||||||
license = MIT
|
license = MIT
|
||||||
license_file = LICENSE
|
license_file = LICENSE
|
||||||
author = python-escpos developers
|
author = python-escpos developers
|
||||||
@@ -23,6 +24,7 @@ classifiers =
|
|||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
Programming Language :: Python :: 3.11
|
Programming Language :: Python :: 3.11
|
||||||
Programming Language :: Python :: 3.12
|
Programming Language :: Python :: 3.12
|
||||||
|
Programming Language :: Python :: 3.13
|
||||||
Programming Language :: Python :: Implementation :: CPython
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
Topic :: Software Development :: Libraries :: Python Modules
|
Topic :: Software Development :: Libraries :: Python Modules
|
||||||
Topic :: Office/Business :: Financial :: Point-Of-Sale
|
Topic :: Office/Business :: Financial :: Point-Of-Sale
|
||||||
@@ -38,12 +40,11 @@ include_package_data = true
|
|||||||
install_requires =
|
install_requires =
|
||||||
Pillow>=2.0
|
Pillow>=2.0
|
||||||
qrcode>=4.0
|
qrcode>=4.0
|
||||||
python-barcode>=0.9.1,<1
|
python-barcode>=0.15.0,<1
|
||||||
setuptools
|
setuptools
|
||||||
six
|
six
|
||||||
appdirs
|
platformdirs
|
||||||
PyYAML
|
PyYAML
|
||||||
argparse
|
|
||||||
argcomplete
|
argcomplete
|
||||||
importlib_resources
|
importlib_resources
|
||||||
setup_requires = setuptools_scm
|
setup_requires = setuptools_scm
|
||||||
@@ -75,6 +76,6 @@ all =
|
|||||||
pywin32; platform_system=='Windows'
|
pywin32; platform_system=='Windows'
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
|
exclude = .git,.venv,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
extend-ignore = E203, W503
|
extend-ignore = E203, W503
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""python-escpos enables you to manipulate escpos-printers."""
|
"""python-escpos enables you to manipulate escpos-printers."""
|
||||||
|
|
||||||
__all__ = ["constants", "escpos", "exceptions", "printer"]
|
__all__ = ["constants", "escpos", "exceptions", "printer", "__version__"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .version import version as __version__ # noqa
|
from .version import version as __version__ # noqa
|
||||||
|
@@ -11,16 +11,14 @@ from tempfile import mkdtemp
|
|||||||
from typing import Any, Dict, Optional, Type
|
from typing import Any, Dict, Optional, Type
|
||||||
|
|
||||||
import importlib_resources
|
import importlib_resources
|
||||||
import six
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
logging.basicConfig()
|
if environ.get("ESCPOS_CAPABILITIES_DEBUG", 0):
|
||||||
logger = logging.getLogger(__name__)
|
logging.basicConfig()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", mkdtemp())
|
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", mkdtemp())
|
||||||
pickle_path = path.join(
|
pickle_path = path.join(pickle_dir, f"{platform.python_version()}.capabilities.pickle")
|
||||||
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
|
|
||||||
)
|
|
||||||
# get a temporary file from importlib_resources if no file is specified in env
|
# get a temporary file from importlib_resources if no file is specified in env
|
||||||
file_manager = ExitStack()
|
file_manager = ExitStack()
|
||||||
atexit.register(file_manager.close)
|
atexit.register(file_manager.close)
|
||||||
@@ -94,7 +92,7 @@ class NotSupported(Exception):
|
|||||||
BARCODE_B = "barcodeB"
|
BARCODE_B = "barcodeB"
|
||||||
|
|
||||||
|
|
||||||
class BaseProfile(object):
|
class BaseProfile:
|
||||||
"""This represents a printer profile.
|
"""This represents a printer profile.
|
||||||
|
|
||||||
A printer profile knows about the number of columns, supported
|
A printer profile knows about the number of columns, supported
|
||||||
@@ -113,22 +111,22 @@ class BaseProfile(object):
|
|||||||
Makes sure that the requested `font` is valid.
|
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:
|
if not str(font) in self.fonts:
|
||||||
raise NotSupported(
|
raise NotSupported(f'"{font}" is not a valid font in the current profile')
|
||||||
'"{}" is not a valid font in the current profile'.format(font)
|
|
||||||
)
|
|
||||||
return font
|
return font
|
||||||
|
|
||||||
def get_columns(self, font):
|
def get_columns(self, font) -> int:
|
||||||
"""Return the number of columns for the given font."""
|
"""Return the number of columns for the given font."""
|
||||||
font = self.get_font(font)
|
font = self.get_font(font)
|
||||||
return self.fonts[six.text_type(font)]["columns"]
|
columns = self.fonts[str(font)]["columns"]
|
||||||
|
assert type(columns) is int
|
||||||
|
return columns
|
||||||
|
|
||||||
def supports(self, feature):
|
def supports(self, feature) -> bool:
|
||||||
"""Return true/false for the given feature."""
|
"""Return true/false for the given feature."""
|
||||||
return self.features.get(feature)
|
return self.features.get(feature)
|
||||||
|
|
||||||
def get_code_pages(self):
|
def get_code_pages(self) -> Dict[str, int]:
|
||||||
"""Return the support code pages as a ``{name: index}`` dict."""
|
"""Return the support code pages as a ``{name: index}`` dict."""
|
||||||
return {v: k for k, v in self.codePages.items()}
|
return {v: k for k, v in self.codePages.items()}
|
||||||
|
|
||||||
@@ -158,14 +156,14 @@ def get_profile_class(name: str) -> Type[BaseProfile]:
|
|||||||
profiles: Dict[str, Any] = CAPABILITIES["profiles"]
|
profiles: Dict[str, Any] = CAPABILITIES["profiles"]
|
||||||
profile_data = profiles[name]
|
profile_data = profiles[name]
|
||||||
profile_name = clean(name)
|
profile_name = clean(name)
|
||||||
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
|
class_name = f"{profile_name[0].upper()}{profile_name[1:]}Profile"
|
||||||
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
|
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
|
||||||
CLASS_CACHE[name] = new_class
|
CLASS_CACHE[name] = new_class
|
||||||
|
|
||||||
return CLASS_CACHE[name]
|
return CLASS_CACHE[name]
|
||||||
|
|
||||||
|
|
||||||
def clean(s):
|
def clean(s: str) -> str:
|
||||||
"""Clean profile name."""
|
"""Clean profile name."""
|
||||||
# Remove invalid characters
|
# Remove invalid characters
|
||||||
s = re.sub("[^0-9a-zA-Z_]", "", s)
|
s = re.sub("[^0-9a-zA-Z_]", "", s)
|
||||||
@@ -184,14 +182,14 @@ class Profile(ProfileBaseClass):
|
|||||||
For users, who want to provide their own profile.
|
For users, who want to provide their own profile.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, columns=None, features=None):
|
def __init__(self, columns: Optional[int] = None, features=None) -> None:
|
||||||
"""Initialize profile."""
|
"""Initialize profile."""
|
||||||
super(Profile, self).__init__()
|
super(Profile, self).__init__()
|
||||||
|
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
self.features = features or {}
|
self.features = features or {}
|
||||||
|
|
||||||
def get_columns(self, font):
|
def get_columns(self, font) -> int:
|
||||||
"""Get column count of printer."""
|
"""Get column count of printer."""
|
||||||
if self.columns is not None:
|
if self.columns is not None:
|
||||||
return self.columns
|
return self.columns
|
||||||
|
@@ -20,15 +20,13 @@ except ImportError:
|
|||||||
pass # noqa
|
pass # noqa
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import six
|
from . import config, escpos
|
||||||
|
|
||||||
from . import config
|
|
||||||
from . import printer as escpos_printer_module
|
from . import printer as escpos_printer_module
|
||||||
from . import version
|
from . import version
|
||||||
|
|
||||||
|
|
||||||
# Must be defined before it's used in DEMO_FUNCTIONS
|
# Must be defined before it's used in DEMO_FUNCTIONS
|
||||||
def str_to_bool(string):
|
def str_to_bool(string: str) -> bool:
|
||||||
"""Convert string to bool.
|
"""Convert string to bool.
|
||||||
|
|
||||||
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
|
||||||
@@ -211,6 +209,38 @@ ESCPOS_COMMANDS: List[Dict[str, Any]] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"parser": {
|
||||||
|
"name": "software_columns",
|
||||||
|
"help": "Print a list of texts arranged into columns",
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"func": "software_columns",
|
||||||
|
},
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"option_strings": ("--text_list",),
|
||||||
|
"help": "list of texts to print",
|
||||||
|
"nargs": "+",
|
||||||
|
"type": str,
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"option_strings": ("--widths",),
|
||||||
|
"help": "list of column widths",
|
||||||
|
"nargs": "+",
|
||||||
|
"type": int,
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"option_strings": ("--align",),
|
||||||
|
"help": "list of column alignments",
|
||||||
|
"nargs": "+",
|
||||||
|
"type": str,
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"parser": {
|
"parser": {
|
||||||
"name": "cut",
|
"name": "cut",
|
||||||
@@ -343,7 +373,7 @@ ESCPOS_COMMANDS: List[Dict[str, Any]] = [
|
|||||||
{
|
{
|
||||||
"option_strings": ("--font",),
|
"option_strings": ("--font",),
|
||||||
"help": "Font choice",
|
"help": "Font choice",
|
||||||
"choices": ["left", "center", "right"],
|
"choices": ["A", "B"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"option_strings": ("--text_type",),
|
"option_strings": ("--text_type",),
|
||||||
@@ -492,7 +522,7 @@ def generate_parser() -> argparse.ArgumentParser:
|
|||||||
"""Generate an argparse parser."""
|
"""Generate an argparse parser."""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="CLI for python-escpos",
|
description="CLI for python-escpos",
|
||||||
epilog="Printer configuration is defined in the python-escpos config"
|
epilog="Printer configuration is defined in the python-escpos configuration "
|
||||||
"file. See documentation for details.",
|
"file. See documentation for details.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -563,7 +593,7 @@ def generate_parser() -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Handle main entry point of CLI script.
|
"""Handle main entry point of CLI script.
|
||||||
|
|
||||||
Handles loading of configuration and creating and processing of command
|
Handles loading of configuration and creating and processing of command
|
||||||
@@ -580,9 +610,7 @@ def main():
|
|||||||
if not args_dict:
|
if not args_dict:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
command_arguments = dict(
|
command_arguments = dict([k, v] for k, v in args_dict.items() if v is not None)
|
||||||
[k, v] for k, v in six.iteritems(args_dict) if v is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
# If version should be printed, do this, then exit
|
# If version should be printed, do this, then exit
|
||||||
print_version = command_arguments.pop("version", None)
|
print_version = command_arguments.pop("version", None)
|
||||||
@@ -621,7 +649,7 @@ def main():
|
|||||||
globals()[target_command](**command_arguments)
|
globals()[target_command](**command_arguments)
|
||||||
|
|
||||||
|
|
||||||
def demo(printer, **kwargs):
|
def demo(printer: escpos.Escpos, **kwargs) -> None:
|
||||||
"""Print demos.
|
"""Print demos.
|
||||||
|
|
||||||
Called when CLI is passed `demo`. This function
|
Called when CLI is passed `demo`. This function
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
"""Helper module for codepage handling."""
|
"""Helper module for code page handling."""
|
||||||
from .capabilities import CAPABILITIES
|
from .capabilities import CAPABILITIES
|
||||||
|
|
||||||
|
|
||||||
@@ -9,13 +9,9 @@ class CodePageManager:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
"""Initialize codepage manager."""
|
"""Initialize code page manager."""
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
"""Get list of all codepages."""
|
|
||||||
return self.data.values()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_encoding_name(encoding):
|
def get_encoding_name(encoding):
|
||||||
"""Get encoding name.
|
"""Get encoding name.
|
||||||
|
@@ -3,14 +3,15 @@
|
|||||||
This module contains the implementations of abstract base class :py:class:`Config`.
|
This module contains the implementations of abstract base class :py:class:`Config`.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
import appdirs
|
import platformdirs
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from . import exceptions, printer
|
from . import exceptions, printer
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config:
|
||||||
"""Configuration handler class.
|
"""Configuration handler class.
|
||||||
|
|
||||||
This class loads configuration from a default or specified directory. It
|
This class loads configuration from a default or specified directory. It
|
||||||
@@ -20,7 +21,7 @@ class Config(object):
|
|||||||
_app_name = "python-escpos"
|
_app_name = "python-escpos"
|
||||||
_config_file = "config.yaml"
|
_config_file = "config.yaml"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
"""Initialize configuration.
|
"""Initialize configuration.
|
||||||
|
|
||||||
Remember to add anything that needs to be reset between configurations
|
Remember to add anything that needs to be reset between configurations
|
||||||
@@ -32,7 +33,7 @@ class Config(object):
|
|||||||
self._printer_name = None
|
self._printer_name = None
|
||||||
self._printer_config = None
|
self._printer_config = None
|
||||||
|
|
||||||
def _reset_config(self):
|
def _reset_config(self) -> None:
|
||||||
"""Clear the loaded configuration.
|
"""Clear the loaded configuration.
|
||||||
|
|
||||||
If we are loading a changed config, we don't want to have leftover
|
If we are loading a changed config, we don't want to have leftover
|
||||||
@@ -54,38 +55,43 @@ class Config(object):
|
|||||||
|
|
||||||
if not config_path:
|
if not config_path:
|
||||||
config_path = os.path.join(
|
config_path = os.path.join(
|
||||||
appdirs.user_config_dir(self._app_name), self._config_file
|
platformdirs.user_config_dir(self._app_name), self._config_file
|
||||||
)
|
)
|
||||||
|
if isinstance(config_path, pathlib.Path):
|
||||||
|
# store string if posixpath
|
||||||
|
config_path = config_path.as_posix()
|
||||||
|
if not os.path.isfile(config_path):
|
||||||
|
# supplied path is not a file --> assume default file
|
||||||
|
config_path = os.path.join(config_path, self._config_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First check if it's file like. If it is, pyyaml can load it.
|
with open(config_path, "rb") as config_file:
|
||||||
# I'm checking type instead of catching exceptions to keep the
|
config = yaml.safe_load(config_file)
|
||||||
# exception handling simple
|
|
||||||
if hasattr(config_path, "read"):
|
|
||||||
config = yaml.safe_load(config_path)
|
|
||||||
else:
|
|
||||||
# If it isn't, it's a path. We have to open it first, otherwise
|
|
||||||
# pyyaml will try to read it as yaml
|
|
||||||
with open(config_path, "rb") as config_file:
|
|
||||||
config = yaml.safe_load(config_file)
|
|
||||||
except EnvironmentError:
|
except EnvironmentError:
|
||||||
raise exceptions.ConfigNotFoundError(
|
raise exceptions.ConfigNotFoundError(
|
||||||
"Couldn't read config at {config_path}".format(
|
f"Couldn't read config at {config_path}"
|
||||||
config_path=str(config_path),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
raise exceptions.ConfigSyntaxError("Error parsing YAML")
|
raise exceptions.ConfigSyntaxError("Error parsing YAML")
|
||||||
|
|
||||||
if "printer" in config:
|
if "printer" in config:
|
||||||
self._printer_config = config["printer"]
|
self._printer_config = config["printer"]
|
||||||
self._printer_name = self._printer_config.pop("type").title()
|
printer_name = self._printer_config.pop("type")
|
||||||
|
class_names = {
|
||||||
|
"usb": "Usb",
|
||||||
|
"serial": "Serial",
|
||||||
|
"network": "Network",
|
||||||
|
"file": "File",
|
||||||
|
"dummy": "Dummy",
|
||||||
|
"cupsprinter": "CupsPrinter",
|
||||||
|
"lp": "LP",
|
||||||
|
"win32raw": "Win32Raw",
|
||||||
|
}
|
||||||
|
self._printer_name = class_names.get(printer_name.lower(), printer_name)
|
||||||
|
|
||||||
if not self._printer_name or not hasattr(printer, self._printer_name):
|
if not self._printer_name or not hasattr(printer, self._printer_name):
|
||||||
raise exceptions.ConfigSyntaxError(
|
raise exceptions.ConfigSyntaxError(
|
||||||
'Printer type "{printer_name}" is invalid'.format(
|
f'Printer type "{self._printer_name}" is invalid'
|
||||||
printer_name=self._printer_name,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._has_loaded = True
|
self._has_loaded = True
|
||||||
|
@@ -46,9 +46,7 @@ HW_RESET: bytes = ESC + b"\x3f\x0a\x00" # Reset printer hardware
|
|||||||
# (TODO: Where is this specified?)
|
# (TODO: Where is this specified?)
|
||||||
|
|
||||||
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
|
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
|
||||||
_CASH_DRAWER = (
|
_CASH_DRAWER = lambda m, t1="", t2="": ESC + b"p" + m + bytes((t1, t2))
|
||||||
lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2)
|
|
||||||
)
|
|
||||||
|
|
||||||
#: decimal cash drawer kick sequence
|
#: decimal cash drawer kick sequence
|
||||||
CD_KICK_DEC_SEQUENCE = (
|
CD_KICK_DEC_SEQUENCE = (
|
||||||
@@ -190,8 +188,8 @@ LINESPACING_FUNCS: Dict[int, bytes] = {
|
|||||||
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
|
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
|
||||||
}
|
}
|
||||||
|
|
||||||
#: Prefix to change the codepage. You need to attach a byte to indicate
|
#: Prefix to change the code page. You need to attach a byte to indicate
|
||||||
#: the codepage to use. We use escpos-printer-db as the data source.
|
#: the code page to use. We use escpos-printer-db as the data source.
|
||||||
CODEPAGE_CHANGE: bytes = ESC + b"\x74"
|
CODEPAGE_CHANGE: bytes = ESC + b"\x74"
|
||||||
|
|
||||||
# Barcode format
|
# Barcode format
|
||||||
|
@@ -9,12 +9,16 @@ This module contains the abstract base class :py:class:`Escpos`.
|
|||||||
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
|
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
|
||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import time
|
||||||
|
import warnings
|
||||||
from abc import ABCMeta, abstractmethod # abstract base class support
|
from abc import ABCMeta, abstractmethod # abstract base class support
|
||||||
from re import match as re_match
|
from re import match as re_match
|
||||||
from typing import List, Literal, Optional, Union
|
from types import TracebackType
|
||||||
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
import barcode
|
import barcode
|
||||||
import qrcode
|
import qrcode
|
||||||
@@ -105,9 +109,10 @@ SW_BARCODE_NAMES = {
|
|||||||
for name in barcode.PROVIDED_BARCODES
|
for name in barcode.PROVIDED_BARCODES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Alignment = Union[Literal["center", "left", "right", "justify"], str]
|
||||||
|
|
||||||
@six.add_metaclass(ABCMeta)
|
|
||||||
class Escpos(object):
|
class Escpos(object, metaclass=ABCMeta):
|
||||||
"""ESC/POS Printer object.
|
"""ESC/POS Printer object.
|
||||||
|
|
||||||
This class is the abstract base class for an Esc/Pos-printer. The printer implementations are children of this
|
This class is the abstract base class for an Esc/Pos-printer. The printer implementations are children of this
|
||||||
@@ -120,6 +125,9 @@ class Escpos(object):
|
|||||||
# object -> The connection object (Usb(), Serial(), Network(), etc.)
|
# object -> The connection object (Usb(), Serial(), Network(), etc.)
|
||||||
_device: Union[Literal[False], Literal[None], object] = False
|
_device: Union[Literal[False], Literal[None], object] = False
|
||||||
|
|
||||||
|
# sleep time in fragments:
|
||||||
|
_sleep_in_fragment_ms: int = 0
|
||||||
|
|
||||||
def __init__(self, profile=None, magic_encode_args=None, **kwargs) -> None:
|
def __init__(self, profile=None, magic_encode_args=None, **kwargs) -> None:
|
||||||
"""Initialize ESCPOS Printer.
|
"""Initialize ESCPOS Printer.
|
||||||
|
|
||||||
@@ -153,6 +161,10 @@ class Escpos(object):
|
|||||||
"""Open a printer device/connection."""
|
"""Open a printer device/connection."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close a printer device/connection."""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _raw(self, msg: bytes) -> None:
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Send raw data to the printer.
|
"""Send raw data to the printer.
|
||||||
@@ -163,7 +175,7 @@ class Escpos(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _read(self):
|
def _read(self) -> bytes:
|
||||||
"""Read from printer.
|
"""Read from printer.
|
||||||
|
|
||||||
Returns a NotImplementedError if the instance of the class doesn't override this method.
|
Returns a NotImplementedError if the instance of the class doesn't override this method.
|
||||||
@@ -171,14 +183,29 @@ class Escpos(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def set_sleep_in_fragment(self, sleep_time_ms: int) -> None:
|
||||||
|
"""Configures the currently active sleep time after sending a fragment.
|
||||||
|
|
||||||
|
If during printing an image an issue like "USBTimeoutError: [Errno 110]
|
||||||
|
Operation timed out" occurs, setting this value to roughly 300
|
||||||
|
milliseconds can help resolve the issue.
|
||||||
|
|
||||||
|
:param sleep_time_ms: sleep time in milliseconds
|
||||||
|
"""
|
||||||
|
self._sleep_in_fragment_ms = sleep_time_ms
|
||||||
|
|
||||||
|
def _sleep_in_fragment(self) -> None:
|
||||||
|
"""Sleeps the preconfigured time after sending a fragment."""
|
||||||
|
time.sleep(self._sleep_in_fragment_ms / 1000)
|
||||||
|
|
||||||
def image(
|
def image(
|
||||||
self,
|
self,
|
||||||
img_source,
|
img_source,
|
||||||
high_density_vertical=True,
|
high_density_vertical: bool = True,
|
||||||
high_density_horizontal=True,
|
high_density_horizontal: bool = True,
|
||||||
impl="bitImageRaster",
|
impl: str = "bitImageRaster",
|
||||||
fragment_height=960,
|
fragment_height: int = 960,
|
||||||
center=False,
|
center: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Print an image.
|
"""Print an image.
|
||||||
|
|
||||||
@@ -218,7 +245,7 @@ class Escpos(object):
|
|||||||
max_width = int(self.profile.profile_data["media"]["width"]["pixels"])
|
max_width = int(self.profile.profile_data["media"]["width"]["pixels"])
|
||||||
|
|
||||||
if im.width > max_width:
|
if im.width > max_width:
|
||||||
raise ImageWidthError("{} > {}".format(im.width, max_width))
|
raise ImageWidthError(f"{im.width} > {max_width}")
|
||||||
|
|
||||||
if center:
|
if center:
|
||||||
im.center(max_width)
|
im.center(max_width)
|
||||||
@@ -239,6 +266,7 @@ class Escpos(object):
|
|||||||
impl=impl,
|
impl=impl,
|
||||||
fragment_height=fragment_height,
|
fragment_height=fragment_height,
|
||||||
)
|
)
|
||||||
|
self._sleep_in_fragment()
|
||||||
return
|
return
|
||||||
|
|
||||||
if impl == "bitImageRaster":
|
if impl == "bitImageRaster":
|
||||||
@@ -249,7 +277,7 @@ class Escpos(object):
|
|||||||
header = (
|
header = (
|
||||||
GS
|
GS
|
||||||
+ b"v0"
|
+ b"v0"
|
||||||
+ six.int2byte(density_byte)
|
+ bytes((density_byte,))
|
||||||
+ self._int_low_high(im.width_bytes, 2)
|
+ self._int_low_high(im.width_bytes, 2)
|
||||||
+ self._int_low_high(im.height, 2)
|
+ self._int_low_high(im.height, 2)
|
||||||
)
|
)
|
||||||
@@ -262,8 +290,8 @@ class Escpos(object):
|
|||||||
)
|
)
|
||||||
tone = b"0"
|
tone = b"0"
|
||||||
colors = b"1"
|
colors = b"1"
|
||||||
ym = six.int2byte(1 if high_density_vertical else 2)
|
ym = b"\x01" if high_density_vertical else b"\x02"
|
||||||
xm = six.int2byte(1 if high_density_horizontal else 2)
|
xm = b"\x01" if high_density_horizontal else b"\x02"
|
||||||
header = tone + xm + ym + colors + img_header
|
header = tone + xm + ym + colors + img_header
|
||||||
raster_data = im.to_raster_format()
|
raster_data = im.to_raster_format()
|
||||||
self._image_send_graphics_data(b"0", b"p", header + raster_data)
|
self._image_send_graphics_data(b"0", b"p", header + raster_data)
|
||||||
@@ -304,7 +332,8 @@ class Escpos(object):
|
|||||||
model=QR_MODEL_2,
|
model=QR_MODEL_2,
|
||||||
native=False,
|
native=False,
|
||||||
center=False,
|
center=False,
|
||||||
impl="bitImageRaster",
|
impl=None,
|
||||||
|
image_arguments: Optional[dict] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Print QR Code for the provided string.
|
"""Print QR Code for the provided string.
|
||||||
|
|
||||||
@@ -319,6 +348,8 @@ class Escpos(object):
|
|||||||
printer (Default)
|
printer (Default)
|
||||||
:param center: Centers the code *default:* False
|
:param center: Centers the code *default:* False
|
||||||
:param impl: Image-printing-implementation, refer to :meth:`.image()` for details
|
:param impl: Image-printing-implementation, refer to :meth:`.image()` for details
|
||||||
|
:param image_arguments: arguments passed to :meth:`.image()`.
|
||||||
|
Replaces `impl` and `center`. If `impl` or `center` are set, they will overwrite `image_arguments`.
|
||||||
"""
|
"""
|
||||||
# Basic validation
|
# Basic validation
|
||||||
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
|
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
|
||||||
@@ -333,6 +364,19 @@ class Escpos(object):
|
|||||||
# Handle edge case by printing nothing.
|
# Handle edge case by printing nothing.
|
||||||
return
|
return
|
||||||
if not native:
|
if not native:
|
||||||
|
# impl is deprecated in favor of image_arguments
|
||||||
|
if impl:
|
||||||
|
warnings.warn(
|
||||||
|
"Parameter impl is deprecated in favor of image_arguments and will be dropped in a future release.",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
# assemble arguments for image
|
||||||
|
if not image_arguments:
|
||||||
|
image_arguments = {}
|
||||||
|
if impl:
|
||||||
|
image_arguments["impl"] = impl
|
||||||
|
if "center" not in image_arguments:
|
||||||
|
image_arguments["center"] = center
|
||||||
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
|
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
|
||||||
if model != QR_MODEL_2:
|
if model != QR_MODEL_2:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -354,7 +398,7 @@ class Escpos(object):
|
|||||||
|
|
||||||
# Convert the RGB image in printable image
|
# Convert the RGB image in printable image
|
||||||
self.text("\n")
|
self.text("\n")
|
||||||
self.image(im, center=center, impl=impl)
|
self.image(im, **image_arguments)
|
||||||
self.text("\n")
|
self.text("\n")
|
||||||
self.text("\n")
|
self.text("\n")
|
||||||
return
|
return
|
||||||
@@ -403,9 +447,7 @@ class Escpos(object):
|
|||||||
raise ValueError("Can only output 1-4 bytes")
|
raise ValueError("Can only output 1-4 bytes")
|
||||||
if not 0 <= inp_number <= max_input:
|
if not 0 <= inp_number <= max_input:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Number too large. Can only output up to {0} in {1} bytes".format(
|
f"Number too large. Can only output up to {max_input} in {out_bytes} bytes"
|
||||||
max_input, out_bytes
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
outp = b""
|
outp = b""
|
||||||
for _ in range(0, out_bytes):
|
for _ in range(0, out_bytes):
|
||||||
@@ -419,7 +461,7 @@ class Escpos(object):
|
|||||||
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active.
|
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active.
|
||||||
It will be sent with the next text sequence.
|
It will be sent with the next text sequence.
|
||||||
If you set the variable code to ``AUTO`` it will try to automatically guess the
|
If you set the variable code to ``AUTO`` it will try to automatically guess the
|
||||||
right codepage.
|
right code page.
|
||||||
(This is the standard behavior.)
|
(This is the standard behavior.)
|
||||||
|
|
||||||
:param code: Name of CharCode
|
:param code: Name of CharCode
|
||||||
@@ -694,21 +736,15 @@ class Escpos(object):
|
|||||||
if not function_type or not BARCODE_TYPES.get(function_type.upper()):
|
if not function_type or not BARCODE_TYPES.get(function_type.upper()):
|
||||||
raise BarcodeTypeError(
|
raise BarcodeTypeError(
|
||||||
(
|
(
|
||||||
"Barcode '{bc}' not valid for barcode function type "
|
f"Barcode '{bc}' not valid for barcode function type "
|
||||||
"{function_type}"
|
f"{function_type}"
|
||||||
).format(
|
|
||||||
bc=bc,
|
|
||||||
function_type=function_type,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||||
|
|
||||||
if check and not self.check_barcode(bc, code):
|
if check and not self.check_barcode(bc, code):
|
||||||
raise BarcodeCodeError(
|
raise BarcodeCodeError(
|
||||||
("Barcode '{code}' not in a valid format for type '{bc}'").format(
|
f"Barcode '{code}' not in a valid format for type '{bc}'"
|
||||||
code=code,
|
|
||||||
bc=bc,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Align Bar Code()
|
# Align Bar Code()
|
||||||
@@ -718,12 +754,12 @@ class Escpos(object):
|
|||||||
if 1 <= height <= 255:
|
if 1 <= height <= 255:
|
||||||
self._raw(BARCODE_HEIGHT + six.int2byte(height))
|
self._raw(BARCODE_HEIGHT + six.int2byte(height))
|
||||||
else:
|
else:
|
||||||
raise BarcodeSizeError("height = {height}".format(height=height))
|
raise BarcodeSizeError(f"height = {height}")
|
||||||
# Width
|
# Width
|
||||||
if 2 <= width <= 6:
|
if 2 <= width <= 6:
|
||||||
self._raw(BARCODE_WIDTH + six.int2byte(width))
|
self._raw(BARCODE_WIDTH + six.int2byte(width))
|
||||||
else:
|
else:
|
||||||
raise BarcodeSizeError("width = {width}".format(width=width))
|
raise BarcodeSizeError(f"width = {width}")
|
||||||
# Font
|
# Font
|
||||||
if font.upper() == "B":
|
if font.upper() == "B":
|
||||||
self._raw(BARCODE_FONT_B)
|
self._raw(BARCODE_FONT_B)
|
||||||
@@ -816,9 +852,7 @@ class Escpos(object):
|
|||||||
# Check if barcode type exists
|
# Check if barcode type exists
|
||||||
if barcode_type not in barcode.PROVIDED_BARCODES:
|
if barcode_type not in barcode.PROVIDED_BARCODES:
|
||||||
raise BarcodeTypeError(
|
raise BarcodeTypeError(
|
||||||
"Barcode type {} not supported by software barcode renderer".format(
|
f"Barcode type {barcode_type} not supported by software barcode renderer"
|
||||||
barcode_type
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Render the barcode
|
# Render the barcode
|
||||||
@@ -840,30 +874,29 @@ class Escpos(object):
|
|||||||
image = my_code.writer._image
|
image = my_code.writer._image
|
||||||
self.image(image, impl=impl, center=center)
|
self.image(image, impl=impl, center=center)
|
||||||
|
|
||||||
def text(self, txt):
|
def text(self, txt: str) -> None:
|
||||||
"""Print alpha-numeric text.
|
"""Print alpha-numeric text.
|
||||||
|
|
||||||
The text has to be encoded in the currently selected codepage.
|
The text has to be encoded in the currently selected code page.
|
||||||
The input text has to be encoded in unicode.
|
The input text has to be encoded in Unicode.
|
||||||
|
|
||||||
:param txt: text to be printed
|
:param txt: text to be printed
|
||||||
:raises: :py:exc:`~escpos.exceptions.TextError`
|
:raises: :py:exc:`~escpos.exceptions.TextError`
|
||||||
"""
|
"""
|
||||||
txt = six.text_type(txt)
|
self.magic.write(str(txt))
|
||||||
self.magic.write(txt)
|
|
||||||
|
|
||||||
def textln(self, txt=""):
|
def textln(self, txt: str = "") -> None:
|
||||||
"""Print alpha-numeric text with a newline.
|
"""Print alpha-numeric text with a newline.
|
||||||
|
|
||||||
The text has to be encoded in the currently selected codepage.
|
The text has to be encoded in the currently selected code page.
|
||||||
The input text has to be encoded in unicode.
|
The input text has to be encoded in Unicode.
|
||||||
|
|
||||||
:param txt: text to be printed with a newline
|
:param txt: text to be printed with a newline
|
||||||
:raises: :py:exc:`~escpos.exceptions.TextError`
|
:raises: :py:exc:`~escpos.exceptions.TextError`
|
||||||
"""
|
"""
|
||||||
self.text("{}\n".format(txt))
|
self.text(f"{txt}\n")
|
||||||
|
|
||||||
def ln(self, count=1):
|
def ln(self, count: int = 1) -> None:
|
||||||
"""Print a newline or more.
|
"""Print a newline or more.
|
||||||
|
|
||||||
:param count: number of newlines to print
|
:param count: number of newlines to print
|
||||||
@@ -874,10 +907,10 @@ class Escpos(object):
|
|||||||
if count > 0:
|
if count > 0:
|
||||||
self.text("\n" * count)
|
self.text("\n" * count)
|
||||||
|
|
||||||
def block_text(self, txt, font="0", columns=None):
|
def block_text(self, txt, font="0", columns=None) -> None:
|
||||||
"""Print text wrapped to specific columns.
|
"""Print text wrapped to specific columns.
|
||||||
|
|
||||||
Text has to be encoded in unicode.
|
Text has to be encoded in Unicode.
|
||||||
|
|
||||||
:param txt: text to be printed
|
:param txt: text to be printed
|
||||||
:param font: font to be used, can be :code:`a` or :code:`b`
|
:param font: font to be used, can be :code:`a` or :code:`b`
|
||||||
@@ -887,6 +920,136 @@ class Escpos(object):
|
|||||||
col_count = self.profile.get_columns(font) if columns is None else columns
|
col_count = self.profile.get_columns(font) if columns is None else columns
|
||||||
self.text(textwrap.fill(txt, col_count))
|
self.text(textwrap.fill(txt, col_count))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _justify(txt: str, width: int) -> str:
|
||||||
|
"""Justify-text on left AND right sides by padding spaces.
|
||||||
|
|
||||||
|
code by: Georgina Skibinski https://stackoverflow.com/a/66087666
|
||||||
|
suggested by agordon @https://github.com/python-escpos/python-escpos/pull/652
|
||||||
|
"""
|
||||||
|
prev_txt = txt
|
||||||
|
while (length := width - len(txt)) > 0:
|
||||||
|
txt = re.sub(r"(\s+)", r"\1 ", txt, count=length)
|
||||||
|
if txt == prev_txt:
|
||||||
|
break
|
||||||
|
return txt.rjust(width)
|
||||||
|
|
||||||
|
def _padding(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
width: int,
|
||||||
|
align: Alignment = "center",
|
||||||
|
) -> str:
|
||||||
|
"""Add fill space to meet the width.
|
||||||
|
|
||||||
|
The align parameter sets the alignment of the text in space.
|
||||||
|
"""
|
||||||
|
align = align.lower()
|
||||||
|
if align == "center":
|
||||||
|
text = f"{text:^{width}}"
|
||||||
|
elif align == "left":
|
||||||
|
text = f"{text:<{width}}"
|
||||||
|
elif align == "right":
|
||||||
|
text = f"{text:>{width}}"
|
||||||
|
elif align == "justify":
|
||||||
|
text = self._justify(text, width)
|
||||||
|
else:
|
||||||
|
raise ValueError("Expected a valid alignment: center|left|right|justify")
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _truncate(text: str, width: int, placeholder: str = ".") -> str:
|
||||||
|
"""Truncate an string at a max width or leave it untouched.
|
||||||
|
|
||||||
|
Add a placeholder at the end of the output text if it has been truncated.
|
||||||
|
"""
|
||||||
|
ph_len = len(placeholder)
|
||||||
|
max_len = width - ph_len
|
||||||
|
return f"{text[:max_len]}{placeholder}" if len(text) > width else text
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _repeat_last(iterable, max_iterations: int = 1000):
|
||||||
|
"""Iterate over the items of a list repeating the last one until max_iterations."""
|
||||||
|
i = 0
|
||||||
|
while i < max_iterations:
|
||||||
|
try:
|
||||||
|
yield iterable[i]
|
||||||
|
except IndexError:
|
||||||
|
yield iterable[-1]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def _rearrange_into_cols(self, text_list: list, widths: list[int]) -> list:
|
||||||
|
"""Wrap and convert a list of strings into an array of text columns.
|
||||||
|
|
||||||
|
Set the width of each column by passing a list of widths.
|
||||||
|
Wrap if possible and|or truncate strings longer than its column width.
|
||||||
|
Reorder the wrapped items into an array of text columns.
|
||||||
|
"""
|
||||||
|
n_cols = len(text_list)
|
||||||
|
wrapped = [
|
||||||
|
textwrap.wrap(text, widths[i], break_long_words=False)
|
||||||
|
for i, text in enumerate(text_list)
|
||||||
|
]
|
||||||
|
max_len = max(0, *[len(text_group) for text_group in wrapped])
|
||||||
|
text_colums = []
|
||||||
|
for i in range(max_len):
|
||||||
|
row = ["" for _ in range(n_cols)]
|
||||||
|
for j, item in enumerate(wrapped):
|
||||||
|
if i in range(len(item)):
|
||||||
|
row[j] = self._truncate(item[i], widths[j])
|
||||||
|
text_colums.append(row)
|
||||||
|
return text_colums
|
||||||
|
|
||||||
|
def _add_padding_into_cols(
|
||||||
|
self,
|
||||||
|
text_list: list[str],
|
||||||
|
widths: list[int],
|
||||||
|
align: list[Alignment],
|
||||||
|
) -> list:
|
||||||
|
"""Add padding, width and alignment into the items of a list of strings."""
|
||||||
|
return [
|
||||||
|
self._padding(text, widths[i], align[i]) for i, text in enumerate(text_list)
|
||||||
|
]
|
||||||
|
|
||||||
|
def software_columns(
|
||||||
|
self,
|
||||||
|
text_list: list,
|
||||||
|
widths: Union[list[int], int],
|
||||||
|
align: Union[list[Alignment], Alignment],
|
||||||
|
) -> None:
|
||||||
|
"""Print a list of strings arranged horizontally in columns.
|
||||||
|
|
||||||
|
:param text_list: list of strings, each item in the list will be printed as a column.
|
||||||
|
|
||||||
|
:param widths: width of each column by passing a list of widths,
|
||||||
|
or a single total width to arrange columns of the same size.
|
||||||
|
If the list of width items is shorter than the list of strings then
|
||||||
|
the last width of the list will be applied till the last string (column).
|
||||||
|
|
||||||
|
:param align: alignment of the text into each column by passing a list of alignments,
|
||||||
|
or a single alignment for all the columns.
|
||||||
|
If the list of alignment items is shorter than the list of strings then
|
||||||
|
the last alignment of the list will be applied till the last string (column).
|
||||||
|
"""
|
||||||
|
if not all([text_list, widths, align]):
|
||||||
|
raise TypeError("Value can't be of type None")
|
||||||
|
|
||||||
|
n_cols = len(text_list)
|
||||||
|
|
||||||
|
if isinstance(widths, int):
|
||||||
|
widths = [round(widths / n_cols)]
|
||||||
|
widths = list(self._repeat_last(widths, max_iterations=n_cols))
|
||||||
|
|
||||||
|
if isinstance(align, str):
|
||||||
|
align = [align]
|
||||||
|
align = list(self._repeat_last(align, max_iterations=n_cols))
|
||||||
|
|
||||||
|
columns = self._rearrange_into_cols(text_list, widths)
|
||||||
|
for row in columns:
|
||||||
|
padded = self._add_padding_into_cols(row, widths, align)
|
||||||
|
self.textln("".join(padded))
|
||||||
|
|
||||||
def set(
|
def set(
|
||||||
self,
|
self,
|
||||||
align: Optional[str] = None,
|
align: Optional[str] = None,
|
||||||
@@ -924,8 +1087,8 @@ class Escpos(object):
|
|||||||
:param double_width: doubles the width of the text
|
:param double_width: doubles the width of the text
|
||||||
:param custom_size: uses custom size specified by width and height
|
:param custom_size: uses custom size specified by width and height
|
||||||
parameters. Cannot be used with double_width or double_height.
|
parameters. Cannot be used with double_width or double_height.
|
||||||
:param width: text width multiplier when custom_size is used, decimal range 1-8
|
:param width: requires custom_size=True, text width multiplier when custom_size is used, decimal range 1-8
|
||||||
:param height: text height multiplier when custom_size is used, decimal range 1-8
|
:param height: requires custom_size=True, text height multiplier when custom_size is used, decimal range 1-8
|
||||||
:param density: print density, value from 0-8, if something else is supplied the density remains unchanged
|
:param density: print density, value from 0-8, if something else is supplied the density remains unchanged
|
||||||
:param invert: True enables white on black printing
|
:param invert: True enables white on black printing
|
||||||
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger
|
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger
|
||||||
@@ -1125,7 +1288,7 @@ class Escpos(object):
|
|||||||
try:
|
try:
|
||||||
self._raw(CD_KICK_DEC_SEQUENCE(*pin))
|
self._raw(CD_KICK_DEC_SEQUENCE(*pin))
|
||||||
except TypeError as err:
|
except TypeError as err:
|
||||||
raise CashDrawerError(err)
|
raise CashDrawerError(str(err))
|
||||||
|
|
||||||
def linedisplay_select(self, select_display: bool = False) -> None:
|
def linedisplay_select(self, select_display: bool = False) -> None:
|
||||||
"""Select the line display or the printer.
|
"""Select the line display or the printer.
|
||||||
@@ -1258,10 +1421,10 @@ class Escpos(object):
|
|||||||
else:
|
else:
|
||||||
self._raw(PANEL_BUTTON_OFF)
|
self._raw(PANEL_BUTTON_OFF)
|
||||||
|
|
||||||
def query_status(self, mode: bytes) -> List[int]:
|
def query_status(self, mode: bytes) -> bytes:
|
||||||
"""Query the printer for its status.
|
"""Query the printer for its status.
|
||||||
|
|
||||||
Returns an array of integers containing it.
|
Returns byte array containing it.
|
||||||
|
|
||||||
:param mode: Integer that sets the status mode queried to the printer.
|
:param mode: Integer that sets the status mode queried to the printer.
|
||||||
- RT_STATUS_ONLINE: Printer status.
|
- RT_STATUS_ONLINE: Printer status.
|
||||||
@@ -1281,7 +1444,7 @@ class Escpos(object):
|
|||||||
return False
|
return False
|
||||||
return not (status[0] & RT_MASK_ONLINE)
|
return not (status[0] & RT_MASK_ONLINE)
|
||||||
|
|
||||||
def paper_status(self):
|
def paper_status(self) -> int: # could be IntEnum
|
||||||
"""Query the paper status of the printer.
|
"""Query the paper status of the printer.
|
||||||
|
|
||||||
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
|
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
|
||||||
@@ -1298,6 +1461,8 @@ class Escpos(object):
|
|||||||
return 1
|
return 1
|
||||||
if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
|
if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
|
||||||
return 2
|
return 2
|
||||||
|
# not reached
|
||||||
|
return 0
|
||||||
|
|
||||||
def target(self, type: str = "ROLL") -> None:
|
def target(self, type: str = "ROLL") -> None:
|
||||||
"""Select where to print to.
|
"""Select where to print to.
|
||||||
@@ -1352,7 +1517,7 @@ class Escpos(object):
|
|||||||
self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))
|
self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))
|
||||||
|
|
||||||
|
|
||||||
class EscposIO(object):
|
class EscposIO:
|
||||||
r"""ESC/POS Printer IO object.
|
r"""ESC/POS Printer IO object.
|
||||||
|
|
||||||
Allows the class to be used together with the `with`-statement. You have to define a printer instance
|
Allows the class to be used together with the `with`-statement. You have to define a printer instance
|
||||||
@@ -1398,41 +1563,40 @@ class EscposIO(object):
|
|||||||
"""
|
"""
|
||||||
self.params.update(kwargs)
|
self.params.update(kwargs)
|
||||||
|
|
||||||
def writelines(self, text, **kwargs):
|
def writelines(self, text: str, **kwargs) -> None:
|
||||||
"""Print text."""
|
"""Print text."""
|
||||||
params = dict(self.params)
|
params = dict(self.params)
|
||||||
params.update(kwargs)
|
params.update(kwargs)
|
||||||
|
|
||||||
if isinstance(text, six.text_type):
|
if isinstance(text, str):
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
elif isinstance(text, list) or isinstance(text, tuple):
|
elif isinstance(text, list) or isinstance(text, tuple):
|
||||||
lines = text
|
lines = text
|
||||||
else:
|
else:
|
||||||
lines = [
|
lines = [
|
||||||
"{0}".format(text),
|
f"{text}",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO check unicode handling
|
# TODO check Unicode handling
|
||||||
# TODO flush? or on print? (this should prob rather be handled by the _raw-method)
|
# TODO flush? or on print? (this should prob rather be handled by the _raw-method)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
self.printer.set(**params)
|
self.printer.set(**params)
|
||||||
if isinstance(text, six.text_type):
|
self.printer.text(f"{line}\n")
|
||||||
self.printer.text("{0}\n".format(line))
|
|
||||||
else:
|
|
||||||
self.printer.text("{0}\n".format(line))
|
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
"""Close printer.
|
"""Close printer.
|
||||||
|
|
||||||
Called upon closing the `with`-statement.
|
Called upon closing the `with`-statement.
|
||||||
"""
|
"""
|
||||||
self.printer.close()
|
self.printer.close()
|
||||||
|
|
||||||
def __enter__(self, **kwargs):
|
def __enter__(self, **kwargs: Any) -> "EscposIO":
|
||||||
"""Enter context."""
|
"""Enter context."""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
def __exit__(
|
||||||
|
self, type: type[BaseException], value: BaseException, traceback: TracebackType
|
||||||
|
) -> None:
|
||||||
"""Cut and close if configured.
|
"""Cut and close if configured.
|
||||||
|
|
||||||
If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor),
|
If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor),
|
||||||
|
@@ -26,6 +26,8 @@ Result/Exit codes:
|
|||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
"""Base class for ESC/POS errors.
|
"""Base class for ESC/POS errors.
|
||||||
@@ -37,7 +39,7 @@ class Error(Exception):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg, status=None):
|
def __init__(self, msg: str, status: Optional[int] = None) -> None:
|
||||||
"""Initialize Error object."""
|
"""Initialize Error object."""
|
||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
@@ -45,7 +47,7 @@ class Error(Exception):
|
|||||||
if status is not None:
|
if status is not None:
|
||||||
self.resultcode = status
|
self.resultcode = status
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of Error."""
|
"""Return string representation of Error."""
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
@@ -64,15 +66,15 @@ class BarcodeTypeError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize BarcodeTypeError object."""
|
"""Initialize BarcodeTypeError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 10
|
self.resultcode = 10
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of BarcodeTypeError."""
|
"""Return string representation of BarcodeTypeError."""
|
||||||
return "No Barcode type is defined ({msg})".format(msg=self.msg)
|
return f"No Barcode type is defined ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class BarcodeSizeError(Error):
|
class BarcodeSizeError(Error):
|
||||||
@@ -89,15 +91,15 @@ class BarcodeSizeError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize BarcodeSizeError object."""
|
"""Initialize BarcodeSizeError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 20
|
self.resultcode = 20
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of BarcodeSizeError."""
|
"""Return string representation of BarcodeSizeError."""
|
||||||
return "Barcode size is out of range ({msg})".format(msg=self.msg)
|
return f"Barcode size is out of range ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class BarcodeCodeError(Error):
|
class BarcodeCodeError(Error):
|
||||||
@@ -114,15 +116,15 @@ class BarcodeCodeError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize BarcodeCodeError object."""
|
"""Initialize BarcodeCodeError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 30
|
self.resultcode = 30
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of BarcodeCodeError."""
|
"""Return string representation of BarcodeCodeError."""
|
||||||
return "No Barcode code was supplied ({msg})".format(msg=self.msg)
|
return f"No Barcode code was supplied ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class ImageSizeError(Error):
|
class ImageSizeError(Error):
|
||||||
@@ -137,17 +139,15 @@ class ImageSizeError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize ImageSizeError object."""
|
"""Initialize ImageSizeError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 40
|
self.resultcode = 40
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of ImageSizeError."""
|
"""Return string representation of ImageSizeError."""
|
||||||
return "Image height is longer than 255px and can't be printed ({msg})".format(
|
return f"Image height is longer than 255px and can't be printed ({self.msg})"
|
||||||
msg=self.msg
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ImageWidthError(Error):
|
class ImageWidthError(Error):
|
||||||
@@ -162,15 +162,15 @@ class ImageWidthError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize ImageWidthError object."""
|
"""Initialize ImageWidthError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 41
|
self.resultcode = 41
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of ImageWidthError."""
|
"""Return string representation of ImageWidthError."""
|
||||||
return "Image width is too large ({msg})".format(msg=self.msg)
|
return f"Image width is too large ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class TextError(Error):
|
class TextError(Error):
|
||||||
@@ -186,17 +186,15 @@ class TextError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize TextError object."""
|
"""Initialize TextError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 50
|
self.resultcode = 50
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of TextError."""
|
"""Return string representation of TextError."""
|
||||||
return "Text string must be supplied to the text() method ({msg})".format(
|
return f"Text string must be supplied to the text() method ({self.msg})"
|
||||||
msg=self.msg
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CashDrawerError(Error):
|
class CashDrawerError(Error):
|
||||||
@@ -212,15 +210,15 @@ class CashDrawerError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize CashDrawerError object."""
|
"""Initialize CashDrawerError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 60
|
self.resultcode = 60
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of CashDrawerError."""
|
"""Return string representation of CashDrawerError."""
|
||||||
return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
|
return f"Valid pin must be set to send pulse ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class TabPosError(Error):
|
class TabPosError(Error):
|
||||||
@@ -239,17 +237,15 @@ class TabPosError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize TabPosError object."""
|
"""Initialize TabPosError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 70
|
self.resultcode = 70
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of TabPosError."""
|
"""Return string representation of TabPosError."""
|
||||||
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
|
return f"Valid tab positions must be in the range 0 to 16 ({self.msg})"
|
||||||
msg=self.msg
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CharCodeError(Error):
|
class CharCodeError(Error):
|
||||||
@@ -265,15 +261,15 @@ class CharCodeError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize CharCodeError object."""
|
"""Initialize CharCodeError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 80
|
self.resultcode = 80
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of CharCodeError."""
|
"""Return string representation of CharCodeError."""
|
||||||
return "Valid char code must be set ({msg})".format(msg=self.msg)
|
return f"Valid char code must be set ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class DeviceNotFoundError(Error):
|
class DeviceNotFoundError(Error):
|
||||||
@@ -289,13 +285,13 @@ class DeviceNotFoundError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize DeviceNotFoundError object."""
|
"""Initialize DeviceNotFoundError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 90
|
self.resultcode = 90
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of DeviceNotFoundError."""
|
"""Return string representation of DeviceNotFoundError."""
|
||||||
return f"Device not found ({self.msg})"
|
return f"Device not found ({self.msg})"
|
||||||
|
|
||||||
@@ -313,13 +309,13 @@ class USBNotFoundError(DeviceNotFoundError):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize USBNotFoundError object."""
|
"""Initialize USBNotFoundError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 91
|
self.resultcode = 91
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of USBNotFoundError."""
|
"""Return string representation of USBNotFoundError."""
|
||||||
return f"USB device not found ({self.msg})"
|
return f"USB device not found ({self.msg})"
|
||||||
|
|
||||||
@@ -337,15 +333,15 @@ class SetVariableError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize SetVariableError object."""
|
"""Initialize SetVariableError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 100
|
self.resultcode = 100
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of SetVariableError."""
|
"""Return string representation of SetVariableError."""
|
||||||
return "Set variable out of range ({msg})".format(msg=self.msg)
|
return f"Set variable out of range ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
# Configuration errors
|
# Configuration errors
|
||||||
@@ -364,15 +360,15 @@ class ConfigNotFoundError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize ConfigNotFoundError object."""
|
"""Initialize ConfigNotFoundError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 200
|
self.resultcode = 200
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of ConfigNotFoundError."""
|
"""Return string representation of ConfigNotFoundError."""
|
||||||
return "Configuration not found ({msg})".format(msg=self.msg)
|
return f"Configuration not found ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class ConfigSyntaxError(Error):
|
class ConfigSyntaxError(Error):
|
||||||
@@ -388,15 +384,15 @@ class ConfigSyntaxError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize ConfigSyntaxError object."""
|
"""Initialize ConfigSyntaxError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 210
|
self.resultcode = 210
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of ConfigSyntaxError."""
|
"""Return string representation of ConfigSyntaxError."""
|
||||||
return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
|
return f"Configuration syntax is invalid ({self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class ConfigSectionMissingError(Error):
|
class ConfigSectionMissingError(Error):
|
||||||
@@ -412,12 +408,12 @@ class ConfigSectionMissingError(Error):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg=""):
|
def __init__(self, msg: str = "") -> None:
|
||||||
"""Initialize ConfigSectionMissingError object."""
|
"""Initialize ConfigSectionMissingError object."""
|
||||||
Error.__init__(self, msg)
|
Error.__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.resultcode = 220
|
self.resultcode = 220
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return string representation of ConfigSectionMissingError."""
|
"""Return string representation of ConfigSectionMissingError."""
|
||||||
return "Configuration section is missing ({msg})".format(msg=self.msg)
|
return f"Configuration section is missing ({self.msg})"
|
||||||
|
@@ -10,11 +10,12 @@ This module contains the image format handler :py:class:`EscposImage`.
|
|||||||
|
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
from typing import Iterator, Union
|
||||||
|
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
|
||||||
class EscposImage(object):
|
class EscposImage:
|
||||||
"""
|
"""
|
||||||
Load images in, and output ESC/POS formats.
|
Load images in, and output ESC/POS formats.
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class EscposImage(object):
|
|||||||
PIL, rather than spend CPU cycles looping over pixels.
|
PIL, rather than spend CPU cycles looping over pixels.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, img_source):
|
def __init__(self, img_source: Union[Image.Image, str]) -> None:
|
||||||
"""Load in an image.
|
"""Load in an image.
|
||||||
|
|
||||||
:param img_source: PIL.Image, or filename to load one from.
|
:param img_source: PIL.Image, or filename to load one from.
|
||||||
@@ -48,23 +49,23 @@ class EscposImage(object):
|
|||||||
self._im = im.convert("1")
|
self._im = im.convert("1")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self) -> int:
|
||||||
"""Return width of image in pixels."""
|
"""Return width of image in pixels."""
|
||||||
width_pixels, _ = self._im.size
|
width_pixels, _ = self._im.size
|
||||||
return width_pixels
|
return width_pixels
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width_bytes(self):
|
def width_bytes(self) -> int:
|
||||||
"""Return width of image if you use 8 pixels per byte and 0-pad at the end."""
|
"""Return width of image if you use 8 pixels per byte and 0-pad at the end."""
|
||||||
return (self.width + 7) >> 3
|
return (self.width + 7) >> 3
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def height(self):
|
def height(self) -> int:
|
||||||
"""Height of image in pixels."""
|
"""Height of image in pixels."""
|
||||||
_, height_pixels = self._im.size
|
_, height_pixels = self._im.size
|
||||||
return height_pixels
|
return height_pixels
|
||||||
|
|
||||||
def to_column_format(self, high_density_vertical=True):
|
def to_column_format(self, high_density_vertical: bool = True) -> Iterator[bytes]:
|
||||||
"""Extract slices of an image as equal-sized blobs of column-format data.
|
"""Extract slices of an image as equal-sized blobs of column-format data.
|
||||||
|
|
||||||
:param high_density_vertical: Printed line height in dots
|
:param high_density_vertical: Printed line height in dots
|
||||||
@@ -81,11 +82,11 @@ class EscposImage(object):
|
|||||||
yield (im_bytes)
|
yield (im_bytes)
|
||||||
left += line_height
|
left += line_height
|
||||||
|
|
||||||
def to_raster_format(self):
|
def to_raster_format(self) -> bytes:
|
||||||
"""Convert image to raster-format binary."""
|
"""Convert image to raster-format binary."""
|
||||||
return self._im.tobytes()
|
return self._im.tobytes()
|
||||||
|
|
||||||
def split(self, fragment_height):
|
def split(self, fragment_height: int):
|
||||||
"""Split an image into multiple fragments after fragment_height pixels.
|
"""Split an image into multiple fragments after fragment_height pixels.
|
||||||
|
|
||||||
:param fragment_height: height of fragment
|
:param fragment_height: height of fragment
|
||||||
@@ -102,7 +103,7 @@ class EscposImage(object):
|
|||||||
fragments.append(self.img_original.crop(box))
|
fragments.append(self.img_original.crop(box))
|
||||||
return fragments
|
return fragments
|
||||||
|
|
||||||
def center(self, max_width):
|
def center(self, max_width: int) -> None:
|
||||||
"""Center image in place.
|
"""Center image in place.
|
||||||
|
|
||||||
:param: Maximum width in order to deduce x offset for centering
|
:param: Maximum width in order to deduce x offset for centering
|
||||||
|
@@ -4,14 +4,17 @@
|
|||||||
I doubt that this currently works correctly.
|
I doubt that this currently works correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import types
|
||||||
|
import typing
|
||||||
|
|
||||||
|
jaconv: typing.Optional[types.ModuleType]
|
||||||
try:
|
try:
|
||||||
import jaconv
|
import jaconv
|
||||||
except ImportError:
|
except ImportError:
|
||||||
jaconv = None
|
jaconv = None
|
||||||
|
|
||||||
|
|
||||||
def encode_katakana(text):
|
def encode_katakana(text: str) -> bytes:
|
||||||
"""I don't think this quite works yet."""
|
"""I don't think this quite works yet."""
|
||||||
encoded = []
|
encoded = []
|
||||||
for char in text:
|
for char in text:
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"""Magic Encode.
|
"""Magic Encode.
|
||||||
|
|
||||||
This module tries to convert an UTF-8 string to an encoded string for the printer.
|
This module tries to convert an UTF-8 string to an encoded string for the printer.
|
||||||
It uses trial and error in order to guess the right codepage.
|
It uses trial and error in order to guess the right code page.
|
||||||
The code is based on the encoding-code in py-xml-escpos by @fvdsn.
|
The code is based on the encoding-code in py-xml-escpos by @fvdsn.
|
||||||
|
|
||||||
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
||||||
@@ -23,7 +23,7 @@ from .constants import CODEPAGE_CHANGE
|
|||||||
from .exceptions import Error
|
from .exceptions import Error
|
||||||
|
|
||||||
|
|
||||||
class Encoder(object):
|
class Encoder:
|
||||||
"""Take available code spaces and pick the right one for a given character.
|
"""Take available code spaces and pick the right one for a given character.
|
||||||
|
|
||||||
Note: To determine the code page, it needs to do the conversion, and
|
Note: To determine the code page, it needs to do the conversion, and
|
||||||
@@ -57,19 +57,19 @@ class Encoder(object):
|
|||||||
if encoding not in self.codepages:
|
if encoding not in self.codepages:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
(
|
(
|
||||||
'Encoding "{}" cannot be used for the current profile. '
|
f'Encoding "{encoding}" cannot be used for the current profile. '
|
||||||
"Valid encodings are: {}"
|
f'Valid encodings are: {",".join(self.codepages.keys())}'
|
||||||
).format(encoding, ",".join(self.codepages.keys()))
|
)
|
||||||
)
|
)
|
||||||
return encoding
|
return encoding
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_codepage_char_list(encoding):
|
def _get_codepage_char_list(encoding):
|
||||||
"""Get codepage character list.
|
"""Get code page character list.
|
||||||
|
|
||||||
Gets characters 128-255 for a given code page, as an array.
|
Gets characters 128-255 for a given code page, as an array.
|
||||||
|
|
||||||
:param encoding: The name of the encoding. This must appear in the CodePage list
|
:param encoding: The name of the encoding. This must appear in the code page list
|
||||||
"""
|
"""
|
||||||
codepage = CodePages.get_encoding(encoding)
|
codepage = CodePages.get_encoding(encoding)
|
||||||
if "data" in codepage:
|
if "data" in codepage:
|
||||||
@@ -88,10 +88,10 @@ class Encoder(object):
|
|||||||
# Non-encodable character, just skip it
|
# Non-encodable character, just skip it
|
||||||
pass
|
pass
|
||||||
return encodable_chars
|
return encodable_chars
|
||||||
raise LookupError("Can't find a known encoding for {}".format(encoding))
|
raise LookupError(f"Can't find a known encoding for {encoding}")
|
||||||
|
|
||||||
def _get_codepage_char_map(self, encoding):
|
def _get_codepage_char_map(self, encoding):
|
||||||
"""Get codepage character map.
|
"""Get code page character map.
|
||||||
|
|
||||||
Process an encoding and return a map of UTF-characters to code points
|
Process an encoding and return a map of UTF-characters to code points
|
||||||
in this encoding.
|
in this encoding.
|
||||||
@@ -166,7 +166,7 @@ class Encoder(object):
|
|||||||
1. code pages that we already tried before; there is a good
|
1. code pages that we already tried before; there is a good
|
||||||
chance they might work again, reducing the search space,
|
chance they might work again, reducing the search space,
|
||||||
and by re-using already used encodings we might also
|
and by re-using already used encodings we might also
|
||||||
reduce the number of codepage change instruction we have
|
reduce the number of code page change instruction we have
|
||||||
to send. Still, any performance gains will presumably be
|
to send. Still, any performance gains will presumably be
|
||||||
fairly minor.
|
fairly minor.
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ def split_writable_text(encoder, text, encoding):
|
|||||||
return text, None
|
return text, None
|
||||||
|
|
||||||
|
|
||||||
class MagicEncode(object):
|
class MagicEncode:
|
||||||
"""Help switching to the right code page.
|
"""Help switching to the right code page.
|
||||||
|
|
||||||
A helper that helps us to automatically switch to the right
|
A helper that helps us to automatically switch to the right
|
||||||
@@ -225,7 +225,7 @@ class MagicEncode(object):
|
|||||||
:param encoding: If you know the current encoding of the printer
|
:param encoding: If you know the current encoding of the printer
|
||||||
when initializing this class, set it here. If the current
|
when initializing this class, set it here. If the current
|
||||||
encoding is unknown, the first character emitted will be a
|
encoding is unknown, the first character emitted will be a
|
||||||
codepage switch.
|
code page switch.
|
||||||
:param disabled:
|
:param disabled:
|
||||||
:param defaultsymbol:
|
:param defaultsymbol:
|
||||||
:param encoder:
|
:param encoder:
|
||||||
@@ -284,22 +284,20 @@ class MagicEncode(object):
|
|||||||
def _handle_character_failed(self, char):
|
def _handle_character_failed(self, char):
|
||||||
"""Write a default symbol.
|
"""Write a default symbol.
|
||||||
|
|
||||||
Called when no codepage was found to render a character.
|
Called when no code page was found to render a character.
|
||||||
"""
|
"""
|
||||||
# Writing the default symbol via write() allows us to avoid
|
# Writing the default symbol via write() allows us to avoid
|
||||||
# unnecesary codepage switches.
|
# unnecesary code page switches.
|
||||||
self.write(self.defaultsymbol)
|
self.write(self.defaultsymbol)
|
||||||
|
|
||||||
def write_with_encoding(self, encoding, text):
|
def write_with_encoding(self, encoding, text):
|
||||||
"""Write the text and inject necessary codepage switches."""
|
"""Write the text and inject necessary code page switches."""
|
||||||
if text is not None and type(text) is not six.text_type:
|
if text is not None and type(text) is not str:
|
||||||
raise Error(
|
raise Error(
|
||||||
"The supplied text has to be unicode, but is of type {type}.".format(
|
f"The supplied text has to be Unicode, but is of type {type(text)}."
|
||||||
type=type(text)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# We always know the current code page; if the new codepage
|
# We always know the current code page; if the new code page
|
||||||
# is different, emit a change command.
|
# is different, emit a change command.
|
||||||
if encoding != self.encoding:
|
if encoding != self.encoding:
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
@@ -49,8 +49,8 @@ def dependency_pycups(func):
|
|||||||
"""Throw a RuntimeError if pycups is not imported."""
|
"""Throw a RuntimeError if pycups is not imported."""
|
||||||
if not is_usable():
|
if not is_usable():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Printing with PyCups requires the pycups library to"
|
"Printing with PyCups requires the pycups library to "
|
||||||
"be installed. Please refer to the documentation on"
|
"be installed. Please refer to the documentation on "
|
||||||
"what to install and install the dependencies for pycups."
|
"what to install and install the dependencies for pycups."
|
||||||
)
|
)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@@ -84,7 +84,7 @@ class CupsPrinter(Escpos):
|
|||||||
return is_usable()
|
return is_usable()
|
||||||
|
|
||||||
@dependency_pycups
|
@dependency_pycups
|
||||||
def __init__(self, printer_name: str = "", *args, **kwargs):
|
def __init__(self, printer_name: str = "", *args, **kwargs) -> None:
|
||||||
"""Class constructor for CupsPrinter.
|
"""Class constructor for CupsPrinter.
|
||||||
|
|
||||||
:param printer_name: CUPS printer name (Optional)
|
:param printer_name: CUPS printer name (Optional)
|
||||||
@@ -163,11 +163,10 @@ class CupsPrinter(Escpos):
|
|||||||
return
|
return
|
||||||
logging.info("CupsPrinter printer enabled")
|
logging.info("CupsPrinter printer enabled")
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Append any command sent in raw format to temporary file.
|
"""Append any command sent in raw format to temporary file.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
self.pending_job = True
|
self.pending_job = True
|
||||||
try:
|
try:
|
||||||
@@ -176,8 +175,9 @@ class CupsPrinter(Escpos):
|
|||||||
self.pending_job = False
|
self.pending_job = False
|
||||||
raise TypeError("Bytes required. Printer job not opened")
|
raise TypeError("Bytes required. Printer job not opened")
|
||||||
|
|
||||||
def send(self):
|
def send(self) -> None:
|
||||||
"""Send the print job to the printer."""
|
"""Send the print job to the printer."""
|
||||||
|
assert self.device
|
||||||
if self.pending_job:
|
if self.pending_job:
|
||||||
# Rewind tempfile
|
# Rewind tempfile
|
||||||
self.tmpfile.seek(0)
|
self.tmpfile.seek(0)
|
||||||
@@ -190,7 +190,7 @@ class CupsPrinter(Escpos):
|
|||||||
)
|
)
|
||||||
self._clear()
|
self._clear()
|
||||||
|
|
||||||
def _clear(self):
|
def _clear(self) -> None:
|
||||||
"""Finish the print job.
|
"""Finish the print job.
|
||||||
|
|
||||||
Remove temporary file.
|
Remove temporary file.
|
||||||
@@ -198,18 +198,18 @@ class CupsPrinter(Escpos):
|
|||||||
self.tmpfile.close()
|
self.tmpfile.close()
|
||||||
self.pending_job = False
|
self.pending_job = False
|
||||||
|
|
||||||
def _read(self):
|
def _read(self) -> bytes:
|
||||||
"""Return a single-item array with the accepting state of the print queue.
|
"""Return a single-item array with the accepting state of the print queue.
|
||||||
|
|
||||||
states: idle = [3], printing a job = [4], stopped = [5]
|
states: idle = [3], printing a job = [4], stopped = [5]
|
||||||
"""
|
"""
|
||||||
printer = self.printers.get(self.printer_name, {})
|
printer = self.printers.get(self.printer_name, {})
|
||||||
state = printer.get("printer-state")
|
state = printer.get("printer-state")
|
||||||
if not state:
|
if not state or state in [4, 5]:
|
||||||
return []
|
return b"8" # offline
|
||||||
return [state]
|
return b"0" # online
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
"""Close CUPS connection.
|
"""Close CUPS connection.
|
||||||
|
|
||||||
Send pending job to the printer if needed.
|
Send pending job to the printer if needed.
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
|
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
|
||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from ..escpos import Escpos
|
from ..escpos import Escpos
|
||||||
|
|
||||||
@@ -39,25 +40,24 @@ class Dummy(Escpos):
|
|||||||
"""
|
"""
|
||||||
return is_usable()
|
return is_usable()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
"""Init with empty output list."""
|
"""Init with empty output list."""
|
||||||
Escpos.__init__(self, *args, **kwargs)
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
self._output_list = []
|
self._output_list: List[bytes] = []
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
self._output_list.append(msg)
|
self._output_list.append(msg)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output(self):
|
def output(self) -> bytes:
|
||||||
"""Get the data that was sent to this printer."""
|
"""Get the data that was sent to this printer."""
|
||||||
return b"".join(self._output_list)
|
return b"".join(self._output_list)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clear the buffer of the printer.
|
"""Clear the buffer of the printer.
|
||||||
|
|
||||||
This method can be called if you send the contents to a physical printer
|
This method can be called if you send the contents to a physical printer
|
||||||
@@ -65,6 +65,6 @@ class Dummy(Escpos):
|
|||||||
"""
|
"""
|
||||||
del self._output_list[:]
|
del self._output_list[:]
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
"""Close not implemented for Dummy printer."""
|
"""Close not implemented for Dummy printer."""
|
||||||
pass
|
pass
|
||||||
|
@@ -23,7 +23,7 @@ def is_usable() -> bool:
|
|||||||
class File(Escpos):
|
class File(Escpos):
|
||||||
"""Generic file printer.
|
"""Generic file printer.
|
||||||
|
|
||||||
This class is used for parallel port printer or other printers that are directly attached to the filesystem.
|
This class is used for parallel port printer or other printers that are directly attached to the file system.
|
||||||
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
|
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
|
||||||
and produce arbitrary errors.
|
and produce arbitrary errors.
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class File(Escpos):
|
|||||||
def __init__(self, devfile: str = "", auto_flush: bool = True, *args, **kwargs):
|
def __init__(self, devfile: str = "", auto_flush: bool = True, *args, **kwargs):
|
||||||
"""Initialize file printer with device file.
|
"""Initialize file printer with device file.
|
||||||
|
|
||||||
:param devfile: Device file under dev filesystem
|
:param devfile: Device file under dev file system
|
||||||
:param auto_flush: automatically call flush after every call of _raw()
|
:param auto_flush: automatically call flush after every call of _raw()
|
||||||
"""
|
"""
|
||||||
Escpos.__init__(self, *args, **kwargs)
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
@@ -88,12 +88,12 @@ class File(Escpos):
|
|||||||
if self.device:
|
if self.device:
|
||||||
self.device.flush()
|
self.device.flush()
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
|
assert self.device
|
||||||
self.device.write(msg)
|
self.device.write(msg)
|
||||||
if self.auto_flush:
|
if self.auto_flush:
|
||||||
self.flush()
|
self.flush()
|
||||||
|
@@ -34,7 +34,7 @@ def dependency_linux_lp(func):
|
|||||||
"""Throw a RuntimeError if not on a non-Windows system."""
|
"""Throw a RuntimeError if not on a non-Windows system."""
|
||||||
if not is_usable():
|
if not is_usable():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"This printer driver depends on LP which is not"
|
"This printer driver depends on LP which is not "
|
||||||
"available on Windows systems."
|
"available on Windows systems."
|
||||||
)
|
)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@@ -182,12 +182,13 @@ class LP(Escpos):
|
|||||||
if not self._is_closing:
|
if not self._is_closing:
|
||||||
self.open(_close_opened=False)
|
self.open(_close_opened=False)
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Write raw command(s) to the printer.
|
"""Write raw command(s) to the printer.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
|
assert self.device is not None
|
||||||
|
assert self.device.stdin is not None
|
||||||
if self.device.stdin.writable():
|
if self.device.stdin.writable():
|
||||||
self.device.stdin.write(msg)
|
self.device.stdin.write(msg)
|
||||||
else:
|
else:
|
||||||
|
@@ -110,16 +110,17 @@ class Network(Escpos):
|
|||||||
return
|
return
|
||||||
logging.info("Network printer enabled")
|
logging.info("Network printer enabled")
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
|
assert self.device
|
||||||
self.device.sendall(msg)
|
self.device.sendall(msg)
|
||||||
|
|
||||||
def _read(self):
|
def _read(self) -> bytes:
|
||||||
"""Read data from the TCP socket."""
|
"""Read data from the TCP socket."""
|
||||||
|
assert self.device
|
||||||
return self.device.recv(16)
|
return self.device.recv(16)
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
|
@@ -43,8 +43,8 @@ def dependency_pyserial(func):
|
|||||||
"""Throw a RuntimeError if pyserial not installed."""
|
"""Throw a RuntimeError if pyserial not installed."""
|
||||||
if not is_usable():
|
if not is_usable():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Printing with Serial requires the pyserial library to"
|
"Printing with Serial requires the pyserial library to "
|
||||||
"be installed. Please refer to the documentation on"
|
"be installed. Please refer to the documentation on "
|
||||||
"what to install and install the dependencies for pyserial."
|
"what to install and install the dependencies for pyserial."
|
||||||
)
|
)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@@ -89,7 +89,7 @@ class Serial(Escpos):
|
|||||||
):
|
):
|
||||||
"""Initialize serial printer.
|
"""Initialize serial printer.
|
||||||
|
|
||||||
:param devfile: Device file under dev filesystem
|
:param devfile: Device file under dev file system
|
||||||
:param baudrate: Baud rate for serial transmission
|
:param baudrate: Baud rate for serial transmission
|
||||||
:param bytesize: Serial buffer size
|
:param bytesize: Serial buffer size
|
||||||
:param timeout: Read/Write timeout
|
:param timeout: Read/Write timeout
|
||||||
@@ -155,16 +155,17 @@ class Serial(Escpos):
|
|||||||
return
|
return
|
||||||
logging.info("Serial printer enabled")
|
logging.info("Serial printer enabled")
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
|
assert self.device
|
||||||
self.device.write(msg)
|
self.device.write(msg)
|
||||||
|
|
||||||
def _read(self):
|
def _read(self) -> bytes:
|
||||||
"""Read the data buffer and return it to the caller."""
|
"""Read the data buffer and return it to the caller."""
|
||||||
|
assert self.device
|
||||||
return self.device.read(16)
|
return self.device.read(16)
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
|
@@ -42,8 +42,8 @@ def dependency_usb(func):
|
|||||||
"""Throw a RuntimeError if usb not installed."""
|
"""Throw a RuntimeError if usb not installed."""
|
||||||
if not is_usable():
|
if not is_usable():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Printing with USB connection requires a usb library to"
|
"Printing with USB connection requires a usb library to "
|
||||||
"be installed. Please refer to the documentation on"
|
"be installed. Please refer to the documentation on "
|
||||||
"what to install and install the dependencies for USB."
|
"what to install and install the dependencies for USB."
|
||||||
)
|
)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@@ -74,9 +74,9 @@ class Usb(Escpos):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
idVendor: str = "",
|
idVendor: Optional[int] = None,
|
||||||
idProduct: str = "",
|
idProduct: Optional[int] = None,
|
||||||
usb_args: Dict[str, str] = {},
|
usb_args: Dict[str, Union[str, int]] = {},
|
||||||
timeout: Union[int, float] = 0,
|
timeout: Union[int, float] = 0,
|
||||||
in_ep: int = 0x82,
|
in_ep: int = 0x82,
|
||||||
out_ep: int = 0x01,
|
out_ep: int = 0x01,
|
||||||
@@ -181,16 +181,17 @@ class Usb(Escpos):
|
|||||||
except usb.core.USBError as e:
|
except usb.core.USBError as e:
|
||||||
logging.error("Could not set configuration: %s", str(e))
|
logging.error("Could not set configuration: %s", str(e))
|
||||||
|
|
||||||
def _raw(self, msg):
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
|
assert self.device
|
||||||
self.device.write(self.out_ep, msg, self.timeout)
|
self.device.write(self.out_ep, msg, self.timeout)
|
||||||
|
|
||||||
def _read(self):
|
def _read(self) -> bytes:
|
||||||
"""Read a data buffer and return it to the caller."""
|
"""Read a data buffer and return it to the caller."""
|
||||||
|
assert self.device
|
||||||
return self.device.read(self.in_ep, 16)
|
return self.device.read(self.in_ep, 16)
|
||||||
|
|
||||||
@dependency_usb
|
@dependency_usb
|
||||||
|
@@ -45,8 +45,8 @@ def dependency_win32print(func):
|
|||||||
"""Throw a RuntimeError if win32print not installed."""
|
"""Throw a RuntimeError if win32print not installed."""
|
||||||
if not is_usable():
|
if not is_usable():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Printing with Win32Raw requires a win32print library to"
|
"Printing with Win32Raw requires a win32print library to "
|
||||||
"be installed. Please refer to the documentation on"
|
"be installed. Please refer to the documentation on "
|
||||||
"what to install and install the dependencies for win32print."
|
"what to install and install the dependencies for win32print."
|
||||||
)
|
)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@@ -76,7 +76,7 @@ class Win32Raw(Escpos):
|
|||||||
return is_usable()
|
return is_usable()
|
||||||
|
|
||||||
@dependency_win32print
|
@dependency_win32print
|
||||||
def __init__(self, printer_name: str = "", *args, **kwargs):
|
def __init__(self, printer_name: str = "", *args, **kwargs) -> None:
|
||||||
"""Initialize default printer."""
|
"""Initialize default printer."""
|
||||||
Escpos.__init__(self, *args, **kwargs)
|
Escpos.__init__(self, *args, **kwargs)
|
||||||
self.printer_name = printer_name
|
self.printer_name = printer_name
|
||||||
@@ -148,14 +148,17 @@ class Win32Raw(Escpos):
|
|||||||
win32print.ClosePrinter(self._device)
|
win32print.ClosePrinter(self._device)
|
||||||
self._device = False
|
self._device = False
|
||||||
|
|
||||||
def _raw(self, msg) -> None:
|
def _raw(self, msg: bytes) -> None:
|
||||||
"""Print any command sent in raw format.
|
"""Print any command sent in raw format.
|
||||||
|
|
||||||
:param msg: arbitrary code to be printed
|
:param msg: arbitrary code to be printed
|
||||||
:type msg: bytes
|
|
||||||
"""
|
"""
|
||||||
if self.printer_name is None:
|
if self.printer_name is None:
|
||||||
raise DeviceNotFoundError("Printer not found")
|
raise DeviceNotFoundError("Printer not found")
|
||||||
if not self.device:
|
if not self.device:
|
||||||
raise DeviceNotFoundError("Printer job not opened")
|
raise DeviceNotFoundError("Printer job not opened")
|
||||||
win32print.WritePrinter(self.device, msg)
|
win32print.WritePrinter(self.device, msg) # type: ignore
|
||||||
|
|
||||||
|
# there is a bug in the typeshed
|
||||||
|
# https://github.com/mhammond/pywin32/blob/main/win32/src/win32print/win32print.cpp#L976
|
||||||
|
# https://github.com/python/typeshed/blob/main/stubs/pywin32/win32/win32print.pyi#L27C4-L27C4
|
||||||
|
@@ -5,32 +5,32 @@ from escpos.printer import LP, CupsPrinter, Dummy, File, Network, Serial, Usb, W
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def driver():
|
def driver() -> Dummy:
|
||||||
return Dummy()
|
return Dummy()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def usbprinter():
|
def usbprinter() -> Usb:
|
||||||
return Usb()
|
return Usb()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def serialprinter():
|
def serialprinter() -> Serial:
|
||||||
return Serial()
|
return Serial()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def networkprinter():
|
def networkprinter() -> Network:
|
||||||
return Network()
|
return Network()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fileprinter():
|
def fileprinter() -> File:
|
||||||
return File()
|
return File()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def lpprinter():
|
def lpprinter() -> LP:
|
||||||
return LP()
|
return LP()
|
||||||
|
|
||||||
|
|
||||||
|
@@ -14,14 +14,14 @@ import pytest
|
|||||||
import escpos.escpos as escpos
|
import escpos.escpos as escpos
|
||||||
|
|
||||||
|
|
||||||
def test_abstract_base_class_raises():
|
def test_abstract_base_class_raises() -> None:
|
||||||
"""test whether the abstract base class raises an exception for ESC/POS"""
|
"""test whether the abstract base class raises an exception for ESC/POS"""
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
# This call should raise TypeError because of abstractmethod _raw()
|
# This call should raise TypeError because of abstractmethod _raw()
|
||||||
escpos.Escpos()
|
escpos.Escpos() # type: ignore [abstract]
|
||||||
|
|
||||||
|
|
||||||
def test_abstract_base_class():
|
def test_abstract_base_class() -> None:
|
||||||
"""test whether Escpos has the metaclass ABCMeta"""
|
"""test whether Escpos has the metaclass ABCMeta"""
|
||||||
assert issubclass(escpos.Escpos, object)
|
assert issubclass(escpos.Escpos, object)
|
||||||
assert type(escpos.Escpos) is ABCMeta
|
assert type(escpos.Escpos) is ABCMeta
|
||||||
|
@@ -7,7 +7,6 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import pytest
|
|
||||||
from scripttest import TestFileEnvironment as TFE
|
from scripttest import TestFileEnvironment as TFE
|
||||||
|
|
||||||
import escpos
|
import escpos
|
||||||
@@ -31,22 +30,19 @@ class TestCLI:
|
|||||||
"""Contains setups, teardowns, and tests for CLI"""
|
"""Contains setups, teardowns, and tests for CLI"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(cls):
|
def setup_class(cls) -> None:
|
||||||
"""Create a config file to read from"""
|
"""Create a config file to read from"""
|
||||||
with open(CONFIGFILE, "w") as config:
|
with open(CONFIGFILE, "w") as config:
|
||||||
config.write(CONFIG_YAML)
|
config.write(CONFIG_YAML)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def teardown_class(cls):
|
def teardown_class(cls) -> None:
|
||||||
"""Remove config file"""
|
"""Remove config file"""
|
||||||
os.remove(CONFIGFILE)
|
os.remove(CONFIGFILE)
|
||||||
shutil.rmtree(TEST_DIR)
|
shutil.rmtree(TEST_DIR)
|
||||||
|
|
||||||
def setup_method(self):
|
def setup_method(self) -> None:
|
||||||
"""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
|
|
||||||
|
|
||||||
self.env = TFE(
|
self.env = TFE(
|
||||||
base_path=TEST_DIR,
|
base_path=TEST_DIR,
|
||||||
cwd=os.getcwd(),
|
cwd=os.getcwd(),
|
||||||
@@ -64,24 +60,24 @@ class TestCLI:
|
|||||||
finally:
|
finally:
|
||||||
fhandle.close()
|
fhandle.close()
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self) -> None:
|
||||||
"""Destroy printer file and env"""
|
"""Destroy printer file and env"""
|
||||||
os.remove(DEVFILE)
|
os.remove(DEVFILE)
|
||||||
self.env.clear()
|
self.env.clear()
|
||||||
|
|
||||||
def test_cli_help(self):
|
def test_cli_help(self) -> None:
|
||||||
"""Test getting help from cli"""
|
"""Test getting help from cli"""
|
||||||
result = self.env.run("python-escpos", "-h")
|
result = self.env.run("python-escpos", "-h")
|
||||||
assert not result.stderr
|
assert not result.stderr
|
||||||
assert "usage" in result.stdout
|
assert "usage" in result.stdout
|
||||||
|
|
||||||
def test_cli_version(self):
|
def test_cli_version(self) -> None:
|
||||||
"""Test the version string"""
|
"""Test the version string"""
|
||||||
result = self.env.run("python-escpos", "version")
|
result = self.env.run("python-escpos", "version")
|
||||||
assert not result.stderr
|
assert not result.stderr
|
||||||
assert escpos.__version__ == result.stdout.strip()
|
assert escpos.__version__ == result.stdout.strip()
|
||||||
|
|
||||||
def test_cli_version_extended(self):
|
def test_cli_version_extended(self) -> None:
|
||||||
"""Test the extended version information"""
|
"""Test the extended version information"""
|
||||||
result = self.env.run("python-escpos", "version_extended")
|
result = self.env.run("python-escpos", "version_extended")
|
||||||
assert not result.stderr
|
assert not result.stderr
|
||||||
@@ -89,10 +85,7 @@ class TestCLI:
|
|||||||
# test that additional information on e.g. Serial is printed
|
# test that additional information on e.g. Serial is printed
|
||||||
assert "Serial" in result.stdout
|
assert "Serial" in result.stdout
|
||||||
|
|
||||||
@pytest.mark.skip(
|
def test_cli_text(self) -> None:
|
||||||
reason="disable this test as it is not that easy anymore to predict the outcome of this call"
|
|
||||||
)
|
|
||||||
def test_cli_text(self):
|
|
||||||
"""Make sure text returns what we sent it"""
|
"""Make sure text returns what we sent it"""
|
||||||
test_text = "this is some text"
|
test_text = "this is some text"
|
||||||
result = self.env.run(
|
result = self.env.run(
|
||||||
@@ -107,9 +100,11 @@ class TestCLI:
|
|||||||
)
|
)
|
||||||
assert not result.stderr
|
assert not result.stderr
|
||||||
assert DEVFILE_NAME in result.files_updated.keys()
|
assert DEVFILE_NAME in result.files_updated.keys()
|
||||||
assert result.files_updated[DEVFILE_NAME].bytes == test_text + "\n"
|
assert (
|
||||||
|
result.files_updated[DEVFILE_NAME].bytes == "\x1bt\x00" + test_text + "\n"
|
||||||
|
)
|
||||||
|
|
||||||
def test_cli_text_invalid_args(self):
|
def test_cli_text_invalid_args(self) -> None:
|
||||||
"""Test a failure to send valid arguments"""
|
"""Test a failure to send valid arguments"""
|
||||||
result = self.env.run(
|
result = self.env.run(
|
||||||
*(self.default_args + ("text", "--invalid-param", "some data")),
|
*(self.default_args + ("text", "--invalid-param", "some data")),
|
||||||
|
126
test/test_config.py
Normal file
126
test/test_config.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""tests for config module
|
||||||
|
|
||||||
|
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2023 `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:license: MIT
|
||||||
|
"""
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import platformdirs
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import escpos.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dummy_config(path, content=None):
|
||||||
|
"""Generate a dummy config in path"""
|
||||||
|
dummy_config_content = content
|
||||||
|
if not content:
|
||||||
|
dummy_config_content = "printer:\n type: Dummy\n"
|
||||||
|
path.write_text(dummy_config_content)
|
||||||
|
assert path.read_text() == dummy_config_content
|
||||||
|
|
||||||
|
|
||||||
|
def simple_printer_test(config):
|
||||||
|
"""Simple test for the dummy printer."""
|
||||||
|
p = config.printer()
|
||||||
|
p._raw(b"1234")
|
||||||
|
|
||||||
|
assert p.output == b"1234"
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_load_with_invalid_config_yaml(tmp_path):
|
||||||
|
"""Test the loading of a config with a invalid config file (yaml issue)."""
|
||||||
|
# generate a dummy config
|
||||||
|
config_file = tmp_path / "config.yaml"
|
||||||
|
generate_dummy_config(config_file, content="}invalid}yaml}")
|
||||||
|
|
||||||
|
# test the config loading
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
c = config.Config()
|
||||||
|
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
|
||||||
|
c.load(config_path=config_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_load_with_invalid_config_content(tmp_path):
|
||||||
|
"""Test the loading of a config with a invalid config file (content issue)."""
|
||||||
|
# generate a dummy config
|
||||||
|
config_file = tmp_path / "config.yaml"
|
||||||
|
generate_dummy_config(
|
||||||
|
config_file, content="printer:\n type: NoPrinterWithThatName\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# test the config loading
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
c = config.Config()
|
||||||
|
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
|
||||||
|
c.load(config_path=config_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_load_with_missing_config(tmp_path):
|
||||||
|
"""Test the loading of a config that does not exist."""
|
||||||
|
# test the config loading
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
c = config.Config()
|
||||||
|
with pytest.raises(escpos.exceptions.ConfigNotFoundError):
|
||||||
|
c.load(config_path=tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(
|
||||||
|
"This test creates in the actual appdir files and is therefore skipped."
|
||||||
|
)
|
||||||
|
def test_config_load_from_appdir() -> None:
|
||||||
|
"""Test the loading of a config in appdir."""
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
# generate a dummy config
|
||||||
|
config_file = (
|
||||||
|
pathlib.Path(platformdirs.user_config_dir(config.Config._app_name))
|
||||||
|
/ config.Config._config_file
|
||||||
|
)
|
||||||
|
|
||||||
|
generate_dummy_config(config_file)
|
||||||
|
|
||||||
|
# test the config loading
|
||||||
|
c = config.Config()
|
||||||
|
c.load()
|
||||||
|
|
||||||
|
# test the resulting printer object
|
||||||
|
simple_printer_test(c)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_load_with_file(tmp_path):
|
||||||
|
"""Test the loading of a config with a config file."""
|
||||||
|
# generate a dummy config
|
||||||
|
config_file = tmp_path / "config.yaml"
|
||||||
|
generate_dummy_config(config_file)
|
||||||
|
|
||||||
|
# test the config loading
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
c = config.Config()
|
||||||
|
c.load(config_path=config_file)
|
||||||
|
|
||||||
|
# test the resulting printer object
|
||||||
|
simple_printer_test(c)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_load_with_path(tmp_path):
|
||||||
|
"""Test the loading of a config with a config path."""
|
||||||
|
# generate a dummy config
|
||||||
|
config_file = tmp_path / "config.yaml"
|
||||||
|
generate_dummy_config(config_file)
|
||||||
|
|
||||||
|
# test the config loading
|
||||||
|
from escpos import config
|
||||||
|
|
||||||
|
c = config.Config()
|
||||||
|
c.load(config_path=tmp_path)
|
||||||
|
|
||||||
|
# test the resulting printer object
|
||||||
|
simple_printer_test(c)
|
@@ -1,38 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""tests for the non-native part of qr()
|
|
||||||
|
|
||||||
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
|
||||||
:organization: `python-escpos <https://github.com/python-escpos>`_
|
|
||||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
|
|
||||||
:license: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import mock
|
|
||||||
import pytest
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from escpos.printer import 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.
|
|
||||||
|
|
||||||
The type should be PIL.Image
|
|
||||||
"""
|
|
||||||
d = Dummy()
|
|
||||||
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)
|
|
@@ -1,14 +1,13 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import six
|
|
||||||
|
|
||||||
from escpos import printer
|
from escpos import printer
|
||||||
from escpos.constants import BUZZER
|
from escpos.constants import BUZZER
|
||||||
|
|
||||||
|
|
||||||
def test_buzzer_function_with_default_params():
|
def test_buzzer_function_with_default_params() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.buzzer()
|
instance.buzzer()
|
||||||
expected = BUZZER + six.int2byte(2) + six.int2byte(4)
|
expected = BUZZER + bytes((2, 4))
|
||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
@@ -26,10 +25,10 @@ def test_buzzer_function_with_default_params():
|
|||||||
[9, 9],
|
[9, 9],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_buzzer_function(times, duration):
|
def test_buzzer_function(times: int, duration: int) -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.buzzer(times, duration)
|
instance.buzzer(times, duration)
|
||||||
expected = BUZZER + six.int2byte(times) + six.int2byte(duration)
|
expected = BUZZER + bytes((times, duration))
|
||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +45,9 @@ def test_buzzer_function(times, duration):
|
|||||||
[3, 11, "duration must be between 1 and 9"],
|
[3, 11, "duration must be between 1 and 9"],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_buzzer_fuction_with_outrange_values(times, duration, expected_message):
|
def test_buzzer_fuction_with_outrange_values(
|
||||||
|
times: int, duration: int, expected_message: str
|
||||||
|
) -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
instance.buzzer(times, duration)
|
instance.buzzer(times, duration)
|
@@ -6,7 +6,7 @@ import escpos.printer as printer
|
|||||||
from escpos.exceptions import CashDrawerError
|
from escpos.exceptions import CashDrawerError
|
||||||
|
|
||||||
|
|
||||||
def test_raise_CashDrawerError():
|
def test_raise_CashDrawerError() -> None:
|
||||||
"""should raise an error if the sequence is invalid."""
|
"""should raise an error if the sequence is invalid."""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(CashDrawerError):
|
with pytest.raises(CashDrawerError):
|
@@ -1,12 +1,10 @@
|
|||||||
import six
|
|
||||||
|
|
||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
from escpos.constants import GS
|
from escpos.constants import GS
|
||||||
|
|
||||||
|
|
||||||
def test_cut_without_feed():
|
def test_cut_without_feed() -> None:
|
||||||
"""Test cut without feeding paper"""
|
"""Test cut without feeding paper"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.cut(feed=False)
|
instance.cut(feed=False)
|
||||||
expected = GS + b"V" + six.int2byte(66) + b"\x00"
|
expected = GS + b"V" + bytes((66,)) + b"\x00"
|
||||||
assert instance.output == expected
|
assert instance.output == expected
|
@@ -1,7 +1,7 @@
|
|||||||
from escpos.printer import Dummy
|
from escpos.printer import Dummy
|
||||||
|
|
||||||
|
|
||||||
def test_printer_dummy_clear():
|
def test_printer_dummy_clear() -> None:
|
||||||
printer = Dummy()
|
printer = Dummy()
|
||||||
printer.text("Hello")
|
printer.text("Hello")
|
||||||
printer.clear()
|
printer.clear()
|
@@ -16,7 +16,7 @@ from escpos.exceptions import ImageWidthError
|
|||||||
|
|
||||||
|
|
||||||
# Raster format print
|
# Raster format print
|
||||||
def test_bit_image_black():
|
def test_bit_image_black() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid black bit image (raster)
|
Test printing solid black bit image (raster)
|
||||||
"""
|
"""
|
||||||
@@ -30,7 +30,7 @@ def test_bit_image_black():
|
|||||||
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80"
|
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80"
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_white():
|
def test_bit_image_white() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid white bit image (raster)
|
Test printing solid white bit image (raster)
|
||||||
"""
|
"""
|
||||||
@@ -39,7 +39,7 @@ def test_bit_image_white():
|
|||||||
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00"
|
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00"
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_both():
|
def test_bit_image_both() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/white bit image (raster)
|
Test printing black/white bit image (raster)
|
||||||
"""
|
"""
|
||||||
@@ -48,7 +48,7 @@ def test_bit_image_both():
|
|||||||
assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00"
|
assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00"
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_transparent():
|
def test_bit_image_transparent() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/transparent bit image (raster)
|
Test printing black/transparent bit image (raster)
|
||||||
"""
|
"""
|
||||||
@@ -58,7 +58,7 @@ def test_bit_image_transparent():
|
|||||||
|
|
||||||
|
|
||||||
# Column format print
|
# Column format print
|
||||||
def test_bit_image_colfmt_black():
|
def test_bit_image_colfmt_black() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid black bit image (column format)
|
Test printing solid black bit image (column format)
|
||||||
"""
|
"""
|
||||||
@@ -67,7 +67,7 @@ def test_bit_image_colfmt_black():
|
|||||||
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2"
|
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2"
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_colfmt_white():
|
def test_bit_image_colfmt_white() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid white bit image (column format)
|
Test printing solid white bit image (column format)
|
||||||
"""
|
"""
|
||||||
@@ -76,7 +76,7 @@ def test_bit_image_colfmt_white():
|
|||||||
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2"
|
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2"
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_colfmt_both():
|
def test_bit_image_colfmt_both() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/white bit image (column format)
|
Test printing black/white bit image (column format)
|
||||||
"""
|
"""
|
||||||
@@ -87,7 +87,7 @@ def test_bit_image_colfmt_both():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_bit_image_colfmt_transparent():
|
def test_bit_image_colfmt_transparent() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/transparent bit image (column format)
|
Test printing black/transparent bit image (column format)
|
||||||
"""
|
"""
|
||||||
@@ -99,7 +99,7 @@ def test_bit_image_colfmt_transparent():
|
|||||||
|
|
||||||
|
|
||||||
# Graphics print
|
# Graphics print
|
||||||
def test_graphics_black():
|
def test_graphics_black() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid black graphics
|
Test printing solid black graphics
|
||||||
"""
|
"""
|
||||||
@@ -111,7 +111,7 @@ def test_graphics_black():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_graphics_white():
|
def test_graphics_white() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing solid white graphics
|
Test printing solid white graphics
|
||||||
"""
|
"""
|
||||||
@@ -123,7 +123,7 @@ def test_graphics_white():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_graphics_both():
|
def test_graphics_both() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/white graphics
|
Test printing black/white graphics
|
||||||
"""
|
"""
|
||||||
@@ -135,7 +135,7 @@ def test_graphics_both():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_graphics_transparent():
|
def test_graphics_transparent() -> None:
|
||||||
"""
|
"""
|
||||||
Test printing black/transparent graphics
|
Test printing black/transparent graphics
|
||||||
"""
|
"""
|
||||||
@@ -147,7 +147,7 @@ def test_graphics_transparent():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_large_graphics():
|
def test_large_graphics() -> None:
|
||||||
"""
|
"""
|
||||||
Test whether 'large' graphics that induce a fragmentation are handled correctly.
|
Test whether 'large' graphics that induce a fragmentation are handled correctly.
|
||||||
"""
|
"""
|
||||||
@@ -162,13 +162,13 @@ def test_large_graphics():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def dummy_with_width():
|
def dummy_with_width() -> printer.Dummy:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
|
instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
def test_width_too_large(dummy_with_width):
|
def test_width_too_large(dummy_with_width: printer.Dummy) -> None:
|
||||||
"""
|
"""
|
||||||
Test printing an image that is too large in width.
|
Test printing an image that is too large in width.
|
||||||
"""
|
"""
|
||||||
@@ -180,7 +180,7 @@ def test_width_too_large(dummy_with_width):
|
|||||||
instance.image(Image.new("RGB", (384, 200)))
|
instance.image(Image.new("RGB", (384, 200)))
|
||||||
|
|
||||||
|
|
||||||
def test_center_image(dummy_with_width):
|
def test_center_image(dummy_with_width: printer.Dummy) -> None:
|
||||||
instance = dummy_with_width
|
instance = dummy_with_width
|
||||||
|
|
||||||
with pytest.raises(ImageWidthError):
|
with pytest.raises(ImageWidthError):
|
@@ -11,21 +11,21 @@
|
|||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
|
|
||||||
|
|
||||||
def test_function_linedisplay_select_on():
|
def test_function_linedisplay_select_on() -> None:
|
||||||
"""test the linedisplay_select function (activate)"""
|
"""test the linedisplay_select function (activate)"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.linedisplay_select(select_display=True)
|
instance.linedisplay_select(select_display=True)
|
||||||
assert instance.output == b"\x1B\x3D\x02"
|
assert instance.output == b"\x1B\x3D\x02"
|
||||||
|
|
||||||
|
|
||||||
def test_function_linedisplay_select_off():
|
def test_function_linedisplay_select_off() -> None:
|
||||||
"""test the linedisplay_select function (deactivate)"""
|
"""test the linedisplay_select function (deactivate)"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.linedisplay_select(select_display=False)
|
instance.linedisplay_select(select_display=False)
|
||||||
assert instance.output == b"\x1B\x3D\x01"
|
assert instance.output == b"\x1B\x3D\x01"
|
||||||
|
|
||||||
|
|
||||||
def test_function_linedisplay_clear():
|
def test_function_linedisplay_clear() -> None:
|
||||||
"""test the linedisplay_clear function"""
|
"""test the linedisplay_clear function"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.linedisplay_clear()
|
instance.linedisplay_clear()
|
@@ -11,14 +11,14 @@
|
|||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
|
|
||||||
|
|
||||||
def test_function_panel_button_on():
|
def test_function_panel_button_on() -> None:
|
||||||
"""test the panel button function (enabling) by comparing output"""
|
"""test the panel button function (enabling) by comparing output"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.panel_buttons()
|
instance.panel_buttons()
|
||||||
assert instance.output == b"\x1B\x63\x35\x00"
|
assert instance.output == b"\x1B\x63\x35\x00"
|
||||||
|
|
||||||
|
|
||||||
def test_function_panel_button_off():
|
def test_function_panel_button_off() -> None:
|
||||||
"""test the panel button function (disabling) by comparing output"""
|
"""test the panel button function (disabling) by comparing output"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.panel_buttons(False)
|
instance.panel_buttons(False)
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
:author: `Michael Billington <michael.billington@gmail.com>`_
|
:author: `Michael Billington <michael.billington@gmail.com>`_
|
||||||
:organization: `python-escpos <https://github.com/python-escpos>`_
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
:copyright: Copyright (c) 2023 `Michael Billington <michael.billington@gmail.com>`_
|
||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ import escpos.printer as printer
|
|||||||
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
|
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
|
||||||
|
|
||||||
|
|
||||||
def test_defaults():
|
def test_defaults() -> None:
|
||||||
"""Test QR code with defaults"""
|
"""Test QR code with defaults"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.qr("1234", native=True)
|
instance.qr("1234", native=True)
|
||||||
@@ -25,14 +25,14 @@ def test_defaults():
|
|||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
def test_empty():
|
def test_empty() -> None:
|
||||||
"""Test QR printing blank code"""
|
"""Test QR printing blank code"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.qr("", native=True)
|
instance.qr("", native=True)
|
||||||
assert instance.output == b""
|
assert instance.output == b""
|
||||||
|
|
||||||
|
|
||||||
def test_ec():
|
def test_ec() -> None:
|
||||||
"""Test QR error correction setting"""
|
"""Test QR error correction setting"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
|
instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
|
||||||
@@ -43,7 +43,7 @@ def test_ec():
|
|||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
def test_size():
|
def test_size() -> None:
|
||||||
"""Test QR box size"""
|
"""Test QR box size"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.qr("1234", native=True, size=7)
|
instance.qr("1234", native=True, size=7)
|
||||||
@@ -54,7 +54,7 @@ def test_size():
|
|||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
def test_model():
|
def test_model() -> None:
|
||||||
"""Test QR model"""
|
"""Test QR model"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.qr("1234", native=True, model=QR_MODEL_1)
|
instance.qr("1234", native=True, model=QR_MODEL_1)
|
||||||
@@ -65,44 +65,28 @@ def test_model():
|
|||||||
assert instance.output == expected
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_ec():
|
def test_invalid_ec() -> None:
|
||||||
"""Test invalid QR error correction"""
|
"""Test invalid QR error correction"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
instance.qr("1234", native=True, ec=-1)
|
instance.qr("1234", native=True, ec=-1)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_size():
|
def test_invalid_size() -> None:
|
||||||
"""Test invalid QR size"""
|
"""Test invalid QR size"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
instance.qr("1234", native=True, size=0)
|
instance.qr("1234", native=True, size=0)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_model():
|
def test_invalid_model() -> None:
|
||||||
"""Test invalid QR model"""
|
"""Test invalid QR model"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
instance.qr("1234", native=True, model="Hello")
|
instance.qr("1234", native=True, model="Hello")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("this test has to be debugged")
|
def test_image_invalid_model() -> None:
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def test_image_invalid_model():
|
|
||||||
"""Test unsupported QR model as image"""
|
"""Test unsupported QR model as image"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@@ -114,6 +98,6 @@ def instance():
|
|||||||
return printer.Dummy()
|
return printer.Dummy()
|
||||||
|
|
||||||
|
|
||||||
def test_center_not_implementer(instance):
|
def test_center_not_implementer(instance: printer.Dummy) -> None:
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
instance.qr("test", center=True, native=True)
|
instance.qr("test", center=True, native=True)
|
99
test/test_functions/test_function_qr_non-native.py
Normal file
99
test/test_functions/test_function_qr_non-native.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""tests for the non-native part of qr()
|
||||||
|
|
||||||
|
:author: `Patrick Kanzler <dev@pkanzler.de>`_
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2023 `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:license: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import pytest
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from escpos.printer import Dummy
|
||||||
|
|
||||||
|
|
||||||
|
def test_image() -> None:
|
||||||
|
"""Test QR as image"""
|
||||||
|
instance = Dummy()
|
||||||
|
image_arguments = {
|
||||||
|
"high_density_vertical": True,
|
||||||
|
"high_density_horizontal": True,
|
||||||
|
"impl": "bitImageRaster",
|
||||||
|
"fragment_height": 960,
|
||||||
|
"center": False,
|
||||||
|
}
|
||||||
|
instance.qr("1", native=False, image_arguments=image_arguments, size=1)
|
||||||
|
print(instance.output)
|
||||||
|
expected = (
|
||||||
|
b"\x1bt\x00\n"
|
||||||
|
b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f\x1d\xfcAu\x04]\x1dt]et"
|
||||||
|
b"]%tAI\x04\x7fU\xfc\x00 \x00}\xca\xa8h\xdf u\x95\x80x/ \x0b\xf4\x98\x00"
|
||||||
|
b"T\x90\x7fzxA\x00\xd0]zp]o ]u\x80Ao(\x7fd\x90\x00\x00\x00"
|
||||||
|
b"\n\n"
|
||||||
|
)
|
||||||
|
assert instance.output == expected
|
||||||
|
|
||||||
|
|
||||||
|
@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.
|
||||||
|
|
||||||
|
The type should be PIL.Image
|
||||||
|
"""
|
||||||
|
d = Dummy()
|
||||||
|
d.qr("LoremIpsum")
|
||||||
|
args, kwargs = img_function.call_args
|
||||||
|
assert isinstance(args[0], Image.Image)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("escpos.printer.Dummy.image", spec=Dummy)
|
||||||
|
def test_parameter_image_arguments_passed_to_image_function(img_function):
|
||||||
|
"""Test the parameter passed to non-native qr printing."""
|
||||||
|
d = Dummy()
|
||||||
|
d.qr(
|
||||||
|
"LoremIpsum",
|
||||||
|
native=False,
|
||||||
|
image_arguments={
|
||||||
|
"impl": "bitImageColumn",
|
||||||
|
"high_density_vertical": False,
|
||||||
|
"center": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
args, kwargs = img_function.call_args
|
||||||
|
assert "impl" in kwargs
|
||||||
|
assert kwargs["impl"] == "bitImageColumn"
|
||||||
|
assert "high_density_vertical" in kwargs
|
||||||
|
assert kwargs["high_density_vertical"] is False
|
||||||
|
assert "center" in kwargs
|
||||||
|
assert kwargs["center"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def instance():
|
||||||
|
return Dummy()
|
||||||
|
|
||||||
|
|
||||||
|
def test_center(instance):
|
||||||
|
"""Test printing qr codes."""
|
||||||
|
instance.qr("LoremIpsum", center=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecated_arguments(instance):
|
||||||
|
"""Test deprecation warning."""
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# cause all warnings to always be triggered
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
instance.qr(
|
||||||
|
"LoremIpsum",
|
||||||
|
impl="bitImageRaster",
|
||||||
|
image_arguments={"impl": "bitImageColumn"},
|
||||||
|
)
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated" in str(w[-1].message)
|
@@ -1,12 +1,13 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import six
|
|
||||||
|
|
||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
|
from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
|
||||||
from escpos.exceptions import SetVariableError
|
from escpos.exceptions import SetVariableError
|
||||||
|
|
||||||
|
|
||||||
def test_default_values_with_default():
|
def test_default_values_with_default() -> None:
|
||||||
"""Default test, please copy and paste this block to test set method calls"""
|
"""Default test, please copy and paste this block to test set method calls"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default()
|
instance.set_with_default()
|
||||||
@@ -26,7 +27,7 @@ def test_default_values_with_default():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_default_values():
|
def test_default_values() -> None:
|
||||||
"""Default test"""
|
"""Default test"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set()
|
instance.set()
|
||||||
@@ -37,7 +38,7 @@ def test_default_values():
|
|||||||
# Size tests
|
# Size tests
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2h():
|
def test_set_size_2h() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(double_height=True)
|
instance.set_with_default(double_height=True)
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ def test_set_size_2h():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2h_no_default():
|
def test_set_size_2h_no_default() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set(double_height=True)
|
instance.set(double_height=True)
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ def test_set_size_2h_no_default():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2w():
|
def test_set_size_2w() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(double_width=True)
|
instance.set_with_default(double_width=True)
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ def test_set_size_2w():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2w_no_default():
|
def test_set_size_2w_no_default() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set(double_width=True)
|
instance.set(double_width=True)
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ def test_set_size_2w_no_default():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2x():
|
def test_set_size_2x() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(double_height=True, double_width=True)
|
instance.set_with_default(double_height=True, double_width=True)
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ def test_set_size_2x():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_2x_no_default():
|
def test_set_size_2x_no_default() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set(double_width=True, double_height=True)
|
instance.set(double_width=True, double_height=True)
|
||||||
|
|
||||||
@@ -130,13 +131,13 @@ def test_set_size_2x_no_default():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_size_custom():
|
def test_set_size_custom() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(custom_size=True, width=8, height=7)
|
instance.set_with_default(custom_size=True, width=8, height=7)
|
||||||
|
|
||||||
expected_sequence = (
|
expected_sequence = (
|
||||||
TXT_SIZE, # Custom text size, no normal reset
|
TXT_SIZE, # Custom text size, no normal reset
|
||||||
six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]),
|
bytes((TXT_STYLE["width"][8] + TXT_STYLE["height"][7],)),
|
||||||
TXT_STYLE["flip"][False], # Flip OFF
|
TXT_STYLE["flip"][False], # Flip OFF
|
||||||
TXT_STYLE["smooth"][False], # Smooth OFF
|
TXT_STYLE["smooth"][False], # Smooth OFF
|
||||||
TXT_STYLE["bold"][False], # Bold OFF
|
TXT_STYLE["bold"][False], # Bold OFF
|
||||||
@@ -151,13 +152,13 @@ def test_set_size_custom():
|
|||||||
|
|
||||||
@pytest.mark.parametrize("width", [1, 8])
|
@pytest.mark.parametrize("width", [1, 8])
|
||||||
@pytest.mark.parametrize("height", [1, 8])
|
@pytest.mark.parametrize("height", [1, 8])
|
||||||
def test_set_size_custom_no_default(width, height):
|
def test_set_size_custom_no_default(width: int, height: int) -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set(custom_size=True, width=width, height=height)
|
instance.set(custom_size=True, width=width, height=height)
|
||||||
|
|
||||||
expected_sequence = (
|
expected_sequence = (
|
||||||
TXT_SIZE, # Custom text size, no normal reset
|
TXT_SIZE, # Custom text size, no normal reset
|
||||||
six.int2byte(TXT_STYLE["width"][width] + TXT_STYLE["height"][height]),
|
bytes((TXT_STYLE["width"][width] + TXT_STYLE["height"][height],)),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
@@ -165,7 +166,9 @@ def test_set_size_custom_no_default(width, height):
|
|||||||
|
|
||||||
@pytest.mark.parametrize("width", [None, 0, 9, 10, 4444])
|
@pytest.mark.parametrize("width", [None, 0, 9, 10, 4444])
|
||||||
@pytest.mark.parametrize("height", [None, 0, 9, 10, 4444])
|
@pytest.mark.parametrize("height", [None, 0, 9, 10, 4444])
|
||||||
def test_set_size_custom_invalid_input(width, height):
|
def test_set_size_custom_invalid_input(
|
||||||
|
width: Optional[int], height: Optional[int]
|
||||||
|
) -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
with pytest.raises(SetVariableError):
|
with pytest.raises(SetVariableError):
|
||||||
instance.set(custom_size=True, width=width, height=height)
|
instance.set(custom_size=True, width=width, height=height)
|
||||||
@@ -174,7 +177,7 @@ def test_set_size_custom_invalid_input(width, height):
|
|||||||
# Flip
|
# Flip
|
||||||
|
|
||||||
|
|
||||||
def test_set_flip():
|
def test_set_flip() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(flip=True)
|
instance.set_with_default(flip=True)
|
||||||
|
|
||||||
@@ -193,7 +196,7 @@ def test_set_flip():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_flip_no_default():
|
def test_set_flip_no_default() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set(flip=True)
|
instance.set(flip=True)
|
||||||
|
|
||||||
@@ -205,7 +208,7 @@ def test_set_flip_no_default():
|
|||||||
# Smooth
|
# Smooth
|
||||||
|
|
||||||
|
|
||||||
def test_smooth():
|
def test_smooth() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(smooth=True)
|
instance.set_with_default(smooth=True)
|
||||||
|
|
||||||
@@ -227,7 +230,7 @@ def test_smooth():
|
|||||||
# Type
|
# Type
|
||||||
|
|
||||||
|
|
||||||
def test_set_bold():
|
def test_set_bold() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(bold=True)
|
instance.set_with_default(bold=True)
|
||||||
|
|
||||||
@@ -246,7 +249,7 @@ def test_set_bold():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_underline():
|
def test_set_underline() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(underline=1)
|
instance.set_with_default(underline=1)
|
||||||
|
|
||||||
@@ -265,7 +268,7 @@ def test_set_underline():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_set_underline2():
|
def test_set_underline2() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(underline=2)
|
instance.set_with_default(underline=2)
|
||||||
|
|
||||||
@@ -287,7 +290,7 @@ def test_set_underline2():
|
|||||||
# Align
|
# Align
|
||||||
|
|
||||||
|
|
||||||
def test_align_center():
|
def test_align_center() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(align="center")
|
instance.set_with_default(align="center")
|
||||||
|
|
||||||
@@ -306,7 +309,7 @@ def test_align_center():
|
|||||||
assert instance.output == b"".join(expected_sequence)
|
assert instance.output == b"".join(expected_sequence)
|
||||||
|
|
||||||
|
|
||||||
def test_align_right():
|
def test_align_right() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(align="right")
|
instance.set_with_default(align="right")
|
||||||
|
|
||||||
@@ -328,7 +331,7 @@ def test_align_right():
|
|||||||
# Densities
|
# Densities
|
||||||
|
|
||||||
|
|
||||||
def test_densities():
|
def test_densities() -> None:
|
||||||
for density in range(8):
|
for density in range(8):
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(density=density)
|
instance.set_with_default(density=density)
|
||||||
@@ -352,7 +355,7 @@ def test_densities():
|
|||||||
# Invert
|
# Invert
|
||||||
|
|
||||||
|
|
||||||
def test_invert():
|
def test_invert() -> None:
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.set_with_default(invert=True)
|
instance.set_with_default(invert=True)
|
||||||
|
|
@@ -11,16 +11,16 @@ def instance():
|
|||||||
return printer.Dummy()
|
return printer.Dummy()
|
||||||
|
|
||||||
|
|
||||||
def test_soft_barcode_ean8_invalid(instance):
|
def test_soft_barcode_ean8_invalid(instance: printer.Dummy) -> None:
|
||||||
"""test with an invalid barcode"""
|
"""test with an invalid barcode"""
|
||||||
with pytest.raises(barcode.errors.BarcodeError):
|
with pytest.raises(barcode.errors.BarcodeError):
|
||||||
instance.barcode("1234", "ean8", force_software=True)
|
instance.barcode("1234", "ean8", force_software=True)
|
||||||
|
|
||||||
|
|
||||||
def test_soft_barcode_ean8(instance):
|
def test_soft_barcode_ean8(instance: printer.Dummy) -> None:
|
||||||
"""test with a valid ean8 barcode"""
|
"""test with a valid ean8 barcode"""
|
||||||
instance.barcode("1234567", "ean8", force_software=True)
|
instance.barcode("1234567", "ean8", force_software=True)
|
||||||
|
|
||||||
|
|
||||||
def test_soft_barcode_ean8_nocenter(instance):
|
def test_soft_barcode_ean8_nocenter(instance: printer.Dummy) -> None:
|
||||||
instance.barcode("1234567", "ean8", align_ct=False, force_software=True)
|
instance.barcode("1234567", "ean8", align_ct=False, force_software=True)
|
80
test/test_functions/test_function_software_columns.py
Normal file
80
test/test_functions/test_function_software_columns.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""tests for software_columns
|
||||||
|
|
||||||
|
:author: Benito López and the python-escpos developers
|
||||||
|
:organization: `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:copyright: Copyright (c) 2024 `python-escpos <https://github.com/python-escpos>`_
|
||||||
|
:license: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_rearrange_into_cols(driver) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN a list of columnable text
|
||||||
|
WHEN the column width is different for each column and some strings exceed the max width
|
||||||
|
THEN check the strings are properly wrapped, truncated and rearranged into some columns
|
||||||
|
"""
|
||||||
|
|
||||||
|
output = driver._rearrange_into_cols(
|
||||||
|
text_list=["fits", "row1 row2", "truncate and wrap"], widths=[4, 5, 6]
|
||||||
|
)
|
||||||
|
assert output == [["fits", "row1", "trunc."], ["", "row2", "and"], ["", "", "wrap"]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_padding_into_cols(driver) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN a list of strings
|
||||||
|
WHEN adding padding and different alignments to each string
|
||||||
|
THEN check the strings are correctly padded and aligned
|
||||||
|
"""
|
||||||
|
|
||||||
|
output = driver._add_padding_into_cols(
|
||||||
|
text_list=["col1", "col2", "col3", "col 4"],
|
||||||
|
widths=[6, 6, 6, 6],
|
||||||
|
align=["center", "left", "right", "justify"],
|
||||||
|
)
|
||||||
|
assert output == [" col1 ", "col2 ", " col3", "col 4"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text_list", ["", [], None])
|
||||||
|
@pytest.mark.parametrize("widths", [30.5, "30", None])
|
||||||
|
@pytest.mark.parametrize("align", ["invalid_align_name", "", None])
|
||||||
|
def test_software_columns_invalid_args(driver, text_list, widths, align) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN a dummy printer object
|
||||||
|
WHEN non valid params are passed
|
||||||
|
THEN check raise exception
|
||||||
|
"""
|
||||||
|
bad_text_list = {"text_list": text_list, "widths": 5, "align": "left"}
|
||||||
|
bad_widths = {"text_list": ["valid"], "widths": widths, "align": "left"}
|
||||||
|
bad_align = {"text_list": ["valid"], "widths": 5, "align": align}
|
||||||
|
|
||||||
|
bad_args = [bad_text_list, bad_widths, bad_align]
|
||||||
|
for kwargs in bad_args:
|
||||||
|
with pytest.raises((TypeError, ValueError)):
|
||||||
|
driver.software_columns(**kwargs)
|
||||||
|
driver.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text_list",
|
||||||
|
[
|
||||||
|
["col1", "col2", "col3"],
|
||||||
|
["wrap this string", "wrap this string", "wrap this string"],
|
||||||
|
["truncate_this_string", "truncate_this_string", "truncate_this_string"],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("widths", [[10, 10, 10], [10], 30])
|
||||||
|
@pytest.mark.parametrize("align", [["center", "left", "right"], ["center"], "justify"])
|
||||||
|
def test_software_columns_valid_args(driver, text_list, widths, align) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN a dummy printer object
|
||||||
|
WHEN valid params are passed
|
||||||
|
THEN check no errors
|
||||||
|
"""
|
||||||
|
driver.software_columns(text_list=text_list, widths=widths, align=align)
|
||||||
|
driver.close()
|
@@ -10,26 +10,25 @@
|
|||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
import mock
|
import mock
|
||||||
import pytest
|
from hypothesis import given
|
||||||
from hypothesis import assume, given
|
|
||||||
|
|
||||||
from escpos.printer import Dummy
|
from escpos.printer import Dummy
|
||||||
|
|
||||||
|
|
||||||
def get_printer():
|
def get_printer() -> Dummy:
|
||||||
return Dummy(magic_encode_args={"disabled": True, "encoding": "CP437"})
|
return Dummy(magic_encode_args={"disabled": True, "encoding": "CP437"})
|
||||||
|
|
||||||
|
|
||||||
@given(text=st.text())
|
@given(text=st.text())
|
||||||
def test_text(text):
|
def test_text(text: str):
|
||||||
"""Test that text() calls the MagicEncode object."""
|
"""Test that text() calls the MagicEncode object."""
|
||||||
instance = get_printer()
|
instance = get_printer()
|
||||||
instance.magic.write = mock.Mock()
|
with mock.patch.object(instance.magic, "write") as write:
|
||||||
instance.text(text)
|
instance.text(text)
|
||||||
instance.magic.write.assert_called_with(text)
|
write.assert_called_with(text)
|
||||||
|
|
||||||
|
|
||||||
def test_block_text():
|
def test_block_text() -> None:
|
||||||
printer = get_printer()
|
printer = get_printer()
|
||||||
printer.block_text(
|
printer.block_text(
|
||||||
"All the presidents men were eating falafel for breakfast.", font="a"
|
"All the presidents men were eating falafel for breakfast.", font="a"
|
||||||
@@ -39,25 +38,25 @@ def test_block_text():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_textln():
|
def test_textln() -> None:
|
||||||
printer = get_printer()
|
printer = get_printer()
|
||||||
printer.textln("hello, world")
|
printer.textln("hello, world")
|
||||||
assert printer.output == b"hello, world\n"
|
assert printer.output == b"hello, world\n"
|
||||||
|
|
||||||
|
|
||||||
def test_textln_empty():
|
def test_textln_empty() -> None:
|
||||||
printer = get_printer()
|
printer = get_printer()
|
||||||
printer.textln()
|
printer.textln()
|
||||||
assert printer.output == b"\n"
|
assert printer.output == b"\n"
|
||||||
|
|
||||||
|
|
||||||
def test_ln():
|
def test_ln() -> None:
|
||||||
printer = get_printer()
|
printer = get_printer()
|
||||||
printer.ln()
|
printer.ln()
|
||||||
assert printer.output == b"\n"
|
assert printer.output == b"\n"
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_ln():
|
def test_multiple_ln() -> None:
|
||||||
printer = get_printer()
|
printer = get_printer()
|
||||||
printer.ln(3)
|
printer.ln(3)
|
||||||
assert printer.output == b"\n\n\n"
|
assert printer.output == b"\n\n\n"
|
@@ -3,19 +3,19 @@ import pytest
|
|||||||
from escpos.printer import Dummy
|
from escpos.printer import Dummy
|
||||||
|
|
||||||
|
|
||||||
def test_line_spacing_code_gen():
|
def test_line_spacing_code_gen() -> None:
|
||||||
printer = Dummy()
|
printer = Dummy()
|
||||||
printer.line_spacing(10)
|
printer.line_spacing(10)
|
||||||
assert printer.output == b"\x1b3\n"
|
assert printer.output == b"\x1b3\n"
|
||||||
|
|
||||||
|
|
||||||
def test_line_spacing_rest():
|
def test_line_spacing_rest() -> None:
|
||||||
printer = Dummy()
|
printer = Dummy()
|
||||||
printer.line_spacing()
|
printer.line_spacing()
|
||||||
assert printer.output == b"\x1b2"
|
assert printer.output == b"\x1b2"
|
||||||
|
|
||||||
|
|
||||||
def test_line_spacing_error_handling():
|
def test_line_spacing_error_handling() -> None:
|
||||||
printer = Dummy()
|
printer = Dummy()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
printer.line_spacing(99, divisor=44)
|
printer.line_spacing(99, divisor=44)
|
@@ -7,11 +7,12 @@ converted to ESC/POS column & raster formats.
|
|||||||
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
|
||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from escpos.image import EscposImage
|
from escpos.image import EscposImage
|
||||||
|
|
||||||
|
|
||||||
def test_image_black():
|
def test_image_black() -> None:
|
||||||
"""
|
"""
|
||||||
Test rendering solid black image
|
Test rendering solid black image
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +20,7 @@ def test_image_black():
|
|||||||
_load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"])
|
_load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"])
|
||||||
|
|
||||||
|
|
||||||
def test_image_black_transparent():
|
def test_image_black_transparent() -> None:
|
||||||
"""
|
"""
|
||||||
Test rendering black/transparent image
|
Test rendering black/transparent image
|
||||||
"""
|
"""
|
||||||
@@ -29,7 +30,7 @@ def test_image_black_transparent():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_image_black_white():
|
def test_image_black_white() -> None:
|
||||||
"""
|
"""
|
||||||
Test rendering black/white image
|
Test rendering black/white image
|
||||||
"""
|
"""
|
||||||
@@ -39,7 +40,7 @@ def test_image_black_white():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_image_white():
|
def test_image_white() -> None:
|
||||||
"""
|
"""
|
||||||
Test rendering solid white image
|
Test rendering solid white image
|
||||||
"""
|
"""
|
||||||
@@ -47,7 +48,7 @@ def test_image_white():
|
|||||||
_load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"])
|
_load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"])
|
||||||
|
|
||||||
|
|
||||||
def test_split():
|
def test_split() -> None:
|
||||||
"""
|
"""
|
||||||
test whether the split-function works as expected
|
test whether the split-function works as expected
|
||||||
"""
|
"""
|
||||||
@@ -62,12 +63,12 @@ def test_split():
|
|||||||
|
|
||||||
|
|
||||||
def _load_and_check_img(
|
def _load_and_check_img(
|
||||||
filename,
|
filename: str,
|
||||||
width_expected,
|
width_expected: int,
|
||||||
height_expected,
|
height_expected: int,
|
||||||
raster_format_expected,
|
raster_format_expected: bytes,
|
||||||
column_format_expected,
|
column_format_expected: List[bytes],
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
|
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
|
||||||
"""
|
"""
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
|
|
||||||
|
|
||||||
def test_instantiation():
|
def test_instantiation() -> None:
|
||||||
"""test the instantiation of a escpos-printer class and basic printing"""
|
"""test the instantiation of a escpos-printer class and basic printing"""
|
||||||
instance = printer.Dummy()
|
instance = printer.Dummy()
|
||||||
instance.text("This is a test\n")
|
instance.text("This is a test\n")
|
||||||
|
@@ -13,7 +13,7 @@ import tempfile
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def test_instantiation():
|
def test_instantiation() -> None:
|
||||||
"""test the instantiation of a escpos-printer class"""
|
"""test the instantiation of a escpos-printer class"""
|
||||||
# inject an environment variable that points to an empty capabilities file
|
# inject an environment variable that points to an empty capabilities file
|
||||||
os.environ["ESCPOS_CAPABILITIES_FILE"] = tempfile.NamedTemporaryFile().name
|
os.environ["ESCPOS_CAPABILITIES_FILE"] = tempfile.NamedTemporaryFile().name
|
||||||
|
@@ -7,13 +7,15 @@
|
|||||||
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
|
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
|
||||||
:license: MIT
|
:license: MIT
|
||||||
"""
|
"""
|
||||||
|
import types
|
||||||
|
import typing
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
import pytest
|
import pytest
|
||||||
from hypothesis import example, given
|
from hypothesis import example, given
|
||||||
|
|
||||||
from escpos.exceptions import CharCodeError, Error
|
from escpos import printer
|
||||||
|
from escpos.exceptions import Error
|
||||||
from escpos.katakana import encode_katakana
|
from escpos.katakana import encode_katakana
|
||||||
from escpos.magicencode import Encoder, MagicEncode
|
from escpos.magicencode import Encoder, MagicEncode
|
||||||
|
|
||||||
@@ -23,23 +25,23 @@ class TestEncoder:
|
|||||||
Tests the single encoders.
|
Tests the single encoders.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_can_encode(self):
|
def test_can_encode(self) -> None:
|
||||||
assert not Encoder({"CP437": 1}).can_encode("CP437", "€")
|
assert not Encoder({"CP437": 1}).can_encode("CP437", "€")
|
||||||
assert 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({"foobar": 1}).can_encode("foobar", "a")
|
||||||
|
|
||||||
def test_find_suitable_encoding(self):
|
def test_find_suitable_encoding(self) -> None:
|
||||||
assert not Encoder({"CP437": 1}).find_suitable_encoding("€")
|
assert not Encoder({"CP437": 1}).find_suitable_encoding("€")
|
||||||
assert Encoder({"CP858": 1}).find_suitable_encoding("€") == "CP858"
|
assert Encoder({"CP858": 1}).find_suitable_encoding("€") == "CP858"
|
||||||
|
|
||||||
def test_find_suitable_encoding_unnecessary_codepage_swap(self):
|
def test_find_suitable_encoding_unnecessary_codepage_swap(self) -> None:
|
||||||
enc = Encoder({"CP857": 1, "CP437": 2, "CP1252": 3, "CP852": 4, "CP858": 5})
|
enc = Encoder({"CP857": 1, "CP437": 2, "CP1252": 3, "CP852": 4, "CP858": 5})
|
||||||
# desired behavior would be that the encoder always stays in the lower
|
# desired behavior would be that the encoder always stays in the lower
|
||||||
# available codepages if possible
|
# available codepages if possible
|
||||||
for character in ("Á", "É", "Í", "Ó", "Ú"):
|
for character in ("Á", "É", "Í", "Ó", "Ú"):
|
||||||
assert enc.find_suitable_encoding(character) == "CP857"
|
assert enc.find_suitable_encoding(character) == "CP857"
|
||||||
|
|
||||||
def test_get_encoding(self):
|
def test_get_encoding(self) -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Encoder({}).get_encoding_name("latin1")
|
Encoder({}).get_encoding_name("latin1")
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ class TestMagicEncode:
|
|||||||
Test initialization.
|
Test initialization.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_disabled_requires_encoding(self, driver):
|
def test_disabled_requires_encoding(self, driver: printer.Dummy) -> None:
|
||||||
"""
|
"""
|
||||||
Test that disabled without encoder raises an error.
|
Test that disabled without encoder raises an error.
|
||||||
|
|
||||||
@@ -64,33 +66,33 @@ class TestMagicEncode:
|
|||||||
MagicEncode(driver, disabled=True)
|
MagicEncode(driver, disabled=True)
|
||||||
|
|
||||||
class TestWriteWithEncoding:
|
class TestWriteWithEncoding:
|
||||||
def test_init_from_none(self, driver):
|
def test_init_from_none(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver, encoding=None)
|
encode = MagicEncode(driver, encoding=None)
|
||||||
encode.write_with_encoding("CP858", "€ ist teuro.")
|
encode.write_with_encoding("CP858", "€ ist teuro.")
|
||||||
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
|
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
|
||||||
|
|
||||||
def test_change_from_another(self, driver):
|
def test_change_from_another(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver, encoding="CP437")
|
encode = MagicEncode(driver, encoding="CP437")
|
||||||
encode.write_with_encoding("CP858", "€ ist teuro.")
|
encode.write_with_encoding("CP858", "€ ist teuro.")
|
||||||
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
|
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
|
||||||
|
|
||||||
def test_no_change(self, driver):
|
def test_no_change(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver, encoding="CP858")
|
encode = MagicEncode(driver, encoding="CP858")
|
||||||
encode.write_with_encoding("CP858", "€ ist teuro.")
|
encode.write_with_encoding("CP858", "€ ist teuro.")
|
||||||
assert driver.output == b"\xd5 ist teuro."
|
assert driver.output == b"\xd5 ist teuro."
|
||||||
|
|
||||||
class TestWrite:
|
class TestWrite:
|
||||||
def test_write(self, driver):
|
def test_write(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver)
|
encode = MagicEncode(driver)
|
||||||
encode.write("€ ist teuro.")
|
encode.write("€ ist teuro.")
|
||||||
assert driver.output == b"\x1bt\x0f\xa4 ist teuro."
|
assert driver.output == b"\x1bt\x0f\xa4 ist teuro."
|
||||||
|
|
||||||
def test_write_disabled(self, driver):
|
def test_write_disabled(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver, encoding="CP437", disabled=True)
|
encode = MagicEncode(driver, encoding="CP437", disabled=True)
|
||||||
encode.write("€ ist teuro.")
|
encode.write("€ ist teuro.")
|
||||||
assert driver.output == b"? ist teuro."
|
assert driver.output == b"? ist teuro."
|
||||||
|
|
||||||
def test_write_no_codepage(self, driver):
|
def test_write_no_codepage(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(
|
encode = MagicEncode(
|
||||||
driver,
|
driver,
|
||||||
defaultsymbol="_",
|
defaultsymbol="_",
|
||||||
@@ -101,7 +103,7 @@ class TestMagicEncode:
|
|||||||
assert driver.output == b"_ ist teuro."
|
assert driver.output == b"_ ist teuro."
|
||||||
|
|
||||||
class TestForceEncoding:
|
class TestForceEncoding:
|
||||||
def test(self, driver):
|
def test(self, driver: printer.Dummy) -> None:
|
||||||
encode = MagicEncode(driver)
|
encode = MagicEncode(driver)
|
||||||
encode.force_encoding("CP437")
|
encode.force_encoding("CP437")
|
||||||
assert driver.output == b"\x1bt\x00"
|
assert driver.output == b"\x1bt\x00"
|
||||||
@@ -110,6 +112,7 @@ class TestMagicEncode:
|
|||||||
assert driver.output == b"\x1bt\x00? ist teuro."
|
assert driver.output == b"\x1bt\x00? ist teuro."
|
||||||
|
|
||||||
|
|
||||||
|
jaconv: typing.Optional[types.ModuleType]
|
||||||
try:
|
try:
|
||||||
import jaconv
|
import jaconv
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -122,9 +125,9 @@ class TestKatakana:
|
|||||||
@example("カタカナ")
|
@example("カタカナ")
|
||||||
@example("あいうえお")
|
@example("あいうえお")
|
||||||
@example("ハンカクカタカナ")
|
@example("ハンカクカタカナ")
|
||||||
def test_accept(self, text):
|
def test_accept(self, text: str) -> None:
|
||||||
encode_katakana(text)
|
encode_katakana(text)
|
||||||
|
|
||||||
def test_result(self):
|
def test_result(self) -> None:
|
||||||
assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
|
assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
|
||||||
assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5"
|
assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5"
|
||||||
|
@@ -19,7 +19,7 @@ pytestmark = pytest.mark.skipif(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_device_not_initialized(cupsprinter):
|
def test_device_not_initialized(cupsprinter) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN it is not initialized
|
WHEN it is not initialized
|
||||||
@@ -28,7 +28,7 @@ def test_device_not_initialized(cupsprinter):
|
|||||||
assert cupsprinter._device is False
|
assert cupsprinter._device is False
|
||||||
|
|
||||||
|
|
||||||
def test_open_raise_exception(cupsprinter, devicenotfounderror):
|
def test_open_raise_exception(cupsprinter, devicenotfounderror) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN open() is set to raise a DeviceNotFoundError on error
|
WHEN open() is set to raise a DeviceNotFoundError on error
|
||||||
@@ -40,7 +40,7 @@ def test_open_raise_exception(cupsprinter, devicenotfounderror):
|
|||||||
cupsprinter.open(raise_not_found=True)
|
cupsprinter.open(raise_not_found=True)
|
||||||
|
|
||||||
|
|
||||||
def test_open_not_raise_exception(cupsprinter, caplog):
|
def test_open_not_raise_exception(cupsprinter, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN open() is set to not raise on error but simply cancel
|
WHEN open() is set to not raise on error but simply cancel
|
||||||
@@ -55,7 +55,7 @@ def test_open_not_raise_exception(cupsprinter, caplog):
|
|||||||
assert cupsprinter.device is None
|
assert cupsprinter.device is None
|
||||||
|
|
||||||
|
|
||||||
def test_open(cupsprinter, caplog, mocker):
|
def test_open(cupsprinter, caplog, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object and a mocked pycups device
|
GIVEN a cups printer object and a mocked pycups device
|
||||||
WHEN a valid connection to a device is opened
|
WHEN a valid connection to a device is opened
|
||||||
@@ -74,7 +74,7 @@ def test_open(cupsprinter, caplog, mocker):
|
|||||||
assert cupsprinter.device
|
assert cupsprinter.device
|
||||||
|
|
||||||
|
|
||||||
def test_close_on_reopen(cupsprinter, mocker):
|
def test_close_on_reopen(cupsprinter, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object and a mocked connection
|
GIVEN a cups printer object and a mocked connection
|
||||||
WHEN a valid connection to a device is reopened before close
|
WHEN a valid connection to a device is reopened before close
|
||||||
@@ -112,7 +112,7 @@ def test_close(cupsprinter, caplog, mocker):
|
|||||||
assert cupsprinter._device is False
|
assert cupsprinter._device is False
|
||||||
|
|
||||||
|
|
||||||
def test_send_on_close(cupsprinter, mocker):
|
def test_send_on_close(cupsprinter, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object and a mocked pycups device
|
GIVEN a cups printer object and a mocked pycups device
|
||||||
WHEN closing connection before send the buffer
|
WHEN closing connection before send the buffer
|
||||||
@@ -133,7 +133,7 @@ def test_send_on_close(cupsprinter, mocker):
|
|||||||
assert cupsprinter.pending_job is False
|
assert cupsprinter.pending_job is False
|
||||||
|
|
||||||
|
|
||||||
def test_raw_raise_exception(cupsprinter):
|
def test_raw_raise_exception(cupsprinter) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN passing a non byte string to _raw()
|
WHEN passing a non byte string to _raw()
|
||||||
@@ -145,7 +145,7 @@ def test_raw_raise_exception(cupsprinter):
|
|||||||
assert cupsprinter.pending_job is False
|
assert cupsprinter.pending_job is False
|
||||||
|
|
||||||
|
|
||||||
def test_raw(cupsprinter):
|
def test_raw(cupsprinter) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN passing a byte string to _raw()
|
WHEN passing a byte string to _raw()
|
||||||
@@ -156,7 +156,7 @@ def test_raw(cupsprinter):
|
|||||||
assert cupsprinter.tmpfile.read() == b"Test"
|
assert cupsprinter.tmpfile.read() == b"Test"
|
||||||
|
|
||||||
|
|
||||||
def test_printers_no_device(cupsprinter):
|
def test_printers_no_device(cupsprinter) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN device is None
|
WHEN device is None
|
||||||
@@ -166,11 +166,11 @@ def test_printers_no_device(cupsprinter):
|
|||||||
assert cupsprinter.printers == {}
|
assert cupsprinter.printers == {}
|
||||||
|
|
||||||
|
|
||||||
def test_read_no_device(cupsprinter):
|
def test_read_no_device(cupsprinter) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN a cups printer object
|
GIVEN a cups printer object
|
||||||
WHEN device is None
|
WHEN device is None
|
||||||
THEN check the return value is []
|
THEN check the return value is b'8'
|
||||||
"""
|
"""
|
||||||
cupsprinter.device = None
|
cupsprinter.device = None
|
||||||
assert cupsprinter._read() == []
|
assert cupsprinter._read() == b"8"
|
||||||
|
@@ -14,16 +14,16 @@ import escpos
|
|||||||
import escpos.exceptions
|
import escpos.exceptions
|
||||||
|
|
||||||
|
|
||||||
def test_raise_error_wrongly():
|
def test_raise_error_wrongly() -> None:
|
||||||
"""raise error the wrong way
|
"""raise error the wrong way
|
||||||
|
|
||||||
should reproduce https://github.com/python-escpos/python-escpos/issues/257
|
should reproduce https://github.com/python-escpos/python-escpos/issues/257
|
||||||
"""
|
"""
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
raise escpos.Error("This should raise an AttributeError.")
|
raise escpos.Error("This should raise an AttributeError.") # type: ignore [attr-defined]
|
||||||
|
|
||||||
|
|
||||||
def tests_raise_error():
|
def tests_raise_error() -> None:
|
||||||
"""raise error the right way"""
|
"""raise error the right way"""
|
||||||
with pytest.raises(escpos.exceptions.Error):
|
with pytest.raises(escpos.exceptions.Error):
|
||||||
raise escpos.exceptions.Error("This should raise an error.")
|
raise escpos.exceptions.Error("This should raise an error.")
|
||||||
|
@@ -12,7 +12,7 @@ import escpos.escpos as escpos
|
|||||||
import escpos.printer as printer
|
import escpos.printer as printer
|
||||||
|
|
||||||
|
|
||||||
def test_with_statement():
|
def test_with_statement() -> None:
|
||||||
"""Use with statement
|
"""Use with statement
|
||||||
|
|
||||||
.. todo:: Extend these tests as they don't really do anything at the moment"""
|
.. todo:: Extend these tests as they don't really do anything at the moment"""
|
||||||
|
11
tox.ini
11
tox.ini
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py38, py39, py310, py311, docs, flake8
|
envlist = py38, py39, py310, py311, py312, py313, docs, flake8
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
@@ -11,6 +11,7 @@ python =
|
|||||||
3.10: py310
|
3.10: py310
|
||||||
3.11: py311
|
3.11: py311
|
||||||
3.12: py312
|
3.12: py312
|
||||||
|
3.13: py313
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = jaconv
|
deps = jaconv
|
||||||
@@ -21,7 +22,7 @@ deps = jaconv
|
|||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-mock
|
pytest-mock
|
||||||
hypothesis>=6.83
|
hypothesis>=6.83
|
||||||
python-barcode
|
python-barcode>=0.15.0,<1
|
||||||
extras = all
|
extras = all
|
||||||
commands = pytest
|
commands = pytest
|
||||||
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_*
|
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_*
|
||||||
@@ -32,7 +33,7 @@ basepython = python
|
|||||||
changedir = doc
|
changedir = doc
|
||||||
deps = sphinx>=7.2.3
|
deps = sphinx>=7.2.3
|
||||||
setuptools_scm
|
setuptools_scm
|
||||||
python-barcode
|
python-barcode>=0.15.0,<1
|
||||||
sphinx-argparse
|
sphinx-argparse
|
||||||
sphinxcontrib-spelling>=8.0.0
|
sphinxcontrib-spelling>=8.0.0
|
||||||
sphinxcontrib.datatemplates
|
sphinxcontrib.datatemplates
|
||||||
@@ -54,10 +55,10 @@ deps = mypy
|
|||||||
types-six
|
types-six
|
||||||
types-mock
|
types-mock
|
||||||
types-PyYAML
|
types-PyYAML
|
||||||
types-appdirs
|
|
||||||
types-Pillow
|
types-Pillow
|
||||||
types-pyserial
|
types-pyserial
|
||||||
types-pywin32>=306.0.0.6
|
types-pywin32>=306.0.0.6
|
||||||
|
types-flask
|
||||||
hypothesis>=6.83
|
hypothesis>=6.83
|
||||||
jaconv
|
jaconv
|
||||||
commands = mypy src test
|
commands = mypy src test examples
|
||||||
|
Reference in New Issue
Block a user