PPT 格式提取与风格套用
> 给使用者:本文件是给 AI 读的说明书。人类使用请看同目录下的 使用说明.txt
> (双击即开,含安装步骤、使用方法、换模板步骤、常见问题)。
八个脚本
| 脚本 | 作用 |
|---|---|
scripts/build_pptx.py |
主力:plan.json → 套用基准格式的 pptx |
scripts/ppt_style.py |
样式常量 + 7 类页型构件函数(build_pptx 底层,也可单独 import) |
scripts/extract_pptx_format.py |
反向:任意 pptx → spec.json(主题色/字体/每形状坐标/每 run 字号颜色) |
scripts/summarize_pptx_style.py |
spec.json → 「层级→字号/粗细/颜色/字体」映射表 + 配色频次 |
scripts/analyze_pptx_layouts.py |
推断各页标题候选 + 背景形状 → 归纳页型模式 |
scripts/dump_pptx_page.py |
dump 指定页完整形状结构(定页型蓝本用) |
scripts/strip_fonts.py |
剥掉 pptx 内嵌字体,体积可从 12MB 降到 55KB |
scripts/check_pptx_layout.py |
验收:越界 / 非法字号 / 字体颜色合规 / 文本重叠 |
依赖
唯一依赖:python-pptx
pip install python-pptx
装完即可用,脚本无其他外部依赖、无网络请求、无凭据需求。
⭐ 主力用法:给内容计划,出标准格式 PPT
# ① 看 plan.json 该怎么写
python scripts/build_pptx.py --print-plan-schema
# ② 生成
python scripts/build_pptx.py plan.json -o "输出.pptx"
# ③ 验收(改完必跑,不靠肉眼)
python scripts/check_pptx_layout.py "输出.pptx"
7 种页型
| type | 用于 | 关键字段 |
|---|---|---|
cover |
封面 | lines(多行)、author |
toc |
目录(最多 6 项) | items = [["01","第一部分"],…] |
section |
章节过渡 | title、sub、color(默认深藏青) |
content |
顶部色条页(最常用) | title、sub、color、cards[] |
page |
白底居中标题页 | 同上,可加 dark:true / bar |
compare |
左右对比(绿=对/红=错) | left / right = {title, color, lines[]} |
dark |
暗底金句页 | title |
卡片字段
{ "no":"01", "title":"卡标题", "desc":"说明文字",
"quote":"引文块(带语义底色的那格)", "tags":"标签(分隔线下方)",
"color":"蓝" }
quote 省略时自动上移 desc;全部省略则卡片更矮。
颜色可写中文
蓝 / 深蓝 / 亮蓝 / 红 / 深红 / 绿 / 橙 / 深橙 / 白 / 深底,或直接 #RRGGBB。
完整示例
见 test/plan_regression.json(中性内容,7 页覆盖全部页型)。
基准格式规范
完整规范见 assets/TEMPLATE-格式规范.md(9 节:色板 / 字体 / 字号层级表 / 7 类页型几何参数 / 页脚规范 / 10 条铁律)。核心速查:
- 画布 960×540pt(16:9)
- 字体 Noto Sans SC(页脚用微软雅黑 9pt)
- 主色
#2B5CE6深蓝#004FDD错误红#E65050/#B44646正确绿#008C50提示橙#E6A032 - 字号 页标题 32 / 卡片序号 40 / 卡片标题 24 / 要点 20 / 正文 16 / 页脚 9
- 页脚 每页右下
L812.4 T523.4,格式{章节} | {页码两位}
10 条套用铁律
- 字体一律显式指定 Noto Sans SC
- 字号只取规范表中的值;正文 16pt
- 颜色只取规范表中的值;语义色不互换(红=错/绿=对/橙=提示)
- 内容页标题 32pt bold
#2B5CE6;暗底页 32pt bold 白 - 卡片 = 白底 + 1pt 语义色描边 + 6pt 顶脊
- 每页右下 9pt
#969696页脚 - 页面四周留边 ≥ 24pt
- 内容超长先降级字号(16→14→12),不缩字距不换字体
- 背景优先母版白底;暗底用
#001548或#2B5CE6满幅 - 禁用渐变、阴影、3D、非常规字体
换一份参考 PPT(脚本不动)
# ① 抽出新参考的格式
python scripts/extract_pptx_format.py "新参考.pptx" -o ./spec
# ② 看层级映射表与配色频次,据此改 ppt_style.py 的常量
python scripts/summarize_pptx_style.py ./spec/spec.json
# ③ 推断页型模式,据此调整构件函数
python scripts/analyze_pptx_layouts.py ./spec/spec.json
# ④ 选几页 dump 出几何蓝本
python scripts/dump_pptx_page.py ./spec/spec.json 1,6,33
# ⑤ 生成空骨架并剥字体(保留母版/主题/版式,去掉全部幻灯片与内嵌字体)
python -c "
import shutil;from pptx import Presentation
shutil.copy('新参考.pptx','assets/_raw.pptx')
p=Presentation('assets/_raw.pptx')
L=p.slides._sldIdLst
R='{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id'
for s in list(L): p.part.drop_rel(s.get(R)); L.remove(s)
p.save('assets/_raw.pptx'); print('ok')"
python scripts/strip_fonts.py assets/_raw.pptx -o "assets/TEMPLATE-基准骨架.pptx"
rm assets/_raw.pptx
改完必须跑回归,确保 7 页仍全过校验:
python scripts/build_pptx.py test/plan_regression.json -o test/out.pptx
python scripts/check_pptx_layout.py test/out.pptx
关键教训(都是踩过的坑)
| 坑 | 现象 | 对策 |
|---|---|---|
| 主题色 ≠ 实际用色 | 参考 PPT 主题板是 #2875FF 系,实际页面用的是更饱和的 #2B5CE6 |
复刻要抓直接格式层(run.font.color),不是 theme |
| 内嵌字体撑爆体积 | 骨架 12MB,其中 12.2MB 全在 ppt/fonts |
用 strip_fonts.py 剥掉 → 55KB;同时避免上云撞配额 |
| 剥字体后 XML 要同步清理 | 只删 ppt/fonts/* 会导致文件打不开 |
必须同步改 [Content_Types].xml、presentation.xml.rels、presentation.xml 的 embeddedFontLst |
| 压缩包追变量名不可行 | 压缩后单字母短名全包复用上万处 | 判断功能是否解锁要看 ① 门控关键字是否还在 ② 回调是否被 undefined 包裹 ③ 底层 RPC 是否存在 |
os.replace 覆盖被锁 |
pptx 被预览面板占用报 PermissionError |
换新文件名输出 |
边界
- 本技能只做风格级继承(配色/字体/字号层级/版式语汇),不做坐标级 1:1 复刻。
坐标级复刻需用ppt_style.py的构件函数显式指定每个形状坐标。 - 需要非常规版式(流程图/时间轴/复杂表格)时,在 plan 里用
"type":"custom",
然后直接import ppt_style调Deck.box()/Deck.text()手搭。 - 输入 pptx 需为非加密、未设编辑密码的 .pptx(.ppt 不支持)。
- 骨架已剥离内嵌字体;目标机器若未装 Noto Sans SC 会回退到系统无衬线字体。
该字体免费可商用,缺失时安装即可保真。 - 全包无写死绝对路径,
assets由__file__相对定位,整目录可复制到任意机器/任意路径。
v2 版式(2026-10-05 修正)
🔴 v1 的两个硬编码缺陷(已修)
| 参数 | v1 值 | 后果 |
|---|---|---|
card_row 的 w |
硬编码 296 | 4 卡必然溢出:4×296+3×12=1220 > 画布 960 |
card_row 的 h |
硬编码 330 | 内容只占约 90pt,60% 是空白;视觉上重下轻 |
| 正文字号 | 硬用 SZ.BODY=16 |
后排偏小(课件约定 ≥21pt) |
plan.json 可用的版式参数(content / page 页型)
| 参数 | 缺省 | 作用 |
|---|---|---|
h |
null |
卡片高。null = 按内容自适应(走 card_h()) |
w |
null |
卡片宽。null = 按张数均分,4 卡也不溢出 |
body_size |
null |
正文字号。课件建议 20 |
center_y |
false |
卡片区在标题区与页脚区之间垂直居中 |
max_h |
null |
自适应高度上限,配合 center_y |
y |
109.7 | 卡片区顶部 |
推荐组合(商业培训课件)
{ "type": "content", "title": "…", "color": "蓝",
"cards": [ { "no":"01", "title":"…", "desc":"…" },
{ "no":"02", "title":"…", "desc":"…" },
{ "no":"03", "title":"…", "desc":"…" },
{ "no":"04", "title":"…", "desc":"…" } ],
"body_size": 20, "center_y": true, "max_h": 300 }
自适应高度估算公式(card_h)
h = 6(顶脊) + 40(序号) + 8(间隙) + 48(标题)
+ 72(若有 quote) + 36(若有 tags)
+ 22 × desc行数 + 24(底部留白)
下限 170pt
卡宽公式(w=None 时):
w = min(340.0, (W - 2*MARGIN - (n - 1) * gap) / n)
⇒ 4 卡时 w≈219,总宽 4×219+3×12=912 ≤ 960 ✔
v1 / v2 效果对照
| 项 | v1 | v2 |
|---|---|---|
| 4 卡 | 溢出 130pt,右侧被裁 | 总宽 912 ≤ 960 ✔ |
| 卡高 | 330pt 固定,60% 空白 | 170~200pt 贴合内容 |
| 正文 | 16pt | 20pt |
| 视觉重心 | 上重下轻,下方 90pt 全空 | 垂直居中,上下均衡 |
备份
scripts/ppt_style.py.bak —— v1 版本,可回退(覆盖后重跑 build 即可)
🔴 踩坑
card_row内引用MARGIN/W必须写模块级常量,
不能写self.MARGIN(无该类属性 → AttributeError)card_row的**kw会把未知 key 透传给card();
新增参数务必显式声明在签名里,否则被**kw吞掉不生效- 颜色别名只有 蓝/深蓝/亮蓝/红/深红/绿/橙/深橙/白/深底 或
#RRGGBB,
写 青/琥珀 会抛ValueError
验收闭环(改完必跑)
python scripts/build_pptx.py plan.json -o out.pptx
python scripts/check_pptx_layout.py out.pptx # 必须「版式校验通过」
只看脚本无报错不作数 —— 必须渲染出图肉眼核对。
本机用 WPS CLI(比 LibreOffice 快、不占 1GB):
WPS="/d/Users/Neo/WPS Office/12.1.0.28488/office6"
"$WPS/kwpsconvert.exe" ppt2pdf "D:/…/out.pptx" --output "D:/…/_render"
"$WPS/kwpsconvert.exe" pdf2photo "D:/…/_render/out.pdf" --output "D:/…/_render/png"
⚠️ 必须绝对路径(正斜杠 D:/…),相对路径会被解析到
C:\\Users\\…\\AppData\\Local\\wpscli → Error(203) File not found。
产物是 .jpg;多页会拼成一张大图,需 PIL 切片后单页查看。