Version imprimable Version PDF RSS
dim, 21/06/2009 - 20:12 | Yoran   Tutoriels Drupal
Drupal, Printer et wkhtmltopdf
acroread.png

Pendant longtemps pour générer des vues PDF pour un node, il n'y avait guère d'autre solution que TCPDF ou DOMPDF, via le module PdfView ou plus récemment Printer. Aujourd'hui, il existe une nouvelle méthode native et diablement efficace utilisant le célèbre moteur webKit.

État des lieux

Sous Drupal, il existe au moins deux modules qui permettent d'obtenir la version PDF d'un contenu : pdfview et printer. Le second est plus récent, un peu plus bordélique aussi, mais propose outre le PDF, l'expédition d'un article à un ami, et une version imprimable. Ca ne se refuse pas...

Le module printer pouvait (et peut toujours d'ailleurs) être paramétré pour utiliser l'une des deux librairies PHP parmi les plus connues pour générer du PDF : DomPDF et tcpdf. Je dis les plus connues car il existe un autre projet tout jeune (et français) qui cherche à se faire une place, HTMLtoPDF.

Tout serait parfait sauf que la DOMPDF gère les accents comme il peux pour planter le reste du temps, et TCPDF ne prend pas en compte les feuilles de style. Et cerise sur le gâteau, les deux sont loin d'être des foudres de guerre... Donc en attendant de tester une éventuelle inclusion de HTMLtoPDF, le sujet est donc un peu mort du côté "pur PHP".

WebKit avec Drupal

Depuis peu, printer inclus une troisième option bien plus intéressante pour qui a la main sur son serveur, l'utilisation directe du moteur de rendu WebKit à travers l'outil wkhtmltopdf.

khtmltopdf utilise WebKit (Pour les rares qui ne le savent pas encore, il s'agit du moteur de rendu de Konqueror, Safari ou encore Google Chrome) pour un rendu visuel fidèle d'une page HTML et des ses feuilles de style, et le système d'impression de Qt4.5 pour transformer le tout en PDF. Le système fonctionne sous *nix ET sous Windows (les binaires sont fournis sur le site).

Sous *nix, Qt basant sa gestion des pixmaps sur X11, nous allons avoir en plus besoin de ce serveur pour fonctionner. Enfin, pas exactement de ce serveur, mais d'un utilitaire bien utile qui simule en mémoire le protocole X11, Xvfb. Une fois l'outil installé, le plus simple est d'en faire un lien symbolique dans la racine du module printer.

Pour ce qui est de la compilation de khtmltopdf sous linux, vous trouver toutes les informations utiles ici. Ensuite, pour Linuxiens et Windowiens, il ne reste plus qu'à déplacer (ou symlinker) le binaire khtmltopdf, lui aussi, dans à la racine du module.

Paramétrage de Printer

Pour commencer, vous devez activer (au minimum) les modules suivants :

  • Printer-friendly pages (core)
  • PDF version

Ensuite, allez dans Configuration du site, puis sur l'onglet PDF. Vous devriez alors voir apparaître en haut de la page un bouton radio avec comme libellé le chemin complet vers wkhtmltopdf. Activez ce bouton radio pour que printer utilise bien wkhtmltopdf. Ceci fait, cochez Link area pour que le lien d'impression PDF soit ajouté à ceux des contenus, puis descendez en bas de la page.

Vous avez ici les options pour wkhtmltopdf, supprimez tout ce qui s'y trouve. Ces options ne sont en effet pas compatible avec la version 0.8. Une fois la zone vidée, les Unixiens constaterons que juste en dessous se trouvent les options pour Xvfb. En effet, Printer va directement lancer lui-même le faux serveur X11 avant wkhtmltopdf, sans que vous n'ayez rien à faire de plus (pas besoin de créer un service donc).

Vous pouvez maintenant enregistrer la configuration et aller sur un contenu pour cliquer sur le lien PDF et vérifier ainsi que tout fonctionne.

Printer est un peu optimiste quant au temps de lancement de Xvfb et il arrive que ce dernier n'ai pas encore terminé son initialisation lorsque Printer lance wkhtmltopdf. Pour éviter cela, allez dans le dossier du module, et dans le fichier print_pdf.pages.inc, à la ligne 287, juste après le $xprocess = proc_open..., ajoutez la fonction sleep(1). Cela devrait largement suffire.

Mise en cache du PDF

La génération a beau être beaucoup plus véloce qu'avec n'importe quelle librairie PHP, il n'en reste pas moins ridicule de générer systématiquement un PDF à chaque click. Le mieux serait de le générer la première fois, de le stocker sur le serveur et de le servir de manière statique, via apache, les fois suivantes.

Pour faire cela, j'ai bidouillé un module expérimental qui s'inspire trééés fortement de l'excellent imagecache que j'ai appelé en tout originalité anycache.

Le principe est de mettre sur la page un lien vers files/anycache/pdf/NID.pdf (typiquement dans le node.tpl.php). Lorsque l'utilisateur click dessus la première fois, Apache ne trouve pas le fichier et génère une 404 qui est transmise à Drupal. Le module AnyCache intercepte ce chemin, cherche un sous-module qui gérerait le domaine /pdf/ et lui transmet la demande de génération.

AnyCache est conçu de sorte à ce que l'on puisse facilement construire des modules qui servent de "handler" pour un domaine donné. En standard, il y a dans AnyCache un module d'exemple appelé pdf_printer qui gère justement ce domaine. Son seul rôle est de faire appel à la génération PDF du module Printer et de renvoyer stocker le résultat dans le chemin de fichier (files/anycache/pdf/NID.pdf) que lui aura fourni AnyCache. En retour, AnyCache déclenche le download du fichier généré, fin de l'histoire.

Pour le second click sur le lien, Apache détecte la présence du fichier files/anycache/pdf/NID.pdf et le renvoie directement à l'utilisateur, SANS faire appel à Drupal.

Pour compléter le dispositif, le module pdf_printer implémente nodeapi pour détecter une modification sur un node et ainsi détruire l'éventuel pdf associé de sorte à ce que la génération puisse se faire au prochain coup.

En réalité pas tout à fait fin de l'histoire car Printer est un tout petit peu développé à la "vas-y que j'te pousse", avec pour conséquence l'implémentation du download dans chacun des plugins qui gère une librairie HTML/PDF, et donc aussi dans celle de wkhtmltopdf. La conséquence est qu'il n'y a pas de moyen propre de faire appel à la méthode de rendu de printer sans se coltiner la génération d'en-tête HTTP qui vont rentrer en conflit avec ceux d'anycache. La solution est donc à commenter, dans print_pdf.pages.inc comme ceci :

  /*
  header("Cache-Control: private");
  header("Content-Type: application/pdf");

  $attachment =  ($print_pdf_content_disposition == 2) ?  "attachment" :  "inline";

  header("Content-Disposition: $attachment; filename="$filename"");
*/

  echo $pdf;
  /*
  flush();
  */
suppression de la génération des headers

Pas terrible j'en conviens. Si cela vous amuse de tenter ce mécanisme, le module est disponible ici.

Conclusion

L'intégration de wkhtmltopdf à Printer est une bonne nouvelle pour la génération de PDF sous Drupal, permettant un rendu beaucoup plus propre en prenant beaucoup moins de ressources. Le seul inconvénient est que les liens ne seront pas clickables, mais en soit, ce n'est pas bien grave.

dsy, le sam, 04/07/2009 - 11:56

Le système fonctionne sous *nix ET sous Windows (les binaires sont fournis sur le site)

Bonjour,

Avez-vous testé wkhtmltopdf sous Windows ? Pour ma part, cela ne fonctionne pas.
En effet, il existe bien une version windows du binaire mais le module pour Drupal ne semble pas être adapté à Windows. Je n'ai pas trouvé d'appel à l'exécutable notamment.

Yoran, le sam, 04/07/2009 - 12:21

Ah pour le module j'avoue ne pas avoir fait le test, j'avais juste lancé le binaire sous Win. Le code n'est ceci dit pas très difficile à changer. Il est aussi possible de grandement le simplifié en recopiant le contenu dans un fichier temporaire et en faisant un simple appel d'exécutable sur ce fichier.

dsy, le sam, 04/07/2009 - 12:48

Ok merci pour ta réponse, je pourrais effectivement changer le code mais en lisant tes autres interventions, je vais regarder s'il est possible de "batcher" la création des PDF : un trigger à chaque publication d'une page créé un PDF avec wkhtmltopdf.
Merci pour tes tutoriaux :)

Yoran, le sam, 04/07/2009 - 12:52

Elle est intéressante ton idée, je n'y avais même pas pensé. Il y a une action Drupal qui lance un exécutable (le cas échéant ça ne doit pas être compliqué à faire) ? Avec cela, il suffirait de lancer wkhtmltopdf avec l'url de la version "print" et stoquer le tout dans un dossier accessible d'apache, du coup plus de gestion de cache... Merci, c'est une approche qui me plaît pas mal (deux modules en moins :)

Alexis , le lun, 03/05/2010 - 19:49

Bonjour,

 

Je travaille sur un site (encore en dév) avec Drupal 6.16 sous Windows Vista (édition familiale - on ne rit pas).

1. J'ai installé le module "printer".

2. J'ai téléchargé et installé "wkhtmltopdf".

3. J'ai copié l'exe + les DLLs dans [répertoire du module printer]/lib.

4. J'ai configuré le module "printer" en suivant ce qui est indiqué dans l'article.

5. Lorsque je consulte une page de mon site Drupal, j'ai bien l'icône "version PDF". Jusque là tout va bien.

Lorsque je clique sur le lien, je n'ai qu'un refresh de la page HTML, pas de téléchargement ou d'affichage de PDF. En faisant "enregistrer la cible sous..." (ou équivalent), j'obtiens la page html aussi.

Pouvez-vous m'éclairer ?

Par avance merci

Publier un nouveau commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement. Si vous avez un compte gravatar, l'utilisez pour afficher votre avatar.
  • Les adresses de pages web et de messagerie électronique sont transformées en liens automatiquement.
  • Every instance of custom tags in the input text will be replaced with a specific tool shortcut.
  • To highlight piece of code, just surround them with <code type="language"> Your code &tl;/code>>. Language can be java,c++,bash,etc... Everything Geshi support.
  • Les lignes et les paragraphes vont à la ligne automatiquement.

Plus d'informations sur les options de formatage

Êtes-vous humain ?
Cette question est là pour déterminer si vous êtes humain ou pas...