Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop cleanup 2 #82

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
4 changes: 2 additions & 2 deletions src/labelle/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ def default(
render_engine=render_engine,
justify=justify,
visible_horizontal_margin_px=margin_px,
labeler_margin_px=dymo_labeler.labeler_margin_px,
labeler_margin_px=dymo_labeler.get_labeler_margin_px(),
max_width_px=max_payload_len_px,
min_width_px=min_payload_len_px,
)
Expand All @@ -547,7 +547,7 @@ def default(
dymo_labeler=dymo_labeler,
justify=justify,
visible_horizontal_margin_px=margin_px,
labeler_margin_px=dymo_labeler.labeler_margin_px,
labeler_margin_px=dymo_labeler.get_labeler_margin_px(),
max_width_px=max_payload_len_px,
min_width_px=min_payload_len_px,
)
Expand Down
4 changes: 2 additions & 2 deletions src/labelle/gui/q_labels_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def render_preview(self) -> None:
dymo_labeler=self.dymo_labeler,
justify=self.justify,
visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm),
labeler_margin_px=self.dymo_labeler.labeler_margin_px,
labeler_margin_px=self.dymo_labeler.get_labeler_margin_px(),
max_width_px=None,
min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm),
)
Expand All @@ -180,7 +180,7 @@ def render_print(self) -> None:
render_engine=self._payload_render_engine,
justify=self.justify,
visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm),
labeler_margin_px=self.dymo_labeler.labeler_margin_px,
labeler_margin_px=self.dymo_labeler.get_labeler_margin_px(),
max_width_px=None,
min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm),
)
Expand Down
27 changes: 12 additions & 15 deletions src/labelle/lib/devices/dymo_labeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from labelle.lib.devices.device_config import DeviceConfig
from labelle.lib.devices.device_manager import get_device_config_by_id
from labelle.lib.devices.usb_device import UsbDevice, UsbDeviceError
from labelle.lib.render_engines.margins import LabelMarginsPx

LOG = logging.getLogger(__name__)
POSSIBLE_USB_ERRORS = (UsbDeviceError, NoBackendError, USBError)
Expand Down Expand Up @@ -273,15 +274,6 @@ def get_label_height_px(self):
"""Get the (usable) tape height in pixels."""
return self.tape_print_properties.usable_tape_height_px

@property
def _functions(self) -> DymoLabelerFunctions:
assert self._device is not None
return DymoLabelerFunctions(
devout=self._device.devout,
devin=self._device.devin,
synwait=64,
)

@property
def minimum_horizontal_margin_mm(self):
# Return distance between printhead and cutter
Expand All @@ -290,11 +282,10 @@ def minimum_horizontal_margin_mm(self):
self.device_config.distance_between_print_head_and_cutter_px
)

