关于 html2pptx
html2pptx 是将渲染后的 HTML 页面转换为可编辑 PowerPoint 形状的技能,面向需要把海报交付为演示文稿的用户。它通过浏览器提取 DOM 并生成幻灯片,还可渲染结果、比较视觉差异并迭代修正。
主要功能
- 01将 HTML 转为可编辑 PPTX
- 02检测页面画布与字体
- 03渲染幻灯片并生成对照图
- 04视觉审计版面保真问题
- 05通过隔离副本运行自动修复
适用场景
- 01把 HTML 研究海报转成交付用 PPTX
- 02检查转换后页面与 HTML 的视觉差异
- 03针对单张海报迭代修复保真问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ researchstudio --skill html2pptx安装后在 Claude Code 中输入/html2pptx确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
__init__.py.gitignoredifference.mdGOTCHAS.mdREADME.mdrequirements.txtscripts/__init__.pyauto_correct_loop.pyauto_fix_loop.pyfont_embedder.pyfont_resolver.pyhtml_to_pptx.pyvision_aggregate.pyvision_audit.pyvision_compare.pyvision_eval_harness.py详细介绍
html2pptx
将渲染后的 HTML 页面转换为原生 PowerPoint .pptx,并保留可编辑形状(而非插入 PNG)。
适用场景
- “将这个 HTML 海报转换为 PPT。”
- “我需要这个 HTML 页面对应的可编辑 PowerPoint 版本。”
- “按 1:1 还原 poster.html 到 PPT——字体、颜色和布局都要一致。”
- “把海报交给不懂开发的人,让他们用 PPT 编辑。”
不适用场景
- 需要纯 PNG 幻灯片 → 使用
paper2poster/demo/presentation/posters.pptx。 - HTML 使用了大量 JS 动画或视频 → 这些内容无法转换为 PPT 形状。
- 内容大量使用 KaTeX 数学公式 → 数学公式会通过栅格化作为后备方案处理(失去公式可编辑性)。
一次性设置
1. Python 依赖项
pip install python-pptx playwright pdf2image lxml Pillow pyphen cairosvg
playwright install chromium # ~110MB headless browser
2. soffice(用于验证)
# Linux:
sudo apt install libreoffice-impress
# Mac:
brew install --cask libreoffice
3. Web 字体——对还原度至关重要(阅读 GOTCHAS.md)
默认字体 = Arial → 可以跳过本节。 paper2poster 的默认字体是 Arial,因此海报会以 Arial(或其他适用于 PPT 的字体系列:Calibri、 Aptos、Cambria、Times New Roman、Verdana、Georgia、Trebuchet MS)进行渲染。这些字体 已预装在所有 Mac 和 Windows PowerPoint 上,因此无需安装 下方提到的字体,且
.pptx无需嵌入字体即可在不同设备间使用。 下方的 Inter / Source Serif 4 / JetBrains Mono 配置仅适用于海报使用可选的 Inter Web 字体覆盖设置进行渲染的情况。
如果未安装 Inter / Source Serif 4 / JetBrains Mono,soffice 会回退到 DejaVu Sans(Linux)或 Helvetica(Mac)——字符宽度会发生变化,文本换行也会不同,所有元素的位置都会随之偏移。
Linux:
# Download static-weight TTFs (variable fonts register under wrong family name)
mkdir -p /tmp/fonts-dl && cd /tmp/fonts-dl
curl -sL "https://github.com/rsms/inter/releases/download/v4.0/Inter-4.0.zip" -o Inter.zip
curl -sL "https://github.com/adobe-fonts/source-serif/releases/download/4.005R/source-serif-4.005_Desktop.zip" -o SourceSerif4.zip
curl -sL "https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip" -o JetBrainsMono.zip
unzip -qo Inter.zip -d Inter/
unzip -qo SourceSerif4.zip -d SourceSerif4/
unzip -qo JetBrainsMono.zip -d JetBrainsMono/
mkdir -p ~/.local/share/fonts
cp Inter/extras/ttf/Inter-{Regular,Medium,SemiBold,Bold,Italic,SemiBoldItalic,BoldItalic}.ttf ~/.local/share/fonts/
cp SourceSerif4/source-serif-4.005_Desktop/TTF/SourceSerif4-{Regular,Semibold,Bold,It,SemiboldIt,BoldIt}.ttf ~/.local/share/fonts/
cp JetBrainsMono/fonts/ttf/JetBrainsMono-{Regular,Medium,Bold,Italic,BoldItalic}.ttf ~/.local/share/fonts/
fc-cache -f ~/.local/share/fonts/
# Verify: fc-match MUST return the font itself, not a fallback
fc-match "Inter" "Source Serif 4" "JetBrains Mono"
Mac: 下载方式相同,但请将字体复制到 ~/Library/Fonts/,并完全退出后重新打开 PowerPoint(Cmd+Q),以加载新字体。
对于使用默认 Arial(或任何适用于 PPT 的字体系列)的海报,查看者无需安装任何字体——这些字体已随 PowerPoint 提供,因此 pptx 可直接跨设备使用。只有在使用可选的 Inter 覆盖设置时,查看者才需要安装 Inter,或者通过可选的 scripts/font_embedder.py 步骤将字体嵌入 pptx(嵌入 4 种 Inter 字重;约 3 MB)。
快速开始 — 一条命令
cd skills/paper2poster/html2pptx
python -m scripts.auto_correct_loop --html /path/to/poster.html
默认情况下,输出文件会保存到输入 HTML 的父目录(同级存放约定,2026-06-12 规范)——例如,my_paper/poster.html → my_paper/poster.pptx。在同一目录中连续运行会覆盖之前的 .pptx。使用 --outdir /custom/path/ 覆盖默认路径。
*Bundle 契约(paper2 pipeline)。作为海报 pipeline 的最终交接步骤调用时,调用方传入 --outdir <bundle>/assets/_pptx_build/,以便所有 html2pptx 产物(DOM json、sanity PNG 和 soffice 渲染文件)都保存在 assets/ 下,然后再将演示文稿移至 bundle 根目录——cp <bundle>/assets/_pptx_build/poster.pptx <bundle>/poster.pptx——因为 poster.pptx 是顶层交付物(也是唯一会离开 assets/ 的 html2pptx 输出)。输入的 poster.html 使用以根目录为基准的 assets/… src 路径**引用图片、徽标和字体;转换器通过 file:// 加载 HTML,因此这些路径会自动相对于 HTML 所在的磁盘位置解析——转换器无需处理路径。
这是规范的入口。它会:
- 自动检测画布,根据 CSS
@page { size: ... }确定画布尺寸;如果海报超过 56"(PPT 限制),则按比例缩放至不超过 55" - 自动检测同级目录中的
poster.pdf(如果存在):对其运行pdffonts,构建 CSS 名称 → PDF 字体的别名映射。写入 fontconfig<match>运行时覆盖项,使浏览器使用与参考 PDF 相同的字体——匹配一一对应。通过atexit清理。 - 自动下载 HTML 引用的其他 Google Fonts 字体(
font_resolver.py) - 提取 DOM(运行一次 playwright,并缓存为
<name>_dom.json) - 构建 pptx → 通过 soffice 渲染 → 生成 PNG
- 保存对比 PNG:
<name>_html_print.png(打印视口,与 PPT 画布一致)和<name>_html_browser.png(1920×1080 浏览器视口,fullPage) - 视觉审查(默认开启):调用 Claude vision 对比
<name>_html_print.png与<name>.png,并写入<name>_audit.json,其中包含结构化的保真度问题(严重程度、类别、block_idx、位置、描述)。如果已有先前的审查 JSON,则会输出差异(例如+/- 3 issues vs previous)。默认模型:Opus 4.8(每张海报约 $0.10;如果预算比检出率更重要,可使用--vision-model claude-sonnet-4-6,每张海报约 $0.02);额外耗时约 70 秒。
模型选择(供调用此技能的编排器使用)
此技能提供两个相互独立的模型选项。用户说“使用 Opus”/“使用 Sonnet”/“使用更便宜的那个”等时,负责编排的 Claude 必须将其转换为正确的标志——技能脚本不支持自然语言解析。
| 用户意图 | 要传入的标志 | 适用范围 |
|---|---|---|
| “使用 Opus”/“最高质量”/未指定模型(默认) | (省略标志——默认使用 Opus 4.8) | 两者 |
| “使用 Sonnet”/“更便宜” | --vision-model claude-sonnet-4-6(如果运行 auto_fix_loop,还需传入 --fix-model claude-sonnet-4-6) | 两者 |
| “不做审查”/“跳过视觉检查”/批量自动化 | --no-vision-audit | auto_correct_loop |
| “视觉审查用 Opus,但修复用 Sonnet”(少见) | --vision-model claude-opus-4-8 --fix-model claude-sonnet-4-6 | 仅限 auto_fix_loop |
两个独立选项,对应两个独立脚本:
scripts/auto_correct_loop.py(构建 + 审查)→--vision-model <MODEL>scripts/auto_fix_loop.py(构建 + 审查 + 自主代码修复子 Agent)→--vision-model <MODEL>和--fix-model <MODEL>(两者默认均为 Opus 4.8;--fix-model会明确传给claude -p --model,确保子 Agent 不会悄然继承编排器的 CLI 配置)
可复现性:两个脚本都会在启动时记录模型选择。视觉审查日志会显示 [vision-audit] calling Claude vision (claude-opus-4-8, auth=...);auto_fix_loop 日志会显示 [fix-loop] models: vision-audit=..., fixer=...。如果用户报告异常行为,可在运行日志中查看所用模型。
Vision-audit 身份验证
有两种方式,任选其一即可:
| 模式 | 环境变量 | 使用场景 |
|---|---|---|
| 直接 API | ANTHROPIC_AUTH_TOKEN(或 ANTHROPIC_API_KEY) | 个人密钥,直接连接 api.anthropic.com |
| Base-URL 代理 | ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL | 企业代理 / Vertex / Bedrock / 公司网关。本地开发机器使用此配置。 |
如果未配置任何身份验证,审核会静默跳过,并显示一行提示(因此离线 / CI 运行无需指定 --no-vision-audit)。在批处理自动化中传入 --no-vision-audit 可显式禁用(100 张海报 ≈ $2 + 额外约 2 小时)。
3 轮修订工作流(开发周期)
Vision audit 用更实用的信号取代了旧的基于 PIL 的 L2 循环:结构保真度差异,而非外观上的字体缩小。每次重新运行都会再次执行审核,并显示 +/- N issues vs previous——开发迭代循环如下:
# round 1: baseline (defaults to ./output/<timestamp>/)
python -m scripts.auto_correct_loop --html poster.html
# → look at output/<ts>/poster_audit.json, identify top systemic bug
# round 2: after fixing the bug in html_to_pptx.py
python -m scripts.auto_correct_loop --html poster.html --outdir output/<ts>/
# → output shows e.g. `[vision-audit] 2 issues (-9 vs previous)`
# round 3: fix next bug, target near-zero high-severity
python -m scripts.auto_correct_loop --html poster.html --outdir output/<ts>/
根据实际经验:新的 HTML 模板经过 2-3 轮即可解决大多数问题。每轮约需 1 分钟 + ~$0.02。
跨海报汇总:python -m scripts.vision_aggregate <root_dir> 会遍历所有 <poster>/poster_audit.json,并输出类别频次 + 每张海报的问题数 → 揭示值得通过修改代码来修复的 SYSTEMIC 错误(同一类别的问题出现在 N 张海报中),而非一次性问题。
自主修复循环(auto_fix_loop.py)——完全由 Agent 驱动
对于单张海报的保真度修复,如果不希望这些修复影响已发布的技能,请运行:
python -m scripts.auto_fix_loop --html /path/poster.html --max-rounds 3
每轮:
- 构建并审计(通过
output/<ts>/_skill_run_copy/下scripts/的隔离副本) - 从审计中选出最值得处理的问题(类别白名单会过滤掉容易受视觉判断影响的误报,例如
alignment_off) - 启动
claude -p子 Agent,并将其限制在副本中(--add-dir <copy>,仅提供 Read/Edit/Grep/Glob 工具——不提供 Bash、git,且无法越界) - 子 Agent 读取
GOTCHAS.md和副本中的html_to_pptx.py,进行最小差异修改 - 重新构建并再次审计
- 如果问题数量减少 → 将更改保留在副本的内部 git 中
否则 → 在副本中执行
git reset --hard→ 尝试下一个问题 - 循环终止条件:收敛(没有可处理的问题)、达到最大轮数,或达到连续失败上限
每次运行相互隔离是此设计的关键:已发布的 skills/paper2poster/html2pptx/ 永远不会被修改。每次运行都会生成:
output/<ts>/<name>.pptx— 最终修复后的 PPToutput/<ts>/<name>_audit.json— 最终审计结果(目标:0 个问题)output/<ts>/<name>_run_patches.diff— 相对于基线的子 Agent 累积差异(请审阅!)output/<ts>/_skill_run_copy/— 修补后的技能副本(可使用相同 HTML 重新运行)output/<ts>/<name>_fix_loop_summary.json— 每轮处理轨迹
如果审阅 .diff 后发现修复确实具有普遍适用性(而非针对特定海报),请通过常规 PR 将其 cherry-pick 到已发布的技能中。否则,补丁只会作用于该输出目录,不会影响其他海报或其他人的安装。
成本:使用默认模型时,每次收敛运行约为 1–3 美元(视觉审计和修复子 Agent 均使用 Opus 4.8)。可通过 --vision-model / --fix-model(例如 claude-sonnet-4-6)覆盖其中任一模型,以约 5 倍的成本降幅为代价,降低问题检出率并减弱根因分析能力。两种模型的选择都会在运行开始时记录,因此运行结果完全可复现。
身份验证:与 --vision-audit 相同(直接使用 ANTHROPIC_AUTH_TOKEN,或搭配 ANTHROPIC_BASE_URL 使用企业代理)。
已于 2026-06-05 移除:此前的“L2 闭环”字体缩小 轮次——PIL 预测的溢出与 soffice 的实际渲染结果脱节 (审计显示,paper_4_d 在 4 轮中的溢出数量始终为 17→17→17→17),而且 缩小器偶尔会误缩正常的正文段落。现已由默认启用的
--vision-audit取代, 它能发现真正的保真度问题(缺失的 logo、丢失的 span、错误的换行)——这些问题 是 PIL 无法发现的。换行保真度问题现已在 DOM 提取阶段解决 (浏览器通过 CSS 行高阈值使用extractWrapLines),而不是 事后处理。--rounds/--shrink-step标志仍可解析(静默空操作), 因此不会破坏现有调用位置。
输出约定:默认情况下,输出会保存到 dirname(input_html)/(源 HTML 同级目录);传入 --outdir <path> 可覆盖此设置。
默认幻灯片尺寸为 47×33.1"(A0 横向)。可通过 --width-inch / --height-inch 覆盖。
字体工作流
该技能按以下优先级链解析字体:
-
存在同级的
poster.pdf(或使用--reference-pdf标志):该技能通过pdffonts提取 PDF 中实际使用的字体,构建别名映射(例如,CSSInter→ PDFArimo),在本地安装 PDF 字体,并写入 fontconfig<match>运行时覆盖规则,强制浏览器使用 PDF 字体(而不是 CSS 中指定名称的字体)进行渲染。PPT XML 字体名称 = PDF 字体名称。与同事已有的 PDF 渲染结果匹配时,这种方式的保真度最高,尤其是在其机器上使用的回退字体与 CSS 设计意图不同的情况下。 -
不存在同级 PDF:该技能会遍历 HTML 中所有
font-family声明,并通过font_resolver.py自动从 Google Fonts 下载尚未安装的字体。设计意图中的字体(Inter、Source Serif 4、JetBrains Mono、Roboto、Playfair Display 等)只要在 Google Fonts 上提供,都会自动安装。 -
CSS / Mac / Win 系统字体中没有 Google Fonts 对应字体的,会通过
html_to_pptx.py中的FONT_ALIASES映射到已安装的等效字体(Inter、JetBrains Mono、Source Serif 4),同时用于浏览器端的 fontconfig 别名和 PPT 端的 run.font.name。
验证输出
# Open the last round's PNG; compare with the HTML render PNG:
ls /tmp/uframe_round_*.png
# soffice → PDF → PNG at 96 DPI to match playwright (DPI mismatch = false font-size-look-smaller bug)
重要:所有地方都使用 dpi=96。对比 PNG 中的 DPI 不匹配会造成视觉尺寸差异,看起来像是字体大小错误,但实际上并非如此。
soffice 渲染 ≠ PowerPoint 渲染。 务必在实际的 PowerPoint(Mac/Win)中检查最终的 .pptx。soffice 在阴影模糊、字体整形器字距调整和渐变色标方面经常存在差异。
架构
HTML
↓ [playwright chromium] render + DOM walk
↓ getComputedStyle on every visible element
↓ canvas.fillStyle = anyColor → normalized rgba()
DOM extract (cached as <name>_dom.json)
├─ elements → boxes (bg/border/gradient/shadow) + images (with object-fit + decoration tile)
└─ text_blocks → block-level (p/li/h1-h6/figcaption/td/th) with inline runs
(incl. ::before/::after pseudo-element content as inherited-style runs)
↓ [python-pptx + lxml] generate native shapes (Z-order by DOM depth)
PPTX
- Picture (object-fit:contain math → centered, letterboxed inside content area = bbox - padding)
+ optional decoration tile UNDER picture (CSS background-color/border-radius/box-shadow)
+ SVG → PNG via cairosvg before embed
- Rectangle / RoundedRect (CSS border-radius → exact roundRect adj)
+ solid fill / linear gradient (gradFill XML)
+ border (line.color/width)
+ box-shadow (outerShdw effect)
- TextBox per block element
+ multiple inline Runs (one per <strong>/<em>/text node/pseudo-element)
+ CSS padding → tf.margin_*
+ CSS line-height → absolute Pt line_spacing
+ CSS text-transform applied per-run (so ::before uppercase doesn't inherit paragraph 'none')
+ CSS letter-spacing → OOXML rPr spc
+ CSS hyphens:auto → pyphen-inserted U+00AD soft hyphens
+ <li> → native OOXML <a:buChar> with hanging indent + section color marker
+ overflow:hidden + line-clamp → PIL-measured truncation + "…"
+ single-word short text (≤6 chars, no spaces) → word_wrap=False (honors browser-proven fit)
PPTX (final)
↓ [optional: --vision-audit] Claude vision diff (html_print.png vs ppt.png)
↓ structured fidelity report (<name>_audit.json): 12 categories × 3 severities
↓ delta vs previous audit if one exists → dev-cycle smoke test
功能表
| 功能 | 状态 |
|---|---|
| 位置 / 尺寸像素级精准 | ✓ |
| 计算后的颜色(oklab / color-mix / color() / hsl / 命名颜色) | ✓ 通过 canvas 实现 |
| Alpha 合成(rgba 的 α<1 → 与白色合成) | ✓ |
同一段落中的行内 <strong>/<em> 作为文本运行 | ✓ |
| 章节彩色横幅(h3 背景 + 文本 + 编号标记) | ✓ |
| 线性渐变 | ✓ (gradFill) |
| 盒阴影 | ✓ (outerShdw) |
| 圆角(精确 CSS px → roundRect adj) | ✓ |
| 各边单独设置边框(border-top/right/bottom/left、不对称) | ✓ 每个存在的边都使用细 Rect |
| 行内文本背景 → 文本运行高亮(文本突出显示颜色) | ✓ 符合架构规范的 rPr 顺序 |
上标 / 下标(<sup>/<sub> 或 CSS vertical-align) | ✓ 基线偏移 + 可读性下限 |
| 图片 object-fit:contain(在边界框内留白) | ✓ |
| CSS 内边距 → 文本框边距 | ✓ |
| CSS 绝对行高(消除尾随空白) | (需要时采用) |
| CSS hyphens:auto(libhyphen → pyphen 软连字符) | ✓ |
| 带悬挂缩进和章节颜色的原生 OOXML 项目符号 | ✓ |
| overflow:hidden + line-clamp 截断 | ✓ |
从 @page { size: ... } 自动检测画布尺寸 | ✓ |
| 将幻灯片限制为 PPT 56" 并按比例缩放 | ✓ |
| 从 Google Fonts 自动下载字体 | ✓(通过 font_resolver) |
| 自动检测同级 poster.pdf → 提取其中字体并建立别名映射 | ✓ 存在时 |
将 CSS ::before/::after 生成内容作为继承样式的文本运行 | ✓(支持“So what →”提示框、内容箭头图标) |
| 每个文本运行的 text-transform 覆盖(伪内容大写,不受段落 'none' 影响) | ✓ |
<img> 装饰底板(透明 PNG logo 下方的白色圆角矩形) | ✓(CSS background-color + border-radius + padding) |
| SVG 嵌入(通过 cairosvg 栅格化 → PNG) | ✓ |
| 单词较短的文本 → word_wrap=False(如 ICLR/ICML 徽章) | ✓(≤6 个字符,且不含空格) |
| 视觉审计保真度报告(Claude 视觉差异,12 个类别) | ✓ 默认开启(每张海报约 $0.02;无认证时静默跳过) |
| 自主代码修复循环(在隔离的技能副本中运行 claude-p 子 Agent) | ✓ 通过 scripts/auto_fix_loop.py(每次运行约 $0.50-1.50;发布的技能文件不会被修改) |
| 跨海报系统性错误聚合 | ✓ 通过 scripts/vision_aggregate.py |
| 溢出时闭环缩小字体(L2) | ✗ 已于 2026-06-05 移除(PIL 预测与 soffice 解耦,导致其不起作用且可能产生误报;已由 --vision-audit 替代) |
| 匹配换行(强制 PPT 在浏览器位置换行) | ✗ 默认关闭;可通过标志启用 |
| 在 pptx 中嵌入字体(跨机器可移植性) | ✗(第 3 阶段待定) |
| KaTeX 数学公式 | ✗(第 4 阶段使用 PNG 回退方案) |
| CSS transforms / filters | ✗(第 4 阶段使用 PNG 回退方案) |
脚本
scripts/html_to_pptx.py— DOM 提取 + PPTX 构建(接受旧版--corrections,以及用于强制浏览器换行位置的--match-wrap)。支持提取::before/::after伪元素内容(例如「所以呢 →」等 CSS 标注)、逐文本运行的text-transform、<img>装饰图块(透明 PNG 徽标下方的白色圆角矩形),以及通过 cairosvg 栅格化 SVG。scripts/auto_correct_loop.py— 规范入口:单次执行 DOM 提取 → 构建 → 渲染 → 生成 PNG 对比图。处理同目录 PDF 的字体别名和 Google Fonts 自动下载。默认输出目录 =dirname(input_html)/(与输入文件位于同一目录)。启用--vision-audit(默认开启)时,会在渲染后运行视觉差异检查。scripts/auto_fix_loop.py— 自主修复循环:将技能脚本复制到隔离的output/<ts>/_skill_run_copy/,每轮启动一次claude -p,修复副本中的首要审计问题(通过--add-dir进行沙箱隔离),然后重新构建、重新审计,并通过副本内部的 git 保留或回滚。发布的技能保持不变;每次运行的差异会导出,供人工挑选合并。scripts/vision_audit.py— Claude 视觉保真度审计器。接收 (html_truth.png, ppt.png, dom.json) → 结构化问题列表,归类为 12 个类别(missing_element、wrap_mismatch、text_clipped、color_drift 等),并标注严重程度和 block_idx。替代了已弃用的基于 PIL 的 L2 循环。scripts/vision_aggregate.py— 跨海报类别频次汇总。遍历<root>/*/poster_audit.json,打印反复出现的错误及其频次,从而揭示 SYSTEMIC(系统性)问题,而非单次问题。scripts/font_resolver.py— Google Fonts 自动下载 + 同目录 PDF 字体检测 + fontconfig 运行时别名
为何优于基于像素的 PDF→PPTX 转换器
→ 参见 difference.md — 相较于 iLovePDF / Smallpdf 风格的栅格转换器的具体优势:行内文本高亮作为文本运行属性(而非浮动形状)、原生列表项目符号(而非孤立字形)、粗体保留字体系列(而非相似替代字体),以及其他原生语义相较于像素图像的保留优势。
注意事项
→ 参见 GOTCHAS.md — 记录了每种不易察觉的故障模式及其根本原因和修复方法,均源自生产环境中的实际问题。
快速链接:
- G1:字体名称与字体系列(Inter Variable ≠ Inter)
- G2:soffice ≠ PowerPoint(务必在 PowerPoint 中进行检查)
- G3:PNG 对比的 DPI 一致性
- G4:
line_spacing仅在字体正确时生效 - G5:CSS color-mix 返回 oklab — 通过 canvas 进行规范化
- G6:
isInlineOnly基于标签名称,而非 CSS display - G7:h3 编号标记(跳过装饰,重新着色文本)
- G8:object-fit:contain — 手动计算留白
- G9:通过 pyphen + U+00AD 实现 hyphens:auto
- G10:
::marker是生成内容 — 使用 OOXML 原生项目符号 - G11:Mac 版 PowerPoint 也需要安装字体
- G12:可变字体会使 fontconfig 混淆 — 使用静态字重
- G15:
<img>装饰图块必须渲染在图片下方(透明徽标下方放置白色图块) - G16:单词较短的文本 → 设置
word_wrap=False(ICLR/ICML 徽标) - G17:DOM 遍历器无法识别
::before/::after— 通过getComputedStyle(el, '::before')获取 - G18:视觉审计误报:宽边界框中的左对齐内容看起来像居中
依赖项
| 软件包 | 用途 |
|---|---|
| playwright | 用于 DOM 提取的 chromium |
| python-pptx | OOXML 构建 API |
| lxml | python-pptx 不支持时直接操作 OOXML(渐变、阴影、项目符号) |
| Pillow | 图像解码 + 文本测量 |
| pdf2image | PDF → PNG 预览 |
| pyphen | 插入软连字符以实现 CSS hyphens:auto |
| cairosvg | 嵌入前将 SVG <img> 栅格化为 PNG(PIL 无法解码 SVG) |
可选(用于 --vision-audit):
| ANTHROPIC_AUTH_TOKEN | 用于视觉差异检查的 Claude API 身份验证 |
用于验证:
| libreoffice | 通过 soffice 将 PPTX 转换为 PDF |
| poppler-utils | pdf2image 的二进制后端(pdftoppm) |
路线图
- Phase 3(待定):在 pptx 中嵌入 Web 字体(
ppt/fonts/*.fntdata+embeddedFontLst)。此方案可解决 Mac/Win 上的“缺少字体”问题,但每个 pptx 文件会增加约 3 MB。 - Phase 4(待定):为不受支持的子树(KaTeX 数学公式、复杂 SVG、CSS 变换)提供 PNG 备用方案。