La transformation de pages HTML en documents PDF est une exigence fréquente dans le développement d'applications d'entreprise, qu'il s'agisse de factures, de rapports analytiques ou de certificats. Historiquement, cette tâche était complexe en PHP, nécessitant soit une manipulation manuelle de coordonnées (bibliothèques bas niveau), soit la gestion de moteurs de rendu instables. La bibliothèque html2pdf s'est imposée comme une solution robuste, s'appuyant sur le moteur TCPDF pour offrir une interprétation fidèle du HTML et du CSS.
Comprendre l'architecture interne de html2pdf
Pour exploiter pleinement cet outil, il est essentiel d'appréhender ses composants principaux situés dans le répertoire src/ :
- Moteur principal (Html2Pdf.php) : Le point d'antrée qui orchestre la configuraton de la page, le rendu des polices et la sortie finale.
- Analyseur HTML (Parsing/) : Ce module décompose le code source HTML.
HtmlLexers'occupe de l'analyse lexicale tandis queTagParserinterprète la hiérarchie des balises. - Convertisseur CSS (CssConverter.php) : Il traduit les propriétés CSS standards (box-model, couleurs, fontes) en instructions compréhensibles par le moteur PDF.
- Système de Tags (Tag/) : Chaque balise HTML ou SVG possède sa propre classe de traitement (ex:
Div.php,Table.php), permettant une extension facile des fonctionnalités.
Mise en œuvre : Création d'un service de génération de factures
Plutôt que d'écrire du code procédural, nous allons structurer notre logique dans une classe de service réutilisable. Commençons par l'installation via Composer :
composer require spipu/html2pdf
Voici un exemple d'implémentation pour un système de facturation :
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Spipu\Html2Pdf\Html2Pdf;
use Spipu\Html2Pdf\Exception\Html2PdfException;
use Spipu\Html2Pdf\Exception\ExceptionFormatter;
class DocumentPdfService {
private $generator;
public function __construct($orientation = 'P', $format = 'A4', $langue = 'fr') {
// Initialisation avec support UTF-8
$this->generator = new Html2Pdf($orientation, $format, $langue, true, 'UTF-8', [10, 10, 10, 10]);
$this->generator->setDefaultFont('Arial');
}
public function exporterFacture($donnees) {
try {
$vueHtml = $this->preparerTemplate($donnees);
$this->generator->writeHTML($vueHtml);
// Retourne le contenu binaire pour un envoi par email ou stockage
return $this->generator->output('facture_' . $donnees['ref'] . '.pdf', 'S');
} catch (Html2PdfException $e) {
$formatter = new ExceptionFormatter($e);
throw new Exception("Erreur PDF : " . $formatter->getHtmlMessage());
}
}
private function preparerTemplate($data) {
// Utilisation de la balise <page> propre à html2pdf pour la mise en page
$html = '
<page backtop="20mm" backbottom="20mm" backleft="10mm" backright="10mm">
<page_header>
<table style="width: 100%; border-bottom: 1px solid #ccc;">
<tr>
<td style="width: 50%">Société ' . htmlspecialchars($data['entreprise']) . '</td>
<td style="width: 50%; text-align: right">Facture N° ' . $data['ref'] . '</td>
</tr>
</table>
</page_header>
<h1 style="text-align: center; margin-top: 50px;">Détail de la commande</h1>
<table border="1" style="width: 100%; border-collapse: collapse; margin-top: 20px;">
<thead>
<tr style="background: #f0f0f0;">
<th style="width: 60%; padding: 10px;">Description</th>
<th style="width: 20%;">P.U.</th>
<th style="width: 20%;">Total</th>
</tr>
</thead>
<tbody>';
foreach ($data['lignes'] as $ligne) {
$html .= '
<tr>
<td style="padding: 8px;">' . htmlspecialchars($ligne['nom']) . '</td>
<td style="text-align: right;">' . number_format($ligne['prix'], 2) . ' €</td>
<td style="text-align: right;">' . number_format($ligne['total'], 2) . ' €</td>
</tr>';
}
$html .= '
</tbody>
</table>
<page_footer>
<p style="text-align: center; font-size: 10pt;">Page [[page_cu]]/[[page_nb]]</p>
</page_footer>
</page>';
return $html;
}
}
Techniques d'optimisation avancées
1. Gestion de la mémoire pour les documents volumineux
Lors de la génération de rapports de plusieurs centaines de pages, la consommation de mémoire peut devenir critique. Il est conseillé d'augmenter la limite de mémoire PHP ou de segmenter l'écriture :
// Augmentation temporaire pour les gros traitements
ini_set('memory_limit', '256M');
// Utilisation de blocs HTML pour réduire la charge de l'analyseur
foreach ($blocsContenu as $bloc) {
$pdf->writeHTML($bloc);
}
2. Support des graphiques vectoriels (SVG)
Html2pdf supporte nativement les balises SVG, ce qui est idéal pour insérer des logos ou des graphiques sans perte de qualité :
<svg width="200" height="60">
<rect x="0" y="0" width="200" height="60" fill="#2c3e50" />
<text x="100" y="35" text-anchor="middle" fill="white" font-family="Arial" font-size="14">
LOGO ENTREPRISE
</text>
</svg>
3. Mise en cache des fichiers générés
Pour économiser les ressources serveur, implémentez une stratégie de cache basée sur l'empreinte (hash) du contenu :
$cleCache = 'report_' . md5($htmlSource);
$cheminCache = __DIR__ . '/cache/' . $cleCache . '.pdf';
if (file_exists($cheminCache)) {
header('Content-Type: application/pdf');
echo file_get_contents($cheminCache);
exit;
}
// Sinon, générer et sauvegarder
$pdfContent = $html2pdf->output('', 'S');
file_put_contents($cheminCache, $pdfContent);
Résolution des problèmes de rendu fréquents
- Images manquantes : Utilisez toujours des chemins absolus (
realpath()) ou encodez vos images en Base64 pour garantir leur accès par le moteur de rendu. - Caractères spéciaux : Assurez-vous que votre document HTML contient la balise
<meta charset="UTF-8">et que vous avez sélectionné une police supportant l'Unicode (commefreeserifpour les caractères étendus). - Sauts de page intempestifs : Utilisez la propriété CSS
page-break-inside: avoid;sur les tableaux ou les blocs div pour éviter qu'ils ne soient coupés de manière inesthétique entre deux pages.
Débogage et logs
En phase de développement, l'utilisation de l'ExceptionFormatter est cruciale. Elle permet d'identifier précisément quelle balise HTML mal fermée ou quelle propriété CSS non supportée bloque la génération. Pour les environnements de production, encapsulez toujours vos appels dans des blocs try...catch pour éviter d'exposer des erreurs serveur aux utilisateurs finaux.