1
0
Files
PPTX/openspec/changes/archive/2026-03-05-template-metadata-and-font-ref/specs/font-theme/spec.md
lanyuanxiaoyao 98098dc911 feat: 实现模板库metadata和跨域字体引用系统
实现了统一的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>
2026-03-05 18:12:05 +08:00

399 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Font Theme Delta Spec
## ADDED Requirements
### 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: 文档元素和内联模板元素必须支持跨域引用字体
文档中的元素和内联模板(文档中定义的 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: 文档 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: 系统必须检测跨域循环引用
系统 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: 文档和模板库的 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 只能引用模板库内部的字体配置"
## MODIFIED 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_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: 元素 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: 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 在解析字体引用时检测循环引用,包括单域内部循环和跨域循环,检测到循环时抛出 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: 系统必须支持属性继承链
字体属性解析 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** 元素使用系统默认字体