Génération améliorée de fichiers PDF

Ceci est une ARCHIVE, peut-être périmée. Vérifiez bien les compatibilités !

La génération de fichiers au format PDF à partir de SPIP est une fonctionnalité intéressante, voire bientôt indispensable. Voici une amélioration, et quelques pistes supplémentaires pour la génération de ces fichiers à partir d’un site SPIP.

L’équipe dans laquelle je travaille utilise SPIP tous les jours. SPIP est employé en tant que base de connaissance en intranet, sur laquelle nous publions nos documentations techniques qui permettent le passage de connaissance dans un environnement mouvant.

L’avantage de cette utilisation de SPIP est que nous avons au bout de maintenant plus d’un an des articles qui sont suffisamment génériques pour être utilisables à l’extérieur. Et cette utilisation doit se faire sous une forme présentable, et peu facilement modifiable : le PDF.

Cet article vise deux points : décrire rapidement le fonctionnement de la génération [1]
de PDF depuis SPIP, puis de voir comment cette génération peut être améliorée : ce qui a été fait, et ce qui reste à faire.

Fonctionnement de la génération de PDF

La génération de PDF est basée sur... un squelette SPIP. Le PDF a donc actuellement une base HTML, qui est ensuite passée une classe dérivée de FPDF [2] qui se charge d’analyser le HTML généré par SPIP.

Il est important pour ce qui va suivre de savoir que ce HTML est propre. En effet, l’analyse de HTML s’appuie sur l’ouverture, mais aussi et surtout sur la fermeture des « tags » HTML. Notamment en ce qui concerne :
-  les listes (enfin pas encore, mais c’est comme ça que je le vois) ;
-  les champs <code> (on parle ici du marqueur SPIP, pas de celui de HTML) ;
-  les tableaux.

Pour plus de détails, voir les méthodes OpenTag et CloseTag de la classe PDF (fichier lib_pdf_global.php).

Pour ceux qui font de la rétro-conception (en anglais : reverse-engineering), le gros du code est dérivé des exemples de gestion de tableaux de FPDF, comme l’était le code 0.3 de la génération de PDF de Drop Zone City.

Capacités actuelles de l’extension PDF

Les capacités décrites ici s’appliquent à la version 0.4 (pourquoi pas ?) dont les sources sont joints à cet article.

A noter que cette distribution est partielle : il vous faut installer la version de Drop Zone City, puis remplacer les deux fichiers lib_pdf_global.php et lib_pdf_spip.php par ceux livrés ici. De plus, il vous faudra aussi modifier le fichier mes_fonctions.php3 avec le code fournis en annexe, et aussi remplacer le squelette de l’article par la version fournie ici (article_pdf.html).

J’ai donc fait quelques modifications sur la génération de PDF afin de :

-  gérer un peu mieux les balises SPIP &lt;code>, qui deviennent du texte entouré de marqueurs HTML <TT> ;
-  gérer les images dans le texte (ne pas oublier de n’autoriser que les formats PNG et JPEG en images dans un article, en virant de la table qui va bien les lignes qui vont aussi bien : FIXME) ;
-  gérer correctement les tableaux, pour éviter que le contenu ne déborde des cellules, et adapter la largeur des colonnes à leur contenu ;
-  gérer les balises cadre de SPIP de la même façon que les balises code (ce qui n’a pas été sans mal, cf. l’expression régulière dans mes_fonctions.php3).

A noter plusieurs choses :

-  la gestion des images gère trois cas :

  • images de largeur inférieure à 30 : pas de changement d’échelle, ou plutôt ratio fixe, c’est essentiellement bricolé pour avoir la puce SPIP dans le PDF ;
  • image de 30 à 600 pixels de large : ratio fixe (pour avoir à peu près ce qu’on avoir sur un écran web, soit 600 pixels de largeur utile) ;
  • image de taille > à 600 pixels, on taille dans le gras, et on ne fait pas de détails ;

-  si une image fait passer une page, on regarde si, à 80% de la taille, elle ne passe pas dans la page, de façon à remplir les pages (c’est un peu bête d’abattre des arbres pour 4 lignes entre deux images, surtout pour une documentation utilisateur) ;

