pptx-insert-pages-editor-sdk
通过本地 editor_sdk(tencent-local-office-edit 技能)往已有 PPTX 插页的完整工作流。
核心思路:复制一张现成内容页 → 剥掉正文只留页眉页脚骨架 → 改骨架文案 → 重建正文。
这样新页自动继承母版、页眉装饰条、页脚灰条,不用手工复刻。
0. 入口
先走 tencent-docs-routing(本地 slide 单文件编辑 → 转 tencent-local-office-edit),再按该技能用 edsdk.py 调工具。
SKILL_DIR="D:/Workbuddy/resources/app.asar.unpacked/resources/plugins/workbuddy-builtin/skills/tencent-local-office-edit"
cd "$SKILL_DIR" && python3 edsdk.py schema <工具名> # 用到一个查一个,别全量拉
优先复用 host 注入的 file_id(<active_document ... file_id="..."/>);没有就 get_pool_status 按路径反查。
present_files 之后不要再 open_file(会开出与前端 SSE 脱节的孤儿实例)。
1. 先踩点,别急着写
# 全片扫标题,搞清章节结构与插入点
for pg in range(0, slide_count):
d = slide_get_page_info(pg) # 取 font_size>=20 的第一个形状当标题
必须确认三件事,否则容易白做:
- 插入点的语义——「第42页后面」= 1-based 页42 =
page_index41 之后。先打印 41/42/43 页确认。 - 有没有内容重复——目录/大纲指定的内容,后面是不是已经有一套更详细的页?有重复就问用户(保留/跳过/替换),别默默插进去。
- 页脚编号体系——先扫一遍邻页页脚是
xx / 71、xx / 06还是xx / 02。多数 deck 编号本身就是乱的;沿用用户此前口径(如「先不写页码」),别自己发明编号。
2. 复制骨架页
挑一张结构最接近、有完整页眉页脚的内容页当模板(不是章节封面页——封面页页脚通常是空的)。
for k in range(N):
slide_duplicate_slide(page_indexes=[TPL], target_page_index=INSERT_AT + k)
> ⚠️ target_page_index 的语义是「副本落在哪个索引」,不是「插在哪个索引之后」。
> 源页 index=42、target 依次 43..47 → 副本落在 43–47,源页仍留在 42。
> 也就是说新页会比你预期的位置晚一位。
>
> 对策:先用 page_indexes=[我的新页...] + before_slide_index=源页index 整体挪一次:
> python
> slide_move_slide(page_indexes=[43,44,45,46,47], before_slide_index=42)
>
> 这个写法在两种索引解读下结果都对,最稳。
> 插完必须回读 slide_get_page_info 逐页验证顺序,不要凭 target 参数推断。
3. 剥离正文,只留骨架
按位置白名单保留:y <= 82(背景、装饰条、标题、英文标签、副标题)或 y >= 490(页脚条、页脚线、页码、页脚字)。其余全删。
keep, rem = [], []
for s in page_info["shapes"]:
(keep if (s["y"] <= 82 or s["y"] >= 490) else rem).append(s)
slide_remove_shapes(page_index, [s["id"] for s in rem])
剥离后一页通常剩 9 个骨架形状。先打印保留清单确认,再执行删除。
4. 改骨架文案
对标题/英文标签/副标题用 slide_set_text,紧接着必须补 slide_set_text_property:
> ⚠️ slide_set_text 会重置逐字符样式(粗体/颜色/字号 → 默认值),所以改完字一定要重新上样式。
> ⚠️ slide_set_text 传空字符串不会清空文本(被当成「无变更」静默忽略)。
> 要清页码/清标签,直接 slide_remove_shapes 把那个形状删掉。
5. 重建正文
slide_add_shapes(批量建底:卡片矩形 + 左侧强调竖条 + 编号圆角块)+ slide_add_texts(批量建文本)。
> ⚠️ slide_add_texts 不支持 bold 字段——传了静默丢弃;font_size 还会被取整(12.75→12、15.75→15、11.25→11)。
>
> 必须补一遍样式,且要用 slide_set_text_property 的批量一对一模式(传 shapes 数组),
> 一次调用搞定整页,比逐个 shape 调快一个数量级:
> python
> slide_set_text_property(
> file_id=FID, page_index=pg,
> shapes=[{"shape_id": sid, "index": 0, "count": -1,
> "font_size": 11.25, "color": "1F4E79", "font_name": "微软雅黑", "bold": True}, ...])
>
> index: 0, count: -1 = 整段。只传 shape_id 不带 index/count 会报 -32603。
slide_add_shapes 里每个形状至少要设 fill_color 或 border_color,否则报错。
6. 设计令牌怎么拿
不要猜。挑一张标准内容页 dump 出真实几何,抄下来当常量:
page_info = slide_get_page_info(参考页)
# 逐形状打印 x/y/w/h/fill/font_size/font_color/font_name
典型 960×540 内容页令牌(换 deck 必须重新取):
- 装饰竖条 x=48 y=20 w=3 h=21;标题 x=63 y=15 25.5pt 粗体;英文标签 x=835 y=15 10.5pt;副标题 x=63 y=62 12pt
- 页脚灰条 x=48 y=495 w=864 h=30;页码 x=879 y=504;页脚字 x=53 y=507
- 内容区安全边界:x ∈ [48, 912],正文底部不超过 y=488(页脚条从 495 开始)
想给多个概念做视觉编码时,钉死三个色(如 技能=蓝 / 专家=紫 / 连接器=橙)并在全页保持一致。
7. 保存与验证
slide_move_slide(...) # 若需挪位
# 几何自检:越界 / 超宽 / 压页脚
for s in shapes:
assert 0 <= s["x"] and s["x"]+s["w"] <= 960.5
assert 0 <= s["y"] and s["y"]+s["h"] <= 540.5
if s["x"] >= 318 and s["x"]+s["w"] > 913: 报「超宽」
if 490 < s["y"] < 495: 报「压页脚」
save_file(file_id=FID) # 不要主动 close_file
保存前先备份(cp 一份带时间戳的副本),并解包 XML 复验:
zipfile.ZipFile(P).read(f"ppt/slides/slide{n}.xml")
# 查 sz="..." 是否为半磅值(1125/1275/1425 而非 11/12/14)→ 证明样式修正生效
# 查 b="1" 数量 → 证明粗体生效
> save_file 报 "Export file is occupied" = 原文件被 PowerPoint 占用(同目录有 ~$ 锁文件)。
> 保存到新路径即可绕过;要写回原路径需先让用户关掉 PowerPoint。
8. 环境坑(Windows + Git Bash)
- Bash 报
dirname: command not found/cd: null directory(exit 127)不是坏了,是 PATH 被清空 → 命令前加export PATH="/usr/bin:/bin:$PATH"即可。 - 脚本写到工作目录(如
D:\ProgramData\workbuddylog\<session>\),别写/tmp(在 Windows 上映射到别处)。 - 用
common.py+ 每页一个bN.py的方式组织,脚本里from common import *,避免每个脚本重复写call()/mk()样板。 - 收尾用
[System.IO.File]::Delete(path)批量删临时脚本;rm -f a b c ...长链在本机会被沙箱 SIGTERM。
最小调用序列
get_pool_status → slide_get_info → 扫标题定位
→ slide_duplicate_slide ×N
→ slide_remove_shapes(剥正文)
→ slide_set_text + slide_set_text_property(改骨架)
→ slide_add_shapes + slide_add_texts + slide_set_text_property(shapes=[...])(建正文)
→ slide_move_slide(如需挪位)
→ 几何自检 → 备份 → save_file → 解包 XML 复验