# src/certflow/config/print_config.py
"""打印配置 - 一次加载,全链路透传
同时提供打印配置的模块级 helper(原 ``services/cert_print_config.py``,
已于重构 §2.3 迁入本 canonical 模块):加载、缓存、热重载与字段定义读取。
"""
from __future__ import annotations
from dataclasses import dataclass, field
from functools import lru_cache
from typing import Any, Literal
from certflow.config.settings import cfg, get_override_section
from certflow.utils.logger import logger
# 毫米 → 磅 换算系数
MM2PT = 2.83465
# ============================================================
# 模块级 helper(原 services/cert_print_config.py 迁入)
# ============================================================
[文档]
@lru_cache(maxsize=1)
def load_print_config() -> dict[str, Any]:
"""从 config.yaml → print 加载打印配置(canonical 唯一信源,带缓存)"""
try:
return cfg("print", {})
except Exception as e:
logger.warning(f"加载打印配置失败,使用空配置: {e}")
return {}
[文档]
def reload_print_config() -> None:
"""热重载打印配置:清除 YAML 缓存 + 打印配置缓存"""
try:
import certflow.config.settings as settings_mod
settings_mod._config_cache = None
except Exception as e:
logger.debug(f"清除 YAML 缓存失败: {e}")
load_print_config.cache_clear()
logger.info("打印配置热重载完成")
[文档]
def get_paper() -> dict[str, Any]:
"""纸张尺寸 (mm)"""
return load_print_config().get("paper", {"width": 60, "height": 100})
[文档]
def get_layout() -> dict[str, Any]:
"""打印坐标"""
return load_print_config().get(
"layout",
{
"label_x": 3,
"value_x": 20,
"start_y": 22,
"line_spacing": 6.5,
"font_size": 3.0,
},
)
[文档]
def get_font() -> dict[str, Any]:
"""打印字体"""
return load_print_config().get("font", {"name": "SimSun", "weight": 400})
[文档]
def get_print_fields() -> list[dict[str, Any]]:
"""打印字段定义"""
return load_print_config().get(
"print_fields",
[
{"label": "产品名称:", "key": "product_name", "offset": 0},
{"label": "产品型号:", "key": "product_model", "offset": 1},
{"label": "公称通径:", "key": "dn", "offset": 2},
{"label": "公称压力:", "key": "pn", "offset": 3},
{"label": "工作温度:", "key": "temperature", "offset": 4},
{"label": "适用介质:", "key": "medium", "offset": 5},
{"label": "检验标准:", "key": "check_standard", "offset": 6},
{"label": "检验工号:", "key": "inspector_id", "offset": 7},
{"label": "出厂日期:", "key": "manufacture_date", "offset": 8},
{"label": "产品编号:", "key": "serial_number", "offset": 9},
],
)
[文档]
def fields_to_tuples() -> list[tuple[str, str, int]]:
"""转为 (label, key, offset) 元组列表"""
return [(f["label"], f["key"], f["offset"]) for f in get_print_fields()]
[文档]
def get_empty_markers() -> dict[str, Any]:
"""从 config.yaml → print_log_empty_markers 加载空值默认标记"""
try:
return cfg("print_log_empty_markers", {})
except Exception as e:
logger.warning(f"加载空值标记配置失败: {e}")
return {}
# ============================================================
# 子结构
# ============================================================
[文档]
@dataclass
class PaperConfig:
"""纸张尺寸配置(数据类)
定义打印纸张的宽高,单位为毫米(mm).
Attributes:
width_mm: 纸张宽度,单位毫米,默认为 60。
height_mm: 纸张高度,单位毫米,默认为 100。
"""
width_mm: float = 60
height_mm: float = 100
[文档]
@dataclass
class LayoutConfig:
"""打印布局配置(数据类)
定义打印内容的坐标、偏移、行间距与字体等布局参数.
Attributes:
x_offset_mm: 打印机物理 X 偏移修正,单位毫米,默认为 14。
y_offset_mm: 打印机物理 Y 偏移修正,单位毫米,默认为 0。
start_y_mm: 起始 Y 坐标,单位毫米,默认为 22。
line_spacing_mm: 行间距,单位毫米,默认为 6.5。
font_size: 字体大小(磅),默认为 9。
font_name: 字体名称,默认为 "SimSun"。
font_weight: 字体粗细,默认为 400。
"""
x_offset_mm: float = 14 # 打印机物理偏移
y_offset_mm: float = 0
start_y_mm: float = 22
line_spacing_mm: float = 6.5
font_size: int = 9
font_name: str = "SimSun"
font_weight: int = 400
[文档]
@dataclass
class FieldDef:
"""打印字段定义(数据类)
描述单个打印字段的键名、标签与在模板中的坐标和样式.
Attributes:
key: 字段键名,用于程序内引用。
label: 字段显示标签(中文)。
x_mm: 字段 X 坐标,单位毫米,默认为 0。
y_mm: 字段 Y 坐标,单位毫米,默认为 0。
width_mm: 字段宽度,单位毫米,默认为 0。
height_mm: 字段高度,单位毫米,默认为 0。
font_size: 字体大小(磅),默认为 9。
align: 对齐方式,默认为 "left"。
bold: 是否加粗,默认为 False。
"""
key: str
label: str
x_mm: float = 0
y_mm: float = 0
width_mm: float = 0
height_mm: float = 0
font_size: int = 9
align: str = "left"
bold: bool = False
[文档]
@dataclass
class TemplateConfig:
"""打印模板配置(数据类)
聚合单个打印模板的名称、页面尺寸与字段列表.
Attributes:
name: 模板显示名称。
key: 模板键名,用于从配置中检索。
variant: 模板变体标识,默认为空字符串。
background: 背景图片路径,默认为空字符串。
page_width_mm: 页面宽度,单位毫米,默认为 60。
page_height_mm: 页面高度,单位毫米,默认为 100。
fields: 模板字段列表,默认为空列表。
"""
name: str
key: str # 模板键名
variant: str = ""
background: str = ""
page_width_mm: float = 60
page_height_mm: float = 100
fields: list[FieldDef] = field(default_factory=list)
[文档]
def get_field(self, key: str) -> FieldDef | None:
"""按字段键名查找模板字段
Args:
key: 字段键名。
Returns:
FieldDef | None: 匹配到的字段定义;未找到时返回 None。
"""
for f in self.fields:
if f.key == key:
return f
return None
[文档]
@dataclass
class RuntimePrintConfig:
"""打印聚合配置 - 包含一次打印所需的全部参数
注意:与 ``certflow.config.models.PrintConfig``(pydantic 引擎配置模型)同名,
此处运行时聚合类已重命名为 ``RuntimePrintConfig`` 以消除歧义(见 §8/§16.3)。
"""
engine: Literal["escp", "gdi"] = "escp"
printer_name: str = ""
copies: int = 1
paper: PaperConfig = field(default_factory=PaperConfig)
layout: LayoutConfig = field(default_factory=LayoutConfig)
template: TemplateConfig | None = None
# ============================================================
# 工厂方法 - 从主配置一次性加载
# ============================================================
[文档]
@classmethod
def from_template(
cls,
template_key: str = "full_chinese_1",
printer_name: str = "",
copies: int = 1,
) -> RuntimePrintConfig:
"""从模板键名加载完整打印配置
Args:
template_key: 模板键名 (full_chinese_1, bilingual_1, russian...)
printer_name: 打印机名称,为空则用默认
copies: 打印份数
Returns:
RuntimePrintConfig 实例,包含一次打印所需的全部参数
"""
# --- 从主配置通过 cfg() 读取 ---
# canonical 唯一信源为 print.* 段(§16.3 统一后);certificate_print.* 已废弃删除。
engine = cfg("print.engine", "escp")
paper_w = cfg("print.paper.width", 60)
paper_h = cfg("print.paper.height", 100)
x_offset = cfg("print.layout.x_offset", 14)
y_offset = cfg("print.layout.y_offset", 0)
start_y = cfg("print.layout.start_y", 22)
line_spacing = cfg("print.layout.line_spacing", 6.5)
font_size = cfg("print.layout.font_size", 9)
font_name = cfg("print.font.name", "SimSun")
font_weight = cfg("print.font.weight", 400)
default_printer = cfg("print.default_printer", "")
# --- 用户覆盖层 print_layout(§11-3,即时生效)---
# 系统设置写入的打印版式覆盖,优先级高于基础层;空段则忽略。
override = get_override_section("print_layout")
if override:
ov_paper = override.get("paper", {}) or {}
paper_w = ov_paper.get("width", paper_w)
paper_h = ov_paper.get("height", paper_h)
ov_layout = override.get("layout", {}) or {}
x_offset = ov_layout.get("x_offset", x_offset)
y_offset = ov_layout.get("y_offset", y_offset)
start_y = ov_layout.get("start_y", start_y)
line_spacing = ov_layout.get("line_spacing", line_spacing)
font_size = ov_layout.get("font_size", font_size)
ov_font = override.get("font", {}) or {}
font_name = ov_font.get("name", font_name)
font_weight = ov_font.get("weight", font_weight)
default_printer = override.get("default_printer", default_printer)
# 默认份数覆盖(仅当用户未显式指定份数时生效)
ov_copies = override.get("default_copies")
if ov_copies is not None and copies == 1:
copies = int(ov_copies)
paper = PaperConfig(width_mm=float(paper_w), height_mm=float(paper_h))
layout = LayoutConfig(
x_offset_mm=float(x_offset),
y_offset_mm=float(y_offset),
start_y_mm=float(start_y),
line_spacing_mm=float(line_spacing),
font_size=int(font_size),
font_name=str(font_name),
font_weight=int(font_weight),
)
# --- 从 template_coordinates 读取模板坐标 ---
templates_cfg = cfg("print_templates", {})
tmpl_data = templates_cfg.get(template_key) if templates_cfg else None
if not tmpl_data:
raise ValueError(f"模板 '{template_key}' 不存在于 print_templates 配置中")
fields = []
for field_key, pos in tmpl_data.get("fields", {}).items():
fields.append(
FieldDef(
key=field_key,
label="",
# 注意:打印机物理 X 偏移修正由 LQ635KIIPrinter.set_absolute_x_mm
# 统一叠加(见 lq635kii_printer.py),此处不可再加,否则重复累计为 2 倍。
x_mm=float(pos.get("x", 0)),
y_mm=float(pos.get("y", 0)),
width_mm=float(pos.get("width", 0)),
height_mm=float(pos.get("height", 0)),
font_size=int(pos.get("font_size", layout.font_size)),
align=str(pos.get("align", "left")),
)
)
template = TemplateConfig(
name=tmpl_data.get("name", ""),
key=template_key,
variant=tmpl_data.get("variant", ""),
background=tmpl_data.get("background", ""),
page_width_mm=tmpl_data.get("page_width", 60),
page_height_mm=tmpl_data.get("page_height", 100),
fields=fields,
)
return cls(
engine=engine,
printer_name=printer_name or default_printer,
copies=copies,
paper=paper,
layout=layout,
template=template,
)