Pour rappel, les images GIF ne sont pas gérées officiellement, avec le FPDF officiel en date d’aujourd’hui (1.51), et il n’est pas complètement souhaitable que ce le soit.

Cela dit, j’ai modifié FPDF pour utiliser les classes GIF (en PHP pur) de Yamasoft [3], et ça marche, à plusieurs conditions :

  • ne pas être pressé ;
  • que votre hébergeur ne le soit pas non plus (temps maximal d’exécution d’un script PHP) ;
  • ne pas être gourmand, ni être limité en mémoire (les images sont construite dans l’espace mémoire de PHP).

Cette modification n’est pas encore publiée officiellement sur le site de FPDF, vous l’avez ici en avant-première.

Toujours pour des limitations de PDF (plutôt que de FPDF), les images PNG avec canal alpha ne sont pas non plus supportées.

Le mieux est donc :

  • d’interdire l’utilisation du GIF (avec phpMyAdmin, ça se fait simplement en détruisant la ligne GIF dans la table spip_types_documents, par un DELETE FROM spip_types_documents WHERE extension='gif' and inclus='image') ;
  • d’ajouter la ligne suivante pour modifier la puce par défaut dans mes_fonctions.php3 : $GLOBALS['puce'] = "<img src='puce.png' align='top' alt='- ' border='0'>";. Cette image en PNG pourra comporter une couleur transparente, mais pas de canal alpha.

-  Tableaux

Les tableaux sont gérés correctement en terme de largeur, avec un algorithme un peu brut qui consiste à réduire la taille de la police tant que la largeur du tableau dépasse la largeur imprimable de la page. Si vous avez beaucoup de tableaux de largeurs différentes dans votre article, le résultat peut ne pas être très esthétique.

Les entêtes de colonne (si elles sont spécifiées avec | {{ titre }} |) sont affichés en gras sur fond jaune (choix qui n’appartient qu’à moi).

-  Listes

Les listes sont gérées correctement, y compris les listes numérotées, avec indentation. On doit même pouvoir relativement facilement modifier le type de puce en fonction de la profondeur.

-   Entités HTML

Il y avait aussi quelques problèmes avec des entités HTML qui ont maintenant été corrigées, grâce à quelques ajouts dans la fonction de filtrage pdf_first_clean.

-  Cas particulier : marqueur SPIP code

Le marqueur SPIP <code> doit être marqué dans le source SPIP avec les marqueurs HTML : &lt;code&gt; et &lt;/code&gt;.

Ils sont donc passés en HTML avec les caractères qui vont bien, et au passage dans le générateur PDF, sont vus comme des marqueurs HTML code. Ce qui nous permet d’injecter dans le PDF le texte qui va bien pour obtenir le résultat voulu (bon OK, c’est une bidouille infâme, mais ça marche ;-).

-  Autre cas particulier : marqueur SPIP cadre

Alors que SPIP fait tout le travail avec le marqueur <code>, il en va différemment avec le marqueur . En effet, SPIP le transforme en bout de formulaire HTML (<TEXTAREA> pour les intimes), et c’est ensuite le navigateur qui fait tout, et en particulier le traitement des retours chariot. Il va donc nous falloir faire quelques petites choses avant de pouvoir utiliser ce qui se trouve entre les marqueurs <TEXTAREA> pour en faire quelque chose.

Tout d’abord, rappelons que la présence d’un caractère retour chariot ou saut de line dans un fichier HTML n’aide pas à la présentation du contenu, mais uniquement du source : ils sont interprétés comme des espaces. Sauf dans quelques cas : au sein de marqueurs <PRE>, <TEXTAREA>, et quelques autres. Dans ces cas-là, et uniquement ceux-là, les caractères en question font passer à la ligne. Et l’on trouve aux environs de la ligne 68 dans lib_pdf_global.php une ligne qui remplace toutes les occurences de saut de ligne (\n) par une espace.

