Générer un fichier pdf à partir d’un document LaTeX

crée et attache automatiquement le pdf et le html associés à un fichier LaTeX attaché.

A propos de Latex

LaTeX est un logiciel de traitement de texte à orientation scientifique. Dans mon milieu (une fac de math), on l’utilise à la place d’OpenOffice ou d’autres suites bureautiques pour à peu près tout écrire. Mes rédacteurs sont des collègues qui écrivent des textes avec des math dedans, ils maîtrisent LaTeX et n’ont pas envie de se former à spip ou de modifier un document qu’ils ont déjà sous la forme LaTeX, simplement pour le mettre en ligne.

Ces documents sont : des feuilles de Travaux Dirigés, des notes de cours, des descriptions de contenu du cours, des notes techniques, des partitions de musique...

Je propose donc à mes rédacteurs d’attacher un fichier LaTeX et que ça compile pour eux directement des fichiers lisibles par les autres (du pdf et du html).

L’intérêt est double : 1. pas de manipulations pour eux et 2. celà tient lieu d’archivage du source (qu’ils peuvent cacher si c’est la fonctionnalité principale de la chose). Ainsi 1. les étudiants ont un document imprimable et 2. l’année suivante on sait où est le code, pas sur le disque dur de quelqu’un de qui il faut obtenir le fichier, mais mis en commun, attaché dans l’arborescence, là où on en a besoin.

Cette contribution

Cette contribution s’adresse donc à des administrateurs qui ont un accès shell (pas de site sur free par exemple), qui ont confiance dans leurs rédacteurs (LaTeX est capable de tout, c’est un vrai langage de programmation si on sait l’utiliser), lesquels désirent mettre en ligne simplement le résultat de la compilation d’un fichier LaTeX.

Ce filtre prend en entrée un id_document associé à un fichier .tex, une archive .tex.zip, ou même un .dvi ou un .ps, et fabrique le pdf associé, ainsi que le html au cas où. Le html est rarement utilisable avec latex2html, tex4ht est mieux mais plus rare.

Il suffit d’appeler le filtre dans la boucle des documents attachés, par exemple :

