Alternative De Texvc PHP

This page is a translated version of the page Texvc PHP Alternative and the translation is 100% complete.

Vue d'ensemble

MediaWiki utilise un programme supplémentaire, Texvc, pour transformer les expressions mathématiques en des images, ce qui est plus pratique pour l'affichage. Malheureusement, texvc doit être compilé et est écrit en Ocaml, ce qui peut causer un problème pour la majorité des hôtes publics même si toutes les autres conditions sont remplies (ghostscript, latec, etc). Ce contournement remplace le code de présentation de maths par une version PHP intégrée, effectuant toutes les manipulations d'images, les rendus, etc. que Texvc ferait normalement.

Limitations

Ce "hack" ne vous dispense, pas d'utiliser "latex", "dvips", et "convert" (ces trois commandes unix sont nécessaires), mais d'avoir à obtenir l'accès pour pouvoir installer Ocaml et pour compiler texvc. Bien que ce hack conserve la mise en cache de l'image, la plupart des autres fonctionnalités disponibles pour le rendu mathématique ont été perdues et de légères différences apparaissent entre l'image finale et l'image de sortie. Une différence notable est que seules des images sont produites, le projet MyMCAT ne s'intéressait pas aux sorties MathML ou texte des formules, et ces options ont donc été supprimées.

Comment ça marche

En gros, dans Math.php, la fonction d'affichage ne comporte plus d'appels à texvc et PHP fait tous les appels directement aux fonctions nécessaires.

Comment l'installer

Voici ci-dessous une copie de la source dans Math.php. Vous devrez vous assurer que vous avez un répertoire en état de marche et que les commandes shell fonctionnent correctement pour que ceci marche. Si vous avez précédemment essayé d'installer texvc, il faut remplacer le Math.php dans includes/Math.php. Si il n'existe pas encore, vous devriez le créer avec les éléments ci-dessous.ßß

La fonction d'affichage passe toujours ses arguments, mais ils ne sont plus lus. Si quelqu'un veut mettre à jour ce script avec des fonctionnalités plus sophistiquées, envoyez-moi un email, j'aimerais beaucoup en savoir plus.
<?php
/**
 * Contain everything related to <math> </math> parsing
 */

class MathRenderer {
	
	//shell programs:
	var $LATEX_PATH = "/usr/bin/latex";
	var $DVIPS_PATH = "/usr/bin/dvips";
	var $CONVERT_PATH = "/usr/bin/convert";
	
	//image url stuff
 	var $URL_PATH = "http://www.mymcat.com/testing/cache";

	var $tex = '';
	var $inputhash = '';
	var $hash = '';
	var $html = '';

	//right now we have NO params, but it might be worth keeping...
	function __construct( $tex, $params=array() ) {
		$this->tex = $tex;
		$this->params = $params;
 	}

