""" 字体解析器模块 提供字体引用解析、继承链处理和预设类别映射功能。 """ from typing import Optional, Dict, Set, Union from core.elements import FontConfig # 预设字体类别映射(跨平台推荐) PRESET_FONT_MAPPING = { "sans": "Arial", # 西文无衬线,跨平台通用 "serif": "Times New Roman", # 西文衬线,跨平台通用 "mono": "Courier New", # 等宽字体,跨平台通用 "cjk-sans": "Microsoft YaHei", # 中文无衬线(Windows 推荐) "cjk-serif": "SimSun", # 中文衬线(Windows 推荐) } class FontResolver: """字体解析器,处理字体引用、继承和预设类别映射""" def __init__(self, fonts: Optional[Dict] = None, fonts_default: Optional[str] = None): """ 初始化字体解析器 Args: fonts: 字体配置字典,键为字体名称,值为字体配置 fonts_default: 默认字体引用(格式:@xxx) """ self.fonts = fonts or {} self.fonts_default = fonts_default self._max_depth = 10 # 最大引用深度,防止循环引用 def resolve_font(self, font_config: Union[FontConfig, str, dict, None]) -> FontConfig: """ 解析字体配置 Args: font_config: 字体配置(FontConfig对象、字符串引用或字典) Returns: 解析后的 FontConfig 对象 Raises: ValueError: 引用不存在或循环引用 """ # 如果为 None,使用 fonts_default if font_config is None: if self.fonts_default: return self._resolve_reference(self.fonts_default, set()) return FontConfig() # 如果已经是 FontConfig 对象,直接返回 if isinstance(font_config, FontConfig): return font_config # 如果是字符串,处理整体引用 if isinstance(font_config, str): if font_config.startswith("@"): return self._resolve_reference(font_config, set()) raise ValueError(f"字体引用必须以 @ 开头: {font_config}") # 如果是字典,处理继承覆盖或独立定义 if isinstance(font_config, dict): return self._resolve_font_dict(font_config, set()) raise ValueError(f"不支持的字体配置类型: {type(font_config)}") def _resolve_reference(self, reference: str, visited: Set[str]) -> FontConfig: """ 解析字体引用 Args: reference: 引用字符串(格式:@xxx) visited: 已访问的引用集合,用于检测循环引用 Returns: 解析后的 FontConfig 对象 Raises: ValueError: 引用不存在或循环引用 """ if not reference.startswith("@"): raise ValueError(f"字体引用必须以 @ 开头: {reference}") font_name = reference[1:] # 移除 @ 前缀 # 检测循环引用 if reference in visited: path = " -> ".join(visited) + f" -> {reference}" raise ValueError(f"检测到字体引用循环: {path}") # 检查引用深度 if len(visited) >= self._max_depth: raise ValueError(f"字体引用深度超过限制 ({self._max_depth} 层)") # 检查引用是否存在 if font_name not in self.fonts: raise ValueError(f"引用的字体配置不存在: {reference}") # 添加到已访问集合 visited.add(reference) # 获取引用的字体配置 font_dict = self.fonts[font_name] # 递归解析 return self._resolve_font_dict(font_dict, visited.copy()) def _resolve_font_dict(self, font_dict: dict, visited: Set[str]) -> FontConfig: """ 解析字体字典 Args: font_dict: 字体配置字典 visited: 已访问的引用集合 Returns: 解析后的 FontConfig 对象 """ # 处理 parent 继承 parent_config = FontConfig() if "parent" in font_dict and font_dict["parent"]: parent_config = self._resolve_reference(font_dict["parent"], visited) # 创建当前配置(从 parent 继承) config = FontConfig( parent=None, # parent 已经解析,不需要保留 family=font_dict.get("family", parent_config.family), size=font_dict.get("size", parent_config.size), bold=font_dict.get("bold", parent_config.bold), italic=font_dict.get("italic", parent_config.italic), underline=font_dict.get("underline", parent_config.underline), strikethrough=font_dict.get("strikethrough", parent_config.strikethrough), color=font_dict.get("color", parent_config.color), align=font_dict.get("align", parent_config.align), line_spacing=font_dict.get("line_spacing", parent_config.line_spacing), space_before=font_dict.get("space_before", parent_config.space_before), space_after=font_dict.get("space_after", parent_config.space_after), baseline=font_dict.get("baseline", parent_config.baseline), caps=font_dict.get("caps", parent_config.caps), ) # 解析 family 字段中的预设类别 if config.family and config.family in PRESET_FONT_MAPPING: config.family = PRESET_FONT_MAPPING[config.family] # 如果当前配置的属性仍为 None,从 fonts_default 继承 if self.fonts_default: default_config = self._get_default_config(visited) config = self._merge_with_default(config, default_config) return config def _get_default_config(self, visited: Set[str]) -> FontConfig: """ 获取默认字体配置 Args: visited: 已访问的引用集合 Returns: 默认字体配置 """ if not self.fonts_default: return FontConfig() # 避免在获取默认配置时再次访问 fonts_default if self.fonts_default in visited: return FontConfig() return self._resolve_reference(self.fonts_default, visited.copy()) def _merge_with_default(self, config: FontConfig, default: FontConfig) -> FontConfig: """ 将配置与默认配置合并 Args: config: 当前配置 default: 默认配置 Returns: 合并后的配置 """ return FontConfig( parent=None, family=config.family if config.family is not None else default.family, size=config.size if config.size is not None else default.size, bold=config.bold if config.bold is not None else default.bold, italic=config.italic if config.italic is not None else default.italic, underline=config.underline if config.underline is not None else default.underline, strikethrough=config.strikethrough if config.strikethrough is not None else default.strikethrough, color=config.color if config.color is not None else default.color, align=config.align if config.align is not None else default.align, line_spacing=config.line_spacing if config.line_spacing is not None else default.line_spacing, space_before=config.space_before if config.space_before is not None else default.space_before, space_after=config.space_after if config.space_after is not None else default.space_after, baseline=config.baseline if config.baseline is not None else default.baseline, caps=config.caps if config.caps is not None else default.caps, )