# src/certflow/services/printer/lq635kii_printer.py
"""LQ-635KII 打印机底层驱动
基于 ESC/P-K 命令集,包含手册 P101 所有命令及补充功能
定位策略:
- X 轴:绝对定位 ESC $(有效)
- Y 轴:相对移动 ESC J(有效),绝对定位 ESC ( V 无效
"""
from __future__ import annotations
try:
import win32print
_HAS_WIN32 = True
except ImportError:
_HAS_WIN32 = False
win32print = None # type: ignore[assignment]
from .escp_commands import (
Align,
Font,
LQ635KIICommand,
PaperSize,
PrinterDPI,
PrintQuality,
)
[文档]
class LQ635KIIPrinter:
"""LQ-635KII 打印机控制类
专门为 EPSON LQ-635KII 24针打印机设计
使用 ESC/P-K 命令集,包含手册全部命令
定位策略:
- X: 绝对定位(ESC $),自动应用 x_offset
- Y: 相对移动(ESC J),自动应用 y_offset
使用示例:
# 1. 基础打印(推荐使用上下文管理器)
with LQ635KIIPrinter("EPSON LQ-635KII") as printer:
printer.init()
printer.set_line_spacing_1_6()
printer.println("产品名称:高温高压阀门")
printer.println("产品型号:J41H-64")
printer.println("公称通径:DN150")
printer.println("公称压力:PN64")
printer.form_feed()
# 2. 精确定位打印(预印卡纸)- 使用推荐方法
with LQ635KIIPrinter() as printer:
printer.init()
printer.set_quality(PrintQuality.LETTER)
# 方法1:分别设置 X 和 Y(推荐)
printer.set_absolute_x_mm(25) # X=25mm(绝对定位,自动应用 x_offset)
printer.set_absolute_y_mm(30) # Y=30mm(相对移动实现,自动应用 y_offset)
printer.print_text("DN150")
printer.set_absolute_x_mm(25)
printer.set_absolute_y_mm(38)
printer.print_text("PN64")
printer.form_feed()
# 2.1 或者使用合并方法
with LQ635KIIPrinter() as printer:
printer.init()
printer.set_quality(PrintQuality.LETTER)
printer.set_absolute_xy_mm(25, 30) # 同时设置 X,Y
printer.print_text("DN150")
printer.set_absolute_xy_mm(25, 38)
printer.print_text("PN64")
printer.form_feed()
# 3. 图形模式打印(任意字体/字号)
with LQ635KIIPrinter() as printer:
printer.init()
# 需要配合 PrinterService 使用
pass
# 4. 手动控制(不使用上下文管理器)
printer = LQ635KIIPrinter("EPSON LQ-635KII")
printer.open()
printer.init()
printer.set_bold(True)
printer.println("重要提示")
printer.set_bold(False)
printer.flush() # 立即发送
printer.close()
"""
def __init__(
self,
printer_name: str | None = None,
calibration_x_offset_mm: float = 13,
y_offset_mm: float = 0,
) -> None:
"""
初始化打印机
Args:
printer_name: 打印机名称,None 则使用默认打印机
calibration_x_offset_mm: 打印机物理校准 X 偏移补偿 (mm),默认 13。
用于补偿打印机硬件装纸/打印头起点的物理偏差,对**所有**文字统一
叠加,正值向右。与英制字段偏移(TemplateFieldFormatter 的
imperial_x_offset_mm / PrinterService.x_offsets)语义不同、相互独立。
y_offset_mm: Y 方向偏移补偿 (mm),默认 0(与 PrinterService._load_y_offset 回退一致)
"""
self.printer_name = printer_name
if printer_name is None:
self.printer_name = win32print.GetDefaultPrinter() if _HAS_WIN32 else ""
self._hprinter = None
self._buffer = bytearray()
self._is_open = False
self._printer_cal_x_offset = calibration_x_offset_mm
self._y_offset = y_offset_mm
self._current_y_mm = 0.0 # 维护当前 Y 位置(用于相对移动)
self._line_spacing_mm = 4.23 # 默认 1/6 英寸
self._last_job_id: int | None = None # 最近一次 _flush 提交的 OS 作业 ID
# 原始数据监控
self._raw_monitor = False
self._raw_monitor_file: str | None = None
# ============================================================
# 基础方法
# ============================================================
[文档]
def open(self) -> LQ635KIIPrinter:
"""打开打印机连接
如果已打开则跳过,支持链式调用。
Returns:
LQ635KIIPrinter: 返回自身实例,支持链式调用
"""
if not self._is_open:
self._hprinter = win32print.OpenPrinter(self.printer_name)
self._is_open = True
return self
[文档]
def close(self) -> None:
"""关闭打印机连接
先刷新缓冲区将剩余数据发送到打印机,再关闭打印机句柄。
"""
if self._hprinter and self._is_open:
try:
self._flush()
win32print.ClosePrinter(self._hprinter)
except Exception:
pass
finally:
self._hprinter = None
self._is_open = False
[文档]
def __enter__(self) -> LQ635KIIPrinter:
"""上下文管理器入口,自动打开打印机
Returns:
LQ635KIIPrinter: 自身实例
"""
self.open()
return self
[文档]
def __exit__(self, *args: object) -> None:
"""上下文管理器出口,自动关闭打印机"""
self.close()
def _append(self, data: bytes) -> None:
"""追加原始字节数据到打印缓冲区
Args:
data: 要追加的原始字节数据
"""
self._buffer.extend(data)
if self._raw_monitor:
self._log_raw(data)
def _flush(self, job_name: str = "CertFlow") -> int | None:
"""将缓冲区数据作为一次打印作业发送到打印机
使用 RAW 模式发送,适用于 ESC/P-K 命令数据。
发送完毕后清空缓冲区。
捕获并打印作业提交后由 spooler 分配的 OS 作业 ID(``StartDocPrinter``
的返回值),用于后续 OS 级取消 / 状态查询(见 BLUEPRINT §2.3 偏差 5
作业 ID 捕获链路)。无 ``win32print``(headless)时返回 ``None``。
Args:
job_name: 打印作业名称,会写入监控日志便于区分不同任务
Returns:
int | None: OS 打印作业 ID;未真正提交(无句柄 / 空缓冲)时返回 None
"""
if not self._buffer:
return None
if not self._is_open:
self.open()
all_data = bytes(self._buffer)
if self._raw_monitor:
self._log_flush(all_data, job_name)
job_id: int | None = None
if self._hprinter:
job_id = win32print.StartDocPrinter(self._hprinter, 1, (job_name, None, "RAW"))
win32print.StartPagePrinter(self._hprinter)
win32print.WritePrinter(self._hprinter, all_data)
win32print.EndPagePrinter(self._hprinter)
win32print.EndDocPrinter(self._hprinter)
self._buffer.clear()
self._last_job_id = job_id
return job_id
[文档]
def flush(self, job_name: str = "CertFlow") -> int | None:
"""强制将缓冲区数据发送到打印机
Args:
job_name: 打印作业名称
Returns:
int | None: 本次提交的 OS 打印作业 ID(见 :meth:`_flush`)
"""
return self._flush(job_name)
# ============================================================
# 原始数据监控
# ============================================================
[文档]
def enable_raw_monitoring(
self, output_dir: str = "raw_logs", filter_cmds: list[bytes] | None = None
) -> None:
"""启用原始数据监控
启用后,每次 _append 和 _flush 都会记录原始字节数据:
- 控制台:实时显示 [RAW] 十六进制日志
- 文件:raw_full_YYYYMMDD_HHMMSS.prn 保存完整数据流
Args:
output_dir: 输出目录路径
filter_cmds: 可选,仅记录包含这些字节序列的数据块。
例如 [b'\\x1b$', b'\\x1b(V'] 只记录定位命令相关数据。
为 None 时记录所有数据。
"""
from datetime import datetime
from pathlib import Path
self._raw_monitor = True
self._raw_filter_cmds = filter_cmds or []
log_dir = Path(output_dir)
log_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self._raw_monitor_file = str(log_dir / f"raw_full_{timestamp}.prn")
print("[RAW MONITOR] 已启用")
print(f" 输出目录: {log_dir.resolve()}")
print(f" 完整数据: {self._raw_monitor_file}")
if self._raw_filter_cmds:
print(
f" 过滤模式: 仅记录包含 {[c.hex().upper() for c in self._raw_filter_cmds]} 的数据"
)
[文档]
def disable_raw_monitoring(self) -> None:
"""禁用原始数据监控"""
self._raw_monitor = False
self._raw_monitor_file = None
self._raw_filter_cmds = []
print("[RAW MONITOR] 已禁用")
def _log_raw(self, data: bytes) -> None:
"""记录单次 _append 的原始数据"""
# 过滤模式:仅记录匹配过滤条件的数据块
if self._raw_filter_cmds and not any(cmd in data for cmd in self._raw_filter_cmds):
return
hex_str = data.hex(" ").upper()
ascii_str = "".join(chr(b) if 32 <= b < 127 else "." for b in data)
print(f"[RAW +{len(data):3d}] {hex_str:<48s} |{ascii_str}|")
def _log_flush(self, all_data: bytes, job_name: str = "CertFlow") -> None:
"""记录 _flush 时的完整数据"""
print(f"\n[FLUSH] 发送 {len(all_data)} 字节到打印机")
if self._raw_monitor_file:
# 检查文件大小,超过50MB自动轮转
import os
if os.path.exists(self._raw_monitor_file):
file_size = os.path.getsize(self._raw_monitor_file)
if file_size > 50 * 1024 * 1024:
base = self._raw_monitor_file.replace(".prn", "")
new_file = f"{base}_2.prn"
print(f"[RAW MONITOR] 日志轮转: {file_size / 1024 / 1024:.1f}MB -> {new_file}")
self._raw_monitor_file = new_file
# 写入作业分隔标识
with open(self._raw_monitor_file, "ab") as f:
f.write(b"\n" + f"=== JOB: {job_name} ===\n".encode() + b"\n")
f.write(all_data)
print(f"[FLUSH] 数据已追加到: {self._raw_monitor_file}")
# ============================================================
# 基本控制
# ============================================================
[文档]
def carriage_return(self) -> None:
"""回车 (CR)
将打印位置移动到当前行的最左侧。
"""
self._append(LQ635KIICommand.CR)
[文档]
def backspace(self) -> None:
"""退格 (BS)
打印位置向左移动一个字符位。
"""
self._append(LQ635KIICommand.BS)
[文档]
def cancel_line(self) -> None:
"""取消行缓冲区 (CAN)
清除当前行缓冲区中的所有数据。
"""
self._append(LQ635KIICommand.CAN)
[文档]
def delete_char(self) -> None:
"""删除字符 (DEL)
删除上一个字符。
"""
self._append(LQ635KIICommand.DEL)
[文档]
def online(self) -> None:
"""联机 (DC1)
将打印机设置为联机状态,准备接收数据。
"""
self._append(LQ635KIICommand.DC1)
[文档]
def offline(self) -> None:
"""脱机 (DC3)
将打印机设置为脱机状态,停止接收数据。
"""
self._append(LQ635KIICommand.DC3)
# ============================================================
# 初始化与基本控制
# ============================================================
[文档]
def init(self) -> None:
"""初始化打印机 (ESC @)
清除缓冲区,恢复打印机到开机默认状态。
会先发送缓冲区中未完成的数据,避免丢失。
"""
self._flush() # 发送未完成的数据
self._buffer = bytearray(LQ635KIICommand.INIT) # 重置为 INIT 命令
[文档]
def home(self) -> None:
"""打印头归位 (ESC <)
将打印头移动到左侧起始位置。
"""
self._append(LQ635KIICommand.HOME)
self._current_y_mm = 0.0
[文档]
def beep(self) -> None:
"""蜂鸣器鸣叫 (BEL)
发送蜂鸣命令,打印机发出提示音。
"""
self._append(LQ635KIICommand.BEL)
[文档]
def feed(self, lines: int = 1) -> None:
"""走纸 N 行 (LF)
Args:
lines: 行数,取值范围 1-255,超出自动截断
"""
if lines > 0:
self._append(LQ635KIICommand.LF * min(lines, 255))
self._current_y_mm += lines * self._line_spacing_mm # 1/6 英寸 ≈ 4.23mm
# ============================================================
# 纸张格式
# ============================================================
[文档]
def set_paper_size(self, paper: PaperSize) -> None:
"""设置纸张尺寸
Args:
paper: 纸张尺寸对象
"""
if self._hprinter:
try:
devmode = win32print.GetPrinter(self._hprinter, 2)["pDevMode"]
devmode.PaperSize = 256
devmode.PaperWidth = int(paper.width_mm * 10)
devmode.PaperLength = int(paper.height_mm * 10)
win32print.SetPrinter(self._hprinter, 2, devmode, 0)
except Exception:
pass
[文档]
def set_left_margin(self, col: int) -> None:
"""设置左边界 (ESC l n)
Args:
col: 列数 (在10cpi下)
"""
self._append(LQ635KIICommand.LEFT_MARGIN + bytes([col & 0xFF]))
[文档]
def set_right_margin(self, col: int) -> None:
"""设置右边界 (ESC Q n)
Args:
col: 列数 (在10cpi下)
"""
self._append(LQ635KIICommand.RIGHT_MARGIN + bytes([col & 0xFF]))
[文档]
def set_line_spacing(self, n: int, unit: str = "180") -> None:
"""设置行间距
Args:
n: 间距值 (1-255)
unit: 单位 ("60", "180", "360")
"""
n_val = min(max(n, 0), 255)
if unit == "60":
self._append(LQ635KIICommand.LINE_SPACING_N_60 + bytes([n_val]))
self._line_spacing_mm = n_val * 25.4 / 60
elif unit == "360":
self._append(LQ635KIICommand.LINE_SPACING_N_360 + bytes([n_val]))
self._line_spacing_mm = n_val * 25.4 / 360
else: # "180"
self._append(LQ635KIICommand.LINE_SPACING_N_180 + bytes([n_val]))
self._line_spacing_mm = n_val * 25.4 / 180
[文档]
def set_line_spacing_1_6(self) -> None:
"""设置行间距为 1/6 英寸(打印机默认值)
适用于常规文本打印。
"""
self._append(LQ635KIICommand.LINE_SPACING_1_6)
self._line_spacing_mm = 25.4 / 6 # ≈ 4.233mm
[文档]
def set_line_spacing_1_8(self) -> None:
"""设置行间距为 1/8 英寸
比默认间距更紧凑。
"""
self._append(LQ635KIICommand.LINE_SPACING_1_8)
self._line_spacing_mm = 25.4 / 8 # ≈ 3.175mm
[文档]
def set_skip_perf(self, lines: int) -> None:
"""设置页缝跳过 (ESC N n)
Args:
lines: 每页底部跳过的行数
"""
self._append(LQ635KIICommand.PAGE_SKIP_PERF + bytes([lines & 0xFF]))
[文档]
def cancel_skip_perf(self) -> None:
"""取消页缝跳过 (ESC O)
取消之前设置的页缝跳过功能,恢复完整页面打印。
"""
self._append(LQ635KIICommand.PAGE_SKIP_OFF)
[文档]
def set_page_length_lines(self, lines: int) -> None:
"""设置页长(按行) (ESC C n)
Args:
lines: 每页行数,取值范围 1-127
"""
self._append(LQ635KIICommand.PAGE_LENGTH_LINES + bytes([min(lines, 127) & 0xFF]))
[文档]
def set_page_length_inch(self, inches: int) -> None:
"""设置页长(按英寸) (ESC C NUL n)
Args:
inches: 每页长度,单位英寸,取值范围 1-22
"""
self._append(LQ635KIICommand.PAGE_LENGTH_INCH + bytes([min(inches, 22) & 0xFF]))
# ============================================================
# 对齐
# ============================================================
[文档]
def set_align(self, align: Align) -> None:
"""设置对齐方式 (ESC a n)
Args:
align: 对齐方式枚举值 (LEFT/CENTER/RIGHT/JUSTIFY)
"""
self._append(LQ635KIICommand.ALIGN_LEFT + bytes([align.value]))
[文档]
def left_align(self) -> None:
"""设置左对齐"""
self.set_align(Align.LEFT)
[文档]
def center(self) -> None:
"""设置居中对齐"""
self.set_align(Align.CENTER)
[文档]
def right_align(self) -> None:
"""设置右对齐"""
self.set_align(Align.RIGHT)
[文档]
def justify(self) -> None:
"""设置两端对齐"""
self.set_align(Align.JUSTIFY)
# ============================================================
# 打印品质与字体
# ============================================================
[文档]
def set_quality(self, quality: PrintQuality) -> None:
"""设置打印品质 (ESC x n)
Args:
quality: 打印品质,DRAFT(草体/快速) 或 LETTER(文书体/高质量)
"""
if quality == PrintQuality.DRAFT:
self._append(LQ635KIICommand.DRAFT_QUALITY)
else:
self._append(LQ635KIICommand.LETTER_QUALITY)
[文档]
def set_draft(self) -> None:
"""设置为草体模式(快速打印)"""
self.set_quality(PrintQuality.DRAFT)
[文档]
def set_letter(self) -> None:
"""设置为文书体模式(高质量打印)"""
self.set_quality(PrintQuality.LETTER)
[文档]
def set_font(self, font: Font) -> None:
"""设置西文字体 (ESC k n)
Args:
font: 字体枚举值 (ROMAN/SANS_SERIF/COURIER/PRESTIGE/SCRIPT)
"""
self._append(LQ635KIICommand.FONT_ROMAN + bytes([font.value]))
[文档]
def set_pitch(self, cpi: int) -> None:
"""设置字符间距 (CPI)
Args:
cpi: 每英寸字符数,支持 10/12/15
"""
if cpi == 10:
self._append(LQ635KIICommand.PITCH_10CPI)
elif cpi == 12:
self._append(LQ635KIICommand.PITCH_12CPI)
elif cpi == 15:
self._append(LQ635KIICommand.PITCH_15CPI)
[文档]
def set_proportional(self, on: bool = True) -> None:
"""设置比例间距模式
比例模式下每个字符宽度不同(如 i 比 W 窄),关闭后恢复等宽模式。
Args:
on: True 开启比例间距,False 关闭
"""
if on:
self._append(LQ635KIICommand.PROPORTIONAL_ON)
else:
self._append(LQ635KIICommand.PROPORTIONAL_OFF)
[文档]
def set_character_space(self, n: int) -> None:
"""设置字符间距 (ESC SP n)
Args:
n: 间距值,单位 n/120 英寸 (0-127)
"""
self._append(LQ635KIICommand.CHARACTER_SPACE + bytes([min(n, 127) & 0xFF]))
# ============================================================
# 放大缩小
# ============================================================
[文档]
def set_double_width(self, on: bool = True) -> None:
"""倍宽打印 (ESC W)
Args:
on: True 开启倍宽,False 恢复正常宽度
"""
if on:
self._append(LQ635KIICommand.DOUBLE_WIDTH_ON)
else:
self._append(LQ635KIICommand.DOUBLE_WIDTH_OFF)
[文档]
def set_double_height(self, on: bool = True) -> None:
"""倍高打印 (ESC w)
Args:
on: True 开启倍高,False 恢复正常高度
"""
if on:
self._append(LQ635KIICommand.DOUBLE_HEIGHT_ON)
else:
self._append(LQ635KIICommand.DOUBLE_HEIGHT_OFF)
[文档]
def set_condensed(self, on: bool = True) -> None:
"""压缩打印模式(约 17.5 CPI) (SI / DC2)
Args:
on: True 开启压缩模式,False 关闭
"""
if on:
self._append(LQ635KIICommand.CONDENSED_ON)
else:
self._append(LQ635KIICommand.CONDENSED_OFF)
[文档]
def set_single_line_width(self, on: bool = True) -> None:
"""单行倍宽 (SO / DC4)
仅对当前行有效,换行后自动恢复。
Args:
on: True 开启单行倍宽,False 关闭
"""
if on:
self._append(LQ635KIICommand.SINGLE_LINE_WIDTH)
else:
self._append(LQ635KIICommand.SINGLE_LINE_WIDTH_OFF)
# ============================================================
# 特殊效果
# ============================================================
[文档]
def set_bold(self, on: bool = True) -> None:
"""粗体效果 (ESC E / ESC F)
Args:
on: True 开启粗体,False 关闭
"""
if on:
self._append(LQ635KIICommand.BOLD_ON)
else:
self._append(LQ635KIICommand.BOLD_OFF)
[文档]
def set_italic(self, on: bool = True) -> None:
"""斜体效果 (ESC 4 / ESC 5)
Args:
on: True 开启斜体,False 关闭
"""
if on:
self._append(LQ635KIICommand.ITALIC_ON)
else:
self._append(LQ635KIICommand.ITALIC_OFF)
[文档]
def set_underline(self, on: bool = True) -> None:
"""下划线效果 (ESC - 1 / ESC - 0)
Args:
on: True 开启下划线,False 关闭
"""
if on:
self._append(LQ635KIICommand.UNDERLINE_ON)
else:
self._append(LQ635KIICommand.UNDERLINE_OFF)
[文档]
def set_double_strike(self, on: bool = True) -> None:
"""重叠打印效果(加深颜色) (ESC G / ESC H)
打印机在同一位置重复打印两次以加深颜色。
Args:
on: True 开启重叠打印,False 关闭
"""
if on:
self._append(LQ635KIICommand.DOUBLE_STRIKE_ON)
else:
self._append(LQ635KIICommand.DOUBLE_STRIKE_OFF)
[文档]
def set_superscript(self) -> None:
"""设置为上标模式 (ESC S 0)"""
self._append(LQ635KIICommand.SUPERSCRIPT)
[文档]
def set_subscript(self) -> None:
"""设置为下标模式 (ESC S 1)"""
self._append(LQ635KIICommand.SUBSCRIPT)
[文档]
def cancel_super_sub(self) -> None:
"""取消上标/下标模式 (ESC T)"""
self._append(LQ635KIICommand.SUPERSUB_OFF)
[文档]
def set_print_mode(self, mode_byte: int) -> None:
"""组合打印模式 (ESC ! n)
Args:
mode_byte: 模式字节,各bit含义:
bit0: 10cpi (0) / 12cpi (1)
bit1: 压缩打印
bit2: 粗体
bit3: 倍宽
bit4: 斜体
bit5: 下划线
bit6: 上标/下标
bit7: 倍高
"""
self._append(LQ635KIICommand.PRINT_MODE + bytes([mode_byte & 0xFF]))
# ============================================================
# 定位(核心:X 绝对 + Y 相对)
# ============================================================
[文档]
def set_absolute_x(self, x_60inch: int) -> None:
"""绝对水平定位 (ESC $ nL nH)
Args:
x_60inch: 水平位置,单位 1/60 英寸
"""
nl = x_60inch & 0xFF
nh = (x_60inch >> 8) & 0xFF
self._append(LQ635KIICommand.ABSOLUTE_HORIZONTAL + bytes([nl, nh]))
[文档]
def set_absolute_x_mm(self, x_mm: float) -> None:
"""绝对水平定位(毫米单位)- 自动叠加打印机物理校准偏移
Args:
x_mm: 水平位置,单位毫米(模板/字段逻辑坐标,不含物理校准)
"""
# _printer_cal_x_offset 为打印机硬件校准,对全部文字统一叠加;
# 英制字段的额外偏移已在调用方 x_mm 参数中预先加好(见 PrinterService)。
actual_x = x_mm + self._printer_cal_x_offset
x_60inch = int(actual_x * 60 / 25.4)
self.set_absolute_x(x_60inch)
[文档]
def move_y_relative(self, dots: int) -> None:
"""向下移动 (ESC J n) - 单位 1/180 英寸"""
n = min(max(dots, 0), 255)
self._append(LQ635KIICommand.RELATIVE_VERTICAL_MOVE + bytes([n]))
[文档]
def move_y_mm(self, delta_mm: float) -> None:
"""向下移动指定毫米(相对移动)"""
delta_dots = int(delta_mm * 180 / 25.4)
if delta_dots > 0:
self.move_y_relative(delta_dots)
self._current_y_mm += delta_mm
[文档]
def set_absolute_y_mm(self, y_mm: float) -> None:
"""绝对垂直定位(毫米单位)- 使用相对移动实现
由于 ESC ( V 无效,改用 ESC J 相对移动,
从当前位置移动到目标 Y 位置。
"""
# 应用 Y 偏移补偿
target_y = y_mm + self._y_offset
delta_y = target_y - self._current_y_mm
if delta_y > 0:
self.move_y_mm(delta_y)
elif delta_y < 0:
# 向上移动(使用 ESC T,有限支持)
delta_dots = int(abs(delta_y) * 180 / 25.4)
self.move_y_up(min(delta_dots, 255))
self._current_y_mm = target_y
# 注意:向下移动后,_current_y_mm 在 move_y_mm 中已更新
# 向上移动后需要手动更新
[文档]
def set_absolute_xy_mm(self, x_mm: float, y_mm: float) -> None:
"""同时设置 X,Y 绝对位置(推荐方法)
Args:
x_mm: X 坐标(毫米)
y_mm: Y 坐标(毫米)
"""
self.set_absolute_x_mm(x_mm)
self.set_absolute_y_mm(y_mm)
[文档]
def move_y_up(self, dots: int) -> None:
"""向上移动 (ESC T n) - 单位 1/180 英寸"""
n = min(max(dots, 0), 255)
self._append(LQ635KIICommand.REVERSE_VERTICAL_MOVE + bytes([n]))
[文档]
def move_x_relative(self, dots: int) -> None:
r"""相对水平移动 (ESC \ nL nH) - 备用,一般用绝对定位
Args:
dots: 移动点数,单位 1/180 英寸 (正数右移,负数左移)
"""
if dots >= 0:
nl = dots & 0xFF
nh = (dots >> 8) & 0xFF
else:
n = 65536 + dots
nl = n & 0xFF
nh = (n >> 8) & 0xFF
self._append(LQ635KIICommand.RELATIVE_HORIZONTAL + bytes([nl, nh]))
# 弃用方法(保留但不推荐使用,因为 ESC ( V 无效)
[文档]
def set_absolute_y(self, y_180inch: int) -> None:
"""绝对垂直定位 (ESC ( V) - 已弃用,命令无效"""
pass
# ============================================================
# 字符表选择
# ============================================================
[文档]
def set_character_table(self, table: int) -> None:
"""选择字符表 (ESC t n)
Args:
table: 0=斜体表, 1=图形表, 2=下载字符表
"""
self._append(LQ635KIICommand.CHARACTER_TABLE_ITALIC + bytes([table & 0x03]))
[文档]
def set_italic_table(self) -> None:
"""选择斜体字符表"""
self._append(LQ635KIICommand.CHARACTER_TABLE_ITALIC)
[文档]
def set_graphics_table(self) -> None:
"""选择图形字符表"""
self._append(LQ635KIICommand.CHARACTER_TABLE_GRAPHICS)
# ============================================================
# 文本打印
# ============================================================
[文档]
def print_text(self, text: str) -> None:
"""打印文本
LQ-635KII 自动识别中文字符,直接发送 GB2312/GB18030 编码即可。
Args:
text: 要打印的文本内容
"""
try:
# 使用 GB18030 编码 (兼容 GB2312,支持更多汉字)
self._append(text.encode("gb18030", errors="ignore"))
except Exception:
self._append(text.encode("gbk", errors="ignore"))
[文档]
def println(self, text: str = "") -> None:
"""打印一行文本并换行
Args:
text: 要打印的文本内容,默认为空(仅换行)
"""
if text:
self.print_text(text)
self._append(LQ635KIICommand.LF)
self._current_y_mm += self._line_spacing_mm # 1/6 英寸 ≈ 4.23mm
[文档]
def print_line(self, text: str) -> None:
"""打印一行文本并换行(println 的别名)
Args:
text: 要打印的文本内容
"""
self.println(text)
[文档]
def newline(self) -> None:
"""发送换行命令 (LF)"""
self._append(LQ635KIICommand.LF)
self._current_y_mm += self._line_spacing_mm
# ============================================================
# 图形打印
# ============================================================
[文档]
def print_graphics_8pin(self, data: bytes, width: int, mode: str = "single") -> None:
"""8针图形打印
Args:
data: 图形数据 (每列1字节)
width: 宽度 (列数)
mode: 模式,可选:
- "single": 60 DPI
- "double": 120 DPI
- "hs_double": 120 DPI 高速
- "quad": 240 DPI
"""
nl = width & 0xFF
nh = (width >> 8) & 0xFF
if mode == "double":
cmd = LQ635KIICommand.GRAPHICS_8PIN_DOUBLE
elif mode == "hs_double":
cmd = LQ635KIICommand.GRAPHICS_8PIN_HS_DOUBLE
elif mode == "quad":
cmd = LQ635KIICommand.GRAPHICS_8PIN_QUAD
else:
cmd = LQ635KIICommand.GRAPHICS_8PIN_SINGLE
self._append(cmd + bytes([nl, nh]) + data)
[文档]
def print_graphics_24pin(
self, data: bytes, width: int, dpi: PrinterDPI = PrinterDPI.DPI_180
) -> None:
"""24针图形打印
Args:
data: 图形数据 (每列3字节)
width: 宽度 (列数)
dpi: 分辨率,可选 DPI_60/DPI_90/DPI_120/DPI_180/DPI_360
"""
nl = width & 0xFF
nh = (width >> 8) & 0xFF
if dpi == PrinterDPI.DPI_60:
cmd = LQ635KIICommand.GRAPHICS_24PIN_SINGLE
elif dpi == PrinterDPI.DPI_90:
cmd = LQ635KIICommand.GRAPHICS_24PIN_CRT
elif dpi == PrinterDPI.DPI_120:
cmd = LQ635KIICommand.GRAPHICS_24PIN_DOUBLE
elif dpi == PrinterDPI.DPI_360:
cmd = LQ635KIICommand.GRAPHICS_24PIN_HEX
else:
cmd = LQ635KIICommand.GRAPHICS_24PIN_TRIPLE
self._append(cmd + bytes([nl, nh]) + data)
[文档]
def redefine_graphics(self, target: bytes, source: bytes) -> None:
"""重新定义图形模式 (ESC ? s m)
将标准图形命令重映射到 ESC * 的某个模式。
Args:
target: 要重定义的命令 (如 b"K", b"L", b"Y", b"Z")
source: 源模式 (如 b"\x20", b"\x21", b"\x26", b"\x27", b"\x28")
"""
self._append(LQ635KIICommand.REDEFINE_GRAPHICS + target + source)
# ============================================================
# 制表位
# ============================================================
[文档]
def set_horizontal_tabs(self, tabs: list[int]) -> None:
"""设置水平制表位 (ESC D)
Args:
tabs: 制表位列位置列表,以0结尾
"""
data = LQ635KIICommand.SET_HORIZONTAL_TABS
for tab in tabs:
data += bytes([tab])
data += b"\x00"
self._append(data)
[文档]
def tab_horizontal(self) -> None:
"""跳转到下一个水平制表位 (HT)"""
self._append(LQ635KIICommand.HORIZONTAL_TAB)
[文档]
def set_vertical_tabs(self, tabs: list[int]) -> None:
"""设置垂直制表位 (ESC B)
Args:
tabs: 制表位行位置列表,以0结尾
"""
data = LQ635KIICommand.SET_VERTICAL_TABS
for tab in tabs:
data += bytes([tab])
data += b"\x00"
self._append(data)
[文档]
def tab_vertical(self) -> None:
"""跳转到下一个垂直制表位 (VT)"""
self._append(LQ635KIICommand.VERTICAL_TAB)
[文档]
def set_vertical_tabs_channel(self, channel: int, tabs: list[int]) -> None:
"""设置垂直制表位通道 (ESC b c n1 n2...0)
Args:
channel: 通道号 (0-7)
tabs: 制表位行位置列表
"""
data = LQ635KIICommand.SET_VERTICAL_TABS_CHANNEL + bytes([channel])
for tab in tabs:
data += bytes([tab])
data += b"\x00"
self._append(data)
[文档]
def select_vfu_channel(self, channel: int) -> None:
"""选择 VFU 通道 (ESC / c)
Args:
channel: 通道号 (0-7)
"""
self._append(LQ635KIICommand.SELECT_VFU_CHANNEL + bytes([channel & 0x07]))
# ============================================================
# 高级功能
# ============================================================
[文档]
def print_barcode(self, code: str, barcode_type: int = 0) -> None:
"""打印条码
注意:此功能需要打印机固件支持条码命令。
Args:
code: 条码内容
barcode_type: 条码类型编号 (需要打印机支持)
"""
# 使用 ESC (B 命令 (需要确认打印机支持)
self._append(LQ635KIICommand.ESC + b"(B" + bytes([len(code) & 0xFF, 0, barcode_type]))
self._append(code.encode("ascii"))