1
0
Files
lanyuanxiaoyao bd12fce14b feat: 实现字体主题系统和东亚字体支持
实现完整的字体主题系统,支持可复用字体配置、预设类别和扩展属性。
同时修复中文字体渲染问题,确保 Source Han Sans 等东亚字体正确显示。

核心功能:
- 字体主题配置:metadata.fonts 和 fonts_default
- 三种引用方式:整体引用、继承覆盖、独立定义
- 预设字体类别:sans、serif、mono、cjk-sans、cjk-serif
- 扩展字体属性:family、underline、strikethrough、line_spacing、
  space_before、space_after、baseline、caps
- 表格字体字段:font 和 header_font 替代旧的 style.font_size
- 引用循环检测和属性继承链
- 模板字体继承支持

东亚字体修复:
- 添加 _set_font_with_eastasian() 方法
- 同时设置拉丁字体、东亚字体和复杂脚本字体
- 修复中文字符使用默认字体的问题

测试:
- 58 个单元测试覆盖所有字体系统功能
- 3 个集成测试验证端到端场景
- 移除旧语法相关测试

文档:
- 更新 README.md 添加字体主题系统使用说明
- 更新 README_DEV.md 添加技术文档
- 创建 4 个示例 YAML 文件
- 同步 delta specs 到主 specs

归档:
- 归档 font-theme-system 变更到 openspec/changes/archive/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-05 10:38:59 +08:00

