refactor: 重构文档结构,采用渐进式信息披露模式
将 README.md 拆分为多个专题文档,减少认知负荷: - 用户文档迁移到 docs/ (用户指南、元素、模板、参考等) - 开发文档迁移到 docs/development/ (架构、模块、规范) - README.md 精简至 ~290 行,仅保留概览和导航 - 删除 README_DEV.md,内容已迁移 - 归档 OpenSpec 变更 refactor-docs-progressive-disclosure
This commit is contained in:
48
docs/README.md
Normal file
48
docs/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# yaml2pptx 文档
|
||||
|
||||
欢迎使用 yaml2pptx 文档!本目录包含项目的完整文档。
|
||||
|
||||
## 用户文档
|
||||
|
||||
### 快速开始
|
||||
- [用户指南](user-guide.md) - 完整的使用指南
|
||||
|
||||
### 元素类型
|
||||
- [文本元素](elements/text.md) - 文本框和字体配置
|
||||
- [图片元素](elements/image.md) - 图片插入和配置
|
||||
- [形状元素](elements/shape.md) - 几何形状绘制
|
||||
- [表格元素](elements/table.md) - 表格创建和样式
|
||||
|
||||
### 模板系统
|
||||
- [内联模板](templates/inline.md) - 在源文件中定义模板
|
||||
- [外部模板库](templates/external-library.md) - 独立的模板库文件
|
||||
- [混合模式](templates/mixing-mode.md) - 模板与自定义元素组合
|
||||
- [条件渲染](templates/condition-rendering.md) - 元素和页面的条件显示
|
||||
|
||||
### 其他主题
|
||||
- [字体主题系统](fonts.md) - 字体配置和主题管理
|
||||
- [示例集合](examples.md) - 实战示例
|
||||
- [故障排查](troubleshooting.md) - 常见问题解决
|
||||
|
||||
### API 参考
|
||||
- [命令行选项](reference/commands.md) - 所有命令和参数
|
||||
- [坐标系统](reference/coordinates.md) - 位置和尺寸单位
|
||||
- [颜色格式](reference/colors.md) - 颜色表示方法
|
||||
- [验证功能](reference/validation.md) - YAML 验证说明
|
||||
|
||||
## 开发文档
|
||||
|
||||
详见 [development/](development/) 目录。
|
||||
|
||||
- [架构设计](development/architecture.md) - 代码结构和技术决策
|
||||
- [模块详解](development/modules/) - 各模块详细说明
|
||||
- [字体系统](development/font-system.md) - 字体解析实现
|
||||
- [作用域系统](development/scope-system.md) - 字体作用域规则
|
||||
- [开发规范](development/development-guide.md) - 编码规范和约定
|
||||
- [扩展指南](development/extending.md) - 添加新功能
|
||||
- [测试规范](development/testing.md) - 测试指南
|
||||
- [维护指南](development/maintenance.md) - 代码审查和优化
|
||||
|
||||
---
|
||||
|
||||
返回 [项目主页](../README.md)
|
||||
193
docs/development/architecture.md
Normal file
193
docs/development/architecture.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 架构设计
|
||||
|
||||
本文档说明 yaml2pptx 项目的代码结构和技术决策。
|
||||
|
||||
## 项目概述
|
||||
|
||||
yaml2pptx 是一个将 YAML 格式的演示文稿源文件转换为 PPTX 文件的工具,支持模板系统和浏览器预览功能。
|
||||
|
||||
## 代码结构
|
||||
|
||||
项目采用模块化架构,按功能职责组织代码:
|
||||
|
||||
```
|
||||
html2pptx/
|
||||
├── yaml2pptx.py (200 行) # 入口脚本,CLI + main 函数
|
||||
├── utils.py (74 行) # 工具函数(日志、颜色转换)
|
||||
├── core/ # 核心领域模型
|
||||
│ ├── elements.py (200 行) # 元素抽象层(dataclass + validate)
|
||||
│ ├── template.py (191 行) # 模板系统
|
||||
│ ├── condition_evaluator.py # 条件表达式评估器
|
||||
│ └── presentation.py (91 行) # 演示文稿类
|
||||
├── loaders/ # 数据加载层
|
||||
│ └── yaml_loader.py (113 行) # YAML 加载和验证
|
||||
├── validators/ # 验证层
|
||||
│ ├── __init__.py # 导出主验证器
|
||||
│ ├── result.py (70 行) # 验证结果数据结构
|
||||
│ ├── validator.py (150 行) # 主验证器
|
||||
│ ├── geometry.py (120 行) # 几何验证器
|
||||
│ └── resource.py (110 行) # 资源验证器
|
||||
├── renderers/ # 渲染层
|
||||
│ ├── pptx_renderer.py (292 行) # PPTX 渲染器
|
||||
│ └── html_renderer.py (172 行) # HTML 渲染器(预览)
|
||||
└── preview/ # 预览功能
|
||||
└── server.py (244 行) # Flask 服务器 + 文件监听
|
||||
```
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
yaml2pptx.py (入口)
|
||||
↓
|
||||
├─→ utils (工具函数)
|
||||
├─→ loaders.yaml_loader (YAML 加载)
|
||||
├─→ validators.validator (验证器)
|
||||
│ ↓
|
||||
│ ├─→ validators.result (验证结果)
|
||||
│ ├─→ validators.geometry (几何验证)
|
||||
│ ├─→ validators.resource (资源验证)
|
||||
│ └─→ core.elements (元素验证)
|
||||
├─→ core.presentation (演示文稿)
|
||||
│ ↓
|
||||
│ ├─→ core.template (模板)
|
||||
│ └─→ core.elements (元素)
|
||||
├─→ renderers.pptx_renderer (PPTX 生成)
|
||||
│ ↓
|
||||
│ └─→ core.elements
|
||||
└─→ preview.server (预览服务)
|
||||
↓
|
||||
└─→ renderers.html_renderer
|
||||
↓
|
||||
└─→ core.elements
|
||||
```
|
||||
|
||||
### 依赖原则
|
||||
|
||||
- **单向依赖**:入口 → 验证/渲染 → 核心 ← 加载
|
||||
- **无循环依赖**
|
||||
- **核心层不依赖**其他业务模块
|
||||
- **验证层可以调用**核心层的元素验证方法
|
||||
|
||||
## 模块概览
|
||||
|
||||
### 入口层 (yaml2pptx.py)
|
||||
|
||||
- **职责**:CLI 参数解析、流程编排
|
||||
- **包含**:
|
||||
- `/// script` 依赖声明
|
||||
- `parse_args()` - 命令行参数解析
|
||||
- `main()` - 主流程编排
|
||||
- `handle_convert()` - 转换流程
|
||||
- **不包含**:业务逻辑、数据处理
|
||||
|
||||
### 工具层 (utils/)
|
||||
|
||||
- **职责**:通用工具函数
|
||||
- **包含**:
|
||||
- 日志函数:`log_info()`, `log_success()`, `log_error()`, `log_progress()`
|
||||
- 颜色转换:`hex_to_rgb()`, `validate_color()`
|
||||
|
||||
### 加载层 (loaders/)
|
||||
|
||||
- **职责**:YAML 文件加载和验证
|
||||
- **包含**:
|
||||
- `YAMLError` - 自定义异常
|
||||
- `load_yaml_file()` - 加载 YAML 文件
|
||||
- `validate_presentation_yaml()` - 验证演示文稿结构
|
||||
|
||||
### 核心层 (core/)
|
||||
|
||||
#### elements.py
|
||||
|
||||
- **职责**:定义元素数据类和工厂函数
|
||||
- **包含**:
|
||||
- `FontConfig` - 字体配置数据类
|
||||
- `TextElement`, `ImageElement`, `ShapeElement`, `TableElement`
|
||||
- `create_element()` - 元素工厂函数
|
||||
|
||||
#### template.py
|
||||
|
||||
- **职责**:模板加载和变量解析
|
||||
- **包含**:
|
||||
- `Template` 类
|
||||
- 变量解析:`resolve_value()`, `resolve_element()`
|
||||
- 条件渲染:`evaluate_condition()`
|
||||
- 模板渲染:`render()`
|
||||
|
||||
#### condition_evaluator.py
|
||||
|
||||
- **职责**:安全地评估条件表达式
|
||||
- **包含**:
|
||||
- `ConditionEvaluator` 类
|
||||
- 使用 simpleeval 库进行安全评估
|
||||
|
||||
#### presentation.py
|
||||
|
||||
- **职责**:演示文稿管理和幻灯片渲染
|
||||
- **包含**:
|
||||
- `Presentation` 类
|
||||
- 内联模板存储和查找
|
||||
- 幻灯片渲染:`render_slide()`
|
||||
|
||||
### 验证层 (validators/)
|
||||
|
||||
- **职责**:YAML 文件验证
|
||||
- **包含**:
|
||||
- `ValidationIssue`, `ValidationResult` - 验证结果结构
|
||||
- `GeometryValidator` - 几何验证器
|
||||
- `ResourceValidator` - 资源验证器
|
||||
- `Validator` - 主验证器
|
||||
|
||||
### 渲染层 (renderers/)
|
||||
|
||||
#### pptx_renderer.py
|
||||
|
||||
- **职责**:PPTX 文件生成
|
||||
- **包含**:
|
||||
- `PptxGenerator` 类
|
||||
- 渲染方法:`_render_text()`, `_render_image()`, `_render_shape()`, `_render_table()`
|
||||
|
||||
#### html_renderer.py
|
||||
|
||||
- **职责**:HTML 预览渲染
|
||||
- **包含**:
|
||||
- `HtmlRenderer` 类
|
||||
- 渲染方法:`render_text()`, `render_image()`, `render_shape()`, `render_table()`
|
||||
|
||||
### 预览层 (preview/)
|
||||
|
||||
- **职责**:浏览器预览和热重载
|
||||
- **包含**:
|
||||
- Flask 应用:`create_flask_app()`
|
||||
- 文件监听:`YAMLChangeHandler`
|
||||
- 预览服务器:`start_preview_server()`
|
||||
|
||||
## 技术决策概要
|
||||
|
||||
### 1. 元素抽象层使用 dataclass
|
||||
|
||||
- **理由**:简洁性、类型提示、创建时验证
|
||||
- **实现**:`@dataclass` 装饰器 + `__post_init__` 验证
|
||||
|
||||
### 2. 创建时验证
|
||||
|
||||
- **理由**:尽早失败、清晰错误位置
|
||||
- **实现**:在 `__post_init__` 中进行验证
|
||||
|
||||
### 3. 验证职责分层
|
||||
|
||||
- **元素级验证**:放在元素类本身
|
||||
- **系统级验证**:放在独立的验证器模块
|
||||
|
||||
### 4. 模板系统架构
|
||||
|
||||
- **内联模板**:在源文件中定义,适合简单场景
|
||||
- **外部模板库**:独立文件,适合复用和共享
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [模块详解](modules/) - 各模块详细说明
|
||||
- [开发规范](development-guide.md) - 编码规范和约定
|
||||
- [扩展指南](extending.md) - 添加新功能
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
132
docs/development/development-guide.md
Normal file
132
docs/development/development-guide.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# 开发规范
|
||||
|
||||
本文档说明 yaml2pptx 项目的开发规范和约定。
|
||||
|
||||
## Python 环境
|
||||
|
||||
### 必须使用 uv 运行脚本
|
||||
|
||||
```bash
|
||||
# 正确
|
||||
uv run yaml2pptx.py input.yaml output.pptx
|
||||
|
||||
# 错误 - 严禁直接使用主机环境的 python
|
||||
python yaml2pptx.py input.yaml output.pptx
|
||||
```
|
||||
|
||||
### 依赖管理
|
||||
|
||||
- 所有依赖在 `pyproject.toml` 的 `[project.dependencies]` 中声明
|
||||
- uv 会自动安装依赖,无需手动 `pip install`
|
||||
|
||||
## 命令行接口
|
||||
|
||||
### 子命令架构
|
||||
|
||||
```bash
|
||||
# check - 验证 YAML 文件
|
||||
uv run yaml2pptx.py check <input> [--template <path>]
|
||||
|
||||
# convert - 转换为 PPTX
|
||||
uv run yaml2pptx.py convert <input> [output] [--template <path>] [--skip-validation] [--force]
|
||||
|
||||
# preview - 启动预览服务器
|
||||
uv run yaml2pptx.py preview <input> [--template <path>] [--port <port>] [--host <host>] [--no-browser]
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
- `--template`:指定模板库文件路径
|
||||
- `--skip-validation`:convert 专用,跳过自动验证
|
||||
- `--force/-f`:convert 专用,强制覆盖已存在文件
|
||||
- `--port`:preview 专用,指定端口
|
||||
- `--host`:preview 专用,指定主机地址
|
||||
- `--no-browser`:preview 专用,不自动打开浏览器
|
||||
|
||||
## 文件组织
|
||||
|
||||
### 代码文件
|
||||
|
||||
- 每个模块文件控制在 150-300 行
|
||||
- 入口脚本约 200 行
|
||||
- 使用有意义的文件名和目录结构
|
||||
|
||||
### 测试文件
|
||||
|
||||
- 所有测试文件、临时文件必须放在 `temp/` 目录下
|
||||
- 不污染项目根目录
|
||||
|
||||
## 代码风格
|
||||
|
||||
### 导入顺序
|
||||
|
||||
```python
|
||||
# 1. 标准库
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 2. 第三方库
|
||||
import yaml
|
||||
from pptx import Presentation
|
||||
|
||||
# 3. 本地模块
|
||||
from utils import log_info
|
||||
from core.elements import TextElement
|
||||
```
|
||||
|
||||
### 文档字符串
|
||||
|
||||
- 每个模块必须有模块级文档字符串
|
||||
- 每个类和函数必须有文档字符串
|
||||
- 使用中文编写注释和文档
|
||||
|
||||
### 命名规范
|
||||
|
||||
- **模块名**:小写 + 下划线(如 `yaml_loader.py`)
|
||||
- **类名**:大驼峰(如 `TextElement`)
|
||||
- **函数名**:小写 + 下划线(如 `load_yaml_file()`)
|
||||
- **常量**:大写 + 下划线(如 `HTML_TEMPLATE`)
|
||||
|
||||
## 项目约束
|
||||
|
||||
1. **面向中文开发者**:注释、文档、错误消息使用中文
|
||||
2. **使用 uv 运行**:严禁直接使用主机环境的 python
|
||||
3. **测试文件隔离**:所有测试文件放在 `temp/` 目录
|
||||
4. **不污染主机环境**:不修改主机的 Python 配置
|
||||
|
||||
## 验证容忍度
|
||||
|
||||
几何验证时,允许 0.1 英寸的容忍度:
|
||||
|
||||
```python
|
||||
TOLERANCE = 0.1 # 英寸
|
||||
|
||||
if right > slide_width + TOLERANCE:
|
||||
# 报告 WARNING
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 模块职责
|
||||
|
||||
- 每个模块职责明确,单一功能
|
||||
- 模块间通过清晰接口通信
|
||||
- 避免循环依赖
|
||||
|
||||
### 错误处理
|
||||
|
||||
- 使用自定义异常类(如 `YAMLError`)
|
||||
- 提供清晰的错误信息
|
||||
- 包含错误位置(文件、行号、元素)
|
||||
|
||||
### 日志
|
||||
|
||||
- 使用统一的日志函数(`log_info()`, `log_success()`, `log_error()`)
|
||||
- 显示友好的中文错误信息
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [架构设计](architecture.md) - 代码结构
|
||||
- [扩展指南](extending.md) - 添加新功能
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
280
docs/development/extending.md
Normal file
280
docs/development/extending.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# 扩展指南
|
||||
|
||||
本文档说明如何扩展 yaml2pptx 的功能。
|
||||
|
||||
## 添加新元素类型
|
||||
|
||||
假设要添加 `VideoElement`:
|
||||
|
||||
### 1. 在 core/elements.py 中定义数据类
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
class VideoElement:
|
||||
type: str = 'video'
|
||||
src: str = ''
|
||||
box: list = field(default_factory=lambda: [1, 1, 4, 3])
|
||||
autoplay: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.src:
|
||||
raise ValueError("视频元素必须指定 src")
|
||||
if len(self.box) != 4:
|
||||
raise ValueError("box 必须包含 4 个数字")
|
||||
```
|
||||
|
||||
### 2. 在工厂函数中添加分支
|
||||
|
||||
```python
|
||||
def create_element(elem_dict: dict):
|
||||
elem_type = elem_dict.get('type')
|
||||
|
||||
if elem_type == 'text':
|
||||
return TextElement(**elem_dict)
|
||||
elif elem_type == 'image':
|
||||
return ImageElement(**elem_dict)
|
||||
# ... 其他类型 ...
|
||||
elif elem_type == 'video':
|
||||
return VideoElement(**elem_dict)
|
||||
else:
|
||||
raise ValueError(f"Unknown element type: {elem_type}")
|
||||
```
|
||||
|
||||
### 3. 在 PptxGenerator 中实现渲染方法
|
||||
|
||||
```python
|
||||
def _render_element(self, slide, elem, base_path):
|
||||
# ... 其他类型 ...
|
||||
elif isinstance(elem, VideoElement):
|
||||
self._render_video(slide, elem, base_path)
|
||||
|
||||
def _render_video(self, slide, elem: VideoElement, base_path):
|
||||
"""实现视频渲染逻辑"""
|
||||
movie = slide.shapes.add_movie(
|
||||
str(Path(base_path) / elem.src),
|
||||
left=Inches(elem.box[0]),
|
||||
top=Inches(elem.box[1]),
|
||||
width=Inches(elem.box[2]),
|
||||
height=Inches(elem.box[3])
|
||||
)
|
||||
|
||||
if elem.autoplay:
|
||||
movie.click.action = pp.action.Action(pyppote.xmlns.namespace('p').MSO_ANIMATION_VIDEO_CLICK)
|
||||
```
|
||||
|
||||
### 4. 在 HtmlRenderer 中实现渲染方法
|
||||
|
||||
```python
|
||||
def render_slide(self, slide_data, index, base_path=None):
|
||||
elements_html = ""
|
||||
|
||||
for elem in slide_data:
|
||||
# ... 其他类型 ...
|
||||
elif isinstance(elem, VideoElement):
|
||||
elements_html += self.render_video(elem, base_path)
|
||||
|
||||
return self.SLIDE_TEMPLATE.format(content=elements_html)
|
||||
|
||||
def render_video(self, elem: VideoElement, base_path):
|
||||
"""实现 HTML 视频渲染"""
|
||||
src_path = str(Path(base_path) / elem.src) if base_path else elem.src
|
||||
autoplay_attr = "autoplay" if elem.autoplay else ""
|
||||
|
||||
return f'''
|
||||
<video src="{src_path}" {autoplay_attr}
|
||||
style="position:absolute; left:{elem.box[0]*96}px; top:{elem.box[1]*96}px;
|
||||
width:{elem.box[2]*96}px; height:{elem.box[3]*96}px;">
|
||||
</video>
|
||||
'''
|
||||
```
|
||||
|
||||
### 5. 更新验证器
|
||||
|
||||
如果需要验证视频文件:
|
||||
|
||||
```python
|
||||
# validators/resource.py
|
||||
def validate_video(self, src, slide_index, elem_index):
|
||||
"""检查视频文件是否存在"""
|
||||
video_path = self.base_dir / src
|
||||
if not video_path.exists():
|
||||
return ValidationIssue(
|
||||
level="ERROR",
|
||||
message=f"视频文件不存在: {src}",
|
||||
location=f"[幻灯片 {slide_index + 1}, 元素 {elem_index + 1}]",
|
||||
code="VIDEO_FILE_NOT_FOUND"
|
||||
)
|
||||
return None
|
||||
```
|
||||
|
||||
## 添加新渲染器
|
||||
|
||||
假设要添加 PDF 渲染器:
|
||||
|
||||
### 1. 创建 renderers/pdf_renderer.py
|
||||
|
||||
```python
|
||||
from core.elements import TextElement, ImageElement, ShapeElement, TableElement
|
||||
|
||||
class PdfRenderer:
|
||||
def __init__(self, size="16:9"):
|
||||
# 初始化 PDF 库
|
||||
self.size = size
|
||||
# ...
|
||||
|
||||
def add_slide(self, slide_data, base_path=None):
|
||||
"""添加页面"""
|
||||
# 实现...
|
||||
pass
|
||||
|
||||
def _render_element(self, page, elem, base_path):
|
||||
"""渲染元素到 PDF 页面"""
|
||||
if isinstance(elem, TextElement):
|
||||
self._render_text(page, elem)
|
||||
elif isinstance(elem, ImageElement):
|
||||
self._render_image(page, elem, base_path)
|
||||
elif isinstance(elem, ShapeElement):
|
||||
self._render_shape(page, elem)
|
||||
elif isinstance(elem, TableElement):
|
||||
self._render_table(page, elem)
|
||||
|
||||
def _render_text(self, page, elem):
|
||||
"""渲染文本到 PDF"""
|
||||
# 实现...
|
||||
pass
|
||||
|
||||
def _render_image(self, page, elem, base_path):
|
||||
"""渲染图片到 PDF"""
|
||||
# 实现...
|
||||
pass
|
||||
|
||||
def _render_shape(self, page, elem):
|
||||
"""渲染形状到 PDF"""
|
||||
# 实现...
|
||||
pass
|
||||
|
||||
def _render_table(self, page, elem):
|
||||
"""渲染表格到 PDF"""
|
||||
# 实现...
|
||||
pass
|
||||
|
||||
def save(self, output_path):
|
||||
"""保存 PDF 文件"""
|
||||
# 实现...
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. 在 yaml2pptx.py 中添加 PDF 模式
|
||||
|
||||
```python
|
||||
from renderers.pdf_renderer import PdfRenderer
|
||||
|
||||
def main():
|
||||
# ... 解析参数 ...
|
||||
if args.pdf:
|
||||
# PDF 生成模式
|
||||
generator = PdfRenderer(size=args.size)
|
||||
# ... 渲染逻辑
|
||||
```
|
||||
|
||||
### 3. 添加命令行参数
|
||||
|
||||
```python
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='YAML to PPTX converter')
|
||||
subparsers = parser.add_subparsers(dest='command', help='子命令')
|
||||
|
||||
# ... 其他命令 ...
|
||||
|
||||
# PDF 命令
|
||||
pdf_parser = subparsers.add_parser('pdf', help='生成 PDF')
|
||||
pdf_parser.add_argument('input', help='输入的 YAML 文件')
|
||||
pdf_parser.add_argument('output', help='输出的 PDF 文件', nargs='?')
|
||||
pdf_parser.add_argument('--template', help='模板库文件路径')
|
||||
pdf_parser.add_argument('--size', default='16:9', choices=['16:9', '4:3'])
|
||||
|
||||
return parser.parse_args()
|
||||
```
|
||||
|
||||
## 添加新的验证规则
|
||||
|
||||
### 1. 在 validators/ 中创建新的验证器
|
||||
|
||||
```python
|
||||
# validators/custom.py
|
||||
|
||||
class CustomValidator:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate(self, presentation):
|
||||
"""执行自定义验证"""
|
||||
issues = []
|
||||
# 验证逻辑
|
||||
return issues
|
||||
```
|
||||
|
||||
### 2. 在主验证器中集成
|
||||
|
||||
```python
|
||||
# validators/validator.py
|
||||
|
||||
class Validator:
|
||||
def __init__(self, ...):
|
||||
# ...
|
||||
self.custom_validator = CustomValidator()
|
||||
|
||||
def validate_presentation(self, presentation):
|
||||
# ...
|
||||
# 调用自定义验证器
|
||||
custom_issues = self.custom_validator.validate(presentation)
|
||||
result.infos.extend(custom_issues)
|
||||
```
|
||||
|
||||
## 测试新功能
|
||||
|
||||
### 1. 创建测试文件
|
||||
|
||||
```python
|
||||
# tests/unit/test_video_element.py
|
||||
|
||||
import pytest
|
||||
from core.elements import VideoElement, create_element
|
||||
|
||||
def test_create_video_element():
|
||||
elem_dict = {
|
||||
'type': 'video',
|
||||
'src': 'test.mp4',
|
||||
'box': [1, 1, 4, 3],
|
||||
'autoplay': True
|
||||
}
|
||||
elem = create_element(elem_dict)
|
||||
assert isinstance(elem, VideoElement)
|
||||
assert elem.autoplay is True
|
||||
|
||||
def test_video_element_without_src():
|
||||
with pytest.raises(ValueError, match="必须指定 src"):
|
||||
VideoElement(src='', box=[1, 1, 4, 3])
|
||||
```
|
||||
|
||||
### 2. 运行测试
|
||||
|
||||
```bash
|
||||
uv run pytest tests/unit/test_video_element.py -v
|
||||
```
|
||||
|
||||
## 提交变更
|
||||
|
||||
1. 更新相关文档
|
||||
2. 添加测试
|
||||
3. 运行完整测试套件
|
||||
4. 提交 Pull Request
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [架构设计](architecture.md) - 代码结构
|
||||
- [Elements 模块](modules/elements.md) - 元素抽象层
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
144
docs/development/font-system.md
Normal file
144
docs/development/font-system.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 字体系统实现
|
||||
|
||||
字体解析和继承链处理的实现。
|
||||
|
||||
## 职责
|
||||
|
||||
- 字体引用解析
|
||||
- 继承链处理
|
||||
- 预设类别映射
|
||||
- 循环引用检测
|
||||
|
||||
## PRESET_FONT_MAPPING
|
||||
|
||||
预设字体类别映射常量:
|
||||
|
||||
```python
|
||||
PRESET_FONT_MAPPING = {
|
||||
'sans': 'Arial',
|
||||
'serif': 'Times New Roman',
|
||||
'mono': 'Courier New',
|
||||
'cjk-sans': 'Microsoft YaHei',
|
||||
'cjk-serif': 'SimSun',
|
||||
}
|
||||
```
|
||||
|
||||
## FontResolver 类
|
||||
|
||||
### 初始化
|
||||
|
||||
```python
|
||||
class FontResolver:
|
||||
def __init__(self, fonts, fonts_default, scope="document", template_fonts=None):
|
||||
"""
|
||||
Args:
|
||||
fonts: 当前作用域的字体字典
|
||||
fonts_default: 当前作用域的默认字体
|
||||
scope: 作用域标识 ("document" 或 "template")
|
||||
template_fonts: 模板库字体字典(仅文档作用域需要)
|
||||
"""
|
||||
```
|
||||
|
||||
### resolve_font()
|
||||
|
||||
解析字体配置的主入口:
|
||||
|
||||
```python
|
||||
def resolve_font(self, font_config):
|
||||
"""解析字体配置(主入口)"""
|
||||
if font_config is None:
|
||||
return self._get_default_config(set())
|
||||
|
||||
if isinstance(font_config, str):
|
||||
# "@xxx" 格式
|
||||
return self._resolve_reference(font_config.strip('@'), set())
|
||||
|
||||
if isinstance(font_config, dict):
|
||||
if 'parent' in font_config:
|
||||
# {parent: "@xxx", ...} 格式
|
||||
return self._resolve_font_dict(font_config, set())
|
||||
else:
|
||||
# 独立定义
|
||||
return FontConfig(**font_config)
|
||||
```
|
||||
|
||||
### _resolve_reference()
|
||||
|
||||
解析字体引用:
|
||||
|
||||
```python
|
||||
def _resolve_reference(self, reference, visited):
|
||||
"""解析字体引用"""
|
||||
if reference in visited:
|
||||
raise ValueError(f"检测到字体引用循环: {' -> '.join(visited + [reference])}")
|
||||
|
||||
visited.add(reference)
|
||||
|
||||
# 检查作用域
|
||||
if reference in self.fonts:
|
||||
font_data = self.fonts[reference]
|
||||
elif self.template_fonts and reference in self.template_fonts:
|
||||
font_data = self.template_fonts[reference]
|
||||
else:
|
||||
raise ValueError(f"字体配置不存在: @{reference}")
|
||||
|
||||
return self._build_font_config(font_data, visited)
|
||||
```
|
||||
|
||||
### _resolve_font_dict()
|
||||
|
||||
解析字体字典:
|
||||
|
||||
```python
|
||||
def _resolve_font_dict(self, font_dict, visited):
|
||||
"""解析字体字典"""
|
||||
parent_ref = font_dict.get('parent', '').strip('@')
|
||||
if parent_ref:
|
||||
# 先解析父级
|
||||
parent_config = self._resolve_reference(parent_ref, visited.copy())
|
||||
# 合并当前属性
|
||||
return self._merge_with_parent(font_dict, parent_config)
|
||||
|
||||
# 独立定义
|
||||
return FontConfig(**font_dict)
|
||||
```
|
||||
|
||||
## 字体引用解析逻辑
|
||||
|
||||
1. 如果 `font_config` 为 None,使用 `fonts_default`
|
||||
2. 如果是字符串(`"@xxx"`),解析为整体引用
|
||||
3. 如果是字典且包含 `parent`,先解析 `parent` 再覆盖当前属性
|
||||
4. 如果是字典且不包含 `parent`,直接使用字典属性
|
||||
5. 未定义的属性从 `fonts_default` 继承
|
||||
6. 如果 `family` 是预设类别,映射到具体字体名称
|
||||
7. 返回完整的 `FontConfig` 对象
|
||||
|
||||
## 继承链
|
||||
|
||||
属性继承顺序:
|
||||
|
||||
```
|
||||
parent → 当前属性 → fonts_default → 系统默认
|
||||
```
|
||||
|
||||
## 循环引用检测
|
||||
|
||||
- 维护已访问集合 `visited`
|
||||
- 检测重复引用
|
||||
- 最大引用深度限制:10 层
|
||||
|
||||
## 预设类别映射
|
||||
|
||||
在 `family` 字段中识别预设类别名称:
|
||||
|
||||
```python
|
||||
if family in PRESET_FONT_MAPPING:
|
||||
family = PRESET_FONT_MAPPING[family]
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [作用域系统](scope-system.md) - 字体作用域规则
|
||||
- [字体主题系统](../../fonts.md) - 用户指南
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
172
docs/development/maintenance.md
Normal file
172
docs/development/maintenance.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 维护指南
|
||||
|
||||
本文档提供 yaml2pptx 项目的维护指南。
|
||||
|
||||
## 代码审查要点
|
||||
|
||||
在审查代码时,检查以下要点:
|
||||
|
||||
### 结构和设计
|
||||
|
||||
- [ ] 模块文件大小合理(150-300 行)
|
||||
- [ ] 无循环依赖
|
||||
- [ ] 单一职责原则
|
||||
- [ ] 清晰的接口和抽象
|
||||
|
||||
### 代码质量
|
||||
|
||||
- [ ] 所有类和函数有文档字符串
|
||||
- [ ] 使用中文注释
|
||||
- [ ] 元素验证在 `__post_init__` 中完成
|
||||
- [ ] 导入语句按标准库、第三方库、本地模块排序
|
||||
|
||||
### 测试
|
||||
|
||||
- [ ] 有相应的测试用例
|
||||
- [ ] 测试覆盖率足够
|
||||
- [ ] 测试文件在 `tests/` 目录下
|
||||
|
||||
### 约束检查
|
||||
|
||||
- [ ] 使用 `uv run` 运行脚本
|
||||
- [ ] 测试文件在 `temp/` 目录
|
||||
- [ ] 面向中文开发者
|
||||
- [ ] 不污染主机环境
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 模板缓存
|
||||
|
||||
Presentation 类已实现模板缓存,避免重复加载:
|
||||
|
||||
```python
|
||||
def get_template(self, template_name):
|
||||
if template_name not in self.template_cache:
|
||||
self.template_cache[template_name] = self._load_template(template_name)
|
||||
return self.template_cache[template_name]
|
||||
```
|
||||
|
||||
### 2. 元素验证
|
||||
|
||||
只在创建时验证一次,渲染时不再验证:
|
||||
|
||||
```python
|
||||
# 创建时验证
|
||||
elem = TextElement(**elem_dict) # __post_init__ 验证
|
||||
|
||||
# 渲染时直接使用,不重复验证
|
||||
self._render_text(slide, elem)
|
||||
```
|
||||
|
||||
### 3. 文件监听
|
||||
|
||||
预览模式使用 watchdog 高效监听文件变化:
|
||||
|
||||
```python
|
||||
class YAMLChangeHandler(FileSystemEventHandler):
|
||||
def on_modified(self, event):
|
||||
if event.src_path.endswith('.yaml'):
|
||||
# 重新加载和渲染
|
||||
```
|
||||
|
||||
## 代码质量工具
|
||||
|
||||
### 格式化
|
||||
|
||||
使用 Black 格式化 Python 代码:
|
||||
|
||||
```bash
|
||||
uv run black .
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
使用 Pylint 检查代码质量:
|
||||
|
||||
```bash
|
||||
uv run pylint core/ loaders/ renderers/ validators/
|
||||
```
|
||||
|
||||
### 类型检查
|
||||
|
||||
使用 mypy 进行类型检查:
|
||||
|
||||
```bash
|
||||
uv run mypy core/
|
||||
```
|
||||
|
||||
## 常见维护任务
|
||||
|
||||
### 添加新的依赖
|
||||
|
||||
1. 在 `pyproject.toml` 中添加依赖
|
||||
2. 更新 `README.md` 的依赖项列表
|
||||
3. 运行测试确保兼容性
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"python-pptx>=0.6.21",
|
||||
"pyyaml>=6.0",
|
||||
"flask>=3.0",
|
||||
"watchdog>=3.0",
|
||||
"new-dependency>=1.0",
|
||||
]
|
||||
```
|
||||
|
||||
### 更新文档
|
||||
|
||||
功能变更后,同步更新以下文档:
|
||||
|
||||
- `README.md` - 用户文档
|
||||
- `README_DEV.md` - 开发文档
|
||||
- 相关的 `docs/` 子文档
|
||||
|
||||
### 版本发布
|
||||
|
||||
1. 更新版本号(在 pyproject.toml 中)
|
||||
2. 更新 CHANGELOG
|
||||
3. 创建 git tag
|
||||
4. 发布到 PyPI(如果需要)
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 使用预览模式
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py preview temp/test.yaml
|
||||
```
|
||||
|
||||
### 查看详细错误
|
||||
|
||||
```python
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
### 使用 Python 调试器
|
||||
|
||||
```bash
|
||||
uv run python -m pdb yaml2pptx.py convert test.yaml
|
||||
```
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **导入错误**:检查是否使用 `uv run`
|
||||
2. **依赖缺失**:运行 `uv pip install -e .[dev]`
|
||||
3. **测试失败**:检查 `temp/` 目录权限
|
||||
|
||||
### 获取帮助
|
||||
|
||||
- 查看项目 Issues
|
||||
- 查看 `README.md` 和 `README_DEV.md`
|
||||
- 联系维护者
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [开发规范](development-guide.md) - 编码规范
|
||||
- [测试规范](testing.md) - 测试指南
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
18
docs/development/modules/_index.md
Normal file
18
docs/development/modules/_index.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 模块详解
|
||||
|
||||
本目录包含各代码模块的详细说明。
|
||||
|
||||
## 核心模块
|
||||
|
||||
- [Elements](elements.md) - 元素抽象层
|
||||
- [Template](template.md) - 模板系统
|
||||
- [Validators](validators.md) - 验证器
|
||||
- [Renderers](renderers.md) - 渲染器
|
||||
- [Loaders](loaders.md) - 加载器
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [架构设计](../architecture.md) - 整体代码结构
|
||||
- [开发规范](../development-guide.md) - 编码规范
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
137
docs/development/modules/elements.md
Normal file
137
docs/development/modules/elements.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Elements 模块
|
||||
|
||||
`core/elements.py` 定义了所有元素类型的数据类和工厂函数。
|
||||
|
||||
## 职责
|
||||
|
||||
- 定义元素数据类(使用 `@dataclass`)
|
||||
- 在创建时进行元素级验证
|
||||
- 提供元素工厂函数
|
||||
|
||||
## 包含的内容
|
||||
|
||||
### FontConfig 类
|
||||
|
||||
字体配置数据类,支持所有字体属性:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class FontConfig:
|
||||
parent: Optional[str] = None
|
||||
family: Optional[str] = None
|
||||
size: Optional[int] = None
|
||||
bold: Optional[bool] = None
|
||||
italic: Optional[bool] = None
|
||||
underline: Optional[bool] = None
|
||||
strikethrough: Optional[bool] = None
|
||||
color: Optional[str] = None
|
||||
align: Optional[str] = None
|
||||
line_spacing: Optional[float] = None
|
||||
space_before: Optional[float] = None
|
||||
space_after: Optional[float] = None
|
||||
baseline: Optional[str] = None
|
||||
caps: Optional[str] = None
|
||||
```
|
||||
|
||||
在 `__post_init__` 中验证 baseline 和 caps 枚举值。
|
||||
|
||||
### 元素类
|
||||
|
||||
#### TextElement
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TextElement:
|
||||
type: str = 'text'
|
||||
content: str = ''
|
||||
box: list = field(default_factory=lambda: [1, 1, 8, 1])
|
||||
font: FontConfig | str | dict = field(default_factory=dict)
|
||||
```
|
||||
|
||||
#### ImageElement
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ImageElement:
|
||||
type: str = 'image'
|
||||
src: str = ''
|
||||
box: list = field(default_factory=lambda: [1, 1, 4, 3])
|
||||
```
|
||||
|
||||
#### ShapeElement
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ShapeElement:
|
||||
type: str = 'shape'
|
||||
box: list = field(default_factory=lambda: [1, 1, 4, 2])
|
||||
shape: str = 'rectangle'
|
||||
fill: str = '#000000'
|
||||
line: dict = field(default_factory=dict)
|
||||
```
|
||||
|
||||
#### TableElement
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TableElement:
|
||||
type: str = 'table'
|
||||
position: list = field(default_factory=lambda: [0, 0])
|
||||
col_widths: list = field(default_factory=list)
|
||||
data: list = field(default_factory=list)
|
||||
font: FontConfig | str | dict = field(default_factory=dict)
|
||||
header_font: FontConfig | str | dict = field(default_factory=dict)
|
||||
style: dict = field(default_factory=dict)
|
||||
```
|
||||
|
||||
### create_element() 工厂函数
|
||||
|
||||
```python
|
||||
def create_element(elem_dict: dict):
|
||||
elem_type = elem_dict.get('type')
|
||||
|
||||
if elem_type == 'text':
|
||||
return TextElement(**elem_dict)
|
||||
elif elem_type == 'image':
|
||||
return ImageElement(**elem_dict)
|
||||
elif elem_type == 'shape':
|
||||
return ShapeElement(**elem_dict)
|
||||
elif elem_type == 'table':
|
||||
return TableElement(**elem_dict)
|
||||
else:
|
||||
raise ValueError(f"Unknown element type: {elem_type}")
|
||||
```
|
||||
|
||||
自动将字典形式的 font 转换为 FontConfig 对象。
|
||||
|
||||
## 创建时验证
|
||||
|
||||
每个元素类在 `__post_init__` 中进行验证:
|
||||
|
||||
```python
|
||||
def __post_init__(self):
|
||||
# 检查必需字段
|
||||
if len(self.box) != 4:
|
||||
raise ValueError("box 必须包含 4 个数字")
|
||||
|
||||
# 验证颜色格式
|
||||
if self.fill and not _is_valid_color(self.fill):
|
||||
raise ValueError(f"无效的颜色格式: {self.fill}")
|
||||
```
|
||||
|
||||
## 元素级验证职责
|
||||
|
||||
- 必需字段检查
|
||||
- 数据类型检查
|
||||
- 值的有效性检查:
|
||||
- 颜色格式验证
|
||||
- 字体大小合理性
|
||||
- 枚举值检查(如形状类型)
|
||||
- 表格数据一致性
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Template 模块](template.md) - 模板系统
|
||||
- [Validators 模块](validators.md) - 验证器详解
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
135
docs/development/modules/loaders.md
Normal file
135
docs/development/modules/loaders.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Loaders 模块
|
||||
|
||||
`loaders/yaml_loader.py` 负责 YAML 文件加载和验证。
|
||||
|
||||
## 职责
|
||||
|
||||
- YAML 文件加载
|
||||
- 演示文稿结构验证
|
||||
- 内联模板验证
|
||||
- 外部模板验证
|
||||
|
||||
## 包含的内容
|
||||
|
||||
### YAMLError
|
||||
|
||||
自定义异常类:
|
||||
|
||||
```python
|
||||
class YAMLError(Exception):
|
||||
"""YAML 相关错误"""
|
||||
pass
|
||||
```
|
||||
|
||||
### load_yaml_file()
|
||||
|
||||
加载 YAML 文件:
|
||||
|
||||
```python
|
||||
def load_yaml_file(file_path):
|
||||
"""加载 YAML 文件并返回解析后的数据"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data
|
||||
except yaml.YAMLError as e:
|
||||
raise YAMLError(f"YAML 语法错误: {e}")
|
||||
except FileNotFoundError:
|
||||
raise YAMLError(f"文件不存在: {file_path}")
|
||||
```
|
||||
|
||||
### validate_presentation_yaml()
|
||||
|
||||
验证演示文稿结构:
|
||||
|
||||
```python
|
||||
def validate_presentation_yaml(data, file_path):
|
||||
"""验证演示文稿 YAML 结构"""
|
||||
# 检查必需字段
|
||||
if 'metadata' not in data:
|
||||
raise YAMLError(f"缺少 metadata 字段: {file_path}")
|
||||
|
||||
if 'slides' not in data:
|
||||
raise YAMLError(f"缺少 slides 字段: {file_path}")
|
||||
|
||||
# 验证 metadata
|
||||
metadata = data['metadata']
|
||||
if 'size' not in metadata:
|
||||
raise YAMLError(f"metadata 缺少 size 字段: {file_path}")
|
||||
|
||||
if metadata['size'] not in ['16:9', '4:3']:
|
||||
raise YAMLError(f"无效的 size 值: {metadata['size']}")
|
||||
|
||||
# 验证内联模板(如果有)
|
||||
if 'templates' in data:
|
||||
validate_templates_yaml(data['templates'])
|
||||
|
||||
# 验证幻灯片
|
||||
for slide in data['slides']:
|
||||
# 验证 enabled 字段
|
||||
if 'enabled' in slide:
|
||||
if not isinstance(slide['enabled'], bool):
|
||||
raise YAMLError(f"enabled 必须是布尔值: {file_path}")
|
||||
```
|
||||
|
||||
### validate_template_yaml()
|
||||
|
||||
验证外部模板结构:
|
||||
|
||||
```python
|
||||
def validate_template_yaml(template_data, template_name):
|
||||
"""验证外部模板结构"""
|
||||
if 'elements' not in template_data:
|
||||
raise YAMLError(f"模板缺少 elements 字段: {template_name}")
|
||||
|
||||
if 'vars' in template_data:
|
||||
for var in template_data['vars']:
|
||||
if 'name' not in var:
|
||||
raise YAMLError(f"变量缺少 name 字段: {template_name}")
|
||||
```
|
||||
|
||||
### validate_templates_yaml()
|
||||
|
||||
验证内联模板结构:
|
||||
|
||||
```python
|
||||
def validate_templates_yaml(templates):
|
||||
"""验证内联模板结构"""
|
||||
if not isinstance(templates, dict):
|
||||
raise YAMLError("templates 必须是字典类型")
|
||||
|
||||
for name, template_data in templates.items():
|
||||
validate_template_yaml(template_data, f"templates.{name}")
|
||||
|
||||
# 检测默认值中引用不存在的变量
|
||||
vars_def = {v['name']: v for v in template_data.get('vars', [])}
|
||||
for var in template_data.get('vars', []):
|
||||
if 'default' in var:
|
||||
# 检查默认值是否引用了不存在的变量
|
||||
# 实现省略...
|
||||
```
|
||||
|
||||
## 内联模板验证
|
||||
|
||||
内联模板验证包括:
|
||||
- 结构验证(是否为字典)
|
||||
- 元素验证(是否有 elements 字段)
|
||||
- 变量定义验证(是否有 name 字段)
|
||||
- 默认值验证(检测循环引用)
|
||||
|
||||
## enabled 字段验证
|
||||
|
||||
验证 `enabled` 字段必须是布尔值:
|
||||
|
||||
```python
|
||||
if 'enabled' in slide:
|
||||
if not isinstance(slide['enabled'], bool):
|
||||
raise YAMLError(f"enabled 必须是布尔值,不能是字符串或条件表达式")
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [架构设计](../architecture.md) - 代码结构
|
||||
- [验证器模块](validators.md) - 验证详解
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
153
docs/development/modules/renderers.md
Normal file
153
docs/development/modules/renderers.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Renderers 模块
|
||||
|
||||
`renderers/` 目录包含 PPTX 和 HTML 渲染器。
|
||||
|
||||
## 模块组成
|
||||
|
||||
### pptx_renderer.py
|
||||
|
||||
PPTX 文件生成器:
|
||||
|
||||
```python
|
||||
class PptxGenerator:
|
||||
def __init__(self, size="16:9"):
|
||||
self.presentation = Presentation()
|
||||
self.slide_width = 10 # 英寸
|
||||
self.slide_height = 5.625 if size == "16:9" else 7.5
|
||||
|
||||
def add_slide(self):
|
||||
"""添加新幻灯片"""
|
||||
slide = self.presentation.slides.add_slide(self.blank_layout)
|
||||
return slide
|
||||
|
||||
def _render_element(self, slide, elem, base_path):
|
||||
"""分发元素到对应的渲染方法"""
|
||||
if isinstance(elem, TextElement):
|
||||
self._render_text(slide, elem)
|
||||
elif isinstance(elem, ImageElement):
|
||||
self._render_image(slide, elem, base_path)
|
||||
# ...
|
||||
|
||||
def _render_text(self, slide, elem):
|
||||
"""渲染文本元素"""
|
||||
# 实现...
|
||||
|
||||
def _render_image(self, slide, elem, base_path):
|
||||
"""渲染图片元素"""
|
||||
# 实现...
|
||||
|
||||
def _render_shape(self, slide, elem):
|
||||
"""渲染形状元素"""
|
||||
# 实现...
|
||||
|
||||
def _render_table(self, slide, elem):
|
||||
"""渲染表格元素"""
|
||||
# 实现...
|
||||
|
||||
def save(self, output_path):
|
||||
"""保存 PPTX 文件"""
|
||||
self.presentation.save(output_path)
|
||||
```
|
||||
|
||||
### html_renderer.py
|
||||
|
||||
HTML 预览渲染器:
|
||||
|
||||
```python
|
||||
class HtmlRenderer:
|
||||
def __init__(self, size="16:9"):
|
||||
self.slide_width = 960 # 96 DPI * 10"
|
||||
self.slide_height = 540 if size == "16:9" else 720
|
||||
|
||||
def render_slide(self, slide_data, index, base_path=None):
|
||||
"""渲染幻灯片为 HTML"""
|
||||
elements_html = ""
|
||||
|
||||
for elem in slide_data:
|
||||
if isinstance(elem, TextElement):
|
||||
elements_html += self.render_text(elem)
|
||||
elif isinstance(elem, ImageElement):
|
||||
elements_html += self.render_image(elem, base_path)
|
||||
# ...
|
||||
|
||||
return self.SLIDE_TEMPLATE.format(
|
||||
width=self.slide_width,
|
||||
height=self.slide_height,
|
||||
content=elements_html
|
||||
)
|
||||
|
||||
def render_text(self, elem):
|
||||
"""渲染文本为 HTML"""
|
||||
# 实现...
|
||||
|
||||
def render_image(self, elem, base_path):
|
||||
"""渲染图片为 HTML"""
|
||||
# 实现...
|
||||
|
||||
def render_shape(self, elem):
|
||||
"""渲染形状为 HTML"""
|
||||
# 实现...
|
||||
|
||||
def render_table(self, elem):
|
||||
"""渲染表格为 HTML"""
|
||||
# 实现...
|
||||
```
|
||||
|
||||
## 设计特点
|
||||
|
||||
### 渲染器内置在生成器中
|
||||
|
||||
- **封装性**:渲染逻辑与生成器紧密相关
|
||||
- **简单性**:不需要额外的渲染器接口
|
||||
- **性能**:避免额外的方法调用开销
|
||||
|
||||
### 使用 isinstance() 检查类型
|
||||
|
||||
```python
|
||||
if isinstance(elem, TextElement):
|
||||
self._render_text(slide, elem)
|
||||
```
|
||||
|
||||
### 通过元素对象属性访问数据
|
||||
|
||||
```python
|
||||
def _render_text(self, slide, elem):
|
||||
text_box = elem.box
|
||||
content = elem.content
|
||||
font_config = elem.font
|
||||
```
|
||||
|
||||
## 共享元素抽象层
|
||||
|
||||
两个渲染器共享相同的元素数据类:
|
||||
- `TextElement`
|
||||
- `ImageElement`
|
||||
- `ShapeElement`
|
||||
- `TableElement`
|
||||
|
||||
## 单位转换
|
||||
|
||||
### HTML 渲染器
|
||||
|
||||
使用固定 DPI (96) 进行单位转换:
|
||||
|
||||
```python
|
||||
DPI = 96
|
||||
pixels = inches * DPI
|
||||
```
|
||||
|
||||
### PPTX 渲染器
|
||||
|
||||
python-pptx 使用 Inches 单位:
|
||||
|
||||
```python
|
||||
from pptx.util import Inches
|
||||
left = Inches(box[0])
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Elements 模块](elements.md) - 元素数据类
|
||||
- [预览功能](../../user-guide.md) - 用户指南
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
148
docs/development/modules/template.md
Normal file
148
docs/development/modules/template.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Template 模块
|
||||
|
||||
`core/template.py` 实现了模板系统,支持变量解析、条件渲染和模板渲染。
|
||||
|
||||
## 职责
|
||||
|
||||
- 模板加载和变量解析
|
||||
- 条件表达式评估
|
||||
- 模板渲染
|
||||
|
||||
## 包含的内容
|
||||
|
||||
### Template 类
|
||||
|
||||
#### from_data() 类方法
|
||||
|
||||
从字典创建模板实例(用于内联模板):
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_data(cls, template_data, template_name, base_dir=None):
|
||||
"""从字典创建模板(内联模板或外部模板)"""
|
||||
obj = cls.__new__(cls)
|
||||
obj.data = template_data
|
||||
obj.base_dir = base_dir
|
||||
obj.vars_def = {var['name']: var for var in template_data.get('vars', [])}
|
||||
obj.elements = template_data.get('elements', [])
|
||||
return obj
|
||||
```
|
||||
|
||||
#### 变量解析
|
||||
|
||||
**resolve_value()** - 解析变量值:
|
||||
|
||||
```python
|
||||
def resolve_value(self, value, vars_values):
|
||||
"""解析变量值,支持 {varname} 语法"""
|
||||
if isinstance(value, str) and '{' in value:
|
||||
# 替换变量
|
||||
result = value
|
||||
for var_name, var_value in vars_values.items():
|
||||
result = result.replace(f'{{{var_name}}}', str(var_value))
|
||||
return result
|
||||
return value
|
||||
```
|
||||
|
||||
**resolve_element()** - 解析元素中的变量:
|
||||
|
||||
```python
|
||||
def resolve_element(self, element, vars_values):
|
||||
"""递归解析元素中的所有变量"""
|
||||
resolved = {}
|
||||
for key, value in element.items():
|
||||
if isinstance(value, dict):
|
||||
resolved[key] = self.resolve_element(value, vars_values)
|
||||
elif isinstance(value, str):
|
||||
resolved[key] = self.resolve_value(value, vars_values)
|
||||
else:
|
||||
resolved[key] = value
|
||||
return resolved
|
||||
```
|
||||
|
||||
#### 条件渲染
|
||||
|
||||
**evaluate_condition()** - 委托给 ConditionEvaluator:
|
||||
|
||||
```python
|
||||
def evaluate_condition(self, condition, vars_values):
|
||||
"""评估条件表达式"""
|
||||
from core.condition_evaluator import ConditionEvaluator
|
||||
evaluator = ConditionEvaluator()
|
||||
return evaluator.evaluate(condition, vars_values)
|
||||
```
|
||||
|
||||
#### 模板渲染
|
||||
|
||||
**render()** - 渲染模板:
|
||||
|
||||
```python
|
||||
def render(self, vars_values):
|
||||
"""渲染模板,返回解析后的元素列表"""
|
||||
elements = []
|
||||
|
||||
for elem in self.elements:
|
||||
# 检查条件渲染
|
||||
if 'visible' in elem:
|
||||
if not self.evaluate_condition(elem['visible'], vars_values):
|
||||
continue
|
||||
|
||||
# 解析变量
|
||||
resolved_elem = self.resolve_element(elem, vars_values)
|
||||
elements.append(resolved_elem)
|
||||
|
||||
return elements
|
||||
```
|
||||
|
||||
## 特点
|
||||
|
||||
### 支持两种模板方式
|
||||
|
||||
- **外部模板**:从文件加载
|
||||
- **内联模板**:从字典创建(通过 `from_data()`)
|
||||
|
||||
### 变量替换
|
||||
|
||||
支持 `{varname}` 语法的变量替换:
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
title-slide:
|
||||
vars:
|
||||
- name: title
|
||||
elements:
|
||||
- type: text
|
||||
content: "{title}" # 变量替换
|
||||
```
|
||||
|
||||
### 条件渲染
|
||||
|
||||
支持 `visible` 属性的条件表达式:
|
||||
|
||||
```yaml
|
||||
elements:
|
||||
- type: text
|
||||
content: "有数据"
|
||||
visible: "{count > 0}"
|
||||
```
|
||||
|
||||
## 内联模板支持
|
||||
|
||||
`from_data()` 类方法使模板可以从字典创建:
|
||||
|
||||
```python
|
||||
# 从内联模板定义创建
|
||||
template = Template.from_data(
|
||||
template_data=inline_template_dict,
|
||||
template_name="title-slide",
|
||||
base_dir=document_base_dir
|
||||
)
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [条件评估器](../condition-rendering.md) - 条件表达式语法
|
||||
- [Elements 模块](elements.md) - 元素数据类
|
||||
- [内联模板](../../templates/inline.md) - 用户指南
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
112
docs/development/modules/validators.md
Normal file
112
docs/development/modules/validators.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Validators 模块
|
||||
|
||||
`validators/` 目录包含所有验证相关的代码。
|
||||
|
||||
## 模块组成
|
||||
|
||||
### validators/result.py
|
||||
|
||||
验证结果数据结构:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ValidationIssue:
|
||||
level: str # ERROR/WARNING/INFO
|
||||
message: str
|
||||
location: str = ""
|
||||
code: str = ""
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
errors: List[ValidationIssue]
|
||||
warnings: List[ValidationIssue]
|
||||
infos: List[ValidationIssue]
|
||||
```
|
||||
|
||||
### validators/geometry.py
|
||||
|
||||
几何验证器,检查元素边界:
|
||||
|
||||
```python
|
||||
class GeometryValidator:
|
||||
def __init__(self, slide_width, slide_height):
|
||||
self.slide_width = slide_width
|
||||
self.slide_height = slide_height
|
||||
self.tolerance = 0.1 # 英寸
|
||||
|
||||
def validate_element(self, element, slide_index, elem_index):
|
||||
"""检查元素是否在页面范围内"""
|
||||
# 检查边界
|
||||
# 支持容忍度
|
||||
```
|
||||
|
||||
### validators/resource.py
|
||||
|
||||
资源验证器,检查文件存在性:
|
||||
|
||||
```python
|
||||
class ResourceValidator:
|
||||
def __init__(self, base_dir):
|
||||
self.base_dir = Path(base_dir)
|
||||
|
||||
def validate_image(self, src, slide_index, elem_index):
|
||||
"""检查图片文件是否存在"""
|
||||
# 检查文件路径
|
||||
# 验证文件存在
|
||||
```
|
||||
|
||||
### validators/validator.py
|
||||
|
||||
主验证器,协调所有子验证器:
|
||||
|
||||
```python
|
||||
class Validator:
|
||||
def __init__(self, slide_width, slide_height, base_dir):
|
||||
self.geometry_validator = GeometryValidator(slide_width, slide_height)
|
||||
self.resource_validator = ResourceValidator(base_dir)
|
||||
|
||||
def validate_presentation(self, presentation):
|
||||
"""验证整个演示文稿"""
|
||||
# 遍历所有幻灯片和元素
|
||||
# 调用子验证器
|
||||
```
|
||||
|
||||
## 验证职责分层
|
||||
|
||||
### 元素级验证
|
||||
|
||||
在元素类中完成:
|
||||
- 必需字段检查
|
||||
- 数据类型检查
|
||||
- 值的有效性检查
|
||||
|
||||
### 系统级验证
|
||||
|
||||
在验证器中完成:
|
||||
- 几何验证(需要页面尺寸)
|
||||
- 资源验证(需要文件路径)
|
||||
- 跨元素验证
|
||||
|
||||
## 验证容忍度
|
||||
|
||||
几何验证时,允许 0.1 英寸的容忍度:
|
||||
|
||||
```python
|
||||
TOLERANCE = 0.1 # 英寸
|
||||
|
||||
if right > slide_width + TOLERANCE:
|
||||
# 报告 WARNING
|
||||
```
|
||||
|
||||
## 分级错误报告
|
||||
|
||||
- **ERROR**:阻止转换的严重问题
|
||||
- **WARNING**:影响视觉效果的问题
|
||||
- **INFO**:优化建议
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Elements 模块](elements.md) - 元素级验证
|
||||
- [开发规范](../development-guide.md) - 验证职责
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
174
docs/development/scope-system.md
Normal file
174
docs/development/scope-system.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 作用域系统
|
||||
|
||||
字体作用域系统实现了文档和模板库之间的字体隔离和跨域引用控制。
|
||||
|
||||
## 作用域定义
|
||||
|
||||
系统定义了两个字体作用域:
|
||||
|
||||
### 文档作用域(document)
|
||||
|
||||
- 包含文档的 `metadata.fonts` 中定义的字体
|
||||
- 文档的 `fonts_default` 只能引用文档作用域的字体
|
||||
- 文档元素可以引用文档作用域和模板库作用域的字体
|
||||
|
||||
### 模板库作用域(template)
|
||||
|
||||
- 包含模板库的 `metadata.fonts` 中定义的字体
|
||||
- 模板库的 `fonts_default` 只能引用模板库作用域的字体
|
||||
- 模板元素只能引用模板库作用域的字体(不能引用文档字体)
|
||||
|
||||
## 跨域引用规则
|
||||
|
||||
### 允许的引用
|
||||
|
||||
- 文档元素 → 文档字体
|
||||
- 文档元素 → 模板库字体(跨域引用)
|
||||
- 模板元素 → 模板库字体
|
||||
- 文档 fonts_default → 文档字体
|
||||
- 模板库 fonts_default → 模板库字体
|
||||
|
||||
### 禁止的引用
|
||||
|
||||
- 模板元素 → 文档字体(跨域引用被禁止)
|
||||
- 模板库 fonts_default → 文档字体(跨域引用被禁止)
|
||||
- 文档 fonts_default → 模板库字体(跨域引用被禁止)
|
||||
|
||||
### 设计理由
|
||||
|
||||
- 模板库应该是自包含的,不依赖特定文档的字体配置
|
||||
- 文档可以引用模板库字体,实现样式复用
|
||||
- 防止模板库与文档之间的紧耦合
|
||||
|
||||
## FontResolver 实现
|
||||
|
||||
### 初始化
|
||||
|
||||
```python
|
||||
class FontResolver:
|
||||
def __init__(self, fonts, fonts_default, scope="document", template_fonts=None):
|
||||
"""
|
||||
Args:
|
||||
scope: 作用域标识 ("document" 或 "template")
|
||||
template_fonts: 模板库字体字典(仅文档作用域需要)
|
||||
"""
|
||||
```
|
||||
|
||||
### 跨域引用检测
|
||||
|
||||
- 使用作用域标签(`doc.@font-name` 或 `template.@font-name`)追踪引用路径
|
||||
- 检测跨域循环引用(如 `doc.@a → template.@b → doc.@a`)
|
||||
- 在 `parent` 引用时根据作用域限制跨域访问
|
||||
|
||||
## fonts_default 级联规则
|
||||
|
||||
当元素未指定字体时,按以下顺序查找默认字体:
|
||||
|
||||
### 模板元素
|
||||
|
||||
1. 模板库的 `fonts_default`(如果存在)
|
||||
2. 文档的 `fonts_default`(如果存在)
|
||||
3. 系统默认字体
|
||||
|
||||
### 文档元素
|
||||
|
||||
1. 文档的 `fonts_default`(如果存在)
|
||||
2. 系统默认字体
|
||||
|
||||
## 循环引用检测
|
||||
|
||||
### 单域内循环
|
||||
|
||||
```yaml
|
||||
fonts:
|
||||
a:
|
||||
parent: "@b"
|
||||
b:
|
||||
parent: "@a"
|
||||
```
|
||||
|
||||
错误信息:`检测到字体引用循环: doc.@a -> doc.@b -> doc.@a`
|
||||
|
||||
### 跨域循环
|
||||
|
||||
```yaml
|
||||
# 文档
|
||||
fonts:
|
||||
doc-font:
|
||||
parent: "@template-font"
|
||||
|
||||
# 模板库
|
||||
fonts:
|
||||
template-font:
|
||||
parent: "@doc-font" # 这会被禁止
|
||||
```
|
||||
|
||||
错误信息:`检测到跨域字体引用循环: doc.@doc-font -> template.@template-font -> doc.@doc-font`
|
||||
|
||||
## 错误代码
|
||||
|
||||
### 模板库 metadata 相关
|
||||
|
||||
- `TEMPLATE_LIBRARY_MISSING_METADATA` - 模板库缺少 metadata 字段
|
||||
- `TEMPLATE_LIBRARY_METADATA_MISSING_SIZE` - 模板库 metadata 缺少 size 字段
|
||||
- `TEMPLATE_LIBRARY_METADATA_INVALID_SIZE` - 模板库 metadata.size 值无效
|
||||
|
||||
### Size 一致性
|
||||
|
||||
- `SIZE_MISMATCH` - 文档和模板库的 size 不一致
|
||||
|
||||
### 字体引用相关
|
||||
|
||||
- `TEMPLATE_FONT_REF_DOC_FORBIDDEN` - 模板元素引用文档字体
|
||||
- `TEMPLATE_PARENT_REF_DOC_FORBIDDEN` - 模板库字体的 parent 引用文档字体
|
||||
- `FONT_NOT_FOUND` - 字体配置不存在
|
||||
- `CIRCULAR_REFERENCE` - 检测到字体引用循环
|
||||
- `FONT_DEFAULT_INVALID` - fonts_default 引用无效
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 正确的跨域引用
|
||||
|
||||
```yaml
|
||||
# templates.yaml(模板库)
|
||||
metadata:
|
||||
size: "16:9"
|
||||
fonts:
|
||||
template-title:
|
||||
family: "cjk-sans"
|
||||
size: 44
|
||||
bold: true
|
||||
|
||||
# presentation.yaml(文档)
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "正文"
|
||||
font: "@template-title" # 文档元素可以引用模板库字体
|
||||
```
|
||||
|
||||
### 错误的跨域引用
|
||||
|
||||
```yaml
|
||||
# templates.yaml(模板库)
|
||||
metadata:
|
||||
size: "16:9"
|
||||
fonts_default: "@doc-body" # 模板库 fonts_default 不能引用文档字体
|
||||
|
||||
templates:
|
||||
title-slide:
|
||||
elements:
|
||||
- type: text
|
||||
content: "{title}"
|
||||
font: "@doc-body" # 模板元素不能引用文档字体
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [字体系统实现](font-system.md) - FontResolver 实现
|
||||
- [字体主题系统](../../fonts.md) - 用户指南
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
267
docs/development/testing.md
Normal file
267
docs/development/testing.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# 测试规范
|
||||
|
||||
本文档说明 yaml2pptx 项目的测试框架和规范。
|
||||
|
||||
## 测试框架
|
||||
|
||||
项目使用 pytest 作为测试框架,测试代码位于 `tests/` 目录。
|
||||
|
||||
## 测试结构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # pytest 配置和共享 fixtures
|
||||
├── conftest_pptx.py # PPTX 文件验证工具
|
||||
├── unit/ # 单元测试
|
||||
│ ├── test_elements.py # 元素类测试
|
||||
│ ├── test_template.py # 模板系统测试
|
||||
│ ├── test_utils.py # 工具函数测试
|
||||
│ ├── test_validators/ # 验证器测试
|
||||
│ │ ├── test_geometry.py
|
||||
│ │ ├── test_resource.py
|
||||
│ │ ├── test_result.py
|
||||
│ │ └── test_validator.py
|
||||
│ └── test_loaders/ # 加载器测试
|
||||
│ └── test_yaml_loader.py
|
||||
├── integration/ # 集成测试
|
||||
│ ├── test_presentation.py
|
||||
│ ├── test_rendering_flow.py
|
||||
│ └── test_validation_flow.py
|
||||
├── e2e/ # 端到端测试
|
||||
│ ├── test_convert_cmd.py
|
||||
│ ├── test_check_cmd.py
|
||||
│ └── test_preview_cmd.py
|
||||
└── fixtures/ # 测试数据
|
||||
├── yaml_samples/ # YAML 样本
|
||||
├── templates/ # 测试模板
|
||||
└── images/ # 测试图片
|
||||
```
|
||||
|
||||
## 运行测试
|
||||
|
||||
### 基本命令
|
||||
|
||||
```bash
|
||||
# 安装测试依赖
|
||||
uv pip install -e ".[dev]"
|
||||
|
||||
# 运行所有测试
|
||||
uv run pytest
|
||||
|
||||
# 运行特定类型的测试
|
||||
uv run pytest tests/unit/ # 单元测试
|
||||
uv run pytest tests/integration/ # 集成测试
|
||||
uv run pytest tests/e2e/ # 端到端测试
|
||||
|
||||
# 运行特定测试文件
|
||||
uv run pytest tests/unit/test_elements.py
|
||||
|
||||
# 显示详细输出
|
||||
uv run pytest -v
|
||||
|
||||
# 显示测试覆盖率
|
||||
uv run pytest --cov=. --cov-report=html
|
||||
```
|
||||
|
||||
### 测试文件位置
|
||||
|
||||
- **自动化测试**:`tests/` 目录
|
||||
- **手动测试文件**:`temp/` 目录
|
||||
|
||||
## 编写测试
|
||||
|
||||
### 测试类命名
|
||||
|
||||
使用 `Test<ClassName>` 格式:
|
||||
|
||||
```python
|
||||
class TestTextElement:
|
||||
"""TextElement 测试类"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 测试方法命名
|
||||
|
||||
使用 `test_<what_is_being_tested>` 格式:
|
||||
|
||||
```python
|
||||
def test_create_with_defaults(self):
|
||||
"""测试使用默认值创建 TextElement"""
|
||||
pass
|
||||
|
||||
def test_invalid_color_raises_error(self):
|
||||
"""测试无效颜色会引发错误"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 测试示例
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from core.elements import TextElement, FontConfig
|
||||
|
||||
class TestTextElement:
|
||||
"""TextElement 测试类"""
|
||||
|
||||
def test_create_with_defaults(self):
|
||||
"""测试使用默认值创建 TextElement"""
|
||||
elem = TextElement()
|
||||
assert elem.type == 'text'
|
||||
assert elem.content == ''
|
||||
assert elem.box == [1, 1, 8, 1]
|
||||
|
||||
def test_create_with_custom_values(self):
|
||||
"""测试使用自定义值创建 TextElement"""
|
||||
elem = TextElement(
|
||||
content="Test",
|
||||
box=[2, 2, 6, 1],
|
||||
font={"size": 24}
|
||||
)
|
||||
assert elem.content == "Test"
|
||||
assert elem.box == [2, 2, 6, 1]
|
||||
|
||||
def test_invalid_color_raises_error(self):
|
||||
"""测试无效颜色会引发错误"""
|
||||
with pytest.raises(ValueError, match="无效颜色"):
|
||||
TextElement(font={"color": "red"})
|
||||
```
|
||||
|
||||
## Fixtures
|
||||
|
||||
共享 fixtures 定义在 `tests/conftest.py` 中:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir(tmp_path):
|
||||
"""临时目录 fixture"""
|
||||
return tmp_path
|
||||
|
||||
@pytest.fixture
|
||||
def sample_yaml(temp_dir):
|
||||
"""最小测试 YAML 文件"""
|
||||
yaml_file = temp_dir / "test.yaml"
|
||||
yaml_file.write_text("""
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
box: [1, 1, 8, 1]
|
||||
content: "Test"
|
||||
""")
|
||||
return yaml_file
|
||||
|
||||
@pytest.fixture
|
||||
def sample_image(temp_dir):
|
||||
"""测试图片 fixture"""
|
||||
import PIL.Image
|
||||
img_path = temp_dir / "test.png"
|
||||
img = PIL.Image.new('RGB', (100, 100), color='red')
|
||||
img.save(img_path)
|
||||
return img_path
|
||||
|
||||
@pytest.fixture
|
||||
def pptx_validator():
|
||||
"""PPTX 验证器 fixture"""
|
||||
from tests.conftest_pptx import PptxFileValidator
|
||||
return PptxFileValidator()
|
||||
```
|
||||
|
||||
### 使用 Fixtures
|
||||
|
||||
```python
|
||||
def test_with_fixture(sample_yaml):
|
||||
"""使用 fixture 的测试"""
|
||||
assert sample_yaml.exists()
|
||||
assert sample_yaml.stat().st_size > 0
|
||||
```
|
||||
|
||||
## PPTX 验证
|
||||
|
||||
使用 `PptxFileValidator` 验证生成的 PPTX 文件:
|
||||
|
||||
```python
|
||||
def test_pptx_generation(temp_dir, pptx_validator):
|
||||
"""测试 PPTX 生成"""
|
||||
from core.presentation import Presentation
|
||||
from renderers.pptx_renderer import PptxGenerator
|
||||
|
||||
# 生成 PPTX
|
||||
yaml_path = temp_dir / "test.yaml"
|
||||
output_path = temp_dir / "output.pptx"
|
||||
|
||||
# ... 创建演示文稿 ...
|
||||
|
||||
# 验证文件
|
||||
assert pptx_validator.validate_file(output_path) is True
|
||||
|
||||
# 验证内容
|
||||
from pptx import Presentation as PPTX
|
||||
prs = PPTX(str(output_path))
|
||||
assert len(prs.slides) == 1
|
||||
assert pptx_validator.validate_text_element(
|
||||
prs.slides[0],
|
||||
index=0,
|
||||
expected_content="Test"
|
||||
) is True
|
||||
```
|
||||
|
||||
## 手动测试
|
||||
|
||||
```bash
|
||||
# 验证 YAML 文件
|
||||
uv run yaml2pptx.py check temp/test.yaml
|
||||
|
||||
# 使用模板时验证
|
||||
uv run yaml2pptx.py check temp/demo.yaml --template ./templates.yaml
|
||||
|
||||
# 转换 YAML 为 PPTX
|
||||
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx
|
||||
|
||||
# 自动生成输出文件名
|
||||
uv run yaml2pptx.py convert temp/test.yaml
|
||||
|
||||
# 跳过自动验证
|
||||
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --skip-validation
|
||||
|
||||
# 强制覆盖已存在文件
|
||||
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --force
|
||||
|
||||
# 使用模板
|
||||
uv run yaml2pptx.py convert temp/demo.yaml temp/output.pptx --template ./templates.yaml
|
||||
|
||||
# 启动预览服务器
|
||||
uv run yaml2pptx.py preview temp/test.yaml
|
||||
|
||||
# 指定端口
|
||||
uv run yaml2pptx.py preview temp/test.yaml --port 8080
|
||||
|
||||
# 允许局域网访问
|
||||
uv run yaml2pptx.py preview temp/test.yaml --host 0.0.0.0
|
||||
|
||||
# 不自动打开浏览器
|
||||
uv run yaml2pptx.py preview temp/test.yaml --no-browser
|
||||
```
|
||||
|
||||
## 测试覆盖率
|
||||
|
||||
目标测试覆盖率:>80%
|
||||
|
||||
```bash
|
||||
# 生成覆盖率报告
|
||||
uv run pytest --cov=. --cov-report=html
|
||||
|
||||
# 查看报告
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [开发规范](development-guide.md) - 编码规范
|
||||
- [扩展指南](extending.md) - 添加新功能
|
||||
|
||||
[返回开发文档索引](../README.md)
|
||||
17
docs/elements/_index.md
Normal file
17
docs/elements/_index.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 元素类型文档
|
||||
|
||||
本目录包含各种元素类型的详细说明。
|
||||
|
||||
## 元素类型
|
||||
|
||||
- [文本元素](text.md) - 文本框和字体配置
|
||||
- [图片元素](image.md) - 图片插入和配置
|
||||
- [形状元素](shape.md) - 几何形状绘制
|
||||
- [表格元素](table.md) - 表格创建和样式
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [字体主题系统](../fonts.md) - 字体配置和主题管理
|
||||
- [坐标系统](../reference/coordinates.md) - 位置和尺寸单位
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
100
docs/elements/image.md
Normal file
100
docs/elements/image.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# 图片元素
|
||||
|
||||
图片元素用于在幻灯片中插入图片。
|
||||
|
||||
## 基本语法
|
||||
|
||||
```yaml
|
||||
- type: image
|
||||
box: [x, y, width, height]
|
||||
src: "path/to/image.png" # 支持相对路径和绝对路径
|
||||
```
|
||||
|
||||
## 属性
|
||||
|
||||
| 属性 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `type` | 字符串 | 是 | 必须为 "image" |
|
||||
| `box` | 数组 | 是 | 位置和尺寸 [x, y, width, height](英寸) |
|
||||
| `src` | 字符串 | 是 | 图片文件路径 |
|
||||
|
||||
## 图片路径
|
||||
|
||||
支持相对路径和绝对路径:
|
||||
|
||||
```yaml
|
||||
# 相对路径(相对于 YAML 文件位置)
|
||||
- type: image
|
||||
src: "images/logo.png"
|
||||
box: [1, 1, 2, 2]
|
||||
|
||||
# 绝对路径
|
||||
- type: image
|
||||
src: "/Users/username/pictures/photo.jpg"
|
||||
box: [1, 1, 4, 3]
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
### 基本图片
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: image
|
||||
src: "photo.jpg"
|
||||
box: [1, 1, 4, 3]
|
||||
```
|
||||
|
||||
### 多个图片
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: image
|
||||
src: "logo.png"
|
||||
box: [0.5, 0.5, 2, 2]
|
||||
|
||||
- type: image
|
||||
src: "banner.jpg"
|
||||
box: [3, 0.5, 6.5, 2]
|
||||
```
|
||||
|
||||
### 与文本组合
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "项目 Logo"
|
||||
box: [1, 3, 3, 0.5]
|
||||
font:
|
||||
size: 24
|
||||
align: center
|
||||
|
||||
- type: image
|
||||
src: "logo.png"
|
||||
box: [1, 3.5, 3, 3]
|
||||
```
|
||||
|
||||
## 支持的格式
|
||||
|
||||
支持常见图片格式,包括:
|
||||
- PNG (.png)
|
||||
- JPEG (.jpg, .jpeg)
|
||||
- GIF (.gif)
|
||||
- BMP (.bmp)
|
||||
- 其他 PowerPoint 支持的格式
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 图片路径相对于 YAML 文件位置
|
||||
- 建议使用高分辨率图片以获得最佳显示效果
|
||||
- 图片会按照 box 指定的尺寸进行缩放
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [坐标系统](../reference/coordinates.md) - 位置和尺寸单位
|
||||
- [形状元素](shape.md) - 几何形状绘制
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
139
docs/elements/shape.md
Normal file
139
docs/elements/shape.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 形状元素
|
||||
|
||||
形状元素用于在幻灯片中绘制几何形状。
|
||||
|
||||
## 基本语法
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [x, y, width, height]
|
||||
shape: rectangle # rectangle/ellipse/rounded_rectangle
|
||||
fill: "#4a90e2" # 填充颜色
|
||||
line:
|
||||
color: "#000000" # 边框颜色
|
||||
width: 2 # 边框宽度(磅)
|
||||
```
|
||||
|
||||
## 属性
|
||||
|
||||
| 属性 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `type` | 字符串 | 是 | 必须为 "shape" |
|
||||
| `box` | 数组 | 是 | 位置和尺寸 [x, y, width, height](英寸) |
|
||||
| `shape` | 字符串 | 是 | 形状类型 |
|
||||
| `fill` | 字符串 | 否 | 填充颜色(#RRGGBB 或 #RGB) |
|
||||
| `line` | 对象 | 否 | 边框配置 |
|
||||
|
||||
## 形状类型
|
||||
|
||||
### rectangle(矩形)
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#4a90e2"
|
||||
```
|
||||
|
||||
### ellipse(椭圆)
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: ellipse
|
||||
fill: "#e74c3c"
|
||||
```
|
||||
|
||||
### rounded_rectangle(圆角矩形)
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rounded_rectangle
|
||||
fill: "#2ecc71"
|
||||
```
|
||||
|
||||
## 边框配置
|
||||
|
||||
```yaml
|
||||
line:
|
||||
color: "#000000" # 边框颜色
|
||||
width: 2 # 边框宽度(磅)
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
### 简单矩形
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#4a90e2"
|
||||
```
|
||||
|
||||
### 带边框的形状
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#ffffff"
|
||||
line:
|
||||
color: "#000000"
|
||||
width: 2
|
||||
```
|
||||
|
||||
### 多个形状组合
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
# 背景矩形
|
||||
- type: shape
|
||||
box: [0, 0, 10, 5.625]
|
||||
shape: rectangle
|
||||
fill: "#f5f5f5"
|
||||
|
||||
# 装饰圆形
|
||||
- type: shape
|
||||
box: [1, 1, 2, 2]
|
||||
shape: ellipse
|
||||
fill: "#3498db"
|
||||
|
||||
# 文本
|
||||
- type: text
|
||||
box: [3.5, 1.5, 5, 1]
|
||||
content: "欢迎"
|
||||
font:
|
||||
size: 44
|
||||
```
|
||||
|
||||
### 无填充形状
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "transparent" # 透明填充
|
||||
line:
|
||||
color: "#000000"
|
||||
width: 2
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 形状会按照 box 指定的尺寸进行绘制
|
||||
- 填充颜色和边框颜色使用相同的格式
|
||||
- 边框宽度单位为磅(pt)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [坐标系统](../reference/coordinates.md) - 位置和尺寸单位
|
||||
- [颜色格式](../reference/colors.md) - 颜色表示方法
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
147
docs/elements/table.md
Normal file
147
docs/elements/table.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 表格元素
|
||||
|
||||
表格元素用于在幻灯片中创建表格。
|
||||
|
||||
## 基本语法
|
||||
|
||||
```yaml
|
||||
- type: table
|
||||
position: [x, y]
|
||||
col_widths: [2, 2, 2] # 每列宽度(英寸)
|
||||
data:
|
||||
- ["表头1", "表头2", "表头3"]
|
||||
- ["数据1", "数据2", "数据3"]
|
||||
- ["数据4", "数据5", "数据6"]
|
||||
font:
|
||||
family: "Arial"
|
||||
size: 14
|
||||
color: "#333333"
|
||||
header_font:
|
||||
bold: true
|
||||
color: "#ffffff"
|
||||
style:
|
||||
header_bg: "#4a90e2"
|
||||
```
|
||||
|
||||
## 属性
|
||||
|
||||
| 属性 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `type` | 字符串 | 是 | 必须为 "table" |
|
||||
| `position` | 数组 | 是 | 表格位置 [x, y](英寸) |
|
||||
| `col_widths` | 数组 | 是 | 每列宽度(英寸) |
|
||||
| `data` | 数组 | 是 | 表格数据(二维数组) |
|
||||
| `font` | 对象 | 否 | 数据单元格字体样式 |
|
||||
| `header_font` | 对象 | 否 | 表头单元格字体样式 |
|
||||
| `style` | 对象 | 否 | 表格样式 |
|
||||
|
||||
## 字体配置
|
||||
|
||||
### font(数据单元格)
|
||||
|
||||
数据单元格的字体样式:
|
||||
|
||||
```yaml
|
||||
font:
|
||||
family: "Arial"
|
||||
size: 14
|
||||
color: "#333333"
|
||||
bold: false
|
||||
```
|
||||
|
||||
### header_font(表头单元格)
|
||||
|
||||
表头单元格的字体样式。如果未定义,继承 `font` 的配置:
|
||||
|
||||
```yaml
|
||||
header_font:
|
||||
bold: true
|
||||
color: "#ffffff"
|
||||
# 其他属性继承自 font
|
||||
```
|
||||
|
||||
### style(表格样式)
|
||||
|
||||
```yaml
|
||||
style:
|
||||
header_bg: "#4a90e2" # 表头背景色
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
### 基本表格
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: table
|
||||
position: [1, 1]
|
||||
col_widths: [2, 2, 2]
|
||||
data:
|
||||
- ["姓名", "年龄", "城市"]
|
||||
- ["张三", "25", "北京"]
|
||||
- ["李四", "30", "上海"]
|
||||
```
|
||||
|
||||
### 样式化表格
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: table
|
||||
position: [1, 1]
|
||||
col_widths: [2.5, 2.5, 2.5]
|
||||
data:
|
||||
- ["产品", "价格", "库存"]
|
||||
- ["产品A", "100元", "50"]
|
||||
- ["产品B", "200元", "30"]
|
||||
font:
|
||||
family: "sans"
|
||||
size: 14
|
||||
color: "#333333"
|
||||
header_font:
|
||||
bold: true
|
||||
color: "#ffffff"
|
||||
style:
|
||||
header_bg: "#3498db"
|
||||
```
|
||||
|
||||
### 使用字体主题
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
fonts:
|
||||
table-font:
|
||||
family: "sans"
|
||||
size: 14
|
||||
color: "#333333"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: table
|
||||
position: [1, 1]
|
||||
col_widths: [2, 2, 2]
|
||||
data:
|
||||
- ["列1", "列2"]
|
||||
- ["数据1", "数据2"]
|
||||
font: "@table-font"
|
||||
header_font:
|
||||
parent: "@table-font"
|
||||
bold: true
|
||||
color: "#ffffff"
|
||||
style:
|
||||
header_bg: "#4a90e2"
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `col_widths` 数组长度必须与每行的列数一致
|
||||
- 所有行的列数必须相同
|
||||
- 第一行默认为表头
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [字体主题系统](../fonts.md) - 字体配置和主题管理
|
||||
- [坐标系统](../reference/coordinates.md) - 位置和尺寸单位
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
121
docs/elements/text.md
Normal file
121
docs/elements/text.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 文本元素
|
||||
|
||||
文本元素用于在幻灯片中添加文本内容。
|
||||
|
||||
## 基本语法
|
||||
|
||||
```yaml
|
||||
- type: text
|
||||
box: [x, y, width, height] # 位置和尺寸(英寸)
|
||||
content: "文本内容"
|
||||
font:
|
||||
size: 18 # 字号(磅)
|
||||
bold: true # 粗体
|
||||
italic: false # 斜体
|
||||
color: "#ff0000" # 颜色
|
||||
align: center # left/center/right
|
||||
```
|
||||
|
||||
## 字体属性
|
||||
|
||||
### 基础属性
|
||||
|
||||
| 属性 | 类型 | 说明 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| `size` | 数字 | 字号(磅) | 18 |
|
||||
| `bold` | 布尔 | 粗体 | false |
|
||||
| `italic` | 布尔 | 斜体 | false |
|
||||
| `color` | 字符串 | 颜色(#RRGGBB 或 #RGB) | #000000 |
|
||||
| `align` | 字符串 | 对齐方式 | left |
|
||||
| `family` | 字符串 | 字体族或预设类别 | Arial |
|
||||
|
||||
### 对齐方式
|
||||
|
||||
- `left` - 左对齐
|
||||
- `center` - 居中对齐
|
||||
- `right` - 右对齐
|
||||
|
||||
### 高级字体样式
|
||||
|
||||
- `underline` - 下划线(true/false)
|
||||
- `strikethrough` - 删除线(true/false)
|
||||
|
||||
### 段落属性
|
||||
|
||||
- `line_spacing` - 行距倍数(如 1.5)
|
||||
- `space_before` - 段前间距(磅)
|
||||
- `space_after` - 段后间距(磅)
|
||||
|
||||
### 高级属性
|
||||
|
||||
- `baseline` - 基线位置(normal/superscript/subscript)
|
||||
- `caps` - 大小写转换(normal/allcaps/smallcaps)
|
||||
|
||||
## 示例
|
||||
|
||||
### 简单文本
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
box: [1, 1, 8, 1]
|
||||
content: "Hello, World!"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
```
|
||||
|
||||
### 多行文本
|
||||
|
||||
```yaml
|
||||
- type: text
|
||||
box: [1, 1, 8, 2]
|
||||
content: "第一行\n第二行\n第三行"
|
||||
font:
|
||||
size: 18
|
||||
```
|
||||
|
||||
### 样式化文本
|
||||
|
||||
```yaml
|
||||
- type: text
|
||||
box: [1, 1, 8, 1]
|
||||
content: "标题文本"
|
||||
font:
|
||||
size: 32
|
||||
bold: true
|
||||
color: "#2c3e50"
|
||||
underline: true
|
||||
```
|
||||
|
||||
### 使用字体主题
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
fonts:
|
||||
title:
|
||||
family: "cjk-sans"
|
||||
size: 44
|
||||
bold: true
|
||||
color: "#2c3e50"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "标题文本"
|
||||
box: [1, 1, 8, 1]
|
||||
font: "@title" # 引用字体主题
|
||||
```
|
||||
|
||||
## 特性
|
||||
|
||||
文本框默认启用自动换行,文字超出宽度时会自动换行。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [字体主题系统](../fonts.md) - 字体配置和主题管理
|
||||
- [坐标系统](../reference/coordinates.md) - 位置和尺寸单位
|
||||
- [颜色格式](../reference/colors.md) - 颜色表示方法
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
5
docs/examples.md
Normal file
5
docs/examples.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 示例集合
|
||||
|
||||
本文档为占位文件,未来将添加更多示例。
|
||||
|
||||
目前各主题文档中已内嵌代码示例供参考。
|
||||
198
docs/fonts.md
Normal file
198
docs/fonts.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 字体主题系统
|
||||
|
||||
字体主题系统允许你定义可复用的字体配置,统一管理演示文稿的字体样式。
|
||||
|
||||
## 定义字体主题
|
||||
|
||||
在 `metadata.fonts` 中定义命名字体配置:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
fonts:
|
||||
title:
|
||||
family: "cjk-sans"
|
||||
size: 44
|
||||
bold: true
|
||||
color: "#2c3e50"
|
||||
body:
|
||||
family: "sans"
|
||||
size: 18
|
||||
color: "#34495e"
|
||||
line_spacing: 1.5
|
||||
fonts_default: "@body" # 默认字体(可选)
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "标题文本"
|
||||
box: [1, 1, 8, 1]
|
||||
font: "@title" # 引用字体主题
|
||||
|
||||
- type: text
|
||||
content: "正文内容"
|
||||
box: [1, 2.5, 8, 2]
|
||||
# 未定义 font 时使用 fonts_default
|
||||
```
|
||||
|
||||
## 预设字体类别
|
||||
|
||||
系统提供五种预设字体类别,自动映射到跨平台通用字体:
|
||||
|
||||
| 类别 | 映射字体 | 说明 |
|
||||
|------|---------|------|
|
||||
| `sans` | Arial | 西文无衬线 |
|
||||
| `serif` | Times New Roman | 西文衬线 |
|
||||
| `mono` | Courier New | 等宽字体 |
|
||||
| `cjk-sans` | Microsoft YaHei | 中文无衬线 |
|
||||
| `cjk-serif` | SimSun | 中文衬线 |
|
||||
|
||||
### 使用预设类别
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
fonts:
|
||||
body:
|
||||
family: "cjk-sans" # 自动映射到 Microsoft YaHei
|
||||
size: 18
|
||||
```
|
||||
|
||||
## 字体引用方式
|
||||
|
||||
### 1. 整体引用
|
||||
|
||||
完全使用定义的字体配置:
|
||||
|
||||
```yaml
|
||||
font: "@title"
|
||||
```
|
||||
|
||||
### 2. 继承覆盖
|
||||
|
||||
继承字体配置并覆盖特定属性:
|
||||
|
||||
```yaml
|
||||
font:
|
||||
parent: "@title"
|
||||
size: 60 # 覆盖字号
|
||||
color: "#ff0000" # 覆盖颜色
|
||||
```
|
||||
|
||||
### 3. 独立定义
|
||||
|
||||
完全自定义字体:
|
||||
|
||||
```yaml
|
||||
font:
|
||||
family: "SimSun"
|
||||
size: 24
|
||||
bold: true
|
||||
```
|
||||
|
||||
## 扩展字体属性
|
||||
|
||||
除了基础属性(size、bold、italic、color、align),还支持:
|
||||
|
||||
### 字体样式
|
||||
|
||||
- `family`:字体族名称或预设类别
|
||||
- `underline`:下划线(true/false)
|
||||
- `strikethrough`:删除线(true/false)
|
||||
|
||||
### 段落属性
|
||||
|
||||
- `line_spacing`:行距倍数(如 1.5)
|
||||
- `space_before`:段前间距(磅)
|
||||
- `space_after`:段后间距(磅)
|
||||
|
||||
### 高级属性
|
||||
|
||||
- `baseline`:基线位置(normal/superscript/subscript)
|
||||
- `caps`:大小写转换(normal/allcaps/smallcaps)
|
||||
|
||||
## 完整示例
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
fonts:
|
||||
heading:
|
||||
family: "cjk-sans"
|
||||
size: 32
|
||||
bold: true
|
||||
color: "#2c3e50"
|
||||
line_spacing: 1.2
|
||||
space_after: 12
|
||||
body:
|
||||
family: "sans"
|
||||
size: 18
|
||||
color: "#34495e"
|
||||
line_spacing: 1.5
|
||||
fonts_default: "@body"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "章节标题"
|
||||
box: [1, 1, 8, 1]
|
||||
font: "@heading"
|
||||
|
||||
- type: text
|
||||
content: "正文内容\n支持多行文本"
|
||||
box: [1, 2, 8, 2]
|
||||
font:
|
||||
parent: "@body"
|
||||
underline: true
|
||||
|
||||
- type: table
|
||||
position: [1, 4]
|
||||
col_widths: [3, 3]
|
||||
data:
|
||||
- ["列1", "列2"]
|
||||
- ["数据1", "数据2"]
|
||||
font: "@body"
|
||||
header_font:
|
||||
parent: "@body"
|
||||
bold: true
|
||||
color: "#ffffff"
|
||||
style:
|
||||
header_bg: "#3498db"
|
||||
```
|
||||
|
||||
## 跨域引用
|
||||
|
||||
### 文档元素引用模板库字体
|
||||
|
||||
```yaml
|
||||
# templates.yaml(模板库)
|
||||
metadata:
|
||||
size: "16:9"
|
||||
fonts:
|
||||
template-title:
|
||||
family: "cjk-sans"
|
||||
size: 44
|
||||
bold: true
|
||||
|
||||
# presentation.yaml(文档)
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "标题"
|
||||
font: "@template-title" # 文档元素可以引用模板库字体
|
||||
```
|
||||
|
||||
### 引用规则
|
||||
|
||||
- 文档元素 → 文档字体
|
||||
- 文档元素 → 模板库字体
|
||||
- 模板元素 → 模板库字体
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [外部模板库](templates/external-library.md) - 模板库字体配置
|
||||
- [作用域系统](../development/scope-system.md) - 字体作用域详细规则
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
17
docs/reference/_index.md
Normal file
17
docs/reference/_index.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# API 参考文档
|
||||
|
||||
本目录包含各种 API 参考文档。
|
||||
|
||||
## 参考主题
|
||||
|
||||
- [命令行选项](commands.md) - 所有命令和参数
|
||||
- [坐标系统](coordinates.md) - 位置和尺寸单位
|
||||
- [颜色格式](colors.md) - 颜色表示方法
|
||||
- [验证功能](validation.md) - YAML 验证说明
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [用户指南](../user-guide.md) - 完整使用说明
|
||||
- [故障排查](../troubleshooting.md) - 常见问题解决
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
141
docs/reference/colors.md
Normal file
141
docs/reference/colors.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 颜色格式
|
||||
|
||||
yaml2pptx 支持两种十六进制颜色格式。
|
||||
|
||||
## 支持的格式
|
||||
|
||||
### 短格式 #RGB
|
||||
|
||||
3 位十六进制,每位颜色值重复一次:
|
||||
|
||||
```yaml
|
||||
color: "#fff" # 白色 (#ffffff)
|
||||
color: "#000" # 黑色 (#000000)
|
||||
color: "#f00" # 红色 (#ff0000)
|
||||
color: "#0f0" # 绿色 (#00ff00)
|
||||
color: "#00f" # 蓝色 (#0000ff)
|
||||
```
|
||||
|
||||
### 完整格式 #RRGGBB
|
||||
|
||||
6 位十六进制,标准的颜色表示:
|
||||
|
||||
```yaml
|
||||
color: "#ffffff" # 白色
|
||||
color: "#000000" # 黑色
|
||||
color: "#ff0000" # 红色
|
||||
color: "#00ff00" # 绿色
|
||||
color: "#0000ff" # 蓝色
|
||||
```
|
||||
|
||||
## 常用颜色参考
|
||||
|
||||
### 基础颜色
|
||||
|
||||
| 颜色 | 短格式 | 完整格式 |
|
||||
|------|--------|----------|
|
||||
| 黑色 | `#000` | `#000000` |
|
||||
| 白色 | `#fff` | `#ffffff` |
|
||||
| 红色 | `#f00` | `#ff0000` |
|
||||
| 绿色 | `#0f0` | `#00ff00` |
|
||||
| 蓝色 | `#00f` | `#0000ff` |
|
||||
|
||||
### 常用色彩
|
||||
|
||||
| 颜色 | 完整格式 | 说明 |
|
||||
|------|----------|------|
|
||||
| 灰色 | `#808080` | 中性灰 |
|
||||
| 深灰 | `#333333` | 深灰色 |
|
||||
| 浅灰 | `#cccccc` | 浅灰色 |
|
||||
| 黄色 | `#ffff00` | 纯黄色 |
|
||||
| 青色 | `#00ffff` | 纯青色 |
|
||||
| 品红 | `#ff00ff` | 纯品红 |
|
||||
|
||||
### Material Design 色彩
|
||||
|
||||
| 颜色 | 完整格式 | 说明 |
|
||||
|------|----------|------|
|
||||
| 红色 | `#f44336` | Material Red |
|
||||
| 粉色 | `#e91e63` | Material Pink |
|
||||
| 紫色 | `#9c27b0` | Material Purple |
|
||||
| 深紫 | `#673ab7` | Material Deep Purple |
|
||||
| 靛蓝 | `#3f51b5` | Material Indigo |
|
||||
| 蓝色 | `#2196f3` | Material Blue |
|
||||
| 浅蓝 | `#03a9f4` | Material Light Blue |
|
||||
| 青色 | `#00bcd4` | Material Cyan |
|
||||
| 蓝绿 | `#009688` | Material Teal |
|
||||
| 绿色 | `#4caf50` | Material Green |
|
||||
| 浅绿 | `#8bc34a` | Material Light Green |
|
||||
| 橙色 | `#ff9800` | Material Orange |
|
||||
| 深橙 | `#ff5722` | Material Deep Orange |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 文本颜色
|
||||
|
||||
```yaml
|
||||
- type: text
|
||||
content: "红色文本"
|
||||
font:
|
||||
color: "#ff0000"
|
||||
```
|
||||
|
||||
### 形状填充
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#3498db" # 蓝色
|
||||
```
|
||||
|
||||
### 边框颜色
|
||||
|
||||
```yaml
|
||||
- type: shape
|
||||
box: [1, 1, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#ffffff"
|
||||
line:
|
||||
color: "#000000"
|
||||
width: 2
|
||||
```
|
||||
|
||||
### 表格样式
|
||||
|
||||
```yaml
|
||||
- type: table
|
||||
position: [1, 1]
|
||||
col_widths: [2, 2, 2]
|
||||
data: [...]
|
||||
style:
|
||||
header_bg: "#4a90e2"
|
||||
header_font:
|
||||
color: "#ffffff"
|
||||
```
|
||||
|
||||
## 颜色验证
|
||||
|
||||
系统会自动验证颜色格式:
|
||||
|
||||
- **短格式**:`/#[0-9a-fA-F]{3}/`
|
||||
- **完整格式**:`/#[0-9a-fA-F]{6}/`
|
||||
|
||||
无效的颜色格式会导致验证错误:
|
||||
|
||||
```
|
||||
[幻灯片 1, 元素 1] 无效的颜色格式: red (应为 #RRGGBB)
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 颜色代码必须以 `#` 开头
|
||||
- 不支持颜色名称(如 `red`、`blue`)
|
||||
- 不支持 RGB/RGBA 格式(如 `rgb(255, 0, 0)`)
|
||||
- 大小写均可(`#fff` 和 `#FFF` 等效)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [坐标系统](coordinates.md) - 位置和尺寸单位
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
123
docs/reference/commands.md
Normal file
123
docs/reference/commands.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 命令行选项
|
||||
|
||||
yaml2pptx 提供三个主要命令:check、convert、preview。
|
||||
|
||||
## check 命令
|
||||
|
||||
验证 YAML 文件的正确性。
|
||||
|
||||
### 语法
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py check <input> [--template <path>]
|
||||
```
|
||||
|
||||
### 选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `input` | 输入的 YAML 文件路径(必需) |
|
||||
| `--template` | 模板库文件路径 |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 验证基本文件
|
||||
uv run yaml2pptx.py check presentation.yaml
|
||||
|
||||
# 验证使用模板的文件
|
||||
uv run yaml2pptx.py check presentation.yaml --template ./templates.yaml
|
||||
```
|
||||
|
||||
## convert 命令
|
||||
|
||||
将 YAML 文件转换为 PPTX 文件。
|
||||
|
||||
### 语法
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py convert <input> [output] [options]
|
||||
```
|
||||
|
||||
### 选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `input` | 输入的 YAML 文件路径(必需) |
|
||||
| `output` | 输出的 PPTX 文件路径(可选) |
|
||||
| `--template` | 模板库文件路径 |
|
||||
| `--skip-validation` | 跳过自动验证 |
|
||||
| `--force` / `-f` | 强制覆盖已存在文件 |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 基本转换(自动生成输出文件名)
|
||||
uv run yaml2pptx.py convert presentation.yaml
|
||||
|
||||
# 指定输出文件
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx
|
||||
|
||||
# 使用模板库
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx --template ./templates.yaml
|
||||
|
||||
# 跳过验证
|
||||
uv run yaml2pptx.py convert presentation.yaml --skip-validation
|
||||
|
||||
# 强制覆盖
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx --force
|
||||
```
|
||||
|
||||
## preview 命令
|
||||
|
||||
启动预览服务器,实时查看演示文稿效果。
|
||||
|
||||
### 语法
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py preview <input> [options]
|
||||
```
|
||||
|
||||
### 选项
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| `input` | 输入的 YAML 文件路径(必需) |
|
||||
| `--template` | 模板库文件路径 |
|
||||
| `--port` | 服务器端口(默认:随机端口 30000-40000) |
|
||||
| `--host` | 主机地址(默认:127.0.0.1) |
|
||||
| `--no-browser` | 不自动打开浏览器 |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 启动预览(自动打开浏览器)
|
||||
uv run yaml2pptx.py preview presentation.yaml
|
||||
|
||||
# 指定端口
|
||||
uv run yaml2pptx.py preview presentation.yaml --port 8080
|
||||
|
||||
# 允许局域网访问
|
||||
uv run yaml2pptx.py preview presentation.yaml --host 0.0.0.0
|
||||
|
||||
# 不自动打开浏览器
|
||||
uv run yaml2pptx.py preview presentation.yaml --no-browser
|
||||
|
||||
# 使用模板
|
||||
uv run yaml2pptx.py preview presentation.yaml --template ./templates.yaml
|
||||
```
|
||||
|
||||
## 通用选项
|
||||
|
||||
所有命令都支持以下行为:
|
||||
|
||||
- 自动安装依赖(通过 uv)
|
||||
- 显示友好的错误信息
|
||||
- 支持相对路径和绝对路径
|
||||
|
||||
## 退出代码
|
||||
|
||||
- `0` - 成功
|
||||
- `1` - 错误(文件不存在、验证失败等)
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
154
docs/reference/coordinates.md
Normal file
154
docs/reference/coordinates.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 坐标系统
|
||||
|
||||
yaml2pptx 使用英寸(inch)作为位置和尺寸的单位。
|
||||
|
||||
## 基本概念
|
||||
|
||||
- **单位**:英寸 (inch)
|
||||
- **原点**:幻灯片左上角 (0, 0)
|
||||
- **方向**:X 轴向右,Y 轴向下
|
||||
|
||||
## 幻灯片尺寸
|
||||
|
||||
### 16:9 宽高比
|
||||
|
||||
- 尺寸:10" × 5.625"
|
||||
- 宽度:10 英寸
|
||||
- 高度:5.625 英寸
|
||||
|
||||
### 4:3 宽高比
|
||||
|
||||
- 尺寸:10" × 7.5"
|
||||
- 宽度:10 英寸
|
||||
- 高度:7.5 英寸
|
||||
|
||||
## box 属性
|
||||
|
||||
`box` 属性用于指定元素的位置和尺寸:
|
||||
|
||||
```yaml
|
||||
box: [x, y, width, height]
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 单位 |
|
||||
|------|------|------|
|
||||
| `x` | 左上角 X 坐标 | 英寸 |
|
||||
| `y` | 左上角 Y 坐标 | 英寸 |
|
||||
| `width` | 元素宽度 | 英寸 |
|
||||
| `height` | 元素高度 | 英寸 |
|
||||
|
||||
## 示例
|
||||
|
||||
```yaml
|
||||
# 位置:(1", 2"),尺寸:宽 8",高 1"
|
||||
box: [1, 2, 8, 1]
|
||||
```
|
||||
|
||||
这表示:
|
||||
- 元素左上角位于 (1", 2")
|
||||
- 元素宽度为 8"
|
||||
- 元素高度为 1"
|
||||
|
||||
## position 属性(表格)
|
||||
|
||||
表格元素使用 `position` 属性指定位置:
|
||||
|
||||
```yaml
|
||||
position: [x, y]
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 单位 |
|
||||
|------|------|------|
|
||||
| `x` | 左上角 X 坐标 | 英寸 |
|
||||
| `y` | 左上角 Y 坐标 | 英寸 |
|
||||
|
||||
## 定位示例
|
||||
|
||||
### 居中文本
|
||||
|
||||
```yaml
|
||||
# 16:9 幻灯片
|
||||
# 宽度 10",要居中一个 8" 宽的元素
|
||||
# x = (10 - 8) / 2 = 1
|
||||
|
||||
- type: text
|
||||
box: [1, 2, 8, 1]
|
||||
content: "居中文本"
|
||||
font:
|
||||
align: center
|
||||
```
|
||||
|
||||
### 全屏背景
|
||||
|
||||
```yaml
|
||||
# 16:9 幻灯片
|
||||
- type: shape
|
||||
box: [0, 0, 10, 5.625] # 完全覆盖
|
||||
shape: rectangle
|
||||
fill: "#ffffff"
|
||||
```
|
||||
|
||||
### 四个象限
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- elements:
|
||||
# 左上象限
|
||||
- type: text
|
||||
box: [0.25, 0.25, 4.5, 2.5]
|
||||
content: "左上"
|
||||
|
||||
# 右上象限
|
||||
- type: text
|
||||
box: [5.25, 0.25, 4.5, 2.5]
|
||||
content: "右上"
|
||||
|
||||
# 左下象限
|
||||
- type: text
|
||||
box: [0.25, 3, 4.5, 2.5]
|
||||
content: "左下"
|
||||
|
||||
# 右下象限
|
||||
- type: text
|
||||
box: [5.25, 3, 4.5, 2.5]
|
||||
content: "右下"
|
||||
```
|
||||
|
||||
## 坐标计算
|
||||
|
||||
### 水平居中
|
||||
|
||||
```python
|
||||
x = (slide_width - element_width) / 2
|
||||
```
|
||||
|
||||
### 垂直居中
|
||||
|
||||
```python
|
||||
y = (slide_height - element_height) / 2
|
||||
```
|
||||
|
||||
### 完全居中
|
||||
|
||||
```yaml
|
||||
# 16:9 幻灯片,居中 4" × 2" 的元素
|
||||
# x = (10 - 4) / 2 = 3
|
||||
# y = (5.625 - 2) / 2 = 1.8125
|
||||
|
||||
- type: shape
|
||||
box: [3, 1.8125, 4, 2]
|
||||
shape: rectangle
|
||||
fill: "#3498db"
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 所有坐标和尺寸必须为正数
|
||||
- 元素可以超出页面边界(会发出警告)
|
||||
- 浮点数精度建议保留 2-3 位小数
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [颜色格式](colors.md) - 颜色表示方法
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
191
docs/reference/validation.md
Normal file
191
docs/reference/validation.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# 验证功能
|
||||
|
||||
yaml2pptx 提供强大的验证功能,在转换前自动检查 YAML 文件,提前发现问题。
|
||||
|
||||
## 验证级别
|
||||
|
||||
验证结果分为三个级别:
|
||||
|
||||
### ERROR(错误)
|
||||
|
||||
阻止转换的严重问题:
|
||||
- 文件不存在(图片、模板文件等)
|
||||
- YAML 语法错误
|
||||
- 必需的变量未提供
|
||||
- 无效的数据类型
|
||||
|
||||
### WARNING(警告)
|
||||
|
||||
影响视觉效果但不阻止转换的问题:
|
||||
- 元素超出页面范围
|
||||
- 字体大小过小或过大
|
||||
- 颜色格式不规范但可解析
|
||||
|
||||
### INFO(信息)
|
||||
|
||||
优化建议和提示:
|
||||
- 性能优化建议
|
||||
- 最佳实践提示
|
||||
|
||||
## 验证内容
|
||||
|
||||
### YAML 语法和结构
|
||||
|
||||
- 检查 YAML 语法是否正确
|
||||
- 检查必需字段是否存在
|
||||
- 检查数据类型是否正确
|
||||
|
||||
### 元素边界
|
||||
|
||||
- 检查元素是否超出页面范围
|
||||
- 0.1 英寸容忍度内的超出会发出警告
|
||||
|
||||
### 资源文件
|
||||
|
||||
- 检查图片文件是否存在
|
||||
- 检查模板文件是否存在
|
||||
- 验证文件路径是否有效
|
||||
|
||||
### 颜色格式
|
||||
|
||||
- 检查颜色格式是否正确(#RGB 或 #RRGGBB)
|
||||
- 检查颜色值是否有效
|
||||
|
||||
### 字体配置
|
||||
|
||||
- 检查字体大小是否合理(<6 或 >72 会警告)
|
||||
- 检查字体引用是否存在
|
||||
- 检查字体引用循环
|
||||
|
||||
### 表格数据
|
||||
|
||||
- 检查表格数据一致性
|
||||
- 检查列数是否匹配
|
||||
|
||||
## 使用验证
|
||||
|
||||
### 独立验证
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py check presentation.yaml
|
||||
```
|
||||
|
||||
### 使用模板时验证
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py check presentation.yaml --template ./templates.yaml
|
||||
```
|
||||
|
||||
### 自动验证
|
||||
|
||||
转换时默认会自动验证:
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx
|
||||
```
|
||||
|
||||
### 跳过验证
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx --skip-validation
|
||||
```
|
||||
|
||||
## 验证结果示例
|
||||
|
||||
### 有错误和警告
|
||||
|
||||
```
|
||||
正在检查 YAML 文件...
|
||||
|
||||
- 错误 (2):
|
||||
[幻灯片 2, 元素 1] 无效的颜色格式: red (应为 #RRGGBB)
|
||||
[幻灯片 3, 元素 2] 图片文件不存在: logo.png
|
||||
|
||||
- 警告 (1):
|
||||
[幻灯片 1, 元素 1] 元素右边界超出: 10.50 > 10
|
||||
|
||||
检查完成: 发现 2 个错误, 1 个警告
|
||||
转换已终止
|
||||
```
|
||||
|
||||
### 验证通过
|
||||
|
||||
```
|
||||
正在检查 YAML 文件...
|
||||
|
||||
检查完成: 未发现问题
|
||||
```
|
||||
|
||||
### 仅警告
|
||||
|
||||
```
|
||||
正在检查 YAML 文件...
|
||||
|
||||
- 警告 (2):
|
||||
[幻灯片 1, 元素 1] 元素右边界超出: 10.05 > 10
|
||||
[幻灯片 2, 元素 2] 字体大小过小: 5
|
||||
|
||||
检查完成: 发现 2 个警告
|
||||
```
|
||||
|
||||
## 常见验证错误
|
||||
|
||||
### 文件不存在
|
||||
|
||||
```
|
||||
错误: 图片文件未找到: images/logo.png
|
||||
```
|
||||
|
||||
**解决方法**:检查图片路径是否正确
|
||||
|
||||
### YAML 语法错误
|
||||
|
||||
```
|
||||
错误: YAML 语法错误: 第 15 行
|
||||
```
|
||||
|
||||
**解决方法**:检查缩进和语法,确保 YAML 格式正确
|
||||
|
||||
### 缺少必需变量
|
||||
|
||||
```
|
||||
错误: 缺少必需变量: title
|
||||
```
|
||||
|
||||
**解决方法**:在 `vars` 中提供该变量
|
||||
|
||||
### 无效颜色格式
|
||||
|
||||
```
|
||||
错误: 无效的颜色格式: red (应为 #RRGGBB)
|
||||
```
|
||||
|
||||
**解决方法**:使用 `#ff0000` 格式
|
||||
|
||||
## 验证容忍度
|
||||
|
||||
几何验证时,允许 0.1 英寸的容忍度:
|
||||
|
||||
- 超出 ≤ 0.1 英寸:不报错
|
||||
- 超出 > 0.1 英寸:发出警告
|
||||
|
||||
```python
|
||||
TOLERANCE = 0.1 # 英寸
|
||||
|
||||
if right > slide_width + TOLERANCE:
|
||||
# 报告 WARNING
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **开发时频繁验证**:使用 `check` 命令快速验证
|
||||
2. **修复所有错误**:确保没有 ERROR 级别的问题
|
||||
3. **关注警告**:WARNING 虽不阻止转换,但可能影响视觉效果
|
||||
4. **使用预览模式**:结合预览模式查看实际效果
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [命令行选项](commands.md) - 所有命令和参数
|
||||
- [故障排查](../troubleshooting.md) - 常见问题解决
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
24
docs/templates/_index.md
vendored
Normal file
24
docs/templates/_index.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# 模板系统文档
|
||||
|
||||
本目录包含模板系统的详细说明。
|
||||
|
||||
## 模板类型
|
||||
|
||||
- [内联模板](inline.md) - 在源文件中定义模板
|
||||
- [外部模板库](external-library.md) - 独立的模板库文件
|
||||
- [混合模式](mixing-mode.md) - 模板与自定义元素组合
|
||||
- [条件渲染](condition-rendering.md) - 元素和页面的条件显示
|
||||
|
||||
## 使用指南
|
||||
|
||||
- **简单场景**:使用内联模板
|
||||
- **跨文档复用**:使用外部模板库
|
||||
- **灵活布局**:使用混合模式
|
||||
- **动态内容**:使用条件渲染
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [字体主题系统](../fonts.md) - 字体配置
|
||||
- [用户指南](../user-guide.md) - 完整使用说明
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
180
docs/templates/condition-rendering.md
vendored
Normal file
180
docs/templates/condition-rendering.md
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
# 条件渲染
|
||||
|
||||
条件渲染允许你根据变量值控制元素和幻灯片的显示。
|
||||
|
||||
## 元素级条件渲染
|
||||
|
||||
使用 `visible` 属性控制元素显示,支持强大的条件表达式:
|
||||
|
||||
### 基本示例
|
||||
|
||||
```yaml
|
||||
# 简单比较
|
||||
- type: text
|
||||
content: "有数据"
|
||||
visible: "{count > 0}"
|
||||
|
||||
# 字符串比较
|
||||
- type: text
|
||||
content: "草稿状态"
|
||||
visible: "{status == 'draft'}"
|
||||
|
||||
# 非空检查(向后兼容)
|
||||
- type: text
|
||||
content: "{subtitle}"
|
||||
visible: "{subtitle != ''}"
|
||||
```
|
||||
|
||||
### 支持的表达式类型
|
||||
|
||||
#### 1. 比较运算
|
||||
|
||||
`==`, `!=`, `>`, `<`, `>=`, `<=`
|
||||
|
||||
```yaml
|
||||
visible: "{score >= 60}"
|
||||
visible: "{price <= 100}"
|
||||
```
|
||||
|
||||
#### 2. 逻辑运算
|
||||
|
||||
`and`, `or`, `not`
|
||||
|
||||
```yaml
|
||||
visible: "{count > 0 and status == 'active'}"
|
||||
visible: "{is_draft or is_preview}"
|
||||
visible: "{not (count == 0)}"
|
||||
```
|
||||
|
||||
#### 3. 成员测试
|
||||
|
||||
`in`, `not in`
|
||||
|
||||
```yaml
|
||||
visible: "{status in ['draft', 'review', 'published']}"
|
||||
visible: "{level in (1, 2, 3)}"
|
||||
visible: "{'test' in version}" # 字符串包含
|
||||
```
|
||||
|
||||
#### 4. 数学运算
|
||||
|
||||
`+`, `-`, `*`, `/`, `%`, `**`
|
||||
|
||||
```yaml
|
||||
visible: "{(price * discount) > 50}"
|
||||
visible: "{(total / count) >= 10}"
|
||||
```
|
||||
|
||||
#### 5. 内置函数
|
||||
|
||||
`int()`, `float()`, `str()`, `len()`, `bool()`, `abs()`, `min()`, `max()`
|
||||
|
||||
```yaml
|
||||
visible: "{len(items) > 0}"
|
||||
visible: "{int(value) > 100}"
|
||||
```
|
||||
|
||||
### 复杂条件示例
|
||||
|
||||
```yaml
|
||||
# 范围检查
|
||||
- type: text
|
||||
content: "评分: {score}"
|
||||
visible: "{score >= 60 and score <= 100}"
|
||||
|
||||
# 多条件组合
|
||||
- type: text
|
||||
content: "管理员或高分用户"
|
||||
visible: "{is_admin or (score >= 90)}"
|
||||
|
||||
# 嵌套条件
|
||||
- type: text
|
||||
content: "符合条件"
|
||||
visible: "{((count > 0) and (status == 'active')) or (is_admin and (level >= 3))}"
|
||||
```
|
||||
|
||||
## 页面级启用控制
|
||||
|
||||
使用 `enabled` 参数控制整个幻灯片是否渲染:
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
# 正常渲染的幻灯片
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "主标题"
|
||||
|
||||
# 临时禁用的幻灯片(开发调试)
|
||||
- enabled: false
|
||||
template: work-in-progress
|
||||
vars:
|
||||
title: "未完成的内容"
|
||||
|
||||
# 继续渲染后续幻灯片
|
||||
- template: content-slide
|
||||
vars:
|
||||
title: "内容页"
|
||||
```
|
||||
|
||||
### enabled 参数说明
|
||||
|
||||
- **类型**:布尔值(`true` 或 `false`)
|
||||
- **默认值**:`true`(未指定时默认启用)
|
||||
- **用途**:临时禁用幻灯片,无需删除或注释 YAML 内容
|
||||
- **场景**:开发调试、版本控制、A/B 测试
|
||||
|
||||
### enabled vs visible 的区别
|
||||
|
||||
| 特性 | `enabled`(页面级) | `visible`(元素级) |
|
||||
|------|-------------------|-------------------|
|
||||
| 作用范围 | 整个幻灯片 | 单个元素 |
|
||||
| 类型 | 布尔值 | 条件表达式 |
|
||||
| 判断时机 | 加载时(静态) | 渲染时(动态) |
|
||||
| 使用场景 | 临时禁用页面 | 条件显示元素 |
|
||||
|
||||
### 示例
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
# 页面启用,但副标题元素可能隐藏
|
||||
- enabled: true
|
||||
template: title-slide
|
||||
vars:
|
||||
title: "标题"
|
||||
subtitle: "" # 空字符串,元素级 visible 会隐藏副标题
|
||||
|
||||
# 整页禁用,不渲染
|
||||
- enabled: false
|
||||
elements:
|
||||
- type: text
|
||||
content: "这一页不会出现在最终 PPTX 中"
|
||||
box: [1, 1, 8, 1]
|
||||
font: {size: 44}
|
||||
```
|
||||
|
||||
## 安全策略
|
||||
|
||||
条件表达式评估使用 simpleeval 库,具有以下安全限制:
|
||||
|
||||
- 表达式最大长度限制:500 字符
|
||||
- 禁止属性访问(obj.attr)
|
||||
- 禁止函数定义(lambda, def)
|
||||
- 禁止模块导入(import)
|
||||
- 白名单函数限制
|
||||
|
||||
## 错误处理
|
||||
|
||||
常见错误信息:
|
||||
|
||||
- `条件表达式中的变量未定义` - 使用了未在 vars 中定义的变量
|
||||
- `条件表达式中使用了不支持的函数` - 使用了白名单之外的函数
|
||||
- `条件表达式使用了不支持的语法特性` - 使用了禁止的语法
|
||||
- `不支持属性访问` - 尝试访问对象属性
|
||||
- `条件表达式语法错误` - 表达式语法有误
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [内联模板](inline.md) - 在源文件中定义模板
|
||||
- [混合模式](mixing-mode.md) - 模板与自定义元素组合
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
192
docs/templates/external-library.md
vendored
Normal file
192
docs/templates/external-library.md
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
# 外部模板库
|
||||
|
||||
外部模板库是一个包含多个模板的 YAML 文件,适合跨文档复用和团队共享。
|
||||
|
||||
## 创建模板库文件
|
||||
|
||||
创建模板库文件 `templates.yaml`:
|
||||
|
||||
```yaml
|
||||
# 模板库元数据(必需)
|
||||
metadata:
|
||||
size: "16:9" # 必需:模板库尺寸,必须与使用它的文档一致
|
||||
description: "公司标准模板库" # 可选:描述信息
|
||||
fonts: # 可选:模板库字体主题
|
||||
template-title:
|
||||
family: "cjk-sans"
|
||||
size: 44
|
||||
bold: true
|
||||
color: "#2c3e50"
|
||||
template-body:
|
||||
family: "sans"
|
||||
size: 20
|
||||
color: "#34495e"
|
||||
fonts_default: "@template-body" # 可选:模板库默认字体
|
||||
|
||||
# 模板定义(必需)
|
||||
templates:
|
||||
title-slide:
|
||||
description: "标题页模板"
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
- name: subtitle
|
||||
required: false
|
||||
default: ""
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 2, 8, 1]
|
||||
content: "{title}"
|
||||
font: "@template-title" # 引用模板库字体
|
||||
- type: text
|
||||
box: [1, 3.5, 8, 0.5]
|
||||
content: "{subtitle}"
|
||||
visible: "{subtitle != ''}"
|
||||
font:
|
||||
parent: "@template-title"
|
||||
size: 24
|
||||
align: center
|
||||
|
||||
content-slide:
|
||||
description: "内容页模板"
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
- name: content
|
||||
required: true
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 1, 8, 0.8]
|
||||
content: "{title}"
|
||||
font:
|
||||
parent: "@template-title"
|
||||
size: 32
|
||||
- type: text
|
||||
box: [1, 2, 8, 3]
|
||||
content: "{content}"
|
||||
# 未指定 font 时使用 fonts_default
|
||||
```
|
||||
|
||||
## 重要说明
|
||||
|
||||
### 1. metadata 字段是必需的
|
||||
|
||||
- `metadata.size` 必须指定("16:9" 或 "4:3")
|
||||
- 模板库的 size 必须与使用它的文档 size 一致,否则会报错
|
||||
|
||||
### 2. 字体主题系统
|
||||
|
||||
- 模板库可以定义自己的字体主题(`metadata.fonts`)
|
||||
- 文档可以引用模板库的字体(跨域引用)
|
||||
- 模板库不能引用文档的字体(单向引用)
|
||||
- 模板库的 `fonts_default` 只能引用模板库内部字体
|
||||
|
||||
### 3. 字体级联规则
|
||||
|
||||
- 模板元素未指定字体时,使用模板库的 `fonts_default`
|
||||
- 如果模板库没有 `fonts_default`,使用文档的 `fonts_default`
|
||||
- 如果都没有,使用系统默认字体
|
||||
|
||||
## 使用外部模板库
|
||||
|
||||
在命令行中指定模板库文件:
|
||||
|
||||
```bash
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx --template ./templates.yaml
|
||||
```
|
||||
|
||||
在 YAML 文件中引用模板:
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "我的演示文稿"
|
||||
subtitle: "使用外部模板库"
|
||||
|
||||
- template: content-slide
|
||||
vars:
|
||||
title: "第一章"
|
||||
content: "这是内容"
|
||||
```
|
||||
|
||||
## 模板库特性
|
||||
|
||||
- 单个文件包含多个模板
|
||||
- 支持模板库元数据(description、version、author)
|
||||
- 每个模板可以有独立的 description
|
||||
- 便于版本控制和分发
|
||||
- 支持相对路径的图片资源(相对于模板库文件所在目录)
|
||||
|
||||
## 模板库文件结构
|
||||
|
||||
```yaml
|
||||
# 顶层元数据(可选)
|
||||
description: "模板库描述"
|
||||
version: "版本号"
|
||||
author: "作者"
|
||||
|
||||
# 模板定义(必需)
|
||||
templates:
|
||||
模板名称1:
|
||||
description: "模板描述(可选)"
|
||||
vars: [...]
|
||||
elements: [...]
|
||||
|
||||
模板名称2:
|
||||
description: "模板描述(可选)"
|
||||
vars: [...]
|
||||
elements: [...]
|
||||
```
|
||||
|
||||
## 模板 description 字段
|
||||
|
||||
模板可以包含可选的 `description` 字段,用于描述模板的用途和设计意图:
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
title-slide:
|
||||
description: "用于章节标题页的模板,包含主标题和副标题"
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 2, 8, 1]
|
||||
content: "{title}"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
```
|
||||
|
||||
## 资源路径解析
|
||||
|
||||
模板库中的图片资源使用相对于模板库文件所在目录的路径:
|
||||
|
||||
```yaml
|
||||
# templates.yaml 所在目录:/path/to/templates/
|
||||
templates:
|
||||
logo-slide:
|
||||
elements:
|
||||
- type: image
|
||||
src: "images/logo.png" # 相对于 templates.yaml 所在目录
|
||||
box: [1, 1, 2, 2]
|
||||
```
|
||||
|
||||
## 与内联模板的对比
|
||||
|
||||
| 特性 | 内联模板 | 外部模板库 |
|
||||
|------|---------|-----------|
|
||||
| 定义位置 | 源文件中 | 独立文件 |
|
||||
| 适用场景 | 单文档使用 | 跨文档复用 |
|
||||
| 命令行参数 | 无需 | 需要 `--template` |
|
||||
| 模板间引用 | 不支持 | 支持 |
|
||||
| 字体主题 | 使用文档字体 | 可定义独立字体 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [内联模板](inline.md) - 在源文件中定义模板
|
||||
- [混合模式](mixing-mode.md) - 模板与自定义元素组合
|
||||
- [字体主题系统](../fonts.md) - 字体配置和跨域引用
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
190
docs/templates/inline.md
vendored
Normal file
190
docs/templates/inline.md
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
# 内联模板
|
||||
|
||||
内联模板允许你在 YAML 源文件中直接定义模板,无需创建单独的模板文件。
|
||||
|
||||
## 定义内联模板
|
||||
|
||||
在 YAML 文件顶层添加 `templates` 字段:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
templates:
|
||||
title-slide:
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
- name: subtitle
|
||||
required: false
|
||||
default: ""
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 2, 8, 1]
|
||||
content: "{title}"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
align: center
|
||||
- type: text
|
||||
box: [1, 3.5, 8, 0.5]
|
||||
content: "{subtitle}"
|
||||
visible: "{subtitle != ''}"
|
||||
font:
|
||||
size: 24
|
||||
align: center
|
||||
|
||||
slides:
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "我的演示文稿"
|
||||
subtitle: "使用内联模板"
|
||||
```
|
||||
|
||||
## 变量定义
|
||||
|
||||
### required 变量
|
||||
|
||||
```yaml
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
```
|
||||
|
||||
### 可选变量与默认值
|
||||
|
||||
```yaml
|
||||
vars:
|
||||
- name: subtitle
|
||||
required: false
|
||||
default: ""
|
||||
```
|
||||
|
||||
## 内联模板特性
|
||||
|
||||
- 支持变量替换和条件渲染
|
||||
- 可以与外部模板混合使用
|
||||
- 无需指定 `--template` 参数
|
||||
- 内联模板不能相互引用
|
||||
- 内联和外部模板同名时会发出警告,优先使用内联模板
|
||||
|
||||
## 何时使用内联模板
|
||||
|
||||
**适合使用内联模板**:
|
||||
- 模板仅在单个文档中使用
|
||||
- 快速原型开发
|
||||
- 简单的模板定义(1-3 个元素)
|
||||
- 文档自包含,无需外部依赖
|
||||
|
||||
**适合使用外部模板**:
|
||||
- 需要跨多个文档复用
|
||||
- 复杂的模板定义(>5 个元素)
|
||||
- 团队共享的模板库
|
||||
- 需要版本控制和独立维护
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
|
||||
- 内联模板使用描述性名称(如 `title-slide`, `content-slide`)
|
||||
- 避免与外部模板同名,否则会发出警告
|
||||
- 使用一致的命名风格(kebab-case 推荐)
|
||||
|
||||
### 2. 模板大小
|
||||
|
||||
- 内联模板建议不超过 50 行
|
||||
- 超过 50 行考虑拆分或使用外部模板
|
||||
- 保持 YAML 文件可读性
|
||||
|
||||
### 3. 混合使用
|
||||
|
||||
- 可以在同一文档中混合使用内联和外部模板
|
||||
- 通用模板使用外部模板(如标题页、结束页)
|
||||
- 文档特定模板使用内联模板
|
||||
|
||||
### 4. 迁移策略
|
||||
|
||||
- 原型阶段使用内联模板快速迭代
|
||||
- 模板稳定后,如需复用则迁移到外部模板
|
||||
- 使用 `--template` 参数指定外部模板库文件
|
||||
|
||||
## 内联模板限制
|
||||
|
||||
- 内联模板不能相互引用(会报错)
|
||||
- 内联和外部模板同名时会发出警告,优先使用内联模板
|
||||
- 内联模板不支持继承或组合
|
||||
|
||||
## 示例
|
||||
|
||||
### 简单标题页
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
templates:
|
||||
simple-title:
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 2.5, 8, 1]
|
||||
content: "{title}"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
align: center
|
||||
|
||||
slides:
|
||||
- template: simple-title
|
||||
vars:
|
||||
title: "欢迎使用 yaml2pptx"
|
||||
```
|
||||
|
||||
### 带条件渲染的模板
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
optional-subtitle:
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
- name: subtitle
|
||||
required: false
|
||||
default: ""
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 2, 8, 1]
|
||||
content: "{title}"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
align: center
|
||||
- type: text
|
||||
box: [1, 3.2, 8, 0.6]
|
||||
content: "{subtitle}"
|
||||
visible: "{subtitle != ''}"
|
||||
font:
|
||||
size: 24
|
||||
align: center
|
||||
|
||||
slides:
|
||||
- template: optional-subtitle
|
||||
vars:
|
||||
title: "标题"
|
||||
subtitle: "有副标题"
|
||||
|
||||
- template: optional-subtitle
|
||||
vars:
|
||||
title: "只有标题"
|
||||
# subtitle 省略,使用默认值 ""
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [外部模板库](external-library.md) - 独立的模板库文件
|
||||
- [混合模式](mixing-mode.md) - 模板与自定义元素组合
|
||||
- [条件渲染](condition-rendering.md) - 元素和页面的条件显示
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
228
docs/templates/mixing-mode.md
vendored
Normal file
228
docs/templates/mixing-mode.md
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
# 混合模式
|
||||
|
||||
混合模式允许你在使用模板的同时添加自定义元素,实现更灵活的布局组合。
|
||||
|
||||
## 基本用法
|
||||
|
||||
在使用模板的幻灯片中,同时指定 `template` 和 `elements` 字段:
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
# 混合模式:模板 + 自定义元素
|
||||
- template: standard-header
|
||||
vars:
|
||||
title: "混合模式示例"
|
||||
theme_color: "#3949ab"
|
||||
elements:
|
||||
# 自定义内容区域
|
||||
- type: text
|
||||
box: [1, 1.5, 8, 1]
|
||||
content: "这是自定义内容"
|
||||
font:
|
||||
size: 24
|
||||
# 自定义形状
|
||||
- type: shape
|
||||
shape: rectangle
|
||||
box: [1, 3, 8, 2]
|
||||
fill: "#f5f5f5"
|
||||
```
|
||||
|
||||
## 变量共享
|
||||
|
||||
自定义元素可以访问模板中定义的变量:
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
branded-header:
|
||||
vars:
|
||||
- name: title
|
||||
- name: theme_color
|
||||
default: "#3949ab"
|
||||
elements:
|
||||
- type: shape
|
||||
box: [0, 0, 10, 0.8]
|
||||
fill: "{theme_color}"
|
||||
- type: text
|
||||
box: [0.5, 0.2, 9, 0.5]
|
||||
content: "{title}"
|
||||
|
||||
slides:
|
||||
- template: branded-header
|
||||
vars:
|
||||
title: "我的页面"
|
||||
theme_color: "#4caf50"
|
||||
elements:
|
||||
# 自定义元素使用模板变量
|
||||
- type: shape
|
||||
box: [1, 2, 8, 3]
|
||||
fill: "{theme_color}" # 使用模板的 theme_color
|
||||
```
|
||||
|
||||
## 元素渲染顺序
|
||||
|
||||
混合模式中,元素按以下顺序渲染(z 轴顺序):
|
||||
|
||||
1. **模板元素**(先渲染,在底层)
|
||||
2. **自定义元素**(后渲染,在上层)
|
||||
|
||||
这意味着自定义元素会覆盖在模板元素之上。
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- template: background-template # 提供背景和头部
|
||||
vars:
|
||||
title: "标题"
|
||||
elements:
|
||||
# 这些元素会显示在模板元素之上
|
||||
- type: shape
|
||||
box: [2, 2, 6, 3]
|
||||
fill: "#ffffff" # 白色框会覆盖背景
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
**适合使用混合模式**:
|
||||
|
||||
- 复用统一的头部/底部,自定义中间内容
|
||||
- 使用模板提供的背景和品牌元素,添加页面特定内容
|
||||
- 需要在标准布局基础上添加特殊元素
|
||||
|
||||
## 示例
|
||||
|
||||
### 统一头部 + 自定义内容
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
standard-header:
|
||||
vars:
|
||||
- name: title
|
||||
elements:
|
||||
# 统一的头部样式
|
||||
- type: shape
|
||||
box: [0, 0, 10, 0.8]
|
||||
fill: "#3949ab"
|
||||
- type: text
|
||||
box: [0.5, 0.2, 9, 0.5]
|
||||
content: "{title}"
|
||||
font:
|
||||
color: "#ffffff"
|
||||
|
||||
slides:
|
||||
# 页面 1:头部 + 文本内容
|
||||
- template: standard-header
|
||||
vars:
|
||||
title: "文本页面"
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 1.5, 8, 3]
|
||||
content: "页面内容..."
|
||||
|
||||
# 页面 2:头部 + 表格
|
||||
- template: standard-header
|
||||
vars:
|
||||
title: "数据页面"
|
||||
elements:
|
||||
- type: table
|
||||
position: [1, 1.5]
|
||||
col_widths: [3, 3]
|
||||
data:
|
||||
- ["列1", "列2"]
|
||||
- ["数据1", "数据2"]
|
||||
|
||||
# 页面 3:头部 + 图片
|
||||
- template: standard-header
|
||||
vars:
|
||||
title: "图片页面"
|
||||
elements:
|
||||
- type: image
|
||||
box: [2, 1.5, 6, 3.5]
|
||||
src: "chart.png"
|
||||
```
|
||||
|
||||
### 背景模板 + 覆盖层
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
gradient-bg:
|
||||
vars:
|
||||
- name: accent_color
|
||||
default: "#3498db"
|
||||
elements:
|
||||
- type: shape
|
||||
box: [0, 0, 10, 5.625]
|
||||
fill: "{accent_color}"
|
||||
|
||||
slides:
|
||||
- template: gradient-bg
|
||||
vars:
|
||||
accent_color: "#9b59b6"
|
||||
elements:
|
||||
# 白色内容框覆盖在渐变背景上
|
||||
- type: shape
|
||||
box: [1, 1, 8, 3.625]
|
||||
fill: "#ffffff"
|
||||
- type: text
|
||||
box: [1.5, 2, 7, 2]
|
||||
content: "内容区域"
|
||||
font:
|
||||
size: 32
|
||||
```
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
混合模式完全向后兼容:
|
||||
|
||||
- **纯模板模式**:只指定 `template`,行为不变
|
||||
- **纯自定义模式**:只指定 `elements`,行为不变
|
||||
- **混合模式**:同时指定 `template` 和 `elements`,新功能
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
# 纯模板模式(原有行为)
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "标题"
|
||||
|
||||
# 纯自定义模式(原有行为)
|
||||
- elements:
|
||||
- type: text
|
||||
content: "自定义内容"
|
||||
|
||||
# 混合模式(新功能)
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "标题"
|
||||
elements:
|
||||
- type: text
|
||||
content: "额外内容"
|
||||
```
|
||||
|
||||
## 幻灯片 description 字段
|
||||
|
||||
幻灯片可以包含可选的 `description` 字段,用于描述该幻灯片的作用和内容。**`description` 内容会自动写入 PPT 备注页**,方便在演示时查看演讲说明:
|
||||
|
||||
```yaml
|
||||
slides:
|
||||
- description: "介绍项目背景和目标"
|
||||
template: title-slide
|
||||
vars:
|
||||
title: "项目背景"
|
||||
|
||||
- description: "展示核心功能特性"
|
||||
elements:
|
||||
- type: text
|
||||
content: "功能特性"
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 仅幻灯片级别的 `description` 会写入备注
|
||||
- 模板的 `description` 不会继承到幻灯片备注
|
||||
- `metadata.description` 用于描述整个演示文稿,不写入单个幻灯片备注
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [内联模板](inline.md) - 在源文件中定义模板
|
||||
- [外部模板库](external-library.md) - 独立的模板库文件
|
||||
- [条件渲染](condition-rendering.md) - 元素和页面的条件显示
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
216
docs/troubleshooting.md
Normal file
216
docs/troubleshooting.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 故障排查
|
||||
|
||||
本文档提供常见问题的解决方法。
|
||||
|
||||
## 常见错误
|
||||
|
||||
### 文件不存在: xxx.yaml
|
||||
|
||||
**原因**:找不到输入文件
|
||||
|
||||
**解决方法**:
|
||||
- 检查文件路径是否正确
|
||||
- 确认文件在当前目录或使用相对/绝对路径
|
||||
- 检查文件名拼写是否正确
|
||||
|
||||
```bash
|
||||
# 错误示例
|
||||
uv run yaml2pptx.py convert presntation.yaml # 拼写错误
|
||||
|
||||
# 正确示例
|
||||
uv run yaml2pptx.py convert presentation.yaml
|
||||
```
|
||||
|
||||
### YAML 语法错误: 第 X 行
|
||||
|
||||
**原因**:YAML 格式错误
|
||||
|
||||
**解决方法**:
|
||||
- 检查缩进是否正确(使用空格,不要使用 Tab)
|
||||
- 确保冒号后有空格
|
||||
- 检查引号是否成对
|
||||
- 使用 YAML 验证工具检查语法
|
||||
|
||||
```yaml
|
||||
# 错误示例
|
||||
slides:
|
||||
- elements: # 缩进错误
|
||||
- type: text
|
||||
content: "hello"
|
||||
|
||||
# 正确示例
|
||||
slides:
|
||||
- elements:
|
||||
- type: text
|
||||
content: "hello"
|
||||
```
|
||||
|
||||
### 模板文件不存在: xxx
|
||||
|
||||
**原因**:模板文件未找到
|
||||
|
||||
**解决方法**:
|
||||
- 检查 `--template` 参数是否正确
|
||||
- 确认模板库文件存在
|
||||
- 检查模板名称拼写
|
||||
|
||||
```bash
|
||||
# 错误示例
|
||||
uv run yaml2pptx.py convert presentation.yaml --template ./templat.yaml # 拼写错误
|
||||
|
||||
# 正确示例
|
||||
uv run yaml2pptx.py convert presentation.yaml --template ./templates.yaml
|
||||
```
|
||||
|
||||
### 缺少必需变量: xxx
|
||||
|
||||
**原因**:未提供必需的模板变量
|
||||
|
||||
**解决方法**:
|
||||
- 在 `vars` 中提供该变量
|
||||
- 检查模板定义中哪些变量是 `required: true`
|
||||
|
||||
```yaml
|
||||
# 模板定义
|
||||
templates:
|
||||
title-slide:
|
||||
vars:
|
||||
- name: title
|
||||
required: true
|
||||
|
||||
# 错误示例
|
||||
- template: title-slide
|
||||
vars: {} # 缺少 title
|
||||
|
||||
# 正确示例
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "我的标题"
|
||||
```
|
||||
|
||||
### 图片文件未找到: xxx
|
||||
|
||||
**原因**:图片文件不存在
|
||||
|
||||
**解决方法**:
|
||||
- 检查图片路径是否正确
|
||||
- 确认图片文件存在
|
||||
- 使用相对路径(相对于 YAML 文件位置)
|
||||
|
||||
```yaml
|
||||
# 错误示例
|
||||
- type: image
|
||||
src: "images/logo.png" # 文件不存在
|
||||
|
||||
# 正确示例
|
||||
- type: image
|
||||
src: "../assets/logo.png" # 使用正确的相对路径
|
||||
```
|
||||
|
||||
### 无效的颜色格式: xxx
|
||||
|
||||
**原因**:颜色格式不符合要求
|
||||
|
||||
**解决方法**:
|
||||
- 使用 `#RGB` 或 `#RRGGBB` 格式
|
||||
- 不要使用颜色名称(如 `red`、`blue`)
|
||||
|
||||
```yaml
|
||||
# 错误示例
|
||||
font:
|
||||
color: "red"
|
||||
|
||||
# 正确示例
|
||||
font:
|
||||
color: "#ff0000"
|
||||
```
|
||||
|
||||
## 其他问题
|
||||
|
||||
### 元素超出页面范围
|
||||
|
||||
**警告**:元素右边界超出: 10.50 > 10
|
||||
|
||||
**说明**:元素的右边界超出了幻灯片宽度
|
||||
|
||||
**解决方法**:
|
||||
- 调整元素的 `box` 尺寸
|
||||
- 确保元素在页面范围内
|
||||
- 0.1 英寸内的超出是允许的
|
||||
|
||||
```yaml
|
||||
# 16:9 幻灯片,最大宽度 10"
|
||||
# 错误示例
|
||||
box: [1, 1, 9, 1] # 右边界 = 1 + 9 = 10"(警告)
|
||||
|
||||
# 正确示例
|
||||
box: [1, 1, 8.9, 1] # 右边界 = 1 + 8.9 = 9.9"(正常)
|
||||
```
|
||||
|
||||
### 模板名称冲突
|
||||
|
||||
**警告**:模板名称冲突: 'title-slide'
|
||||
|
||||
**说明**:内联和外部模板同名
|
||||
|
||||
**解决方法**:
|
||||
- 重命名其中一个模板
|
||||
- 系统会优先使用内联模板
|
||||
|
||||
### 字体大小过小
|
||||
|
||||
**警告**:字体大小过小: 5
|
||||
|
||||
**说明**:字体小于 6pt 可能难以阅读
|
||||
|
||||
**解决方法**:
|
||||
- 增加字体大小到至少 6pt
|
||||
- 建议使用 12pt 以上
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 使用验证功能
|
||||
|
||||
```bash
|
||||
# 在转换前验证
|
||||
uv run yaml2pptx.py check presentation.yaml
|
||||
```
|
||||
|
||||
### 使用预览模式
|
||||
|
||||
```bash
|
||||
# 实时查看效果
|
||||
uv run yaml2pptx.py preview presentation.yaml
|
||||
```
|
||||
|
||||
### 检查 YAML 语法
|
||||
|
||||
使用在线 YAML 验证工具:
|
||||
- https://www.yamllint.com/
|
||||
- https://yaml-online-parser.appspot.com/
|
||||
|
||||
### 查看详细错误
|
||||
|
||||
使用 `-v` 参数查看详细输出:
|
||||
|
||||
```bash
|
||||
uv run python -c "
|
||||
import yaml
|
||||
with open('presentation.yaml') as f:
|
||||
yaml.safe_load(f)
|
||||
"
|
||||
```
|
||||
|
||||
## 获取帮助
|
||||
|
||||
如果以上方法无法解决问题:
|
||||
|
||||
1. 查看 [开发文档](../development/) 了解更多信息
|
||||
2. 检查 GitHub Issues
|
||||
3. 提交新的 Issue,包含:
|
||||
- 完整的错误信息
|
||||
- YAML 文件内容(脱敏后)
|
||||
- 使用的命令
|
||||
- 系统环境信息
|
||||
|
||||
[返回文档索引](../README.md)
|
||||
221
docs/user-guide.md
Normal file
221
docs/user-guide.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# 用户指南
|
||||
|
||||
本指南提供 yaml2pptx 的完整使用说明。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **YAML 声明式语法** - 使用简单易读的 YAML 定义演示文稿
|
||||
- **智能验证** - 转换前自动检查 YAML 文件,提前发现问题
|
||||
- **模板系统** - 支持参数化模板,复用幻灯片布局
|
||||
- **丰富的元素类型** - 文本、图片、形状、表格
|
||||
- **实时预览** - 浏览器预览模式,支持热重载
|
||||
- **灵活尺寸** - 支持 16:9 和 4:3 两种宽高比
|
||||
- **模块化架构** - 易于扩展和维护
|
||||
|
||||
## 安装
|
||||
|
||||
本工具使用 [uv](https://github.com/astral-sh/uv) 管理依赖。项目依赖在 pyproject.toml 中声明,运行时会自动安装所需的 Python 包。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 转换 YAML 为 PPTX
|
||||
|
||||
```bash
|
||||
# 转换 YAML 为 PPTX
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx
|
||||
|
||||
# 自动生成输出文件名
|
||||
uv run yaml2pptx.py convert presentation.yaml
|
||||
|
||||
# 使用模板库
|
||||
uv run yaml2pptx.py convert presentation.yaml output.pptx --template ./templates.yaml
|
||||
```
|
||||
|
||||
### 实时预览
|
||||
|
||||
```bash
|
||||
# 启动预览服务器(自动打开浏览器)
|
||||
uv run yaml2pptx.py preview presentation.yaml
|
||||
|
||||
# 指定端口
|
||||
uv run yaml2pptx.py preview presentation.yaml --port 8080
|
||||
|
||||
# 允许局域网访问
|
||||
uv run yaml2pptx.py preview presentation.yaml --host 0.0.0.0
|
||||
|
||||
# 不自动打开浏览器
|
||||
uv run yaml2pptx.py preview presentation.yaml --no-browser
|
||||
```
|
||||
|
||||
预览模式会自动监听文件变化,修改 YAML 文件后浏览器会自动刷新。
|
||||
|
||||
### 验证功能
|
||||
|
||||
在转换前验证 YAML 文件,提前发现问题:
|
||||
|
||||
```bash
|
||||
# 独立验证命令
|
||||
uv run yaml2pptx.py check presentation.yaml
|
||||
|
||||
# 使用模板时验证
|
||||
uv run yaml2pptx.py check presentation.yaml --template ./templates.yaml
|
||||
```
|
||||
|
||||
验证功能会检查:
|
||||
- YAML 语法和结构
|
||||
- 元素是否超出页面范围
|
||||
- 图片和模板文件是否存在
|
||||
- 颜色格式是否正确
|
||||
- 字体大小是否合理
|
||||
- 表格数据是否一致
|
||||
|
||||
**自动验证**:转换时默认会自动验证,如果发现错误会终止转换。可以使用 `--skip-validation` 跳过验证:
|
||||
|
||||
```bash
|
||||
# 跳过自动验证
|
||||
uv run yaml2pptx.py convert presentation.yaml --skip-validation
|
||||
```
|
||||
|
||||
**验证结果示例**:
|
||||
|
||||
```
|
||||
正在检查 YAML 文件...
|
||||
|
||||
- 错误 (2):
|
||||
[幻灯片 2, 元素 1] 无效的颜色格式: red (应为 #RRGGBB)
|
||||
[幻灯片 3, 元素 2] 图片文件不存在: logo.png
|
||||
|
||||
- 警告 (1):
|
||||
[幻灯片 1, 元素 1] 元素右边界超出: 10.50 > 10
|
||||
|
||||
检查完成: 发现 2 个错误, 1 个警告
|
||||
```
|
||||
|
||||
- **ERROR**:阻止转换的严重问题(文件不存在、语法错误等)
|
||||
- **WARNING**:影响视觉效果的问题(元素超出页面、字体太小等)
|
||||
- **INFO**:优化建议
|
||||
|
||||
## YAML 语法基础
|
||||
|
||||
### 最小示例
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9" # 或 "4:3"
|
||||
|
||||
slides:
|
||||
- background:
|
||||
color: "#ffffff"
|
||||
elements:
|
||||
- type: text
|
||||
box: [1, 1, 8, 1]
|
||||
content: "Hello, World!"
|
||||
font:
|
||||
size: 44
|
||||
bold: true
|
||||
color: "#333333"
|
||||
align: center
|
||||
```
|
||||
|
||||
### description 字段
|
||||
|
||||
`metadata.description` 字段用于描述整个演示文稿的概要和用途,仅用于文档目的,不影响生成的 PPTX 文件:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
description: "2024年度项目进展总结,包含背景、成果和展望"
|
||||
```
|
||||
|
||||
### 使用模板
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
size: "16:9"
|
||||
|
||||
slides:
|
||||
- template: title-slide
|
||||
vars:
|
||||
title: "我的演示文稿"
|
||||
subtitle: "使用 yaml2pptx 创建"
|
||||
author: "张三"
|
||||
|
||||
- template: content-slide
|
||||
vars:
|
||||
title: "功能概览"
|
||||
content: "yaml2pptx 支持多种元素类型..."
|
||||
```
|
||||
|
||||
## 使用技巧
|
||||
|
||||
1. **开发流程**:使用预览模式实时查看效果,确认无误后再生成 PPTX
|
||||
2. **模板复用**:为常用布局创建模板,保持演示文稿风格一致
|
||||
3. **相对路径**:图片路径相对于 YAML 文件位置,便于项目管理
|
||||
4. **文本换行**:文本框默认启用自动换行,无需手动处理长文本
|
||||
|
||||
## 扩展性
|
||||
|
||||
yaml2pptx 采用模块化架构,易于扩展:
|
||||
|
||||
- **添加新元素类型**:定义新的元素数据类和渲染方法
|
||||
- **添加新渲染器**:支持输出到其他格式(如 PDF)
|
||||
- **自定义模板**:创建符合你需求的模板库
|
||||
|
||||
详见 [开发文档](../development/extending.md)。
|
||||
|
||||
## 依赖项
|
||||
|
||||
- `python-pptx` - PowerPoint 文件生成
|
||||
- `pyyaml` - YAML 解析
|
||||
- `flask` - 预览服务器
|
||||
- `watchdog` - 文件监听
|
||||
|
||||
依赖在 pyproject.toml 中声明,由 uv 自动管理,无需手动安装。
|
||||
|
||||
## 测试
|
||||
|
||||
项目包含完整的测试套件,使用 pytest 框架。
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 安装测试依赖
|
||||
uv pip install -e ".[dev]"
|
||||
|
||||
# 运行所有测试
|
||||
uv run pytest
|
||||
|
||||
# 运行特定类型的测试
|
||||
uv run pytest tests/unit/ # 单元测试
|
||||
uv run pytest tests/integration/ # 集成测试
|
||||
uv run pytest tests/e2e/ # 端到端测试
|
||||
|
||||
# 运行特定测试文件
|
||||
uv run pytest tests/unit/test_elements.py
|
||||
|
||||
# 显示详细输出
|
||||
uv run pytest -v
|
||||
|
||||
# 显示测试覆盖率
|
||||
uv run pytest --cov=. --cov-report=html
|
||||
```
|
||||
|
||||
### 测试结构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # 单元测试 - 测试各模块独立功能
|
||||
├── integration/ # 集成测试 - 测试模块间协作
|
||||
├── e2e/ # 端到端测试 - 测试完整用户场景
|
||||
└── fixtures/ # 测试数据
|
||||
```
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎贡献代码、报告问题或提出建议!
|
||||
|
||||
开发者请参阅 [开发文档](../development/) 了解代码结构和开发规范。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
Reference in New Issue
Block a user