Open main menu

Extension:WikiTex/Installation

< Extension:WikiTex

WikiTex Installation and Source code

Contents

The worker programEdit

This external program is called with three arguments:

 texconvert <texfile> <dpi> <resultpicture>
  • texfile is a fully qualified random filename without extension which contains the plain tex file to be processed. It resides in a directory which is writeable by the process, thus a temp directory can be generated there. (Very handy as TeX loves to leave a lot of garbages around.)
  • dpi is the resolution for the generated picture; the default is 120.
  • resultpicture is the fully qualified filename without the .png extension where the resulting png picture should be generated. The picture might exist prior to the call.

The worker program should erase and not create the picture if there is any error in the TeX file. Otherwise the output should be

 OK:12.34

where the number after OK is the depth of the formula below the base line measured in points. See the Extension:WikiTex/Documentation for a description how the TeX file was prepared.

The bash script below makes calls to tex and dvipng. On a debian based linux distribution (such as UBUNTU) you can get these programs by issuing

 sudo apt-get install tetex-bin tetex-base tetex-extra dvipng

If you have math extension enabled on your wiki, with all probability TeX is installed. Locate and change the location of these program in the script below.

#!/bin/bash
# texconvert: convert plain tex --> png
# usage: texconvert <texfile> <dpi> <result>
# make sure all garbage is cleaned up

#check that the tex file is there, last args are not empty
if [ ! -f "$1" -o "x$2" = "x" -o "x$3" = "x" ] ; then
  echo Fail: "usage: texconvert texfile dpi result"
  exit 1
fi
# can we create $3
if ! touch "$3.png" 2>/dev/null ; then
  echo Fail: "cannot write png directory $3"
  exit 1
fi
if [ ! -f "$3.png" ] ; then
  echo Fail: "cannot create png file $3"
  exit 1
