# src/certflow/services/printer/print_strategy.py
"""打印策略接口 - 支持 GDI 和 ESC/P-K 切换
允许通过 PrintConfig 配置切换打印引擎,业务层无需修改代码。
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Protocol
if TYPE_CHECKING:
from certflow.config.print_config import RuntimePrintConfig
from certflow.services.printer.printer_service import PrinterService
[文档]
class PrintStrategy(Protocol):
"""打印策略协议"""
[文档]
def print_certificate(
self,
data: dict[str, Any],
serial_number: str,
copies: int = 1,
config: RuntimePrintConfig | None = None,
) -> bool:
"""打印合格证"""
...
[文档]
class GDIPrintStrategy:
"""GDI 打印策略(原有实现)
使用 win32ui GDI 进行精确坐标打印,适用于预印卡纸。
"""
def __init__(self, printer_name: str = "") -> None:
self.printer_name = printer_name
self._last_os_job_id: int | None = None
[文档]
def print_certificate(
self,
data: dict[str, Any],
serial_number: str,
copies: int = 1,
config: RuntimePrintConfig | None = None,
) -> bool:
"""调用原有 GDI 打印逻辑"""
from certflow.services.cert_print_engine import CertPrintEngine
engine = CertPrintEngine(self.printer_name)
ok = engine._print_single_gdi(data, serial_number, self.printer_name)
self._last_os_job_id = engine.last_os_job_id
return ok
@property
def last_os_job_id(self) -> int | None:
"""最近一次 GDI 打印提交的 OS 作业 ID(由 CertPrintEngine 捕获)。"""
return self._last_os_job_id
[文档]
class ESCPPrintStrategy:
"""ESC/P-K 打印策略(新驱动)
使用 ESC/P-K 命令直接控制 LQ-635KII 打印机,
坐标从 PrintConfig.template.fields 获取。
"""
def __init__(self, printer_name: str = "") -> None:
self.printer_name = printer_name
self._service = None
def _get_service(self) -> PrinterService | None:
if self._service is None:
from .printer_service import PrinterService
self._service = PrinterService(self.printer_name or None)
return self._service
@property
def last_os_job_id(self) -> int | None:
"""最近一次 ESC/P-K 打印提交的 OS 作业 ID(委托 PrinterService)。"""
return self._service.last_os_job_id if self._service is not None else None
[文档]
def print_certificate(
self,
data: dict[str, Any],
serial_number: str,
copies: int = 1,
config: RuntimePrintConfig | None = None,
) -> bool:
"""使用 RuntimePrintConfig 驱动的精确定位打印。
队列路径(config=None)在此构建 RuntimePrintConfig:读 ``coordinates.yaml``
逐字段坐标 + ``print_layout`` 用户覆盖(见 ``print_config.from_template``),
使实打与预览/配置同源,不再使用硬编码 ``positions``(蓝图 §13 D1/D3 修复)。
"""
from certflow.config.print_config import RuntimePrintConfig as _RuntimePrintConfig
from certflow.services.printer.template_field_formatter import (
family_of,
get_field_formatter,
resolve_template_key,
)
service = self._get_service()
cert_data = {
"product_name": data.get("product_name", ""),
"product_model": data.get("product_model", ""),
"dn": data.get("dn", ""),
"pn": data.get("pn", ""),
"temperature": data.get("temperature", ""),
"medium": data.get("medium", ""),
"check_standard": data.get("check_standard", ""),
"inspector_id": data.get("inspector_id", ""),
"manufacture_date": data.get("manufacture_date", ""),
"cert_number": serial_number,
}
# 英制字段 X 偏移(mm):优先用调用方透传的 data["_x_offsets"];
# 队列路径 data 无此键时,按模板语言族现场计算(与 PrintView._format_print_data 同源)。
# 注意:这是「英制字段相对模板基点的额外右移」,与
# LQ635KIIPrinter._printer_cal_x_offset(打印机物理校准,对所有文字统一叠加)
# 语义不同、相互独立;两者在 set_absolute_x_mm 内叠加生效,均为正值向右。
x_offsets = data.get("_x_offsets") if isinstance(data, dict) else None
if not x_offsets and config is None:
template_key = resolve_template_key(
data.get("template_type") or data.get("template_name")
)
family = family_of(template_key)
formatter = get_field_formatter()
off: dict[str, float] = {}
for f in ("pn", "temperature", "medium", "dn"):
raw = data.get(f)
if raw:
_, xo = formatter.format(f, raw, family)
if xo:
off[f] = xo
x_offsets = off or None
if config is None:
template_key = resolve_template_key(
data.get("template_type") or data.get("template_name")
)
config = _RuntimePrintConfig.from_template(template_key, printer_name=self.printer_name)
return service.print_by_template_config(config, cert_data, copies, x_offsets=x_offsets)
[文档]
class PrintStrategyFactory:
"""打印策略工厂
根据配置返回对应的打印策略实例。
"""
[文档]
@staticmethod
def create(engine: str = "escp", printer_name: str = "") -> PrintStrategy:
"""创建打印策略
Args:
engine: 引擎类型 ("gdi" 或 "escp")
printer_name: 打印机名称,为空则使用默认打印机
Returns:
打印策略实例
"""
if engine == "gdi":
return GDIPrintStrategy(printer_name)
return ESCPPrintStrategy(printer_name)
[文档]
@staticmethod
def from_config(
printer_name: str = "", config: RuntimePrintConfig | None = None
) -> PrintStrategy:
"""从 RuntimePrintConfig 或 config.yaml 读取引擎类型并创建策略
Args:
printer_name: 打印机名称,为空则使用默认打印机
config: RuntimePrintConfig 实例,优先使用;为 None 时回退到 config.yaml
Returns:
打印策略实例
"""
engine = "escp"
if config:
engine = config.engine
else:
try:
from certflow.config.settings import cfg
engine = cfg("print.engine", "escp")
except Exception:
pass
return PrintStrategyFactory.create(engine, printer_name)