From 3413211c4975ecb98d858384b3f5473800394f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=9C=EA=B3=A8=EC=95=BD=EC=82=AC?= Date: Thu, 29 Jan 2026 20:03:13 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20ESC/POS=20QR=20=EC=9D=B8=EC=87=84=20?= =?UTF-8?q?=ED=98=B8=ED=99=98=EC=84=B1=20=EA=B0=9C=EC=84=A0=20(ESC=20*=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QR 크기 축소: 150x150px → 100x100px (안정성 향상) - ESC * 명령어 사용 (24-dot double-density) - 더 많은 프린터 지원 - image_to_raster_esc_star() 함수 추가 - 디버깅 로그 추가 (각 단계 print) - QR 비트맵 실패 시 URL 텍스트로 폴백 GS v 0 방식은 일부 프린터에서 미지원 → ESC * 방식으로 변경 Co-Authored-By: Claude Sonnet 4.5 --- backend/utils/pos_qr_printer.py | 90 +++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/backend/utils/pos_qr_printer.py b/backend/utils/pos_qr_printer.py index 35b7606..3ecea75 100644 --- a/backend/utils/pos_qr_printer.py +++ b/backend/utils/pos_qr_printer.py @@ -27,17 +27,17 @@ def print_qr_receipt_escpos(qr_url, transaction_id, total_amount, """ try: - # 1. QR 코드 이미지 생성 (150x150px) - qr = qrcode.QRCode(version=1, box_size=5, border=2) + print(f"[ESC/POS] QR 인쇄 시작: {qr_url}") + + # 1. QR 코드 이미지 생성 (작게: 100x100px) + qr = qrcode.QRCode(version=1, box_size=3, border=2) qr.add_data(qr_url) qr.make(fit=True) qr_image = qr.make_image(fill_color="black", back_color="white") - qr_image = qr_image.resize((150, 150)) + qr_image = qr_image.resize((100, 100)) + print(f"[ESC/POS] QR 이미지 생성 완료: 100x100px") - # 2. QR 이미지를 ESC/POS 비트맵으로 변환 - qr_bitmap = image_to_raster(qr_image) - - # 3. ESC/POS 명령어 조립 + # 2. ESC/POS 명령어 조립 ESC = b'\x1b' GS = b'\x1d' @@ -67,11 +67,20 @@ def print_qr_receipt_escpos(qr_url, transaction_id, total_amount, commands.append("================================\n".encode('euc-kr')) commands.append("\n".encode('euc-kr')) - # QR 코드 인쇄 - commands.append(qr_bitmap) # QR 비트맵 데이터 - commands.append("\n".encode('euc-kr')) + # 3. QR 코드 인쇄 (ESC * 방식 - 더 호환성 높음) + try: + qr_bitmap = image_to_raster_esc_star(qr_image) + commands.append(qr_bitmap) + commands.append(b"\n") + print(f"[ESC/POS] QR 비트맵 변환 완료 (ESC *)") + except Exception as e: + print(f"[ESC/POS] QR 비트맵 변환 실패: {e}") + # QR 실패 시 URL 텍스트로 대체 + commands.append("QR 코드:\n".encode('euc-kr')) + commands.append(f"{qr_url}\n".encode('euc-kr')) # 안내 문구 + commands.append("\n".encode('euc-kr')) commands.append(ESC + b'!\x08') # 작게 commands.append("QR 촬영하고 포인트 받으세요!\n".encode('euc-kr')) commands.append(ESC + b'!\x00') # 보통 @@ -100,6 +109,67 @@ def print_qr_receipt_escpos(qr_url, transaction_id, total_amount, return False +def image_to_raster_esc_star(image): + """ + PIL 이미지를 ESC/POS 비트맵으로 변환 (ESC * 방식 - 호환성 높음) + + ESC * m nL nH d1...dk 명령어 사용 + m=33 (24-dot double-density) + + Args: + image (PIL.Image): PIL 이미지 객체 + + Returns: + bytes: ESC/POS 비트맵 명령어 + """ + # 이미지를 흑백으로 변환 + image = image.convert('1') # 1-bit 흑백 + width, height = image.size + + # 픽셀 데이터 가져오기 + pixels = image.load() + + # ESC * 명령어로 라인별 인쇄 (24-dot 방식) + ESC = b'\x1b' + commands = [] + + # 24픽셀(3바이트) 단위로 인쇄 + for y in range(0, height, 24): + # 현재 라인의 높이 (최대 24픽셀) + line_height = min(24, height - y) + + # 너비 바이트 수 + width_bytes = (width + 7) // 8 + + # ESC * m nL nH: m=33 (24-dot double-density) + nL = width & 0xFF + nH = (width >> 8) & 0xFF + commands.append(ESC + b'*' + bytes([33, nL, nH])) + + # 라인 데이터 생성 + for x in range(width): + # 세로 24픽셀을 3바이트로 변환 + byte1, byte2, byte3 = 0, 0, 0 + + for bit in range(line_height): + pixel_y = y + bit + if pixel_y < height: + if pixels[x, pixel_y] == 0: # 검은색 + if bit < 8: + byte1 |= (1 << (7 - bit)) + elif bit < 16: + byte2 |= (1 << (15 - bit)) + else: + byte3 |= (1 << (23 - bit)) + + commands.append(bytes([byte1, byte2, byte3])) + + # 라인 피드 + commands.append(b'\n') + + return b''.join(commands) + + def image_to_raster(image): """ PIL 이미지를 ESC/POS 비트맵 래스터 데이터로 변환