	function render() {
		global $wgTmpDirectory;
		$fname = 'MathRenderer::render';

		if( !$this->_recall() ) {
			# Ensure that the temp and output directories are available before continuing...
			if( !file_exists( $wgTmpDirectory ) ) {
				if( !@mkdir( $wgTmpDirectory ) ) {
					return $this->_error( "wgTmpDirectory: $wgTmpDirectory does not exist! " );
				}
			} elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
				return $this->_error( "wgTmpDirectory: $wgTmpDirectory is not accessible!" );
			}

			if( function_exists( 'is_executable' ) && !is_executable( $this->LATEX_PATH ) ) {
				return $this->_error( "latex not found..." );
			}
			if( function_exists( 'is_executable' ) && !is_executable( $this->DVIPS_PATH ) ) {                                
                                return $this->_error( "dvips not found" );      
                        }	
			if( function_exists( 'is_executable' ) && !is_executable( $this->CONVERT_PATH ) ) {                                
                                return $this->_error( "convert (imagemagick) not found" );   
                        }


			//wrap the math text with the generic latex requirements
			//in the future, this wrapper should be modifyable by the parameters
			$thunk = $this->_wrap($this->tex);
			
			//begin working...
			$hash = md5($this->tex);
			$this->hash = $hash;
			wfDebug( "Math: hash is: $this->hash\n" );

			//get to the tmp dir:
			$current_dir = getcwd();
			chdir( $wgTmpDirectory );
			
			// create temporary LaTeX file
			$fp = fopen( "$hash.tex", "w+");
			fputs($fp, $thunk);
			fclose($fp);

			//run latex:
			$command = $this->LATEX_PATH . " --interaction=nonstopmode " . $hash . ".tex";
 			exec($command);
			wfDebug( "Math: latex command, $command\n" );

			//run dvips:
			$command = $this->DVIPS_PATH . " -E $hash" . ".dvi -o " .  "$hash.ps";
 			exec($command);
			wfDebug( "Math: dvips command, $command\n" );


			//run ps through imageMagick:
		 	$command = $this->CONVERT_PATH . " -density 120 $hash.ps $hash.png";
 			exec($command);
			wfDebug( "Math: convert command, $command\n" );

  	//		copy("$hash.png", $this->CACHE_DIR . "/$hash.png");
			chdir($current_dir);

			if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
				return $this->_error( "could not match the hash anywhere" );
			}

			if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
				return $this->_error( 'math_image_error' . " $wgTmpDirectory/{$this->hash}.png  , $dirandhash , " . getcwd() );
			}

			$hashpath = $this->_getHashPath();
			if( !file_exists( $hashpath ) ) {
				if( !@wfMkdirParents( $hashpath, 0755 ) ) {
					return $this->_error( "hashpath error type one: $hashpath" );
				}
			} elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
				return $this->_error( 'hashpath error type two' );
			}

			if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
				return $this->_error( "hashpath rename failed" );
			}

			# Now save it back to the DB:
			if ( !wfReadOnly() ) {
				$outmd5_sql = pack('H32', $this->hash);

				$md5_sql = pack('H32', $this->md5); # Binary packed, not hex

				$dbw = wfGetDB( DB_MASTER );
				$dbw->replace( 'math', array( 'math_inputhash' ),
				  array(
					'math_inputhash' => $dbw->encodeBlob($md5_sql),
					'math_outputhash' => $dbw->encodeBlob($outmd5_sql),
					'math_html_conservativeness' => "",
					'math_html' => $this->html,
					'math_mathml' => "",
				  ), $fname, array( 'IGNORE' )
				);
			}
			
			$this->_cleanup( $hash );
		}
		
		return $this->_doRender();
	}

	function _error( $msg, $append = '' ) {
		$mf   = htmlspecialchars( wfMsg( 'math_failure' ) );
		$errmsg = htmlspecialchars( $msg );
		$source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
		return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
	}

	function _wrap($thunk) {
  		return <<<EOS
    		\documentclass[10pt]{article}

	 	% add additional packages here
		\usepackage{amsmath}
		\usepackage{amsfonts}
		\usepackage{amssymb}
		\usepackage{pst-plot}
		\usepackage{color}

		\pagestyle{empty}
		\begin{document}
		\begin{equation*}
		\large
		$thunk
		\end{equation*}
		\end{document}
EOS;
	}


	function _cleanup($hash) {

		$current_dir = getcwd();
		chdir( $wgTmpDirectory );

		unlink($this->TMP_DIR . "/$hash.tex");
		unlink($this->TMP_DIR . "/$hash.aux");
		unlink($this->TMP_DIR . "/$hash.log");
		unlink($this->TMP_DIR . "/$hash.dvi");
		unlink($this->TMP_DIR . "/$hash.ps");
		unlink($this->TMP_DIR . "/$hash.png");

		chdir($current_dir);
	}

	function _recall() {
		global $wgMathDirectory;
		$fname = 'MathRenderer::_recall';

		$this->md5 = md5( $this->tex );
		$dbr = wfGetDB( DB_SLAVE );
		$rpage = $dbr->selectRow( 'math',
			array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
			array( 'math_inputhash' => $dbr->encodeBlob(pack("H32", $this->md5))), # Binary packed, not hex
			$fname
		);

		if( $rpage !== false ) {
			# Tailing 0x20s can get dropped by the database, add it back on if necessary:
			$xhash = unpack( 'H32md5', $dbr->decodeBlob($rpage->math_outputhash) . "                " );
			$this->hash = $xhash ['md5'];

			$this->conservativeness = $rpage->math_html_conservativeness;
			$this->html = $rpage->math_html;
			$this->mathml = $rpage->math_mathml;

			if( file_exists( $this->_getHashPath() . "/{$this->hash}.png" ) ) {
				return true;
			}

			if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
				$hashpath = $this->_getHashPath();

				if( !file_exists( $hashpath ) ) {
					if( !@wfMkdirParents( $hashpath, 0755 ) ) {
						return false;
					}
				} elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
					return false;
				}
				if ( function_exists( "link" ) ) {
					return link ( $wgMathDirectory . "/{$this->hash}.png",
							$hashpath . "/{$this->hash}.png" );
				} else {
					return rename ( $wgMathDirectory . "/{$this->hash}.png",
							$hashpath . "/{$this->hash}.png" );
				}
			}

		}

		# Missing from the database and/or the render cache
		return false;
	}

	/**
	 * Select among PNG, HTML, or MathML output depending on
	 * THIS ONLY does PNG now...
	 */
	function _doRender() {
		return $this->_linkToMathImage();
	}
	
	function _attribs( $tag, $defaults=array(), $overrides=array() ) {
		$attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
		$attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
		$attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
		return $attribs;
	}

	function _linkToMathImage() {
		global $wgMathPath;
		$url = "$wgMathPath/" . substr($this->hash, 0, 1)
					.'/'. substr($this->hash, 1, 1) .'/'. substr($this->hash, 2, 1)
					. "/{$this->hash}.png";

		return Xml::element( 'img',
			$this->_attribs(
				'img',
				array(
					'class' => 'tex',
					'alt' => $this->tex ),
				array(
					'src' => $url ) ) );
	}

	function _getHashPath() {
		global $wgMathDirectory;
		$path = $wgMathDirectory .'/'. substr($this->hash, 0, 1)
					.'/'. substr($this->hash, 1, 1)
					.'/'. substr($this->hash, 2, 1);
		wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
		return $path;
	}

	public static function renderMath( $tex, $params=array() ) {
		global $wgUser;
		$math = new MathRenderer( $tex, $params );
	//	$math->setOutputMode( $wgUser->getOption('math'));
		return $math->render();
	}
}

