mirror of
				https://github.com/python-escpos/python-escpos
				synced 2025-10-23 09:30:00 +00:00 
			
		
		
		
	New feature: Software columns (#645)
* add type hint Alignment
* Add static method padding()
* Add static method truncate()
* Add static method _repeat_last()
* Add private method _rearrange_into_cols()
* Add private method _add_padding_into_cols()
* Add public method software_columns
* Make truncate and padding private staticmethods
* Revert "add type hint Alignment"
This reverts commit 546391cb9c.
* Add type hint Alignment
* Fix typo in docstring
---------
Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
			
			
This commit is contained in:
		@@ -107,6 +107,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.
 | 
			
		||||
@@ -897,6 +899,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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user