Merge branch 'master' into aerialist-patch-1
This commit is contained in:
commit
9b34c5bd96
10
.github/workflows/pythonpackage-windows.yml
vendored
10
.github/workflows/pythonpackage-windows.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@ -44,12 +44,14 @@ jobs:
|
||||
env:
|
||||
ESCPOS_CAPABILITIES_FILE: D:\a\python-escpos\python-escpos\capabilities-data\dist\capabilities.json
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
directory: ./coverage/reports/
|
||||
env_vars: OS,PYTHON
|
||||
fail_ci_if_error: true
|
||||
files: ./coverage.xml,!./cache
|
||||
files: ./coverage.xml
|
||||
exclude: "**/.mypy_cache"
|
||||
flags: unittests
|
||||
name: coverage-tox-${{ matrix.python-version }}
|
||||
verbose: true
|
||||
|
10
.github/workflows/pythonpackage.yml
vendored
10
.github/workflows/pythonpackage.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5.0.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@ -54,12 +54,14 @@ jobs:
|
||||
env:
|
||||
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
directory: ./coverage/reports/
|
||||
env_vars: OS,PYTHON
|
||||
fail_ci_if_error: true
|
||||
files: ./coverage.xml,!./cache
|
||||
files: ./coverage.xml
|
||||
exclude: "**/.mypy_cache"
|
||||
flags: unittests
|
||||
name: coverage-tox-${{ matrix.python-version }}
|
||||
verbose: true
|
||||
|
@ -100,4 +100,4 @@ Disclaimer
|
||||
|
||||
None of the vendors cited in this project agree or endorse any of the
|
||||
patterns or implementations.
|
||||
Its names are used only to maintain context.
|
||||
Their names are used only to maintain context.
|
||||
|
@ -4,9 +4,9 @@ blinker==1.6.2
|
||||
click==8.1.3
|
||||
Flask==2.3.2
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.3
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==2.1.2
|
||||
Pillow==10.2.0
|
||||
Pillow==10.3.0
|
||||
pycups==2.0.1
|
||||
pypng==0.20220715.0
|
||||
pyserial==3.5
|
||||
@ -17,4 +17,4 @@ PyYAML==6.0
|
||||
qrcode==7.4.2
|
||||
six==1.16.0
|
||||
typing_extensions==4.5.0
|
||||
Werkzeug==3.0.1
|
||||
Werkzeug==3.0.3
|
@ -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": {
|
||||
"name": "cut",
|
||||
|
@ -76,7 +76,18 @@ class Config:
|
||||
|
||||
if "printer" in config:
|
||||
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):
|
||||
raise exceptions.ConfigSyntaxError(
|
||||
|
@ -108,6 +108,8 @@ SW_BARCODE_NAMES = {
|
||||
for name in barcode.PROVIDED_BARCODES
|
||||
}
|
||||
|
||||
Alignment = Union[Literal["center", "left", "right"], str]
|
||||
|
||||
|
||||
class Escpos(object, metaclass=ABCMeta):
|
||||
"""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
|
||||
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(
|
||||
self,
|
||||
align: Optional[str] = None,
|
||||
@ -936,8 +1047,8 @@ class Escpos(object, metaclass=ABCMeta):
|
||||
:param double_width: doubles the width of the text
|
||||
:param custom_size: uses custom size specified by width and 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 height: text height 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: 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 invert: True enables white on black printing
|
||||
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger
|
||||
|
Loading…
x
Reference in New Issue
Block a user