Il est hors de question dans le filtre SPIP pour le PDF (pdf_first_clean) de remplacer - filtre vu avant la ligne 68 pré-citée - tous les \n par des &ltBR>, cela aurait un effet désastreux. D’où l’expression régulière qui permet de trouver toutes les occurences de marqueurs HTML <TEXTAREA>, et de remplacer entre les balises de début et de fin les \n par ce qu’il faut. Gymnastique un peu tordue, mais la moins pire que j’ai pu trouvé.

Ce qui reste à faire...

En 6 mois de développement erratique, cette liste s’est amoindrie (je n’ai plus l’historique, enfin si, regardez plus haut les améliorations apportées). Il reste peu de choses à mon sens à corriger :

  • un léger décalage à droite sur les élements de liste (<LI> quoi) entre la puce et les premiers caractères de la première ligne, les lignes suivantes du même élément se recadrant un pouillème plus à gauche ;
  • il semble y avoir des problèmes avec les liens internes, mais je n’ai pas envie de fouiller (j’utilise SPIP2PDF pour éviter de donner l’accès à tout mon SPIP) ;
  • encore un peu de nettoyage à faire dans le code, histoire d’y mettre les commentaires adéquats.

En enfin une suggestion que je devrais passer sur la liste : abandonner le GIF dans SPIP comme format d’image, et le remplacer par le PNG (sans canal alpha, please).

Annexes

-   Contenu de mes_fonctions.php3

function pdf_first_clean($texte) {
	// $texte = ereg_replace("<p class[^>]*>", "<P>", $texte);
	//Translation des codes iso
	// PB avec l'utilisation de <code>
	$trans = get_html_translation_table(HTML_ENTITIES);
	$trans = array_flip($trans);
	$trans["<br />\n"] = "<BR>";	// Pour éviter que le \n ne se tranforme en espace dans les <DIV class=spip_code> (TT, tag SPIP : code)
	$trans["&#339;"] = "oe";
	$trans["&#8230;"] = "...";
	$trans["'"] = "'";
	$trans["-"] = "-";
	$trans["'"] = "'";
	$trans["""] = "\"";
	$trans["""] = "\"";
	$trans["&ucirc;"] = "û";
	$trans['$'] = '\$';
	$trans['->'] = '-»';
	$trans['<-'] = '«-';
	$texte = strtr($texte, $trans);
  		
	// Echappement des "
  	$texte = ereg_replace("\"", "\\\"", $texte);
 
	// Traitement des Espaces
 	$texte = ereg_replace("(&nbsp;| )+", " ", $texte);

	// Traitement des cadres
	$trans=array("\n" => '<BR>');
	while (preg_match('/(.*<textarea[^>]*>)(.*\n.*)(<\/textarea>.*)/ims', $texte, $textarea)) {
		$rep=strtr($textarea[2], $trans);
		$texte=$textarea[1].$rep.$textarea[3];
	}

 	return $texte;
}

INSTALLATION


-  Installer d’abord les fichiers de Drop Zone city ;
-  Puis installer les fichiers présents dans cet article. Ils vont écraser certains fichiers de DropZone City : c’est normal.

Discussion

