Générer un PDF depuis du HTML avec le renderer Chrome d'Artisan
Le pont Artisan effectue le rendu du HTML via un processus headless Chrome, puis importe le résultat dans un document NextPDF sous forme de Form XObject vectoriel. Le texte reste sélectionnable et indexable, au lieu d’être rastérisé. Tu associes un ChromeRendererConfig, tu appelles writeHtmlChrome() sur un document (ou tu utilises ChromeHtmlRenderer directement), et Chrome prend en charge la mise en page. Ce guide couvre l’appel de rendu, la politique d’isolation réseau, le modèle de taille de page et de hauteur de contenu, ainsi que le cycle de vie du renderer de longue durée pour un worker.
Les prérequis, d’emblée :
- Le cœur NextPDF et
nextpdf/artisansont installés. - Un binaire Chrome ou Chromium est installé, et l’utilisateur du worker peut le lancer en mode headless. Vérifie-le avec
chromium --headless --dump-dom about:blankavant de commencer. Le provisionnement du binaire et le choix lié au sandbox du conteneur sont traités sur la page de configuration du renderer Chrome référencée sous Voir aussi.
Il s’agit d’un guide pratique. Il suppose que tu peux lancer un processus Chrome à proximité de l’application. Pour un premier exemple directement exécutable, consulte le démarrage rapide d’Artisan.
Installation
Section intitulée « Installation »Installe le pont aux côtés du cœur.
composer require nextpdf/artisanInstalle une version de Chrome ou Chromium que l’utilisateur du worker peut lancer. Sous Debian ou Ubuntu, utilise le paquet de la distribution.
apt-get install -y chromiumConfirme que le binaire s’exécute en mode headless avec l’utilisateur du worker.
chromium --headless --dump-dom about:blankUn code de sortie 0 accompagné d’un DOM vide signifie que le binaire et ses bibliothèques partagées sont présents. Un code de sortie non nul correspond au même échec que le pont fait remonter sous forme de ChromeRenderException. Corrige ce point ici en premier.
Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »writeHtmlChrome() est une méthode du Document du cœur NextPDF. Elle valide l’entrée, résout le renderer Artisan, envoie le HTML à Chrome via le Chrome DevTools Protocol (CDP), analyse le PDF renvoyé et incorpore la page 0 sous forme de Form XObject à la position courante du curseur. Chrome s’exécute en tant que processus enfant du worker PHP. Le pont le pilote via CDP au lieu de se connecter à un Chrome lancé séparément sur un port de débogage, si bien qu’aucun point d’accès réseau n’a besoin d’être exposé ni authentifié.
Le pont effectue le rendu derrière une posture réseau de refus par défaut. Chaque rendu est encapsulé dans une Content-Security-Policy qui refuse toutes les origines de ressources (default-src 'none') et n’autorise que les images en ligne (img-src data:). Le pont bloque aussi chaque URL de sous-ressource à la couche de transport CDP avec Network.setBlockedURLs(['*']). De ce fait, une image, une feuille de style, une police, un script ou un iframe distant présent dans ton HTML ne se charge pas. Intègre chaque ressource en ligne sous forme d’URI data:. C’est ainsi que le pont répond au risque de falsification de requête côté serveur (SSRF) lors du rendu de HTML potentiellement non fiable, et cette protection s’applique quelle que soit la configuration.
Le modèle de taille de page comporte deux modes. Quand tu fournis à la fois la largeur et la hauteur (en points PDF), Chrome imprime exactement au format papier demandé. Quand la hauteur est omise ou null, le pont mesure la hauteur du contenu rendu dans Chrome, la convertit en points et ajoute une petite marge de sécurité pour le reflux (environ 14,4 points), afin que printToPDF ne déborde pas sur une seconde page que l’importateur, limité à la page 0, tronquerait.
Surface d’API
Section intitulée « Surface d’API »// On a NextPDF core Document (the HasTextOutput concern):writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static
// The standalone renderer:new ChromeHtmlRenderer(ChromeRendererConfig $config, ?LoggerInterface $logger = null)ChromeHtmlRenderer::render(string $html, float $widthPt, float $heightPt = 0.0): ChromeRenderResultChromeHtmlRenderer::close(): void
// The configuration value object (final readonly):new ChromeRendererConfig( ?string $chromeBinaryPath = null, int $renderTimeout = 30, string $defaultCss = '', int $maxHtmlSize = 5_000_000, bool $noSandbox = false,)ChromeRendererConfig::fromArray(array $config): selfChromeRendererConfig est l’unique surface de configuration, et elle est immuable : construis donc une nouvelle instance pour changer une valeur. ChromeRenderResult::getPdfData() renvoie les octets du PDF. La référence complète des options et des drapeaux de lancement Chrome figés se trouve sur la page de configuration d’Artisan référencée sous Voir aussi.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Associe la config à un document, rends du HTML de confiance, puis enregistre.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Core\Document;
$config = new ChromeRendererConfig( chromeBinaryPath: '/usr/bin/chromium',);
$document = Document::createStandalone();$document->setChromeRendererConfig($config);$document->addPage();
$document->writeHtmlChrome(' <div style="display: flex; gap: 20px; font-family: sans-serif;"> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Revenue</h2> <p style="font-size: 2em; color: #2563eb;">$124,500</p> </div> <div style="flex: 1; background: #f0f0f0; padding: 24px;"> <h2>Orders</h2> <p style="font-size: 2em; color: #16a34a;">1,847</p> </div> </div>');
$document->save('/tmp/report.pdf');Chrome gère la mise en page flex, et les chiffres restent sélectionnables dans la sortie parce que la page est incorporée sous forme de Form XObject vectoriel, et non comme image matricielle. Pour tenir sur une page A4 fixe, passe la largeur et la hauteur en points.
$document->writeHtmlChrome($html, width: 595.28, height: 841.89);Exemple de code — Production
Section intitulée « Exemple de code — Production »En production, construis un seul renderer par worker, injecte un logger PSR-3, intercepte séparément les deux types d’exception, et libère le processus Chrome de manière déterministe à l’arrêt.
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Artisan\Exception\ChromeNotAvailableException;use NextPDF\Artisan\Exception\ChromeRenderException;use Psr\Log\LoggerInterface;
final class ReportRenderer{ private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger) { $config = ChromeRendererConfig::fromArray([ 'chrome_binary' => getenv('CHROME_BINARY') ?: null, 'render_timeout' => 45, 'max_html_size' => 2_000_000, 'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'), ]);
$this->renderer = new ChromeHtmlRenderer($config, $logger); }
public function render(string $html, float $widthPt, float $heightPt = 0.0): string { try { return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData(); } catch (ChromeNotAvailableException $exception) { // Deployment fault: the Chrome runtime is missing. Page on-call. throw $exception; } catch (ChromeRenderException $exception) { // Render-time fault: timeout, crash, or empty output. Retryable once. throw $exception; } }
public function shutdown(): void { $this->renderer->close(); }}Le renderer est construit une seule fois et réutilisé. Le pool de navigateurs sous-jacent maintient un processus Chrome en vie et le redémarre tous les 100 rendus pour borner la croissance mémoire. Les deux blocs catch séparent une panne de déploiement (runtime manquant) d’une panne au moment du rendu (réessayable), et aucun des deux blocs catch n’est vide. Appelle shutdown() à l’arrêt du worker pour libérer le processus Chrome au lieu d’attendre le destructeur.
Construis la config à partir d’un tableau de configuration du framework afin d’obtenir des clés en snake_case, et fige chromeBinaryPath en production pour utiliser un binaire déterministe.
Cas limites et pièges
Section intitulée « Cas limites et pièges »- Un HTML vide est sans effet.
writeHtmlChrome('')renvoie le document inchangé. - Aucune page pour l’instant. Si le document n’a aucune page,
writeHtmlChrome()en ajoute une avant le rendu. - Les ressources distantes ne se chargent pas — par conception.
<img src="https://...">est rendu vide. Intègre chaque ressource en ligne sous forme d’URIdata:. Il s’agit de la posture d’isolation réseau, pas d’un défaut. - Seule la page 0 est importée. L’ajustement automatique de la hauteur ajoute la marge de reflux afin qu’une seule page soit produite. Avec une hauteur explicite, aucune marge n’est ajoutée et la sortie correspond exactement à la taille de papier demandée : dimensionne donc la hauteur pour couvrir tout ton contenu.
- Pont manquant. Si
nextpdf/artisann’est pas installé, le cœur lève une exception de mise en page plutôt qu’une erreur fatale. Si la bibliothèquechrome-php/chromeest absente, le pont lèveChromeNotAvailableExceptionavec la commande d’installation. defaultCsset</style>. Toute séquence</style>présente dansdefaultCssest retirée avant l’injection, afin d’empêcher l’échappement de la balise style. Tiens-en compte si tu génères ton CSS depuis un modèle.
Performance
Section intitulée « Performance »Le premier rendu supporte le coût du démarrage de Chrome et de la mise en page. Les rendus suivants réutilisent le processus Chrome déjà actif, si bien que le coût de démarrage n’est payé que rarement. Construis un seul renderer par worker et réutilise-le. N’en crée pas un par requête. Attends-toi à un pic de latence tous les 100 rendus, lorsque le pont redémarre le processus Chrome pour borner la mémoire. Tiens-en compte dans tes objectifs de latence au lieu de le traiter comme un incident. Aligne renderTimeout sur un budget de requête en amont pour tout chemin accessible depuis une entrée non fiable.
Notes de sécurité
Section intitulée « Notes de sécurité »- L’isolation réseau est le contrôle principal. Le pont n’autorise aucune récupération de sous-ressource sortante — CSP
default-src 'none'plus un blocage de chaque URL au niveau du transport CDP. Il n’implémente pas de liste d’autorisation par domaine parce qu’il n’en a pas besoin. Intègre les ressources en ligne sous forme d’URIdata:. - L’entrée est bornée avant que Chrome ne soit contacté. Le pont rejette tout HTML dépassant
maxHtmlSize(5 Mo par défaut), tout URI de données base64 surdimensionné (une protection contre les bombes de décompression), et toute balise<meta http-equiv="refresh">(qui pourrait déclencher une navigation vers un point d’accès interne). GardemaxHtmlSizeà sa valeur par défaut tant qu’une charge de travail connue n’en exige pas plus. L’augmenter élargit la surface d’épuisement des ressources. - Le sandbox de Chrome est un contrôle distinct. Définir
noSandbox: truelance Chrome avec--no-sandbox, ce qui supprime l’isolation des processus de Chrome — une réduction réelle du confinement, pas un drapeau cosmétique. Laisse-le àfalseen dehors des conteneurs. Quand le sandbox du conteneur ne peut pas s’initialiser, lance Chrome sous un utilisateur non root dans un conteneur restreint, et considère le déploiement comme une exigence de confiance plus élevée vis-à-vis de l’entrée. - Les journaux ne contiennent que des métadonnées. Injecte un logger PSR-3. Le pont journalise les longueurs en octets, les dimensions et les événements de cycle de vie, jamais le HTML, les octets du PDF ni le texte extrait.
- N’expose jamais de port de débogage distant Chrome. Le pont n’en utilise pas, et un port CDP ouvert est un canal de contrôle non authentifié.
Le modèle de menace complet — la défense contre la SSRF, la limite du sandbox énoncée explicitement et le catalogue des modes de défaillance — se trouve sur la page de sécurité et d’exploitation d’Artisan référencée sous Voir aussi, qui fixe les clauses pertinentes d’OWASP, du CWE et du NIST.
Conformité
Section intitulée « Conformité »Ce guide ne formule aucune revendication normative de conformité qui lui soit propre. Les contrôles réseau, d’isolation et d’épuisement des ressources du pont sont mappés à OWASP ASVS, au CWE Top 25 (SSRF / consommation de ressources non maîtrisée) et à NIST SP 800-53 SC-7 sur la page amont de sécurité et d’exploitation d’Artisan. Cette page du Cookbook reformule l’usage et renvoie ces citations normatives à cette page. Le pont n’effectue aucune opération cryptographique — la signature et le chiffrement relèvent du cœur ou de l’édition commerciale, et ne sont pas affectés par Artisan.
Voir aussi
Section intitulée « Voir aussi »- Rendre en périphérie avec Cloudflare — rendre du HTML en périphérie avec un repli local.
- Démarrage rapide d’Artisan — le premier rendu minimal.
- Configuration du renderer Chrome — provisionner le binaire, choisir le sandbox du conteneur et configurer une sonde de santé.
- Sécurité et exploitation d’Artisan — le modèle d’isolation réseau, la limite du sandbox et les modes de défaillance.