139 lines
4.6 KiB
Python
139 lines
4.6 KiB
Python
import socket
|
||
import os
|
||
from io import BytesIO
|
||
from PIL import Image, ImageDraw
|
||
|
||
# 引入条形码生成库
|
||
try:
|
||
import barcode
|
||
from barcode.writer import ImageWriter
|
||
except ImportError:
|
||
print("❌ 错误: 请先安装 python-barcode (pip install python-barcode)")
|
||
exit()
|
||
|
||
|
||
class BarcodeStackTester:
|
||
# 打印机配置
|
||
PRINTER_IP = "192.168.9.205"
|
||
PRINTER_PORT = 9100
|
||
|
||
# 纸张配置: 40mm x 30mm @ 300 DPI
|
||
DOTS_PER_MM = 12
|
||
CANVAS_W = int(40 * DOTS_PER_MM) # 480px
|
||
CANVAS_H = int(30 * DOTS_PER_MM) # 360px
|
||
|
||
@staticmethod
|
||
def _generate_raw_barcode(content, bar_height_mm=4):
|
||
"""
|
||
生成原始条码图片 (不强制缩放宽度,让其自然生长)
|
||
"""
|
||
try:
|
||
# Code128 自动优化 (数字会被压缩,所以20位数字其实不长)
|
||
writer = ImageWriter()
|
||
|
||
# module_width: 条码黑条的最小宽度(mm)。
|
||
# 0.2mm 在 300DPI 下约等于 2.4像素。这是一个比较清晰且节省空间的数值。
|
||
# 如果设得太大(如0.3),20位可能会超出40mm纸张。
|
||
options = {
|
||
"module_width": 0.2,
|
||
"module_height": bar_height_mm, # 条码高度
|
||
"quiet_zone": 2.0, # 两侧留白(mm)
|
||
"write_text": False, # 【关键】不写文字
|
||
"dpi": 300 # 对应打印机DPI
|
||
}
|
||
|
||
code_img = barcode.get('code128', content, writer=writer)
|
||
|
||
# 渲染到内存
|
||
buffer = BytesIO()
|
||
code_img.write(buffer, options=options)
|
||
buffer.seek(0)
|
||
|
||
return Image.open(buffer)
|
||
except Exception as e:
|
||
print(f"生成失败: {e}")
|
||
return None
|
||
|
||
def run_test(self):
|
||
# 1. 创建白色底图 (40x30mm)
|
||
canvas = Image.new('RGB', (self.CANVAS_W, self.CANVAS_H), color='white')
|
||
|
||
# 测试用例: 长度从小到大
|
||
test_lengths = [13, 15, 17, 20]
|
||
|
||
# 布局参数
|
||
current_y = 20 # 顶部起始留白 (px)
|
||
gap = 10 # 间距 (px)
|
||
|
||
print("-" * 50)
|
||
print("🚀 开始生成堆叠条码测试图...")
|
||
|
||
for length in test_lengths:
|
||
# 生成全7的内容
|
||
content = "7" * length
|
||
print(f" 正在处理: {length}位 -> {content}")
|
||
|
||
# 生成条码 (高度设为5mm左右,方便在一张纸上放下4个)
|
||
bar_img = self._generate_raw_barcode(content, bar_height_mm=5)
|
||
|
||
if bar_img:
|
||
# 计算居中位置
|
||
# bar_img.width 是条码生成的自然宽度
|
||
x_pos = (self.CANVAS_W - bar_img.width) // 2
|
||
|
||
# 如果宽度超出了纸张 (x_pos < 0),说明40mm纸打不下这么多位
|
||
if x_pos < 0:
|
||
print(f" ⚠️ 警告: {length}位条码过宽 ({bar_img.width}px > {self.CANVAS_W}px),可能会被裁切!")
|
||
x_pos = 0
|
||
|
||
# 粘贴到画布
|
||
canvas.paste(bar_img, (x_pos, current_y))
|
||
|
||
# 更新Y坐标,准备画下一个
|
||
current_y += bar_img.height + gap
|
||
else:
|
||
print(" 生成失败")
|
||
|
||
# 2. 保存预览图
|
||
preview_file = "test_stack_preview.jpg"
|
||
canvas.save(preview_file)
|
||
print(f"✅ 图片已生成: {preview_file} (请打开查看宽度是否超出)")
|
||
|
||
# 3. 发送打印 (二值化处理)
|
||
self._send_to_printer(canvas)
|
||
|
||
def _send_to_printer(self, img_rgb):
|
||
try:
|
||
# 转二值化 (白=1, 黑=0)
|
||
img_bw = img_rgb.convert('L').convert('1', dither=Image.Dither.NONE)
|
||
bitmap_data = img_bw.tobytes()
|
||
|
||
width_bytes = (img_bw.width + 7) // 8
|
||
height_dots = img_bw.height
|
||
|
||
# TSPL 指令
|
||
header = (
|
||
f"SIZE 40 mm, 30 mm\r\n"
|
||
"GAP 2 mm, 0 mm\r\n"
|
||
"CLS\r\n"
|
||
"DIRECTION 1\r\n"
|
||
).encode('gbk')
|
||
|
||
bitmap_cmd = f"BITMAP 0,0,{width_bytes},{height_dots},0,".encode('gbk')
|
||
footer = b"\r\nPRINT 1,1\r\n"
|
||
|
||
print("🖨️ 正在发送打印指令...")
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
s.settimeout(5)
|
||
s.connect((self.PRINTER_IP, self.PRINTER_PORT))
|
||
s.sendall(header + bitmap_cmd + bitmap_data + footer)
|
||
s.close()
|
||
print("✅ 指令已发送")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 打印失败: {e}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
tester = BarcodeStackTester()
|
||
tester.run_test() |