""" OTC 용법 라벨 출력 모듈 Brother QL-810W 프린터용 가로형 와이드 라벨 생성 및 출력 기반: person-lookup-web-local/print_label.py """ from PIL import Image, ImageDraw, ImageFont import logging import re from pathlib import Path # 프린터 설정 (QL-810W) PRINTER_IP = "192.168.0.168" # QR 라벨과 동일한 Brother QL-810W PRINTER_MODEL = "QL-810W" LABEL_TYPE = "29" # 29mm 연속 출력 용지 # 폰트 경로 (Windows/Linux 크로스 플랫폼) FONT_PATHS = [ "C:/Windows/Fonts/malgunbd.ttf", # Windows "/srv/person-lookup-web-local/pop_maker/fonts/malgunbd.ttf", # Linux "/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf", # Linux 대체 ] def get_font_path(): """사용 가능한 폰트 경로 반환""" for path in FONT_PATHS: if Path(path).exists(): return path return None def create_otc_label_image(drug_name, effect="", dosage_instruction="", usage_tip=""): """ OTC 용법 라벨 이미지 생성 (800 x 306px) 레이아웃: - 효능: 중앙 상단에 크게 강조 (72pt) - 약품명: 오른쪽 중간 (36pt) - 용법: 왼쪽 하단 체크박스 (40pt) - 약국명: 오른쪽 하단 테두리 박스 (32pt) Args: drug_name (str): 약품명 effect (str): 효능 dosage_instruction (str): 복용 방법 usage_tip (str): 사용 팁 Returns: PIL.Image: 가로형 와이드 라벨 이미지 (800 x 306px, mode='1') """ try: # 1. 캔버스 생성 (가로로 긴 형태) width = 800 height = 306 # Brother QL 29mm 용지 폭 img = Image.new('1', (width, height), 1) # 흰색 배경 draw = ImageDraw.Draw(img) # 2. 폰트 로드 font_path = get_font_path() try: font_effect = ImageFont.truetype(font_path, 72) # 효능 (매우 크게!) font_drugname = ImageFont.truetype(font_path, 36) # 약품명 (중간) font_dosage = ImageFont.truetype(font_path, 40) # 용법 (크게) font_pharmacy = ImageFont.truetype(font_path, 32) # 약국명 (크게) font_small = ImageFont.truetype(font_path, 26) # 사용팁 except (IOError, TypeError): font_effect = ImageFont.load_default() font_drugname = ImageFont.load_default() font_dosage = ImageFont.load_default() font_pharmacy = ImageFont.load_default() font_small = ImageFont.load_default() logging.warning("폰트 로드 실패. 기본 폰트 사용.") # 3. 레이아웃 x_margin = 25 # 효능 - 중앙 상단에 크게 (매우 강조!) if effect: effect_bbox = draw.textbbox((0, 0), effect, font=font_effect) effect_width = effect_bbox[2] - effect_bbox[0] effect_x = (width - effect_width) // 2 # 굵게 표시 (offset) for offset in [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]: draw.text((effect_x + offset[0], 20 + offset[1]), effect, font=font_effect, fill=0) # 약품명 - 오른쪽 중간 여백에 배치 drugname_bbox = draw.textbbox((0, 0), drug_name, font=font_drugname) drugname_width = drugname_bbox[2] - drugname_bbox[0] drugname_x = width - drugname_width - 30 # 오른쪽에서 30px 여백 drugname_y = 195 draw.text((drugname_x, drugname_y), drug_name, font=font_drugname, fill=0) # 용법 - 왼쪽 하단에 크게 표시 y = 120 # 효능 아래부터 시작 # 사용팁이 없으면 복용방법을 더 크게 if not usage_tip: try: font_dosage_adjusted = ImageFont.truetype(font_path, 50) except: font_dosage_adjusted = font_dosage else: font_dosage_adjusted = font_dosage if dosage_instruction: # 대괄호로 묶인 부분을 별도 줄로 분리 dosage_text = re.sub(r'\s*(\[.*?\])\s*', r'\n\1\n', dosage_instruction) # 여러 줄 처리 max_chars_per_line = 32 dosage_lines = [] text_parts = dosage_text.split('\n') for part in text_parts: part = part.strip() if not part: continue if part.startswith('[') and part.endswith(']'): dosage_lines.append(part) elif len(part) > max_chars_per_line: words = part.split() current_line = "" for word in words: if len(current_line + word) <= max_chars_per_line: current_line += word + " " else: if current_line: dosage_lines.append(current_line.strip()) current_line = word + " " if current_line: dosage_lines.append(current_line.strip()) else: dosage_lines.append(part) # 첫 줄에 체크박스 추가 if dosage_lines: first_line = f"□ {dosage_lines[0]}" draw.text((x_margin, y), first_line, font=font_dosage_adjusted, fill=0) line_spacing = 60 if not usage_tip else 50 y += line_spacing for line in dosage_lines[1:]: indent = 0 if (line.startswith('[') and line.endswith(']')) else 30 draw.text((x_margin + indent, y), line, font=font_dosage_adjusted, fill=0) y += line_spacing + 2 # 사용팁 (체크박스 + 텍스트) if usage_tip and y < height - 60: tip_text = f"□ {usage_tip}" if len(tip_text) > 55: tip_text = tip_text[:52] + "..." draw.text((x_margin, y), tip_text, font=font_small, fill=0) # 약국명 - 오른쪽 하단에 크게 (테두리 박스) sign_text = "청춘약국" sign_bbox = draw.textbbox((0, 0), sign_text, font=font_pharmacy) sign_width = sign_bbox[2] - sign_bbox[0] sign_height = sign_bbox[3] - sign_bbox[1] sign_padding_lr = 10 sign_padding_top = 5 sign_padding_bottom = 10 sign_x = width - sign_width - x_margin - 10 - sign_padding_lr sign_y = height - 55 # 테두리 박스 그리기 box_x1 = sign_x - sign_padding_lr box_y1 = sign_y - sign_padding_top box_x2 = sign_x + sign_width + sign_padding_lr box_y2 = sign_y + sign_height + sign_padding_bottom draw.rectangle([box_x1, box_y1, box_x2, box_y2], outline=0, width=2) # 약국명 텍스트 (굵게) for offset in [(0, 0), (1, 0), (0, 1), (1, 1)]: draw.text((sign_x + offset[0], sign_y + offset[1]), sign_text, font=font_pharmacy, fill=0) # 5. 테두리 (가위선 스타일) for i in range(3): draw.rectangle([5 + i, 5 + i, width - 5 - i, height - 5 - i], outline=0) logging.info(f"OTC 라벨 이미지 생성 성공: {drug_name}") return img except Exception as e: logging.error(f"OTC 라벨 이미지 생성 실패: {e}") raise def print_otc_label(drug_name, effect="", dosage_instruction="", usage_tip=""): """ OTC 용법 라벨을 Brother QL-810W 프린터로 출력 Args: drug_name (str): 약품명 effect (str): 효능 dosage_instruction (str): 복용 방법 usage_tip (str): 사용 팁 Returns: bool: 성공 여부 """ try: from brother_ql.raster import BrotherQLRaster from brother_ql.conversion import convert from brother_ql.backends.helpers import send # 1. 라벨 이미지 생성 label_img = create_otc_label_image(drug_name, effect, dosage_instruction, usage_tip) # 2. 이미지 90도 회전 (Brother QL이 세로 방향 기준이므로) label_img_rotated = label_img.rotate(90, expand=True) logging.info(f"이미지 회전 완료: {label_img_rotated.size}") # 3. Brother QL 프린터로 전송 qlr = BrotherQLRaster(PRINTER_MODEL) instructions = convert( qlr=qlr, images=[label_img_rotated], label='29', rotate='0', threshold=70.0, dither=False, compress=False, red=False, dpi_600=False, hq=True, cut=True ) send(instructions, printer_identifier=f"tcp://{PRINTER_IP}:9100") logging.info(f"[SUCCESS] OTC 용법 라벨 인쇄 성공: {drug_name}") return True except ImportError: logging.error("brother_ql 라이브러리가 설치되지 않았습니다.") return False except Exception as e: logging.error(f"[ERROR] OTC 용법 라벨 인쇄 실패: {e}") return False def generate_preview_image(drug_name, effect="", dosage_instruction="", usage_tip=""): """ 미리보기용 PNG 이미지 생성 (Base64 인코딩) Args: drug_name (str): 약품명 effect (str): 효능 dosage_instruction (str): 복용 방법 usage_tip (str): 사용 팁 Returns: str: Base64 인코딩된 PNG 이미지 (data:image/png;base64,... 형태) """ import base64 from io import BytesIO try: # 라벨 이미지 생성 label_img = create_otc_label_image(drug_name, effect, dosage_instruction, usage_tip) # RGB로 변환 (1-bit → RGB) label_img_rgb = label_img.convert('RGB') # PNG로 인코딩 buffer = BytesIO() label_img_rgb.save(buffer, format='PNG') buffer.seek(0) # Base64 인코딩 img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') return f"data:image/png;base64,{img_base64}" except Exception as e: logging.error(f"미리보기 이미지 생성 실패: {e}") return None