引言
如果您准备使用AI帮您做PPT,也许曾经考虑过让AI调用python-pptx库编写脚本来生成PPT,但有的时候会遇到类似作者的问题——调整这份PPT里面的文字字号时出现"bug"
故事的开始
最近打算用deepseek+claude code帮同学做PPT,于是安装了一个新的库——python-pptx。当我用 200 多行 Python + python-pptx 生成了一个 8 页的学术报告 PPT,打开检查——排版完美,字体统一,一切正常。
然后就出怪事了。
我选中一个 14pt 的正文文本框,改成 18pt。
没反应。弹回 14pt
改成 16pt?可以。19pt?也可以。18pt?不行。
不是玄学。换了三种字体,试了两个版本的 PowerPoint,症状纹丝不动。我把"不自动调整"勾了,没用。我把整个文本框清空格式重建,没用。我把 `.pptx` 复制一份,换台电脑打开,没用。
不是 autofit 的锅。这玩意已经用 `disinfect_autofit` 全局关掉了,XML 里确认是 `<a:noAutofit/>`,干干净净。
对照实验
所有的猜测都能被验证,前提是你要看到底层到底是什么。
我让 AI 在脚本末尾加了一页测试用的 slide 9,上面只放一个脚本生成的文本框,14pt,内容固定。生成 PPT。
然后我自己在 PowerPoint 里打开,在 slide 9 上手动新建一个文本框,也设成 14pt,保存退出。
同一页,两个文本框。一个出问题,一个正常。唯一区别是来源。
把 `.pptx` 后缀改成 `.zip`,解压,打开 `ppt/slides/slide9.xml`。
两个文本框
以下是从 XML 里提取的核心差异。为了清晰,我只保留了关键标签。
脚本生成的(改不了 18pt):
<a:pPr> <a:defRPrsz="1400"> <!-- 段落级默认 --> <a:latintypeface="微软雅黑"/> </a:defRPr></a:pPr><a:r> <a:rPrdirty="0"/> <!-- run 上没有显式字号 --> <a:t>这是脚本生成的14号文本</a:t></a:r>
手动创建的(完全正常):
<a:r> <a:rPrsz="1400"> <!-- run 上有显式字号 --> <a:latintypeface="微软雅黑"/> <a:eatypeface="微软雅黑"/> </a:rPr> <a:t>这是手动创建的文本</a:t></a:r>
根因直接写在了 XML 里。
根因:defRPr,一个你不知道自己在设的"锚"
python-pptx 里,有这么一行:
它生成的不是 `<a:rPr sz="1400">`(run 级的字号),而是 `<a:defRPr sz="1400">`(段落级的**默认**字号)。
二者的区别:
API | OOXML 输出 | 含义 |
p.font.size | <a:defRPr sz="1400"> | 这个段落里的文字,默认 14pt |
run.font.size | <a:rPr sz="1400"> | 这段文字,就是 14pt |
"默认" 和 "就是",在 PowerPoint UI 里是完全不同的两回事。
当你打开 PPT,选中文字,把字号从 14 拉到 18,PowerPoint 会在 `<a:rPr>` 上写 `sz="1800"`。但 `<a:defRPr sz="1400">` 还在。当 PowerPoint 评估文字是否适配文本框时,它参考的不是你刚设的 18pt,而是默认的 14pt。发现"溢出"——弹回去。
而你手动新建的文本框,没有 `defRPr`,字号直接焊在每个 run 的 `rPr` 上。PowerPoint 找不到默认值可以参考,只能听你的。
所以换字体没用、改 autofit 没用、清除格式没用。 因为这些东西操作的都是 run 级属性,而病根在段落级的 `defRPr` 上。
为什么你大概率也会踩这个坑
python-pptx 的 API 里,`paragraph.font.size` 和 `run.font.size`都能正常使用,不会报任何错误或警告。生成的 PPT 打开也完全正常——文字确实显示为 14pt。
问题只在人工二次编辑时暴露。
而生成 `.pptx`(而不是 PDF)的核心原因,恰恰就是需要被编辑。学术报告要微调,企业汇报要更新数据,课程作业要个性化——只要你是为人而生成 PPT,你就一定会踩到这个坑。
不是 bug,是 API 设计的沉默陷阱。
解决:三行代码,一劳永逸
规则很简单:永远用 run 级设置字号,禁止 `p.font.size`。
工具函数:
def _set_first_run(p, text, size, bold=False, color=None): """清空段落,写入 run 级字号(消灭 defRPr)""" p.clear() run = p.add_run() run.text = text run.font.size = Pt(size) run.font.bold = bold if color: run.font.color.rgb = color
以后所有新建段落和修改第一段,都用它:
# 修改第一段p = tf.paragraphs[0]_set_first_run(p,"标题",28,bold=True)p.alignment =PP_ALIGN.CENTER# 追加新段落p = tf.add_paragraph()_set_first_run(p,"正文内容",16)
再也不用 `p.font.size`。
后续
我已将这个发现 + 可复现脚本 + 解决方案提交到了 python-pptx 的 GitHub Issue(`scanny/python-pptx`),欢迎搜索 `defRPr` 或 `paragraph.font.size` 找到并 +1,链接为`https://github.com/scanny/python-pptx/issues/1135`
这个问题从发现、对照实验、XML 分析到最终修复,整个过程我整理成了一篇完整的技术记录,包含完整工具函数和 autofit 防护代码,见我的 GitHub账号`https://github.com/YTH-coding`。
注意:本文部分内容由AI生成