From c9ff02d19186ec2af8d06aa56e234e29a3032b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Szcz=C4=99sny?= Date: Fri, 10 Nov 2023 13:55:50 +0100 Subject: [PATCH] LUT based label size and offset handler --- src/dymoprint/command_line.py | 6 ++++- src/dymoprint/constants.py | 28 ++++++++++++++++++++++ src/dymoprint/detect.py | 3 +++ src/dymoprint/dymo_print_engines.py | 36 +++++++++++++++++++++-------- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/dymoprint/command_line.py b/src/dymoprint/command_line.py index 839ac81..6b78771 100755 --- a/src/dymoprint/command_line.py +++ b/src/dymoprint/command_line.py @@ -244,6 +244,9 @@ def main(): justify=justify, ) + if not args.test_pattern: + label_bitmap = render_engine.add_offset(label_bitmap) + # print or show the label if args.preview or args.preview_inverted or args.imagemagick: print("Demo mode: showing label..") @@ -258,4 +261,5 @@ def main(): if args.imagemagick: ImageOps.invert(label_image).show() else: - print_label(label_bitmap, margin_px=args.m, tape_size_mm=args.t) + print_label(render_engine.detected_device, + label_bitmap, margin_px=args.m, tape_size_mm=args.t) diff --git a/src/dymoprint/constants.py b/src/dymoprint/constants.py index 3d7803e..bb59ba7 100755 --- a/src/dymoprint/constants.py +++ b/src/dymoprint/constants.py @@ -80,3 +80,31 @@ DEFAULT_FONT_DIR = Path(dymoprint.resources.fonts.__file__).parent ICON_DIR = Path(dymoprint.resources.icons.__file__).parent + +# Print offset dictionaries +# The three values indicate: +# - Printer header size in dots +# - A number of the first visible dot on a test print, +# - A number of the last visible dot on a test print. +# Note the dot numeration is zero based. +# Impossible combinations (such as PnP with 19mm tape) should have sane defaults. +dict_pnp = {6: (64,11,51), 9: (64,1,62), 12: (64,0,63), 19: (64,0,63)} +dict_420p = {6: (128,44,85), 9: (128,31,94), 12: (128,38,117), 19: (128,2,127)} + +# Offset meta-dictionary +# This one binds printer PID with an offset dictionary. +# All supported products must have an entry here. +OFFSETS = { + 0x0011: dict_pnp, + 0x0015: dict_pnp, + 0x1001: dict_pnp, + 0x1002: dict_pnp, + 0x1003: dict_420p, + 0x1004: dict_420p, + 0x1005: dict_pnp, + 0x1006: dict_pnp, + 0x1007: dict_pnp, + 0x1008: dict_pnp, + 0x1009: dict_pnp +} + diff --git a/src/dymoprint/detect.py b/src/dymoprint/detect.py index 435e439..2690ed6 100644 --- a/src/dymoprint/detect.py +++ b/src/dymoprint/detect.py @@ -11,6 +11,7 @@ PRINTER_INTERFACE_CLASS, SUPPORTED_PRODUCTS, UNCONFIRMED_MESSAGE, + OFFSETS, ) GITHUB_LINK = "" @@ -47,6 +48,8 @@ def device_info(dev: usb.core.Device) -> str: res += f" - {repr(intf)}\n" return res +def get_device_offsets(det_dev, tape_size) -> tuple[int, int, int]: + return OFFSETS[det_dev.id][tape_size] def detect_device() -> DetectedDevice: dymo_devs = list(usb.core.find(idVendor=DEV_VENDOR, find_all=True)) diff --git a/src/dymoprint/dymo_print_engines.py b/src/dymoprint/dymo_print_engines.py index f049bd6..6243534 100644 --- a/src/dymoprint/dymo_print_engines.py +++ b/src/dymoprint/dymo_print_engines.py @@ -8,7 +8,7 @@ import usb from PIL import Image, ImageFont, ImageOps -from dymoprint.detect import detect_device +from dymoprint.detect import detect_device, get_device_offsets from . import DymoLabeler from .barcode_writer import BarcodeImageWriter @@ -18,15 +18,20 @@ class DymoRenderEngine: tape_size_mm: int + tape_size_dots: int + offsets: tuple[int,int,int] + detected_device: DetectedDevice def __init__(self, tape_size_mm: int = 12) -> None: """Initialize a DymoRenderEngine object with a specified tape size.""" self.tape_size_mm = tape_size_mm + self.detected_device = detect_device() + self.offsets = get_device_offsets(self.detected_device, self.tape_size_mm) + self.tape_size_dots = self.offsets[2] - self.offsets[1] + 1 def render_empty(self, label_len: int = 1) -> Image.Image: """Render an empty label image.""" - label_height = DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 - return Image.new("1", (label_len, label_height)) + return Image.new("1", (label_len, self.tape_size_dots)) def render_test(self, width: int = 100) -> Image.Image: """Render a test pattern""" @@ -62,7 +67,7 @@ def render_test(self, width: int = 100) -> Image.Image: def render_qr(self, qr_input_text: str) -> Image.Image: """Render a QR code image from the input text.""" - label_height = DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 + label_height = self.tape_size_dots if len(qr_input_text) == 0: return Image.new("1", (1, label_height)) @@ -98,7 +103,7 @@ def render_barcode( self, barcode_input_text: str, bar_code_type: str ) -> Image.Image: """Render a barcode image from the input text and barcode type.""" - label_height = DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 + label_height = self.tape_size_dots if len(barcode_input_text) == 0: return Image.new("1", (1, label_height)) @@ -110,7 +115,7 @@ def render_barcode( "font_size": 0, "vertical_margin": 8, "module_height": ( - DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 - 16 + self.tape_size_dots ), "module_width": 2, "background": "black", @@ -139,7 +144,7 @@ def render_text( text_lines = [" "] # create an empty label image - label_height_px = DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 + label_height_px = self.tape_size_dots line_height = float(label_height_px) / len(text_lines) font_size_px = int(round(line_height * font_size_ratio)) @@ -186,7 +191,7 @@ def render_text( def render_picture(self, picture_path: str) -> Image.Image: if len(picture_path): if os.path.exists(picture_path): - label_height = DymoLabeler.max_bytes_per_line(self.tape_size_mm) * 8 + label_height = self.tape_size_dots with Image.open(picture_path) as img: if img.height > label_height: ratio = label_height / img.height @@ -255,9 +260,23 @@ def merge_render( return out_label_bitmap return label_bitmap + + def add_offset(self, label_bitmap) -> Image.Image: + """Add offset specified for given printer model and tape size""" + label_padded = Image.new( + "1", + ( + label_bitmap.width, + label_bitmap.height + self.offsets[0] - self.offsets[2] - 1 + ), + ) + label_padded.paste(label_bitmap, (0, 0)) + return label_padded + def print_label( + detected_device: DetectedDevice, label_bitmap: Image.Image, margin_px: int = DEFAULT_MARGIN_PX, tape_size_mm: int = 12, @@ -267,7 +286,6 @@ def print_label( The label bitmap is a PIL image in 1-bit format (mode=1), and pixels with value equal to 1 are burned. """ - detected_device = detect_device() # Convert the image to the proper matrix for the dymo labeler object so that # rows span the width of the label, and the first row corresponds to the left