288 lines
11 KiB
Markdown
Raw Permalink 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.
# Element Rendering
## Purpose
Element rendering系统负责将 YAML 中定义的各类元素(文本、图片、形状、表格)转换为 PPTX 文档中的实际对象。它处理元素的定位、样式应用、层次管理,以及幻灯片背景设置。
## Requirements
### Requirement: 系统必须支持文本元素渲染
系统 SHALL 将 YAML 中定义的文本元素渲染为 PPTX 文本框并默认启用文字自动换行。当文本内容包含换行符YAML 多行字符串语法)时,系统必须将所有字体样式(大小、粗体、斜体、颜色、对齐)应用到文本框中的每个段落。
#### Scenario: 渲染基本文本元素
- **WHEN** 元素定义为 `{type: text, content: "Hello", box: [1, 2, 8, 3]}`
- **THEN** 系统在幻灯片的 (1, 2) 位置创建 8×3 英寸的文本框,内容为 "Hello"
#### Scenario: 应用文本字体样式
- **WHEN** 文本元素定义了 `font: {size: 32, bold: true, color: "#333333"}`
- **THEN** 系统将文本设置为 32pt、粗体、颜色为 #333333
#### Scenario: 多行文本样式应用到所有段落
- **WHEN** 文本内容包含换行符(如 YAML 多行字符串 `content: |\n 第一行\n 第二行\n 第三行`)且定义了 `font: {size: 12}`
- **THEN** 系统将 12pt 字号应用到所有段落(第一行、第二行、第三行)
- **AND** 每个段落的字体大小都应一致
#### Scenario: 多行文本其他样式应用到所有段落
- **WHEN** 文本内容包含换行符且定义了 `font: {size: 12, bold: true, italic: true, color: "#ff0000", align: center}`
- **THEN** 系统将所有样式(大小、粗体、斜体、颜色、对齐)应用到所有段落
- **AND** 每个段落的样式都应一致
#### Scenario: 单行文本样式行为不变
- **WHEN** 文本内容不包含换行符且定义了 `font: {size: 12}`
- **THEN** 系统将 12pt 字号应用到文本(行为与之前相同)
#### Scenario: 应用文本对齐方式
- **WHEN** 文本元素定义了 `font: {align: center}`
- **THEN** 系统将文本设置为居中对齐
#### Scenario: 支持多种对齐方式
- **WHEN** 文本对齐方式为 `left``center``right` 之一
- **THEN** 系统正确应用对应的对齐方式
#### Scenario: 文本框默认启用自动换行
- **WHEN** 系统渲染任何文本元素
- **THEN** 系统设置 `text_frame.word_wrap = True`,文字在文本框边界处自动换行
### Requirement: 系统必须支持图片元素渲染
系统 SHALL 将 YAML 中定义的图片元素渲染为 PPTX 图片对象。
#### Scenario: 渲染本地图片
- **WHEN** 元素定义为 `{type: image, src: "images/logo.png", box: [2, 3, 4, 3]}`
- **THEN** 系统从指定路径加载图片,在 (2, 3) 位置渲染为 4×3 英寸大小
#### Scenario: 图片文件不存在时报错
- **WHEN** 图片 src 指向不存在的文件路径
- **THEN** 系统抛出错误,明确指出图片文件未找到
#### Scenario: 相对路径处理
- **WHEN** 图片 src 使用相对路径 `"assets/images/logo.png"`
- **THEN** 系统基于演示文稿文件所在目录解析相对路径
### Requirement: 图片元素的 box 参数必须存在
系统 SHALL 要求图片元素必须包含 `box` 参数,否则验证失败。
#### Scenario: 缺少 box 参数时报错
- **WHEN** 图片元素未定义 `box` 参数
- **THEN** 系统抛出 ERROR提示 box 参数为必需
#### Scenario: box 参数格式正确
- **WHEN** 图片元素定义了 `box: [1, 2, 4, 3]`
- **THEN** 系统验证通过,将 box 用于图片定位和尺寸
### Requirement: 系统必须支持形状元素渲染
系统 SHALL 将 YAML 中定义的形状元素渲染为 PPTX 形状对象。
#### Scenario: 渲染矩形形状
- **WHEN** 元素定义为 `{type: shape, shape: rectangle, box: [1, 2, 3, 1], fill: "#4a90e2"}`
- **THEN** 系统在 (1, 2) 位置创建 3×1 英寸的矩形,填充颜色为 #4a90e2
#### Scenario: 渲染圆形形状
- **WHEN** 元素的 `shape` 字段为 `ellipse`
- **THEN** 系统渲染椭圆/圆形形状
#### Scenario: 渲染圆角矩形
- **WHEN** 元素的 `shape` 字段为 `rounded_rectangle`
- **THEN** 系统渲染圆角矩形形状
#### Scenario: 应用形状边框样式
- **WHEN** 形状定义了 `line: {color: "#000000", width: 2}`
- **THEN** 系统为形状添加黑色、2pt 宽度的边框
### Requirement: 系统必须支持表格元素渲染
系统 SHALL 将 YAML 中定义的表格元素渲染为 PPTX 表格对象。表格元素支持 font 和 header_font 字段用于字体配置style 字段仅用于非字体样式(如背景色)。
#### Scenario: 渲染基本表格
- **WHEN** 元素定义为 `{type: table, position: [1, 2], data: [["A", "B"], ["C", "D"]], col_widths: [2, 2]}`
- **THEN** 系统在 (1, 2) 位置创建 2×2 表格,列宽各为 2 英寸
#### Scenario: 表格定义整体字体
- **WHEN** 表格定义 `font: {family: "Arial", size: 14, color: "#333333"}`
- **THEN** 系统将数据单元格和表头都应用该字体样式
#### Scenario: 表格定义表头字体
- **WHEN** 表格定义 `header_font: {bold: true, color: "#ffffff"}`
- **THEN** 系统将表头应用该字体样式,数据单元格继承 font 或使用默认字体
#### 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 或系统默认字体
#### Scenario: 表格定义背景样式
- **WHEN** 表格定义 `style: {header_bg: "#4a90e2"}`
- **THEN** 系统将表头背景色设置为 #4a90e2
#### Scenario: 表格同时定义字体和背景样式
- **WHEN** 表格定义 `font: {size: 14}``style: {header_bg: "#4a90e2"}`
- **THEN** 系统同时应用字体样式和背景色
#### Scenario: 表格数据为空时报错
- **WHEN** 表格的 `data` 字段为空数组
- **THEN** 系统抛出错误,提示表格数据不能为空
#### Scenario: 表格列宽不匹配时报错
- **WHEN** `col_widths` 数组长度与表格列数不一致
- **THEN** 系统抛出错误,要求列宽数量与列数匹配
### Requirement: 元素位置必须使用英寸单位
系统 SHALL 将元素的 box 或 position 字段中的数值解释为英寸单位。
#### Scenario: box 数组表示位置和尺寸
- **WHEN** 元素定义了 `box: [1, 2, 8, 3]`
- **THEN** 系统将其解释为 x=1英寸, y=2英寸, width=8英寸, height=3英寸
#### Scenario: position 数组表示位置
- **WHEN** 表格元素定义了 `position: [1, 2]`
- **THEN** 系统将其解释为 x=1英寸, y=2英寸
#### Scenario: 负数坐标值报错
- **WHEN** box 或 position 包含负数值
- **THEN** 系统抛出错误,要求坐标值必须为非负数
### Requirement: 系统必须支持幻灯片背景设置
系统 SHALL 支持为幻灯片设置纯色背景或图片背景。
#### Scenario: 设置纯色背景
- **WHEN** 幻灯片定义了 `background: {color: "#ffffff"}`
- **THEN** 系统将幻灯片背景设置为白色
#### Scenario: 设置图片背景
- **WHEN** 幻灯片定义了 `background: {image: "bg.png"}`
- **THEN** 系统使用指定图片作为幻灯片背景
#### Scenario: 背景图片不存在时报错
- **WHEN** 背景图片路径不存在
- **THEN** 系统抛出错误,提示背景图片文件未找到
### Requirement: 系统必须处理元素重叠和层次
系统 SHALL 按照元素在 elements 列表中的顺序渲染,后定义的元素显示在前面元素之上。
#### Scenario: 元素按定义顺序渲染
- **WHEN** elements 列表为 `[shape1, text1, image1]`
- **THEN** 系统先渲染 shape1再渲染 text1最后渲染 image1image1 在最上层)
#### Scenario: 重叠元素的显示层次
- **WHEN** 两个元素的位置有重叠
- **THEN** 后定义的元素遮盖先定义的元素
### Requirement: 系统必须使用元素数据类进行渲染
系统 SHALL 使用元素数据类TextElement, ImageElement, ShapeElement, TableElement进行渲染而不是直接处理字典数据。渲染器接收元素对象通过类型检查分发到对应的渲染方法。
#### Scenario: 渲染器接收元素对象
- **WHEN** 渲染器的 `_render_element()` 方法被调用
- **THEN** 系统应接收元素对象(如 TextElement 实例),而不是字典
#### Scenario: 通过类型检查分发渲染
- **WHEN** 渲染器处理元素对象
- **THEN** 系统应使用 `isinstance()` 检查元素类型,分发到对应的渲染方法(如 `_render_text()`, `_render_image()`
#### Scenario: 元素验证在创建时完成
- **WHEN** 元素对象被创建
- **THEN** 系统应在 `__post_init__` 方法中完成验证,渲染时不再需要验证
### Requirement: 渲染器必须内置在生成器中
系统 SHALL 将渲染逻辑内置在 PptxGenerator 类中,作为私有方法(`_render_text()`, `_render_image()` 等),而不是独立的函数。
#### Scenario: 渲染方法作为生成器的私有方法
- **WHEN** 开发者查看 PptxGenerator 类
- **THEN** 应包含 `_render_text()`, `_render_image()`, `_render_shape()`, `_render_table()` 等私有方法
#### Scenario: 渲染方法接收元素对象
- **WHEN** 调用 `_render_text(slide, elem)` 方法
- **THEN** `elem` 参数应为 TextElement 对象,包含 content、box、font 等属性
### Requirement: 元素渲染必须支持元素对象的属性访问
系统 SHALL 通过元素对象的属性(如 `elem.content`, `elem.box`, `elem.font`)访问数据,而不是通过字典的 `get()` 方法。
#### Scenario: 访问文本元素的属性
- **WHEN** 渲染文本元素
- **THEN** 系统应使用 `elem.content` 而不是 `elem.get('content', '')`
#### Scenario: 访问图片元素的属性
- **WHEN** 渲染图片元素
- **THEN** 系统应使用 `elem.src``elem.box` 而不是 `elem.get('src')``elem.get('box')`
#### Scenario: 访问形状元素的属性
- **WHEN** 渲染形状元素
- **THEN** 系统应使用 `elem.shape`, `elem.fill`, `elem.line` 等属性
#### Scenario: 访问表格元素的属性
- **WHEN** 渲染表格元素
- **THEN** 系统应使用 `elem.data`, `elem.position`, `elem.col_widths`, `elem.style` 等属性
### Requirement: 系统必须验证元素类型
系统 SHALL 验证每个元素的 type 字段,仅支持已定义的元素类型。
#### Scenario: 支持的元素类型
- **WHEN** 元素的 `type``text``image``shape``table` 之一
- **THEN** 系统正常渲染该元素
#### Scenario: 不支持的元素类型报错
- **WHEN** 元素的 `type` 为未定义的值(如 "video"
- **THEN** 系统抛出错误,提示不支持该元素类型,并列出支持的类型
#### Scenario: 缺少 type 字段报错
- **WHEN** 元素未定义 `type` 字段
- **THEN** 系统抛出错误,要求每个元素必须包含 type 字段