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 `ls $spip_dir/IMG/eps/*.eps`; ";
  $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.

updated on 27 January 2007

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.

    Reply to this 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 `ls $spip_dir/IMG/eps/*.eps`;  
      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

    Reply to this message

Ajouter un commentaire

Who are you?
[Log in]

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.

Add a document

Follow the comments: RSS 2.0 | Atom