Cela fait plusieurs fois que je promet de vous parler de la génération PDF des bulletins (notamment ici et là). Je vais maintenant enfin tenir parole en abordant ce point dans ce petit billet. Accrochez vos ceintures, c’est parti !
Le choix d’une librairie de génération PDF.
Il a d’abord fallu faire le choix d’une librairie de génération de PDF. Il existe plusieurs type de librairie PHP de génération de PDF. Certaines permettent de convertir directement du code HTML en PDF, d’autres obligent le développeur à utiliser une syntaxe propre à l’outil.
On trouve sur le net différentes librairies parmi lesquelles :
- FDPF assez ancien, avec sa propre syntaxe (très contraignant avec son système assez dépassé de cellules).
- FPDI permettant de manipuler des documents et notamment de concaténer plusieurs pages, d’ajouter des pages vierges.
- TCPDF le poids lourd du domaine, avec gestion d’en têtes et pieds de page mais un assez mauvais support des CSS.
- mPDF très robuste mais pas très flexible, assez lourd au niveau des temps de génération à cause des nombreuses possibilités qu’il propose.
- HTML2PDF, lui même basé sur TCPDF (sucre syntaxique pour faciliter la manipulation de TCPDF)
- wkhtml2pdf issu du projet Webkit et assez avancé. Disposant d’un excellent rendu à l’heure actuelle, il reste aussi un petit peu plus compliquer à mettre en place étant donné qu’il faut installer un binaire sur le serveur.
- dompdf, petit nouveau qui m’a bien plu de part son excellente interprétation des feuilles de style CSS.
J’ai eu du mal à départager certains concurrent mais j’ai fini par jeter mon dévolu sur dompdf. Il s’agit d’une excellente librairie qui supporte la majorité des propriétés CSS2 ainsi que quelques propriétés CSS3. Elle supporte également l’utilisation des media queries CSS3 ce qui permet de gérer très facilement les en-têtes et les pieds de pages qui se répètent sur l’ensemble des pages du document.
Le seul défaut qu’on peu lui trouver est sa relative jeunesse. Cela dit, je suis fermement convaincu qu’il faut donner leur chance aux nouveaux projets … et comme l’équipe derrière est soudée et dynamique et qu’elle aime son bébé, ce n’est que du bon !
J’ai aussi été amené à utiliser le couple FPDF/FPDI. FPDI me permet en effet d’ajouter des pages blanches automatiquement pour gérer l’impression recto-verso et de concaténer les PDF de l’ensemble des élèves de façon à proposer un fichier global de la classe.
L’interface côté utilisateur ?
Comme à mon habitude, j’ai essayé de garder des choses simples. On a donc simplement un titre permettant d’identifier le bulletin dans le logiciel, les périodes à générer.
Il est ensuite possible de personnaliser les en-têtes et les pieds de pages. Les drapeaux #PRENOM# et #NOM# peuvent être insérés et le numéro de page est automatiquement inséré à la fin de la ligne de pied de page.
Il est ensuite possible d’insérer manuellement des sauts de pages à certains endroits du document. Cette solution n’est pas forcément la plus confortable pour l’utilisateur puisqu’elle n’est pas automatique. Cependant, aucune façon de faire automatiquement ne m’a satisfaite pour le moment. J’ai donc décidé de proposer cette alternative en attendant de trouver mieux 😉
La case prêt pour le recto verso permet d’insérer automatiquement des pages blanches quand le nombre de feuilles d’un bulletin individuel s’avère impaire de façon à ce que le PDF global générer puisse être envoyé à l’imprimante en mode recto-verso 🙂
Il est ensuite possible de cliquer en regard de la ligne concernant le bulletin souhaité sur le bouton Générer pour lancer le processus de génération des bulletins de l’ensemble de la classe.
Actuellement, la génération dure quelques secondes, ce qui est encore à mon avis trop. Cependant, il faut le temps de générer le pdf de chaque élève, puis de concaténer le tout ensuite.
Les axes d’amélioration : et si on jouait avec des background workers ? CakeResque à la rescousse.
Cependant, je pense qu’il est possible d’aller plus loin. D’une part, je n’aime pas fondamentalement le fait de faire attendre l’utilisateur (une petite minute environ pour une classe de 30 élèves). C’est toujours agaçant d’avoir a attendre.
D’autre part, cette solution n’est vraiment pas top au niveau de la gestion de la charge. Si 50 utilisateurs se mettent à générer en même temps le PDF d’une grosse classe, ceux qui souhaitent juste ajouter des évaluations où saisir leurs résultats risquent d’attendre longtemps (et mon pauvre serveur risque d’avoir bien chaud 😳 ).
L’idée serait donc de gérer cela en utilisant un concept de file (un peu comme lorsque vous uploadez une vidéo sur Youtube ou Vimeo et que vous devez attendre que le robot passe pour convertir votre vidéo dans le bon format qui vas bien).
Cela résoudrait donc de façon élégante le problème de charge en autorisant une seule génération à la fois (ou plusieurs d’ailleurs, mais de façon mesurée) ! Dans le meilleur des cas, on pourrait également donner l’ordre de génération automatiquement (par exemple lorsque l’ensemble des résultats ont été saisis pour une évaluation).
CakeResque, propose cela en utilisant un concept de files (Queues), de Pushers (process chargé de mettre des travaux dans la file) et de Workers (des gentils travailleurs qui exécutent les travaux (Jobs) depuis la pile que vous lui avez affecté). Il est possible de prioriser certaines files par rapport à d’autre par exemple (système de comptes Premium par exemple) et de moduler le nombre de Workers dans chaque file ce qui permet de réellement moduler de façon poussée la charge. Nous ne sommes pas loin du concept de Cloud Computing des industriels.
Si vous souhaitez en savoir plus sur la mise en place de CakeResque, vous pouvez jeter un oeil sur le blog du développeur principal qui propose un excellent tutoriel :
[en] Background jobs with php and resque: part 1, introduction
Je reviendrai peut-être – en français – sur cette solution dans un prochain billet.
Que pensez vous de ces avancées ? N’hésitez pas à donner votre avis dans les commentaires 😉 Cet espace d’expression est le votre ! Bonne soirée 😛
Ping : Problème des producteurs et des consommateurs ou comment penser la scalabilité de son application
Ping : Producteurs/consommateurs : paralléliser les tâches consommatrices