diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index fb2e7b8..fc00e71 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -11,6 +11,7 @@ This module contains the abstract base class :py:class:`Escpos`. """ from __future__ import annotations +import re import textwrap import time import warnings @@ -108,7 +109,7 @@ SW_BARCODE_NAMES = { for name in barcode.PROVIDED_BARCODES } -Alignment = Union[Literal["center", "left", "right"], str] +Alignment = Union[Literal["center", "left", "right", "justify"], str] class Escpos(object, metaclass=ABCMeta): @@ -920,7 +921,21 @@ class Escpos(object, metaclass=ABCMeta): 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", @@ -936,6 +951,10 @@ class Escpos(object, metaclass=ABCMeta): 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 @@ -972,7 +991,7 @@ class Escpos(object, metaclass=ABCMeta): 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]) + 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)] @@ -1013,6 +1032,9 @@ class Escpos(object, metaclass=ABCMeta): 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): diff --git a/test/test_functions/test_function_software_columns.py b/test/test_functions/test_function_software_columns.py index 9ec1771..0eb10c5 100644 --- a/test/test_functions/test_function_software_columns.py +++ b/test/test_functions/test_function_software_columns.py @@ -33,11 +33,11 @@ def test_add_padding_into_cols(driver) -> None: """ output = driver._add_padding_into_cols( - text_list=["col1", "col2", "col3"], - widths=[6, 6, 6], - align=["center", "left", "right"], + text_list=["col1", "col2", "col3", "col 4"], + widths=[6, 6, 6, 6], + align=["center", "left", "right", "justify"], ) - assert output == [" col1 ", "col2 ", " col3"] + assert output == [" col1 ", "col2 ", " col3", "col 4"] @pytest.mark.parametrize("text_list", ["", [], None]) @@ -55,7 +55,7 @@ def test_software_columns_invalid_args(driver, text_list, widths, align) -> None bad_args = [bad_text_list, bad_widths, bad_align] for kwargs in bad_args: - with pytest.raises(Exception): + with pytest.raises((TypeError, ValueError)): driver.software_columns(**kwargs) driver.close() @@ -69,7 +69,7 @@ def test_software_columns_invalid_args(driver, text_list, widths, align) -> None ], ) @pytest.mark.parametrize("widths", [[10, 10, 10], [10], 30]) -@pytest.mark.parametrize("align", [["center", "left", "right"], ["center"], "center"]) +@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