1
0

feat: 实现字体主题系统和东亚字体支持

实现完整的字体主题系统,支持可复用字体配置、预设类别和扩展属性。
同时修复中文字体渲染问题,确保 Source Han Sans 等东亚字体正确显示。

核心功能:
- 字体主题配置:metadata.fonts 和 fonts_default
- 三种引用方式:整体引用、继承覆盖、独立定义
- 预设字体类别:sans、serif、mono、cjk-sans、cjk-serif
- 扩展字体属性:family、underline、strikethrough、line_spacing、
  space_before、space_after、baseline、caps
- 表格字体字段:font 和 header_font 替代旧的 style.font_size
- 引用循环检测和属性继承链
- 模板字体继承支持

东亚字体修复:
- 添加 _set_font_with_eastasian() 方法
- 同时设置拉丁字体、东亚字体和复杂脚本字体
- 修复中文字符使用默认字体的问题

测试:
- 58 个单元测试覆盖所有字体系统功能
- 3 个集成测试验证端到端场景
- 移除旧语法相关测试

文档:
- 更新 README.md 添加字体主题系统使用说明
- 更新 README_DEV.md 添加技术文档
- 创建 4 个示例 YAML 文件
- 同步 delta specs 到主 specs

归档:
- 归档 font-theme-system 变更到 openspec/changes/archive/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 10:38:59 +08:00
parent 7ef29ea039
commit bd12fce14b
22 changed files with 3142 additions and 103 deletions

View File

@@ -0,0 +1,154 @@
"""
字体系统集成测试
本测试文件包含字体主题系统的集成测试,验证完整的工作流程:
1. 多行文本扩展属性应用
- 验证扩展属性line_spacing、space_before、space_after应用到所有段落
- 测试从 YAML 加载到渲染的完整流程
2. 模板字体继承
- 验证模板元素继承 fonts_default
- 测试内联模板的字体配置
3. 引用循环错误
- 验证循环引用在渲染时被检测并抛出错误
- 测试错误信息的准确性
测试策略:
- 使用临时 YAML 文件模拟真实场景
- 测试完整的加载 → 解析 → 渲染流程
- 验证错误处理和边界情况
注意:
- 这些测试验证字体系统与其他模块的集成
- 单元测试在 test_font_system.py 中
- 渲染器测试在 test_pptx_renderer.py 中
"""
import pytest
from pathlib import Path
from core.presentation import Presentation
from renderers.pptx_renderer import PptxGenerator
class TestMultilineTextExtendedProperties:
"""多行文本扩展属性应用集成测试"""
def test_multiline_text_with_extended_properties(self, tmp_path):
"""测试多行文本应用扩展属性到所有段落"""
# 创建测试 YAML 文件
yaml_content = """
metadata:
size: "16:9"
fonts:
body:
family: "Arial"
size: 18
line_spacing: 1.5
space_before: 12
space_after: 6
slides:
- elements:
- type: text
content: "第一行\\n第二行\\n第三行"
box: [1, 1, 8, 2]
font: "@body"
"""
yaml_file = tmp_path / "test.yaml"
yaml_file.write_text(yaml_content)
# 加载并渲染
pres = Presentation(yaml_file)
gen = PptxGenerator(pres.size, fonts=pres.fonts, fonts_default=pres.fonts_default)
slide_data = pres.data.get('slides', [])[0]
rendered = pres.render_slide(slide_data)
# 验证元素被正确解析
assert len(rendered['elements']) == 1
elem = rendered['elements'][0]
# YAML 会将 \n 解析为实际的换行符
assert elem.content == "第一行\n第二行\n第三行"
class TestTemplateFontInheritance:
"""模板字体继承集成测试"""
def test_template_inherits_fonts_default(self, tmp_path):
"""测试模板元素继承 fonts_default"""
# 创建测试 YAML 文件
yaml_content = """
metadata:
size: "16:9"
fonts:
body:
family: "Arial"
size: 18
color: "#333333"
fonts_default: "@body"
templates:
simple:
elements:
- type: text
content: "模板文本"
box: [1, 1, 8, 1]
slides:
- template: simple
"""
yaml_file = tmp_path / "test.yaml"
yaml_file.write_text(yaml_content)
# 加载并渲染
pres = Presentation(yaml_file)
gen = PptxGenerator(pres.size, fonts=pres.fonts, fonts_default=pres.fonts_default)
slide_data = pres.data.get('slides', [])[0]
rendered = pres.render_slide(slide_data)
# 验证模板元素被渲染
assert len(rendered['elements']) == 1
class TestCircularReferenceError:
"""引用循环错误集成测试"""
def test_circular_reference_in_yaml_raises_error(self, tmp_path):
"""测试 YAML 中的循环引用抛出错误"""
# 创建包含循环引用的 YAML 文件
yaml_content = """
metadata:
size: "16:9"
fonts:
a:
parent: "@b"
size: 44
b:
parent: "@a"
size: 18
slides:
- elements:
- type: text
content: "测试"
box: [1, 1, 8, 1]
font: "@a"
"""
yaml_file = tmp_path / "test.yaml"
yaml_file.write_text(yaml_content)
# 加载演示文稿
pres = Presentation(yaml_file)
gen = PptxGenerator(pres.size, fonts=pres.fonts, fonts_default=pres.fonts_default)
slide_data = pres.data.get('slides', [])[0]
# 渲染时应该抛出循环引用错误
with pytest.raises(ValueError, match="检测到字体引用循环"):
rendered = pres.render_slide(slide_data)
# 触发字体解析
for elem in rendered['elements']:
gen.font_resolver.resolve_font(elem.font)