Conversion HTML en PDF : Comparaison de jsPDF, html2pdf.js et html2canvas

La nécessité de générer des fichiers PDF à partir de contenu HTML est fréquente, souvent avec des exigences précises de formatage. Cet article explore et compare trois approches populaires côté client pour cette tâche : jsPDF, html2pdf.js (qui s'appuie sur jsPDF et html2canvas), et une méthode combinant html2canvas avec jsPDF.

Les versions des bibliothèques utilisées dans cet exemple sont :

  • jsPDF : 2.5.1
  • html2canvas : 1.4.1
  • html2pdf.js : v0.9.3

Nous commencerons par un exemple de page HTML simple, stylisé avec Bootstrap 5, qui servira de contenu source pour la conversion.

<html>
  <head>
    <meta charset="utf-8" />
    <title>Génération PDF</title>
    <link href="./plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <script src="./plugins/jquery/jquery-1.9.1.js"></script>
    <script src="./plugins/jspdf/dist/jspdf.umd.min.js"></script>
    <script src="./plugins/html2canvas.min.js"></script>
    <!-- Le fichier msyh-normal.js est une police personnalisée pour supporter les caractères chinois avec jsPDF -->
    <script src="./plugins/jspdf/msyh-normal.js"></script> 
    <script src="./plugins/html2pdf.bundle.min.js"></script>
    <script src="./plugins/jsPDF-AutoTable-master/dist/jspdf.plugin.autotable.js"></script>
    <style>
      body * {
        font-family: "msyh", sans-serif; /* Utilisation de la police msyh pour le support des caractères chinois */
        letter-spacing: 0.01px; /* Correction des espacements de caractères avec jsPDF */
      }
      tr {
        height: 3rem;
      }
    </style>
  </head>
  <!-- La largeur du body est ajustée pour une meilleure compatibilité avec la conversion PDF -->
  <body style="width: 158mm">
    <div id="zoneContenuExportable" class="container pt-2">
      <div style="page-break-inside: avoid">
        <h1>Titre H1</h1>
        <h2>Titre H2</h2>
        <h3>Titre H3</h3>
        <h4>Titre H4</h4>
        <h5>Titre H5</h5>
        <h6>Titre H6</h6>
      </div>
      | Texte Chinois | Premier | Nom | Poignée |
|---|---|---|---|
| 1 | Marc | Otto | mdo |
| 2 | Jacob | Thornton | fat |
| 3 | Larry l'Oiseau | twitter |
      <div class="row g-1 mt-2" style="page-break-inside: avoid">
        <div class="col-6">
          <div class="card">
            ![Image de carte](./images/1.jpg)
            <div class="card-body">
              <h5 class="card-title">Demain</h5>
              <p class="card-text">Demain sera un nouveau jour.</p>
            </div>
          </div>
        </div>
        <div class="col-6">
          <div class="card">
            ![Image de carte](./images/1.jpg)
            <div class="card-body">
              <h5 class="card-title">Demain</h5>
              <p class="card-text">Demain sera un nouveau jour.</p>
            </div>
          </div>
        </div>
      </div>
      <div class="row g-1 mt-2" style="page-break-inside: avoid">
        <div class="col-6">
          <div class="card">
            ![Image de carte](./images/1.jpg)
            <div class="card-body">
              <h5 class="card-title">Demain</h5>
              <p class="card-text">Demain sera un nouveau jour.</p>
            </div>
          </div>
        </div>
        <div class="col-6">
          <div class="card">
            ![Image de carte](./images/1.jpg)
            <div class="card-body">
              <h5 class="card-title">Demain</h5>
              <p class="card-text">Demain sera un nouveau jour.</p>
            </div>
          </div>
        </div>
      </div>
      | Texte Chinois | Premier | Nom | Poignée |
|---|---|---|---|
| 1 | Marc | Otto | mdo |
| 2 | Jacob | Thornton | fat |
| 3 | Larry l'Oiseau | twitter |
      | Texte Chinois | Premier | Nom | Poignée |
|---|---|---|---|
| 1 | Marc | Otto | mdo |
| 2 | Jacob | Thornton | fat |
| 3 | Larry l'Oiseau | twitter |
    </div>
    <div style="margin-top: 3rem">
      <button type="button" class="btn btn-primary mb-2" onclick="genererPdfViaJsPdf('zoneContenuExportable')">Télécharger PDF avec jsPDF (HTML)</button>
      <button type="button" class="btn btn-primary mb-2" onclick="genererPdfAvecJsPdfAutoTable()">Télécharger PDF avec jsPDF (AutoTable)</button>
      <button type="button" class="btn btn-primary mb-2" onclick="genererPdfAvecHtml2Pdf('zoneContenuExportable')">Télécharger PDF avec html2pdf.js</button>
      <button type="button" class="btn btn-primary mb-2" onclick="exporterPdfViaHtml2Canvas('zoneContenuExportable')">Exporter PDF (html2canvas+jsPDF)</button>
    </div>
  </body>
</html>

Méthode 1 : Utilisation directe de jsPDF avec doc.html()

Cette approche utilise la fonction html() de jsPDF pour convertir le contenu HTML directement en PDF.

<script>
    /**
     * Génère un PDF à partir d'un élément HTML en utilisant jsPDF.
     * @param {string} idElement - L'ID de l'élément HTML à convertir.
     */
    function genererPdfViaJsPdf(idElement) {
      const documentPdf = new jspdf.jsPDF("portrait", "pt", "a4");
      documentPdf.setFont("msyh"); // Assure le support des caractères chinois
      documentPdf.html(document.getElementById(idElement), {
        callback: function (pdfResultat) {
          // La ligne suivante peut être utilisée pour télécharger le PDF :
          // pdfResultat.save("document_jspdf_html.pdf");
          // Pour prévisualiser le PDF dans une nouvelle fenêtre :
          window.open(pdfResultat.output("bloburl")); 
        },
        // Options de positionnement pour le rendu HTML
        x: 10,
        y: 10,
        html2canvas: {
            scale: 0.8 // Ajuste l'échelle pour s'assurer que le contenu rentre
        }
      });
    }
</script>

Observation : Avec cette méthode, le texte du PDF reste sélectionnable, ce qui est un avantage. Cependant, la propriété CSS page-break-inside: avoid peut ne pas toujours être respectée de manière fiable. Le rendu des styles CSS complexes peut parfois être imprécis.

Méthode 2 : Utilisation de jsPDF avec jsPDF-AutoTable

jsPDF-AutoTable est un plugin pour jsPDF qui facilite la génération de tableaux structurés dans les documents PDF. Cette démonstration se concentre sur le rendu des tableaux uniquement pour illustrer la capacité du plugin.

<script>
    /**
     * Génère un PDF en utilisant jsPDF et son plugin AutoTable pour les tableaux.
     * Les tableaux sont rendus séquentiellement.
     */
    function genererPdfAvecJsPdfAutoTable() {
      const documentPdf = new jspdf.jsPDF("portrait", "pt", "a4");
      documentPdf.setFont("msyh"); // Définir la police pour la compatibilité chinoise

      let positionVerticale = 20; // Position de départ pour le premier tableau

      // Convertir le premier tableau
      documentPdf.autoTable({ 
        html: "#table1", 
        styles: { font: "msyh", fontStyle: "normal" }, 
        useCss: true,
        startY: positionVerticale 
      });
      positionVerticale = documentPdf.lastAutoTable.finalY + 10; // Mettre à jour la position pour le tableau suivant

      // Convertir le deuxième tableau
      documentPdf.autoTable({ 
        html: "#table2", 
        styles: { font: "msyh", fontStyle: "normal" }, 
        useCss: true,
        startY: positionVerticale 
      });
      positionVerticale = documentPdf.lastAutoTable.finalY + 10;

      // Convertir le troisième tableau
      documentPdf.autoTable({ 
        html: "#table3", 
        styles: { font: "msyh", fontStyle: "normal" }, 
        useCss: true,
        startY: positionVerticale 
      });

      // Télécharger le document PDF résultant
      documentPdf.save("tables_avec_autotable.pdf");
      // Ou prévisualiser : window.open(documentPdf.output("bloburl"));
    }
</script>

Observation : Cete méthode offre un excellent contrôle sur la structure et le style des tableaux. Le texte à l'intérieur des tableaux est sélectionnable. Pour le contenu HTML non tabulaire, il faudrati utiliser d'autres méthodes de jsPDF ou les insérer manuellement. La méthode autoTable respecte mieux les sauts de page pour les tableaux qu'un simple doc.html() pour des éléments de tableau.

Méthode 3 : Utilisation de html2pdf.js

html2pdf.js est une bibliothèque wrapper qui combine html2canvas et jsPDF pour offrir une API plus simple. Elle est souvent plus efficace pour gérer les mises en page complexes et les sauts de page.

<script>
    /**
     * Génère un PDF à partir d'un élément HTML en utilisant html2pdf.js.
     * @param {string} idElementCible - L'ID de l'élément HTML à convertir.
     */
    function genererPdfAvecHtml2Pdf(idElementCible) {
      const elementCible = document.getElementById(idElementCible);
      const optionsConversion = {
        margin: [10, 10, 10, 10], // Marges [haut, gauche, bas, droite]
        filename: "document_html2pdf.pdf",
        image: { type: "jpeg", quality: 0.98 },
        html2canvas: { 
          dpi: 192, 
          scale: 2, 
          letterRendering: true,
          useCORS: true, // Important pour les images externes si applicable
          allowTaint: true // Permet de charger des images de différentes origines dans le canvas
        },
        jsPDF: { 
          unit: "pt", 
          format: "a4", 
          orientation: "portrait" 
        },
        pagebreak: { 
          mode: ["avoid-all", "css", "legacy"] // Gérer les sauts de page
        },
      };
      html2pdf().set(optionsConversion).from(elementCible).save();
    }
</script>

Observation : Cette approche gère généralement mieux les sauts de page et la propriété page-break-inside: avoid est souvent respectée, ce qui est un avantage majeur pour la mise en page. Cependant, html2pdf.js convertit l'HTML en une image avant de l'intégrer dans le PDF. Par conséquent, le texte du PDF n'est pas sélectionnable, et le document est traité comme une collection d'images.

Méthode 4 : Combinaison de html2canvas et jsPDF

Cette méthode implique de convertir manuellement le contenu HTML en un canevas (une image) à l'aide de html2canvas, puis d'ajouter cette image à un nouveau document PDF avec jsPDF, en gérant les sauts de page pour le contenu long.

<script>
    /**
     * Exporte un élément HTML en PDF en utilisant html2canvas pour le rendu, puis jsPDF.
     * Gère la pagination pour le contenu s'étendant sur plusieurs pages.
     * @param {string} idConteneur - L'ID de l'élément HTML à exporter.
     */
    function exporterPdfViaHtml2Canvas(idConteneur) {
      const elementConteneur = document.getElementById(idConteneur);
      html2canvas(elementConteneur, { scale: 2, useCORS: true, allowTaint: true }).then((canevasRendu) => {
        const largeurContenu = canevasRendu.width;
        const hauteurContenu = canevasRendu.height;

        // Dimensions standard d'une page A4 en points (72 points/pouce)
        const largeurA4_pt = 595.28; 
        const hauteurA4_pt = 841.89; 

        // Hauteur du contenu une fois mis à l'échelle pour la largeur d'une page A4
        const hauteurPageProportionnelle = (largeurContenu / largeurA4_pt) * hauteurA4_pt;
        
        let hauteurRestante = hauteurContenu; // Hauteur du contenu restant à traiter
        let decalageVertical = 0; // Décalage pour positionner l'image sur les pages PDF

        const largeurImagePdf = largeurA4_pt;
        // La hauteur de l'image sur le PDF est calculée proportionnellement à la largeur A4
        const hauteurImagePdf = (largeurImagePdf / largeurContenu) * hauteurContenu; 

        // Convertir le canevas en données d'image JPEG
        const donneesImage = canevasRendu.toDataURL("image/jpeg", 0.9); // Qualité JPEG 0.9

        const documentPdf = new jspdf.jsPDF("p", "pt", "a4");

        // Si le contenu tient sur une seule page PDF
        if (hauteurRestante < hauteurPageProportionnelle) {
          documentPdf.addImage(donneesImage, "JPEG", 0, 0, largeurImagePdf, hauteurImagePdf);
        } else {
          // Gérer le contenu sur plusieurs pages
          while (hauteurRestante > 0) {
            documentPdf.addImage(donneesImage, "JPEG", 0, decalageVertical, largeurImagePdf, hauteurImagePdf);
            hauteurRestante -= hauteurPageProportionnelle;
            decalageVertical -= hauteurA4_pt; // Déplacer l'image vers le haut pour afficher la partie suivante sur la nouvelle page
            if (hauteurRestante > 0) {
              documentPdf.addPage(); // Ajouter une nouvelle page si nécessaire
            }
          }
        }
        window.open(documentPdf.output("bloburl")); // Prévisualiser le PDF
        // documentPdf.save("rendu_html2canvas_jspdf.pdf"); // Ou télécharger le PDF
      });
    }
</script>

Observaiton : Comme avec html2pdf.js, cette méthode produit un PDF où le contenu est une image rasterisée. Le texte n'est donc pas sélectionnable. La gestion des sauts de page est entièrement manuelle et dépend des calculs proportionnels, ce qui peut être complexe pour des mises en page dynamiques. La propriété page-break-inside: avoid n'est pas directement supportée par html2canvas.

Étiquettes: jsPDF html2pdf.js html2canvas Génération PDF Conversion HTML-PDF

Publié le 2 août à 20h26