Word → 模板化 PPT
把一份 Word 的内容,原封不动地放进一份既有 PPT 的版式里。产出的每一页都是
模板某一页的深拷贝,因此主题、母版、版式背景美术、字体方案全部自动继承,不需要
重画任何版式元素。
三条不可违背的原则
- 模板是容器,不是参考图。每页 =
copy.deepcopy(模板第 N 页)+ 替换文字/图片。
绝不用工具重建母版或版式,那会丢掉背景美术与主题色。 - 内容零改动。不改写、不概括、不合并、不调序。选项、题干、批注原样保留
(答案与解析是否上屏,由下面的「屏幕内容」一条决定)。 - 公式必须是原生公式。Word 的 OMML 迁成
<a14:m><m:oMath>,在 PowerPoint 与
WPS 里双击都可编辑。禁止截图、禁止降级成纯文本。
屏幕内容与字体(默认要求,用户改口前一直生效)
交付对象是机构课件,屏幕是给学生看的,所以版面按下面四条出,全部由 config.json
驱动,不要临时改脚本:
| 项 | 要求 | 配置 |
|---|---|---|
| 屏幕内容 | 只放题干 + 配图(含选项图);答案、解析、点拨正文一律不上屏 | rules.quiz_page_content: "stem_and_figure_only"、rules.show_answer/show_solution: false |
| 中文 | 微软雅黑(含页眉页脚、表格、编号徽章、公式里的中文) | body.ea_font |
| 数字与字母 | Times New Roman(正文 run 与公式 run 都要,含表格内联公式) | body.latin_font |
| 公式 | 必须是 WPS/PowerPoint 原生可编辑公式对象,不许是图 | rules.math_as_omml: true |
| 汉字 | 汉字一个都不许进公式。公式里只能有数字、字母、符号;中文、全角标点一律回到普通文字 run | split_omath()(自动) |
| 配图 | 每道提到「如图 / 图中 / 统计图 / 画出图象」的小问,页面上必须有那张图 | fix_figs()(自动) |
| 字号 | 跟模板正文一致,不自己定 | style.body_pt / style.opt_pt / table.font |
| 行距 | 跟模板段落一致 | style.lnspc |
| 颜色 | 跟模板正文一致(多数是黑,不是灰) | style.color |
| 对齐 | 题干段落跟模板走(本模板 algn="just") |
生成器内固定 |
「格式和模板一致」= 字体 + 字号 + 行距 + 颜色 + 位置五项都要对,只对字体是不够的。
字号行距颜色不能凭经验填,必须从模板 XML 里量出来:抓一页同类正文(如含「典题」的那页),
看它的 a:rPr/@sz、a:pPr/a:lnSpc/@val、a:solidFill,或者看 a:lstStyle 里
defRPr/@sz(模板正文常写「继承」,这时去查 ppt/presentation.xml 的
p:defaultTextStyle,本模板是 1800)。青藤善思班模板实测:正文 18pt、行距 150%、
纯黑 000000、题干 algn="just"、表格 16pt;早先按 16pt / 135% / 404040 出,
用户一眼就看出「和上传的 PPT 格式不一致」——页数差不多,字明显小一号、行明显挤。
字号一改,para_h() 的行盒系数必须回到 1.2(字号 × 1.2 × 行距%),否则每行少估
2pt 左右,一页十行就把最后一行顶出正文框。
实现要点:
- 一个 run 里中西文混排时,字体要分三条写:
a:latin(西文)+a:ea(中文)+
a:cs(复杂文种),并守CT_TextCharacterProperties的子元素顺序
(latin < ea < cs < sym)。只写a:ea会让数字跟着变成微软雅黑。 - 公式内部的 run 也要分流(
deckgen.fit_math_sz→font_math_run):不写字体时
公式会退回 Cambria Math,和周围新罗马对不齐。字号同时按 0.96 收到正文字号。 - 末尾还要再过一遍整页字体(
deckgen.normalize_fonts):生成 run 时写字体只覆盖
我们自己造的文字;模板自带的徽章 A/B/C/D、页眉页脚、装饰标签要么不写字体(继承主题
Calibri/宋体),要么带着a:sym符号字体把 latin/ea 盖掉,要么是「方正兰亭黑」
「汉仪粗圆简」这类原稿字体。成品 XML 里所有a:rPr / a:defRPr / a:endParaRPr
统一补latin=Times New Roman, ea=微软雅黑, cs=Times New Roman并删a:sym,
主题majorFont/minorFont一起换掉兜底。scripts/audit.py现在把「字体未显式指定」
和「字面不符」判成 FAIL,不再靠肉眼。 - 验证原生公式不是看图,而是往返:用 WPS 另存一份再解包,
<m:oMath>结构还在,
说明 WPS 认它是公式对象(实测另存后连a:latin都保留)。脚本见
references/workflow.md。 - 公式内的拉丁字体最终会归到 Cambria Math:模板自身 1015 个公式 run 里有 913 个
就是 Cambria Math,PowerPoint 另存也一律归到这里。所以「公式内字体 = Cambria Math」
才是与模板一致,不要为了统一新罗马把它硬改回去;audit.py比对字体时先剔除
<a14:m>…</a14:m>段正是为此。 - 扫「公式里有没有汉字」时只取
<m:t>的文本内容,别对整个片段跑正则——
typeface="微软雅黑"这种属性值会被误判(详见ooxml-pitfalls.md第 25 条)。 - 去掉答案页后,
config.shapes.answer不再被引用,但不要删模板那页——换讲或
出教师版时还要用。
不要做的事
- 不要用 python-pptx 重建幻灯片。它无法复用已有版式美术,且会把公式变图片。
正确做法是直接改写 OOXML 包(本技能已实现)。 - 不要把页号、形状名、版式文件名写死在脚本里。全部来自
config.json,
换一份模板只改配置,不改代码。 - 不要把某一讲的结构规则硬编码。
rules.section_titles/item_patterns
描述文档结构,不同教材不同章节只改这里。
流程
0. 指纹分析模板
python scripts/analyze_template.py template.pptx work/template_map.json
打印每页的形状数、图片数、样本文本量、风险标记、形状名。据此决定
「哪个模板页当封面 / 分隔页 / 正文壳 / 编号条目页 / 章节页」,填进 config.pages。
判断依据见 references/template-mapping.md。
1. 提取 Word
python scripts/extract_docx.py chapter.docx work
# blocks=404 math=908 img=17 tables=1 rels=27
产出 work/blocks.json(段落/表格/公式原文/图片尺寸)与 work/media/。
核对计数:有公式的文档 math 必须大于 0。
2. 写配置
复制 assets/config.sample.json 到 work/config.json,按需修改:
| 键 | 作用 |
|---|---|
pages |
各内容角色对应的模板页号 |
shapes |
要替换的文本框形状名 |
body |
正文区坐标、行高、字号 |
rules.section_titles |
章节标题 → 容器 / 分隔页 / 仅切状态 |
rules.item_patterns |
例题、练习等编号条目的匹配规则 |
figure / table / chip |
图片缩放、表格配色、编号胶囊与星级 |
样例配置是一套可用的 4:3 教学课件参数,跨章节通常只需改 pages、shapes、章节标题。
3. 生成
python scripts/deckgen.py --workdir work --template template.pptx \
--config work/config.json --out out/deck.pptx
# slides: 75 -> out/deck.pptx
页数是最快的回归信号。同一份源两次产出页数不同,说明度量参数被动了
(见 references/layout-metrics.md)。
整册/多讲一次跑完:
python scripts/batch_convert.py --template template.pptx --config cfg.json \
--out out --docs 第8讲.docx 第9讲.docx 第10讲.docx
每份独立走「提取 → 生成 → 校验 → WPS 复开」,失败不中断(整册要的是 8 份好的,
不是 stack trace),末尾打印汇总表并写 out/_batch_report.json。加 --renderroot <dir>
可把 <dir>/<文档名>/*.PNG 纳入版面检查;--no-wps 关闭 WPS 闸门。
内容表格:总览表 vs 正文表
Word 里 w:tbl 有两种身份,必须先分清再决定版面:
- 总览表(
【课程解读】后面那张):独立一页,pill 标题 + 满宽网格。 - 正文表(收费标准、购衣价格、会员卡、解方程步骤、几何模型对照):必须和题干
同页排版,不能各占一页。
classify() 按上下文自动分流为 k='table' 与 kind='btable',后者由
layout_btable() 按内容定列宽、按换行定行高。单元格里可以同时有文字、行内公式
和图片——公式按 a/b 与上标线性化,图片叠放在表格之上(a:tc 不能放 p:pic)。
细节见 references/layout-metrics.md。
4. 校验(不可跳过)
python scripts/verify.py out/deck.pptx --blocks work/blocks.json --render work/render
三道独立闸门:包结构(能否被 PowerPoint 打开)、内容覆盖(是否漏段)、版面密度
(是否有空白页)。任一项 FAIL 就不要交付。--render 需要先用 PowerPoint COM
导出 PNG,命令见 references/workflow.md。
4.5 WPS 复开(交付对象是 WPS 用户时不可跳过)
这些课件最终大多在 WPS 里打开,而 WPS 与 PowerPoint 是两个不同的 OOXML 读取器。
PowerPoint 能开,不等于 WPS 能开,所以要在 WPS 里真开一次:
python scripts/wps_check.py out/deck.pptx # 轻量:能开 + 页数一致 + 抽样有字
python scripts/wps_check.py out/*.pptx --export render/wps --json wps.json
判据是三条:WPS 能打开(无修复提示、无异常)、WPS 报的页数等于包里
ppt/slides/slideN.xml 的个数(少页说明 WPS 静默丢了它解析不了的页)、抽样页能读到
文本(不是空壳)。batch_convert.py 默认已经带这一步,汇总表的 wps: 列即结果。
依赖 comtypes(pip install comtypes)。缺依赖或本机没装 WPS 时脚本打印 SKIP
并以退出码 2 结束——跳过不等于通过,要在交付说明里如实写「未在 WPS 验证」。
4.5.1 WPS 自动化失效时的替代闸门
WPS 12.1.0.29161(2026-10-28)起,智能体进程已经无法通过 COM 拉起 WPS:
CoCreateInstance 先报「无效的类字符串」,手工补注册表后改报「服务器运行失败」,
wpscli.exe 更是直接说「父进程签名校验失败」。这时候不要反复注册表挖坑,改用静态
闸门:
python scripts/audit.py out/*.pptx # 零依赖,非 0 退出即有硬伤
python scripts/geom_check.py out/a.pptx # 自己摆的东西不出界、不压字
python scripts/imgcheck2.py out/a.pptx work/a # 图不能丢:按像素指纹比对
python scripts/imgcheck.py out/a.pptx work/a # 旧版:按 QFIG 形状名计数
geom_check.py 只看生成器自己命名的形状(QBODY/QOPT/QBDG/QFIG/QOPI/表格*):
底边 ≤ 505、题干底 ≤ 选项顶、配图不压题干。模板自带美术件(图片 1、组合 1)
本来就有 1pt 级出界,别把它们算成回归。
imgcheck2.py 按像素指纹核图,而不是数字形名字——imgcheck.py 只数
QFIG/QOPI,表格格内图、名字没换过来的图会漏计,既会误报丢图(实测 8 份里
3 份假警报),也查不出「图在包里却没贴到页上」。它同时排掉答案/解析段里的
解题配图(按用户要求不上屏),所以「缺 0」才等于真的一张没丢。
它查的都是历史上真让 WPS 崩过或让包被判损坏的东西:m:limUpp、空 m:t、零尺寸
形状、r:embed 断链、custDataLst 悬挂引用、答案/解析残留。细节见
references/workflow.md 第 11、12 节。
4.6 真机调试:让 PowerPoint 把实测几何回写回来(推荐闸门)
静态闸门只能看「生成器以为自己摆成什么样」。要确认真实渲染不溢出、不压字、不
缺图,用 PowerPoint COM 走一遍生成 → 实测 → 回写 → 再实测 → 体检:
# 1) 回写:按实测 BoundHeight 改框高、选项与徽章中线对齐、越界配图等比缩
scriptblock: pptfit.ps1 -SrcDir out4 -Dst out5
# 2) 复测:逐页 Export PNG + 采每个形状的 Left/Top/Width/Height 与 BoundHeight
scriptblock: pptall.ps1 -SrcDir out5 -Dst dbg9
# 3) 体检:出界 / 溢出 / 表格越界 / 选项错位 / 空页,五项都应为 0
python scripts/pptcheck.py dbg9
scripts/pptdump.ps1 是单文件版采集(-In a.pptx -OutDir d -JsonPath d/a.json)。
体检基线(8 讲 381 页)与全部环境坑(Visible=-1、-Encoding UTF8、中文路径、
SaveCopyAs 不建目录、八讲要分两批跑)写在 references/workflow.md 第 24 节。
两个反例值得记:表格回写阶段压单行行高会把 3 行表压扁、格内图错位,表格高度
只能由生成器按内容算;媒体文件数变少不等于丢图,PowerPoint 会把字节相同的图
去重(281→138),判丢图要数 <p:pic> 和 r:embed 能否解析到 ../media/。
5. 交付
说明页面对应关系,并主动交代刻意偏差(例如「章节页改用自绘标题,因为模板原形状
带 custDataLst,复用会导致文件损坏」「答案统一另起一页以保持讲课节奏」)。
排障顺序
不要猜,按层排查,见 references/workflow.md 末尾:
- 打不开 → 包结构问题(第一个查
[Content_Types].xml与p:timing) - 页数不对 → 内容被漏(跑
verify.py --blocks) - 版式不对 → 逐页看渲染图
- 单页打不开 → 逐个删形状二分定位
所有已知会破坏文件的 OOXML 构造,连同症状、原因、修法,都记在
references/ooxml-pitfalls.md。动手改 XML 之前先读它,能省掉几小时的二分定位。
文件
scripts/analyze_template.py 模板指纹 → template_map.json
scripts/extract_docx.py DOCX → blocks.json + media/
scripts/deckgen.py 核心生成器(配置驱动)
scripts/batch_convert.py 多文档批量转换 + 汇总报告(含 WPS 闸门)
scripts/verify.py 包结构 / 内容 / 版面三重校验
scripts/wps_check.py WPS 复开验证(comtypes,可选依赖)
scripts/audit.py 静态体检:WPS 崩溃面 + 包完整性(零依赖)
scripts/geom_check.py 几何自检:不出界、选项/配图不压题干(零依赖)
scripts/imgcheck2.py 图片齐备性(像素指纹版,推荐)
scripts/imgcheck.py 图片齐备性:讲义引用的每张图都在成品里(零依赖)
scripts/imgcheck3.py 图片齐备性(贴图清单版,最准:生成器埋点 rid 集合比对)
scripts/pptdump.ps1 真机采集单文件:Export PNG + 每个形状的实测几何
scripts/pptall.ps1 真机采集批量版(整册/多讲)
scripts/pptfit.ps1 真机回写:按实测高度改框、中线对齐、越界配图等比缩
scripts/pptcheck.py 真机体检:出界 / 溢出 / 表格越界 / 选项错位 / 空页
scripts/textfit.py 真实字体度量折行(PIL + msyh/times,无 WPS 时用)
scripts/pngtool.py 纯 Python PNG 缩放与拼图(无第三方依赖)
references/ooxml-pitfalls.md 25 类会损坏文件或静默丢内容的坑
references/template-mapping.md 模板页 → 内容角色的映射方法
references/layout-metrics.md 度量、分页规则与调参顺序
references/workflow.md 端到端命令、PNG 导出、排障流程
assets/config.sample.json 可用的完整配置样例