从参考 PPT 反推框架,生成汇报提纲(PPT 框架稿)
触发词:"参考这个 PPT 的框架""在此基础上修改""生成新的汇报提纲""这个提纲要生成 PPT""在这个 PPT 的基础上改、另存""换个模板重新设计""按提纲重做一版 PPT""重新设计 PPT 第二版"。
适用场景
- 用户给了往年同类汇报 PPT,要求"参考这个框架,在此基础上改"。
- 用户要一份汇报提纲,且明确"最终要生成 PPT"。
- 需要把一份只有标题层级的粗提纲,改造成可直接转 PPT 的逐页设计稿。
标准流程
第 1 步 提取参考 PPT 的框架
用 python-pptx 逐页提取文字、表格、图表、图片占位,输出为纯文本框架文件。
# dump_ppt.py
import os
from pptx import Presentation
p = r'<参考PPT路径>'
prs = Presentation(p)
print('尺寸: %.1f x %.1f cm' % (prs.slide_width/360000, prs.slide_height/360000))
print('总页数:', len(prs.slides))
out = []
for i, s in enumerate(prs.slides, 1):
out.append('\n===== SLIDE %d =====' % i)
try:
out.append('[layout] %s' % s.slide_layout.name)
except Exception:
out.append('[layout] ?')
for sh in s.shapes:
txt = ''
if sh.has_text_frame:
txt = ' | '.join(par.text for par in sh.text_frame.paragraphs if par.text.strip())
if txt:
out.append(' <%s|%s> %s' % (sh.name, sh.shape_type, txt))
elif getattr(sh, 'has_table', False) and sh.has_table:
rows = [' / '.join(c.text.replace('\n', ' ') for c in r.cells) for r in sh.table.rows]
out.append(' <TABLE|%s> %s' % (sh.name, ' || '.join(rows)))
else:
out.append(' <SHAPE|%s>' % sh.name)
fp = r'<输出txt>'
open(fp, 'w', encoding='utf-8').write('\n'.join(out))
print('written', fp)
抽象出三件事(这是"借框架"的实质):
1. 篇章结构——哪些页是篇章隔断页,把内容切成几段。
2. 主线标识——每页是否固定标注当前主线名称(如"一、强化专业建设,做强师范品牌"),这是听众跟上节奏的关键。
3. 可视化手法——数字卡片、表格、图表、时间轴、关系图分别用在什么类型的内容上。
第 2 步 重构提纲逻辑
先用业务逻辑定层次,再往 PPT 框架里装。常用五段式:
底色 → 动因 → 举措 → 成效 → 展望
诊断原提纲的常见四类问题:
- 层次不齐(把不同逻辑层面的内容并列,如学校层面改革与专业层面迎评);
- 缺政策对标(开篇即讲自身历史,未回答"为什么这样改");
- 关键机制未独立成节(如质量保障被附在末尾,分量不足);
- 缺成效数据(通篇是举措、没有结果)。
第 3 步 逐页设计
每页写清四件事:页码|页面标题|核心内容|呈现形式。必要时加"说明"行点出该页解决什么问题。
- 每页标注所属篇章,并在正文中给出篇章的页序区间与时长配比。
- 时长按 1 分钟 250—300 字估,汇总后须落在用户给的时长区间内。
- 图片页、篇章页可快速过,内容页约 1 分钟。
第 4 步 生成公文版式 Word
用 python-docx,版式基准:
| 项 | 值 |
|---|---|
| 页面 | A4 210×297mm |
| 页边距 | 上 37 / 下 32 / 左 27 / 右 27 mm |
| 大标题 | 方正小标宋简体 二号(22pt)居中 |
| 一级标题 | 黑体 三号(16pt) |
| 二级标题 / 页码行 | 楷体_GB2312 三号(16pt),页码行加粗 |
| 正文 | 仿宋_GB2312 三号(16pt) |
| 首行缩进 | 2 字符(Pt(size*2)) |
| 行距 | 29pt(WD_LINE_SPACING.EXACTLY) |
用元组列表驱动正文(便于增删页):
STRUCT = [
('【第一部分】篇章名', '第4至8页,约1.5分钟', [
('第5页|学校概况', ['核心内容:……', '呈现形式:……']),
]),
]
for sec_title, sec_meta, pages in STRUCT:
h1(sec_title + '(' + sec_meta + ')')
for pt, lines in pages:
pageline(pt)
for ln in lines:
body(ln)
第 5 步 必做校验
- 引号校验:正文 ASCII 引号必须为 0,中文左右引号成对。
python full = ''.join(p.text for p in doc.paragraphs) assert full.count('"') == 0 and full.count("'") == 0 assert full.count('“') == full.count('”') - 版式校验:抽查页面尺寸、页边距、各级字体字号、首行缩进。
- 页数校验:正文声明的总页数须与实际逐页条目数一致。
已知陷阱
- Write 写长句时行尾的
')可能丢失,导致 Python 报SyntaxError: unterminated string literal。修复前先逐行预检引号奇偶,一次性找全,不要靠反复运行来逐个暴露:
python
# check_quotes.py
lines = open(script_path, encoding='utf-8').read().split('\n')
for i, ln in enumerate(lines, 1):
if ln.count("'") % 2 or ln.count('"') % 2:
print('line %d sq=%d dq=%d tail=...%s' % (i, ln.count("'"), ln.count('"'), ln[-40:]))
校验语句本身(如 full.count("'"))会被误报,可忽略。
- 本机 Bash 工具 PATH 不全(
ls/cat/grep/tail/dirname均不可用,cat > file << EOF写不出文件)。一律:先用 Write 工具写脚本,再用 Python 绝对路径运行。 - 长文档提取时 PowerPoint 文件可能极大(含大量图片),只提文字不会慢;不要试图导出图片。
- 页数压缩的落点:参考 PPT 里"一条主线铺 8—10 页"的做法,在时长变短时要压成 1—2 页并合并同类项。
与其他技能的分工
- 需要改已有公文并标红:若环境中装有公文改稿标红类技能,可配合使用。
- 需要校对公文版式与规范提法:若环境中装有公文校对类技能,可配合使用。
- 本技能负责从 PPT 反推框架 → 生成 PPT 框架版提纲,也负责在既有 PPT 上改造并另存(见下),但不负责从零生成 .pptx。
第二部分:在既有 PPT 上改造并另存
用户说"在这个 PPT 的基础上改,然后另存"时用此法。核心原则:一律走 PowerPoint COM,不要用 python-pptx 重建——COM 能 100% 保留母版、版式、图片、动画资源,而重建会让 180MB 的图片资产归零。
第 1 步 探测环境
import winreg, os
for hive, hn in [(winreg.HKEY_LOCAL_MACHINE, 'HKLM'), (winreg.HKEY_CURRENT_USER, 'HKCU')]:
for exe in ['POWERPNT.EXE', 'wpp.exe']:
for sub in [r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\\' + exe,
r'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\\' + exe]:
try:
k = winreg.OpenKey(hive, sub)
print(hn, exe, winreg.QueryValueEx(k, '')[0])
except Exception:
pass
try:
import win32com.client
print('pywin32 OK')
except Exception as e:
print('缺 pywin32:', e)
没有 Office 时退回 python-pptx(只能改文字,删页需操作 XML)。
第 2 步 复制副本、打开
import win32com.client as win32
app = win32.DispatchEx('PowerPoint.Application')
try:
app.WindowState = 2 # 最小化;注意 Visible=False 会报错
except Exception:
pass
app.DisplayAlerts = 0
pres = app.Presentations.Open(DST, ReadOnly=False, WithWindow=False)
打开 180MB 文件约 3—10 秒。结尾务必 pres.Close() + app.Quit(),否则残留进程。
第 3 步 分阶段改造(每阶段保存并导出样张验证)
阶段 A|全局替换:遍历所有页、所有形状(含 Group 递归、表格单元格),按段落替换以保留格式:
def fix_text_frame(tr, rules):
n = 0
for i in range(1, tr.Paragraphs().Count + 1):
para = tr.Paragraphs(i)
t = nt = para.Text
for a, b in rules:
nt = nt.replace(a, b)
if nt != t:
para.Text = nt.rstrip('\r') # 段落级赋值,保留格式
n += 1
return n
阶段 B|内容改造:改封面/篇章页文字、Duplicate 出新增页后 MoveTo(n) 定位、改主线标识与底部色带文字。
阶段 C|清理与美化:删除模板页复制过来的残留图形;用 Shapes.AddTextbox/AddLine 补内容与装饰;tr.Paragraphs(k).Characters(1, n).Font.Bold = True 给前缀加粗着色。
阶段 D|校对与标注:加待填提示文本框、全量扫描残留关键词。
必知陷阱
| 陷阱 | 表现 | 解法 |
|---|---|---|
| 形状名中英不一致 | '组合' in sh.Name 恒为 False,删除静默失败,内容叠在原图上 |
COM 返回 Group 4/Chart 32 等英文名,python-pptx 返回中文名。先打印 Name 确认再删 |
app.Visible = False |
抛异常 | 用 WindowState = 2 |
| 整框赋值丢格式 | tr.Text = ... 会把全框格式重置 |
用 tr.Paragraphs(i).Text |
Duplicate() 返回值 |
不是 Slide | 是 SlideRange,须 rng.Item(1) |
MoveTo() 后索引漂移 |
循环移动时移错页 | 每次移动后重新按当前索引取页;插入 N 页后后续页号 +N |
AddLine 重复叠加 |
二次运行时多出一条线 | 新建连接符名为 Straight Connector N,无法靠名称识别,按位置判断(如 sh.Type == 9 and 15 < sh.Left < 70 and sh.Top > 85)删除 |
| 组内形状 | 漏改组合内的文字 | 递归处理 sh.Type == 6 的 GroupItems |
交付前的残留扫描
改完用 python-pptx 扫一遍旧关键词(旧年份、旧机构名、旧提法、旧专业名),并打印每页首行文字核对页面顺序:
from pptx import Presentation
prs = Presentation(P)
for i, s in enumerate(prs.slides, 1):
texts = [...各形状文字...]
for k in KEYWORDS:
if k in ' '.join(texts):
hits.setdefault(k, []).append(i)
无法自行补齐的内容(如各学院的专业数据、成果图片),在对应页加红色小字提示「※ 本页内容待××学院提供后替换」,并在交付说明中给出清单——不要编造数据。
第三部分:以模板版式"清空重建"(python-pptx)
适用:用户给了新的模板 PPT,但要按一份新的逐页提纲重做整本(不是改旧内容)、且要求保留模板的母版/版式/配色/装饰。此时第一、二部分都不适用——重建会丢母版,改旧内容又对不上新提纲。
要点:复制模板文件 → 用 python-pptx 把 sldIdLst 清空 → 用模板的 layout 逐页新建。 版式里的装饰(顶栏、金线、logo、背景图)会自动带过来,不必逐个重画。
第 1 步 侦察模板的视觉系统
from pptx import Presentation
prs = Presentation(TPL)
print('尺寸 %.2f x %.2f cm' % (prs.slide_width/360000, prs.slide_height/360000))
for mi, m in enumerate(prs.slide_masters):
print('master', mi)
for i, lay in enumerate(m.slide_layouts):
print(' [%d] %s' % (i, lay.name))
再看 ppt/theme/themeN.xml 的 clrScheme 取品牌色,看 layout 里 shapes 的填充类型确认装饰是否在版式上:
lay = prs.slide_masters[0].slide_layouts[2]
for sh in lay.shapes:
f = sh.fill
print(sh.shape_type, getattr(sh, 'name', ''), f.type) # type=6 → 图片填充
务必查 layout 里矩形的填充类型:若为图片填充(fill.type == 6),在该版式上叠白字会看不清——深色带必须自己用 add_shape 补画,不能指望版式底色。
第 2 步 清空副本
import shutil
from pptx import Presentation
shutil.copy2(TPL, OUT)
prs = Presentation(OUT)
lst = prs.slides._sldIdLst
for s in list(lst):
rId = s.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')
prs.part.drop_rel(rId) # 必须同时 drop_rel,否则残留关系导致文件损坏
lst.remove(s)
必须 drop_rel 再 remove,只删 sldIdLst 节点会留下悬空关系,PowerPoint 打开时报修复。
第 3 步 建公共版式库
不要在一支脚本里堆 30 页代码。抽出 deck_lib.py:设计令牌(色值、尺寸 cm、边距)+ textbox / P / rect / pic / notes / card / kpi / chip / open_deck / add / title。生成脚本只写"第几页放什么"。改版式只改一处。
关键实现:
- 字号行内统一设字体:中文模板必须把
a:latin / a:ea / a:cs三个typeface全设,只设latin中文会回落到宋体。 - 图片"填充式"裁切:
add_picture后按crop_left/right/top/bottom居中裁剪,才能保证任意比例的图都铺满不留白。 - 半透明遮罩:
add_shape后往a:srgbClr里插<a:alpha val="66000"/>(万分比)。 - 去阴影:给
spPr追加空<a:effectLst/>,否则默认主题会带阴影。
第 4 步 写备注(讲稿提纲)
新建的页若版式不含备注占位符,slide.notes_slide.notes_text_frame 返回 None,直接写会报错。补插占位符:
ns = slide.notes_slide
if ns.notes_text_frame is None:
xml = ('<p:sp %s><p:nvSpPr><p:cNvPr id="9" name="Notes Placeholder 9"/>'
'<p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>'
'<p:nvPr><p:ph type="body" idx="1"/></p:nvPr></p:nvSpPr>'
'<p:spPr><a:xfrm><a:off x="685800" y="4343400"/>'
'<a:ext cx="5486400" cy="3600450"/></a:xfrm>'
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>'
'<p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody></p:sp>') % nsdecls('p', 'a')
ns.shapes._spTree.append(parse_xml(xml))
陷阱:包一层 helper 时务必 s = chapter(...) 接收返回值。 若写成裸调用 chapter(...) 再 notes(s, ...),s 还停留在上一页,备注会静默串页(后面所有页跟着错位)。交付前必须逐页打印备注核对。
第 5 步 渲染质检
用 PowerPoint COM 导出 PNG 逐页看,别靠想象:
app = win32.DispatchEx('PowerPoint.Application')
app.WindowState = 2 # Visible=False 会抛异常
pres = app.Presentations.Open(TMPCOPY, ReadOnly=True, WithWindow=False)
for i, sl in enumerate(pres.Slides, 1):
out = os.path.normpath(os.path.join(OUTDIR, 'P%02d.png' % i)).replace('/', '\\')
for _ in range(4): # 偶发失败,重试
try:
sl.Export(out, 'PNG', 1600, 900); break
except Exception:
time.sleep(0.6)
导出路径必须 os.path.normpath(...).replace('/', '\\'),否则报「PowerPoint 无法将 ^0 保存到 ^1」。打开前先 taskkill /F /IM POWERPNT.EXE 清残留进程,并把文件复制到临时目录再开,否则 Presentations.Open 报"发生意外"。
交付前必做三项校验
- 备注覆盖:
s.has_notes_slide且文字非空,逐页打印,页数对得上、无串页。 - 引号规范:正文+备注合并统计,ASCII
"'必须为 0,中文“/”成对。 - 视觉抽查:至少看封面页、篇章页、典型内容页、结尾页各一,确认深色带上白字可见。
配套交付物
除 PPT 外,通常应一并给出:
- 逐页备注 Word 稿(页号+页面标题+备注原文)——便于领导脱离 PPT 审稿;
- 图片素材清单(选用文件名|来源|原始编号|尺寸/新闻出处)——满足"来源可溯"要求;
- 待核数据清单——凡脚本里写"(待××处核定)"的,汇总成表,交付时一并说明。