Puppeteer PDF : marges, polices et sauts de page
Générez des PDF Puppeteer avec des réglages explicites, du CSS d’impression, une pagination fiable et un diagnostic des caractères chinois manquants.
BotBrowser Team
Vous voulez la documentation structurée pour Démarrage ?
Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.
Puppeteer peut enregistrer une page au format PDF, mais le résultat suit les règles de mise en page pour l’impression et non celles d’une longue capture d’écran. BotBrowser peut fournir des profils avec un jeu de polices déclaré pour reproduire le rendu du navigateur ; il ne garantit pas que les fichiers installés couvrent chaque glyphe CJK demandé et ne choisit ni la feuille de style d’impression ni le moment où les données sont prêtes. Définissez explicitement le papier et les marges, attendez le signal de disponibilité propre à la page ainsi que le chargement des polices, puis vérifiez les sauts de page et quelques glyphes représentatifs avant de considérer le fichier comme complet.
Résumé
page.pdf()utilise le médiaprint. Définissez le format papier, les marges et les règles de pagination dans@media printet@page.- Fixez
format,marginetprintBackgroundlorsque ces choix comptent ; activezdisplayHeaderFooteruniquement si vous fournissez des modèles. - Attendez le signal indiquant que les données de l’application sont prêtes et
document.fonts.ready. La fin du chargement des polices ne prouve pas que la police retenue contient tous les glyphes chinois, japonais ou coréens. - Préférez
break-before,break-afteretbreak-inside; les nomspage-break-*restent des alias historiques en CSS d’impression. - Un carré à la place d’un glyphe impose de vérifier les polices disponibles et le PDF produit, plutôt que de supposer que Chromium n’a pas incorporé une police.
Sommaire
- Ce que rend
page.pdf() - Définir le papier, les marges et les couleurs imprimées
- Maîtriser la pagination avec le CSS d’impression
- Diagnostiquer les glyphes CJK et vérifier le fichier
- Conclusion pratique
Ce que rend page.pdf()
page.pdf() imprime la page actuelle avec le type de média CSS print. Une règle dans @media print peut donc modifier le PDF sans changer la vue à l’écran. Une capture avec page.screenshot() est une image matricielle d’une fenêtre ou d’une page ; un PDF est une sortie paginée avec des dimensions de papier, des marges imprimables et des règles de fragmentation. Les options décrites dans les bonnes pratiques de capture d’écran répondent à un autre besoin : conserver les pixels d’une vue donnée plutôt que répartir le contenu sur des feuilles.
Cette distinction explique bien des signalements du type « le PDF a ignoré mon CSS ». La feuille peut ne contenir que des règles d’écran, ou le CSS peut définir les dimensions dans @page alors que l’appel PDF spécifie aussi un format papier format. Puppeteer documente preferCSSPageSize pour donner la priorité au format de page CSS ; sinon, les options PDF peuvent l’emporter et le contenu peut être mis à l’échelle. Choisissez une seule source de vérité pour le papier au lieu de modifier les deux jusqu’à obtenir un résultat qui semble correct.
L’API renvoie un tampon PDF correspondant à l’état courant de la page. Elle n’attend pas le contrat asynchrone de données de l’application si l’appelant ne l’a pas fait au préalable. networkidle0 convient à certaines pages, mais ne signifie pas universellement « le rapport est terminé » : l’analytique, les interrogations périodiques ou les requêtes longues peuvent garder une connexion ouverte une fois le rapport prêt. Un sélecteur ou un événement applicatif comme data-report-ready="true" fournit une condition de fin propre au domaine.
Définir le papier, les marges et les couleurs imprimées
Les options qui déterminent le plus souvent si le fichier est utilisable sont peu nombreuses et explicites :
| Option | Ce qu’elle contrôle | Choix pratique |
|---|---|---|
format | Un format papier nommé, comme A4 ou Letter | Fixez-le si le contrat de sortie nomme un format. |
margin | Les marges haute, droite, basse et gauche | Indiquez des unités comme mm ou in et restez cohérent avec @page. |
printBackground | L’inclusion des éléments graphiques d’arrière-plan | Activez-le si les couleurs ou images de fond ont un sens. |
displayHeaderFooter | L’impression des modèles d’en-tête et de pied | Activez-le avec des modèles explicites pour les numéros de page ou les métadonnées. |
preferCSSPageSize | La priorité donnée au format CSS @page | Utilisez-le quand la feuille de style est responsable des dimensions. |
Les valeurs par défaut documentées ne sont pas des recommandations de conception : le papier vaut Letter, aucune marge n’est définie, l’impression des fonds est désactivée, tout comme les en-têtes et les pieds de page. Ces valeurs conviennent à une impression élémentaire, mais peuvent surprendre un pipeline de rapport qui suppose A4, des graphiques en couleur ou une pagination. Enregistrez les choix avec la configuration du test ou du service afin qu’une mise à niveau ne change pas discrètement le sens de « le PDF » pour les systèmes en aval.
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();
}
Remplacez l’URL et le sélecteur d’exemple par la route et le signal propres à l’application. Ici, l’API décide du format papier ; si la feuille de style de production en est responsable, déclarez @page { size: A4; } et définissez preferCSSPageSize: true. Ne laissez pas les deux couches diverger en silence. Les modèles d’en-tête et de pied sont de petits fragments HTML ; la feuille de style normale de la page ne s’y applique pas comme substitut. Utilisez des styles en ligne si leur apparence doit être contrôlée.
Les marges peuvent être déclarées dans les options page.pdf() ou dans @page. N’ajoutez pas les deux ensembles sans réflexion : la zone imprimable dépend des dimensions du papier, des marges et du contenu, et des valeurs concurrentes rendent un rognage difficile à expliquer. Si le rapport utilise @page, vérifiez les dimensions produites et définissez preferCSSPageSize: true ; si le contrat de l’application fixe format et margin dans l’API, limitez la feuille d’impression au contenu et aux sauts.
Le format papier agit également sur les dimensions CSS existantes. format sélectionne un format standard ; width et height sont les options plus bas niveau lorsque le contrat de sortie prévoit des dimensions personnalisées. La documentation des options PDF indique que format prévaut lorsqu’il est présent : fournir les trois options ne rend donc pas le papier personnalisé « plus explicite ». Choisissez un format nommé ou une paire largeur-hauteur, puis indiquez portrait ou paysage avec le format ou une règle CSS de page correspondante. Vérifiez les dimensions du fichier une fois ; ne les déduisez pas de la fenêtre du navigateur, qui est une entrée différente.
Pour un rapport composé de plusieurs parties, pageRanges permet de réduire un long document à un échantillon de vérification utile, par exemple la couverture, un tableau dense et une page de texte CJK long. C’est plus rapide que d’ouvrir toutes les pages après un changement CSS. Un intervalle ne sélectionne cependant pas du contenu : la pagination peut changer si une section précédente gagne une ligne. Gardez un test du nombre total de pages et un rendu ciblé des pages à risque, afin qu’un échantillon correct ne masque pas une dernière section manquante.
Les arrière-plans sont eux aussi un choix volontaire. Avec printBackground désactivé, un graphique coloré peut perdre son remplissage tout en conservant ses légendes. Si les couleurs exactes font partie du livrable, testez le PDF dans le même circuit colorimétrique que celui des lecteurs ; CSS peut demander un ajustement des couleurs d’impression, mais le lecteur PDF et l’imprimante peuvent encore appliquer leur propre traitement. Ne vous fiez pas uniquement à une comparaison de captures d’écran pour décider si une couleur imprimée est correcte.
L’en-tête et le pied de page forment un petit document à part. Lorsque displayHeaderFooter est activé, fournissez headerTemplate et footerTemplate ; les marqueurs documentés comprennent date, title, url, pageNumber et totalPages. Un numéro de page peut aider dans un PDF imprimé ou cité, tandis qu’un logo pleine largeur peut consommer la zone imprimable et pousser le contenu sur une feuille supplémentaire. Gardez les modèles compacts, définissez leur police et leur espacement dans le fragment, puis contrôlez la première, une page intermédiaire et la dernière : un long titre peut tenir sur l’une et être rogné sur une autre. Pour un reçu d’une page, un modèle vide peut être moins déroutant que des en-têtes activés sans contenu visible.
Maîtriser la pagination avec le CSS d’impression
La feuille d’impression doit préciser où les pages peuvent se couper, ce qui doit rester groupé et ce qui ne doit apparaître que sur papier. @page configure la géométrie de la feuille, tandis que @media print réserve les styles au mode impression :
@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;
}
}
Utilisez break-before: page pour commencer une section sur une nouvelle feuille. Appliquez break-inside: avoid à une unité compacte, comme une carte, une figure ou une ligne de tableau, qui serait illisible si elle était coupée. Cette règle ne garantit pas qu’un élément de hauteur arbitraire tiendra sur une page : s’il dépasse l’espace disponible, le moteur doit tout de même placer son contenu. Évitez de conserver groupée une section complète et longue du rapport : cela peut laisser un grand blanc sur la page précédente ou produire un débordement gênant.
Les anciennes feuilles de style peuvent contenir page-break-before, page-break-after ou page-break-inside. Ces propriétés historiques sont des alias de compatibilité pour les propriétés modernes break-* correspondantes ; les nouvelles règles doivent utiliser les noms modernes. Une règle telle que page-break-before: always ne remplace pas la vérification de la vraie limite : les marges, le contenu précédent et la hauteur de l’élément déterminent aussi où le saut se produit.
Si le bouton « page suivante » ne produit pas de saut dans le PDF, vérifiez le chemin de rendu dans cet ordre :
- Confirmez que l’appel PDF utilise le média d’impression et que la règle de saut se trouve dans une feuille chargée par cette route.
- Vérifiez si
@pagecontreditformatoumargindans l’API ; ne laissez qu’une seule couche faire autorité. - Inspectez les styles calculés pour l’impression et les dimensions de l’élément. Un sélecteur réservé à l’écran ou l’absence de feuille d’impression peut rendre la règle inopérante.
- Cherchez un parent très haut doté de
break-inside: avoid; déplacez la règle vers l’unité plus petite qui doit réellement rester intacte. - Produisez de nouveau les pages concernées et examinez le titre, le tableau et le pied de page près de la coupure.
Pour un long tableau, répéter son en-tête et éviter de couper une ligne sont deux objectifs distincts. thead peut être déclaré comme groupe d’en-tête de tableau en impression, et les lignes peuvent recevoir break-inside: avoid ; aucun de ces réglages ne garantit que tout le tableau tiendra sur une seule feuille. Dans les rapports contrôlés, ajoutez un cas sur plusieurs pages avec une ligne courte, une ligne haute et un titre de section, afin qu’une modification de la feuille de style ne passe pas les tests avec un simple exemple d’une page.
La fragmentation révèle aussi des problèmes invisibles à l’écran. Une ligne flex ou grid adaptée à une fenêtre large peut grandir une fois imprimée à la largeur du papier ; un panneau à hauteur fixe peut couper du texte après son retour à la ligne avec la police d’impression ; un titre près du bas de page peut se retrouver seul au-dessus de son paragraphe. Les règles d’impression peuvent garder un titre avec le bloc suivant en utilisant break-after: avoid-page, et supprimer les commandes, la navigation ou les éléments fixes qui ne servent qu’à l’écran. Examinez les styles calculés avec le média d’impression dans la même version du navigateur que celle qui produit le PDF : la cascade écran ne révèle pas quelle règle a gagné à l’impression.
N’utilisez pas les sauts de page pour compenser des données pas encore prêtes. Si un graphique ou un tableau est rempli après le signal de disponibilité du document, le nombre de pages peut changer à l’exécution suivante et faire paraître un saut fixe instable. La condition de disponibilité doit représenter les données réelles du rapport ; la feuille de style doit représenter la pagination voulue. En séparant ces responsabilités, on peut distinguer une réponse tardive d’une mauvaise règle break-before.
Diagnostiquer les glyphes CJK et vérifier le fichier
Un carré à la place d’un caractère chinois, japonais ou coréen indique généralement que le chemin de police sélectionné n’a pas fourni ce glyphe. La cause peut être une famille absente, une couverture de glyphes incomplète, une police web qui n’a pas fini de se charger ou un remplacement différent sur l’hôte. Voir document.fonts.status === "loaded" ne suffit pas : cet état concerne le jeu de chargement, pas la présence de tous les caractères du document dans un fichier de police précis.
Commencez par un petit échantillon comprenant du chinois simplifié et traditionnel, des kana japonais, du hangul coréen, de la ponctuation et les symboles du vrai document. Attendez le signal explicite de disponibilité de la page et document.fonts.ready, puis inspectez l’inventaire de l’hôte et le PDF. Sur un exécuteur Linux, fc-match "Noto Sans CJK SC" indique la police installée que Fontconfig associerait à cette famille ; fc-list :lang=zh liste les polices déclarant la couverture du chinois. Ces commandes inspectent l’inventaire de l’hôte, pas la police exacte choisie pour chaque glyphe : elles servent au diagnostic, pas de preuve du contenu du PDF.
Rendez ensuite en image la page PDF contenant l’échantillon et inspectez la sortie réelle. pdffonts report.pdf peut montrer les ressources de police utilisées par le PDF et si elles sont incorporées ; pdftoppm -f 1 -singlefile -png report.pdf /tmp/report-page produit une page à examiner. Une police peut figurer dans les ressources PDF tout en étant un mauvais remplacement pour un caractère : comparez le glyphe, pas seulement les métadonnées. Pour une mise en production exigeant un texte CJK prévisible, épinglez la version du navigateur et fournissez le même jeu de polices à chaque exécuteur. Le guide du chargement des polices et de la stabilité de mise en page explique pourquoi une police tardive peut déplacer le texte ; la production PDF ajoute le média d’impression et la pagination à ce même problème de sélection.
L’inventaire des polices fait partie de l’environnement de rendu. Un profil BotBrowser peut déclarer une collection de polices, mais l’hôte doit toujours mettre les fichiers correspondants à disposition du moteur, et ces fichiers doivent couvrir le système d’écriture demandé. Si le même rapport diffère entre un poste local et CI, comparez l’inventaire réel et la version du navigateur avant de modifier l’interligne ou les marges. Le verrouillage des versions du navigateur couvre séparément la maîtrise de version ; épingler le binaire n’installe pas les polices manquantes et ne fige pas les bibliothèques du système.
L’extraction du texte est une vérification complémentaire utile. pdftotext report.pdf - peut montrer si le fichier contient les caractères attendus et le bon ordre de lecture, mais ne prouve pas qu’un lecteur les dessine avec la forme voulue. Un PDF peut contenir du Unicode extractible tout en affichant un carré de remplacement ; il peut aussi sembler correct tout en ayant un ordre d’extraction malcommode pour les outils d’assistance. Traitez l’inspection visuelle, l’extraction, le nombre de pages et l’examen des ressources de police comme des signaux distincts : chacun répond à une question différente.
Si un fichier diffère uniquement par les retours à la ligne ou le nombre de pages, comparez-le d’abord à un exemple minimal de deux pages avant de modifier les marges. Corrigez une variable à la fois : commencez par fixer le papier, puis les fichiers de police et le signal de disponibilité, puis la fragmentation. Un long paragraphe qui traverse une coupure est souvent acceptable ; un titre séparé de son premier paragraphe ou une ligne de tableau coupée justifie une règle d’impression ciblée. Un break-inside: avoid général sur tous les conteneurs n’est pas une correction sûre, puisqu’un conteneur plus haut qu’une feuille ne peut pas rester entier.
Questions fréquentes
Pourquoi @media print modifie-t-il le PDF mais pas la capture d’écran ?
page.pdf() rend avec le média d’impression par défaut. Une capture utilise généralement le média écran, sauf si le test le modifie explicitement. Gardez des assertions écran et impression séparées si les deux sorties font partie du produit.
Pourquoi les couleurs d’arrière-plan manquent-elles ?
L’option API printBackground vaut false par défaut. Activez-la si l’arrière-plan transmet une information, puis inspectez le PDF, car les lecteurs et les imprimantes peuvent appliquer une gestion des couleurs supplémentaire.
Pourquoi un caractère chinois devient-il un carré uniquement en CI ?
L’exécuteur peut ne pas avoir la même famille ou la même couverture de glyphes que la machine de développement, ou la police web peut ne pas être chargée au moment de la capture. Comparez l’inventaire des polices de l’exécuteur, attendez le jeu de polices du document et rendez la page PDF produite ; ne déduisez pas la cause de la seule version du navigateur.
Faut-il employer page-break-before ou break-before ?
Utilisez break-before dans les nouvelles règles CSS. page-break-before reste un alias historique, mais changer le nom ne résout ni un format papier contradictoire, ni une feuille d’impression absente, ni un parent trop grand.
Sources
- Puppeteer :
Page.pdf() - Puppeteer :
PDFOptions - Puppeteer : génération de PDF
- MDN : impression
- MDN :
page-break-before - MDN :
Document.fonts - BotBrowser : polices et configuration
Conclusion pratique
Définissez une seule politique de format papier et de marges, attendez le signal de disponibilité de l’application et les polices, puis vérifiez le PDF produit plutôt que le seul DOM. Si la pagination échoue, inspectez la cascade d’impression, la géométrie de la feuille et la taille de l’élément ; si les glyphes CJK sont des carrés, vérifiez la couverture des polices de l’hôte et le fichier rendu avant de modifier la mise en page. Conservez la version du navigateur et l’environnement de polices avec le livrable pour attribuer un changement ultérieur au moteur plutôt qu’à une modification du contenu.
Cet article fait partie du guide : Débuter avec l'automatisation du navigateur : carte initiale
Articles Connexes
Comment le rendu des polices CJK révèle votre système réel
EmpreinteQu'est-ce que l'empreinte des polices ? Cohérence du texte
DémarrageScraping avec Selenium : capacités et relevés
DéploiementCaptures fiables avec un navigateur headless
DémarragePuppeteer + BotBrowser : pièges de lancement et de profil
DémarrageFiabiliser les tests de transfert de fichiers dans le navigateur
Exécutez la même vérification dans BotBrowser
Chargez un profil, gardez dans le navigateur les signaux que vous venez de lire, puis confirmez le résultat sur votre propre machine avant le déploiement.