# qr_printer.py - Brother QL-710W QR 라벨 인쇄 # person-lookup-web-local/print_label.py에서 핵심 기능만 추출 from PIL import Image, ImageDraw, ImageFont import io import logging import qrcode # 프린터 설정 PRINTER_IP = "192.168.0.121" PRINTER_MODEL = "QL-710W" LABEL_TYPE = "29" # 29mm 연속 출력 용지 # Windows 폰트 경로 FONT_PATH = "C:/Windows/Fonts/malgunbd.ttf" logging.basicConfig(level=logging.INFO) def create_drug_qr_label(drug_name, barcode, sale_price, drug_code=None, pharmacy_name='청춘약국'): """ 약품 QR 라벨 이미지 생성 Parameters: drug_name (str): 약품명 barcode (str): 바코드 (QR 코드로 변환) sale_price (float): 판매가격 drug_code (str, optional): 약품 코드 (바코드가 없을 때 대체) pharmacy_name (str, optional): 약국 이름 Returns: PIL.Image: 생성된 라벨 이미지 """ label_width = 306 label_height = 380 image = Image.new("1", (label_width, label_height), "white") draw = ImageDraw.Draw(image) # 폰트 설정 try: drug_name_font = ImageFont.truetype(FONT_PATH, 32) price_font = ImageFont.truetype(FONT_PATH, 36) label_font = ImageFont.truetype(FONT_PATH, 24) except IOError: drug_name_font = ImageFont.load_default() price_font = ImageFont.load_default() label_font = ImageFont.load_default() logging.warning("폰트 로드 실패. 기본 폰트 사용.") # 바코드가 없으면 약품 코드 사용 qr_data = barcode if barcode else (drug_code if drug_code else "NO_BARCODE") # QR 코드 생성 qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=4, border=1, ) qr.add_data(qr_data) qr.make(fit=True) qr_img = qr.make_image(fill_color="black", back_color="white") # QR 코드 크기 조정 및 배치 qr_size = 130 qr_img = qr_img.resize((qr_size, qr_size), Image.LANCZOS) qr_x = (label_width - qr_size) // 2 qr_y = 15 if qr_img.mode != '1': qr_img = qr_img.convert('1') image.paste(qr_img, (qr_x, qr_y)) # 약품명 (QR 코드 아래) y_position = qr_y + qr_size + 10 def draw_wrapped_text(draw, text, y, font, max_width): """텍스트를 여러 줄로 표시""" chars = list(text) lines = [] current_line = "" for char in chars: test_line = current_line + char bbox = draw.textbbox((0, 0), test_line, font=font) w = bbox[2] - bbox[0] if w <= max_width: current_line = test_line else: if current_line: lines.append(current_line) current_line = char if current_line: lines.append(current_line) lines = lines[:2] # 최대 2줄 for line in lines: bbox = draw.textbbox((0, 0), line, font=font) w, h = bbox[2] - bbox[0], bbox[3] - bbox[1] draw.text(((label_width - w) / 2, y), line, font=font, fill="black") y += h + 5 return y y_position = draw_wrapped_text(draw, drug_name, y_position, drug_name_font, label_width - 40) y_position += 8 # 가격 if sale_price and sale_price > 0: price_text = f"₩{int(sale_price):,}" else: price_text = "가격 미정" bbox = draw.textbbox((0, 0), price_text, font=price_font) w, h = bbox[2] - bbox[0], bbox[3] - bbox[1] draw.text(((label_width - w) / 2, y_position), price_text, font=price_font, fill="black") y_position += h + 15 # 구분선 line_margin = 30 draw.line([(line_margin, y_position), (label_width - line_margin, y_position)], fill="black", width=2) y_position += 20 # 약국 이름 signature_text = " ".join(pharmacy_name) bbox = draw.textbbox((0, 0), signature_text, font=label_font) w_sig, h_sig = bbox[2] - bbox[0], bbox[3] - bbox[1] padding = 10 box_x = (label_width - w_sig) / 2 - padding box_y = y_position box_x2 = box_x + w_sig + 2 * padding box_y2 = box_y + h_sig + 2 * padding draw.rectangle([(box_x, box_y), (box_x2, box_y2)], outline="black", width=2) draw.text(((label_width - w_sig) / 2, box_y + padding), signature_text, font=label_font, fill="black") # 절취선 테두리 draw_scissor_border(draw, label_width, label_height) return image def draw_scissor_border(draw, width, height, edge_size=10, steps=20): """절취선 테두리""" # 상단 top_points = [] step_x = width / (steps * 2) for i in range(steps * 2 + 1): x = i * step_x y = 0 if i % 2 == 0 else edge_size top_points.append((int(x), int(y))) draw.line(top_points, fill="black", width=2) # 하단 bottom_points = [] for i in range(steps * 2 + 1): x = i * step_x y = height if i % 2 == 0 else height - edge_size bottom_points.append((int(x), int(y))) draw.line(bottom_points, fill="black", width=2) # 좌측 left_points = [] step_y = height / (steps * 2) for i in range(steps * 2 + 1): y = i * step_y x = 0 if i % 2 == 0 else edge_size left_points.append((int(x), int(y))) draw.line(left_points, fill="black", width=2) # 우측 right_points = [] for i in range(steps * 2 + 1): y = i * step_y x = width if i % 2 == 0 else width - edge_size right_points.append((int(x), int(y))) draw.line(right_points, fill="black", width=2) def print_drug_qr_label(drug_name, barcode, sale_price, drug_code=None, pharmacy_name='청춘약국'): """ 약품 QR 라벨 인쇄 실행 Parameters: drug_name (str): 약품명 barcode (str): 바코드 sale_price (float): 판매가격 drug_code (str, optional): 약품 코드 pharmacy_name (str, optional): 약국 이름 Returns: dict: 성공/실패 결과 """ try: from brother_ql.raster import BrotherQLRaster from brother_ql.conversion import convert from brother_ql.backends.helpers import send label_image = create_drug_qr_label(drug_name, barcode, sale_price, drug_code, pharmacy_name) # 이미지를 메모리 스트림으로 변환 image_stream = io.BytesIO() label_image.save(image_stream, format="PNG") image_stream.seek(0) # Brother QL 프린터로 전송 qlr = BrotherQLRaster(PRINTER_MODEL) instructions = convert( qlr=qlr, images=[Image.open(image_stream)], label=LABEL_TYPE, rotate="0", threshold=70.0, dither=False, compress=False, lq=True, red=False ) send(instructions, printer_identifier=f"tcp://{PRINTER_IP}:9100") logging.info(f"QR 라벨 인쇄 성공: {drug_name}, 바코드={barcode}") return {"success": True, "message": f"{drug_name} QR 라벨 인쇄 완료"} except ImportError as e: logging.error(f"brother_ql 라이브러리 없음: {e}") return {"success": False, "error": "brother_ql 라이브러리가 설치되지 않았습니다"} except Exception as e: logging.error(f"QR 라벨 인쇄 실패: {e}") return {"success": False, "error": str(e)} def preview_qr_label(drug_name, barcode, sale_price, drug_code=None, pharmacy_name='청춘약국'): """ QR 라벨 미리보기 (base64 이미지 반환) """ import base64 label_image = create_drug_qr_label(drug_name, barcode, sale_price, drug_code, pharmacy_name) # PNG로 변환 image_stream = io.BytesIO() # 1-bit 이미지를 RGB로 변환하여 더 깔끔하게 rgb_image = label_image.convert('RGB') rgb_image.save(image_stream, format="PNG") image_stream.seek(0) base64_image = base64.b64encode(image_stream.read()).decode('utf-8') return f"data:image/png;base64,{base64_image}" if __name__ == "__main__": # 테스트 result = print_drug_qr_label( drug_name="벤포파워Z", barcode="8806418067510", sale_price=3000, pharmacy_name="청춘약국" ) print(result)