Inclusion de formules TeX/LaTeX
La technique par génération d'images
Solution de WikiMedia?
En s'inspirant
beaucoup de Mediawiki, le moteur de
WikiPedia, on peut supporter simplement les formules
TeX. Suivant que la formule est simple (i_2) ou compliquée (\sum_i...), le rendu se fera en HTML directement, ou sous la forme d'une image png (installation
LaTeX requise côté serveur). J'ai repris mediawiki dans wikini pour supporter ce genre d'écritures : \( i_2 \), et \[ i_2 \]. Le premier est associé à une classe CSS "math_inline", le second "math_center", pour pouvoir émuler la mise en page
LaTeX. Dernier détail : il y a un mécanisme de cache pour stocker les images/codes html générés pour accélérer le chargement des pages.
Marche à suivre :
- avoir installé wikini, LaTeX, ghostcript sur le serveur wikini.
- sur le serveur ou toute autre machine où Objective Caml est installé, récupérer le répertoire "math" du cvs de mediawiki (http://cvs.sourceforge.net/viewcvs.py/wikipedia/phase3/math/). Le compiler (make), ce qui générera 3 binaires, dont texvc. texvc est le moteur de rendu TeX -> HTML ou MathML en Caml. Le README explique le format qu'il sort.
- mettre le binaire texvc dans formatters/ du wikini sur le serveur.
- mettre le script suivant dans formatters/ de wikini et l'appeler math.php (repris quasiment texto du script includes/Math.php de mediawiki).
<?
/*
* Almost entirely taken from mediawiki :
* http://cvs.sourceforge.net/viewcvs.py/wikipedia/phase3/includes/
*/
if (!function_exists("wfEscapeHTML"))
{
function wfEscapeHTML( $in )
{
return str_replace(
array( "&", "\"", ">", "<" ),
array( "&", """, ">", "<" ),
$in );
}
}
if (!function_exists("linkToMathImage"))
{
function linkToMathImage ($wgMathPath, $tex, $outputhash )
{
global $wiki;
return "<img src=\"".$wgMathPath."/"
.$outputhash.$wiki->config["ghostscript_png_ext"]
."\" alt=\"".wfEscapeHTML($tex)."\">";
}
}
if (!function_exists("renderMath"))
{
function renderMath( $tex )
{
global $wiki;
$mf = "math_failure";
$munk = "math_unknown_error";
$math_dir_url = $wiki->config["math_dir_url"];
$math_dir_sys = $wiki->config["math_dir_sys"];
$math_tmp_dir = $wiki->config["math_tmp_dir"];
$math_inputenc = $wiki->config["math_inputenc"];
$math_render_type = $wiki->config["math_render_type"];
/* 0 : "Toujours produire une image PNG",
1 : "HTML si tres simple, autrement PNG",
2 : "HTML si possible, autrement PNG",
3 : "Laisser le code TeX original",
4 : "Pour les navigateurs modernes" (mathml) */
if ($math_render_type == 3)
return ('$ '.wfEscapeHTML($tex).' $');
$md5 = md5($tex);
$md5_sql = mysql_escape_string(pack("H32", $md5));
if ($math_render_type == 0)
$sql = "SELECT math_outputhash FROM ".$wiki->config["table_prefix"]
."math WHERE math_inputhash = '".$md5_sql."'";
else
$sql = "SELECT math_outputhash,math_html_conservativeness,math_html FROM ".$wiki->config["table_prefix"]."math WHERE math_inputhash = '".$md5_sql."'";
$res = $wiki->Query($sql);
if( $rpage = mysql_fetch_object( $res ) ) {
$outputhash = unpack( "H32md5",
$rpage->math_outputhash
. " " );
$outputhash = $outputhash ['md5'];
if( file_exists( "$math_dir_sys/$outputhash"
.$wiki->config["ghostscript_png_ext"] ) )
{
if (($math_render_type == 0)
|| ($rpage->math_html == '')
|| (($math_render_type == 1)
&& ($rpage->math_html_conservativeness != 2))
|| (($math_render_type == 4)
&& ($rpage->math_html_conservativeness == 0)))
return linkToMathImage ( $wiki->config["math_dir_url"],
$tex, $outputhash );
else
{
return $rpage->math_html;
}
}
}
$cmd = $wiki->config["math_texvc_path"]." "
.escapeshellarg($math_tmp_dir)." "
.escapeshellarg($math_dir_sys)." "
.escapeshellarg($tex)." ".escapeshellarg($math_inputenc);
echo $cmd;
$contents = `$cmd`;
if (strlen($contents) == 0)
return "<b>".$mf." (".$munk." 1): ".wfEscapeHTML($tex)."</b>";
$retval = substr ($contents, 0, 1);
if (($retval == "C") || ($retval == "M") || ($retval == "L")) {
if ($retval == "C")
$conservativeness = 2;
else if ($retval == "M")
$conservativeness = 1;
else
$conservativeness = 0;
$outdata = substr ($contents, 33);
$i = strpos($outdata, "\000");
$outhtml = substr($outdata, 0, $i);
$mathml = substr($outdata, $i+1);
$sql_html = "'".mysql_escape_string($outhtml)."'";
$sql_mathml = "'".mysql_escape_string($mathml)."'";
} else if (($retval == "c") || ($retval == "m") || ($retval == "l")) {
$outhtml = substr ($contents, 33);
if ($retval == "c")
$conservativeness = 2;
else if ($retval == "m")
$conservativeness = 1;
else
$conservativeness = 0;
$sql_html = "'".mysql_escape_string($outhtml)."'";
$mathml = '';
$sql_mathml = 'NULL';
} else if ($retval == "X") {
$outhtml = '';
$mathml = substr ($contents, 33);
$sql_html = 'NULL';
$sql_mathml = "'".mysql_escape_string($mathml)."'";
$conservativeness = 0;
} else if ($retval == "+") {
$outhtml = '';
$mathml = '';
$sql_html = 'NULL';
$sql_mathml = 'NULL';
$conservativeness = 0;
} else {
if ($retval == "E")
$errmsg = wfMsg( "math_lexing_error" );
else if ($retval == "S")
$errmsg = wfMsg( "math_syntax_error" );
else if ($retval == "F")
$errmsg = wfMsg( "math_unknown_function" );
else
$errmsg = $munk." ".$retval;
return "<h3>".$mf." (".$errmsg.substr($contents, 1)."): "
.wfEscapeHTML($tex)."</h3>";
}
$outmd5 = substr ($contents, 1, 32);
if (!preg_match("/^[a-f0-9]{32}$/", $outmd5))
return "<b>".$mf." (".$munk." 3): ".wfEscapeHTML($tex)."</b>";
$outmd5_sql = mysql_escape_string(pack("H32", $outmd5));
$sql = "REPLACE INTO ".$wiki->config["table_prefix"]."math VALUES ('"
.$md5_sql."', '".$outmd5_sql."', ".$conservativeness.", ".$sql_html
.", ".$sql_mathml.")";
$res = $wiki->Query($sql);
# we don't really care if it fails
if (($math_render_type == 0) || ($rpage->math_html == '')
|| (($math_render_type == 1) && ($conservativeness != 2))
|| (($math_render_type == 4) && ($conservativeness == 0)))
return linkToMathImage($wiki->config["math_dir_url"],
$tex, $outmd5);
else
return $outhtml;
}
}
echo renderMath($text);
?>
- Rajouter un répertoire math_img accessible en écriture par le serveur http, par exemple à la racine de wikini.
- Pour personnaliser la config, rajouter dans le wakka.config.php (en l'incluant dans la fonction array);
/* Chemin HTTP pour math_img */
"math_dir_url" => "/wikini/math_img",
/* Chemin système pour math_img */
"math_dir_sys" => "/var/httpd/htdocs/wikini/math_img",
/* Chemin système vers l'exécutable texvc */
"math_texvc_path" => "/var/httpd/htdocs/wikini/formatters/texvc",
/* Répertoire système pour les fichiers temporaires */
"math_tmp_dir" => "/tmp",
"math_inputenc" => "UTF-8",
"math_render_type" => "1",
/* Valeurs possibles pour math_render_type :
0 : "Toujours produire une image PNG",
1 : "HTML si tres simple, autrement PNG",
2 : "HTML si possible, autrement PNG",
3 : "Laisser le code TeX original",
4 : "Pour les navigateurs modernes" (mathml) */
"ghostscript_png_ext" => ".png",
/* Valeurs possibles pour ghostscript_png_ext :
Versions anciennes de ghostscript : ".png.0"
Versions récentes de ghostscript : ".png" */
- Modifier le script formatters/wakka.php du wikini :
@@ -117,6 +119,26 @@
{
return $matches[1];
}
+ // \(math\)
+ else if (preg_match("/^[\\\\]\\((.*)[\\\\]\\)$/s",
+ $thing, $matches))
+ {
+ $output = "<div class=\"math_inline\">";
+ $output .= $wiki->Format(trim($matches[1]), "math");
+ $output .= "</div>";
+
+ return $output;
+ }
+ // \[math\]
+ else if (preg_match("/^[\\\\]\\[(.*)[\\\\]\\]$/s",
+ $thing, $matches))
+ {
+ $output = "<div class=\"math_center\">";
+ $output .= $wiki->Format(trim($matches[1]), "math");
+ $output .= "</div>";
+
+ return $output;
+ }
// code text
else if (preg_match("/^\%\%(.*)\%\%$/s", $thing, $matches))
{
@@ -278,6 +300,8 @@
$text = trim($text)."\n";
$text = preg_replace_callback(
"/(\%\%.*?\%\%|".
+ "[\\\\]\\[.*?[\\\\]\\]|".
+ "[\\\\]\\(.*?[\\\\]\\)|".
"\"\".*?\"\"|".
"\[\[.*?\]\]|".
"\b[a-z]+:\/\/\S+|".
- créer la table wikini_math suivante dans la base wikini sur mysql. Elle permet de maintenir en cache la liste des images déjà générées pour accélérer le chargement d'une parge :
- Dans le wakka.css rajouter le nécessaire pour la mise en page des deux types de formules maths :
div.math_inline { display: inline; }
div.math_center { text-align: center; }
Et voilà.
--
DavidDecotigny
Solution de Spip: le mode client-serveur
Références:
- la page de description sur le wiki spip-contrib
- la page sur le wiki de rezo.net
Cette solution nécessite évidemment d'avoir accès à un serveur pour générer les images. Spip propose
math.spip.org/tex.php mais idéalement il faudrait faire tourner le script sur un serveur dédié à
WikiNi je pense...
NB.: la solution n'est pas encore adaptée à
WikiNi,
SpikiNi utilise le formateur de Spip, et il n'est donc pas possible de transposer directement le code sous
WikiNi...
--
LordFarquaad
Proposition php pur
Je trouve que le fait de devoir utiliser un logiciel externe à php n'est vraiment pas pratique: non seulement cela ne fonctionne que sous GNU/Linux, mais en plus cela nécessite les droits d'exécution depuis php. En pratique il n'y a pratiquement aucun hébergeur qui accepte cela... (question de sécurité)
La solution à ce problème est une gestion complète via php. Au niveau de
WikiNi, je pense que le mieux est la création d'une action, qui serait en même temps capable de générer l'image:
- Côté utilisateur, elle fonctionnerait (par exemple) comme ceci: {{math formule="\frac{9 \cdot a_1^3}{b}"}}
- A noter que l'utilisateur devra faire attention à ne pas placer deux accolades fermantes d'affilée. Placer un espace entre ne devrait normalement pas poser de problème.
- Côté programmation, le fichier de l'action aurait deux modes de fonctionnement (ou alors il faudrait deux fichiers)
- en tant qu'action, il s'agirait juste de placer une image dans la page, quelque chose du genre
- <img src="actions/math.php?formule="La formle, urlencodée" title="..."> (pour le title, je propose soit simplement "formule mathématique", soit la formule telle quelle en LaTeX)
- en tant qu'"image", elle travaillerait de façon modulaire: tout d'abord elle parse la chaine pour repérer les appels de fonctions, ensuite elle inclut d'autres fichiers, contenant (à voir) des classes ou des fonctions servant à génerer de plus petites images. Le travail est ainsi divisé en de plus petites tâches, dans mon exemple:
- On évalue le premier couple d'accolades: 9 \cdot a_1^3:
- des fonctions internes permettent de générer des images pour une chaine de carractère (à la bonne taille), ainsi que les indices et les exposants
- On appèle une fonction modulaire, par exemple Latexcdot, qui va générer une image du point
- On recolle les trois images ensemble.
- On évalue le deuxième couple d'accolades pour obtenir une image de "b"
- On passe les deux images générées en argument d'une fonction modulaire, par exemple Latexfrac, qui s'occupe entièrement de générer une nouvelle image sur base de celles-ci.
- Il est certain que ce ne sera pas aussi beau que de vraies images LaTeX, et que ça pourrait dans certains cas s'avérer assez lourd (je n'ai aucune idée des performances de php en matière de création dimage, mais je ne pense pas qu'elles soient vraiment mauvaises...), mais au moins ça fonctionnera, normalement, chez tous les hébergeurs. Par ailleurs rien n'empêche de mettre les images générées en cache (par exemple dans des fichiers dont le nom est la formule, encodée en base 64). (l'idéal étant que ces images ne soient pas mises en cache lors de la prévisualisation, mais seulement lors du premier affichage)
- Je n'ai aucune d'idée de la faisabilité, il est d'ailleurs possible que certaines fonctions LaTeX soient vraiment difficiles à rendre sur base de ce modèle. Il faudrait aussi vérifier la synthaxe des fonctions math, par exemple si les crochets sont parfois utilisés etc.
- Cela m'intéresserait pas mal de me lancer là-dedans, mais PatrickPaul voulait aussi apparemment travailler là-dessus (voir EnCoursDeDeveloppement) et il ne précise ni ce qu'il a fait, ni où il en est... -- LordFarquaad
Autre possibilité: le MathML
Avantages
Le
MathML présente de nombreux avantages par rapport à l'utilisation d'images, notamment:
- Une description sous forme de texte, permettant ainsi une utilisation à des fins de recherche et d'édition, et donnant un véritable sens sémantique aux formules
- Une meilleure accessibilité pour les personnes ayant des difficultés visuelles
- Un rendu non-fixe: l'apparence des formules peut être adaptée au reste de la page et au préférences de l'utilisateur
- Une éconmie pour les serveurs: moins de charge (génération plus simple et non-utilité de la mise en cache des formules générées) et d'utilisation de la bande passante (pour des sites fortements utilisés)
Aspects techniques
La mise en oeuvre du
MathML n'est pas une chose simple.
- Tout d'abord il faut permettre au navigateur de détecter la présence dans la page de MathML. Pour ce faire il faut envoyer le bon Content-type dans les entêtes, à savoir application/xhtml+xml. Cependant certains navigateurs risquent de ne pas le reconnaître et de proposer à l'utilisateur de télécharger le fichier plutôt que de l'afficher. Ceci implique donc qu'il faudra procéder, malheureusement, à une discrimination, avec les risques d'erreur que cela comporte...
- On verra à ce sujet la page Serve it Up! pour plus de détails
- Ensuite il faut générer le MathML. Clairement on ne peut pas demander à un non-initié d'écrire directement en MathML. Il existe pour ce faire certains logiciels/scripts permettant de le gérer de manière WYSYWYG, mais notre but dans le cas présent serait de transformer du code LaTeX, très connu des scientifiques, des étudiants et de certains professionnels, en MathML. Il existe déjà pour ce faire le logiciel-script qui est disponible sous license GPL ce qui nous convient parfaitement. Cependant il a un gros problème: ce script gère les équations de manière trop simpliste
- en considérant que les fonctions ont toutes le même nom en LaTeX qu'en MathML, ce qui n'est pas du tout le cas
- en ne vérifiant pas la validité des équations introduites, et dès lors rend la page inaffichable à la moindre erreur de l'utilisateur...
- Il serait donc possible de se baser sur ce script, mais mes premières tentatives se sont avérées peu fructueuses à cause de ces deux points...
Problèmes
- La page doit être "parfaitement" conforme aux standards XML du W3C, sous peine de ne pas être affichée du tout. Les tests effectués sous Mozilla/FireFox 1.0 et sous IE6/MathPlayer? 2.0 révèlent que les deux navigateurs refusent d'afficher la page à la moindre erreur... mais que certaines choses (notemment d'après mes souvenirs au niveau des \inf ou \infty: est-ce &inf; ou &infty; qui est correct ? sous LaTeX les deux existent, je ne connais pas la différence faute d'avoir testé...) passent sous Firefox et non sous IE, et vice versa... (d'où le "parfaitement"). Par ailleurs WikiNi ne garantit pas parfaitement la validité XML, pour plusieurs raisons:
- L'utilisateur peut inverser certaines balises de formattage, ou oublie d'en fermer
- L'utilisateur peut inclure du code XHTML arbitraire (et donc éventuellement non-valide) en utilisant les guillemets doubles.
--
LordFarquaad