Bogues connus

À part que les paramètres ne peuvent pas être passés à ce script, et que le rendu est parfois légèrement différent d'autres rendus latex, il y a quelques autres problèmes mineurs: - Parfois des avertissements PHP sont affichés sur la page lorsque vous affichez des maths. Je crois avoir ce problème car je fais ma gestion de fichiers dans /usr/temp, ce qui n'est pas vraiment le bon endroit. Cet avertissement n'apparaît qu'une seule fois et le rendu reste correct. - Si vous supprimez manuellement toutes les images dans le dossier cache vous obtenez des avertissements la première fois que Math.php doit les afficher à nouveau mais, encore une fois, le rendu reste correct et les avertissements disparaissent.

Paquet nécessaire Texlive

Pour ceux qui ne savent pas comment tex fonctionne, si vous utilisez texlive pour créer du latex qui marche, vous devrez aussi installer certain packages:

  • "graphics"
  • "pst-plot"
  • "xkeyval"
  • "PSTricks"
  • "multido"

Et peut-être d'autres selon comment vous avez installé texlive.

Exemple actif

Ce travail a été effectué pour MyMCAT. Vous purvez voir un exemple spécifique du rendu sur Le Passage Railgun.

Remerciements

Ce hack est basé sur l'article du journal linux http://www.linuxjournal.com/article/7870 par Titus Barik.