PPT skill v3: 红色硬约束 + 品牌条 + 强制尾页 + Iconify 图标库
修复上一轮生成的实际问题: 模型擅自把红色换成蓝色 / 内页全裸白 / 缺 Q&A 尾页;
并补齐"个性化图标"能力 (此前只有 MSO_SHAPE + unicode 字形,业务概念图标缺位)。
- SKILL.md: 红色主题改硬约束 + ⛔ BLOCKING 八条对齐 (bundled 推荐, 等用户拍板),
封面/尾页改强制项, 不算在 5-8 页正文预算内
- layouts.md: 加 apply_brand(slide, kind) 4 模式品牌条 (cover/inner/section/end),
9 个版式起手必调, 消灭裸白页
- 图标库: 新增 fetch_icon.py 走 Iconify CDN (tabler/lucide/heroicons 等 150+ 集),
主题色染色, 缓存到 assets/icons/, 配 INDEX.md 推荐清单
- icons.md: 移除 MSO_SHAPE 当业务图标的部分 (PENTAGON/LIGHTNING_BOLT 等视觉陈旧),
三层降级 → 两层 (Iconify / unicode 兜底); MSO_SHAPE 退为 layouts.md helper 内部原语
- canvas_presets.md 并入 design_principles.md §0 (减少零碎文件)
- .gitignore: spec_lock.md 与根目录 *.pptx 不入库 (PPT skill 工作产物)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a66849953
commit
0971a500e7
|
|
@ -40,6 +40,8 @@ desktop.ini
|
||||||
*.tmp.pptx
|
*.tmp.pptx
|
||||||
output.pptx
|
output.pptx
|
||||||
untitled*.pptx
|
untitled*.pptx
|
||||||
|
spec_lock.md
|
||||||
|
/*.pptx
|
||||||
|
|
||||||
# 用户本地工具脚本 / 规划文件 (不入库)
|
# 用户本地工具脚本 / 规划文件 (不入库)
|
||||||
规划.docx
|
规划.docx
|
||||||
|
|
|
||||||
23
PROGRESS.md
23
PROGRESS.md
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
> 配合 `DESIGN.md` 阅读。本文件记录已完成的事、关键决策、与原设计的偏差。
|
> 配合 `DESIGN.md` 阅读。本文件记录已完成的事、关键决策、与原设计的偏差。
|
||||||
|
|
||||||
最后更新: 2026-05-06 (PPT skill 完善:references + scripts;v2 加图标系统 + 安全区 + 越界检测 + 默认红色主题)
|
最后更新: 2026-05-06 (PPT skill v3:红色硬约束 + ⛔ blocking + canvas 合并 + apply_brand 品牌条 + 强制尾页 + Iconify 图标库)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -68,15 +68,18 @@
|
||||||
- 三个 skill,均按 WHY+WHAT 风格写,不写 Step 1/2/3:
|
- 三个 skill,均按 WHY+WHAT 风格写,不写 Step 1/2/3:
|
||||||
- `skills/coding/SKILL.md`
|
- `skills/coding/SKILL.md`
|
||||||
- `skills/ppt/` —— 完整渐进披露结构(借鉴 hugohe3/ppt-master 的两阶段 + spec lock 思路):
|
- `skills/ppt/` —— 完整渐进披露结构(借鉴 hugohe3/ppt-master 的两阶段 + spec lock 思路):
|
||||||
- `SKILL.md`(两阶段工作流 + 八条对齐 + 默认红色主题 + 反模式)
|
- `SKILL.md`(两阶段工作流 + ⛔ blocking 八条对齐 + 红色硬约束 + 强制封面/尾页 + 反模式)
|
||||||
- `references/design_principles.md`(字号/配色/留白/图表 + §4.1 **字数预算表**)
|
- `references/design_principles.md`(§0 画布 + §1-9 字号/配色/留白/字数预算/图表)
|
||||||
- `references/canvas_presets.md`(16:9 / 4:3 / 9:16 等画布表)
|
- `references/layouts.md`(9 种版式 + `apply_brand(slide, kind)` 品牌条覆盖每页 + assert_inside)
|
||||||
- `references/layouts.md`(9 种轻量版式 + **safe area 起手** + assert_inside / TEXT_TO_FIT_SHAPE 兜底)
|
- `references/icons.md`(业务图标两层:Iconify / unicode 兜底;MSO_SHAPE 退为 layouts.md helper 内部几何原语)
|
||||||
- `references/icons.md`(MSO_SHAPE 图标目录 + unicode 字形表 + 5 个标准图标 helper)
|
- `assets/icons/INDEX.md`(本地图标库索引 + 推荐清单)
|
||||||
- `scripts/quality_check.py`(页数/标题/bullet/字号/配色 + **shape 越界 + 文本溢出估算**)
|
- `scripts/fetch_icon.py`(Iconify CDN 拉个性化 SVG/PNG,主题色染色,缓存本地)
|
||||||
- `scripts/source_to_md.py`(PDF/DOCX/PPTX/URL → Markdown,策略阶段输入)
|
- `scripts/quality_check.py`(页数/标题/bullet/字号/配色 + shape 越界 + 文本溢出估算)
|
||||||
- `scripts/render_icon.py`(unicode 字形 → 透明 PNG,MSO_SHAPE 兜底)
|
- `scripts/source_to_md.py`(PDF/DOCX/PPTX/URL → Markdown)
|
||||||
- **默认配色**:商务红 PRIMARY `#C00000` / SECONDARY `#E15554` / ACCENT `#FFC107`
|
- `scripts/render_icon.py`(unicode 字形 → 透明 PNG,兜底)
|
||||||
|
- **配色**:商务红 PRIMARY `#C00000` / SECONDARY `#E15554` / ACCENT `#FFC107` —— v3 改为硬约束,模型不能基于"场景判断"自行换色
|
||||||
|
- **品牌条**:v3 加 `apply_brand(slide, kind)`,所有版式起手必调,4 种模式 (cover/inner/section/end) 覆盖左竖条 + 浅底,不再裸白纸
|
||||||
|
- **图标**:v3 加 Iconify CDN 拉取链路 (tabler/lucide/heroicons/material-symbols 等 150+ 集),本地缓存复用
|
||||||
- `skills/proposal/SKILL.md`(含工作目录约定 + 字数表 + python-docx 合并模板)
|
- `skills/proposal/SKILL.md`(含工作目录约定 + 字数表 + python-docx 合并模板)
|
||||||
|
|
||||||
### 7. System Prompt
|
### 7. System Prompt
|
||||||
|
|
|
||||||
|
|
@ -8,79 +8,104 @@ description: 生成 PowerPoint 演示文稿 (.pptx)。当用户要求做汇报 P
|
||||||
把材料变成可演示的 .pptx。**先定调,再出稿,再验收** —— 不要一口气把整份 deck 丢出去。
|
把材料变成可演示的 .pptx。**先定调,再出稿,再验收** —— 不要一口气把整份 deck 丢出去。
|
||||||
|
|
||||||
## 资源
|
## 资源
|
||||||
- `references/design_principles.md` —— 字号/颜色/层级/留白/字数预算等硬规则,出稿前先翻一遍
|
- `references/design_principles.md` —— 画布尺寸 + 字号/配色/留白/字数预算等硬规则
|
||||||
- `references/canvas_presets.md` —— 16:9 / 4:3 / 9:16 / A4 等画布尺寸表
|
- `references/layouts.md` —— 9 种版式的 python-pptx 起手代码 + 安全区/越界保护 + `apply_brand` 品牌条
|
||||||
- `references/layouts.md` —— 9 种常用版式的 python-pptx 起手代码 + safe area 辅助 (封面/目录/分章/要点/双栏/图表/图片/金句/结尾)
|
- `references/icons.md` —— 业务图标两层:Iconify (在线/本地缓存) / unicode 字形兜底
|
||||||
- `references/icons.md` —— MSO_SHAPE 图标目录 + unicode 字形表 (替代大色块的轻量装饰)
|
- `assets/icons/` —— 本地图标缓存 (Iconify 拉过的图存这,见 `INDEX.md` 推荐清单)
|
||||||
- `scripts/source_to_md.py` —— 可执行,把 PDF/DOCX/PPTX/URL 转成干净 Markdown 再做素材
|
- `scripts/source_to_md.py` —— PDF/DOCX/PPTX/URL → 干净 Markdown
|
||||||
- `scripts/render_icon.py` —— 可执行,unicode 字形 → 透明 PNG (MSO_SHAPE 覆盖不到时兜底)
|
- `scripts/fetch_icon.py` —— 从 Iconify CDN 拉 SVG/PNG (染主题色,缓存本地)
|
||||||
- `scripts/quality_check.py` —— 可执行,产物 .pptx 出来后跑一遍验收 (含越界 / 文本溢出检测)
|
- `scripts/render_icon.py` —— unicode 字形 → 透明 PNG (Iconify 没有时兜底)
|
||||||
|
- `scripts/quality_check.py` —— 产物 .pptx 验收 (越界 / 文本溢出 / 颜色一致)
|
||||||
|
|
||||||
## 默认主题
|
## 默认主题 — 商务红 (硬约束)
|
||||||
**商务红** —— PRIMARY `#C00000` / SECONDARY `#E15554` / ACCENT `#FFC107`。除非 spec_lock 指定其它配色,layouts.md 起手代码就用这套。其它备选见 `design_principles.md` §2。
|
|
||||||
|
**主色 `#C00000` / 辅色 `#E15554` / 强调色 `#FFC107`。**
|
||||||
|
|
||||||
|
⛔ **不允许擅自换色**。除非满足以下任一条件,否则 spec_lock 必须填这套红色:
|
||||||
|
- 用户在请求里**明确**点名其它配色 (例:"做成蓝色"、"用我们公司的紫色")
|
||||||
|
- 用户提供素材里有明确的 brand guideline / 配色卡
|
||||||
|
|
||||||
|
**禁止的自我合理化**(都属违规):
|
||||||
|
- "这个场景蓝色更专业" / "学术汇报红色不合适" / "财务用蓝更稳重"
|
||||||
|
- "我觉得 XX 主题更适合"
|
||||||
|
|
||||||
|
要换色,**先问用户**,不要在 spec_lock 里塞自己的偏好。其它备选见 `design_principles.md §2`。
|
||||||
|
|
||||||
## 两阶段工作流
|
## 两阶段工作流
|
||||||
|
|
||||||
### 阶段一: 策略 (Strategist)
|
### 阶段一: 策略 (Strategist) — 八条对齐
|
||||||
产物:`spec_lock.md` —— 整个 deck 的"宪法",执行阶段每生成一页前都要重读。
|
|
||||||
|
|
||||||
**八条对齐**(不全部确认完,不开工):
|
产物:`spec_lock.md` —— 整个 deck 的"宪法",阶段二每页前都要重读。
|
||||||
1. **画布**: 16:9 / 4:3 / 9:16 (默认 16:9,见 canvas_presets.md)
|
|
||||||
2. **页数**: 默认 5-8 页;长报告再加,但每超 1 页就要问一次"这页非加不可吗"
|
|
||||||
3. **受众**: 领导汇报 / 同行评审 / 大众科普 / 客户 pitch —— 决定信息密度和措辞
|
|
||||||
4. **风格**: 商务正式 / 学术严谨 / 现代简约 / 极简留白 (默认现代简约)
|
|
||||||
5. **配色**: 主色 + 辅色 + 强调色,三色封顶。给具体 hex,不要"蓝色系"这种话
|
|
||||||
6. **字体**: 中文标题/正文,英文标题/正文。Win 默认 微软雅黑 + Arial
|
|
||||||
7. **图标/插图**: 是否要、风格 (线性/扁平/拟物)、来源 (用户提供 / 不用)
|
|
||||||
8. **图表**: 数据 ≥ 3 个点的页面默认配图;明确哪几页要图
|
|
||||||
|
|
||||||
写入 `spec_lock.md` 后给用户看一眼再继续。**spec_lock 写定后不要再改**,有冲突回头跟用户重新对齐。
|
按下表**一次性给出推荐方案**,然后 ⛔ **BLOCKING:等用户确认/修改后才能进阶段二**。不要一条一条问。
|
||||||
|
|
||||||
### 阶段二: 执行 (Executor)
|
| # | 项 | 默认值 |
|
||||||
**逐页生成**,不是一次性 dump 全 deck。每页前先读一次 `spec_lock.md`,然后:
|
|---|----|-------|
|
||||||
|
| 1 | 画布 | **16:9** (13.33×7.5 in) |
|
||||||
|
| 2 | 页数 | **封面 + 5-8 页正文 + 尾页(Q&A)** = 共 7-10 页。**封面 / 尾页强制必有**,不在 5-8 页预算里 |
|
||||||
|
| 3 | 受众 | 看材料推断:领导汇报 / 同行评审 / 客户 pitch |
|
||||||
|
| 4 | 风格 | **现代简约** (白底 + 细线 + 留白) |
|
||||||
|
| 5 | 配色 | **商务红** `#C00000` `#E15554` `#FFC107` (见上"默认主题") |
|
||||||
|
| 6 | 字体 | **微软雅黑 + Arial** |
|
||||||
|
| 7 | 图标 | **MSO_SHAPE 几何形状** (无外部图片资源) |
|
||||||
|
| 8 | 图表 | 数据 ≥ 3 个点的页用 matplotlib 配图 |
|
||||||
|
|
||||||
1. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx,append slide,save)
|
把这 8 项写进 `spec_lock.md`,以表格形式给用户预览,问一句"按这个开干?"。**spec_lock 写定后不再改**,有冲突回头跟用户重新对齐。
|
||||||
2. 跑完报这一页的:版式、标题、要点条数、是否含图
|
|
||||||
3. 用户确认 / 微调后再下一页
|
|
||||||
|
|
||||||
**为什么逐页?** 一次性出全 deck 很容易越到后面越糊。逐页能让用户在第 2 页就发现风格不对,而不是看完 8 页才推翻重来。
|
### 阶段二: 执行 (Executor) — 逐页生成
|
||||||
|
|
||||||
**例外**: 用户明确说 "你别问,直接全做了" —— 那就一次跑完,但跑完后必须用 `quality_check.py` 验收。
|
每页前 **必须 read 一次 `spec_lock.md`**,只用里面定的颜色/字体/图标 —— **不允许凭记忆或临时发挥**。这条规则是为了对抗长 deck 中的上下文漂移。
|
||||||
|
|
||||||
|
每页流程:
|
||||||
|
1. 读 `spec_lock.md` (即使刚读过)
|
||||||
|
2. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx → append slide → save)
|
||||||
|
3. 报这一页:版式、标题、要点条数、是否含图
|
||||||
|
4. 用户确认 / 微调后再下一页
|
||||||
|
|
||||||
|
**为什么逐页?** 一次性出全 deck 中途改方向就要全推翻;逐页能让用户在第 2 页就发现问题。
|
||||||
|
|
||||||
|
**例外**: 用户明确说"别问,直接全做了" —— 一次跑完,但跑完必须用 `quality_check.py` 验收。
|
||||||
|
|
||||||
### 阶段三: 验收
|
### 阶段三: 验收
|
||||||
- `python scripts/quality_check.py <output.pptx>` —— 检页数/标题/bullet 条数/文件大小
|
|
||||||
- 不通过的项,回头 edit 对应页
|
|
||||||
|
|
||||||
## 设计原则 (硬规则)
|
```bash
|
||||||
|
python scripts/quality_check.py <output.pptx> --spec spec_lock.md
|
||||||
|
```
|
||||||
|
|
||||||
|
不通过的项,回头 edit 对应页。
|
||||||
|
|
||||||
|
## 设计原则 (硬规则速查)
|
||||||
- **每页一个核心信息**: 一页讲一件事,塞两件就拆页
|
- **每页一个核心信息**: 一页讲一件事,塞两件就拆页
|
||||||
- **bullet ≤ 5 条**: 超过就拆页或改成图表/双栏
|
- **bullet ≤ 5 条**: 超过就拆页或改成图表/双栏
|
||||||
- **正文不写完整段落**: 列要点;长句留给演讲者口述
|
- **正文不写完整段落**: 列要点;长句留给演讲者口述
|
||||||
- **数据 ≥ 3 个点应有图表**: 用 matplotlib 生成 .png 嵌入
|
- **数据 ≥ 3 个点应有图表**: 用 matplotlib 生成 .png 嵌入
|
||||||
- **中文标题 ≤ 30 字** / **英文标题 ≤ 12 词**
|
- **中文标题 ≤ 30 字**
|
||||||
- **配色三色封顶**: 主色 + 辅色 + 强调色,其他都用灰阶
|
- **配色三色封顶**: 主 + 辅 + 强调,其他用灰阶
|
||||||
- **少用大色块,多用细线 + 图标 + 留白**: 满铺色块只在封面/分章/结尾克制使用
|
- **少用大色块,多用细线 + 图标 + 留白**
|
||||||
- **图标走 MSO_SHAPE**: 原生形状可编辑、可缩放;复杂图标走 `render_icon.py`
|
- **图标走 MSO_SHAPE**: 矢量、可编辑;复杂图标走 `render_icon.py`
|
||||||
- **Shape 不能越界**: `layouts.md` 的起手代码用 `assert_inside` 在生成时即报错;最终必跑 `quality_check.py`
|
- **Shape 不能越界**: `layouts.md` 起手代码用 `assert_inside` 在生成时即报错
|
||||||
- **字数按预算来**: 写 bullet 前查 `design_principles.md §4.1` 的字数预算表,溢出靠拆条不靠收缩字号
|
- **字数按预算来**: 写 bullet 前查 `design_principles.md §4.1` 字数预算表
|
||||||
- 详细规则见 `references/design_principles.md`
|
- 详细规则见 `references/design_principles.md`
|
||||||
|
|
||||||
## 工作目录约定
|
## 工作目录约定
|
||||||
```
|
```
|
||||||
<task_dir>/
|
<task_dir>/
|
||||||
├── source.md # 阶段一: source_to_md.py 转出的素材
|
├── source.md # source_to_md.py 转出的素材
|
||||||
├── spec_lock.md # 阶段一: 八条对齐落定
|
├── spec_lock.md # 八条对齐落定
|
||||||
├── slides/
|
├── slides/
|
||||||
│ └── chart_p3.png # 各页用到的图片素材
|
│ └── chart_p3.png # 各页用到的图片素材
|
||||||
└── <topic>.pptx # 最终产物 (文件名按主题命名,不要 untitled.pptx)
|
└── <topic>.pptx # 最终产物 (按主题命名)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 反模式
|
## 反模式
|
||||||
- 用户没给材料就开始硬编内容
|
- 用户没给材料就开始硬编内容
|
||||||
- 八条没对齐就跑 python-pptx
|
- 八条没对齐就跑 python-pptx
|
||||||
|
- **基于"场景判断"自行换配色**(见上"默认主题"违规清单)
|
||||||
|
- **缺封面 / 缺尾页(Q&A)** —— 两端都是强制项,不算在正文页数预算内
|
||||||
|
- **裸白纸版式** —— 所有版式起手都必须 `apply_brand(slide, kind)`,见 layouts.md
|
||||||
- 一个 `run_python` 出整 deck (中途改方向就要全推翻)
|
- 一个 `run_python` 出整 deck (中途改方向就要全推翻)
|
||||||
- 跑完不做 `quality_check.py` 就交付
|
- 跑完不做 `quality_check.py` 就交付
|
||||||
- 起名 `output.pptx` / `untitled.pptx` —— 务必按主题给文件名
|
- 起名 `output.pptx` / `untitled.pptx` —— 务必按主题给文件名
|
||||||
- 文字塞满整张幻灯片 —— 留白本身是设计
|
|
||||||
|
|
||||||
## 输出
|
## 输出
|
||||||
完成后告诉用户:文件路径、页数、用到的版式列表、是否有未满足的 spec 项。问一句要不要再改。
|
完成后告诉用户:文件路径、页数、用到的版式列表、是否有未满足的 spec 项。问一句要不要再改。
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
# 本地图标库
|
||||||
|
|
||||||
|
> 这里缓存通过 `scripts/fetch_icon.py` 从 Iconify 拉下来的图标。**首次为空**,模型按需下载缓存,再次用时直接读本地不再发请求。
|
||||||
|
|
||||||
|
## 缓存命名规约
|
||||||
|
|
||||||
|
```
|
||||||
|
<set>_<name>_<colorhex>_<sizepx>.png
|
||||||
|
<set>_<name>_<colorhex>.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
例: `tabler_rocket_C00000_128.png` / `lucide_target_FFC107_96.svg`
|
||||||
|
|
||||||
|
## 推荐图标清单 (按业务主题)
|
||||||
|
|
||||||
|
按下面 3 行命令拉取首批最常用 18 个,够覆盖 80% 商务汇报场景:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ICONS_DIR=skills/ppt/assets/icons
|
||||||
|
|
||||||
|
# 战略 / 目标 / 启动
|
||||||
|
for n in target rocket flag bulb; do
|
||||||
|
python skills/ppt/scripts/fetch_icon.py $n --set tabler --color C00000 --size 128 \
|
||||||
|
-o "$ICONS_DIR/tabler_${n}_C00000_128.png"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 数据 / 趋势 / 报表
|
||||||
|
for n in chart-bar chart-line trending-up calculator; do
|
||||||
|
python skills/ppt/scripts/fetch_icon.py $n --set tabler --color C00000 --size 128 \
|
||||||
|
-o "$ICONS_DIR/tabler_${n}_C00000_128.png"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 团队 / 流程 / 时间
|
||||||
|
for n in users settings calendar clock check shield-check arrow-right alert-triangle currency-yuan circle-check; do
|
||||||
|
python skills/ppt/scripts/fetch_icon.py $n --set tabler --color C00000 --size 128 \
|
||||||
|
-o "$ICONS_DIR/tabler_${n}_C00000_128.png"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## 图标集对照
|
||||||
|
|
||||||
|
| 集名 | 风格 | 数量 | License |
|
||||||
|
|-----|-----|-----|---------|
|
||||||
|
| **tabler** ⭐ 推荐 | 描边、商务、克制 | 4500+ | MIT |
|
||||||
|
| lucide | 描边、克制 | 1500+ | ISC |
|
||||||
|
| heroicons | Tailwind 风、双重粗细 | 300+ | MIT |
|
||||||
|
| material-symbols | Google Material 描边/填充 | 3000+ | Apache 2.0 |
|
||||||
|
| carbon | IBM、克制专业 | 2000+ | Apache 2.0 |
|
||||||
|
| fluent | Microsoft、温和现代 | 4000+ | MIT |
|
||||||
|
| mdi | Material Design Icons 社区 | 7000+ | Apache 2.0 |
|
||||||
|
|
||||||
|
## 浏览找名字
|
||||||
|
|
||||||
|
打开 https://icon-sets.iconify.design/ 搜中英文关键词,复制图标名 (如 `tabler:rocket`),回来用 `--set tabler rocket` 拉。
|
||||||
|
|
||||||
|
## 主题色变体
|
||||||
|
|
||||||
|
同一图标按主色/辅色/强调色/灰各拉一份,文件名只在 `<colorhex>` 段不同:
|
||||||
|
- `tabler_target_C00000_128.png` (主红)
|
||||||
|
- `tabler_target_E15554_128.png` (辅红)
|
||||||
|
- `tabler_target_FFC107_128.png` (强调金)
|
||||||
|
- `tabler_target_595959_128.png` (灰)
|
||||||
|
|
||||||
|
## 用图标的硬规则
|
||||||
|
|
||||||
|
见 `references/icons.md §C` —— 风格统一、颜色限定、大小克制、不替表意、避 emoji。
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# 画布尺寸预设
|
|
||||||
|
|
||||||
> 阶段一选画布时查这张表。**画布定了之后所有版式按这个尺寸算坐标**,不要中途改。
|
|
||||||
|
|
||||||
## 标准尺寸表
|
|
||||||
|
|
||||||
| 用途 | 比例 | 宽×高 (英寸) | python-pptx | 说明 |
|
|
||||||
|-----|------|------------|------------|------|
|
|
||||||
| **现代商务汇报** | 16:9 | 13.33 × 7.5 | `Inches(13.33), Inches(7.5)` | **默认选这个** |
|
|
||||||
| 老投影仪 | 4:3 | 10 × 7.5 | `Inches(10), Inches(7.5)` | 老会议室、教学场景 |
|
|
||||||
| 竖屏手机 / 朋友圈 | 9:16 | 7.5 × 13.33 | `Inches(7.5), Inches(13.33)` | 移动端阅读、视频号封面 |
|
|
||||||
| 小红书 | 3:4 | 7.5 × 10 | `Inches(7.5), Inches(10)` | 单图阅读 |
|
|
||||||
| 微信公众号长图 | 1:n | 7.5 × 7.5 起 | `Inches(7.5), Inches(7.5)` | 单页或拼接 |
|
|
||||||
| 海报 (A4 横) | √2:1 | 11.69 × 8.27 | `Inches(11.69), Inches(8.27)` | 打印 |
|
|
||||||
| 海报 (A4 竖) | 1:√2 | 8.27 × 11.69 | `Inches(8.27), Inches(11.69)` | 打印 |
|
|
||||||
| 大屏宣讲 | 16:9 高 dpi | 同 16:9 | 同上 | 字号上调 4-6pt |
|
|
||||||
|
|
||||||
## 选画布的几条经验
|
|
||||||
|
|
||||||
- 不知道选哪个 —— **16:9**,99% 场合通吃
|
|
||||||
- 用户在投影仪墙上看 —— 16:9
|
|
||||||
- 用户在电脑屏幕上看 —— 16:9 或 4:3
|
|
||||||
- 用户在手机上看 —— 9:16
|
|
||||||
- 用户要打印散发 —— A4 横或 A4 竖
|
|
||||||
- 用户说"做个图发朋友圈" —— 3:4 或 1:1,不是 PPT 范畴但 python-pptx 也能干
|
|
||||||
|
|
||||||
## python-pptx 画布初始化
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pptx import Presentation
|
|
||||||
from pptx.util import Inches, Pt
|
|
||||||
|
|
||||||
prs = Presentation()
|
|
||||||
# 16:9 默认
|
|
||||||
prs.slide_width = Inches(13.33)
|
|
||||||
prs.slide_height = Inches(7.5)
|
|
||||||
|
|
||||||
# 4:3 改这两行
|
|
||||||
# prs.slide_width = Inches(10)
|
|
||||||
# prs.slide_height = Inches(7.5)
|
|
||||||
|
|
||||||
# 9:16 改这两行
|
|
||||||
# prs.slide_width = Inches(7.5)
|
|
||||||
# prs.slide_height = Inches(13.33)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安全边距 (各画布通用)
|
|
||||||
|
|
||||||
- 左右边距: **画布宽 × 0.05** (16:9 即 0.67 寸)
|
|
||||||
- 上下边距: **画布高 × 0.07** (16:9 即 0.5 寸)
|
|
||||||
- 内容区域: 画布尺寸减去四周边距,所有元素都摆在这个矩形内
|
|
||||||
|
|
||||||
## 字号随画布缩放
|
|
||||||
|
|
||||||
如果画布超过 16:9 默认尺寸 (比如做 4K 大屏),**所有字号 × (实际宽 / 13.33)**。模型自己换算,不要硬抄默认表。
|
|
||||||
|
|
@ -2,6 +2,18 @@
|
||||||
|
|
||||||
> 出稿前过一遍。**这些不是建议,是工程约束** —— 模型生成 PPT 最常见的失败模式都是违反这些规则。
|
> 出稿前过一遍。**这些不是建议,是工程约束** —— 模型生成 PPT 最常见的失败模式都是违反这些规则。
|
||||||
|
|
||||||
|
## 0. 画布 (默认 16:9)
|
||||||
|
|
||||||
|
| 用途 | 比例 | 宽×高 (英寸) | python-pptx |
|
||||||
|
|-----|------|------------|------------|
|
||||||
|
| **现代商务汇报** ⭐ 默认 | 16:9 | 13.33 × 7.5 | `Inches(13.33), Inches(7.5)` |
|
||||||
|
| 老投影 / 教学 | 4:3 | 10 × 7.5 | `Inches(10), Inches(7.5)` |
|
||||||
|
| 手机 / 视频号 | 9:16 | 7.5 × 13.33 | `Inches(7.5), Inches(13.33)` |
|
||||||
|
| 小红书 | 3:4 | 7.5 × 10 | `Inches(7.5), Inches(10)` |
|
||||||
|
| A4 横 / 竖 | √2:1 | 11.69 × 8.27 / 反 | 同左 |
|
||||||
|
|
||||||
|
不知道选哪个 → **16:9**。安全边距统一:左右 0.7 in,上下 0.5 in。**画布定了不要中途改**,后续坐标全按这个尺寸算。画布超 16:9 默认尺寸时所有字号 × `(实际宽 / 13.33)`。
|
||||||
|
|
||||||
## 1. 字号 (16:9 标准)
|
## 1. 字号 (16:9 标准)
|
||||||
|
|
||||||
| 元素 | 字号 (Pt) | 备注 |
|
| 元素 | 字号 (Pt) | 备注 |
|
||||||
|
|
|
||||||
|
|
@ -1,189 +1,97 @@
|
||||||
# 图标系统
|
# 图标系统 (两层)
|
||||||
|
|
||||||
> **首选 `MSO_SHAPE.*` —— PowerPoint 原生形状,矢量、可编辑、配色随主题。** 复杂图标(齿轮、放大镜、文件夹等无对应 MSO_SHAPE)再走 `render_icon.py` 用 unicode 字形栅格化为 PNG。
|
> 几何装饰 (圆点、徽章、品牌条、装饰线) 已在 `layouts.md` 起手块以 helper 封装 (`add_dot` / `add_badge` / `add_accent_line` / `add_rect`),直接调用,**不要重写**,**也不要把它们当"图标"用**。本文档处理的是真正的**业务概念图标** (火箭 / 目标 / 雷达 / 齿轮 / 盾牌 ...)
|
||||||
|
|
||||||
## A. MSO_SHAPE 图标目录
|
## 选图标两层降级
|
||||||
|
|
||||||
```python
|
```
|
||||||
from pptx.enum.shapes import MSO_SHAPE
|
1) Iconify 个性化图标 ── 业务概念 (火箭、目标、雷达、齿轮) → 见 §A
|
||||||
|
2) Unicode 字形兜底 ── Iconify 没有合适的 (✓ ✗ ★ → ↑) → 见 §B
|
||||||
```
|
```
|
||||||
|
|
||||||
### 标记类 (放在 bullet 前 / 标题旁)
|
整 deck 选**一个图标集**用到底,不要 tabler 跟 lucide 混用。
|
||||||
|
|
||||||
| 用途 | MSO_SHAPE | 说明 |
|
## §A. Iconify 个性化图标 (本地缓存 + 网络拉取)
|
||||||
|-----|-----------|------|
|
|
||||||
| 圆点 bullet | `OVAL` | 0.18×0.18 in,实心填充 |
|
|
||||||
| 方点 bullet | `RECTANGLE` | 0.16×0.16 in,实心 |
|
|
||||||
| 钻石点 | `DIAMOND` | 0.2×0.2 in |
|
|
||||||
| 对号 ✓ | `CHEVRON` 旋转 / 或用字形 | MSO 没有专门"check"形;用字形更清晰 |
|
|
||||||
| 加号 + | `MATH_PLUS` | 强调"新增"语境 |
|
|
||||||
| 星 ★ | `STAR_5_POINT` | 重点项;不要每页都用 |
|
|
||||||
| 心 | `HEART` | 用户向 / 软话题 |
|
|
||||||
|
|
||||||
### 箭头类 (流程 / 趋势)
|
### A1. 本地库
|
||||||
|
路径: `skills/ppt/assets/icons/`,详见 [INDEX.md](../assets/icons/INDEX.md)。
|
||||||
|
命名规约: `<set>_<name>_<colorhex>_<sizepx>.png`(如 `tabler_rocket_C00000_128.png`)
|
||||||
|
|
||||||
| 用途 | MSO_SHAPE | 说明 |
|
**用之前先 `glob` 检查本地有没有**,有就直接 `add_picture`,免去网络往返。
|
||||||
|-----|-----------|------|
|
|
||||||
| 右箭头 → | `RIGHT_ARROW` | 流程下一步 |
|
|
||||||
| 上箭头 ↑ | `UP_ARROW` | 增长 |
|
|
||||||
| 下箭头 ↓ | `DOWN_ARROW` | 下降 |
|
|
||||||
| 双向箭头 ↔ | `LEFT_RIGHT_ARROW` | 对比 / 关联 |
|
|
||||||
| 折线右箭头 | `BENT_ARROW` / `CURVED_RIGHT_ARROW` | 转折 |
|
|
||||||
| 五边形流程 | `PENTAGON` | 流程节点(横排) |
|
|
||||||
| V 形 | `CHEVRON` | 流程节点(空间紧) |
|
|
||||||
|
|
||||||
### 几何/装饰
|
### A2. fetch_icon.py 拉新图标
|
||||||
|
```bash
|
||||||
|
# 主红色 128px PNG (推荐)
|
||||||
|
python skills/ppt/scripts/fetch_icon.py rocket --set tabler --color C00000 \
|
||||||
|
--size 128 -o skills/ppt/assets/icons/tabler_rocket_C00000_128.png
|
||||||
|
|
||||||
| 用途 | MSO_SHAPE | 说明 |
|
# 强调色金黄
|
||||||
|-----|-----------|------|
|
python skills/ppt/scripts/fetch_icon.py target --set tabler --color FFC107 \
|
||||||
| 圆形头像底 | `OVAL` | 头像/数字徽章 |
|
--size 128 -o skills/ppt/assets/icons/tabler_target_FFC107_128.png
|
||||||
| 圆角矩形 | `ROUNDED_RECTANGLE` | 标签 / 按钮态 |
|
|
||||||
| 标注气泡 | `ROUNDED_RECTANGULAR_CALLOUT` | 引述 |
|
|
||||||
| 雷电 | `LIGHTNING_BOLT` | 突破 / 创新 |
|
|
||||||
| 太阳 | `SUN` | 机会 / 启示 |
|
|
||||||
| 月亮 | `MOON` | 夜晚 / 安静主题 |
|
|
||||||
| 云 | `CLOUD` | SaaS / 网络主题 |
|
|
||||||
| 禁止 | `NO_SYMBOL` | 反模式 / 禁止 |
|
|
||||||
| 笑脸 | `SMILEY_FACE` | 用户满意 |
|
|
||||||
|
|
||||||
### 引用/装饰
|
|
||||||
|
|
||||||
| 用途 | MSO_SHAPE | 说明 |
|
|
||||||
|-----|-----------|------|
|
|
||||||
| 大引号 | 字形 `"` 或 `LEFT_BRACE` | 金句页常用 |
|
|
||||||
| 横线分隔 | `RECTANGLE` 高 0.04 in | 标题下装饰线 |
|
|
||||||
| 竖线分隔 | `RECTANGLE` 宽 0.04 in | 双栏中线 |
|
|
||||||
| 三点 ⋯ | `OVAL` × 3 | 加载 / 进行中 |
|
|
||||||
|
|
||||||
## B. 标准用法
|
|
||||||
|
|
||||||
### B1. 圆点 bullet
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pptx.enum.shapes import MSO_SHAPE
|
|
||||||
from pptx.util import Inches
|
|
||||||
|
|
||||||
def add_dot(slide, x, y, size=0.18, color=ACCENT):
|
|
||||||
dot = slide.shapes.add_shape(MSO_SHAPE.OVAL,
|
|
||||||
Inches(x), Inches(y),
|
|
||||||
Inches(size), Inches(size))
|
|
||||||
dot.fill.solid(); dot.fill.fore_color.rgb = color
|
|
||||||
dot.line.fill.background()
|
|
||||||
return dot
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### B2. 编号徽章 (圆 + 数字)
|
`--set` 默认 `tabler`(4500+ 商务图标,MIT)。其它选 `lucide / heroicons / material-symbols / carbon / fluent / mdi`。**整 deck 只用一个 set**。
|
||||||
|
|
||||||
|
PNG 转换需 `pip install cairosvg`(推荐)或 `pip install svglib`。没装也能拿 SVG。
|
||||||
|
|
||||||
|
### A3. 嵌入幻灯片
|
||||||
```python
|
```python
|
||||||
def add_badge(slide, x, y, num, diameter=0.7,
|
slide.shapes.add_picture(
|
||||||
fill=PRIMARY, fg=RGBColor(255,255,255)):
|
"skills/ppt/assets/icons/tabler_rocket_C00000_128.png",
|
||||||
circle = slide.shapes.add_shape(MSO_SHAPE.OVAL,
|
Inches(1.0), Inches(2.5),
|
||||||
Inches(x), Inches(y),
|
width=Inches(0.8), # 装饰图标 0.5-1.5 in;别超 2 in
|
||||||
Inches(diameter), Inches(diameter))
|
)
|
||||||
circle.fill.solid(); circle.fill.fore_color.rgb = fill
|
|
||||||
circle.line.fill.background()
|
|
||||||
tf = circle.text_frame
|
|
||||||
tf.text = str(num)
|
|
||||||
p = tf.paragraphs[0]
|
|
||||||
p.alignment = PP_ALIGN.CENTER
|
|
||||||
r = p.runs[0]
|
|
||||||
r.font.bold = True
|
|
||||||
r.font.size = Pt(20)
|
|
||||||
r.font.color.rgb = fg
|
|
||||||
r.font.name = "Arial"
|
|
||||||
return circle
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### B3. 流程节点 (五边形)
|
### A4. 浏览找名字
|
||||||
|
打开 https://icon-sets.iconify.design/ 搜关键词,如 "rocket" / "数据" / "shield",拿到名字 (如 `tabler:rocket`) 直接给 fetch_icon.py。
|
||||||
|
|
||||||
|
### A5. 流程节点 (替代 PENTAGON)
|
||||||
|
|
||||||
|
需要"调研→设计→开发→测试→上线"这种横向流程时,**不要用 PowerPoint 内置 PENTAGON**(视觉陈旧),改用 Iconify 的 `chevron-right` + 文本组合:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def add_pentagon(slide, x, y, w, h, text, fill=PRIMARY):
|
from pptx.util import Inches, Pt
|
||||||
shp = slide.shapes.add_shape(MSO_SHAPE.PENTAGON,
|
stages = ["调研","设计","开发","测试","上线"]
|
||||||
Inches(x), Inches(y),
|
icon_path = "skills/ppt/assets/icons/tabler_chevron-right_C00000_64.png"
|
||||||
Inches(w), Inches(h))
|
for i, label in enumerate(stages):
|
||||||
shp.fill.solid(); shp.fill.fore_color.rgb = fill
|
x = 0.7 + i * 2.4
|
||||||
shp.line.fill.background()
|
add_textbox(slide, x, 3.7, 1.8, 0.5, label, 16, bold=True,
|
||||||
tf = shp.text_frame
|
color=PRIMARY, align=PP_ALIGN.CENTER, name=f"stage_{i}")
|
||||||
tf.text = text
|
if i < len(stages) - 1: # 节点间放 chevron
|
||||||
p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
|
slide.shapes.add_picture(icon_path, Inches(x + 1.85), Inches(3.7),
|
||||||
r = p.runs[0]; r.font.size = Pt(14); r.font.bold = True
|
width=Inches(0.4))
|
||||||
r.font.color.rgb = RGBColor(255,255,255); r.font.name = "微软雅黑"
|
|
||||||
return shp
|
|
||||||
|
|
||||||
# 用法:水平排五个节点
|
|
||||||
for i, label in enumerate(["调研","设计","开发","测试","上线"]):
|
|
||||||
add_pentagon(slide, 0.7 + i*2.4, 3.5, 2.2, 0.8, label)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### B4. 强调箭头 (右箭头)
|
## §B. Unicode 字形 (兜底)
|
||||||
|
|
||||||
```python
|
|
||||||
def add_arrow_right(slide, x, y, w, h, fill=ACCENT):
|
|
||||||
a = slide.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW,
|
|
||||||
Inches(x), Inches(y),
|
|
||||||
Inches(w), Inches(h))
|
|
||||||
a.fill.solid(); a.fill.fore_color.rgb = fill
|
|
||||||
a.line.fill.background()
|
|
||||||
return a
|
|
||||||
```
|
|
||||||
|
|
||||||
### B5. 标题装饰线
|
|
||||||
|
|
||||||
```python
|
|
||||||
def add_accent_line(slide, x, y, length=1.0, thickness=0.05, color=ACCENT):
|
|
||||||
"""标题下面那条 1 寸长的强调横线 (替代大色块的轻量做法)"""
|
|
||||||
bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
|
|
||||||
Inches(x), Inches(y),
|
|
||||||
Inches(length), Inches(thickness))
|
|
||||||
bar.fill.solid(); bar.fill.fore_color.rgb = color
|
|
||||||
bar.line.fill.background()
|
|
||||||
return bar
|
|
||||||
```
|
|
||||||
|
|
||||||
## C. Unicode 字形 (MSO_SHAPE 没有的图形)
|
|
||||||
|
|
||||||
某些图标 MSO_SHAPE 没有对应,用 unicode 字形渲染成 PNG 嵌入。Win/Mac 默认字体覆盖良好。
|
|
||||||
|
|
||||||
### 推荐字形 (避开 emoji,用单色符号)
|
|
||||||
|
|
||||||
|
Iconify 都没合适的时候用。避 emoji,用单色符号:
|
||||||
```
|
```
|
||||||
✓ ✔ ✗ ✘ 对号 / 错号
|
✓ ✔ ✗ ✘ 对号 / 错号
|
||||||
✦ ✧ ✪ ★ 星
|
★ ✦ ✧ ✪ 星
|
||||||
→ ← ↑ ↓ ↔ 箭头
|
→ ← ↑ ↓ ↔ 箭头
|
||||||
⬛ ⬜ ◆ ◇ 方块菱形
|
|
||||||
● ○ ◉ ◎ 圆
|
|
||||||
※ ◇ ⬢ ⬡ 装饰
|
|
||||||
☰ ☱ ☲ ☳ 汉字六十四卦类(简洁)
|
|
||||||
∴ ∵ ⇒ ⇔ 数学
|
|
||||||
№ ¶ § † 文档符号
|
|
||||||
↗ ↘ ↙ ↖ 斜箭头
|
↗ ↘ ↙ ↖ 斜箭头
|
||||||
⌘ ⌥ ⌃ ⏎ 键盘
|
● ○ ◉ ◎ 圆
|
||||||
|
⬛ ⬜ ◆ ◇ 方块菱形
|
||||||
|
∴ ∵ ⇒ ⇔ 数学
|
||||||
|
№ ¶ § † 文档
|
||||||
```
|
```
|
||||||
|
|
||||||
### 用 render_icon.py 生成
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 生成对号 PNG (强调色,96px)
|
# 强调色对号 96px → PNG
|
||||||
python scripts/render_icon.py "✓" --color "#38B2AC" --size 96 -o slides/check.png
|
python scripts/render_icon.py "✓" --color "#C00000" --size 96 -o slides/check.png
|
||||||
|
|
||||||
# 然后嵌入幻灯片
|
|
||||||
slide.shapes.add_picture("slides/check.png", Inches(1), Inches(2),
|
|
||||||
width=Inches(0.5))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## D. 用图标的几条原则
|
## §C. 硬规则
|
||||||
|
|
||||||
1. **同一 deck 风格统一** —— 全用 MSO_SHAPE 或全用字形 PNG,不要混
|
1. **风格统一** —— 整 deck 只用一个 Iconify set;不要 tabler 跟 lucide 混
|
||||||
2. **颜色限定** —— 只用 PRIMARY / SECONDARY / ACCENT / GREY,不要每个图标独立配色
|
2. **颜色限定** —— 只用 PRIMARY / SECONDARY / ACCENT / GREY,不要每图标独立配色
|
||||||
3. **大小克制** —— bullet 前的 dot 0.15-0.2 in;独立装饰图标 0.5-1.5 in;不要超过 2 in
|
3. **大小克制** —— 装饰图标 0.5-1.5 in;不超过 2 in
|
||||||
4. **间距统一** —— 图标右侧到文字的间距固定,通常 0.2-0.3 in
|
4. **不替表意** —— 一个 ★ 不能代替"重点"两字
|
||||||
5. **不替换文字** —— 图标是辅助,不是表意主体;一个 ★ 不能代替"重点"两字
|
5. **避免 emoji** —— 跨系统渲染差异大,且自带颜色冲突主题
|
||||||
6. **避免 emoji** —— emoji 在不同系统渲染差异大,且自带颜色与你的配色冲突
|
6. **不要每页都堆** —— 装饰是配角,文字是主角
|
||||||
|
7. **缓存复用** —— Iconify 拉的图标进 `assets/icons/` 缓存,下次直接用,不要重复请求
|
||||||
|
|
||||||
## E. 不要做什么
|
## §D. 不要把 layouts.md helper 当"图标"
|
||||||
|
|
||||||
- ❌ 在每页都堆图标
|
`add_dot` / `add_badge` / `add_accent_line` / `add_rect` 是几何**装饰**(品牌条、圆点 bullet、编号徽章、装饰短线),不是业务图标。它们底层是 MSO_SHAPE.OVAL/RECTANGLE,但模型不要直接调 MSO_SHAPE —— 全部走 layouts.md 的 helper 接口。
|
||||||
- ❌ 用网上随便下载的彩色图标 (主题不统一)
|
|
||||||
- ❌ 用 emoji (🚀💡⚡) 当严肃汇报的图标
|
|
||||||
- ❌ 图标尺寸大于标题字号高度的 2 倍
|
|
||||||
- ❌ 用 STAR / HEART 装饰严肃议题 (融资额、合规)
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ from pptx.dml.color import RGBColor
|
||||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR, MSO_AUTO_SIZE
|
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR, MSO_AUTO_SIZE
|
||||||
from pptx.enum.shapes import MSO_SHAPE
|
from pptx.enum.shapes import MSO_SHAPE
|
||||||
|
|
||||||
# ---- 配色 (默认红色主题; spec_lock 里有覆盖以 spec_lock 为准) ----
|
# ---- 配色 (商务红 — 硬约束默认) ----
|
||||||
|
# ⛔ 不允许擅自换色:除非用户明确点名其它配色 (例:"做成蓝色") 或 spec_lock 已写其它 hex,
|
||||||
|
# 否则就是这套商务红。禁止以"这个场景蓝色更专业"这类自我合理化做替换。
|
||||||
PRIMARY = RGBColor(0xC0, 0x00, 0x00) # 深红 - 标题/强调/关键数据
|
PRIMARY = RGBColor(0xC0, 0x00, 0x00) # 深红 - 标题/强调/关键数据
|
||||||
SECONDARY = RGBColor(0xE1, 0x55, 0x54) # 砖红 - 次要图形
|
SECONDARY = RGBColor(0xE1, 0x55, 0x54) # 砖红 - 次要图形
|
||||||
ACCENT = RGBColor(0xFF, 0xC1, 0x07) # 金黄 - 关键数据点/CTA
|
ACCENT = RGBColor(0xFF, 0xC1, 0x07) # 金黄 - 关键数据点/CTA
|
||||||
|
|
@ -122,6 +124,37 @@ def page_title(slide, text, page_num=None, total=None, footer="项目汇报"):
|
||||||
add_textbox(slide, 12.0, 7.0, 1.2, 0.4, f"{page_num} / {total}",
|
add_textbox(slide, 12.0, 7.0, 1.2, 0.4, f"{page_num} / {total}",
|
||||||
11, color=GREY_LIGHT, align=PP_ALIGN.RIGHT,
|
11, color=GREY_LIGHT, align=PP_ALIGN.RIGHT,
|
||||||
shrink=False, name="page_num")
|
shrink=False, name="page_num")
|
||||||
|
|
||||||
|
# ---- 品牌条 (每页起手必调,确保不是裸白纸) ----
|
||||||
|
def apply_brand(slide, kind="inner"):
|
||||||
|
"""统一品牌锚点。每个版式第一行调用,给一条窄的主色锚点 + 必要时浅底。
|
||||||
|
kind:
|
||||||
|
cover —— 封面: 左侧主色长竖条 + 顶部短横
|
||||||
|
inner —— 内页 (默认): 左侧主色窄条 (从标题到底部)
|
||||||
|
section —— 分章: 整页浅灰 + 左侧强调色粗竖条
|
||||||
|
end —— 结尾: 整页浅灰 + 顶/底强调色短线
|
||||||
|
"""
|
||||||
|
if kind == "cover":
|
||||||
|
# 顶部短主色横线 + 左侧主色长竖条 + 底部细灰线
|
||||||
|
add_rect(slide, 0, 0, 0.18, SLIDE_H, PRIMARY, "brand_left_bar")
|
||||||
|
add_rect(slide, 0.7, 0.6, 0.8, 0.06, PRIMARY, "brand_top_line")
|
||||||
|
add_rect(slide, SAFE_LEFT, 7.18, SAFE_W, 0.02,
|
||||||
|
RGBColor(0xDD, 0xDD, 0xDD), "brand_btm_hairline")
|
||||||
|
elif kind == "section":
|
||||||
|
add_rect(slide, 0, 0, SLIDE_W, SLIDE_H, BG, "brand_bg")
|
||||||
|
add_rect(slide, 0, 0, 0.18, SLIDE_H, PRIMARY, "brand_left_bar")
|
||||||
|
add_rect(slide, 0.7, 2.5, 0.08, 2.5, ACCENT, "brand_section_bar")
|
||||||
|
elif kind == "end":
|
||||||
|
add_rect(slide, 0, 0, SLIDE_W, SLIDE_H, BG, "brand_bg")
|
||||||
|
add_rect(slide, SAFE_LEFT, 0.6, 0.8, 0.06, ACCENT, "brand_top_line")
|
||||||
|
add_rect(slide, SAFE_RIGHT - 0.8, 6.85, 0.8, 0.06, ACCENT,
|
||||||
|
"brand_btm_line")
|
||||||
|
else: # inner — 默认内页
|
||||||
|
# 左侧主色窄条 (从标题区到页脚)
|
||||||
|
add_rect(slide, 0, 0, 0.10, SLIDE_H, PRIMARY, "brand_left_bar")
|
||||||
|
# 底部一条细灰线 (页脚分隔)
|
||||||
|
add_rect(slide, SAFE_LEFT, 7.18, SAFE_W, 0.02,
|
||||||
|
RGBColor(0xDD, 0xDD, 0xDD), "brand_btm_hairline")
|
||||||
```
|
```
|
||||||
|
|
||||||
> **要点**:
|
> **要点**:
|
||||||
|
|
@ -131,23 +164,20 @@ def page_title(slide, text, page_num=None, total=None, footer="项目汇报"):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## L1 · 封面 (Cover) —— 极简,无大色块
|
## L1 · 封面 (Cover) —— 主色长竖条锚点
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "cover") # 左侧主色长竖条 + 顶部短横
|
||||||
|
|
||||||
# 左上角小色块 + 标题左侧细色条
|
# 主标题 (避开左竖条)
|
||||||
add_rect(slide, 0.7, 0.7, 0.6, 0.06, PRIMARY) # 顶部短线
|
add_textbox(slide, 0.9, 2.6, 11.9, 1.4, "项目名称 / 演示主题",
|
||||||
add_rect(slide, 0.7, 1.05, 0.06, 1.5, ACCENT) # 左侧竖线 (装饰)
|
|
||||||
|
|
||||||
# 主标题
|
|
||||||
add_textbox(slide, 0.7, 2.6, 11.9, 1.4, "项目名称 / 演示主题",
|
|
||||||
44, bold=True, color=INK, name="cover_title")
|
44, bold=True, color=INK, name="cover_title")
|
||||||
# 副标题 (灰色,弱化)
|
# 副标题 (灰色,弱化)
|
||||||
add_textbox(slide, 0.7, 4.1, 11.9, 0.6, "一句话副标题或定位",
|
add_textbox(slide, 0.9, 4.1, 11.9, 0.6, "一句话副标题或定位",
|
||||||
22, color=GREY, name="cover_sub")
|
22, color=GREY, name="cover_sub")
|
||||||
# 汇报人 / 日期
|
# 汇报人 / 日期
|
||||||
add_textbox(slide, 0.7, 6.4, 11.9, 0.4,
|
add_textbox(slide, 0.9, 6.4, 11.9, 0.4,
|
||||||
"汇报人 · 部门 · 2026-05-06", 14, color=GREY_LIGHT,
|
"汇报人 · 部门 · 2026-05-06", 14, color=GREY_LIGHT,
|
||||||
name="cover_meta")
|
name="cover_meta")
|
||||||
# 右下角小图标点缀 (五角星,可选)
|
# 右下角小图标点缀 (五角星,可选)
|
||||||
|
|
@ -161,6 +191,7 @@ add_shape(slide, MSO_SHAPE.STAR_5_POINT, 12.2, 6.3, 0.5, 0.5, ACCENT,
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "inner")
|
||||||
page_title(slide, "目录")
|
page_title(slide, "目录")
|
||||||
|
|
||||||
items = ["背景与现状", "核心问题", "解决方案", "实施计划", "预期成果"]
|
items = ["背景与现状", "核心问题", "解决方案", "实施计划", "预期成果"]
|
||||||
|
|
@ -178,10 +209,7 @@ for i, item in enumerate(items):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
# 整页极浅灰 (替代深色满铺)
|
apply_brand(slide, "section") # 整页浅灰 + 主色左竖条 + 强调装饰
|
||||||
add_rect(slide, 0, 0, SLIDE_W, SLIDE_H, BG)
|
|
||||||
# 左侧装饰竖条
|
|
||||||
add_rect(slide, 0.7, 2.5, 0.08, 2.5, ACCENT)
|
|
||||||
# 大编号 (主色,描边视觉感)
|
# 大编号 (主色,描边视觉感)
|
||||||
add_textbox(slide, 1.1, 2.0, 4, 2.5, "01", 160, bold=True,
|
add_textbox(slide, 1.1, 2.0, 4, 2.5, "01", 160, bold=True,
|
||||||
color=PRIMARY, font=EN_FONT, name="sec_num")
|
color=PRIMARY, font=EN_FONT, name="sec_num")
|
||||||
|
|
@ -204,6 +232,7 @@ add_shape(slide, MSO_SHAPE.RIGHT_ARROW, 5.5, 5.0, 0.6, 0.3, ACCENT,
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "inner")
|
||||||
page_title(slide, "核心结论")
|
page_title(slide, "核心结论")
|
||||||
|
|
||||||
bullets = [
|
bullets = [
|
||||||
|
|
@ -226,6 +255,7 @@ for i, b in enumerate(bullets):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "inner")
|
||||||
page_title(slide, "现状 vs 改进后")
|
page_title(slide, "现状 vs 改进后")
|
||||||
|
|
||||||
mid_x = SLIDE_W / 2
|
mid_x = SLIDE_W / 2
|
||||||
|
|
@ -266,6 +296,7 @@ for i, p in enumerate(right_pts):
|
||||||
```python
|
```python
|
||||||
# chart.png 已用 matplotlib 生成 (见 design_principles.md §7)
|
# chart.png 已用 matplotlib 生成 (见 design_principles.md §7)
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "inner")
|
||||||
page_title(slide, "季度营收持续增长")
|
page_title(slide, "季度营收持续增长")
|
||||||
# 一句话结论
|
# 一句话结论
|
||||||
add_textbox(slide, SAFE_LEFT, SAFE_TOP + 1.1, SAFE_W, 0.5,
|
add_textbox(slide, SAFE_LEFT, SAFE_TOP + 1.1, SAFE_W, 0.5,
|
||||||
|
|
@ -310,6 +341,7 @@ add_shape(slide, MSO_SHAPE.RIGHT_ARROW, 8.4, 6.5, 0.7, 0.35, ACCENT,
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
|
apply_brand(slide, "inner")
|
||||||
# 左上大引号 (用 STAR 不合适;用字形)
|
# 左上大引号 (用 STAR 不合适;用字形)
|
||||||
add_textbox(slide, 0.8, 0.6, 1.5, 1.5, '"', 200, bold=True,
|
add_textbox(slide, 0.8, 0.6, 1.5, 1.5, '"', 200, bold=True,
|
||||||
color=ACCENT, font=EN_FONT, shrink=False, name="quote_mark")
|
color=ACCENT, font=EN_FONT, shrink=False, name="quote_mark")
|
||||||
|
|
@ -326,14 +358,13 @@ add_textbox(slide, 1.5, 5.2, 10.5, 0.5, "—— 公司价值观 2025",
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## L9 · 结尾 / Q&A —— 浅色 + 大字,不再满铺深色
|
## L9 · 结尾 / Q&A —— 浅底 + 大字,**强制必有**
|
||||||
|
|
||||||
|
> **不是可选** —— 任何 deck 都必须以这页收尾。无论是汇报、提案、路演,缺尾页等于"话没说完"。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
slide = prs.slides.add_slide(BLANK)
|
slide = prs.slides.add_slide(BLANK)
|
||||||
# 顶部 + 底部装饰短线 (代替整页色块)
|
apply_brand(slide, "end") # 整页浅灰 + 顶/底强调短线
|
||||||
add_rect(slide, SAFE_LEFT, 0.6, 0.8, 0.06, ACCENT, "top_line")
|
|
||||||
add_rect(slide, SAFE_RIGHT - 0.8, 6.85, 0.8, 0.06, ACCENT, "bottom_line")
|
|
||||||
|
|
||||||
add_textbox(slide, 0, 2.5, SLIDE_W, 1.6, "Thank You", 80, bold=True,
|
add_textbox(slide, 0, 2.5, SLIDE_W, 1.6, "Thank You", 80, bold=True,
|
||||||
color=PRIMARY, align=PP_ALIGN.CENTER, font=EN_FONT,
|
color=PRIMARY, align=PP_ALIGN.CENTER, font=EN_FONT,
|
||||||
name="thanks")
|
name="thanks")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
"""fetch_icon.py: 从 Iconify CDN 拉个性化图标,按主题色染色,缓存本地。
|
||||||
|
|
||||||
|
Iconify 聚合了 150+ 免费开源图标集,无需账号、无 API key:
|
||||||
|
tabler -- 现代描边 (Apache 2.0) ⭐ 推荐
|
||||||
|
lucide -- 开源经典 (ISC)
|
||||||
|
heroicons -- Tailwind (MIT)
|
||||||
|
material-symbols -- Google (Apache 2.0)
|
||||||
|
carbon -- IBM (Apache 2.0)
|
||||||
|
fluent -- Microsoft (MIT)
|
||||||
|
mdi -- Material Design (Apache 2.0)
|
||||||
|
|
||||||
|
每个集都有数千图标,在 https://icon-sets.iconify.design/ 浏览找名字。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
# 推荐: 染主色,导出 PNG (需 cairosvg 或 svglib)
|
||||||
|
python fetch_icon.py rocket --set tabler --color C00000 --size 128 \\
|
||||||
|
-o slides/rocket.png
|
||||||
|
|
||||||
|
# 只要 SVG (PowerPoint 2016+ 支持嵌入 SVG)
|
||||||
|
python fetch_icon.py target --set lucide --color FFC107 \\
|
||||||
|
-o slides/target.svg
|
||||||
|
|
||||||
|
# 默认值: set=tabler, color=C00000(主红), size=128
|
||||||
|
python fetch_icon.py chart-bar -o slides/chart_bar.png
|
||||||
|
|
||||||
|
环境:
|
||||||
|
PNG 转换依赖任一: `pip install cairosvg` (推荐) 或 `pip install svglib`
|
||||||
|
若都没有,会保存 .svg 到目标路径(扩展名自动改).
|
||||||
|
|
||||||
|
退出码:
|
||||||
|
0 = 成功 PNG/SVG
|
||||||
|
1 = SVG 有了但 PNG 转换失败 (已保存 SVG)
|
||||||
|
2 = 网络/图标名错误 (没拉到)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ICONIFY_API = "https://api.iconify.design/{set}/{name}.svg"
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_svg(name: str, icon_set: str, color: str, size: int) -> str:
|
||||||
|
"""从 Iconify 拉 SVG,带主题色和大小参数。"""
|
||||||
|
params: dict[str, str] = {}
|
||||||
|
if color:
|
||||||
|
params["color"] = "#" + color.lstrip("#")
|
||||||
|
if size:
|
||||||
|
params["height"] = str(size)
|
||||||
|
params["width"] = str(size)
|
||||||
|
url = ICONIFY_API.format(set=icon_set, name=name)
|
||||||
|
if params:
|
||||||
|
url += "?" + urllib.parse.urlencode(params)
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url, headers={"User-Agent": "ppt-skill-fetch_icon/1.0"}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
|
body = resp.read().decode("utf-8")
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def svg_to_png(svg_text: str, out_path: Path, size: int) -> bool:
|
||||||
|
"""SVG → PNG,降级链:cairosvg → svglib+reportlab → 失败。"""
|
||||||
|
# 路径 1: cairosvg (推荐,质量最好)
|
||||||
|
try:
|
||||||
|
import cairosvg # type: ignore
|
||||||
|
cairosvg.svg2png(
|
||||||
|
bytestring=svg_text.encode("utf-8"),
|
||||||
|
write_to=str(out_path),
|
||||||
|
output_width=size,
|
||||||
|
output_height=size,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[warn] cairosvg 渲染失败: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# 路径 2: svglib + reportlab
|
||||||
|
try:
|
||||||
|
from svglib.svglib import svg2rlg # type: ignore
|
||||||
|
from reportlab.graphics import renderPM # type: ignore
|
||||||
|
|
||||||
|
drawing = svg2rlg(io.StringIO(svg_text))
|
||||||
|
if drawing is None:
|
||||||
|
return False
|
||||||
|
renderPM.drawToFile(drawing, str(out_path), fmt="PNG")
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[warn] svglib 渲染失败: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="从 Iconify CDN 拉个性化 SVG/PNG 图标"
|
||||||
|
)
|
||||||
|
ap.add_argument("name", help="图标名,见 https://icon-sets.iconify.design/")
|
||||||
|
ap.add_argument(
|
||||||
|
"--set", default="tabler",
|
||||||
|
help="图标集 (默认 tabler;可选 lucide/heroicons/material-symbols/carbon/fluent/mdi)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--color", default="C00000",
|
||||||
|
help="主题色 hex,无 # (默认 C00000 商务红主色)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--size", type=int, default=128,
|
||||||
|
help="像素 (默认 128,适合 0.5-1.0 in PPT 图标)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"-o", "--out", required=True, type=Path,
|
||||||
|
help="输出路径 (.png 或 .svg)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--svg-only", action="store_true",
|
||||||
|
help="只输出 SVG,跳过 PNG 转换",
|
||||||
|
)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
svg = fetch_svg(args.name, args.set, args.color, args.size)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(
|
||||||
|
f"[error] Iconify 返回 {e.code}: 图标 '{args.set}:{args.name}' "
|
||||||
|
f"可能不存在,在 https://icon-sets.iconify.design/{args.set}/ 搜",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 2
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[error] 拉取失败: {e}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if "<svg" not in svg:
|
||||||
|
print(
|
||||||
|
f"[error] 返回不是 SVG: 图标 '{args.set}:{args.name}' 不存在",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
out: Path = args.out
|
||||||
|
want_svg = args.svg_only or out.suffix.lower() == ".svg"
|
||||||
|
|
||||||
|
if want_svg:
|
||||||
|
if out.suffix.lower() != ".svg":
|
||||||
|
out = out.with_suffix(".svg")
|
||||||
|
out.write_text(svg, encoding="utf-8")
|
||||||
|
print(f"[ok] SVG → {out}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if svg_to_png(svg, out, args.size):
|
||||||
|
print(f"[ok] PNG → {out} ({args.set}:{args.name} #{args.color})")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# PNG 转换失败,保存 SVG 兜底
|
||||||
|
svg_alt = out.with_suffix(".svg")
|
||||||
|
svg_alt.write_text(svg, encoding="utf-8")
|
||||||
|
print(
|
||||||
|
f"[warn] PNG 转换不可用 (装 `pip install cairosvg` 或 `pip install svglib`)\n"
|
||||||
|
f" 已保存 SVG → {svg_alt}\n"
|
||||||
|
f" PowerPoint 2016+ 直接 add_picture(svg) 也可以",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Loading…
Reference in New Issue