From 7ef29ea03945af87f54338ec91142f6d3cd1d882 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Wed, 4 Mar 2026 14:47:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=B9=BB=E7=81=AF?= =?UTF-8?q?=E7=89=87=E5=A4=87=E6=B3=A8=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B0=86?= =?UTF-8?q?description=E5=86=99=E5=85=A5PPT=E5=A4=87=E6=B3=A8=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 PptxGenerator._set_notes() 方法设置备注 - 在 add_slide() 中调用 _set_notes() 处理 description - 仅幻灯片级别的 description 写入备注,不继承模板 - 添加备注功能测试用例(8个测试) - 更新 README.md 和 README_DEV.md 文档 - 新建 pptx-slide-notes spec - 更新 page-description spec 允许写入备注 - 归档 add-slide-notes 变更 --- README.md | 7 +- README_DEV.md | 30 ++-- .../2026-03-04-add-slide-notes/.openspec.yaml | 2 + .../2026-03-04-add-slide-notes/design.md | 75 +++++++++ .../2026-03-04-add-slide-notes/proposal.md | 25 +++ .../specs/page-description/spec.md | 23 +++ .../specs/pptx-slide-notes/spec.md | 52 ++++++ .../2026-03-04-add-slide-notes/tasks.md | 17 ++ openspec/specs/page-description/spec.md | 6 +- openspec/specs/pptx-slide-notes/spec.md | 58 +++++++ renderers/pptx_renderer.py | 19 +++ .../unit/test_renderers/test_pptx_renderer.py | 155 ++++++++++++++++++ 12 files changed, 454 insertions(+), 15 deletions(-) create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/.openspec.yaml create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/design.md create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/proposal.md create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/specs/page-description/spec.md create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/specs/pptx-slide-notes/spec.md create mode 100644 openspec/changes/archive/2026-03-04-add-slide-notes/tasks.md create mode 100644 openspec/specs/pptx-slide-notes/spec.md diff --git a/README.md b/README.md index 38744fc..a02cf31 100644 --- a/README.md +++ b/README.md @@ -549,7 +549,7 @@ slides: #### 幻灯片 description 字段 -幻灯片可以包含可选的 `description` 字段,用于描述该幻灯片的作用和内容,仅用于文档目的,不影响生成的 PPTX 文件: +幻灯片可以包含可选的 `description` 字段,用于描述该幻灯片的作用和内容。**`description` 内容会自动写入 PPT 备注页**,方便在演示时查看演讲说明: ```yaml slides: @@ -564,6 +564,11 @@ slides: content: "功能特性" ``` +**注意事项**: +- 仅幻灯片级别的 `description` 会写入备注 +- 模板的 `description` 不会继承到幻灯片备注 +- `metadata.description` 用于描述整个演示文稿,不写入单个幻灯片备注 + ### 条件渲染 #### 元素级条件渲染 diff --git a/README_DEV.md b/README_DEV.md index c25f898..3b84b7d 100644 --- a/README_DEV.md +++ b/README_DEV.md @@ -545,40 +545,48 @@ if right > slide_width + TOLERANCE: **理由**: - 自文档化:提高模板和演示文稿的可读性和可维护性 -- 不影响渲染:description 仅用于文档目的,不参与 PPTX 生成 +- 备注支持:幻灯片 description 会写入 PPT 备注页,方便演讲者查看 - 完全向后兼容:字段为可选,现有文件无需修改 **实现要点**: 1. **数据模型**: - - `Presentation.description`:从 `metadata.description` 读取 - - `Template.description`:从模板文件的 `description` 字段读取 - - `Slide.description`:在 `render_slide()` 返回值中保留 + - `Presentation.description`:从 `metadata.description` 读取,用于描述整个演示文稿 + - `Template.description`:从模板文件的 `description` 字段读取,描述模板用途 + - `Slide.description`:在 `render_slide()` 返回值中保留,会写入 PPT 备注页 2. **YAML 解析**: - 使用 `.get('description')` 自动处理缺失情况(返回 None) - YAML 原生支持多行文本格式 -3. **不参与渲染**: - - description 字段不传递给渲染器 - - 不写入最终的 PPTX 文件 +3. **PPT 备注功能**: + - 仅幻灯片级别的 `description` 会写入 PPT 备注页 + - 模板的 `description` 不继承到幻灯片备注 + - `metadata.description` 不写入单个幻灯片备注 + - 如果幻灯片没有 `description`,则不设置备注 + +4. **渲染实现**: + - `PptxGenerator._set_notes()`:设置幻灯片备注的私有方法 + - `PptxGenerator.add_slide()`:调用 `_set_notes()` 设置备注 **示例**: ```yaml -# metadata description +# metadata description - 描述整个演示文稿 metadata: description: "2024年度项目进展总结" -# 模板 description +# 模板 description - 描述模板用途 templates: title-slide: description: "用于章节标题页的模板" elements: [...] -# 幻灯片 description +# 幻灯片 description - 写入 PPT 备注页 slides: - - description: "介绍项目背景" + - description: "介绍项目背景和目标,包含以下要点:..." template: title-slide + vars: + title: "项目背景" ``` ## 扩展指南 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/.openspec.yaml b/openspec/changes/archive/2026-03-04-add-slide-notes/.openspec.yaml new file mode 100644 index 0000000..5aae5cf --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-04 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/design.md b/openspec/changes/archive/2026-03-04-add-slide-notes/design.md new file mode 100644 index 0000000..42cb21e --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/design.md @@ -0,0 +1,75 @@ +## Context + +当前系统中,`description` 字段已在以下位置被解析: +- `core/presentation.py`: 演示文稿级别的 `metadata.description` +- `core/template.py`: 模板级别的 `template.description` +- `core/presentation.py`: 幻灯片级别的 `slide.description` 在 `render_slide` 方法返回值中 + +然而,PPTX 渲染器 (`renderers/pptx_renderer.py`) 的 `add_slide` 方法只处理 `background` 和 `elements`,完全忽略了 `description` 字段。 + +python-pptx 库原生支持备注功能,通过 `slide.notes_slide.notes_text_frame.text` 即可设置。 + +## Goals / Non-Goals + +**Goals:** +- 将幻灯片的 `description` 字段写入 PPT 备注页 +- 仅处理幻灯片级别的 description,不涉及模板或演示文稿级别的 description +- 保持向后兼容,没有 description 时不设置备注 + +**Non-Goals:** +- 不实现富文本备注(仅支持纯文本) +- 不合并模板的 description +- 不处理演示文稿级别的 metadata.description + +## Decisions + +### 1. 备注内容来源 + +**决策**: 仅使用幻灯片自己的 `description` 字段 + +**理由**: +- 幻灯片 description 是用户针对具体页面编写的说明 +- 模板 description 描述的是模板用途,不适合作为单个幻灯片的备注 +- 避免复杂的合并逻辑,保持简单清晰 + +**考虑的替代方案**: +- 合并模板 description 和幻灯片 description → 过于复杂,可能产生冗余内容 +- 仅在没有幻灯片 description 时使用模板 description → 增加认知负担 + +### 2. 无 description 时的行为 + +**决策**: 不设置备注,保持备注页为空 + +**理由**: +- python-pptx 默认创建空备注页 +- 不需要额外判断或清理操作 +- 保持行为可预测 + +### 3. 代码位置 + +**决策**: 在 `PptxGenerator` 类中添加私有方法 `_set_notes` + +**理由**: +- 与现有的 `_render_background`、`_render_element` 等方法保持一致 +- 封装备注设置逻辑,便于测试和维护 +- 不影响 `add_slide` 方法的主流程 + +## Risks / Trade-offs + +### Risk: 长文本可能导致备注溢出 + +**影响**: description 内容过长时,可能在备注页中显示不佳 + +**缓解**: python-pptx 会自动处理文本换行,这是库的默认行为,无需额外处理 + +### Trade-off: 仅支持纯文本 + +**限制**: python-pptx 的备注不支持富文本格式(加粗、颜色等) + +**权衡**: 备注的主要用途是演讲者参考,纯文本已满足需求;富文本支持需要更复杂的实现,收益不大 + +### Risk: 现有 spec 需要更新 + +**影响**: `page-description` spec 明确规定 "description不写入PPTX文件" + +**缓解**: 这正是本次变更的目的,需要更新该 spec 以反映新行为 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/proposal.md b/openspec/changes/archive/2026-03-04-add-slide-notes/proposal.md new file mode 100644 index 0000000..e678ab8 --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/proposal.md @@ -0,0 +1,25 @@ +## Why + +幻灯片的 description 字段已经在系统中被解析和传递,但目前未被实际使用。PPT 备注页是演讲者的原生工具,非常适合存储这些演讲说明。通过将 description 写入 PPT 备注,可以让用户在演示时查看说明而不显示给观众,充分利用已有数据。 + +## What Changes + +- 在 PPTX 生成时,将幻灯片的 `description` 字段写入备注页 +- 仅处理幻灯片级别的 `description`,不继承模板的 `description` +- 如果幻灯片没有 `description`,则不设置备注 +- 修改现有 `page-description` spec 的需求,因为 description 现在会影响 PPTX 输出 + +## Capabilities + +### New Capabilities +- `pptx-slide-notes`: 幻灯片备注功能,支持将 description 内容写入 PPT 备注页 + +### Modified Capabilities +- `page-description`: 现有 spec 规定 "description字段不得影响渲染逻辑" 和 "description不写入PPTX文件",需要更新为允许 description 写入备注页 + +## Impact + +- `renderers/pptx_renderer.py`: 在 `add_slide` 方法中添加设置备注的逻辑 +- `openspec/specs/page-description/spec.md`: 更新需求,移除 "不得影响渲染逻辑" 的限制 +- `openspec/specs/pptx-slide-notes/spec.md`: 新建 spec 定义备注功能需求 +- `README.md` 和 `README_DEV.md`: 更新文档说明备注功能 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/specs/page-description/spec.md b/openspec/changes/archive/2026-03-04-add-slide-notes/specs/page-description/spec.md new file mode 100644 index 0000000..7e6c171 --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/specs/page-description/spec.md @@ -0,0 +1,23 @@ +## MODIFIED Requirements + +### Requirement: description字段不得影响渲染逻辑 + +系统 SHALL 在渲染幻灯片时忽略 `description` 字段对视觉元素的影响,但会将幻灯片级别的 `description` 写入 PPTX 备注页。 + +#### Scenario: 渲染包含description的模板 + +- **WHEN** 系统渲染包含 `description` 字段的模板 +- **THEN** description不参与元素渲染,不影响幻灯片视觉输出 + +#### Scenario: 渲染包含description的幻灯片 + +- **WHEN** 系统渲染包含 `description` 字段的幻灯片 +- **THEN** description写入PPTX文件的备注页,不影响幻灯片视觉输出 + +## REMOVED Requirements + +### Requirement: description不写入PPTX文件 + +**Reason**: 幻灯片备注功能需要将 description 写入 PPTX 备注页,这是对 description 字段的合理利用。 + +**Migration**: 无需迁移,这是新增功能,不影响现有行为。没有 description 的幻灯片行为与之前完全一致。 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/specs/pptx-slide-notes/spec.md b/openspec/changes/archive/2026-03-04-add-slide-notes/specs/pptx-slide-notes/spec.md new file mode 100644 index 0000000..a4dc244 --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/specs/pptx-slide-notes/spec.md @@ -0,0 +1,52 @@ +## ADDED Requirements + +### Requirement: 幻灯片description必须写入PPT备注页 + +系统 SHALL 在生成 PPTX 时,将幻灯片的 `description` 字段内容写入该幻灯片的备注页。 + +#### Scenario: 幻灯片包含description + +- **WHEN** 幻灯片定义了 `description: "这是幻灯片的演讲说明"` +- **THEN** PPTX 幻灯片的备注页包含该文本内容 + +#### Scenario: description包含多行文本 + +- **WHEN** 幻灯片的 description 使用 YAML 多行文本格式定义 +- **THEN** 备注页保留完整的文本内容,包括换行符 + +#### Scenario: description为空字符串 + +- **WHEN** 幻灯片定义了 `description: ""` +- **THEN** 备注页设置为空字符串(不为 None) + +### Requirement: 无description时不设置备注 + +系统 SHALL 在幻灯片没有 `description` 字段时不设置备注内容。 + +#### Scenario: 幻灯片不包含description + +- **WHEN** 幻灯片定义未包含 `description` 字段 +- **THEN** PPTX 幻灯片不设置备注,使用默认空备注页 + +### Requirement: 模板description不写入备注 + +系统 SHALL 仅处理幻灯片级别的 `description`,不使用模板的 `description`。 + +#### Scenario: 模板有description但幻灯片没有 + +- **WHEN** 模板定义了 `description: "模板说明"` 但幻灯片未定义 `description` +- **THEN** PPTX 幻灯片不设置备注,不继承模板的 description + +#### Scenario: 模板和幻灯片都有description + +- **WHEN** 模板定义了 `description` 且幻灯片也定义了 `description` +- **THEN** PPTX 幻灯片备注仅包含幻灯片的 description,忽略模板的 description + +### Requirement: description必须支持中文字符 + +系统 SHALL 支持在 `description` 字段中使用中文字符,并正确写入 PPTX 备注。 + +#### Scenario: description包含中文 + +- **WHEN** 幻灯片的 description 包含中文字符,如 "这是演讲备注内容" +- **THEN** 系统正确处理,PPTX 备注页显示正确的中文内容 diff --git a/openspec/changes/archive/2026-03-04-add-slide-notes/tasks.md b/openspec/changes/archive/2026-03-04-add-slide-notes/tasks.md new file mode 100644 index 0000000..e26d70e --- /dev/null +++ b/openspec/changes/archive/2026-03-04-add-slide-notes/tasks.md @@ -0,0 +1,17 @@ +## 1. 核心实现 + +- [x] 1.1 在 PptxGenerator 类中添加 _set_notes 私有方法 +- [x] 1.2 在 add_slide 方法中调用 _set_notes 设置备注 + +## 2. 测试 + +- [x] 2.1 添加幻灯片包含 description 的测试用例 +- [x] 2.2 添加幻灯片不包含 description 的测试用例 +- [x] 2.3 添加 description 包含多行文本的测试用例 +- [x] 2.4 添加 description 包含中文字符的测试用例 +- [x] 2.5 添加模板有 description 但幻灯片没有的测试用例 + +## 3. 文档更新 + +- [x] 3.1 更新 README.md 添加备注功能说明 +- [x] 3.2 更新 README_DEV.md 添加备注功能开发文档 diff --git a/openspec/specs/page-description/spec.md b/openspec/specs/page-description/spec.md index a522c05..5c6152f 100644 --- a/openspec/specs/page-description/spec.md +++ b/openspec/specs/page-description/spec.md @@ -60,17 +60,17 @@ Page Description功能允许用户为文档元数据、模板和幻灯片添加 ### Requirement: description字段不得影响渲染逻辑 -系统 SHALL 在渲染过程中忽略 `description` 字段,不影响最终的PPTX输出。 +系统 SHALL 在渲染幻灯片时忽略 `description` 字段对视觉元素的影响,但会将幻灯片级别的 `description` 写入 PPTX 备注页。 #### Scenario: 渲染包含description的模板 - **WHEN** 系统渲染包含 `description` 字段的模板 -- **THEN** description不参与元素渲染,不影响输出结果 +- **THEN** description不参与元素渲染,不影响幻灯片视觉输出 #### Scenario: 渲染包含description的幻灯片 - **WHEN** 系统渲染包含 `description` 字段的幻灯片 -- **THEN** description不写入PPTX文件,不影响输出结果 +- **THEN** description写入PPTX文件的备注页,不影响幻灯片视觉输出 ### Requirement: YAML解析器必须正确解析description字段 diff --git a/openspec/specs/pptx-slide-notes/spec.md b/openspec/specs/pptx-slide-notes/spec.md new file mode 100644 index 0000000..199d5c3 --- /dev/null +++ b/openspec/specs/pptx-slide-notes/spec.md @@ -0,0 +1,58 @@ +# PPTX Slide Notes + +## Purpose + +PPTX 备注功能允许用户在生成 PPTX 文件时,将幻灯片的 description 字段内容写入备注页。备注页是 PPT 的原生演讲者工具,在演示时可以查看但不显示给观众,非常适合存储演讲说明和备注信息。 + +## Requirements + +### Requirement: 幻灯片description必须写入PPT备注页 + +系统 SHALL 在生成 PPTX 时,将幻灯片的 `description` 字段内容写入该幻灯片的备注页。 + +#### Scenario: 幻灯片包含description + +- **WHEN** 幻灯片定义了 `description: "这是幻灯片的演讲说明"` +- **THEN** PPTX 幻灯片的备注页包含该文本内容 + +#### Scenario: description包含多行文本 + +- **WHEN** 幻灯片的 description 使用 YAML 多行文本格式定义 +- **THEN** 备注页保留完整的文本内容,包括换行符 + +#### Scenario: description为空字符串 + +- **WHEN** 幻灯片定义了 `description: ""` +- **THEN** 备注页设置为空字符串(不为 None) + +### Requirement: 无description时不设置备注 + +系统 SHALL 在幻灯片没有 `description` 字段时不设置备注内容。 + +#### Scenario: 幻灯片不包含description + +- **WHEN** 幻灯片定义未包含 `description` 字段 +- **THEN** PPTX 幻灯片不设置备注,使用默认空备注页 + +### Requirement: 模板description不写入备注 + +系统 SHALL 仅处理幻灯片级别的 `description`,不使用模板的 `description`。 + +#### Scenario: 模板有description但幻灯片没有 + +- **WHEN** 模板定义了 `description: "模板说明"` 但幻灯片未定义 `description` +- **THEN** PPTX 幻灯片不设置备注,不继承模板的 description + +#### Scenario: 模板和幻灯片都有description + +- **WHEN** 模板定义了 `description` 且幻灯片也定义了 `description` +- **THEN** PPTX 幻灯片备注仅包含幻灯片的 description,忽略模板的 description + +### Requirement: description必须支持中文字符 + +系统 SHALL 支持在 `description` 字段中使用中文字符,并正确写入 PPTX 备注。 + +#### Scenario: description包含中文 + +- **WHEN** 幻灯片的 description 包含中文字符,如 "这是演讲备注内容" +- **THEN** 系统正确处理,PPTX 备注页显示正确的中文内容 diff --git a/renderers/pptx_renderer.py b/renderers/pptx_renderer.py index 4f38527..dc25024 100644 --- a/renderers/pptx_renderer.py +++ b/renderers/pptx_renderer.py @@ -60,6 +60,11 @@ class PptxGenerator: for elem in elements: self._render_element(slide, elem, base_path) + # 设置备注 + description = slide_data.get('description') + if description: + self._set_notes(slide, description) + def _render_element(self, slide, elem, base_path): """ 分发元素到对应的渲染方法 @@ -269,6 +274,20 @@ class PptxGenerator: from utils import log_info log_info(f"图片背景暂未实现: {background['image']}") + def _set_notes(self, slide, text): + """ + 设置幻灯片备注 + + Args: + slide: pptx slide 对象 + text: 备注文本内容 + """ + if text is None: + return + notes_slide = slide.notes_slide + text_frame = notes_slide.notes_text_frame + text_frame.text = text + def save(self, output_path): """ 保存 PPTX 文件 diff --git a/tests/unit/test_renderers/test_pptx_renderer.py b/tests/unit/test_renderers/test_pptx_renderer.py index 0970f48..e4958ca 100644 --- a/tests/unit/test_renderers/test_pptx_renderer.py +++ b/tests/unit/test_renderers/test_pptx_renderer.py @@ -540,3 +540,158 @@ class TestRenderBackground: # 不应该崩溃 gen._render_background(mock_slide, None) + + +class TestSetNotes: + """_set_notes 方法测试类""" + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_set_notes_with_text(self, mock_prs_class): + """测试设置备注文本""" + mock_slide = Mock() + mock_notes_slide = Mock() + mock_text_frame = Mock() + mock_notes_slide.notes_text_frame = mock_text_frame + mock_slide.notes_slide = mock_notes_slide + mock_prs_class.return_value = Mock() + mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()] + mock_prs_class.return_value.slides.add_slide.return_value = mock_slide + + gen = PptxGenerator() + + gen._set_notes(mock_slide, "这是演讲备注内容") + + # 验证备注被设置 + mock_text_frame.text = "这是演讲备注内容" + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_set_notes_with_none(self, mock_prs_class): + """测试设置 None 不设置备注""" + mock_slide = Mock(spec=[]) # 使用 spec=[] 禁止自动创建属性 + mock_prs_class.return_value = Mock() + mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()] + mock_prs_class.return_value.slides.add_slide.return_value = mock_slide + + gen = PptxGenerator() + + # 不应该崩溃,不应该访问 notes_slide + gen._set_notes(mock_slide, None) + # 使用 spec=[] 后,访问不存在的属性会抛出 AttributeError + # 如果没有抛出异常,说明 notes_slide 没有被访问 + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_set_notes_with_empty_string(self, mock_prs_class): + """测试设置空字符串""" + mock_slide = Mock() + mock_notes_slide = Mock() + mock_text_frame = Mock() + mock_notes_slide.notes_text_frame = mock_text_frame + mock_slide.notes_slide = mock_notes_slide + mock_prs_class.return_value = Mock() + mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()] + mock_prs_class.return_value.slides.add_slide.return_value = mock_slide + + gen = PptxGenerator() + + gen._set_notes(mock_slide, "") + + # 验证空字符串也被设置 + mock_text_frame.text = "" + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_set_notes_with_multiline_text(self, mock_prs_class): + """测试设置多行文本""" + mock_slide = Mock() + mock_notes_slide = Mock() + mock_text_frame = Mock() + mock_notes_slide.notes_text_frame = mock_text_frame + mock_slide.notes_slide = mock_notes_slide + mock_prs_class.return_value = Mock() + mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()] + mock_prs_class.return_value.slides.add_slide.return_value = mock_slide + + gen = PptxGenerator() + + multiline_text = "第一行备注\n第二行备注\n第三行备注" + gen._set_notes(mock_slide, multiline_text) + + # 验证多行文本被设置 + mock_text_frame.text = multiline_text + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_set_notes_with_chinese_text(self, mock_prs_class): + """测试设置中文备注""" + mock_slide = Mock() + mock_notes_slide = Mock() + mock_text_frame = Mock() + mock_notes_slide.notes_text_frame = mock_text_frame + mock_slide.notes_slide = mock_notes_slide + mock_prs_class.return_value = Mock() + mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()] + mock_prs_class.return_value.slides.add_slide.return_value = mock_slide + + gen = PptxGenerator() + + chinese_text = "这是中文演讲备注内容,用于演示时参考" + gen._set_notes(mock_slide, chinese_text) + + # 验证中文文本被设置 + mock_text_frame.text = chinese_text + + +class TestAddSlideWithNotes: + """add_slide 方法备注功能测试类""" + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_add_slide_with_description_sets_notes(self, mock_prs_class): + """测试幻灯片包含 description 时设置备注""" + mock_slide = Mock() + mock_notes_slide = Mock() + mock_text_frame = Mock() + mock_notes_slide.notes_text_frame = mock_text_frame + mock_slide.notes_slide = mock_notes_slide + mock_prs = Mock() + mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None] + mock_prs.slides.add_slide.return_value = mock_slide + mock_prs_class.return_value = mock_prs + + gen = PptxGenerator() + slide_data = {"background": None, "elements": [], "description": "这是幻灯片的演讲说明"} + + gen.add_slide(slide_data) + + # 验证备注被设置 + mock_text_frame.text = "这是幻灯片的演讲说明" + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_add_slide_without_description_no_notes(self, mock_prs_class): + """测试幻灯片不包含 description 时不设置备注""" + mock_slide = Mock(spec=[]) # 禁止自动创建属性 + mock_prs = Mock() + mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None] + mock_prs.slides.add_slide.return_value = mock_slide + mock_prs_class.return_value = mock_prs + + gen = PptxGenerator() + slide_data = {"background": None, "elements": []} + + gen.add_slide(slide_data) + + # 不应该访问 notes_slide(使用 spec=[] 会抛出异常如果被访问) + + @patch("renderers.pptx_renderer.PptxPresentation") + def test_add_slide_with_template_description_ignored(self, mock_prs_class): + """测试模板的 description 不被用作备注(仅幻灯片自己的 description 有效)""" + mock_slide = Mock(spec=[]) # 禁止自动创建属性 + mock_prs = Mock() + mock_prs.slide_layouts = [None] * 6 + [Mock()] + [None] + mock_prs.slides.add_slide.return_value = mock_slide + mock_prs_class.return_value = mock_prs + + gen = PptxGenerator() + # 幻灯片没有 description,则不设置备注(即使模板可能有 description) + slide_data = {"background": None, "elements": []} + + gen.add_slide(slide_data) + + # 不应该访问 notes_slide(使用 spec=[] 会抛出异常如果被访问)