# html2patch — design specification **Status**: v1 spec, written before implementation (clean-room design record). ## What it is `html2patch.py` compiles an HTML file into a **deck.py patch** (`{"ops": [...]}`). The agent writes a slide as HTML/CSS — the layout language LLMs are best at — and a headless browser (Playwright/Chromium) is used purely as a *measuring engine*: every rendered element's box and computed style is read back and translated into `add-shape` / `add-picture` / `add-table` / `set-text` ops. The patch is then applied by deck.py like any other patch: validated atomically, linted, fixable, renderable. It deliberately does NOT write a .pptx. One writer (deck.py / python-pptx) owns all output, so created slides get the same shape ids, lint coverage, `fix` loop, and `diff`/`render` verification as edited ones — and creation can target slides in an **existing branded deck** (`add-slide` with a template layout), not just blank files. ## Provenance The general idea — render HTML in a browser and read back element geometry to position PowerPoint shapes — is a known public technique (used by Anthropic's proprietary html2pptx, which emits a .pptx via PptxGenJS). We studied that implementation for *behavioral learnings* (see below) but share no code with it, and the architecture differs: Python not Node, patch-JSON output not a generated file, deck.py as the single writer, template-aware creation, native gradient and table support. Learnings adopted (rediscovered behaviors, reimplemented): - PowerPoint renders text slightly wider than the browser measures — single-line text boxes get ~2% extra width, distributed by alignment (center: both sides, right: leftward, left: rightward). - `textContent` does not reflect CSS `text-transform`; apply uppercase/lowercase/ capitalize manually during extraction. - Bold on single-weight fonts (e.g. Impact) triggers PowerPoint faux-bold, which widens text — suppress bold for those families. - When an inline run's font is larger than the block font, absolute line spacing must be rescaled by the largest run size (browser line boxes grow; Pt spacing doesn't). - PPTX rotation spins the *pre-rotation* box about its center: for ±90° swap w/h around the center; arbitrary angles come from computed `matrix(a,b,...)` via atan2(b, a); `writing-mode: vertical-rl` ≡ 90°, `vertical-lr` ≡ 270°. - Non-uniform borders (accent bars) are emitted as separate line shapes inset by half the border width so the stroke centers on the box edge. - `text-align: start` must be normalized to left. - Overflow check: body scrollWidth/Height vs CSS size with 1px tolerance. ## Coordinate model - The HTML `` is the slide. Its CSS size must be the slide size at **96 px/in**: 16:9 → `width:1280px; height:720px` (13.333 × 7.5 in). - All geometry comes from `getBoundingClientRect()` in CSS pixels; px / 96 = inches. - Conversion constants: 96 px = 1 in = 72 pt → font px × 0.75 = pt. - `--deck deck.pptx` reads the true slide size (and slide count) from a real deck; mismatch between body size and slide size is a hard error listing both. ## Element classification (document order = z-order, back to front) Walk the DOM in document order. Each element matches the FIRST rule below; matched elements are emitted and (except BOX) their subtrees are not descended further. 1. **IMG** — `` → `add-picture` at its border-box rect with exact `size`. `src` must resolve to a local file (file:// or relative to the HTML file); `data:` URIs are materialized to temp files. Remote http(s) sources are an error (agents should download first — keeps the tool offline and deterministic). 2. **TABLE** — `` → `add-table` at its rect with `rows` of plain cell text, followed by `set-text` ops (`"cell":[r,c]`) for cells whose resolved style differs from the table default. Column widths/row heights: v1 uses PowerPoint's even split; cell geometry fidelity is out of scope for v1. 3. **TEXT BLOCK** — `

`, `

`–`

` → one `add-shape kind=textbox` per block. A `