Refactor Canvas with new revert(), text(), fill() methods

This commit is contained in:
Yusuf Cihan
2025-04-17 17:53:26 +03:00
parent 3e47b0116e
commit fe3cf155e3
5 changed files with 379 additions and 159 deletions

View File

@@ -22,28 +22,46 @@
from argparse import ArgumentParser, BooleanOptionalAction
from pathlib import Path
from typing import cast
from typing import Literal, Union, cast
from dymo_bluetooth.bluetooth import discover_printers, create_image
import sys
import asyncio
import os
async def print_image(
input_file : Path,
max_timeout : int,
stretch_factor : int,
use_dither : bool
input_file: Path,
max_timeout: int,
stretch_factor: int,
use_dither: bool,
ensure_mac: bool,
padding: int,
reverse: bool,
is_preview: Union[None, Literal["large", "small"]]
):
canvas = create_image(input_file, dither = use_dither)
canvas = canvas.stretch_image(factor = stretch_factor)
printers = await discover_printers(max_timeout)
if stretch_factor != 1:
canvas = canvas.stretch(factor = stretch_factor)
if padding != 0:
canvas = canvas.fill(padding, padding)
if reverse:
canvas = canvas.revert()
if is_preview:
print(canvas.text(False if is_preview == "large" else True))
exit(0)
print(f"Image size: {canvas.size}", file = sys.stderr)
print(f"Searching for nearby printers to print (timeout: {max_timeout})...", file = sys.stderr)
printers = await discover_printers(max_timeout, ensure_mac)
if not printers:
print("Couldn't find any printers in given timeout!")
print("Couldn't find any printers, is the printer online?", file = sys.stderr)
exit(1)
printer = printers[0]
print(f"Starting to print on {printer._impl.address}...")
print(f"Found: {printer._impl.address}", file = sys.stderr)
await printer.connect()
await printer.print(canvas)
print("Printing...", file = sys.stderr)
result = await printer.print(canvas)
print(f"Result: {result.name} ({result.value})", file = sys.stderr)
def main():
@@ -51,28 +69,107 @@ def main():
args = ArgumentParser(
prog = f"python -m {module_name}",
description = (
"Print monochrome labels with DYMO LetraTag LT-200B label printer over "
"Bluetooth."
"Print monochrome labels with DYMO LetraTag LT-200B label printer over Bluetooth. "
"If executed without --preview, it will search for a printer nearby and automatically "
"start printing the image."
)
)
args.add_argument("image", help = "Image file to print. Must be 32 pixels in height.", type = Path, metavar = "IMAGE") # noqa: E501
args.add_argument("--timeout", default = 5, type = int, help = "Maximum timeout to search for printers.") # noqa: E501
args.add_argument("--dither", default = True, action = BooleanOptionalAction,
help = "Use or don't use dithering when converting it to monochrome image."
args.add_argument(
"image",
help = (
"Image file to print. Can be any type of image that is supported by Pillow. "
"Must be 32 pixels in height, otherwise it will be cropped out."
),
type = Path,
metavar = "IMAGE"
)
args.add_argument("--factor", default = 2, type = int,
help = "Stretch the image N times. Default is 2, otherwise printer" + \
"will print images too thin."
args.add_argument(
"--ensure-mac",
default = 5,
action = "store_true",
help = (
"Also ensures the MAC address does match with the pre-defined MAC prefixes when "
"searching for a printer. Otherwise it will only search for printers by "
"looking for service UUID and its Bluetooth name."
)
)
args.add_argument(
"--timeout",
default = 5,
type = int,
help = "Maximum timeout in seconds to search for printers."
)
args.add_argument(
"--dither",
default = True,
action = BooleanOptionalAction,
help = (
"Use or don't use Floyd-Steinberg dithering provided by Pillow when converting "
"the image to a monochrome image."
)
)
args.add_argument(
"--stretch",
default = 2,
type = int,
help = (
"Stretch the image N times. Default is 2 (as-is in the mobile app), otherwise "
"the printed label will become too thin to actually read."
)
)
args.add_argument(
"--padding",
default = 0,
type = int,
help = (
"Adds N blank width in both sides of the image, leaving the content in the center. "
"So the output width will be ((N * 2) + image width)."
)
)
args.add_argument(
"--reverse",
action = "store_true",
help = (
"Flip the image in color (black becomes white, white becomes black)."
)
)
args.add_argument(
"--preview",
const = "small",
nargs = "?",
choices = ("large", "small"),
help = (
"Don't actually print anything, just print the image in the console with all settings "
"applied to it. The default is 'small', which is the recommended option."
)
)
parsed = args.parse_args()
input_file = cast(Path, parsed.image).absolute()
max_timeout = cast(int, parsed.timeout)
stretch_factor = cast(int, parsed.factor)
stretch_factor = cast(int, parsed.stretch)
use_dither = cast(bool, parsed.dither)
ensure_mac = cast(bool, parsed.ensure_mac)
padding = cast(int, parsed.padding)
reverse = cast(bool, parsed.reverse)
is_preview = cast(Union[None, Literal["large", "small"]], parsed.preview)
if not input_file.exists():
print(f"Given image file doesn't exists: {input_file.as_posix()}")
print(f"Input file doesn't exists: {input_file.as_posix()}", file = sys.stderr)
exit(1)
asyncio.run(print_image(input_file, max_timeout, stretch_factor, use_dither))
if input_file.is_dir():
print(f"Input file can't be a directory: {input_file.as_posix()}", file = sys.stderr)
exit(1)
asyncio.run(print_image(
input_file,
max_timeout,
stretch_factor,
use_dither,
ensure_mac,
padding,
reverse,
is_preview
))
if __name__ == "__main__":

View File

@@ -23,6 +23,7 @@
import asyncio
from io import BytesIO
from pathlib import Path
from sys import stderr
from PIL import Image, ImageChops
from bleak import BleakScanner, BleakClient
from typing import List, TYPE_CHECKING
@@ -41,16 +42,14 @@ PRINT_REPLY_UUID = "be3dd652-{uuid}-42f1-99c1-f0f749dd0678".format(uuid = "2b3d"
UNKNOWN_UUID = "be3dd653-{uuid}-42f1-99c1-f0f749dd0678".format(uuid = "2b3d")
def is_espressif(input_mac : str):
def is_espressif(input_mac: str):
"""
Returns True if given MAC address is from a valid DYMO printer.
The mac address blocks are owned by Espressif Inc.
Returns True if given MAC address is from Espressif Inc.
"""
mac_blocks = [
"58:CF:79",
# Confirmed in pull request #2
"DC:54:75",
"34:85:18"
"DC:54:75", # confirmed in #2
"34:85:18", # confirmed in #4
]
check_mac = int(input_mac.replace(":", ""), base = 16)
for mac in mac_blocks:
@@ -62,7 +61,7 @@ def is_espressif(input_mac : str):
class Printer:
def __init__(self, impl : "BLEDevice") -> None:
def __init__(self, impl: "BLEDevice") -> None:
self._impl = impl
self._client = BleakClient(self._impl)
@@ -76,16 +75,16 @@ class Printer:
return
await self._client.disconnect()
async def print(self, canvas : Canvas):
async def print(self, canvas: Canvas):
if not self._client.is_connected:
raise Exception("Printer is not connected!")
print_request : "BleakGATTCharacteristic" = self._client.services.get_characteristic(PRINT_REQUEST_UUID) # type: ignore # noqa: E501
print_reply : "BleakGATTCharacteristic" = self._client.services.get_characteristic(PRINT_REPLY_UUID) # type: ignore # noqa: E501
future : asyncio.Future[Result] = asyncio.Future()
should_discard : bool = False
print_request: "BleakGATTCharacteristic" = self._client.services.get_characteristic(PRINT_REQUEST_UUID) # type: ignore # noqa: E501
print_reply: "BleakGATTCharacteristic" = self._client.services.get_characteristic(PRINT_REPLY_UUID) # type: ignore # noqa: E501
future: asyncio.Future[Result] = asyncio.Future()
should_discard: bool = False
# Printer sends two messages, first is the PRINTING, and second one is the
# printing result. So we discard the first message.
async def reply_get(_, data : bytearray): # noqa: E501
async def reply_get(_, data: bytearray): # noqa: E501
nonlocal should_discard
result = Result.from_bytes(data)
if (not should_discard) and (result.value in [0, 1]):
@@ -99,24 +98,27 @@ class Printer:
return await future
async def discover_printers(max_timeout : int = 5) -> List[Printer]:
async def discover_printers(max_timeout: int = 5, ensure_mac: bool = False) -> List[Printer]:
"""
Searches for printers nearby and returns a list of Printer objects. If no printer
has found in the initial search, waits for scanning until the max timeout has been
reached.
"""
printers : List[Printer] = []
printers: List[Printer] = []
waited_total = 0
async with BleakScanner(service_uuids = [SERVICE_UUID]) as scanner:
while True:
# TODO: In some cases, advetisement data may be non-null, containing
# additional metadata about printer state but it is not implemented yet.
for device, _ in scanner.discovered_devices_and_advertisement_data.values(): # noqa: E501
if not is_espressif(device.address):
continue
# Also match the "Letratag" name just to be sure that we are looking for
# the right device, since there are also other products made by DYMO.
if (device.name or "") == f"Letratag {device.address.replace(':', '')}":
if ensure_mac and (not is_espressif(device.address)):
print(
f"A possible printer is found, but its MAC {device.address} isn't whitelisted, " +
"thus ignored. If it isn't right, either disable MAC checking or open a issue.",
file = stderr
)
continue
printers.append(Printer(device))
# Do we have any candidate printers? If so, return the found printers.
# Otherwise, wait for the next scans until we found any.
@@ -130,9 +132,9 @@ async def discover_printers(max_timeout : int = 5) -> List[Printer]:
def convert_image_to_canvas(
image : Image.Image,
dither : bool = True,
trim : bool = False
image: Image.Image,
dither: bool = True,
trim: bool = False
):
"""
Converts an Pillow Image to a Canvas object.
@@ -147,14 +149,15 @@ def convert_image_to_canvas(
output = output.crop(diff.getbbox(alpha_only = False))
# Shrink the image from the center if it exceeds the print height,
# or max printable width.
if (output.height > Canvas.HEIGHT):
start_y = int(output.height / 2) - int(Canvas.HEIGHT / 2)
canvas_height = Canvas.BYTES_PER_LINE * 8
if (output.height > canvas_height):
start_y = int(output.height / 2) - int(canvas_height / 2)
output = output.crop(
(0, start_y, output.width, start_y + Canvas.HEIGHT)
(0, start_y, output.width, start_y + canvas_height)
)
elif (output.height < Canvas.HEIGHT):
elif (output.height < canvas_height):
raise ValueError("Image is too small, resizing not implemented.")
if (output.width) > Canvas.MAX_WIDTH:
if (output.width) > Canvas.MAX_LENGTH:
raise ValueError("Image is too large, resizing not implemented.")
# Convert image to pixel array.
canvas = Canvas()
@@ -165,7 +168,7 @@ def convert_image_to_canvas(
return canvas
def create_image(path : Path, dither : bool = True):
def create_image(path: Path, dither: bool = True):
"""
Converts an image file in given path to Canvas.
"""
@@ -177,7 +180,7 @@ def create_image(path : Path, dither : bool = True):
return convert_image_to_canvas(image, dither)
def create_code_128(text : str):
def create_code_128(text: str):
"""
Creates a Code 128 barcode and dumps to Canvas.
"""

View File

@@ -22,12 +22,9 @@
from enum import Enum
from io import SEEK_END, BytesIO
from typing import Union
from typing import Literal, Sequence, Tuple
import math
BytesLike = Union[bytes, bytearray]
class DirectiveCommand(Enum):
"""
Directives that accepted by the printer.
@@ -75,7 +72,7 @@ class Result(Enum):
FAILED_NO_CASETTE = 7
@classmethod
def from_bytes(cls, data : BytesLike):
def from_bytes(cls, data: Sequence[int]):
if data[0] != 27:
raise ValueError("Not a valid result value; 1st byte must be 0x1b (27)")
if chr(data[1]) != "R":
@@ -93,149 +90,142 @@ class Result(Enum):
class Canvas:
"""
An implementation of 1-bit monochrome little-endian encoded 32 pixel height
images for printing them to the label.
An implementation of 1-bit monochrome horizontal images, sequentially
ordered and stored in big-endian for each pixel.
1 byte equals 8 bits, and each bit specifies if pixel should be black (= 1)
or white (= 0). The generated image will contain a multiple of 4 bytes, equaling
the label height in pixels (4 * 8 = 32 pixels).
1 byte equals 8 bits, and each bit specifies if pixel should be filled
(= 1) or not (= 0).
"""
__slots__ = ("buffer", )
HEIGHT = 32
MAX_WIDTH = 8186
# Each line takes 4 bytes, equals to 32 pixels (1 byte = 8 bits).
BYTES_PER_LINE = 4
# Maximum count of bytes that the image can extend into the fixed direction.
MAX_LENGTH = 1024
def __init__(self) -> None:
self.buffer = BytesIO()
def get_pixel(self, x : int, y : int):
def get_pixel(self, x: int, y: int) -> bool:
"""
Gets the pixel in given coordinates.
Returns True if pixel is black, otherwise False.
Returns True if pixel is filled (= black), otherwise False.
"""
if y >= Canvas.HEIGHT:
raise ValueError(f"Canvas can't exceed {Canvas.HEIGHT} pixels in height.")
if x >= Canvas.MAX_WIDTH:
raise ValueError(f"Canvas can't exceed {Canvas.MAX_WIDTH} pixels in width.")
self._raise_if_out_bounds(x, y)
# Get the byte containing the pixel value of given coordinates.
x_offset = math.ceil((x * Canvas.HEIGHT) / 8)
y_offset = ((self.HEIGHT) // 8) - 1 - math.floor(y / 8)
x_offset = x * Canvas.BYTES_PER_LINE
y_offset = Canvas.BYTES_PER_LINE - 1 - math.floor(y / 8)
self.buffer.seek(x_offset + y_offset)
# Check if there is a bit value in given line.
read = self.buffer.read(1)
value = int.from_bytes(read or b"\x00", "little")
value = (self.buffer.read(1) or b"\x00")[0]
is_black = bool(value & (1 << (7 - (y % 8))))
return is_black
def set_pixel(self, x : int, y : int, color : bool):
def set_pixel(self, x: int, y: int, color: Literal[True, False, 0, 1]) -> None:
"""
Sets a pixel to given coordinates.
Setting color to True will paint a black color.
"""
if y >= Canvas.HEIGHT:
raise ValueError(f"Canvas can't exceed {Canvas.HEIGHT} pixels in height.")
if x >= Canvas.MAX_WIDTH:
raise ValueError(f"Canvas can't exceed {Canvas.MAX_WIDTH} pixels in width.")
self._raise_if_out_bounds(x, y)
# Get the byte containing the pixel value of given coordinates.
x_offset = math.ceil((x * Canvas.HEIGHT) / 8)
y_offset = ((self.HEIGHT) // 8) - 1 - math.floor(y / 8)
x_offset = x * Canvas.BYTES_PER_LINE
y_offset = Canvas.BYTES_PER_LINE - 1 - math.floor(y / 8)
self.buffer.seek(x_offset + y_offset)
# Get the one of four slices in line in the given coordinates. Add the bit in
# given location if color is black, otherwise exclude the bit to make it white.
read = self.buffer.read(1)
value = int.from_bytes(read or b"\x00", "little")
curr = self.buffer.read(1)
value = (curr or b"\x00")[0]
if color:
value = value | (1 << (7 - (y % 8)))
else:
value = value & ~(1 << (7 - (y % 8)))
# Change the current part of the line with modified one.
self.buffer.seek(x_offset + y_offset)
# Change the current byte with modified one, if not exists, append a new one.
self.buffer.seek(0 if not curr else -1, 1)
self.buffer.write(bytes([value]))
def stretch_image(self, factor : int = 2):
def stretch(self, factor: int = 2) -> "Canvas":
"""
Stretches image to the right in N times, and returns a new Canvas
containing the stretched image. Factor 1 results in the same image.
Stretches image to the non-fixed direction in N times, and returns a new
Canvas containing the stretched image. Factor 1 results in the same image.
"""
if factor < 1:
raise ValueError("Stretch factor must be at least 1!")
canvas = Canvas()
for x in range(self.get_width()):
for y in range(Canvas.HEIGHT):
for x in range(self.width):
for y in range(self.height):
pixel = self.get_pixel(x, y)
start_x = (x * factor)
for i in range(factor):
canvas.set_pixel(start_x + i, y, pixel)
return canvas
def pad_image(self, width : int):
"""
Adds blank spacing to both sides of the image, until the image
reaches to the given width. Returns a new Canvas containing the
modified image. If image is already longer than given width, it
will return the current Canvas.
"""
current_width = self.get_width()
if current_width >= width:
return self
canvas = Canvas()
left_padding = math.ceil(width / 2)
for x in range(current_width):
for y in range(Canvas.HEIGHT):
canvas.set_pixel(left_padding + x, y, self.get_pixel(x, y))
for i in range(Canvas.HEIGHT):
canvas.set_pixel(current_width + left_padding, i, False)
return canvas
def print(self):
"""
Prints the image to the console.
"""
for y in range(Canvas.HEIGHT):
for x in range(self.get_width()):
print("" if self.get_pixel(x, y) else "", end = "")
print()
def get_width(self):
"""
Gets the painted width of the image.
"""
def _get_byte_size(self):
self.buffer.seek(0, SEEK_END)
cur = self.buffer.tell()
return math.ceil(cur / (self.HEIGHT // 8))
return self.buffer.tell()
def get_size(self):
def _get_unfixed_pixels(self):
return math.ceil(self._get_byte_size() / self.BYTES_PER_LINE)
def _get_fixed_pixels(self):
return self.BYTES_PER_LINE * 8
def _raise_if_out_bounds(self, x: int, y: int):
if (x < 0) or (y < 0):
raise ValueError("Canvas positions can't be negative.")
bits = Canvas.BYTES_PER_LINE * 8
maxbits = Canvas.MAX_LENGTH * 8
if y >= bits:
raise ValueError(f"Canvas can't be or exceed {bits} pixels in height.")
elif x >= maxbits:
raise ValueError(f"Canvas can't be or exceed {maxbits} pixels in width.")
@property
def height(self) -> int:
"""
Gets the height of the image.
"""
return self._get_fixed_pixels()
@property
def width(self) -> int:
"""
Gets the width of the image.
"""
return self._get_unfixed_pixels()
@property
def size(self) -> Tuple[int, int]:
"""
Gets the width and height of the image in tuple.
"""
return (self.get_width(), Canvas.HEIGHT, )
return (self.width, self.height, )
def get_image(self):
def get_image(self) -> bytes:
"""
Gets the created image with added blank padding.
"""
self.buffer.seek(0)
image = self.buffer.read()
return image + (b"\x00" * (self.buffer.tell() % (self.HEIGHT // 8)))
return image + (b"\x00" * (self.buffer.tell() % self.BYTES_PER_LINE))
def empty(self):
"""
Makes all pixels in the canvas in white. Canvas size won't be changed.
Makes all pixels in the canvas in blank (= white). Canvas size won't be changed.
"""
self.buffer.seek(0, SEEK_END)
byte_count = self.buffer.tell()
size = self._get_byte_size()
self.buffer.seek(0)
self.buffer.truncate(0)
self.buffer.seek(byte_count)
self.buffer.seek(size)
self.buffer.write(b"\x00")
def copy(self):
def copy(self) -> "Canvas":
"""
Creates a copy of this canvas.
"""
@@ -251,16 +241,125 @@ class Canvas:
self.buffer.seek(0)
self.buffer.truncate(0)
def revert(self):
"""
Returns a new Canvas with the image is flipped in color; all unfilled
pixels are filled, and all filled pixels are unfilled.
"""
self.buffer.seek(0)
copied = Canvas()
copied.buffer.seek(0)
while (byt := self.buffer.read(1)):
copied.buffer.write(bytes([byt[0] ^ 0xFF]))
return copied
def fill(self, to_left: int, to_right: int) -> "Canvas":
"""
Returns a new Canvas with blank (= white) spacing added to both sides.
"""
canv = Canvas()
canv.buffer.seek(0)
canv.buffer.write(bytes(to_left * self.BYTES_PER_LINE))
canv.buffer.write(self.get_image())
canv.buffer.seek(0, SEEK_END)
canv.buffer.write(bytes(to_right * self.BYTES_PER_LINE))
return canv
def pad(self, until: int) -> "Canvas":
"""
Adds blank (= white) spacing to both sides of the image, until the image reaches
to the given width in pixels. Returns a new Canvas containing the modified image.
If image is already longer than given width, returns the copy of the same image.
"""
if self.width >= until:
return self.copy()
side = math.ceil((until - self.width) / 2)
return self.fill(side, side)
def text(self, in_quad: bool = True, blank_char: int = 0x20, frame: bool = True) -> str:
"""
Converts the image to a string of unicode block symbols, so it can be printed
out to the terminal. If `in_quad` is True, quarter block characters will be used,
so the image will be 2 times smaller, making each 4 pixel to take up a single
character. Empty pixels will be represented as `blank_char` (default is SPACE).
"""
lines = []
frame_length = 0
if frame:
frame_length = self.width if not in_quad else math.ceil(self.width / 2)
if frame_length:
lines.append(chr(0x250C) + (frame_length * chr(0x2500)) + chr(0x2510))
if in_quad:
for h in range(0, self.height, 2):
line = ""
for w in range(0, self.width, 2):
corners = \
self.get_pixel(w, h), \
self.get_pixel(w + 1, h), \
self.get_pixel(w, h + 1), \
self.get_pixel(w + 1, h + 1)
corners = sum([1 << x if corners[x] else 0 for x in range(4)])
line += chr(quartet_to_char(corners) or blank_char)
if frame:
line = chr(0x2502) + line + chr(0x2502)
lines.append(line)
else:
lines = []
for h in range(0, self.height):
line = ""
for w in range(0, self.width):
line += chr(0x2588 if self.get_pixel(w, h) else blank_char)
if frame:
line = chr(0x2502) + line + chr(0x2502)
lines.append(line)
if frame_length:
lines.append(chr(0x2514) + (frame_length * chr(0x2500)) + chr(0x2518))
return "\n".join(lines)
def __len__(self):
return self._get_byte_size()
def __str__(self) -> str:
return self.text(in_quad = True)
def __eq__(self, value: object, /) -> bool:
if not isinstance(value, Canvas):
return False
return value.get_image() == self.get_image()
def __repr__(self) -> str:
self.buffer.seek(0, SEEK_END)
byte_size = self.buffer.tell()
image_size = "x".join(map(str, self.get_size()))
return f"<{self.__class__.__name__} image={image_size} bytes={byte_size}>"
w, h = self.size
return f"<{self.__class__.__name__} size={w}x{h} length={self.__len__()}>"
def quartet_to_char(char: int):
"""
Gets a unicode block symbol code for a 4 bit value (0x0 to 0xF), each bit representing
a corner of the 4 pixels, top left (1 << 0), top right (1 << 1), bottom left (1 << 2)
and bottom right (1 << 3) respectively.
"""
if (char > 0xF) or (char < 0x0):
raise ValueError("Invalid character.")
# https://en.wikipedia.org/wiki/Block_Elements
data = {
0x0: 0x0000, # Blank
0x1: 0x2598, # 0001
0x2: 0x259D, # 0010
0x3: 0x2580, # 0011
0x4: 0x2596, # 0100
0x5: 0x258C, # 0101
0x6: 0x259E, # 0110
0x7: 0x259B, # 0111
0x8: 0x2597, # 1000
0x9: 0x259A, # 1001
0xA: 0x2590, # 1010
0xB: 0x259C, # 1011
0xC: 0x2584, # 1100
0xD: 0x2599, # 1101
0xE: 0x259F, # 1110
0xF: 0x2588, # 1111
}
return data[char]
class DirectiveBuilder:
@@ -277,7 +376,7 @@ class DirectiveBuilder:
return bytes([*DirectiveCommand.START.to_bytes(), 154, 2, 0, 0])
@staticmethod
def media_type(value : int):
def media_type(value: int):
return bytes([*DirectiveCommand.MEDIA_TYPE.to_bytes(), value])
@staticmethod
@@ -294,14 +393,15 @@ class DirectiveBuilder:
@staticmethod
def print(
data : BytesLike,
image_width : int,
bits_per_pixel : int,
alignment : int
data: Sequence[int],
image_width: int,
image_height: int,
bits_per_pixel: int,
alignment: int
):
size = \
image_width.to_bytes(4, "little") + \
Canvas.HEIGHT.to_bytes(4, "little")
image_height.to_bytes(4, "little")
return bytes([
*DirectiveCommand.PRINT_DATA.to_bytes(),
bits_per_pixel,
@@ -311,7 +411,7 @@ class DirectiveBuilder:
])
def create_payload(data : BytesLike, is_print : bool = False):
def create_payload(data: Sequence[int], is_print: bool = False):
"""
Creates a final payload to be sent to the printer from the input data.
@@ -347,7 +447,7 @@ def create_payload(data : BytesLike, is_print : bool = False):
# Split chunk in each 500 bytes.
for index, step in enumerate(range(0, len(data), CHUNK_SIZE)):
current_chunk = bytearray()
chunk = data[step : step + CHUNK_SIZE]
chunk = data[step: step + CHUNK_SIZE]
# TODO: Not sure what is the purpose of the this, but the original
# vendor app skips this index, so we do the same here.
chunk_index = index + 1 if index >= 27 else index
@@ -360,7 +460,7 @@ def create_payload(data : BytesLike, is_print : bool = False):
def command_print(
canvas : Canvas
canvas: Canvas
):
"""
Creates a print directive.
@@ -369,7 +469,8 @@ def command_print(
payload.extend(DirectiveBuilder.start())
payload.extend(DirectiveBuilder.print(
canvas.get_image(),
canvas.get_width(),
canvas.width,
canvas.height,
bits_per_pixel = 1,
alignment = 2
))
@@ -380,7 +481,7 @@ def command_print(
def command_casette(
media_type : int
media_type: int
):
"""
Creates a casette directive.