Working Log

April 12, 2026 · View on GitHub

Changelog

2026-04-11 · Day 1 · Scaffolding + M1/M2/M3/M6 + E2E

  • 创建项目骨架(docs/, src/pptx_skill/, scripts/, tests/)
  • pyproject.toml(python-pptx, Pillow, fonttools + pytest),AGENTS.md,README.md,.gitignore
  • docs/prd.md 定义目标、验收标准、feature list、aggressive transparency 原则
  • docs/rfc.md 定义模块划分、公共 API 签名、CLI 命令 shape、实现里程碑(12 steps)
  • docs/test.md 定义三层测试策略
  • 决定 render path 走 soffice → PDF → pdftoppm → PNG(单 slide render 的唯一可行路径)
  • 决定 shape id 用 OOXML cNvPr id 而非 python-pptx 的 index
  • 决定 CLI 用 argparse(标准库),print 到 stderr 而非 logging
  • M1 实现: doctor 命令,报告 python 依赖版本 + soffice/pdftoppm 状态 + deck 字体列表 + 问题字体 warning
  • M2 实现: Deck/Slide/Shape 类的 read 部分,CLI: list-slides, list-shapes, get-text, get-notes, dump-slide, dump-deck 全部 working
  • M3 实现: Shape.set_text(支持 paragraph/run targeting + 保留 first run 格式),Slide.set_notes,Deck.save(含 .bak 自动备份),CLI: set-text, set-notes--dry-run。Roundtrip test pass
  • M6 实现: render_slide_png,LibreOffice → PDF → pdftoppm 链路,filter macOS harmless warning,CLI render-slide
  • E2E 验证成功: 用 pptx-skill set-text 真的把 v5 deck slide 0 的 Part 4 改成 "两个实战案例 · 落地方式" + "15 min",用 render-slide 渲染出 PNG,AI 视觉确认所有改动正确、其他元素未动、layout 保真

Lessons Learned

关于 LibreOffice render

  • soffice --convert-to png deck.pptx 只输出 slide 1,不能指定 slide index。要 render 特定 slide 必须通过 PDF 中间格式
  • 因此 render 功能需要两个 optional 依赖: LibreOffice(soffice)和 poppler(pdftoppm)。doctor 命令必须检查两个都在才宣告 render ready
  • 首次启动 soffice 有 5-10 秒的 user profile 初始化延迟,后续调用快

关于字体

  • v5 deck 主要用 Calibri(英文)+ 隐式 fallback(中文)。Calibri 是 Microsoft 专有字体,LibreOffice fallback 到 Liberation Sans,字宽不同
  • Georgia 在 v5 里用于大标题,是跨平台字体,不需要替换。normalize_fonts 的替换列表应该是 allowlist(只替换已知问题字体),不是 blocklist
  • East Asian 字体需要通过 OOXML 的 <a:ea typeface="..."> 单独设置,不能和西文字体一起写

关于 v5 deck 的 clone-ability

  • v5 是程序化生成的 deck(shape 命名规律 Text N / Shape N),结构非常扁平
  • 所有 18 张 slide 都用一个自定义的 DEFAULT layout(不是 Office 的标准 title / content layout)
  • 没有任何 SmartArt / Chart / 3D / Animation / Table 等复杂元素
  • 所有 shape 都是 Rectangle + Picture 两种类型,python-pptx 完全覆盖
  • 结论: v5 deck 理论上 100% 可以用 python-pptx clone,这是 PRD success criteria 1 的前提

Render smoke test(Day 1 完成)

环境验证:

  • soffice/opt/homebrew/bin/soffice,LibreOffice 26.2.2.2
  • pdftoppm/opt/homebrew/bin/pdftoppm,poppler 26.03.0
  • 两个都已通过 Homebrew 装好,PATH 上可见,不需要手工 symlink

烟雾测试命令序列:

soffice --headless --convert-to pdf --outdir /tmp/pptx_smoke Sammi_Demo_Deck_v5.pptx
# → /tmp/pptx_smoke/Sammi_Demo_Deck_v5.pdf (990KB, 18 页)

pdftoppm -png -f 1 -l 1 -r 150 /tmp/pptx_smoke/Sammi_Demo_Deck_v5.pdf /tmp/pptx_smoke/slide
# → /tmp/pptx_smoke/slide-01.png (97KB, 150 DPI)

验证结果:

  • 生成 PNG 尺寸 97KB,内容清晰可辨
  • 品牌青绿色 #0D9488 完美保留
  • 黑色背景 + 白色 / 灰色文字 OK
  • 中文字体 fallback 可读,字宽无明显溢出
  • Georgia 大标题字体正常(跨平台字体,无需替换)
  • 议程 "Part 4 · Technical Demo · 实战案例现场演示 · 30 min" 清楚可见
  • 保真度结论: 比预期的高,足够 AI reality check 用途

实现细节记录:

  • pdftoppm 的输出命名是 <prefix>-<page:02d>.png,所以单页 slide 1 输出为 slide-01.png 而不是 slide-1.png。实现时要用 f"{prefix}-{page:02d}.png" 匹配
  • 首次 soffice 调用 ~5-7 秒(含 LibreOffice user profile 初始化),后续调用会快
  • render-slide 的实际耗时: soffice(7s)+ pdftoppm(1s)+ 移动文件(<1s)≈ 8-9 秒 for cold start
  • 可以优化: 一次 deck 的多次 render 应该复用同一个 PDF,不要每次都重新 convert

一个 macOS 特定的无害 warning(可以忽略):

soffice[27698:98951495] Task policy set failed: 4 ((os/kern) invalid argument)

这是 macOS 上 LibreOffice 的已知 harmless log,不影响 render 结果。CLI 应该 filter 掉这一行避免污染 stderr 输出。