实现了统一的metadata结构和字体作用域系统,支持文档和模板库之间的单向字体引用。 主要变更: - 模板库必须包含metadata字段(包括size、fonts、fonts_default) - 实现文档和模板库的size一致性校验 - 实现字体作用域系统(文档可引用模板库字体,反之不可) - 实现跨域循环引用检测 - 实现fonts_default级联规则(模板库→文档→系统默认) - 添加错误代码常量(SIZE_MISMATCH、FONT_NOT_FOUND等) - 更新文档和开发者指南 测试覆盖: - 新增33个测试(单元测试20个,集成测试13个) - 所有457个测试通过 Breaking Changes: - 模板库文件必须包含metadata字段 - 模板库metadata.size为必填字段 - 文档和模板库的size必须一致 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
18 KiB
Font Theme
Purpose
字体主题系统提供可复用的字体配置管理能力,允许用户在 metadata 中定义字体配置模板,通过引用方式应用到元素,实现统一的字体样式管理。支持文档和模板库的字体作用域隔离,以及跨域字体引用。
Requirements
Requirement: 系统必须支持在 metadata 中定义 fonts 字段
系统 SHALL 支持在文档和模板库的 YAML metadata 中定义 fonts 字段,用于存储可复用的字体配置。模板库的 fonts 与文档的 fonts 使用相同的定义和验证规则。
Scenario: 定义 fonts 字段(文档)
- WHEN 文档 metadata 中定义 fonts 字段
- THEN 系统成功解析并存储字体配置字典
Scenario: 定义 fonts 字段(模板库)
- WHEN 模板库 metadata 中定义 fonts 字段
- THEN 系统成功解析并存储字体配置字典
Scenario: fonts 字段为空字典
- WHEN metadata 中定义 fonts: {}
- THEN 系统接受空的字体配置字典
Scenario: fonts 字段未定义
- WHEN metadata 中未定义 fonts 字段
- THEN 系统正常处理,fonts 为空字典
Requirement: fonts 字段必须包含命名字体配置
fonts 字段 SHALL 包含一个或多个命名字体配置,每个配置是一个字体属性字典。
Scenario: 定义单个字体配置
- WHEN fonts 中定义 title: {family: "Arial", size: 44, bold: true}
- THEN 系统创建名为 title 的字体配置
Scenario: 定义多个字体配置
- WHEN fonts 中定义 title、subtitle、body 等多个配置
- THEN 系统为每个配置创建独立的字体对象
Requirement: 文档 metadata 必须支持 version 和 author 字段
文档 metadata SHALL 支持可选的 version 和 author 字段。
Scenario: 文档包含 version 和 author
- WHEN 文档 metadata 包含 version: "1.0" 和 author: "作者名"
- THEN 系统成功解析并存储这些字段
Scenario: 文档不包含 version 和 author
- WHEN 文档 metadata 不包含 version 和 author 字段
- THEN 系统正常处理,这些字段为可选项
Requirement: 系统必须支持 fonts_default 字段
系统 SHALL 支持在文档和模板库的 metadata 中定义可选的 fonts_default 字段,指定默认字体配置的引用。文档的 fonts_default 可以引用文档或模板库的 fonts,模板库的 fonts_default 只能引用模板库的 fonts。
Scenario: 定义 fonts_default 引用(文档)
- WHEN 文档 metadata 中定义 fonts_default: "@body"
- AND body 存在于文档 metadata.fonts 中
- THEN 系统将 fonts_default 解析为对 fonts.body 的引用
Scenario: 定义 fonts_default 引用(模板库)
- WHEN 模板库 metadata 中定义 fonts_default: "@base"
- AND base 存在于模板库 metadata.fonts 中
- THEN 系统将 fonts_default 解析为对 fonts.base 的引用
Scenario: 文档 fonts_default 引用模板库字体
- WHEN 文档 metadata.fonts_default: "@template-base"
- AND template-base 存在于模板库 metadata.fonts 中
- THEN 系统成功解析默认字体
Scenario: 模板库 fonts_default 不能引用文档字体
- WHEN 模板库 metadata.fonts_default: "@doc-body"
- AND doc-body 只存在于文档 metadata.fonts 中
- THEN 系统抛出 ERROR,错误代码为
FONT_DEFAULT_INVALID
Scenario: fonts_default 未定义
- WHEN metadata 中未定义 fonts_default 字段
- THEN 系统使用 python-pptx 的默认字体
Scenario: fonts_default 引用不存在的配置
- WHEN fonts_default: "@undefined" 且 undefined 不存在
- THEN 系统抛出 ERROR,错误代码为
FONT_DEFAULT_INVALID
Scenario: fonts_default 必须是引用格式
- WHEN fonts_default: "Arial"(直接字体名称)
- THEN 系统抛出 ERROR,错误代码为
FONT_DEFAULT_INVALID
Requirement: 文档元素和内联模板元素必须支持跨域引用字体
文档中的元素和内联模板(文档中定义的 templates)中的元素 SHALL 优先引用文档的 fonts,文档没有时可引用模板库的 fonts。
Scenario: 元素引用文档中存在的字体
- WHEN 文档元素定义 font: "@title"
- AND title 存在于文档 metadata.fonts 中
- THEN 系统使用文档的 title 配置
Scenario: 元素引用文档不存在但模板库存在的字体
- WHEN 文档元素定义 font: "@template-title"
- AND template-title 不存在于文档 fonts 中
- AND template-title 存在于模板库 metadata.fonts 中
- THEN 系统使用模板库的 template-title 配置
Scenario: 元素引用不存在的字体(文档和模板库都没有)
- WHEN 文档元素定义 font: "@nonexistent"
- AND nonexistent 不存在于文档和模板库的 fonts 中
- THEN 系统抛出 ERROR,错误代码为
FONT_NOT_FOUND - AND 错误消息包含"引用的字体配置不存在"
Scenario: 同名字体时优先使用文档的
- WHEN 文档元素定义 font: "@title"
- AND title 同时存在于文档 fonts 和模板库 fonts 中
- THEN 系统使用文档的 title 配置(不使用模板库的)
Scenario: 内联模板元素引用文档字体
- WHEN 内联模板(文档 templates 中定义)的元素定义 font: "@body"
- AND body 存在于文档 metadata.fonts 中
- THEN 系统使用文档的 body 配置
Scenario: 内联模板元素引用模板库字体
- WHEN 内联模板的元素定义 font: "@template-body"
- AND template-body 只存在于模板库 metadata.fonts 中
- THEN 系统使用模板库的 template-body 配置
Requirement: 元素 font 字段必须支持多种引用方式
元素 font 字段 SHALL 支持字符串引用(整体引用)、字典引用(继承覆盖或独立定义)两种形式。引用的目标根据元素所在作用域决定。
Scenario: 文档元素引用文档字体
- WHEN 文档元素定义 font: "@title"
- AND title 存在于文档 metadata.fonts 中
- THEN 系统使用文档的 title 配置
Scenario: 文档元素引用模板库字体
- WHEN 文档元素定义 font: "@template-title"
- AND template-title 只存在于模板库 metadata.fonts 中
- THEN 系统使用模板库的 template-title 配置
Scenario: 模板元素只能引用模板库字体
- WHEN 外部模板元素定义 font: "@title"
- AND title 只存在于文档 metadata.fonts 中
- THEN 系统抛出 ERROR,错误代码为
TEMPLATE_FONT_REF_DOC_FORBIDDEN
Scenario: 字典继承覆盖
- WHEN 元素定义 font: {parent: "@title", size: 60}
- THEN 系统继承引用字体的所有属性,覆盖 size 为 60
Scenario: 字典独立定义
- WHEN 元素定义 font: {family: "SimSun", size: 24}
- THEN 系统使用定义的属性,未定义的属性继承 fonts_default
Scenario: font 字段未定义
- WHEN 元素未定义 font 字段
- THEN 元素使用 fonts_default 或系统默认字体
Requirement: 文档 fonts 的 parent 必须支持跨域引用
文档 metadata.fonts 中定义的字体配置,其 parent 字段 SHALL 可以引用文档内部的字体配置或模板库的字体配置。
Scenario: parent 引用文档内部的字体
- WHEN 文档 metadata.fonts 中定义 heading: {parent: "@base"}
- AND base 存在于文档 fonts 中
- THEN 系统成功继承 base 的属性
Scenario: parent 引用模板库的字体
- WHEN 文档 metadata.fonts 中定义 custom: {parent: "@template-base"}
- AND template-base 存在于模板库 metadata.fonts 中
- THEN 系统成功继承 template-base 的属性
Scenario: parent 引用不存在的字体(文档和模板库都没有)
- WHEN 文档 metadata.fonts 中定义 heading: {parent: "@nonexistent"}
- AND nonexistent 不存在于文档和模板库的 fonts 中
- THEN 系统抛出 ERROR,错误代码为
FONT_NOT_FOUND - AND 错误消息包含"引用的字体配置不存在"
Scenario: 同名字体时 parent 优先引用文档的
- WHEN 文档 metadata.fonts 中定义 heading: {parent: "@base"}
- AND base 同时存在于文档 fonts 和模板库 fonts 中
- THEN 系统使用文档的 base 配置
Requirement: parent 字段必须引用有效域中的字体配置
font 字典中的 parent 字段 SHALL 引用有效域中的配置。文档 fonts 的 parent 可以引用文档或模板库的 fonts,模板库 fonts 的 parent 只能引用模板库的 fonts。
Scenario: 文档字体 parent 引用文档字体
- WHEN 文档 fonts 中定义 heading: {parent: "@title"}
- AND title 存在于文档 metadata.fonts 中
- THEN 系统成功继承 title 的属性
Scenario: 文档字体 parent 引用模板库字体
- WHEN 文档 fonts 中定义 custom: {parent: "@template-base"}
- AND template-base 存在于模板库 metadata.fonts 中
- THEN 系统成功继承 template-base 的属性
Scenario: 模板库字体 parent 引用模板库字体
- WHEN 模板库 fonts 中定义 heading: {parent: "@base"}
- AND base 存在于模板库 metadata.fonts 中
- THEN 系统成功继承 base 的属性
Scenario: 模板库字体 parent 引用文档字体(禁止)
- WHEN 模板库 fonts 中定义 custom: {parent: "@doc-base"}
- THEN 系统抛出 ERROR,错误代码为
TEMPLATE_PARENT_REF_DOC_FORBIDDEN
Scenario: parent 引用不存在的配置
- WHEN parent: "@undefined" 且 undefined 不存在
- THEN 系统抛出 ERROR,错误代码为
FONT_NOT_FOUND
Scenario: parent 必须是引用格式
- WHEN parent: "Arial"(直接字体名称)
- THEN 系统抛出 ERROR
Requirement: 系统必须检测跨域循环引用
系统 SHALL 在解析字体引用时检测跨域循环引用,包括文档和模板库之间的循环。
Scenario: 文档内部循环引用
- WHEN 文档 fonts.a.parent: "@b" 且 fonts.b.parent: "@a"
- THEN 系统抛出 ERROR,错误代码为
CIRCULAR_REFERENCE - AND 错误消息包含"检测到字体引用循环"和完整路径
Scenario: 模板库内部循环引用
- WHEN 模板库 fonts.x.parent: "@y" 且 fonts.y.parent: "@x"
- THEN 系统抛出 ERROR,错误代码为
CIRCULAR_REFERENCE - AND 错误消息包含"检测到字体引用循环"和完整路径
Scenario: 跨域循环引用
- WHEN 文档 fonts.a.parent: "@template-b"
- AND 模板库 fonts.b.parent: "@template-c"
- AND 模板库 fonts.c.parent: "@doc-a"
- THEN 系统抛出 ERROR,错误代码为
CIRCULAR_REFERENCE - AND 错误消息包含"检测到跨域字体引用循环"和完整路径
Scenario: 跨域引用链不循环
- WHEN 文档 fonts.a.parent: "@template-b"
- AND 模板库 fonts.b.parent: "@template-c"
- AND 模板库 fonts.c 没有引用其他字体
- THEN 系统成功解析,不报错
Requirement: 系统必须检测并拒绝引用循环
系统 SHALL 在解析字体引用时检测循环引用,包括单域内部循环和跨域循环,检测到循环时抛出 ERROR。
Scenario: 直接循环引用
- WHEN fonts.title.parent: "@title"(引用自身)
- THEN 系统抛出 ERROR,错误代码为
CIRCULAR_REFERENCE
Scenario: 间接循环引用(单域)
- WHEN fonts.a.parent: "@b" 且 fonts.b.parent: "@a"
- THEN 系统抛出 ERROR,显示完整的引用循环路径
Scenario: 跨域循环引用
- WHEN 文档 fonts.a 引用模板库 fonts.b
- AND 模板库 fonts.b 引用模板库 fonts.c
- AND 模板库 fonts.c 引用文档 fonts.a
- THEN 系统抛出 ERROR,错误代码为
CIRCULAR_REFERENCE - AND 错误消息包含"检测到跨域字体引用循环"
Scenario: 深层引用但不循环
- WHEN 引用链深度超过 5 层但没有循环
- THEN 系统成功解析
Scenario: 引用链深度超过限制
- WHEN 引用链深度超过 10 层
- THEN 系统抛出 ERROR,提示引用深度超限
Scenario: 错误信息包含引用路径
- WHEN 系统检测到循环引用
- THEN 错误信息包含完整的引用路径
Requirement: 文档和模板库的 fonts_default 必须独立验证
文档的 metadata.fonts_default SHALL 只能引用文档或模板库中存在的字体配置,模板库的 metadata.fonts_default SHALL 只能引用模板库中存在的字体配置。
Scenario: 文档 fonts_default 引用文档字体
- WHEN 文档 metadata.fonts_default: "@body"
- AND body 存在于文档 metadata.fonts 中
- THEN 系统成功解析默认字体
Scenario: 文档 fonts_default 引用模板库字体
- WHEN 文档 metadata.fonts_default: "@template-base"
- AND template-base 存在于模板库 metadata.fonts 中
- THEN 系统成功解析默认字体
Scenario: 文档 fonts_default 引用不存在的字体
- WHEN 文档 metadata.fonts_default: "@nonexistent"
- AND nonexistent 不存在于文档和模板库的 fonts 中
- THEN 系统抛出 ERROR,错误代码为
FONT_DEFAULT_INVALID - AND 错误消息包含"fonts_default 引用的字体配置不存在"
Scenario: 模板库 fonts_default 引用模板库字体
- WHEN 模板库 metadata.fonts_default: "@base"
- AND base 存在于模板库 metadata.fonts 中
- THEN 系统成功解析默认字体
Scenario: 模板库 fonts_default 引用文档字体
- WHEN 模板库 metadata.fonts_default: "@doc-body"
- AND doc-body 只存在于文档 metadata.fonts 中
- THEN 系统抛出 ERROR,错误代码为
FONT_DEFAULT_INVALID - AND 错误消息包含"模板库 fonts_default 只能引用模板库内部的字体配置"
Requirement: 系统必须支持属性继承链
字体属性解析 SHALL 按照优先级顺序继承:parent → 当前定义 → fonts_default → 系统默认。跨域继承时,文档可继承模板库的,模板库不能继承文档的。
Scenario: parent 定义了基础属性
- WHEN fonts.title 定义 size: 44,元素定义 font: {parent: "@title", bold: true}
- THEN 元素使用 size: 44(继承)、bold: true(覆盖)
Scenario: 文档字体 parent 继承模板库字体属性
- WHEN 文档 fonts.custom 定义 parent: "@template-base"
- AND 模板库 fonts.base 定义 family: "Arial", size: 18
- THEN custom 继承 family 和 size
Scenario: 模板库字体 parent 不能继承文档字体属性
- WHEN 模板库 fonts.custom 定义 parent: "@doc-base"
- THEN 系统抛出 ERROR,错误代码为
TEMPLATE_PARENT_REF_DOC_FORBIDDEN
Scenario: parent 未定义的属性继承 fonts_default
- WHEN fonts_default 定义 size: 18,元素定义 font: {parent: "@title"} 且 title 未定义 size
- THEN 元素使用 size: 18(从 fonts_default 继承)
Scenario: 当前定义覆盖 parent
- WHEN parent 定义 size: 44,当前定义 size: 60
- THEN 元素使用 size: 60(当前定义覆盖 parent)
Requirement: 模板元素必须支持继承 fonts_default
模板中未定义 font 的元素 SHALL 继承 fonts_default 配置。对于外部模板,级联顺序为:文档 fonts_default → 模板库 fonts_default → 系统默认。
Scenario: 外部模板元素未定义 font,文档有 fonts_default
- WHEN 外部模板元素未定义 font
- AND 文档定义了 metadata.fonts_default: "@body"
- THEN 元素使用文档的 fonts_default 配置
Scenario: 外部模板元素未定义 font,文档没有 fonts_default
- WHEN 外部模板元素未定义 font
- AND 文档未定义 fonts_default
- AND 模板库定义了 metadata.fonts_default: "@base"
- THEN 元素使用模板库的 fonts_default 配置
Scenario: 内联模板元素未定义 font
- WHEN 内联模板(文档中定义)元素未定义 font
- THEN 元素继承文档的 fonts_default 配置
Scenario: 模板元素定义了 font
- WHEN 模板元素定义 font: "@title"
- THEN 元素使用 font: "@title",不继承 fonts_default
Scenario: fonts_default 未定义时模板元素行为
- WHEN 模板元素未定义 font
- AND 文档和模板库都未定义 fonts_default
- THEN 元素使用系统默认字体
Requirement: 表格元素必须支持 font 和 header_font 字段
表格元素 SHALL 支持 font 和 header_font 字段,分别控制数据单元格和表头的字体样式。
Scenario: 表格定义整体字体
- WHEN 表格定义 font: {family: "Arial", size: 14}
- THEN 数据单元格和表头都应用该字体(表头可被 header_font 覆盖)
Scenario: 表格定义表头字体
- WHEN 表格定义 header_font: {bold: true, color: "#ffffff"}
- THEN 表头应用该字体,数据单元格继承 font 或 fonts_default
Scenario: 表头字体继承表格字体
- WHEN 表格定义 font: {size: 14} 且 header_font: {parent: "@font", bold: true}
- THEN 表头继承 size: 14,覆盖 bold: true
Scenario: 表格仅定义 header_font
- WHEN 表格仅定义 header_font: {bold: true}
- THEN 表头应用 bold: true,数据单元格继承 fonts_default
Requirement: 系统必须移除旧的表格字体语法
系统 SHALL 移除 style.font_size 和 style.header_color 字段的处理逻辑。
Scenario: 旧语法字段不再生效
- WHEN 表格定义 style: {font_size: 14, header_color: "#fff"}
- THEN 系统忽略这些字段,使用 font 和 header_font 替代
Scenario: style 字段保留用于非字体属性
- WHEN 表格定义 style: {header_bg: "#4a90e2"}
- THEN 系统正常处理背景色等非字体属性