# app.py import asyncio from functools import lru_cache from io import BytesIO from typing import List from barcode import Code128 from barcode.writer import ImageWriter from brother_ql.backends.helpers import send from brother_ql.conversion import convert from brother_ql.raster import BrotherQLRaster from fastapi import FastAPI, HTTPException from PIL import Image, ImageOps from pydantic import BaseModel, Field from pylibdmtx.pylibdmtx import encode if not hasattr(Image, "ANTIALIAS"): Image.ANTIALIAS = Image.Resampling.LANCZOS app = FastAPI(title="QL-1060N Stacked Barcode Printer") @lru_cache(maxsize=256) def make_code128(data: str) -> Image.Image: # 1) Instantiate the barcode object (checksum auto-added for Code128) code = Code128(data, writer=ImageWriter()) # 2) Render to a PIL.Image, passing any writer options and optional text override pil_img = code.render( writer_options={ "module_height": 15.0, # bar height "module_width": 0.5, # bar thickness "quiet_zone": 1.0, # margin on each side "font_size": 10, # text size "text_distance": 1.0, # gap between bars and text # you can also pass 'format': 'PNG' here if needed }, text=data, # explicitly draw your data string under the bars ) # 3) Convert to 1-bit for your Brother QL workflow return pil_img.convert("1") @lru_cache(maxsize=256) def make_datamatrix(data: str) -> Image.Image: dm = encode(data.encode("utf-8")) pil_img = Image.frombytes("RGB", (dm.width, dm.height), dm.pixels) return pil_img.convert("1") class PrintRequest(BaseModel): data: str = Field(..., description="String to encode") printer_ip: str = Field( "192.168.178.49", description="Brother QL-1060N IP on your LAN" ) model: str = Field("QL-1060N", description="Brother model") label: str = Field("12", description="12 mm continuous tape") def compose_label(images: List[Image.Image]) -> Image.Image: border, spacing = 10, 20 framed = [ImageOps.expand(img, border=border, fill="white") for img in images] max_h = max(img.height for img in framed) total_w = sum(img.width for img in framed) + spacing * (len(framed) - 1) canvas = Image.new("1", (total_w, max_h), 1) x = 0 for img in framed: y = (max_h - img.height) // 2 canvas.paste(img, (x, y)) x += img.width + spacing return canvas def send_to_printer(image: Image.Image, printer_ip: str, model: str, label: str): qlr = BrotherQLRaster(model) qlr.exception_on_warning = True instructions = convert( qlr=qlr, images=[image], label=label, rotate="90", # landscape threshold=70.0, dither=False, compress=True, cut=True, ) send( instructions=instructions, printer_identifier=printer_ip, backend_identifier="network", blocking=True, ) @app.post("/print", summary="Print Code128 + DataMatrix side-by-side") async def print_stacked(req: PrintRequest): try: loop = asyncio.get_running_loop() code_task = loop.run_in_executor(None, make_code128, req.data) dm_task = loop.run_in_executor(None, make_datamatrix, req.data) code_img, dm_img = await asyncio.gather(code_task, dm_task) label_img = compose_label([code_img, dm_img]) send_to_printer(label_img, req.printer_ip, req.model, req.label) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) return { "status": "printed", "model": req.model, "label_width_mm": req.label, "layout": "code128 + datamatrix side by side", }