31 discussions

  • Bonjour,

    j’ai modifié le fichier article_pdf.html en ajoutant :

    if (file_exists($file_out)) {
    clearstatcache ;
    $damo = date(YmdHi , strtotime("#DATE_MODIF")) ;
    $dage = date(YmdHi , filemtime($file_out)) ;
    if ($damo > $dage) {unlink($file_out);}
    }

    à la ligne 39, c’est à dire juste avant :

    </BOUCLE_principale>
    </B_principale>

    et juste après :

    $files_pdf = "article_".$id_article.".pdf" ;
    $file_out = $path_pdf . $files_pdf ;

    Ce qui permet, il me semble, qu’un nouveau pdf soit généré à chaque modification de l’article (avec un délai maximum d’une minute).

    En fait, le fichier est supprimé si, à la minute près, la date de dernière modification de l’article est supérieure à la date de génération du PDF présent dans le répertoire de cache. Ainsi le script fonctionne ensuite normalement, en générant un PDF s’il n’existe pas.

    Répondre à ce message

  • 2

    Ca fonctionne aussi chez moi, j’ai été plutôt impressionné par le résultat. J’ai toutefois une question concernant les images attachées comme document. Comment peut-on les faire apparaitre ? J’aimerais en fait les inclure sous forme de portfolio comme dans mon squelette (un tableau avec les vignettes). Est-ce faisable facilement ?

    Merci d’avance pour votre aide,

    • Si tu as déjà le morceau de squelette pour ce faire, essaie de rajouter ce code dans le squelette PDF. Il y a des chances que ça marche. Cependant, je ne garantis pas que les images passeront (et resterons) dans les cellules des tableaux.

    • C’est ce que j’avais essayé de faire mais effectivemet les images ne passent même si les tableaux semblent pris en compte (à l’endroit ou j’appelle le tableau contenant ces images, il y a bien une zone vide). Pourquoi les images ne s’affichent-elles pas ? par quel biais pourrais-je arriver à les afficher ?

      Merci d’avance,

    Répondre à ce message

  • 1
    Arnaud Dupin de Beyssat

    Bonjour,
    Merci pour cette contrib, qui doit être formidable... mais qui ne fonctionne qu’imparfaitement chez moi.
    J’obtiens en effet, partout, l’erreur suivante :

    Erreur : filtre « pdf_first_clean » non défini

    Pb d’installation ? De paramètres ?
    Je ne sais.
    Merci
    ADB

    • As-tu bien modifié ton fichier « mes_fonctions.php3 » afin d’y rajouter la fonction « pdf_first_clean » (cf annexe de cet article) ?

    Répondre à ce message

  • 1

    Bonjour
    Les 2 lignes suivantes de mes_fonctions
    $trans[« »« ] = « \ » » ;
    $trans[« »« ] = « \ » » ;

    génèrent systématiquement un msg d’erreur chez moi.
    Y aurait-il quelque chose à faire ?
    Car cela bloque l’affichage en pdf. Ces 2 lignes supprimées, pas de pb.
    Merci
    ADB

    • Les guillemets entre guillemets (à gauche du signe égal) sont en fait les apostrophes ouvrantes et fermantes à l’américaine “ et ” (visibles ici-même dans la barre de mise en forme des messages du forums, dits apostrophes de second niveau). Ceux-ci ne sont pas à confondre avec les guillements informatiques de nos claviers, qui n’ont pas de direction.

      Mais tu peux tout aussi bien enlever les deux lignes sans plus de soucis.

      Idem pour les deux lignes $trans["’"] = "’" ; qui pourraient éventuellement voir entre guillemets les apostrophes ouvrantes et fermantes "‘" et "’".

      Personnellement, ces quatre lignes sont absentes chez moi.

    Répondre à ce message

  • 1
    jean-marc

    Super, ca marche nickel à part un petit problème avec les images alignées à droite qui dépassent ou sont tronquées, mais bon, on s’adapte.

    Sinon j’aurais voulu savoir s’il n’était pas possible de pouvoir avoir dans le pdf les réactions à l’article(le forum) ou de faire en sorte que l’on puisse choisir de l’inclure ?

    Merci pour ce code bien sympatique

    • Ça doit être plus que faisable en ajoutant les boucles nécessaires dans le squelette (article_pdf.html). Je te laisse faire les tests...

    Répondre à ce message

  • Relire l’article, et en particulier la première annexe.

    Répondre à ce message

  • 1

    Bonjour,

    super contrib... Si pas de gif, mais un article SPIP en contient presque toujours :-(
    Les GIF par contre me font plein de misères.
    J’ai scrupuleusement suivi les indications ci-dessus en installat d’abord le package « Drop Zone city » puis par dessus le votre.

    J’ai activé tous les droits à tous les fichiers. Malheureusement j’ai toujours l’erreur suivante qui se pointe : Fatal error : Failed opening required ’gif.php’ (include_path=’’) in /home/.sites/28/site1/web/spip/lib/class_pdf/fpdf.php on line 1442

    Je tourne en rond, comment resoudre ce problème.

    Toute piste est la bienvenue

    Merci encore et viva SPIP

    RS

    • Relire l’article, et en particulier la note 3.

    Répondre à ce message

  • 1

    Bonjour et encore bravo pour cette contrib.

    Je souhaite, lorsqu’il y a une image gif dans #TEXTE, ne pas afficher la possibilité de générer un PDF. Pouvez-vous m’aider à créer cela ?

    Je vous remercie pour votre aide.

    • Bonjour,

      ça ne va pas être possible facilement (et donc ce ne sera pas moi qui pourra le faire), et pour deux raisons :
      -  les images gif peuvent venir de SPIP (comme la puce) ;
      -  elles peuvent venir des images attachées aux articles.

      Le problème, c’est qu’avant de savoir que l’on a des images gif dans une page, il faut avoir interprété le squelette (car squelette il y a). Et là, il est déjà trop tard, car il faudrait pouvoir renvoyer un REDIRECT HTTP en cas de présence d’un gif, et ça ne peut se faire que dans les en-têtes HTTP. Donc avant la génération de HTML... L’œuf et la poule en somme...

      C’est une des raisons pour laquelle il vaudrait mieux avoir un générateur de PDF qui agisse en amont, au même niveau que l’interpréteur de code SPIP qui transforme les balises SPIP en code HTML. Un jour peut-être. Mais cela nécessitera de maintenir deux moteurs séparés sur la même syntaxe, ce qui ralentira l’introduction de nouvelles balises dans SPIP. Cela nécessitera aussi l’inclusion du générateur PDF directement dans SPIP, ce ne qui ne plaira pas forcément à tout le monde, car tout le monde n’en a pas forcément besoin. KISS : keep it simple & stupid.

    Répondre à ce message

  • 1
    uuencode

    bonjour,

    J’ai un petit souci et je ne sais pas d’ou vient le problème. Pour certains documents, tout marche bien, le pdf se crée. Mais pour certains documents, j’ai un message d’erreur :
    Fatal error : Failed opening required ’gif.php’ (include_path=’’) in /var/www/html/intranet/lib/class_pdf/fpdf.php on line 1442

    D’ou peut bien provenir cette erreur ? Je ne sais pas si cela va vous aider ou vous embrouiller, mais ces pages contiennent des puces (hasard ?)

    • Bonjour,

      La réponse rapide est RTFM ou Use the source, Luke.
      La réponse appropriée est : il suffit de lire le message (ça va mieux en le disant) et l’article.
      En l’occurence, il manque un fichier .php, dont le nom est gif.php, appelé par FPDF. Une version modifiée de FPDF 1.51 permet de lire les gifs. Cherche _parsegif dans ce fichier.

      Le fichier gif.php se trouve dans l’archive ZIP en suivant le lien de la note numéro 3 de l’article.

      Quant aux puces, ainsi qu’il est dit dans l’article, si tu as gardé celles de SPIP par défaut, elles sont en GIF. Et ceci explique bien cela. D’autant plus que l’instruction PHP require_once "gif.php" n’est exécutée que si tu passes dans le code de la méthode FPDF _parsegif. C’est l’inconvénient des langages interprétés.

      Cdt,

      J.

    Répondre à ce message

  • 2
    Jean-Marc Tourreilles

    Super, ça tombe nickel pour mes besoins. Bravo pour le boulot, c’est beau, c’est pro.

    Juste un truc, la date de l’article apparait ainsi :
    samedi 22 f&eacute;vrier 2003

    Que doit-on corriger ?

    • Même réponse que ci-dessous. Remplacez les indications par le caractère ASCII qui s’affiche sur votre écran. Ne pas oublier de vider le répertoire /IMG/_article_pdf/.

    • Il manque efectivement quelques détails. Si ce n’est pas le cas, il faut ajouter un filtre aux dates, par exemple :

      (#DATE_REDAC|affdate)

      devient :

      (#DATE_REDAC|affdate|pdf_first_clean)

      dans le fichier article_pdf.html

    Répondre à ce message

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom