Merge branch 'master' into aerialist-patch-1

This commit is contained in:
Patrick Kanzler 2024-07-19 23:13:28 +02:00 committed by GitHub
commit 9b34c5bd96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 173 additions and 15 deletions

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.0.0 uses: actions/setup-python@v5.1.1
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@v4
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

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.0.0 uses: actions/setup-python@v5.1.1
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@v4
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

View File

@ -100,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.
Its names are used only to maintain context. Their names are used only to maintain context.

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.3 Jinja2==3.1.4
MarkupSafe==2.1.2 MarkupSafe==2.1.2
Pillow==10.2.0 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.3

View File

@ -209,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",

View File

@ -76,7 +76,18 @@ class Config:
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(

View File

@ -108,6 +108,8 @@ 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.
@ -899,6 +901,115 @@ 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,
@ -936,8 +1047,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: 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