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>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-05
|
||||
@@ -0,0 +1,400 @@
|
||||
# Design: 模板库元数据与跨域字体引用
|
||||
|
||||
## Context
|
||||
|
||||
### 当前状态
|
||||
|
||||
当前系统中:
|
||||
- 文档支持 metadata 结构,包含 size、description、fonts、fonts_default 字段
|
||||
- 模板库仅支持 templates 字段和可选的简单元数据(description、version、author)
|
||||
- 字体引用系统(FontResolver)只处理单一作用域的字体配置
|
||||
- 模板中的元素无法引用字体配置,只能直接定义字体属性
|
||||
|
||||
### 约束条件
|
||||
|
||||
- 模板库必须包含 metadata 字段且 size 必填(不向后兼容)
|
||||
- 模板库和文档的 metadata 使用相同的验证规则
|
||||
- 字体引用规则是单向的:文档可引用模板库,模板库不能引用文档
|
||||
- 变量解析逻辑保持现状,不扩展到 font 字段
|
||||
|
||||
### 涉及模块
|
||||
|
||||
- `loaders/yaml_loader.py` - YAML 加载和验证
|
||||
- `validators/validator.py` - 主验证器
|
||||
- `core/presentation.py` - 演示文稿管理
|
||||
- `core/template.py` - 模板渲染
|
||||
- `utils/font_resolver.py` - 字体解析器
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 统一文档和模板库的 metadata 结构,支持相同的字段定义
|
||||
- 模板库必须包含 metadata 且 size 必填(破坏性变更)
|
||||
- 建立清晰的跨域字体引用规则,支持文档引用模板库的字体配置
|
||||
- 实现 size 一致性校验,确保文档和模板库尺寸匹配
|
||||
- 扩展 FontResolver 支持跨域引用和循环检测
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不支持模板库引用文档的字体配置(严格单向)
|
||||
- 不修改现有的变量解析逻辑
|
||||
- 不引入新的作用域前缀语法(如 @template:xxx)
|
||||
- 不改变 font 字段的变量替换行为(font 字段不被变量替换)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: 统一的 Metadata 验证函数
|
||||
|
||||
**决策**: 创建统一的 metadata 验证函数,同时用于文档和模板库。
|
||||
|
||||
**理由**:
|
||||
- 减少代码重复
|
||||
- 确保文档和模板库的 metadata 使用完全相同的验证规则
|
||||
- 未来扩展 metadata 字段时只需修改一处
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# loaders/yaml_loader.py
|
||||
def validate_metadata(metadata, file_path, context="文档"):
|
||||
"""验证 metadata 结构
|
||||
|
||||
Args:
|
||||
metadata: metadata 字典
|
||||
file_path: 文件路径(用于错误消息)
|
||||
context: 上下文描述("文档" 或 "模板库")
|
||||
"""
|
||||
if not isinstance(metadata, dict):
|
||||
raise YAMLError(f"{file_path}: metadata 必须是字典对象")
|
||||
|
||||
# size 必填
|
||||
if "size" not in metadata:
|
||||
raise YAMLError(f"{file_path}: metadata 缺少必填字段 'size'")
|
||||
|
||||
size = metadata["size"]
|
||||
if size not in ["16:9", "4:3"]:
|
||||
raise YAMLError(f"{file_path}: metadata.size 必须是 '16:9' 或 '4:3',当前值: {size}")
|
||||
|
||||
# 其他字段可选
|
||||
# version, author, description: 不验证格式
|
||||
# fonts, fonts_default: 使用字体主题系统的验证逻辑
|
||||
```
|
||||
|
||||
### Decision 2: FontResolver 作用域感知
|
||||
|
||||
**决策**: 扩展 FontResolver 支持作用域参数,区分文档作用域和模板库作用域。
|
||||
|
||||
**理由**:
|
||||
- 保持现有 FontResolver API 的简洁性
|
||||
- 通过作用域参数控制引用行为
|
||||
- 支持跨域引用检测
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# utils/font_resolver.py
|
||||
class FontResolver:
|
||||
def __init__(self, fonts=None, fonts_default=None, scope="document", template_fonts=None):
|
||||
self.fonts = fonts or {}
|
||||
self.fonts_default = fonts_default
|
||||
self.scope = scope # "document" 或 "template"
|
||||
self.template_fonts = template_fonts or {}
|
||||
self._max_depth = 10
|
||||
|
||||
def _resolve_reference(self, reference, visited, allow_cross_domain=False):
|
||||
# 解析引用时考虑作用域规则
|
||||
if self.scope == "template" and not allow_cross_domain:
|
||||
# 模板库作用域:只能引用 template_fonts
|
||||
font_dict = self.template_fonts.get(font_name)
|
||||
if font_dict is None:
|
||||
raise ValueError(
|
||||
f"模板元素不能引用文档的字体配置: {reference},"
|
||||
f"只能引用模板库中定义的字体"
|
||||
)
|
||||
else:
|
||||
# 文档作用域或允许跨域:优先 fonts,fallback template_fonts
|
||||
if font_name in self.fonts:
|
||||
font_dict = self.fonts[font_name]
|
||||
elif font_name in self.template_fonts:
|
||||
font_dict = self.template_fonts[font_name]
|
||||
else:
|
||||
raise ValueError(f"引用的字体配置不存在: {reference}")
|
||||
```
|
||||
|
||||
### Decision 3: 跨域循环引用检测
|
||||
|
||||
**决策**: 在循环引用检测中增加作用域追踪,检测跨域循环。
|
||||
|
||||
**理由**:
|
||||
- 防止文档和模板库之间形成循环引用
|
||||
- 提供清晰的错误消息,帮助用户定位问题
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
def _resolve_reference(self, reference, visited, allow_cross_domain=False):
|
||||
# 检测循环时,记录引用的作用域
|
||||
scope_tag = "doc" if self.scope == "document" else "template"
|
||||
tagged_ref = f"{scope_tag}.{reference}"
|
||||
|
||||
if tagged_ref in visited:
|
||||
path = " -> ".join(visited) + f" -> {tagged_ref}"
|
||||
if any("doc." in v and "template." in v for v in visited):
|
||||
raise ValueError(f"检测到跨域字体引用循环: {path}")
|
||||
else:
|
||||
raise ValueError(f"检测到字体引用循环: {path}")
|
||||
|
||||
visited.add(tagged_ref)
|
||||
# ... 继续解析
|
||||
```
|
||||
|
||||
### Decision 4: Size 一致性校验时机
|
||||
|
||||
**决策**: 在 Presentation 初始化时,加载模板库后立即进行 size 一致性校验。
|
||||
|
||||
**理由**:
|
||||
- 早期失败,避免后续处理才发现问题
|
||||
- 错误消息更清晰,用户可以立即修正
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# core/presentation.py
|
||||
def __init__(self, pres_file, template_file=None):
|
||||
# ... 加载文档
|
||||
|
||||
# 加载并验证模板库文件(如果提供)
|
||||
if self.template_file:
|
||||
self.template_library = load_yaml_file(self.template_file)
|
||||
validate_template_library_yaml(self.template_library, str(self.template_file))
|
||||
|
||||
# 验证模板库 metadata(必须存在)
|
||||
if "metadata" not in self.template_library:
|
||||
raise YAMLError(
|
||||
f"{self.template_file}: 模板库必须包含 metadata 字段"
|
||||
)
|
||||
|
||||
validate_metadata(
|
||||
self.template_library["metadata"],
|
||||
str(self.template_file),
|
||||
context="模板库"
|
||||
)
|
||||
|
||||
# size 一致性校验
|
||||
doc_size = self.data.get("metadata", {}).get("size")
|
||||
template_size = self.template_library["metadata"].get("size")
|
||||
if doc_size != template_size:
|
||||
raise YAMLError(
|
||||
f"文档尺寸 '{doc_size}' 与模板库尺寸 '{template_size}' 不一致"
|
||||
)
|
||||
```
|
||||
|
||||
### Decision 5: fonts_default 级联处理
|
||||
|
||||
**决策**: 在模板渲染时,如果元素未定义 font,级联查找 fonts_default:先文档后模板库。
|
||||
|
||||
**理由**:
|
||||
- 文档优先级更高,允许文档覆盖模板库的默认值
|
||||
- 保持灵活性,模板库提供基础配置
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# core/template.py 或渲染器中
|
||||
def resolve_element_font(self, font_config, doc_fonts_default, template_fonts_default):
|
||||
if font_config is None:
|
||||
# 级联:文档 fonts_default → 模板库 fonts_default → 系统默认
|
||||
if doc_fonts_default:
|
||||
return self.font_resolver_doc.resolve_font(doc_fonts_default)
|
||||
elif template_fonts_default:
|
||||
return self.font_resolver_template.resolve_font(template_fonts_default)
|
||||
else:
|
||||
return FontConfig()
|
||||
else:
|
||||
return self.font_resolver.resolve_font(font_config)
|
||||
```
|
||||
|
||||
### Decision 6: 错误代码标准化
|
||||
|
||||
**决策**: 定义统一的错误代码和错误消息模板。
|
||||
|
||||
**理由**:
|
||||
- 便于测试和文档维护
|
||||
- 用户提供一致的错误体验
|
||||
|
||||
**错误代码**:
|
||||
```
|
||||
SIZE_MISMATCH - 文档和模板库尺寸不一致
|
||||
TEMPLATE_FONT_REF_DOC_FORBIDDEN - 模板元素引用文档字体
|
||||
TEMPLATE_PARENT_REF_DOC_FORBIDDEN - 模板库 parent 引用文档字体
|
||||
FONT_NOT_FOUND - 字体配置不存在
|
||||
CIRCULAR_REFERENCE - 循环引用(包括跨域)
|
||||
FONT_DEFAULT_INVALID - fonts_default 引用无效
|
||||
TEMPLATE_LIBRARY_MISSING_METADATA - 模板库缺少 metadata 字段
|
||||
TEMPLATE_LIBRARY_METADATA_MISSING_SIZE - 模板库 metadata 缺少 size
|
||||
TEMPLATE_LIBRARY_METADATA_INVALID_SIZE - 模板库 metadata.size 无效
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### Risk 1: 旧测试和代码清理
|
||||
|
||||
**风险**: 破坏性变更可能遗留旧的测试代码和逻辑,导致测试失败或行为不一致。
|
||||
|
||||
**缓解措施**:
|
||||
- 系统性识别所有假设模板库没有 metadata 的测试
|
||||
- 移除向后兼容性相关的代码路径
|
||||
- 确保所有错误代码和错误消息一致
|
||||
|
||||
### Risk 2: 跨域引用的复杂性
|
||||
|
||||
**风险**: 跨域引用规则增加理解和使用复杂度。
|
||||
|
||||
**缓解措施**:
|
||||
- 提供清晰的错误消息,明确指出违反了哪条规则
|
||||
- 在文档中详细说明引用规则
|
||||
- 添加验证测试覆盖所有场景
|
||||
|
||||
### Risk 3: 性能影响
|
||||
|
||||
**风险**: 跨域引用和循环检测可能增加解析时间。
|
||||
|
||||
**缓解措施**:
|
||||
- 最大深度限制(10 层)防止无限循环
|
||||
- 早期失败机制避免无效配置的深度解析
|
||||
- 缓存已解析的字体配置(如果需要)
|
||||
|
||||
### Risk 4: 测试覆盖不足
|
||||
|
||||
**风险**: 跨域场景复杂,可能遗漏边界情况。
|
||||
|
||||
**缓解措施**:
|
||||
- 编写全面的单元测试和集成测试
|
||||
- 使用属性测试(property-based testing)覆盖边界情况
|
||||
- 添加循环引用的压力测试
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 阶段 1: 清理旧测试
|
||||
1. 识别并移除与旧模板库逻辑相关的测试
|
||||
2. 移除假设模板库没有 metadata 的测试场景
|
||||
|
||||
### 阶段 2: 实现核心功能
|
||||
1. 添加统一 metadata 验证函数
|
||||
2. 扩展 FontResolver 支持作用域
|
||||
3. 实现跨域引用和循环检测
|
||||
4. 添加 size 一致性校验
|
||||
|
||||
### 阶段 3: 添加新测试
|
||||
1. 添加 metadata 验证相关测试
|
||||
2. 添加跨域字体引用测试
|
||||
3. 添加 size 一致性校验测试
|
||||
4. 添加 fonts_default 级联测试
|
||||
|
||||
### 阶段 4: 更新文档
|
||||
1. 更新 README.md 和 README_DEV.md
|
||||
2. 添加示例配置文件
|
||||
3. 同步更新相关规范文档
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Q1: 是否需要支持模板库的 metadata 继承?
|
||||
|
||||
**问题**: 如果文档和模板库都有 metadata,是否允许模板库的某些字段被文档覆盖?
|
||||
|
||||
**当前决策**: 不支持继承,size 必须严格一致,其他字段各自独立。
|
||||
|
||||
### Q2: fonts_default 的级联是否应该在验证阶段检测?
|
||||
|
||||
**问题**: 是否需要在验证阶段检查 fonts_default 引用的有效性?
|
||||
|
||||
**当前决策**: 在渲染时动态解析,验证阶段只检查格式。
|
||||
|
||||
### Q3: 是否需要提供诊断命令?
|
||||
|
||||
**问题**: 是否需要提供一个命令来显示字体引用链,帮助用户调试?
|
||||
|
||||
**当前决策**: 暂不实现,通过错误消息提供足够信息。
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### 代码组织
|
||||
|
||||
```
|
||||
loaders/yaml_loader.py
|
||||
- validate_metadata() # 新增:统一 metadata 验证
|
||||
- validate_template_library_yaml() # 修改:调用 validate_metadata
|
||||
|
||||
validators/validator.py
|
||||
- Validator # 修改:添加 size 一致性校验
|
||||
|
||||
core/presentation.py
|
||||
- Presentation.__init__() # 修改:解析模板库 metadata
|
||||
- Presentation.get_template() # 修改:传递字体配置
|
||||
|
||||
core/template.py
|
||||
- Template.from_data() # 修改:接收作用域参数
|
||||
- Template.render() # 修改:应用正确的字体解析器
|
||||
|
||||
utils/font_resolver.py
|
||||
- FontResolver.__init__() # 修改:支持作用域和跨域
|
||||
- FontResolver._resolve_reference() # 修改:跨域引用检测
|
||||
- FontResolver._check_circular() # 新增:跨域循环检测
|
||||
```
|
||||
|
||||
### 测试策略
|
||||
|
||||
#### 清理旧测试
|
||||
|
||||
```
|
||||
tests/unit/test_template.py
|
||||
- 移除假设模板库没有 metadata 的测试场景
|
||||
- 移除向后兼容性相关的测试
|
||||
|
||||
tests/integration/test_template_library.py
|
||||
- 移除假设模板库可选 metadata 的测试
|
||||
- 重新设计基于新 metadata 结构的测试
|
||||
```
|
||||
|
||||
#### 添加新测试
|
||||
|
||||
```
|
||||
tests/unit/test_yaml_loader.py
|
||||
- 添加 validate_metadata() 测试
|
||||
- 添加模板库 metadata 必须存在的测试
|
||||
|
||||
tests/unit/test_font_resolver.py
|
||||
- 添加跨域引用测试
|
||||
- 添加跨域循环检测测试
|
||||
- 添加作用域限制测试
|
||||
|
||||
tests/integration/test_font_cross_domain.py # 新增
|
||||
- 测试文档元素引用模板库字体
|
||||
- 测试模板元素只能引用模板库字体
|
||||
- 测试 parent 跨域引用规则
|
||||
- 测试 fonts_default 级联
|
||||
|
||||
tests/integration/test_size_validation.py # 新增
|
||||
- 测试 size 一致性校验
|
||||
- 测试模板库缺少 metadata
|
||||
- 测试模板库缺少 size
|
||||
- 测试模板库 size 无效
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
文档加载
|
||||
↓
|
||||
验证文档 metadata
|
||||
↓
|
||||
加载模板库(如果有)
|
||||
↓
|
||||
验证模板库 metadata
|
||||
↓
|
||||
size 一致性校验
|
||||
↓
|
||||
创建 FontResolver(文档作用域)
|
||||
创建 FontResolver(模板库作用域)
|
||||
↓
|
||||
渲染幻灯片
|
||||
- 文档元素:使用文档 FontResolver(可引用模板库)
|
||||
- 内联模板元素:使用文档 FontResolver
|
||||
- 外部模板元素:使用模板库 FontResolver(不能引用文档)
|
||||
```
|
||||
@@ -0,0 +1,73 @@
|
||||
# Proposal: 模板库元数据与跨域字体引用
|
||||
|
||||
## Why
|
||||
|
||||
当前系统中,文档可以定义 metadata.fonts 来管理字体主题,但模板库缺乏相应的元数据结构,导致:
|
||||
1. 模板库无法定义自己的字体配置,限制了模板的独立性和复用性
|
||||
2. 模板中的元素无法引用统一的字体配置
|
||||
3. 文档和模板库之间缺乏明确的字体引用关系规范
|
||||
|
||||
本变更旨在统一文档和模板库的元数据结构,建立清晰的跨域字体引用规则。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **模板库增加 metadata 结构**
|
||||
- 模板库必须包含 metadata 字段,与文档使用相同的结构
|
||||
- metadata.size 必填,必须是 "16:9" 或 "4:3"
|
||||
- 其他字段(version、author、description、fonts、fonts_default)可选
|
||||
- 模板库的 fonts 定义模板级别的字体配置
|
||||
|
||||
- **文档 metadata 扩展**
|
||||
- 新增 version 字段(可选)
|
||||
- 新增 author 字段(可选)
|
||||
|
||||
- **跨域字体引用规则**
|
||||
- 文档元素和内联模板元素:优先引用文档 fonts,文档没有时可引用模板库 fonts
|
||||
- 外部模板元素:只能引用模板库 fonts,不能引用文档 fonts
|
||||
- 文档 fonts 的 parent:可引用文档内部或模板库 fonts
|
||||
- 模板库 fonts 的 parent:只能引用模板库内部 fonts
|
||||
|
||||
- **fonts_default 级联规则**
|
||||
- 模板元素未定义 font 时:优先使用文档 fonts_default,文档没有则使用模板库 fonts_default
|
||||
|
||||
- **size 一致性校验**
|
||||
- 文档的 size 必须与模板库的 size 一致
|
||||
- 不一致时抛出 ERROR 级别错误
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `template-library`: 扩展模板库能力,支持 metadata 结构和字体配置
|
||||
- `font-theme`: 扩展字体主题能力,支持跨域引用(文档引用模板库)
|
||||
|
||||
## Impact
|
||||
|
||||
### 受影响的代码模块
|
||||
|
||||
- `loaders/yaml_loader.py`: 添加模板库 metadata 验证逻辑
|
||||
- `validators/validator.py`: 添加 size 一致性校验
|
||||
- `core/presentation.py`: 解析和存储模板库 metadata.fonts
|
||||
- `core/template.py`: 模板渲染时应用字体引用规则
|
||||
- `utils/font_resolver.py`: 扩展字体解析器,支持跨域引用
|
||||
|
||||
### API 变更
|
||||
|
||||
- `validate_template_library_yaml()` 增加 metadata 验证
|
||||
- `Template` 类增加字体解析上下文(是文档作用域还是模板库作用域)
|
||||
- `FontResolver` 增加跨域引用检测逻辑
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
- 模板库必须包含 metadata 字段且 size 必填
|
||||
- 需要清理旧的模板库相关测试,重新设计新测试
|
||||
|
||||
### 测试影响
|
||||
|
||||
- 清理旧的模板库验证测试
|
||||
- 重新设计模板库 metadata 相关测试
|
||||
- 新增跨域字体引用测试
|
||||
- 新增 size 一致性校验测试
|
||||
- 新增 fonts_default 级联测试
|
||||
@@ -0,0 +1,398 @@
|
||||
# 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** 元素使用系统默认字体
|
||||
@@ -0,0 +1,173 @@
|
||||
# Template Library Delta Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 模板库必须包含 metadata 结构
|
||||
|
||||
模板库文件 SHALL 必须包含 metadata 字段,与文档使用相同的结构,包含 `size`(必填)、`version`、`author`、`description`、`fonts`、`fonts_default`。
|
||||
|
||||
#### Scenario: 模板库包含完整的 metadata
|
||||
|
||||
- **WHEN** 模板库文件包含 metadata 字段,包含 size、version、author、description、fonts、fonts_default
|
||||
- **THEN** 系统成功解析并存储这些元数据
|
||||
|
||||
#### Scenario: 模板库缺少 metadata 字段
|
||||
|
||||
- **WHEN** 模板库文件不包含 metadata 字段
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_MISSING_METADATA`
|
||||
- **AND** 错误消息包含"模板库必须包含 metadata 字段"
|
||||
|
||||
#### Scenario: 模板库 metadata 缺少 size 字段
|
||||
|
||||
- **WHEN** 模板库包含 metadata 但缺少 size 字段
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_METADATA_MISSING_SIZE`
|
||||
- **AND** 错误消息包含"模板库 metadata 缺少必填字段 'size'"
|
||||
|
||||
#### Scenario: 模板库 metadata 的 size 值无效
|
||||
|
||||
- **WHEN** 模板库 metadata.size 不是有效的尺寸值(如 "16:9" 或 "4:3")
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_METADATA_INVALID_SIZE`
|
||||
|
||||
### Requirement: 模板库 metadata 必须使用与文档相同的验证规则
|
||||
|
||||
模板库 metadata 的字段验证 SHALL 与文档 metadata 使用相同的规则。
|
||||
|
||||
#### Scenario: 模板库 metadata.fonts 验证
|
||||
|
||||
- **WHEN** 模板库 metadata.fonts 中定义字体配置
|
||||
- **THEN** 系统使用与文档 fonts 相同的验证规则
|
||||
|
||||
#### Scenario: 模板库 metadata.fonts_default 验证
|
||||
|
||||
- **WHEN** 模板库 metadata.fonts_default 引用字体配置
|
||||
- **THEN** 系统验证引用存在于模板库 metadata.fonts 中
|
||||
|
||||
#### Scenario: 模板库 metadata.version 和 author 可选
|
||||
|
||||
- **WHEN** 模板库 metadata 包含或不含 version、author 字段
|
||||
- **THEN** 系统正常处理,这些字段为可选项
|
||||
|
||||
### Requirement: 系统必须校验文档和模板库的 size 一致性
|
||||
|
||||
系统 SHALL 在加载模板库时校验文档的 metadata.size 与模板库的 metadata.size 是否一致。
|
||||
|
||||
#### Scenario: 文档和模板库 size 一致
|
||||
|
||||
- **WHEN** 文档 metadata.size 为 "16:9" 且模板库 metadata.size 也为 "16:9"
|
||||
- **THEN** 系统正常加载,不报错
|
||||
|
||||
#### Scenario: 文档和模板库 size 不一致
|
||||
|
||||
- **WHEN** 文档 metadata.size 为 "16:9" 但模板库 metadata.size 为 "4:3"
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `SIZE_MISMATCH`
|
||||
- **AND** 错误消息包含"文档尺寸 '16:9' 与模板库尺寸 '4:3' 不一致"
|
||||
|
||||
### Requirement: 模板库 fonts 的 parent 只能引用模板库内部
|
||||
|
||||
模板库 metadata.fonts 中定义的字体配置,其 parent 字段 SHALL 只能引用模板库内部的字体配置。
|
||||
|
||||
#### Scenario: parent 引用模板库内部的字体
|
||||
|
||||
- **WHEN** 模板库 metadata.fonts 中定义 heading: {parent: "@base"} 且 base 存在于模板库 fonts 中
|
||||
- **THEN** 系统成功解析继承关系
|
||||
|
||||
#### Scenario: parent 引用文档的字体
|
||||
|
||||
- **WHEN** 模板库 metadata.fonts 中定义 custom: {parent: "@doc-font"}
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_PARENT_REF_DOC_FORBIDDEN`
|
||||
- **AND** 错误消息包含"模板库字段的 parent 不能引用文档的字体配置"
|
||||
|
||||
#### Scenario: parent 引用不存在的字体
|
||||
|
||||
- **WHEN** 模板库 metadata.fonts 中定义 heading: {parent: "@nonexistent"}
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `FONT_NOT_FOUND`
|
||||
- **AND** 错误消息包含"引用的字体配置不存在"
|
||||
|
||||
### Requirement: 外部模板元素只能引用模板库的 fonts
|
||||
|
||||
外部模板(模板库中定义的模板)中的元素 SHALL 只能引用模板库 metadata.fonts 中定义的字体配置。
|
||||
|
||||
#### Scenario: 模板元素引用模板库的字体
|
||||
|
||||
- **WHEN** 外部模板元素定义 font: "@title" 且 title 存在于模板库 fonts 中
|
||||
- **THEN** 系统成功应用字体配置
|
||||
|
||||
#### Scenario: 模板元素引用文档的字体
|
||||
|
||||
- **WHEN** 外部模板元素定义 font: "@doc-title" 且 doc-title 只存在于文档 fonts 中
|
||||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_FONT_REF_DOC_FORBIDDEN`
|
||||
- **AND** 错误消息包含"模板元素不能引用文档的字体配置"
|
||||
|
||||
#### Scenario: 模板元素引用同名字体(只使用模板库的)
|
||||
|
||||
- **WHEN** 外部模板元素定义 font: "@title"
|
||||
- **AND** title 同时存在于文档 fonts 和模板库 fonts 中
|
||||
- **THEN** 系统只使用模板库的 title 配置(不查找文档)
|
||||
|
||||
### 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,都没有 fonts_default
|
||||
|
||||
- **WHEN** 模板元素未定义 font
|
||||
- **AND** 文档和模板库都未定义 fonts_default
|
||||
- **THEN** 元素使用系统默认字体
|
||||
|
||||
### Requirement: 表格元素的双字体字段应用相同规则
|
||||
|
||||
表格元素的 font 和 header_font 字段 SHALL 应用与普通元素相同的字体引用规则。
|
||||
|
||||
#### Scenario: 表格的 font 引用模板库字体
|
||||
|
||||
- **WHEN** 模板中的表格定义 font: "@table-body"
|
||||
- **AND** table-body 存在于模板库 fonts 中
|
||||
- **THEN** 系统成功应用字体配置
|
||||
|
||||
#### Scenario: 表格的 header_font 引用模板库字体
|
||||
|
||||
- **WHEN** 模板中的表格定义 header_font: "@table-header"
|
||||
- **AND** table-header 存在于模板库 fonts 中
|
||||
- **THEN** 系统成功应用字体配置
|
||||
|
||||
#### Scenario: 表格的 font 为 null
|
||||
|
||||
- **WHEN** 模板中的表格定义 font: null 或未定义
|
||||
- **THEN** 系统按 fonts_default 级联规则处理
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 模板库文件必须包含元数据字段
|
||||
|
||||
模板库文件 SHALL 必须包含 metadata 字段,元数据结构与文档相同,包含 `size`(必填)、`version`、`author`、`description`、`fonts`、`fonts_default`。
|
||||
|
||||
#### Scenario: 加载包含完整元数据的模板库文件
|
||||
|
||||
- **WHEN** 模板库文件包含 metadata 字段,包含 size、version、author、description、fonts、fonts_default
|
||||
- **THEN** 系统成功加载这些元数据字段
|
||||
- **AND** size 字段必须为有效值
|
||||
- **AND** fonts 和 fonts_default 按字体主题规则验证
|
||||
|
||||
#### Scenario: 模板库 metadata 缺少必填的 size 字段
|
||||
|
||||
- **WHEN** 模板库 metadata 中缺少 size 字段
|
||||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_METADATA_MISSING_SIZE`
|
||||
|
||||
#### Scenario: 模板库缺少 metadata 字段
|
||||
|
||||
- **WHEN** 模板库文件不包含 metadata 字段
|
||||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_MISSING_METADATA`
|
||||
- **AND** 错误消息包含"模板库必须包含 metadata 字段"
|
||||
@@ -0,0 +1,84 @@
|
||||
# Implementation Tasks
|
||||
|
||||
## 0. 清理旧代码和测试
|
||||
|
||||
- [x] 0.1 识别并标记需要清理的旧测试
|
||||
- [x] 0.2 清理 tests/unit/test_template.py 中向后兼容性相关测试
|
||||
- [x] 0.3 清理 tests/integration/test_template_library.py 中向后兼容性相关测试
|
||||
- [x] 0.4 清理旧版本的模板库元数据处理逻辑
|
||||
|
||||
## 1. YAML 加载和验证
|
||||
|
||||
- [x] 1.1 在 loaders/yaml_loader.py 中创建 validate_metadata() 函数
|
||||
- [x] 1.2 修改 validate_presentation_yaml() 调用 validate_metadata()
|
||||
- [x] 1.3 修改 validate_template_library_yaml() 验证模板库必须包含 metadata
|
||||
- [x] 1.4 在 validate_template_library_yaml() 中验证模板库 fonts_default 只能引用模板库内部字体
|
||||
|
||||
## 2. Size 一致性校验
|
||||
|
||||
- [x] 2.1 在 core/presentation.py 中添加 size 一致性校验
|
||||
- [x] 2.2 添加 size 一致性校验的单元测试
|
||||
|
||||
## 3. FontResolver 跨域支持
|
||||
|
||||
- [x] 3.1 扩展 FontResolver 类支持 scope 和 template_fonts 参数
|
||||
- [x] 3.2 修改 _resolve_reference() 方法根据作用域限制引用范围
|
||||
- [x] 3.3 实现跨域循环引用检测
|
||||
- [x] 3.4 添加 parent 引用的跨域限制
|
||||
|
||||
## 4. Presentation 类扩展
|
||||
|
||||
- [x] 4.1 在 Presentation 中解析和存储模板库 metadata.fonts
|
||||
- [x] 4.2 创建文档作用域的 FontResolver
|
||||
- [x] 4.3 创建模板库作用域的 FontResolver
|
||||
|
||||
## 5. Template 类扩展
|
||||
|
||||
- [x] 5.1 修改 Template.from_data() 接收 scope 和 font_resolver 参数
|
||||
- [x] 5.2 修改 Template.render() 应用正确的 FontResolver 和 fonts_default 级联
|
||||
- [x] 5.3 添加表格双字体字段的处理
|
||||
|
||||
## 6. 错误代码定义
|
||||
|
||||
- [x] 6.1 在 validators/result.py 中添加新的错误代码:
|
||||
- SIZE_MISMATCH - 文档和模板库尺寸不一致
|
||||
- TEMPLATE_FONT_REF_DOC_FORBIDDEN - 模板元素引用文档字体
|
||||
- TEMPLATE_PARENT_REF_DOC_FORBIDDEN - 模板库 parent 引用文档字体
|
||||
- FONT_NOT_FOUND - 字体配置不存在
|
||||
- CIRCULAR_REFERENCE - 循环引用(包括跨域)
|
||||
- FONT_DEFAULT_INVALID - fonts_default 引用无效
|
||||
- TEMPLATE_LIBRARY_MISSING_METADATA - 模板库缺少 metadata 字段
|
||||
- TEMPLATE_LIBRARY_METADATA_MISSING_SIZE - 模板库 metadata 缺少 size
|
||||
- TEMPLATE_LIBRARY_METADATA_INVALID_SIZE - 模板库 metadata.size 无效
|
||||
|
||||
## 7. 单元测试
|
||||
|
||||
- [x] 7.1 添加 validate_metadata() 的单元测试
|
||||
- [x] 7.2 添加 FontResolver 跨域引用的单元测试
|
||||
- [x] 7.3 添加 fonts_default 级联的单元测试
|
||||
- [x] 7.4 添加跨域循环引用检测的单元测试(包括单域内循环和跨域循环)
|
||||
- [x] 7.5 添加模板库 fonts_default 验证的单元测试
|
||||
|
||||
## 8. 集成测试
|
||||
|
||||
- [x] 8.1 添加跨域字体引用的集成测试
|
||||
- [x] 8.2 添加 size 一致性校验的集成测试
|
||||
- [x] 8.3 添加完整渲染流程的集成测试
|
||||
- [x] 8.4 添加表格双字体字段(font 和 header_font)的集成测试
|
||||
|
||||
## 9. 文档更新
|
||||
|
||||
- [x] 9.1 更新 README.md 添加模板库 metadata 说明和示例
|
||||
- [x] 9.2 更新 README_DEV.md 记录错误代码和作用域规则
|
||||
- [x] 9.3 添加模板库示例文件
|
||||
|
||||
## 10. 代码清理和格式化
|
||||
|
||||
- [x] 10.1 验证旧测试已完全清理
|
||||
- [x] 10.2 代码格式化和添加中文注释
|
||||
|
||||
## 11. 最终测试
|
||||
|
||||
- [x] 11.1 运行完整的测试套件
|
||||
- [x] 11.2 手动测试关键场景
|
||||
- [x] 11.3 确保所有测试通过
|
||||
Reference in New Issue
Block a user