1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-06-25 08:38:43 +00:00

Compare commits

..

No commits in common. "master" and "v3.1" have entirely different histories.
master ... v3.1

32 changed files with 91 additions and 428 deletions

View File

@ -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 --ignore-installed tox pycups sudo pip install tox pycups
- name: Test doc build - name: Test doc build
run: tox -e docs run: tox -e docs

View File

@ -19,7 +19,7 @@ jobs:
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.0.0
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
@ -44,14 +44,12 @@ 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@v5 uses: codecov/codecov-action@v3
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 files: ./coverage.xml,!./cache
exclude: "**/.mypy_cache"
flags: unittests flags: unittests
name: coverage-tox-${{ matrix.python-version }} name: coverage-tox-${{ matrix.python-version }}
verbose: true verbose: true

View File

@ -22,7 +22,7 @@ jobs:
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.0.0
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
@ -54,14 +54,12 @@ 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@v5 uses: codecov/codecov-action@v3
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 files: ./coverage.xml,!./cache
exclude: "**/.mypy_cache"
flags: unittests flags: unittests
name: coverage-tox-${{ matrix.python-version }} name: coverage-tox-${{ matrix.python-version }}
verbose: true verbose: true

View File

@ -12,7 +12,7 @@
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"python.formatting.provider": "black", "python.formatting.provider": "black",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": true
}, },
"python.testing.unittestEnabled": false, "python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,

View File

@ -1,18 +1,6 @@
Changelog Changelog
========= =========
202x-xx-xx - Version 3.x - ""
-------------------------------------------
changes
^^^^^^^
contributors
^^^^^^^^^^^^
2023-12-17 - Version 3.1 - "Rubric Of Ruin" 2023-12-17 - Version 3.1 - "Rubric Of Ruin"
------------------------------------------- -------------------------------------------
This is the minor release of the new version 3.1. This is the minor release of the new version 3.1.
@ -69,8 +57,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 code page and sets it automatically (called "magic encode") - feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the code page with the old API - as an alternative you can force the codepage 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
@ -335,14 +323,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 code page and sets it automatically (called "magic encode") - feature: the driver tries now to guess the appropriate codepage and sets it automatically (called "magic encode")
- as an alternative you can force the code page with the old API - as an alternative you can force the codepage 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
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -375,7 +363,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)
@ -440,8 +428,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
code page codepage
- 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
@ -479,7 +467,7 @@ contributors
-------------------------- --------------------------
- Merge pull request #53 from ldos/master - Merge pull request #53 from ldos/master
- Extended parameters for serial printers - Extended params 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

View File

