Recetas de generacion desde HTML
Recetas de generacion desde HTML
11 reglasGenera PDFs con headless Chrome y las banderas correctas
Headless Chrome renderiza tu HTML con el mismo motor del navegador, asi que lo que previsualizas es lo que obtienes. Usa --headless=new con --print-to-pdf, agrega --no-pdf-header-footer para matar la franja de fecha y URL por default, y --virtual-time-budget para dar tiempo a que fuentes, imagenes y layout se asienten antes de la captura. Apuntalo a una URL file:// o a un servidor local; la salida respeta tu CSS de @page y @media print.
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
"$CHROME" --headless=new \
--print-to-pdf=out.pdf \
--no-pdf-header-footer \
--virtual-time-budget=10000 \
--run-all-compositor-stages-before-draw \
file:///abs/path/doc.html
Suma Paged.js para desbloquear Paged Media real
Chrome ignora la mayor parte de la spec CSS Paged Media: sin headers corrientes, sin counter(pages), sin cajas de margen de @page, sin bleed/marks. Paged.js es un polyfill que trocea tu flujo en paginas reales en el DOM e implementa esas features, asi que titulos, folios y marcas de corte funcionan. Carga el script en el navegador para previsualizar, o corre el CLI para renderizar directo a PDF.
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
# CLI: entra HTML, sale PDF paginado (usa Puppeteer/Chromium)
npx pagedjs-cli doc.html -o out.pdf
npx pagedjs-cli doc.html --output out.pdf --page-size Letter
Usa WeasyPrint para reportes server-side
WeasyPrint es una libreria de Python que convierte HTML y CSS en PDF con buen soporte nativo de Paged Media: headers corrientes, counter(page), cajas de margen, notas al pie y guionado, sin navegador ni Chromium. No ejecuta JavaScript, asi que es ideal para documentos deterministas basados en plantilla (facturas, estados de cuenta, reportes en lote) generados en un servidor. Dale una URL, un archivo, o un string HTML mas una hoja de estilos.
weasyprint doc.html out.pdf
weasyprint https://example.com/report out.pdf
# Python
from weasyprint import HTML
HTML("doc.html").write_pdf("out.pdf")
Evita wkhtmltopdf para CSS moderno
wkhtmltopdf renderiza con un WebKit congelado de hace anos (Qt WebKit alrededor de 2012) y su proyecto esta archivado y sin mantenimiento. Flexbox, CSS grid, propiedades break-* modernas, fuentes variables y funciones de color actuales salen mal o no salen. Usalo solo para pipelines legados simples que no puedas cambiar; para algo nuevo, usa headless Chrome, Paged.js o WeasyPrint. Si no hay remedio, fuerza estilos de impresion con --print-media-type.
wkhtmltopdf --print-media-type --enable-local-file-access \
doc.html out.pdf
# Estado: proyecto archivado, WebKit congelado ≈ 2012
Usa Prince o DocRaptor cuando Paged Media deba ser perfecto
Prince (y DocRaptor, su API hospedada) es el motor comercial con el soporte mas profundo de CSS Paged Media: notas al pie, referencias cruzadas con target-counter(), salida PDF/X y PDF/A, y control preciso que las herramientas abiertas no tienen. Es de pago (gratis para uso no comercial, con un logo estampado en salidas sin licencia). Elige por necesidad: la tabla de abajo mapea cada motor a su profundidad de Paged Media, licencia y mejor uso.
prince doc.html -o out.pdf
prince doc.html --pdf-profile="PDF/X-4" -o print.pdf
| Motor | Paged Media | Licencia | Mejor para |
|---|---|---|---|
| Headless Chrome | Parcial (necesita Paged.js) | Gratis | JS pesado, fidelidad exacta de navegador |
| Paged.js | Bueno (polyfill) | Gratis / MIT | Libros, headers, marcas |
| WeasyPrint | Bueno (nativo, sin JS) | Gratis / BSD | Reportes server, plantillas |
| wkhtmltopdf | Debil (WebKit viejo) | Gratis / archivado | Legado simple |
| Prince / DocRaptor | El mejor (spec completa) | Comercial | Notas al pie, PDF/X, cross-refs |
Haz el documento autocontenido con data URI
Un pipeline de PDF se rompe cuando una imagen o fuente da 404 a media renderizada o carga muy lento. Inserta los assets criticos como URIs data: (base64) para que el HTML lleve todo lo que necesita y nada dependa de la red. Embebe fuentes igual dentro de @font-face, y declara siempre fallbacks de sistema en el stack por si una tipografia falla al decodificar. Base64 suma cerca de 33% de peso, asi que reservalo para los assets que deben estar presentes al capturar.
<img src="data:image/png;base64,iVBORw0KGgo...">
/* Fuente inline con fallback de sistema */
@font-face { font-family:'Inter';
src:url(data:font/woff2;base64,d09GMgAB...) format('woff2'); }
body { font-family:'Inter', -apple-system, Georgia, serif; }
# Codifica un archivo a base64 (macOS/Linux)
base64 -i logo.png | pbcopy
base64
Separa los estilos de impresion con @media print
Envuelve todo lo que solo pertenece al PDF dentro de @media print: dimensionado fisico de @page, reglas de salto de pagina, ajuste exacto de color, y ocultar el chrome de pantalla como navs, botones y banners de cookies con display:none. Deja el layout de pantalla intacto para que el mismo HTML previsualice normal en el navegador e imprima limpio. Aqui cambias de pensar en viewport (px, scroll) a pensar en hoja (mm, paginas).
@page { size: Letter; margin: 18mm 16mm; }
nav, .toolbar, .cookie-banner { display: none !important; }
a { color: inherit; text-decoration: none; }
h2, h3 { break-after: avoid; }
figure, tr { break-inside: avoid; }
}
Espera fuentes e imagenes antes de capturar
El PDF roto mas comun es una captura disparada antes de que las web fonts terminaran de cargar, asi que el texto cae a Times y el layout se corre. Condiciona la impresion a document.fonts.ready (y a promesas decode() de imagenes) cuando manejas con Puppeteer/Playwright, o dale al CLI un --virtual-time-budget generoso para que Chrome adelante timers hasta que la pagina se asiente. Nunca dependas de un sleep fijo a ciegas; espera la senal real de readiness.
await page.goto(url, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path:'out.pdf', printBackground:true, format:'Letter' });
# Equivalente CLI: da tiempo a que corran los timers
--virtual-time-budget=10000
Postprocesa con Ghostscript para CMYK y PDF/X
Los navegadores solo emiten PDFs RGB, asi que un flujo HTML a PDF que va a prensa necesita un segundo paso. Ghostscript convierte a DeviceCMYK, estampa un perfil PDF/X con Output Intent, hace downsample de imagenes y mergea archivos, todo desde la linea de comandos. Usa -dPDFX con un perfil ICC de salida y una definicion PDFX_def.ps para PDF/X-1a o X-3 real; para un convert CMYK simple usa -sColorConversionStrategy=CMYK -dProcessColorModel=/DeviceCMYK.
gs -o print.pdf -sDEVICE=pdfwrite \
-sColorConversionStrategy=CMYK \
-dProcessColorModel=/DeviceCMYK \
-dOverrideICC -sOutputICCProfile=FOGRA39.icc web.pdf
# PDF/X-3 con Output Intent
gs -dPDFX -dBATCH -dNOPAUSE -sDEVICE=pdfwrite \
-sColorConversionStrategy=CMYK -dProcessColorModel=/DeviceCMYK \
-o printx.pdf PDFX_def.ps web.pdf
Verifica leyendo las paginas reales del PDF
Un PDF solo revela sus fallas al paginarse, nunca en el scroll continuo del preview del navegador. Despues de cada export, abre el archivo y leelo pagina por pagina: el fondo llega a borde sin filos blancos, los saltos son limpios sin titulos huerfanos ni figuras partidas, se embebieron las fuentes buscadas, los margenes son parejos. Convierte paginas a imagenes para revisarlas rapido, y verifica fuentes embebidas en la linea de comandos.
pdffonts out.pdf # la columna 'emb' debe decir 'yes'
# Renderiza paginas a PNG para revisar saltos y sangrado
pdftoppm -png -r 150 out.pdf page
# Numero de paginas y tamano
pdfinfo out.pdf
Receta dark full-bleed: margen cero mas color exacto
Un PDF dark o de color a borde necesita tres cosas juntas o falla. Pon @page { margin: 0 } para que Chrome no deje margenes de papel blanco alrededor de tu body oscuro. Agrega print-color-adjust: exact para que el motor conserve fondos e imagenes en vez de descartarlos. Y coloca el fondo en el elemento que llena la hoja (html, body, .doc), empujando la zona segura del texto como padding de un contenedor interno. Ve la seccion fondos-dark para el tratamiento completo.
html {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
html, body, .doc { background: #14181F; color: #F4F6FA; }
.doc { padding: 15mm 16mm; } /* zona segura del texto */
.cover { min-height: 245mm; break-after: page; } /* Carta, pad 15mm */
- R-310 Genera PDFs con headless Chrome y las banderas correctas
- R-311 Suma Paged.js para desbloquear Paged Media real
- R-312 Usa WeasyPrint para reportes server-side
- R-313 Evita wkhtmltopdf para CSS moderno
- R-314 Usa Prince o DocRaptor cuando Paged Media deba ser perfecto
- R-315 Haz el documento autocontenido con data URI
- R-316 Separa los estilos de impresion con @media print
- R-317 Espera fuentes e imagenes antes de capturar
- R-318 Postprocesa con Ghostscript para CMYK y PDF/X
- R-319 Verifica leyendo las paginas reales del PDF
- R-320 Receta dark full-bleed: margen cero mas color exacto