Puppeteer PDF: márgenes, fuentes y saltos de página
Genera PDF con Puppeteer usando ajustes explícitos, CSS de impresión, saltos fiables y una guía para diagnosticar glifos chinos ausentes.
BotBrowser Team
Quieres la documentación estructurada de Primeros pasos?
Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.
Puppeteer puede guardar una página como PDF, pero el resultado sigue las reglas de maquetación para impresión, no las de una captura de pantalla alargada. BotBrowser puede proporcionar perfiles con un conjunto de fuentes declarado para repetir el renderizado del navegador; no garantiza que los archivos instalados contengan todos los glifos CJK solicitados ni decide el CSS de impresión o la disponibilidad de los datos. Define explícitamente el papel y los márgenes, espera la condición de disponibilidad de la página y la carga de las fuentes, y luego revisa los saltos de página y algunos glifos representativos antes de dar el archivo por terminado.
Resumen
page.pdf()usa el medioprint. Declara en@media printy@pageel tamaño de papel, los márgenes y las reglas de salto.- Define
format,marginyprintBackgroundcuando esas decisiones sean importantes; activadisplayHeaderFootersolo si vas a proporcionar plantillas. - Espera la señal de datos listos de la aplicación y
document.fonts.ready. Que las fuentes hayan terminado de cargar no demuestra que la fuente elegida tenga todos los glifos chinos, japoneses o coreanos. - Prefiere
break-before,break-afterybreak-inside; los nombrespage-break-*son alias heredados en CSS de impresión. - Un cuadro vacío donde debería aparecer un glifo requiere revisar las fuentes instaladas y el PDF real, no asumir que Chromium no incrustó la fuente.
Contenido
- Qué procesa
page.pdf() - Definir papel, márgenes y colores impresos
- Controlar la paginación con CSS de impresión
- Diagnosticar glifos CJK y verificar el archivo
- Conclusión práctica
Qué procesa page.pdf()
page.pdf() imprime la página actual usando el tipo de medio CSS print. Por eso una regla dentro de @media print puede modificar el PDF sin cambiar la vista en pantalla. Una captura con page.screenshot() es una imagen rasterizada de una ventana o página; un PDF es una salida paginada con dimensiones de papel, márgenes imprimibles y reglas de fragmentación. Las opciones de captura descritas en buenas prácticas de capturas de pantalla resuelven otro problema: conservar los píxeles de una vista concreta en lugar de distribuir el contenido entre hojas.
Esa diferencia explica muchos informes del tipo «el PDF ignoró mi CSS». Puede que la hoja de estilos solo contenga reglas para pantalla, o que el CSS defina el tamaño en @page mientras la llamada de PDF también pide un format de papel. Puppeteer documenta preferCSSPageSize para dar prioridad al tamaño CSS de página; de lo contrario, las opciones del PDF pueden prevalecer y el contenido puede escalarse para ajustarse. Elige una sola fuente de verdad para las dimensiones del papel, en lugar de cambiar ambas hasta que el resultado parezca correcto.
La API devuelve un búfer PDF correspondiente al estado actual de la página. No espera el contrato asíncrono de datos de la aplicación si quien llama no lo espera antes. networkidle0 puede servir para algunas páginas, pero no significa universalmente que «el informe está completo»: la analítica, las consultas periódicas o las solicitudes de larga duración pueden mantener una conexión abierta después de que el informe esté listo. Un selector o un evento de la aplicación, como data-report-ready="true", ofrece una condición de finalización específica del dominio.
Definir papel, márgenes y colores impresos
Las opciones que más suelen determinar si el archivo es utilizable son pocas y explícitas:
| Opción | Qué controla | Elección práctica |
|---|---|---|
format | Un tamaño de papel con nombre, como A4 o Letter | Defínelo si el contrato de salida nombra un tamaño de papel. |
margin | Márgenes superior, derecho, inferior e izquierdo | Especifica unidades como mm o in; mantén coherencia con @page. |
printBackground | Si se incluyen los gráficos de fondo | Actívalo si los bloques de color o las imágenes de fondo transmiten información. |
displayHeaderFooter | Si se imprimen las plantillas de encabezado y pie | Actívalo junto con plantillas explícitas para números de página o metadatos. |
preferCSSPageSize | Si el tamaño CSS @page tiene prioridad | Úsalo cuando la hoja de estilos sea propietaria de las dimensiones. |
Los valores predeterminados documentados no son una recomendación de diseño: el papel predeterminado es Letter, no se establecen márgenes, la impresión de fondos está desactivada y también lo están los encabezados y pies. Esos valores pueden ser razonables para una impresión básica, pero sorprenden a un flujo de informes que espera A4, gráficos en color o numeración de páginas. Guarda los ajustes elegidos junto a la configuración de la prueba o del servicio para que una actualización no cambie en silencio el significado de «el PDF» para los sistemas posteriores.
import fs from 'node:fs/promises';
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.test/report/42', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready="true"]');
await page.evaluate(async () => {
await document.fonts.ready;
return document.fonts.status;
});
const pdf = await page.pdf({
format: 'A4',
margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' },
printBackground: true,
displayHeaderFooter: true,
headerTemplate: '<span></span>',
footerTemplate: '<span class="pageNumber"></span>/<span class="totalPages"></span>',
});
await fs.writeFile('report.pdf', pdf);
} finally {
await browser.close();
}
Sustituye la URL de ejemplo y el selector de disponibilidad por la ruta y la señal propias de la aplicación. En esta variante, la API controla el tamaño del papel; si la hoja de estilos de producción lo controla, declara @page { size: A4; } y configura preferCSSPageSize: true. No permitas que ambas capas discrepen en silencio. Las plantillas de encabezado y pie son fragmentos HTML pequeños; la hoja de estilos normal de la página no sustituye los estilos de esos fragmentos, así que usa estilos en línea si necesitas controlar su aspecto.
Los márgenes pueden declararse en las opciones de page.pdf() o en @page. No añadas los dos grupos sin pensarlo: el área imprimible efectiva depende de la interacción entre el tamaño del papel, los márgenes y el contenido, y los valores que compiten hacen difícil explicar un recorte. Si el informe usa @page, verifica las dimensiones generadas y configura preferCSSPageSize: true; si el contrato de la aplicación define format y margin en la API, limita la hoja de impresión al contenido y los saltos.
La elección del papel también cambia cómo se comportan las dimensiones CSS existentes. format selecciona un formato estándar; las opciones width y height son la alternativa de bajo nivel si el contrato de salida usa un tamaño personalizado. La documentación de las opciones PDF indica que format prevalece cuando está presente, así que proporcionar las tres opciones no hace que el papel personalizado sea «más explícito». Elige un formato con nombre o un par de ancho y alto, y declara la orientación vertical u horizontal mediante el formato correspondiente o una regla de página CSS. Verifica una vez las dimensiones del archivo generado; no las deduzcas de la ventana del navegador, que es una entrada distinta.
En informes de varias secciones, pageRanges permite reducir un documento grande a una muestra de revisión útil, por ejemplo la portada, una tabla densa y una página con texto CJK largo. Es una comprobación de regresión más rápida que abrir todas las páginas tras cambiar el CSS. Sin embargo, un rango no selecciona contenido: la numeración puede desplazarse cuando una sección anterior gana una fila. Mantén una prueba que compruebe el recuento total de páginas y otra que renderice las páginas de mayor riesgo, para que una muestra correcta no oculte la última sección ausente.
Los fondos también son una decisión deliberada. Si printBackground está desactivado, un gráfico de color puede perder el relleno aunque sus etiquetas sigan presentes. Si los colores exactos son parte del artefacto, prueba el PDF en el mismo flujo de color que usará quien lo lea; CSS puede solicitar un ajuste de color de impresión, pero el visor y la impresora aún pueden aplicar su propio tratamiento. No uses solo una comparación de capturas de pantalla para decidir si un color impreso es correcto.
El encabezado y el pie son documentos pequeños e independientes. Con displayHeaderFooter activado, proporciona headerTemplate y footerTemplate; los marcadores documentados incluyen date, title, url, pageNumber y totalPages. Un pie con número de página puede ser útil en un PDF que los lectores impriman o citen, mientras que un logotipo de ancho completo puede consumir el área imprimible y empujar contenido a una hoja adicional. Mantén pequeñas las plantillas, define dentro del fragmento la fuente y el espaciado, y revisa la primera, la intermedia y la última página: un título largo puede caber en una y recortarse en otra. Para un recibo de una página, una plantilla vacía quizá sea menos confusa que activar encabezados sin contenido visible.
Controlar la paginación con CSS de impresión
La hoja de impresión debe describir dónde pueden producirse los saltos, qué elementos deben permanecer juntos y qué solo debe aparecer en papel. @page configura la geometría de la hoja y @media print limita los estilos al modo de impresión:
@page {
size: A4;
margin: 18mm 14mm;
}
@media print {
.chapter:not(:first-child) {
break-before: page;
}
.result-card,
figure,
table tr {
break-inside: avoid;
}
.screen-only {
display: none;
}
}
Usa break-before: page para iniciar una sección en una hoja nueva. Usa break-inside: avoid en una unidad pequeña, como una tarjeta, figura o fila de tabla, que resulte ilegible si se divide. No es una promesa de que un elemento de cualquier altura quepa en una página: si es más alto que el espacio disponible, el renderizador tendrá que colocar su contenido igualmente. Evita aplicar «mantener junto» a una sección completa y larga del informe, porque puede dejar un espacio grande en blanco en la página anterior o forzar un desbordamiento incómodo.
Las hojas de estilo antiguas pueden contener page-break-before, page-break-after o page-break-inside. Esas propiedades heredadas son alias de compatibilidad para sus equivalentes modernos break-*; las reglas nuevas deben usar los nombres modernos. Una regla como page-break-before: always no sustituye la comprobación del límite real: los márgenes, el contenido anterior al título y la altura del elemento influyen en dónde cae el salto.
Cuando el botón «página siguiente» no funciona en el PDF, revisa el flujo de renderizado en este orden:
- Confirma que la llamada del PDF usa el medio de impresión y que la regla de salto está en una hoja cargada por esa ruta.
- Comprueba si
@pagediscrepa delformatomarginde la API; deja una sola capa como autoridad. - Inspecciona los estilos calculados para impresión y las dimensiones del elemento. Un selector solo para pantalla o una hoja ausente puede volver irrelevante la regla de salto.
- Busca un contenedor muy alto con
break-inside: avoid; mueve la regla a la unidad menor que realmente deba permanecer intacta. - Vuelve a renderizar el rango afectado e inspecciona el título, la tabla y el pie cerca del límite.
En una tabla larga, repetir el encabezado y evitar partir una fila son objetivos distintos. Un thead puede tener estilos de impresión para repetirse como encabezado de tabla, y las filas pueden recibir break-inside: avoid; ninguno de los dos ajustes garantiza que toda la tabla quepa en una sola hoja. En informes controlados, incluye un caso de varias páginas con una fila corta, otra alta y un título de sección para que un cambio de hoja de estilos no pase la prueba con un ejemplo de una sola página.
La fragmentación de páginas también puede revelar un diseño que la pantalla nunca ejercita. Una fila de flex o grid que cabe en una ventana ancha puede crecer cuando se imprime al ancho del papel; un panel de altura fija puede recortar texto después de que la fuente de impresión lo ajuste; y un título cerca del final de la página puede quedar separado de su párrafo. Las reglas específicas de impresión pueden mantener un título junto al bloque siguiente mediante break-after: avoid-page, además de quitar controles, navegación o elementos adhesivos que solo sirven en pantalla. Comprueba los estilos calculados con el medio de impresión en la misma versión del navegador que genera el PDF, porque inspeccionar la cascada de pantalla no indica qué regla se aplicó al imprimir.
No uses los saltos para compensar datos que no estaban listos. Si un gráfico o una tabla se completa después de que el documento indique que está listo, el recuento de páginas puede cambiar en la siguiente ejecución y hacer que un salto fijo parezca inestable. La condición de disponibilidad debe representar los datos reales del informe; la hoja de estilos debe representar la paginación deseada. Separar esas responsabilidades permite distinguir una respuesta tardía de una regla break-before incorrecta.
Diagnosticar glifos CJK y verificar el archivo
Un cuadrado en lugar de un carácter chino, japonés o coreano suele indicar que la ruta de fuente seleccionada no proporcionó ese glifo. La causa puede ser una familia de fuentes ausente, una cobertura incompleta, una fuente web que aún no terminó de cargar o una fuente alternativa distinta en el host. No basta con ver document.fonts.status === "loaded": eso describe el conjunto de carga, no demuestra que un archivo concreto contenga todos los caracteres del documento.
Empieza con una muestra pequeña que incluya chino simplificado y tradicional, kana japonés, hangul coreano, puntuación y los símbolos del documento real. Espera la señal explícita de disponibilidad de la página y document.fonts.ready; después inspecciona las fuentes del host y el PDF. En un ejecutor Linux, fc-match "Noto Sans CJK SC" muestra qué fuente instalada asociaría Fontconfig con esa familia; fc-list :lang=zh enumera las fuentes instaladas que anuncian cobertura de chino. Esos comandos inspeccionan el inventario del host, no la fuente exacta elegida para cada glifo, así que sirven para diagnosticar, no para demostrar el contenido final del PDF.
Después renderiza como imagen la página PDF que contiene la muestra e inspecciona el resultado real. pdffonts report.pdf puede mostrar los recursos de fuente a los que se refiere el PDF y si están incrustados; pdftoppm -f 1 -singlefile -png report.pdf /tmp/report-page renderiza una página para inspección visual. Que una fuente aparezca en la lista de recursos no garantiza que sea la fuente alternativa correcta para un carácter: compara el glifo y no solo los metadatos. Para una implementación que necesite texto CJK previsible, fija la versión del navegador y proporciona el mismo conjunto de fuentes a cada ejecutor. La guía de carga de fuentes y estabilidad del diseño explica por qué una fuente tardía puede mover el texto; la generación de PDF añade el medio de impresión y la paginación después de ese mismo problema de selección de fuente.
El inventario de fuentes forma parte del entorno de renderizado. Un perfil de BotBrowser puede declarar una colección de fuentes, pero el host aún debe poner esos archivos a disposición del renderizador y cada archivo debe cubrir el sistema de escritura requerido. Si el mismo informe se ve distinto en un equipo local y en CI, compara el inventario real de fuentes y la versión del navegador antes de cambiar el interlineado o los márgenes. La fijación de versiones y actualización del navegador trata el control de versiones por separado; fijar el binario no instala las fuentes ausentes ni congela las bibliotecas de fuentes del sistema operativo.
La extracción de texto es una comprobación complementaria útil. pdftotext report.pdf - puede mostrar si el resultado contiene los caracteres y el orden de lectura esperados, pero no demuestra que el visor dibuje esos caracteres con las formas previstas. Un PDF puede contener Unicode extraíble y mostrar un cuadro alternativo, o verse bien y tener un orden de extracción incómodo para las herramientas de asistencia. Trata la inspección visual, la extracción de texto, el recuento de páginas y la inspección de recursos de fuentes como señales separadas: cada una responde una pregunta distinta sobre el archivo.
Cuando un archivo difiere solo en los saltos de línea o el recuento de páginas, compara primero con un ejemplo mínimo de dos páginas antes de cambiar los márgenes. Corrige una variable a la vez: primero fija el tamaño del papel, luego los archivos de fuentes y la condición de disponibilidad, y por último la fragmentación. Si un solo párrafo largo cruza un límite, a menudo es aceptable; si un título se separa de su primer párrafo o se corta una fila de tabla, aplica CSS de impresión a esa unidad. Un break-inside: avoid general para todos los contenedores no es una reparación segura, porque ningún contenedor más alto que una hoja puede permanecer entero.
Preguntas frecuentes
¿Por qué @media print cambia el PDF pero no la captura de pantalla?
page.pdf() renderiza con el medio de impresión por defecto. Una captura suele usar el medio de pantalla salvo que la prueba lo cambie explícitamente. Mantén comprobaciones de pantalla e impresión separadas cuando ambos resultados sean requisitos del producto.
¿Por qué faltan los colores de fondo?
La opción printBackground de la API tiene el valor predeterminado false. Actívala cuando el fondo aporte información y luego inspecciona el PDF, porque los visores y las impresoras pueden seguir aplicando gestión de color.
¿Por qué un carácter chino aparece como un cuadro solo en CI?
Puede que el ejecutor no tenga la misma familia o cobertura de glifos que el equipo de desarrollo, o que la fuente web aún no haya terminado de cargar antes de la captura. Compara el inventario de fuentes del ejecutor, espera al conjunto de fuentes del documento y renderiza la página PDF producida; no deduzcas la causa solo de la versión del navegador.
¿Conviene usar page-break-before o break-before?
Usa break-before en CSS nuevo. page-break-before sigue siendo un alias heredado, pero cambiar el nombre de la propiedad no resuelve un tamaño de papel en conflicto, una hoja de impresión ausente ni un elemento contenedor demasiado grande.
Fuentes
- Puppeteer:
Page.pdf() - Puppeteer:
PDFOptions - Puppeteer: generación de PDF
- MDN: impresión
- MDN:
page-break-before - MDN:
Document.fonts - BotBrowser: fuentes y configuración
Conclusión práctica
Define una sola política de tamaño de papel y márgenes, espera la condición de disponibilidad de la aplicación y las fuentes, y verifica el PDF final en lugar de inspeccionar solo el DOM. Cuando falle la paginación, revisa la cascada de impresión, la geometría de la hoja y el tamaño del elemento; cuando los glifos CJK sean cuadros, inspecciona la cobertura de las fuentes del host y el archivo renderizado antes de cambiar el diseño. Conserva junto al artefacto la versión del navegador y el entorno de fuentes para poder atribuir un cambio posterior al renderizador y no confundirlo con una edición del contenido.
Este artículo forma parte de la guía: Primeros pasos con automatización de navegador: mapa inicial
Artículos Relacionados
Cómo el renderizado de fuentes CJK revela tu sistema real en la huella
Huella digital¿Qué es la huella de fuentes? Coherencia de texto entre plataformas
Primeros pasosScraping con Selenium: capacidades y registros
DespliegueCapturas fiables en navegadores headless
Primeros pasosPuppeteer + BotBrowser: errores de lanzamiento y de perfil
Primeros pasosPruebas limpias de carga y descarga en la automatización del navegador
Ejecuta la misma comprobación en BotBrowser
Carga un perfil, mantén dentro del navegador las señales que acabas de leer y confirma el resultado en tu propia máquina antes del despliegue.