Générer vos PDF avec PhantomJS

Publié le 21 août 2014 par Guillaume STOEHR
pdf phantomjs htmltopdf serverside

PhantomJS

PhantomJS ?

PhantomJS est un navigateur sans interface graphique, entièrement scriptable en JavaScript basé sur le moteur de rendu Webkit. Il est disponible sur Linux, Windows et MacOS et s'utilise en ligne de commande :

Par exemple, grâce au fichier "helloworld.js" ci-dessous :

console.log('Hello world !');
phantom.exit();

Puis en exécutant la ligne de commande :

$ phantomjs helloworld.js

Nous aurons la sortie suivante :

> Hello world !

Et je peux générer des PDF avec ça ?

En effet, PhantomJS peut ouvrir des pages web et les imprimer sous forme de fichier PNG, GIF, JPG et PDF :

var page = new WebPage();

page.paperSize = {
    format        : "A4",
    orientation    : "portrait",
    margin        : { left:"1cm", right:"1cm", top:"1cm", bottom:"1cm" }
};

page.open("http://www.splitfire.fr", function (status) {
    page.render("export.pdf");
    phantom.exit();
});

En exécutant ce code, vous générez un PDF de la page d'accueil de Splitfire. Sympa non ?

Des PDF ok, mais pourquoi faire ?

Dernièrement, nous cherchions une solution "next-gen" pour imprimer des factures au format PDF depuis nos applications web.

Par le passé, nous avions testé diverses technologies qui se sont avérées très contraignantes, tant au niveau de la mise en place qu'au niveau des benchmark très très moyens. Je pense notamment à :

  • La librairie PHP HTMLtoPDF qui n'a pas été mise à jour depuis 2011 et qui consomme énormément de mémoire (voir crash au milieu de la génération d'un PDF de plusieurs pages sur des petites configurations)
  • FOP qui est un moteur XSL-FO de la fondation Apache développé en JAVA : l'objectif est de réaliser un document PDF à partir d'un fichier XML et d'une feuille de style XSL. Bien que l'idée d'avoir un fichier XML unique pour représenter un document peut paraître séduisante, les temps d'écriture de la feuille XSL sont assez atroces et les benchmarks ne sont pas non plus au rendez-vous

PhantomJS est la solution la plus rapide d’exécution et à mettre en place que nous avons trouvée pour le moment. Après un petit tour sur la documentation vous aurez toutes les armes en main pour générer vos fichier PDF sans vous prendre la tête !

Des spécifications CSS respectées

La puissance de PhantomJS est qu'il embarque une version de Webkit dernière génération qui respecte la plupart des standards CSS de ces dernières années :

La règle @page

@page
{
    size: 21 cm 29.7 cm;
    margin: .5cm .5cm .5cm .5cm;
}

Les médias query

@media print
{
    a 
    {
        color: red;
    }
}

Et bien d'autres

Vous pouvez ainsi utiliser toutes les spécifications CSS3 : box-shadow, police taillée en rem, la gestion des sauts de page avec page-break-after, etc...

Le seul bug que nous avons rencontré est avec la propriété :

page-break-inside: avoid;

Cette propriété bien utile permet d'éviter qu'un élément du DOM soit coupé sur plusieurs pages. Il semblerait que cette propriété ne soit pas supportée par webkit ; ce qui nous a réservé quelques surprises...

Quelques astuces

De la tête au pied

Il n'est pas toujours facile d'ajouter un header, un footer et une pagination automatique qui se répète sur chaque page. Pour ce faire, Phantomjs permet d'ajouter dynamiquement ces derniers depuis le script d'export :

var page = new WebPage();

page.paperSize = {
    format        : "A4",
    orientation    : "portrait",
    margin        : { left:"1cm", right:"1cm", top:"1cm", bottom:"1cm" },
    header        : {
        height        : "3cm",
        contents        : phantom.callback(function(pageNum, numPages){
            return("Mon header : " + pageNum + " / " + numPages);
        })
    },
    footer        : {
        height        : "1cm",
        contents        : phantom.callback(function(pageNum, numPages){
            return("Mon footer : " + pageNum + " / " + numPages);
        })
    }
};

page.open("http://www.splitfire.fr", function (status) {
    page.render("export.pdf");
    phantom.exit();
});

Bien entendu rien ne vous empêche d'utiliser votre moteur de template favori pour générer vos header et footer. Pour ma part, ce sera Handlebars !

Réinitialiser la pagination

Si votre PDF contient plusieurs documents et que vous avez besoin de réinitialiser la pagination, il vous suffit de wrapper vos documents dans un élément contenant la classe "phantomjs_reset_pagination" dans votre fichier HTML :

<div class="phantomjs_reset_pagination">
    Document 1
</div>
<div class="phantomjs_reset_pagination">
    Document 2
</div>
[...]

Conclusion

Je ne peux que vous conseiller de tester PhantomJS si vous avez des problématiques techniques similaires aux nôtres.

Sachez également que vous pouvez utiliser PhantomJS pour automatiser les tests de vos applications web : remplissage de formulaire avec des jeux de données, manipulation de DOM, etc...

N'hésitez pas à donner votre avis sur cette technologie dans les commentaires et à partager avec nous les solutions techniques que vous avez retenues pour générer vos PDF.

Retrouvez la documentation de PhantomJS sur le site officiel

Pour aller plus loin

Notez que SlimerJS est un équivalent de PhantomJS basé sur le moteur de rendu Gecko (utilisé par Firefox). Mais SlimerJS ne supporte pas encore l'export en PDF (dans sa version 0.9.2 au moment où j'écris ces lignes) : il va falloir attendre encore un peu pour l'utiliser dans ce but...

A propos de l'auteur
Guillaume STOEHR

Développeur et co-fondateur de Splitfire.
Spécialiste de la programmation orientée objet en PHP5 et JavaScript.

Partager cet article sur les réseaux sociaux