certflow.config.print_config 源代码

# 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, )