fi
rm "$3.png"
DIR=${1%/*}
if [ ! -d "$DIR" ] ; then
  echo Fail "cannot switch to temp dir $DIR"
  exit 1
fi
# OK, then go ahead: create tmp directory in $1, save old dir
TEMPDIR=`mktemp -d -q "$DIR/XXXXXX"` || ( echo Fail "Cannot create temp dir in $DIR"; exit 1 ; )
#save old dir
OLDDIR=`pwd`

cd "$TEMPDIR"
cp "$1" this.tex
DEPTH="NONE"
#call tex, and check the result
/usr/bin/tex -interaction=nonstopmode this >/dev/null 2>/dev/null
RES=$?
if [ -f this.dvi ] ; then
   DEPTH=`grep "//depth:[0-9.\-]*pt//" this.log 2>/dev/null`
   DEPTH=${DEPTH#*depth:}
   DEPTH=${DEPTH%%pt*}
   #do it only if no error occurred
   if [ "x$RES" = "x0" ]; then
    /usr/bin/dvipng -q --png --bg transparent -T tight -D "$2" -o "$3.png" this.dvi >/dev/null 2>/dev/null
   fi
fi
# cleaning up
cd $OLDDIR
rm -rf $TEMPDIR >/dev/null 2>/dev/null
echo OK:$DEPTH
exit 0

The extension programEdit

Cut and copy the program below into the extension directory just below your main Mediawiki files, and name it "WikiTex.php".

<?php
# extension to handle inline and displayed TeX
# <wikitex attribs> .. $inline$, $$displayed$$ ..</wikitex>
# <tex attribs > tex formula to be typeset </tex>
# Attribs accepted:
#   refresh -> recalculate all formulas even if they are cached
#   noinclude -> don't include the standard MediaWiki:TexInclude
#   include="page1; page2; ..." include the content of indicated pages
#   dpi= size of the picture to be produced; this is 120 by default

if(! defined('MEDIAWIKI')){
 die("This file is a MediaWiki extension, it is not a valid entry point\n");
}

global $wgExtensionFunctions;
global $wgExtensionCredits;

static $wfProblemTexIncludes = array();
static $wfTexConvert = "/usr/local/bin/texconvert";
$wgExtensionFunctions[]='wfSetupLogProblemHook';
$wgExtensionCredits['parserhook'][]=array(
 'name'=>'Problem',
 'status'=>'experimental',
 'type'=> 'hook',
 'url' =>'http://www.mediawiki.org/Extension:WikiTex',
 'author'=>'Laszlo Csirmaz',
 'description'=>'Entering math between dollar signs',
);

## initializations, make sure tmp dirs exist
global $wgTmpDirectory;
if( !file_exists( $wgTmpDirectory ) ) {
    if( ! @mkdir( $wgTmpDirectory ) ) {
       die ("Cannot create tmp directory\n");
    }
}
if( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
    die ( "wrong permissions for tmp directory\n" );
}
if(function_exists( 'is_executable' ) && !is_executable( $wfTexConvert ) ) {
    die ( "TeX helper program does not exist\n" );
}

# render the text recursively
function wfProblemRenderer( $txt, &$parser ){
# For version 1.8 and above uncomment the following line:
# return $parser->recursiveTagParse( $txt );
    $title =& $parser->mTitle;
    $options =& $parser->mOptions;
    $output = $parser->parse($txt,$title,$options,true,false );
    return $output->getText();
}

# extract contents of default include page
function wfExtractTexArticle( $title, $label=''){
   global $wfProblemTexIncludes;
   if(!$label){ $label=$title; }
   $add='';
   if( $title ){
     $texinclude = new Article ( $title );
     $texinclude -> getContent();
     if( $texinclude -> mContentLoaded ) {
       $total=explode( "\n",$texinclude->mContent ) ;
       foreach( $total as $index => $line ) {
          $line = trim( $line );
          if( $line[0] !== '%' ){
             $add .= "\n" . $line ;
          }
       }
    }
    $add .= "\n";
   }
   $wfProblemTexIncludes["$label"] = $add;
}

# extract interesting attribs
function wfProblemExtractAttrib ( $argv=array() ){
    $attribs='';
    if( array_key_exists('refresh',$argv) ){ $attribs = 'refresh'; }
    if( array_key_exists('noinclude',$argv) ) { $attribs = $attribs . ' noinclude'; }
    if( isset( $argv['include'] ) ){
        $attribs = $attribs . ' include="' . $argv['include'] . '"';
    }
    if( isset( $argv['dpi'] ) ) {
        $attribs = $attribs . 'dpi="'. $argv['dpi'].'"';
    }
    return $attribs;
}

## arrange math formulas. There are three cases:
## $$ ...\eqno{(6)}$$  displayed formula with equation number
## $$ ... $$  displayed formula without equation number
## $...$      simple math formula

function wfTexRenderPara ( $txt, $attribs ){
    $res='';
    foreach( explode('$$',$txt) as $cnt => $part ) {
        if( ($cnt%2) ){ // inside display
            unset( $matcharray );
            $eqno='';
            preg_match('/\\\\eqno{(.*?)}/',$part,$matcharray);
            if(isset($matcharray[1])){
              $eqno ="<span class=\"dispno\">".$matcharray[1]."</span>";
            }
            $res .= "<div class=\"texdisplay\">" . $eqno
               . "<tex display $attribs>\\displaystyle "
               . $part."</tex></div>";
        } else { // outside displayed formula
            $res .= preg_replace('/\$([^\$]+)\$/',"<tex $attribs>\\1</tex>",$part);
        }
    }
    return $res;
}

function wfTexRenderer( $txt, $attrib='' ) {
    $res=''; $para=''; $linesep=''; $parasep='';
    foreach( explode("\n",$txt) as $line ) {
        if( trim($line) ){ $para .= $linesep . $line; $linesep = "\n"; }
        else {
            $res .= $parasep; $parasep="\n\n"; $linesep='';
            if( $para ){
                $res .= wfTexRenderPara($para,$attrib);
                $para='';
            }
        }
    }
    if($para) {
        $res .= $parasep. wfTexRenderPara($para,$attrib);
    }
    return $res;
}

## formatting <tex>...</tex> stuff
function wfTexXMLFunction($inp,$argv=array(),&$parser){
# $inp is the text beween tags,
# $argv is the array of attributes
# $parser is the parent parser
# return value: the processed text.
    global $wgMathDirectory;
    global $wgMathPath;
    global $wgTmpDirectory;
    global $wfProblemTexIncludes;
    global $wfTexConvert;
    $inp = trim( $inp ); // the text itself to be typeset
    $display=$argv['display']; // is it a display?
    $hash=md5((isset($display) ? '$$' : '$' ) . $inp );
    $prefix= $hash[0] .'/' . $hash[1] .'/' . $hash[2];
    # it is $prefix/$hash.png
    $storedir= "$wgMathDirectory/$prefix" ;
    # check if the directory exists at all, if not, create
    if( ! file_exists( $storedir ) ){ @wfMkdirParents( $storedir,0755 ); }
    if( !is_dir( $storedir) || !is_writable( $storedir ) ){
         return "<strong class=\"error\">Tex Error: cannot create cache $storedir</strong>";
    }
    $alt= trim(str_replace("\n",' ',htmlspecialchars($inp) ) );
    $url= htmlspecialchars( "$wgMathPath/$prefix/$hash.png" );
    $display=$argv['display']; // is it a display?
    if( !array_key_exists('refresh',$argv) &&
       file_exists( "$storedir/$hash.png" ) ){
       // try to figure out vertical align
       $style='';
       $dp='';
       if(! isset($display) ) {
         $dbr =& wfGetDB( DB_SLAVE );
         $rpage = $dbr->selectRow( 'math',
                    array( 'math_mathml' ),
                    array( 'math_inputhash' => pack("H32",$hash), ),
                    'wfTexXMLFunction' );
         if($rpage !== false ) { $dp=$rpage->math_mathml;}
         if(preg_match( '/^[ \-.0-9]+$/',$dp)){
             $style=" style=\"vertical-align:". $dp . "px\"";
         }
       }
       return "<img class=\"tex\"$style src=\"$url\" alt=\"$alt\" />";
    }
    // load include files
    $include='';
    if( ! array_key_exists('noinclude',$argv) ){
        if( ! array_key_exists('definclude',$wfProblemTexIncludes) ){
            wfExtractTexArticle(Title::makeTitle( 8, 'TexInclude') ,'definclude');
        }
        $include = $include . $wfProblemTexIncludes['definclude'];
    }
    // load all required include files
    $allinclude=$argv['include']; if( isset($allinclude) ){
       foreach( explode(';',$allinclude) as $incfile ){
          $incfile=trim($incfile);
          if( ! array_key_exists("$incfile",$wfProblemTexIncludes) ){
               wfExtractTexArticle(Title::newFromText($incfile),$incfile);
          }
          $include = $include . $wfProblemTexIncludes["$incfile"];
       }
    }
    $dpi=$argv['dpi'];
    if( !isset($dpi) || ! preg_match('/^[0-9]+$/',$dpi) ) { $dpi=120; }

    $tempName = tempnam( $wgTmpDirectory, 'tex_' );
    $tempFile = fopen( $tempName, "w" );
    if(! $tempFile ){
         return "<strong class=\"error\">Tex Error: cannot create temporary file $tempname</strong>";
    }
    fwrite($tempFile,"\\overfullrule0pt\n\\nopagenumbers\n");
    if( $include){ fwrite($tempFile, "$include" . "\n"); }
    fwrite($tempFile,"\\setbox0\\hbox{\$" .
             $inp .
             "\$}%\n\\message{//depth:\\the\\dp0//}%\n" .
             "\\box0%\n\\bye\n");
    fclose( $tempFile );
    $cmd =  wfEscapeShellArg( $wfTexConvert, $tempName, $dpi, "$storedir/$hash" );
    wfProfileIn( "TeX-shellexec" );
    $convtext = trim( wfShellExec( $cmd ) );
    wfProfileOut( "TeX-shellexec" );
    unlink( $tempName );
    if(! file_exists( "$storedir/$hash.png" ) ){
       return "<strong class=\"error\">Tex syntax error</strong><pre>$inp\n</pre>" ;
    }
    $depth = '';
    // compute how much should it go down
    if( substr($convtext,0,3) == 'OK:' ){
        $depth = substr($convtext,3);
    }
    $style='';
    if($depth && !isset($display)) {
        $depth=((float)$depth)*$dpi* -0.012;
        $depth=sprintf("%4.1F",$depth-0.2);
        $style=" style=\"vertical-align:${depth}px\"";
        // save it in DB
        if( !wfReadOnly() ){
            $dbw =& wfgetDB( DB_MASTER );
            $dbw->replace( 'math', array( 'math_inputhash' ),
                array( 'math_inputhash' => pack("H32",$hash),
                       'math_mathml' => $depth, ),
                'wfTexXMLFunction', array ( 'IGNORE' )
            );
         }
    }
    return "<img class=\"tex\"$style src=\"$url\" alt=\"$alt\" />";
}

function wfWikitexXMLFunction($inp,$argv,&$parser){
# $inp is the text beween tags,
# $argv is the array of attributes
# $parser is the parent parser
# return value: the processed text.
    return  wfProblemRenderer(
          wfTexRenderer( $inp, wfProblemExtractAttrib ($argv)),
          $parser ) ;
}

function wfSetupLogProblemHook(){
 global $wgParser;
 $wgParser->setHook('tex','wfTexXMLFunction');
 $wgParser->setHook('wikitex','wfWikitexXMLFunction');
}
?>

LocalSettings.phpEdit

Put the following line somewhere at the end in your LocalSettings.php file:

require_once( "extensions/WikiTex.php" );

Default CSS fileEdit

Formula numbers in displayed formulas use a special CSS setting which should be defined. Edit, as sysop, the MediaWiki:Common.css file. CSS placed there will be applied to all skins. Add the following CSS:

/* displayed formula, formula number */
div.texdisplay {
   margin: 3px; padding: 3px;
   background-color: transparent;
   text-align:center;
}
/* change to float:left if you want formula numbers appear on the left */
div.texdisplay span.dispno {
   float: right;
}

Default Tex Include fileEdit

Predefined macros will be prepended to the text. Edit the file MediaWiki:TexInclude as a sysop where you can define the most frequently used LaTeX macros. In the example below the \frac, \mathcal, \frak, and \mathbb is defined. The macro \eqno is used as formula numbering, and should be defined to avoid TeX complains. Other tips and tricks are in Extension:WikiTex/Documentation.

% default include file for the WikiTex extension
% this is prepended to the formula to be typeset
% this is plain tex
% 
 \def\frac#1#2{{\begingroup#1\endgroup\over#2}}
 \font\tenfrak=eufm10
 \font\eightfrak=eufm8
 \newfam\eufm
 \textfont\eufm=\tenfrak\scriptfont\eufm=\eightfrak
 \def\frak#1{{\fam\eufm #1}}
 \def\mathcal#1{{\cal #1}}
 \def\mathbb#1{\mathord{\mathchoice{\hbox{\tenbf #1}}{\hbox{\tenbf #1}}%
     {\hbox{\sevenbf #1}}{\hbox{\fivebf #1}}}}
 % this is for entering math inside math
 \def\math#1{$#1$}
 % this is for equation numbers: 
 \def\eqno#1{}
%