@ -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", profile="TM-T88III") #Printer IP Address kitchen = Network("192.168.1.100") #Printer IP Address
kitchen.text("Hello World\n") kitchen.text("Hello World\n")
kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '') kitchen.barcode('4006381333931', 'EAN13', 64, 2, '', '')
kitchen.cut() kitchen.cut()
@ -71,22 +71,18 @@ 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( p = Serial(devfile='/dev/tty.usbserial',
devfile='/dev/tty.usbserial',
baudrate=9600, baudrate=9600,
bytesize=8, bytesize=8,
parity='N', parity='N',
stopbits=1, stopbits=1,
timeout=1.00, timeout=1.00,
dsrdtr=True, 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>`_.
@ -104,4 +100,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.
Their names are used only to maintain context. Its names are used only to maintain context.

@ -1 +1 @@
Subproject commit e3bf6056ee75cf70ffaccb925081fffa7ad6ced5 Subproject commit 4006299c0fa82bc4d4c297663628346ce3eff6c5

View File

@ -73,7 +73,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "python-escpos" project = "python-escpos"
copyright = "2024, python-escpos developers" copyright = "2023, 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,6 +134,7 @@ 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"
@ -143,14 +144,6 @@ 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 = []
@ -320,7 +313,7 @@ texinfo_documents = [
# texinfo_no_detailmenu = False # texinfo_no_detailmenu = False
# spellchecker # spellchecker
spelling_ignore_pypi_package_names = False spelling_ignore_pypi_package_names = True
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

View File

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

View File

@ -2,11 +2,11 @@ pyusb
Pillow>=2.0 Pillow>=2.0
qrcode>=4.0 qrcode>=4.0
pyserial pyserial
sphinx-rtd-theme==3.0.2 sphinx-rtd-theme==2.0.0
setuptools setuptools
setuptools-scm setuptools-scm
docutils>=0.12 docutils>=0.12
sphinxcontrib-spelling>=8.0.0 sphinxcontrib-spelling>=7.2.0
python-barcode>=0.15.0,<1 python-barcode>=0.15.0,<1
importlib-metadata importlib-metadata
importlib_resources importlib_resources

View File

@ -87,18 +87,13 @@ 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
@ -108,23 +103,16 @@ 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
@ -133,13 +121,10 @@ src
testcases testcases
th th
Todo Todo
tox
traceback traceback
udev udev
usb usb
USBTimeoutError
usec usec
virtualenvs virtualenvs
viivakoodi
whitespaces whitespaces
xml xml

View File

@ -1,7 +1,7 @@
Usage Usage
===== =====
:Last Reviewed: 2025-02-16 :Last Reviewed: 2023-08-10
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, profile="TM-T88IV") p = printer.Usb(0x04b8,0x0202)
# 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,7 +180,6 @@ 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::
@ -188,7 +187,6 @@ 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::
@ -198,32 +196,23 @@ 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 code page and then send the encoded data to the printer. If this feature does not work, please try to the right codepage and then send the encoded data to the printer. If this feature does not work, please try to
isolate the error and then create an issue on the GitHub project page. isolate the error and then create an issue on the GitHub project page.
If you want or need to you can manually set the code page. If you want or need to you can manually set the codepage.
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 code page the printer won't change it anymore. After you have manually set the codepage the printer won't change it anymore.
You can revert to normal behavior by setting charcode to ``AUTO``. 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
-------------------------------------- --------------------------------------
@ -244,7 +233,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 file system.) that you have found. (Just "print" your commands to a File-printer on your local filesystem.)
However, please keep in mind, that often it is easier and better to just supply the code that you are using. 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:
@ -253,8 +242,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 where is the capabilities-profile Advanced Usage: change 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

View File

@ -33,7 +33,7 @@ def main():
def print_codepage(printer, codepage): def print_codepage(printer, codepage):
"""Print a code page.""" """Print a codepage."""
if codepage.isdigit(): if codepage.isdigit():
codepage = int(codepage) codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + bytes((codepage,))) printer._raw(CODEPAGE_CHANGE + bytes((codepage,)))

View File

@ -4,9 +4,9 @@ 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.6 Jinja2==3.1.2
MarkupSafe==2.1.2 MarkupSafe==2.1.2
Pillow==10.3.0 Pillow==10.0.1
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.6 Werkzeug==3.0.1

View File

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

View File

@ -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")

View File

@ -75,6 +75,6 @@ all =
pywin32; platform_system=='Windows' pywin32; platform_system=='Windows'
[flake8] [flake8]
exclude = .git,.venv,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120 max-line-length = 120
extend-ignore = E203, W503 extend-ignore = E203, W503

View File

@ -209,38 +209,6 @@ 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",
@ -373,7 +341,7 @@ ESCPOS_COMMANDS: List[Dict[str, Any]] = [
{ {
"option_strings": ("--font",), "option_strings": ("--font",),
"help": "Font choice", "help": "Font choice",
"choices": ["A", "B"], "choices": ["left", "center", "right"],
}, },
{ {
"option_strings": ("--text_type",), "option_strings": ("--text_type",),
@ -522,7 +490,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 configuration " epilog="Printer configuration is defined in the python-escpos config"
"file. See documentation for details.", "file. See documentation for details.",
) )

View File

@ -1,4 +1,4 @@
"""Helper module for code page handling.""" """Helper module for codepage handling."""
from .capabilities import CAPABILITIES from .capabilities import CAPABILITIES
@ -9,7 +9,7 @@ class CodePageManager:
""" """
def __init__(self, data): def __init__(self, data):
"""Initialize code page manager.""" """Initialize codepage manager."""
self.data = data self.data = data
@staticmethod @staticmethod

View File

@ -76,18 +76,7 @@ class Config:
if "printer" in config: if "printer" in config:
self._printer_config = config["printer"] self._printer_config = config["printer"]
printer_name = self._printer_config.pop("type") self._printer_name = self._printer_config.pop("type").title()
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(

View File

@ -188,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 code page. You need to attach a byte to indicate #: Prefix to change the codepage. You need to attach a byte to indicate
#: the code page to use. We use escpos-printer-db as the data source. #: the codepage to use. We use escpos-printer-db as the data source.
CODEPAGE_CHANGE: bytes = ESC + b"\x74" CODEPAGE_CHANGE: bytes = ESC + b"\x74"
# Barcode format # Barcode format

View File

@ -12,7 +12,6 @@ This module contains the abstract base class :py:class:`Escpos`.
from __future__ import annotations from __future__ import annotations
import textwrap import textwrap
import time
import warnings 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
@ -108,8 +107,6 @@ SW_BARCODE_NAMES = {
for name in barcode.PROVIDED_BARCODES for name in barcode.PROVIDED_BARCODES
} }
Alignment = Union[Literal["center", "left", "right"], str]
class Escpos(object, metaclass=ABCMeta): class Escpos(object, metaclass=ABCMeta):
"""ESC/POS Printer object. """ESC/POS Printer object.
@ -124,9 +121,6 @@ class Escpos(object, metaclass=ABCMeta):
# 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.
@ -182,21 +176,6 @@ class Escpos(object, metaclass=ABCMeta):
""" """
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,
@ -265,7 +244,6 @@ class Escpos(object, metaclass=ABCMeta):
impl=impl, impl=impl,
fragment_height=fragment_height, fragment_height=fragment_height,
) )
self._sleep_in_fragment()
return return
if impl == "bitImageRaster": if impl == "bitImageRaster":
@ -460,7 +438,7 @@ class Escpos(object, metaclass=ABCMeta):
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 code page. right codepage.
(This is the standard behavior.) (This is the standard behavior.)
:param code: Name of CharCode :param code: Name of CharCode
@ -876,8 +854,8 @@ class Escpos(object, metaclass=ABCMeta):
def text(self, txt: str) -> None: def text(self, txt: str) -> None:
"""Print alpha-numeric text. """Print alpha-numeric text.
The text has to be encoded in the currently selected code page. The text has to be encoded in the currently selected codepage.
The input text has to be encoded in Unicode. The input text has to be encoded in unicode.
:param txt: text to be printed :param txt: text to be printed
:raises: :py:exc:`~escpos.exceptions.TextError` :raises: :py:exc:`~escpos.exceptions.TextError`
@ -887,8 +865,8 @@ class Escpos(object, metaclass=ABCMeta):
def textln(self, txt: str = "") -> None: 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 code page. The text has to be encoded in the currently selected codepage.
The input text has to be encoded in Unicode. The input text has to be encoded in unicode.
: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`
@ -909,7 +887,7 @@ class Escpos(object, metaclass=ABCMeta):
def block_text(self, txt, font="0", columns=None) -> 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`
@ -919,115 +897,6 @@ class Escpos(object, metaclass=ABCMeta):
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 _padding(
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}}"
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(*[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).
"""
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,
@ -1065,8 +934,8 @@ class Escpos(object, metaclass=ABCMeta):
: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: requires custom_size=True, text width multiplier when custom_size is used, decimal range 1-8 :param width: text width 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 height: 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
@ -1555,7 +1424,7 @@ class EscposIO:
f"{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)

View File

@ -4,10 +4,7 @@
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:

View File

@ -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 code page. It uses trial and error in order to guess the right codepage.
The code is based on the encoding-code in py-xml-escpos by @fvdsn. 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>`_
@ -65,11 +65,11 @@ class Encoder:
@staticmethod @staticmethod
def _get_codepage_char_list(encoding): def _get_codepage_char_list(encoding):
"""Get code page character list. """Get codepage 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 code page list :param encoding: The name of the encoding. This must appear in the CodePage list
""" """
codepage = CodePages.get_encoding(encoding) codepage = CodePages.get_encoding(encoding)
if "data" in codepage: if "data" in codepage:
@ -91,7 +91,7 @@ class Encoder:
raise LookupError(f"Can't find a known encoding for {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 code page character map. """Get codepage character map.
Process an encoding and return a map of UTF-characters to code points Process an encoding and return a map of UTF-characters to code points
in this encoding. in this encoding.
@ -166,7 +166,7 @@ class Encoder:
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 code page change instruction we have reduce the number of codepage 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.
@ -225,7 +225,7 @@ class MagicEncode:
: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
code page switch. codepage switch.
:param disabled: :param disabled:
:param defaultsymbol: :param defaultsymbol:
:param encoder: :param encoder:
@ -284,20 +284,20 @@ class MagicEncode:
def _handle_character_failed(self, char): def _handle_character_failed(self, char):
"""Write a default symbol. """Write a default symbol.
Called when no code page was found to render a character. Called when no codepage was found to render a character.
""" """
# Writing the default symbol via write() allows us to avoid # Writing the default symbol via write() allows us to avoid
# unnecesary code page switches. # unnecesary codepage switches.
self.write(self.defaultsymbol) self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text): def write_with_encoding(self, encoding, text):
"""Write the text and inject necessary code page switches.""" """Write the text and inject necessary codepage switches."""
if text is not None and type(text) is not str: if text is not None and type(text) is not str:
raise Error( raise Error(
f"The supplied text has to be Unicode, but is of type {type(text)}." f"The supplied text has to be unicode, but is of type {type(text)}."
) )
# We always know the current code page; if the new code page # We always know the current code page; if the new codepage
# is different, emit a change command. # is different, emit a change command.
if encoding != self.encoding: if encoding != self.encoding:
self.encoding = encoding self.encoding = encoding

View File

@ -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)

View File

@ -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 file system. This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
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 file system :param devfile: Device file under dev filesystem
: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)

View File

@ -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)

View File

@ -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 file system :param devfile: Device file under dev filesystem
: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

View File

@ -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)

View File

@ -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)

View File

@ -1,80 +0,0 @@
#!/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"],
widths=[6, 6, 6],
align=["center", "left", "right"],
)
assert output == [" col1 ", "col2 ", " col3"]
@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(Exception):
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"], "center"])
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()

View File

@ -7,8 +7,7 @@
: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
@ -112,7 +111,6 @@ 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: