1
0
Files
PPTX/openspec/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

18 KiB
Raw Permalink Blame History

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 支持可选的 versionauthor 字段。

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