PPT 视觉重做与逐页自检
0. 这个 skill 解决什么
用 tencent-pptx 插件的 slidep 工作流(受限 DSL → OOXML)重做 PPT 时,真正卡人的不是设计,是三件事:
1. 页面溢出 —— lint 会报 child-containment,但不告诉你为什么;
2. 多页循环写入时页序错位;
3. 写完不知道长什么样,改到最后一页才发现前面全歪。
本 skill 把这三件事的判定与修复固化下来,让"重做一份 20 页 PPT"变成可稳定复现的流水线。
先决条件:必须通过 tencent-docs-routing → tencent-pptx 进入(读路由 skill,不要自己拼命令)。本 skill 是它的执行纪律补充,不替代它。
1. 环境准备(每次开工先做,否则后面全卡)
# 1) markitdown / python-pptx 可能没装;装到受管 venv,不要污染全局
unset PYTHONPATH
/Users/jx/.workbuddy/binaries/python/envs/default/bin/pip install -q python-pptx markitdown pillow
# 2) 抽正文
unset PYTHONPATH
/Users/jx/.workbuddy/binaries/python/envs/default/bin/python3 -m markitdown "<src.pptx>" > material_raw.md
# 3) 抽内嵌图(比 pptx2image 更可靠,本机没有 LibreOffice)
unset PYTHONPATH
/Users/jx/.workbuddy/binaries/python/envs/default/bin/python3 \
"<tencent-pptx skill>/scripts/doc_image_extractor.py" "<src.pptx>" -o "assets_src"
重要事实(本机实测)
- unset PYTHONPATH 是必须的,否则 markitdown 这类重包 import 会因 WorkBuddy shim 卡死。
- scripts/pptx2image.py 不可用(依赖 LibreOffice,本机未装)。要看渲染效果请用 slidep screenshot(见 §4)。
- doc_image_extractor.py 可用,能把 pptx 里的位图导出为 pptx_s{N}_img{M}.png(s{N} = 第 N 页)。
2. 动手前的两个文件(不要跳过)
在 <workspace>/<项目名>/ 下先写死 STORY.md(叙事)+ DESIGN.md(视觉宪法),再碰任何 .slide。
- DESIGN.md 必须写死的量:画布 1280×720;三区 px(推荐 A 标题块 y20–116 / B 内容区 y132–640 / C 页脚 y656–700,Slide
padding: '20px 64px');≤7 个 hex;≤2 套字体家族;字号阶梯;每页 色彩面积分配 + 版式 + 是否 Hero。 - STORY.md 必须显式标注"目录第 k 章 → 第几页是它的 section 扉页"。路由硬约束:目录声明 N 章 → 全篇必须有且仅有 N 个
type: section扉页,编号连续、标题逐字一致。原稿常常只有目录没有扉页,这时候补扉页是"视觉升级"而不是"改结构",要在回复里说明。 - 有材料时:材料里有图就优先用材料图(真图 > 生图),只在缺图时才走 ImageGen。
3. 逐页生成的纪律(最容易翻车的地方)
3.1 一个页面一个文件,写完立刻 lint + upsert,永不批量后置
用下面这个脚本,失败即停:
# .check/put.sh usage: put.sh <NN> <replace|append> [page-index]
#!/bin/zsh
P="<项目绝对路径>"; cd "$P"
F="$1"; MODE="$2"; IDX="$3"
OUT=$(slidep lint "<file>.pptx" --dsl-file "slides/$F.slide" 2>&1 | tail -1)
[[ "$OUT" != *'"ok":true'* ]] && { echo "LINT FAIL $F :: $OUT"; exit 1; }
if [[ "$MODE" == "replace" ]]; then
R=$(slidep upsert-dsl "<file>.pptx" --dsl-file "slides/$F.slide" --page-index "$IDX" 2>&1 | tail -1)
else
R=$(slidep upsert-dsl "<file>.pptx" --dsl-file "slides/$F.slide" 2>&1 | tail -1)
fi
[[ "$R" != *'"ok":true'* ]] && { echo "UPSERT FAIL $F :: $R"; exit 1; }
echo "OK $F"
调用时必须 fail-fast:
for f in 01 02 03 04; do .check/put.sh $f append || exit 1; done
> ⚠️ 坑位(实测踩过):slidep upsert-dsl 省略 --page-index 是"追加到末尾"。如果写成 for f in ...; do put.sh $f append; done(不带 || exit 1),中间某页 lint 失败后循环继续,后续页面会整段错位,而且 lint 不会告诉你。
> 事后才发现页序乱了怎么办:用 unzip -l <file>.pptx | grep -c "ppt/slides/slide.*\.xml" 数页数;然后用 upsert-dsl --page-index <N> 逐页替换回正确内容(0-based),比推倒重来快得多。
3.2 溢出排查:child-containment 的读法与修复优先级
lint 报错长这样,只会给行号 + 超出量:
child element exceeds parent container by bottom+68px:
10 | <Box style={{ height: 508, ... }}>
30 | <Box style={{ flex: 1, flexDirection: 'column', gap: 12 }}>
修复优先级(从高到低):
1. 把 flex: 1 换成显式 height: N。这是最有效的一招:flex: 1 在内容体积接近份额时会溢出,显式高度 + 留 8–12px 余量最稳。
2. 缩短文案(中文一行只能放 ≈ 容器宽 ÷ 1.15 ÷ 字号 个字)。
3. 降字号 / 降 lineHeight(15px + 1.45 是安全组合)。
4. 减少条目数或 gap。
3.3 中文字宽经验值(关键,别按 1.0 倍算)
实测本渲染管线里 中文字符实际占宽 ≈ 1.15–1.6 × fontSize(比 fontSize 本身宽),且行高按 fontSize × lineHeight 计算。
安全估算式:
每行可容纳字数 ≈ 容器可用宽 ÷ (fontSize × 1.15)
需要行数 = ceil(字数 ÷ 每行字数)
按 1.15× 估、并给高度留 10% 余量,基本一次过。按 1.0× 估必翻车。
(注意:同一条 Text 在正好放下时给 0 余量也会被判定溢出,务必留余量。)
3.4 已踩过的 DSL 细节
| 现象 | 原因 / 对策 |
|---|---|
| ≤20px 的 FAIcon 实心图标在深色底上像"绿色色块" | 小尺寸别用实心 FAIcon。改用内联 <svg> 细描边符号:chevron (<path d='M2 2 L10 8 L2 14' stroke>)、4×4 圆角小方块、圆环 + 等宽缩写(IN / LLM / PY / IF / !) |
| 卡片底部空一大截,看着"没填满" | 容器填充率要求 ≥85%。把正文字号从 15→17、gap 从 8→14,或把尾部元素用 marginTop: 'auto' 钉底 |
| 一页里同一行的兄弟卡片高度不齐 | 行容器加 alignItems: 'flex-start',并给子项各自固定 height;不要靠内容自然撑高 |
| 图片右下角有 "AI生成 WORKBUDDY" 水印 | ImageGen 产物自带水印,必须裁掉底部约 150px 再进 assets/(1536×1024 → 1536×874),文件名改英文短名 |
| 图片把主体裁掉了 | objectFit: 'cover' + 容器宽高比要接近图片宽高比;横幅位(如 1152×264)裁竖图会切掉上下 |
4. 视觉自检(这一节是"能不能一次交付"的分水岭)
写完就渲染,逐页看,不要只看截图缩略图总数:
for i in $(seq 0 $((N-1))); do
slidep screenshot "<file>.pptx" --page-index $i --out .check/s$(printf %02d $((i+1))).png
done
slidep screenshot依赖本地 editor_sdk(默认http://localhost:39099,WorkBuddy 桌面端会起),本机可用;pptx2image.py那条路走不通。- 渲染完用 Read 工具逐张读 PNG。重点看四件事:
1. 有没有文字被压扁/重叠/出界(lint 管不到视觉拥挤);
2. 空白分布是不是"均匀稀薄"(这是最典型的 AI 味);
3. 图片主体有没有被裁到;
4. 相邻页版式是否撞车(连续 ≥2 页等宽卡片横排 = 返工)。 - 发现问题就
replace单页,再重新渲染那一页确认,不要整篇重跑。
5. 交付前清单
- [ ] 页数与 STORY.md 的骨架一致;页码
NN / TOTAL全篇统一 - [ ] 目录章节数 ==
type: section扉页数,编号连续且与目录逐字一致 - [ ] Hero 页占 20–30%,且任意两页 Hero 不相邻
- [ ] 全篇 hex ≤ 7、字体家族 ≤ 2;强调色只出现在焦点元素上
- [ ] 相邻页版式不重复;非对称版式占比 ≥ 40%
- [ ] 公司名/品牌名/人名全篇一致(原稿常见不一致,要在回复里明说改了什么)
- [ ] 删除临时产物:
.check/、assets_raw/、抽出的material_raw.md - [ ] 保留
slides/*.slide+DESIGN.md+STORY.md+assets/(下次改单页要用) - [ ] 最后用
present_files打开.pptx
6. 内容红线(做商业材料时尤其注意)
- 不臆造数据:材料只给了汇总值(如"日均 1,810 调用")却要画"近 7 日趋势折线图" —— 不要编 7 天数字。改成用已给的真实 KPI + 结构化图表(如六维治理六边形),并在设计文档里写明这个替换理由。
- 接口级/函数级命名降级表述:原稿写「通过
/verify接口校验」,对外材料改成「通过完整性校验接口校验」。 - 保留全部原有信息量:用户说"视觉统一升级"就是只改视觉,不删内容;要删要合必须在回复里逐条说明。
- 去 emoji:商业材料里的 ⌨️🧠💻🔀🛡 一类 emoji 一律换成矢量符号。
7. 典型时长与规模参考
21 页、4 张配图(2 张材料图 + 2 张 ImageGen)、21 个 .slide 文件:约 40–50 次工具调用。别中途换设计语言,DESIGN.md 定死后严格执行。