| name | html-slide-to-png |
| description | Render a fixed-size HTML slide/diagram to a crisp, full-frame PNG for PowerPoint or docs. Use when asked to "generate a PNG from an HTML slide", "make a slide image", "export architecture-slide.html to PNG", or when a browser-tool screenshot comes out clipped/blank. Uses Edge/Chrome CLASSIC headless — NOT the VS Code Playwright browser tool (its headed viewport is stuck tiny and clips everything). |
HTML slide → PNG (deterministic)
Convert an authored fixed-size HTML slide (e.g. 1280×720, 16:9) into a
clean, full-frame, high-DPI PNG suitable for a full-screen PowerPoint slide.
When to use
- "Generate / export a PNG from
*.html" (architecture slide, diagram).
- A browser screenshot is clipped on the right/bottom or mostly blank.
- You need a specific pixel size and 16:9 aspect ratio.
The one rule that matters
Do NOT use the VS Code Playwright browser tools (open_browser_page,
run_playwright_code, screenshot_page) to capture a fixed-size slide.
That browser is headed with viewport:null; its OS window is stuck at a
tiny size (measured innerWidth:353, innerHeight:50) and
page.setViewportSize() is a silent no-op. Every capture clips the
slide. zoom hacks and element screenshots make it worse (ghost zoom,
overflow:hidden hides real overflow so scrollWidth lies).
Use Edge or Chrome CLASSIC headless with --screenshot instead. It
renders off-screen at exactly the window size you specify.
Working command (Edge, Windows)
Match --window-size to the slide's authored CSS px. Use
--force-device-scale-factor for resolution (2 → 1280×720 becomes
2560×1440, ideal full-screen PowerPoint).
$edge = 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$html = 'C:\path\to\slide.html'
$out = 'C:\path\to\slide.png'
$tmp = "$env:TEMP\edge-shot-profile" # throwaway profile
$uri = 'file:///' + ($html -replace '\\','/')
Remove-Item $out -ErrorAction SilentlyContinue
$p = Start-Process -FilePath $edge -Wait -PassThru -WindowStyle Hidden -ArgumentList @(
'--headless', # CLASSIC headless. NOT --headless=new (it emits no file here)
'--disable-gpu',
'--hide-scrollbars',
'--no-first-run',
'--force-device-scale-factor=2',
'--window-size=1280,720', # = authored slide CSS px
"--user-data-dir=$tmp",
"--screenshot=$out",
$uri
)
Write-Host "exit=$($p.ExitCode)"
Always verify the output (don't trust exit code alone)
Add-Type -AssemblyName System.Drawing
$img = [System.Drawing.Image]::FromFile($out)
Write-Host ("{0} x {1} ratio {2:N3} {3:N0} KB" -f `
$img.Width, $img.Height, ($img.Width/$img.Height), ((Get-Item $out).Length/1KB))
$img.Dispose()
Expect 2560 x 1440 ratio 1.778 for a 1280×720 slide at DSF 2. Then
view_image the file to confirm no clipping (check the far-right and
bottom-most elements are fully present).
Slide HTML hygiene (prevents blank/stalled renders)
- Remove external font
@import (e.g. fonts.googleapis.com). It
errors offline and can stall headless load. Rely on system fonts
('Segoe UI', system-ui, sans-serif) — they render identically.
- Give
body/.slide explicit width/height in px and
overflow: hidden. The window size must match those px.
- Keep a
<meta name="viewport" content="width=1280, height=720">.
Pitfalls (all observed, 2026-07-15)
| Symptom | Cause | Fix |
|---|
| Right/bottom of slide clipped | Playwright headed viewport stuck 353×50; setViewportSize no-op | Use Edge classic headless CLI |
| Mostly blank PNG, giant text top-left | Leftover document.documentElement.style.zoom on the page | New process each time; don't zoom |
scrollWidth == clientWidth but content clipped | overflow:hidden caps scrollWidth — it lies about overflow | Measure getBoundingClientRect().right per element, or just use headless CLI |
--headless=new exits 0 but no file | new headless mode didn't emit --screenshot here | Use classic --headless |
Console error re: fonts.googleapis.com MIME | External @import blocked | Delete the @import; use system fonts |
Chrome instead of Edge
Same flags; point $edge at
C:\Program Files\Google\Chrome\Application\chrome.exe.