# 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** 系统正常处理背景色等非字体属性