@property
def labeler_margin_px(self) -> tuple[float, float]:
return (
self.device_config.distance_between_print_head_and_cutter_px,
self.tape_print_properties.top_margin_px,
def get_labeler_margin_px(self) -> LabelMarginsPx:
return LabelMarginsPx(
horizontal=self.device_config.distance_between_print_head_and_cutter_px,
vertical=self.tape_print_properties.top_margin_px,
)

@property
Expand Down Expand Up @@ -431,7 +422,13 @@ def print(

try:
LOG.debug("Printing label..")
self._functions.print_label(label_matrix)
assert self._device is not None
functions = DymoLabelerFunctions(
devout=self._device.devout,
devin=self._device.devin,
synwait=64,
)
functions.print_label(label_matrix)
LOG.debug("Done printing.")
if self._device is not None:
self._device.dispose()
Expand Down
62 changes: 39 additions & 23 deletions src/labelle/lib/render_engines/margins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import math
from typing import Literal
from typing import Literal, NamedTuple

from PIL import Image

Expand All @@ -14,49 +13,64 @@
)


class LabelMarginsPx(NamedTuple):
horizontal: int
vertical: int

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Python docs, NamedTuple type hint is a "Typed version of collections.namedtuple()". See https://docs.python.org/3/library/typing.html#typing.NamedTuple

Don't you prefer to use collections.namedtuple, instead of a regular Python class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Some benefits:

  1. Types. With collections.namedtuple you just have names, without the types, so you wouldn't get type checking.
  2. Syntax. This behaves and looks like a normal Python class, but the init and string representation are managed for you. This also makes it easy to switch out with similar data containers, like pydantic.BaseModel in case you need input validation.

class BitmapTooBigError(RenderEngineException):
def __init__(self, width_px, max_width_px):
msg = f"width_px: {width_px}, max_width_px: {max_width_px}"
super().__init__(msg)


class MarginsRenderEngine(RenderEngine):
label_margins_px: LabelMarginsPx
min_width_px: int
max_width_px: int
visible_horizontal_margin_px: int
render_engine: RenderEngine

def __init__(
self,
render_engine: RenderEngine,
mode: Literal["print", "preview"],
justify: Direction = Direction.CENTER,
visible_horizontal_margin_px: float = 0,
labeler_margin_px: tuple[float, float] = (0, 0),
max_width_px: float | None = None,
min_width_px: float | None = 0,
visible_horizontal_margin_px: int = 0,
labeler_margin_px: LabelMarginsPx | None = None,
max_width_px: int | None = None,
min_width_px: int | None = None,
):
super().__init__()
labeler_horizontal_margin_px, labeler_vertical_margin_px = labeler_margin_px
if labeler_margin_px is None:
labeler_margin_px = LabelMarginsPx(horizontal=0, vertical=0)
assert visible_horizontal_margin_px >= 0
assert labeler_horizontal_margin_px >= 0
assert labeler_vertical_margin_px >= 0
assert labeler_margin_px.horizontal >= 0
assert labeler_margin_px.vertical >= 0
assert not max_width_px or max_width_px >= 0
if min_width_px is None:
min_width_px = 0
assert min_width_px >= 0
self.mode = mode
self.justify = justify
if is_dev_mode_no_margins():
self.visible_horizontal_margin_px = 0.0
self.labeler_horizontal_margin_px = 0.0
self.min_width_px = 0.0
self.visible_horizontal_margin_px = 0
labeler_horizontal_margin_px = 0
self.min_width_px = 0
else:
self.visible_horizontal_margin_px = visible_horizontal_margin_px
self.labeler_horizontal_margin_px = labeler_horizontal_margin_px
labeler_horizontal_margin_px = labeler_margin_px.horizontal
self.min_width_px = min_width_px
self.labeler_vertical_margin_px = labeler_vertical_margin_px
labeler_vertical_margin_px = labeler_margin_px.vertical
self.label_margins_px = LabelMarginsPx(
horizontal=labeler_horizontal_margin_px,
vertical=labeler_vertical_margin_px,
)
self.max_width_px = max_width_px
self.render_engine = render_engine

def _calculate_visible_width(self, payload_width_px: int) -> float:
def _calculate_visible_width(self, payload_width_px: int) -> int:
minimal_label_width_px = (
payload_width_px + self.visible_horizontal_margin_px * 2.0
payload_width_px + self.visible_horizontal_margin_px * 2
)
if self.max_width_px is not None and minimal_label_width_px > self.max_width_px:
raise BitmapTooBigError(minimal_label_width_px, self.max_width_px)
Expand All @@ -72,7 +86,7 @@ def render(self, _: RenderContext) -> Image.Image:

def render_with_meta(
self, context: RenderContext
) -> tuple[Image.Image, dict[str, float]]:
) -> tuple[Image.Image, dict[str, int]]:
payload_bitmap = self.render_engine.render(context)
payload_width_px = payload_bitmap.width
label_width_px = self._calculate_visible_width(payload_width_px)
Expand All @@ -81,9 +95,11 @@ def render_with_meta(
if self.justify == Direction.LEFT:
horizontal_offset_px = self.visible_horizontal_margin_px
elif self.justify == Direction.CENTER:
horizontal_offset_px = padding_px / 2
horizontal_offset_px = padding_px // 2
elif self.justify == Direction.RIGHT:
horizontal_offset_px = padding_px - self.visible_horizontal_margin_px
else:
raise ValueError(f"Invalid justify value: {self.justify}")
assert horizontal_offset_px >= self.visible_horizontal_margin_px

# In print mode:
Expand All @@ -107,20 +123,20 @@ def render_with_meta(

if self.mode == "print":
# print head is already in offset from label's edge under the cutter
horizontal_offset_px -= self.labeler_horizontal_margin_px
horizontal_offset_px -= self.label_margins_px.horizontal

# add vertical margins to bitmap
bitmap_height = (
float(payload_bitmap.height) + self.labeler_vertical_margin_px * 2.0
payload_bitmap.height + self.label_margins_px.vertical * 2
)

bitmap = Image.new("1", (math.ceil(label_width_px), math.ceil(bitmap_height)))
bitmap = Image.new("1", (label_width_px, bitmap_height))
bitmap.paste(
payload_bitmap,
box=(round(horizontal_offset_px), round(self.labeler_vertical_margin_px)),
box=(horizontal_offset_px, self.label_margins_px.vertical),
)
meta = {
"horizontal_offset_px": horizontal_offset_px,
"vertical_offset_px": self.labeler_vertical_margin_px,
"vertical_offset_px": self.label_margins_px.vertical,
}
return bitmap, meta
4 changes: 2 additions & 2 deletions src/labelle/lib/render_engines/print_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from PIL import Image

from labelle.lib.constants import Direction
from labelle.lib.render_engines.margins import MarginsRenderEngine
from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine
from labelle.lib.render_engines.render_context import RenderContext
from labelle.lib.render_engines.render_engine import RenderEngine

Expand All @@ -14,7 +14,7 @@ def __init__(
render_engine: RenderEngine,
justify: Direction = Direction.CENTER,
visible_horizontal_margin_px: float = 0,
labeler_margin_px: tuple[float, float] = (0, 0),
labeler_margin_px: LabelMarginsPx | None = None,
max_width_px: float | None = None,
min_width_px: float | None = 0,
):
Expand Down
16 changes: 9 additions & 7 deletions src/labelle/lib/render_engines/print_preview.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from __future__ import annotations

import math

from darkdetect import isDark
from PIL import Image, ImageColor, ImageDraw, ImageOps

from labelle.lib.constants import Direction
from labelle.lib.devices.dymo_labeler import DymoLabeler
from labelle.lib.render_engines.margins import MarginsRenderEngine
from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine
from labelle.lib.render_engines.render_context import RenderContext
from labelle.lib.render_engines.render_engine import RenderEngine


class PrintPreviewRenderEngine(RenderEngine):
X_MARGIN_PX = 80
Y_MARGIN_PX = 30
DX = X_MARGIN_PX * 0.3
DY = Y_MARGIN_PX * 0.3
X_MARGIN_PX: int = 80
Y_MARGIN_PX: int = 30
DX: int = math.floor(X_MARGIN_PX * 0.3)
DY: int = math.floor(Y_MARGIN_PX * 0.3)
dymo_labeler: DymoLabeler

def __init__(
Expand All @@ -23,10 +25,10 @@ def __init__(
dymo_labeler: DymoLabeler,
justify: Direction = Direction.CENTER,
visible_horizontal_margin_px: float = 0,
labeler_margin_px: tuple[float, float] = (0, 0),
labeler_margin_px: LabelMarginsPx | None = None,
max_width_px: float | None = None,
min_width_px: float = 0,
):
) -> None:
super().__init__()
self.dymo_labeler = dymo_labeler
self.render_engine = MarginsRenderEngine(
Expand Down