import logging from dataclasses import dataclass from PIL import Image class PrinterError(Exception): """Raised when the printer returns an error or does not respond.""" @dataclass(frozen=True) class PrinterModel: name: str max_width_px: int max_density: int # D-series printers need ALLOW_PRINT_CLEAR / SET_QUANTITY before page data # (labels taller than ~210px fail otherwise). B21 rejects these commands. needs_print_clear: bool = False MODELS = { m.name: m for m in ( PrinterModel("b1", max_width_px=384, max_density=5), PrinterModel("b18", max_width_px=384, max_density=3), PrinterModel("b21", max_width_px=384, max_density=5), PrinterModel("d11", max_width_px=96, max_density=3, needs_print_clear=True), # D101 takes up to 25mm tape (192px head); tested on 12mm tape PrinterModel("d101", max_width_px=192, max_density=3, needs_print_clear=True), PrinterModel("d110", max_width_px=96, max_density=3, needs_print_clear=True), ) } def get_model(name: str) -> PrinterModel: try: return MODELS[name.lower()] except KeyError: raise ValueError(f"Unknown printer model: {name!r}") from None def prepare_print( model: str | PrinterModel, image: Image.Image, density: int, rotate: int = 0 ) -> tuple[Image.Image, int]: """Rotate/validate an image and clamp density for the given model. Returns the (possibly rotated) image and the effective density. """ if isinstance(model, str): model = get_model(model) if density > model.max_density: logging.warning( f"{model.name.upper()} only supports density up to {model.max_density}" ) density = model.max_density rotate = int(rotate) if rotate % 360 != 0: # PIL library rotates counter clockwise, so we need to multiply by -1 image = image.rotate(-rotate, expand=True) if image.width > model.max_width_px: raise ValueError( f"Image width {image.width}px too big for {model.name.upper()} " f"(max {model.max_width_px}px)" ) return image, density