claude code skill
pdf — HTML to continuous single-page PDF
A Claude Code skill that converts an HTML file into one continuous, single-page PDF sized exactly to its rendered content. Two methods, chosen by page complexity.
Choose by page complexity:
- Headless Chrome (DEFAULT — pixel-faithful). Renders exactly like the browser: web fonts, CSS variables, grid/flex, gradients, letter-spacing. Use for anything designed/branded.
- weasyprint (simple documents only). Fine for plain text/tables. Known to
RUIN complex pages: crashes on CSS
var()inside gradients, collapses calc()-based grids, no web fonts → wrong typography. (Learned 2026-07-02: branded datasheets came out mangled.)
1Headless Chrome (preferred)
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
SCRATCH=<scratchpad dir> # work on copies, never the original
# 1. Measure true rendered height: inject a reporter into a COPY, read it via dump-dom
python3 - <<'EOF'
src = open("$FILE").read()
src = src.replace("</body>", "<script>window.addEventListener('load',()=>{setTimeout(()=>{document.title='H:'+document.documentElement.scrollHeight},1500)})</script></body>")
open("$SCRATCH/measure.html","w").write(src)
EOF
H=$("$CHROME" --headless=new --disable-gpu --dump-dom --virtual-time-budget=12000 \
--window-size=<design_width+~120>,2000 "file://$SCRATCH/measure.html" 2>/dev/null \
| grep -o '<title>H:[0-9]*' | grep -o '[0-9]*$')
# 2. Into a print COPY, inject @page sized to content (width = the design's fixed width, else 800px):
# @page { size: <width>px <H+24>px; margin: 0; }
# @media print { body { -webkit-print-color-adjust: exact; } }
# 3. Print
"$CHROME" --headless=new --disable-gpu --no-pdf-header-footer --virtual-time-budget=15000 \
--print-to-pdf="<output.pdf>" "file://$SCRATCH/print.html"
Notes:
--virtual-time-budgetlets web fonts/images finish loading before printing.- Chrome converts CSS px→pt at 0.75 (1440px page reads as 1080pt in the PDF). Correct.
- Verify visually:
sips -s format jpeg --resampleWidth 700 out.pdf --out check.jpg, then look at it.
2weasyprint (simple pages)
- Inject
@page { size: 800px 200px; margin: 0; }, render with Pythonweasyprint, count pages → height ≈ pages × 200. - Replace the @page height with the measured total; run
weasyprint in.html out.pdf. - If the page uses CSS custom properties, flatten every
var(--x)to its literal first (weasyprint crashes on var() in gradients). If it uses calc()-based grids or web fonts, STOP and use Method 1 instead.
weasyprint warnings about box-shadow / print-color-adjust are harmless.
Shared rules
Never edit the source HTML in place — copy to scratchpad, inject, convert.
- No
min-height:100vh(100vh = whole document height in single-page PDFs); nopage-break-*rules. - Always end by rendering the PDF to an image for a visual check before delivering.