<BOUCLE_documents(DOCUMENTS) {id_article} {mode=document} {doublons} {par titre} {"<br />"}>
<p>
[(#LOGO_DOCUMENT|#URL_DOCUMENT)][<br />(#TITRE)][ - (#DESCRIPTIF)][(#ID_DOCUMENT|pdflatex)]
</p>
</BOUCLE_documents>

Une autre utilisation (avec plutôt tex4ht que latex2html) est, quand un article a un corps vide, de carrément le remplacer par la version html du fichier LaTeX attaché :

<B_pas_de_texte>
<br />
<BOUCLE_pas_de_texte(ARTICLES){id_article}{texte=""}>
<BOUCLE_documents_texzip(DOCUMENTS){id_article}{extension==tex|zip}{doublons}>
[(#ID_DOCUMENT|pdflatex)]
</BOUCLE_documents_texzip>
<BOUCLE_documents_html(DOCUMENTS){id_article}{extension=html}{doublons}{"<br />"}>
[(#URL_DOCUMENT|spip_file_get_contents|translate_url{#URL_DOCUMENT})]
</BOUCLE_documents_html>

<BOUCLE_documents_autres(DOCUMENTS) {id_article} {mode=document} {doublons} {par titre} {"<br />"}>
<p>
[(#LOGO_DOCUMENT|#URL_DOCUMENT)][<br />(#TITRE)][ - (#DESCRIPTIF)][(#ID_DOCUMENT|pdflatex)]
</p>
</BOUCLE_documents_autres>

</BOUCLE_pas_de_texte>
[<p>(#TEXTE)</p>]
<B_documents_joints>
<br /> Documents joints: 
<BOUCLE_documents_joints(DOCUMENTS) {id_article} {mode=document} {doublons} {par titre} {"<br />"}>
<p>
[(#LOGO_DOCUMENT|#URL_DOCUMENT)][<br />(#TITRE)][ - (#DESCRIPTIF)][(#ID_DOCUMENT|pdflatex)]
</p>
</BOUCLE_documents_joints>
</B_documents_joints>
<//B_pas_de_texte>

La première fois qu’un document .tex.zip, .tex, .dvi ou .ps est rencontré, ça crée le pdf (et le html), et ça les attache au même élément (article, rubrique ou brève). Pour un zip, il faut que le nom de l’archive soit le même que le nom du fichier à compiler, ex : monFichier.tex.zip contient monFichier.tex (et d’autres fichiers, possiblement des répertoires etc...)

On crée l’archive avec la commande (sous linux)

zip monFichier.tex.zip monDossier/*

Je n’utilise pas pdflatex mais dvipdfm qui doit être installé sur votre système.

Pour que ça fonctionne, il faut installer le filtre suivant dans mes_fonctions.php3 :

/*
 *   +----------------------------------+
 *    Nom du Filtre : PDFLaTeX                                     
 *   +----------------------------------+
 *    Date :  1 février 2005
 *    Auteur :  Christian Mercat
 *   +-------------------------------------+
 *    Fonctions de ce filtre :
 *    Production de fichiers PDF et html
 *    à partir de fichiers attachés LaTeX, 
 *    archives tex.zip, dvi ou ps.
 *   +-------------------------------------+ 
 *  
 * Pour toute suggestion, remarque, proposition d'ajout
 * reportez-vous au forum de l'article :
 * http://www.spip-contrib.net/article848.html
*/
function pdflatex($id_document) {
  // return ""; // Pour désactiver fissa
   $spip_dir = "/var/www/html/SPIP"; // A modifier.
 
  $query = "SELECT id_type, titre, descriptif, fichier FROM spip_documents WHERE id_document=$id_document";
  $result = spip_query($query);
  if (!($row = spip_fetch_array($result))) // Mauvais argument.
    return ""; // Echec silencieux

  $id_type = $row['id_type'];
  $titre = addslashes($row['titre']);
  $descriptif = addslashes($row['descriptif']);
  $nom = $row['fichier'];
    
  $fichier =  "$spip_dir/$nom";

  $nom_court = ereg_replace
    ("IMG/(tex|zip|dvi|ps)/(.*)\.(tex|tex\.zip|dvi|ps)",
     "\\2",$nom);

  if((ereg("([.][.]|/|\"|'|[*])",$nom_court)) || // Nom bizarre: hack?
     (($id_type != 52) && // zip 
      ($id_type != 46) && // tex
      ($id_type != 29) && // dvi
      ($id_type != 37)))  // ps // Mauvais types
    return "";

  // Comparer les dates
  $dtex = date(YmdHi , filemtime($nom)) ;
  $dpdf = @file_exists("IMG/pdf/$nom_court.pdf") ? date(YmdHi , filemtime("IMG/pdf/$nom_court.pdf")) : 0;
  spip_log("pdflatex($id_document): TeX: $dtex pdf: $dpdf");
  if ($dpdf > $dtex) return ""; // Rien à faire.


  // Attaché à un article, une rubrique ou une brève?
  $type = "";

  $query = "SELECT id_article FROM spip_documents_articles WHERE id_document=$id_document";
  $result = spip_query($query);
  if ($row = spip_fetch_array($result)) {
    $type = "article";  
    $id_truc = $row["id_$type"];
  }

  if(!$type){
    $query = "SELECT id_rubrique FROM spip_documents_rubriques WHERE id_document=$id_document";
    $result = spip_query($query);
    if ($row = spip_fetch_array($result)) {
      $type = "rubrique";  
      $id_truc = $row["id_$type"];
    }
  }
  if(!$type){
    $query = "SELECT id_breve FROM spip_documents_breves WHERE id_document=$id_document";
    $result = spip_query($query);
    if ($row = spip_fetch_array($result)) {
      $type = "breve";  
      $id_truc = $row["id_$type"];
    }
  }

  spip_log("pdflatex($id_document):  fichier: $fichier, Nom court: $nom_court, id_type: $id_type, Id: $id_truc");


  // Il faudrait peut-être randomiser. Ce n'est pas dans le site web donc bon.
  $tmp_dir = "/tmp/spipLaTeX".$id_document;
 
  $commande = "mkdir $tmp_dir; cd $tmp_dir; ";
  $commande .= "for f in <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+bHMgJHNwaXBfZGlyL0lNRy9lcHMvKi5lcHM8L2NvZGU+"></span>; ";
  $commande .= "do test -f \${f##*/} || ln -s \$f .; done; ";
  $commande .= "ln -s '$fichier' .;";
  spip_log($commande);
  exec($commande);

  switch ($id_type) {
  case 52: // zip
    if(!ereg("\.tex\.zip$",$nom)) return "";
    require_once(_DIR_RESTREINT . 'pclzip.lib.php');
    $archive = new PclZip($fichier);
    $archive->extract(PCLZIP_OPT_PATH, $tmp_dir, PCLZIP_OPT_REMOVE_ALL_PATH);
    // Continue avec le tex
  case 46: // tex
    // Créer un dvi
    $commande = "cd $tmp_dir; nice latex  -interaction=batchmode '$nom_court' 2> /dev/null;";
    spip_log($commande);
    exec($commande);
    exec($commande); // Compiler deux fois pour les références croisées.
 
    if (!@file_exists("$tmp_dir/$nom_court.dvi")) {// simple TeX?
  $commande = "cd $tmp_dir; nice tex  -interaction=batchmode '$nom_court' 2> /dev/null;";
    spip_log($commande);
    exec($commande);
    exec($commande); // Compiler deux fois pour les références croisées.
      }
    $dossier_html = "IMG/html/$nom_court";

    // Tester si un document différent existe avec ce nom:

    $query = "SELECT d.id_document FROM spip_documents as d, spip_documents_articles as a, spip_documents_rubriques as r, spip_documents_breves as b WHERE d.id_type=33 AND d.fichier=\"$dossier_html\" AND ((d.id_document = a.id_document AND (a.id_article<>$id_truc  OR '$type'<>'article')) OR (d.id_document = r.id_document AND (r.id_rubrique<>$id_truc OR '$type'<>'rubrique')) OR (d.id_document = b.id_document AND (b.id_breve=$id_truc  OR '$type'<>'breve')))";
    $result = spip_query($query);
    if (!spip_fetch_array($result)) { // Non, la place est libre.
      // Effacer le répertoire et le créer avec latex2html
      // tex4ht est beaucoup mieux, l'utiliser si vous l'avez
      $commande = "cd $tmp_dir; rm -rf '$nom_court';  export \$LINKPOINT='\"index.html\"'; nice latex2html -local_icons '$nom_court' 2> /dev/null; ";
      spip_log($commande);
      exec($commande);
      // html ?

      if (@file_exists($tmp_dir."/".$nom_court)) {
	// Bouger
	$commande = "rm -rf '$spip_dir/IMG/html/$nom_court'; mv '$tmp_dir/$nom_court' '$spip_dir/IMG/html/'";
	spip_log($commande);
	exec($commande);

	// Insérer/changer dans la base
	$query = "SELECT id_document FROM spip_documents WHERE id_type=33 AND fichier='$dossier_html'";
	$result = spip_query($query);
	$row = spip_fetch_array($result);
	$id_document_html = $row['id_document'];
	if (!$id_document_html) {
	  $query = "INSERT INTO spip_documents (id_type, titre, descriptif, fichier, mode, date, taille) VALUES (33, '$titre', '$descriptif',  '$dossier_html', 'document', NOW(), 1)";
	  spip_query($query);
	  $query = "SELECT id_document FROM spip_documents WHERE id_type=33 AND fichier='$dossier_html'";
	  $result = spip_query($query);
	  $row = spip_fetch_array($result);
	  $id_document_html = $row['id_document'];
	}
	else {
	  $query = "UPDATE spip_documents SET titre='$titre', descriptif='$descriptif',  mode='document', date=NOW(), taille=$taille WHERE id_document = $id_document_html";
	  spip_query($query);
	}

	// Lier le document au "truc"
	$query = "SELECT id_document FROM spip_documents_".$type."s WHERE id_document=$id_document_html AND id_".$type."=$id_truc";
	$result = spip_query($query);
	$row = spip_fetch_array($result);
	if (!$row['id_document']) {
	  $query = "INSERT INTO spip_documents_".$type."s (id_document, id_".$type.") VALUES ($id_document_html, $id_truc)";
	  if($id_document_html && $id_truc)  spip_query($query);
	  else { // Problème! Mieux vaut tout enlever.
	    spip_query("DELETE FROM spip_documents WHERE id_type=33 AND fichier='$dossier_html'");
	    $commande = "rm -rf '$spip_dir/IMG/html/$nom_court'";
	    spip_log($commande);
	    exec($commande);
	  }
	}
      }
    }
    // Continue avec le dvi
  case 29: // dvi

    // Créer un pdf s'il n'existe pas.
    $commande = "cd $tmp_dir; test -f '$nom_court.pdf' || nice dvipdfm '$nom_court.dvi' 2> /dev/null; ";
    spip_log($commande);
    exec($commande);

    // Créer un ps si le pdf a échoué et si le ps n'existe pas.
    $commande = "cd $tmp_dir; test -f '$nom_court.pdf' || test -f '$nom_court.ps' || nice dvips -o '$nom_court.ps' '$nom_court.dvi' 2> /dev/null; ";
    spip_log($commande);
    exec($commande);

    // Continue avec le ps
  case 37: // ps

    // Créer un pdf s'il n'existe pas.
    $commande = "cd $tmp_dir; test -f '$nom_court.pdf' || nice ps2pdf '$nom_court.ps' 2> /dev/null; ";
    spip_log($commande);
    exec($commande);
    // pdf ?
    $fichier_pdf = "IMG/pdf/$nom_court.pdf";
    $fichier_ps = "IMG/ps/$nom_court.ps";

    if (@file_exists($tmp_dir."/$nom_court.pdf")) { 

      // Tester si un document différent existe avec ce nom:

      $query = "SELECT d.id_document FROM spip_documents as d, spip_documents_articles as a, spip_documents_rubriques as r, spip_documents_breves as b WHERE d.id_type=35 AND d.fichier='$fichier_pdf' AND ((d.id_document = a.id_document AND (a.id_article<>$id_truc  OR '$type'<>'article')) OR (d.id_document = r.id_document AND (r.id_rubrique<>$id_truc OR '$type' <>'rubrique')) OR (d.id_document = b.id_document AND (b.id_breve=$id_truc  OR '$type' <>'breve')))";
      $result = spip_query($query);
      if (!spip_fetch_array($result)) { // Non, la place est libre.

	// Bouger
	$commande = "mv -f '$tmp_dir/$nom_court.pdf' '$spip_dir/$fichier_pdf'";
	spip_log($commande);
	exec($commande);

	// Insérer/changer dans la base
	$query = "SELECT id_document FROM spip_documents WHERE id_type=35 AND fichier='$fichier_pdf'";
	$result = spip_query($query);
	$row = spip_fetch_array($result);
	$id_document_pdf = $row['id_document'];
	$taille = @filesize("$spip_dir/$fichier_pdf");
	if (!$id_document_pdf) {
	  $query = "INSERT INTO spip_documents (id_type, titre, descriptif, fichier, mode, date, taille) VALUES (35, '$titre', '$descriptif',  '$fichier_pdf', 'document', NOW(), $taille)";
	  spip_query($query);
	  $query = "SELECT id_document FROM spip_documents WHERE id_type=35 AND fichier='$fichier_pdf'";
	  $result = spip_query($query);
	  $row = spip_fetch_array($result);
	  $id_document_pdf = $row['id_document'];
	}
	else {
	  $query = "UPDATE spip_documents SET titre='$titre', descriptif='$descriptif',  mode='document', date=NOW(), taille=$taille WHERE id_document = $id_document_pdf";
	  spip_query($query);
	}

	// Lier le document au "truc"
	$query = "SELECT id_document FROM spip_documents_".$type."s WHERE id_document=$id_document_pdf AND id_".$type."=$id_truc";
	$result = spip_query($query);
	$row = spip_fetch_array($result);
	if (!$row['id_document']) {
	  $query = "INSERT INTO spip_documents_".$type."s (id_document, id_".$type.") VALUES ($id_document_pdf, $id_truc)";
	  if($id_document_pdf)  spip_query($query);
	  else { // Problème! Mieux vaut tout enlever.
	    spip_query("DELETE FROM spip_documents WHERE id_type=35 AND fichier='$fichier_pdf'");
	    $commande = "rm -rf '$spip_dir/$fichier_pdf'";
	    spip_log($commande);
	    exec($commande);
	  }
	}
      }
    } elseif (@file_exists($tmp_dir."/$nom_court.ps")) {

      // Tester si un document différent existe avec ce nom:

      $query = "SELECT d.id_document FROM spip_documents as d, spip_documents_articles as a, spip_documents_rubriques as r, spip_documents_breves as b WHERE d.id_type=37 AND d.fichier='$fichier_ps' AND ((d.id_document = a.id_document AND (a.id_article<>$id_truc  OR '$type' <>'article')) OR (d.id_document = r.id_document AND (r.id_rubrique<>$id_truc OR '$type' <>'rubrique')) OR (d.id_document = b.id_document AND (b.id_breve=$id_truc  OR '$type' <>'breve')))";
      $result = spip_query($query);
      if (!spip_fetch_array($result)) {

	// Bouger
	$commande = "mv -f '$tmp_dir/$nom_court.ps' '$spip_dir/$fichier_ps'";
	spip_log($commande);
	exec($commande);
	// Insérer dans la base
	$query = "SELECT id_document FROM spip_documents WHERE id_type=37 AND fichier='$fichier_ps'";
	$result = spip_query($query);
	$row = spip_fetch_array($result);
	$id_document_ps = $row['id_document'];
	$taille = @filesize("$spip_dir/$fichier_s");
	if (!$id_document_ps) {
	  $query = "INSERT INTO spip_documents (id_type, titre, descriptif, fichier, mode, date, taille) VALUES (35, '$titre', '$descriptif',  '$fichier_ps', 'document', NOW(), $taille)";
	  $query = "SELECT id_document FROM spip_documents WHERE id_type=37 AND fichier='$fichier_ps'";
	  $result = spip_query($query);
	  $row = spip_fetch_array($result);
	  $id_document_ps = $row['id_document'];
	}
	else $query = "UPDATE spip_documents SET titre='$titre', descriptif='$descriptif',  mode='document', date=NOW(), taille=$taille WHERE id_document = $id_document_ps";
	spip_query($query);
	
	// Lier le document au "truc"
	$query = "INSERT INTO spip_documents_".$type."s (id_document, id_".$type.") VALUES ($id_document_ps, $id_truc)";
	if($id_document_ps)  spip_query($query);
	else { // Problème! Mieux vaut tout enlever.
	  spip_query("DELETE FROM spip_documents WHERE id_type=37 AND fichier='$fichier_ps'");
	  $commande = "rm -rf '$spip_dir/$fichier_ps'";
	  spip_log($commande);
	  exec($commande);
	}
      }
    }
    break;
  default: return "";
  }

  $commande = "rm -rf $tmp_dir 2> /dev/null;";
  spip_log($commande);
  exec($commande);  
}

function translate_url($texte, $url) {

  $url = ereg_replace("/[^/]*$","/",$url);
 $texte = ereg_replace("((SRC|HREF)=(\"|')?)", "\\1$url", $texte);
  return ereg_replace("$url(http://|mailto)","\\1", $texte);
}

C’est un peu brut de décoffrage, mais ça fonctionne, les commentaires sont les bienvenus.

Le filtre translate_url sert à faire pointer les liens relatifs à l’intérieur du dossier de départ. Il y a sûrement mieux.

On peut bien-sûr utiliser cette idée pour post-traiter automatiquement d’autres types de documents attachés et leur faire subir les pires outrages.

Une contrib autour du même sujet, qui permet de faire une sortie pdf à partir d’un article ou d’une rubrique en utilisant LaTeX. Je l’utilise également pour fabriquer un fichier LaTeX que je peux ensuite éditer de manière à fabriquer rapidement un document très propre qui reprend les informations du site.

Discussion

2 discussions

  • Je cherche une classe ou une fonction PhP qui permettrait la création de fichier PDF à partir d’un fichier LaTeX indépendemment de SPIP (en utilisant EasyPhP en local par exemple). Etant « débutant » mais motivé pour apprendre, je souhaitrais savoir si le code ci-dessus contient ce qu’il faut pour arriver à mon but.

    Par avance merci.

    Répondre à ce message

  • 2

    Bravo, ca répond à un vrai besoin. As-tu essayé de carrément remplacer le champ TEXTE par le HTML généré plutot que de placer celui-ci en pièce jointe ?

    • Oui, c’est ce que ça fait : ça attache le html puis ça insère le html à la place du texte, c’est ce que cette boucle fait :

      <BOUCLE_documents_html(DOCUMENTS){id_article}{extension=html}{doublons}{"<br />"}>
      [(#URL_DOCUMENT|spip_file_get_contents|translate_url{#URL_DOCUMENT})]
      </BOUCLE_documents_html>

      Le petit problème c’est qu’il faut recalculer 2 fois...

      À propos des images aussi, j’étais passé à côté de la commande LaTeX

      \usepackage{graphicx,color}
      \graphicspath{{$spip_dir/IMG/eps/}{$spip_dir/IMG/pdf/}{$spip_dir/IMG/png/}%
      {$spip_dir/IMG/ps/}{$spip_dir/IMG/jpg/}{$spip_dir/IMG/}{./}} 
      % pratique: inclure ici une liste de répertoires {{rep1/}{rep2/}...{repn/}}
      \DeclareGraphicsExtensions{eps, pdf, png,  ps, jpg}

      plutôt que le (couteux)

      for f in <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+bHMgJHNwaXBfZGlyL0lNRy9lcHMvKi5lcHM8L2NvZGU+"></span>;  
      do test -f \${f##*/} || ln -s \$f .; 
      done;  
      ln -s '$fichier' .;

      On m’a demandé aussi si c’était possible sur un serveur windows. En installant cygwin et en faisant un .bat qui va bien (ne me posez pas de question là-dessus), peut-être...

      En espérant que ça serve.

    • Bonjour Monsieur,

      Je cherche un example tres simple d’incorporation de formules mathematiques dans un site Web. J’apprecierais enormement le plus banal des exemples, pourvu qu’il presente tous les etapes elementaires.
      Merci
      Ernest

    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