112 lines
3.2 KiB
Python
112 lines
3.2 KiB
Python
# app.py
|
|
from io import BytesIO
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from typing import List
|
|
from PIL import Image, ImageOps
|
|
|
|
import treepoem
|
|
from brother_ql.raster import BrotherQLRaster
|
|
from brother_ql.conversion import convert
|
|
from brother_ql.backends.helpers import send
|
|
|
|
if not hasattr(Image, "ANTIALIAS"):
|
|
Image.ANTIALIAS = Image.Resampling.LANCZOS
|
|
|
|
app = FastAPI(title="QL-1060N Stacked Barcode Printer")
|
|
|
|
|
|
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 make_barcode_image(data: str, symb: str) -> Image.Image:
|
|
"""Generate a monochrome barcode image via BWIPP."""
|
|
if symb == "code128":
|
|
img = treepoem.generate_barcode(
|
|
barcode_type="code128",
|
|
data=data,
|
|
options={"includetext": True, "height": "0.3"},
|
|
scale=3,
|
|
)
|
|
else: # rectangular DataMatrix
|
|
img = treepoem.generate_barcode(
|
|
barcode_type="datamatrixrectangular",
|
|
data=data,
|
|
options={},
|
|
# options={"columns": "16", "rows": "48"},
|
|
scale=3,
|
|
)
|
|
return img.convert("1")
|
|
|
|
|
|
def compose_label(images: List[Image.Image]) -> Image.Image:
|
|
"""
|
|
Horizontally stack each barcode image with padding,
|
|
on a single monochrome canvas.
|
|
"""
|
|
border = 10 # white border around each barcode
|
|
spacing = 20 # gap between them
|
|
|
|
# add border
|
|
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):
|
|
"""
|
|
Rasterize the PIL image for Brother QL and send it via network.
|
|
"""
|
|
qlr = BrotherQLRaster(model)
|
|
qlr.exception_on_warning = True
|
|
|
|
instructions = convert(
|
|
qlr=qlr,
|
|
images=[image],
|
|
label=label,
|
|
rotate="90",
|
|
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")
|
|
def print_stacked(req: PrintRequest):
|
|
try:
|
|
code_img = make_barcode_image(req.data, "code128")
|
|
dm_img = make_barcode_image(req.data, "datamatrix_rect")
|
|
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",
|
|
}
|