--- loncom/xml/lontexconvert.pm	2006/05/17 22:08:08	1.73
+++ loncom/xml/lontexconvert.pm	2011/03/14 16:10:21	1.106
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # TeX Conversion Module
 #
-# $Id: lontexconvert.pm,v 1.73 2006/05/17 22:08:08 albertel Exp $
+# $Id: lontexconvert.pm,v 1.106 2011/03/14 16:10:21 bisitz Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -45,24 +45,33 @@ use vars qw($errorstring);
 #use Apache::lonxml();
 use Apache::lonlocal;
 use Apache::lonnet;
+use lib '/home/httpd/lib/perl/';
+use LONCAPA;
+use URI::Escape;
+use IO::Socket::INET;
 
 # ====================================================================== Header
 
 sub init_tth {
     my $options=$env{'course.'.$env{'request.course.id'}.'.tthoptions'};
+    if ($options =~ /\S/) {
+	$options = ' '.$options;
+    } else {
+	undef($options);
+    }
     if ($env{'browser.mathml'}) {
 	&tth::ttminit();
 	if ($env{'browser.unicode'}) {
-	    &tth::ttmoptions('-L -u1 '.$options);
+	    &tth::ttmoptions('-L -u1'.$options);
 	} else {
-	    &tth::ttmoptions('-L -u0 '.$options);
+	    &tth::ttmoptions('-L -u0'.$options);
 	}
     } else {
 	&tth::tthinit();
 	if ($env{'browser.unicode'}) {
-	    &tth::tthoptions('-L -u1 '.$options);
+	    &tth::tthoptions('-L -u1'.$options);
 	} else {
-	    &tth::tthoptions('-L -u0 '.$options);
+	    &tth::tthoptions('-L -u0'.$options);
 	}
     }
 }
@@ -71,10 +80,7 @@ sub init_tth {
 
 $Apache::lontexconvert::messedup=0;
 
-# we need this routine because &converted can get called from inside
-# of the safespace (through &xmlparse('<m>stuff</m>') which doesn't
-# allow the opcode for alarm, so we need to compile this before we get
-# into the safe space since opcode checks only occur at compile time
+
 sub convert_real {
     my ($texstring)=@_;
     my ($xmlstring,$errorstring);
@@ -100,6 +106,14 @@ sub convert_real {
     $xmlstring=~s/^\s*\<br clear\=\"all\"/\<br/s;
     $xmlstring=~s/^\s*//;
     $xmlstring=~s/\s*$//;
+    #
+    # \rightleftharpoons is not converted by tth but maps
+    # reasonably well to &#8660;.  If we get many more of these,
+    # we're going to need to have a translation sub.
+    #
+    my $lrharpoon = pack("U", 0x21cc);
+    $xmlstring=~s/\\rightleftharpoons/$lrharpoon/g;
+
     &Apache::lonxml::end_alarm();
     return ($xmlstring,$errorstring);
 }
@@ -143,7 +157,7 @@ ENDCONV
 
 sub clean_out_math_mode {
     my ($texstring)=@_;
-    $$texstring=~s/(?!\\)\$//g;
+    $$texstring=~s/(?<!\\)\$//g;
     $$texstring=~s/\\[\)\(\]\[]//g;
     $$texstring=~s/\\ensuremath//g;
     return '';
@@ -151,10 +165,12 @@ sub clean_out_math_mode {
 
 sub displaystyle {
     my ($texstring)=@_;
-    #has a $$ or \[ or \displaystyle in it, guessinng it's display mode
+    #has a $$ or \[ or \displaystyle or eqnarray in it, guessinng it's display mode
     if ($$texstring=~/[^\\]\$\$/ ||
-	$$texstring=~/\\\[/ ||
-	$$texstring=~/\\displaystyle/) { return 1; }
+        $$texstring=~/\\\[/ ||
+        $$texstring=~/\\displaystyle/ ||
+        $$texstring=~/eqnarray/
+       ) { return 1; }
     return 0;
 }
 
@@ -168,60 +184,129 @@ sub jsMath_converted {
 }
 
 {
-    my $jsMath_sent_header;
+    my @jsMath_sent_header;
     sub jsMath_reset {
-	$jsMath_sent_header=0;
+	undef(@jsMath_sent_header);
+    }
+    sub jsMath_push {
+	push(@jsMath_sent_header,0);
     }
     sub jsMath_header {
-	return '' if $jsMath_sent_header;
-	$jsMath_sent_header=1;
+	if (!@jsMath_sent_header) {
+	    &Apache::lonnet::logthis("mismatched calls of jsMath_header and jsMath_process");
+	    return '';
+	}
+	return '' if $jsMath_sent_header[-1];
+	$jsMath_sent_header[-1]=1;
 	return
             '<script type="text/javascript">
                      function NoFontMessage () {}
+                     jsMath = {Parser: {prototype: {environments: {\'eqnarray*\' :[\'Array\',null,null,\'rcl\',[5/18,5/18],3,\'D\']}}}};
                    </script>'."\n".
 	    '<script type="text/javascript" src="/adm/jsMath/jsMath.js"></script>'."\n";
     }
     sub jsMath_process {
-	return '' if !$jsMath_sent_header;
-	return '<script type="text/javascript">jsMath.Process()</script>';
+	my $state = pop(@jsMath_sent_header);
+	return '' if !$state;
+	return "\n".
+	    '<script type="text/javascript">jsMath.Process()</script>'."\n";
+    }
+    sub jsMath_state {
+	my ($level) = @_;
+	return $jsMath_sent_header[$level];
     }
 }
 
+sub tex_engine {
+    if (exists($env{'form.texengine'})) {
+	if ($env{'form.texengine'} ne '') {
+            return $env{'form.texengine'};
+        }
+    }    
+    if ($env{'request.course.id'}
+	&& exists($env{'course.'.$env{'request.course.id'}.'.texengine'})) {
+	return $env{'course.'.$env{'request.course.id'}.'.texengine'};
+    }
+    if (exists($env{'environment.texengine'})) {
+	return $env{'environment.texengine'};
+    }
+    return 'tth';
+}
+
 sub init_math_support {
+    my ($inherit_jsmath) = @_;
     &init_tth();
-    &Apache::lontexconvert::jsMath_reset();
-    if ($env{'environment.texengine'} eq 'jsMath' ||
-	$env{'form.texengine'}        eq 'jsMath' ) {
+    &Apache::lontexconvert::jsMath_push();
+    if (lc(&tex_engine()) eq 'jsmath' ||
+	($inherit_jsmath && &jsMath_state(-2))) {
 	return &Apache::lontexconvert::jsMath_header();
     }
     return;
 }
 
+sub mimetex_valign {
+    my ($esc_texstring)=@_;
+    my $valign = 0;
+    my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+    my $hostname = &Apache::lonnet::hostname($lonhost);
+    my $path = '/cgi-bin/mimetex.cgi?'.$esc_texstring;
+    my $socket;
+    &Apache::lonxml::start_alarm();
+    my $socket = IO::Socket::INET->new(PeerAddr => $hostname,
+                                        PeerPort => 'http(80)',
+                                        Proto    => 'tcp');
+    if ($socket) {
+        my $headreq = "HEAD $path HTTP/1.0\r\n\r\n";
+        print $socket $headreq;
+        while (<$socket>) {
+            if (/Vertical\-Align\:\s*?([\-\d]+)/) {
+                $valign = $1;
+            }
+        }
+        $socket->close();
+    }
+    &Apache::lonxml::end_alarm();
+    return $valign;
+}
+
 sub mimetex_converted {
     my $texstring=shift;
+
+# Alt-Argument for screen readers
+    my $alt_string=$$texstring;
+    $alt_string=~s/\"/\'\'/g;
+
+# Is this displaystyle?
+
     my $displaystyle=&displaystyle($texstring);
 
+# Remove math environment delimiters
+
     &clean_out_math_mode($texstring);
 
     if ($displaystyle) {
 	$$texstring='\\displaystyle \\Large '.$$texstring;
     }
-    my $result='<img src="/cgi-bin/mimetex.cgi?'.&Apache::lonnet::escape($$texstring).'" />';
+    my $esc_texstring = &uri_escape($$texstring);
+    my $valign = &mimetex_valign($esc_texstring);
+    my $result='<img src="/cgi-bin/mimetex.cgi?'.$esc_texstring.'" style="vertical-align:'.$valign.'px" alt="'.$alt_string.'" />';
     if ($displaystyle) {
-	$result='<center>'.$result.'</center>';
+	$result='<div style="text-align:center">'.$result.'</div>';
     }
     return $result;
 }
 
 sub converted {
     my ($string,$mode)=@_;
-    if ($mode eq '') { $mode=$env{'environment.texengine'}; }
+    if ($mode eq '') { $mode = &tex_engine(); }
     if ($mode =~ /tth/i) {
 	return &tth_converted($string);
     } elsif ($mode =~ /jsmath/i) {
 	return &jsMath_converted($string);
     } elsif ($mode =~ /mimetex/i) {
 	return &mimetex_converted($string);
+    } elsif ($mode =~ /raw/i) {
+        return $$string;
     }
     return &tth_converted($string);
 }
@@ -230,6 +315,7 @@ sub converted {
 
 sub to_convert {
     my ($string) = @_;
+    &init_tth();
     $string=~s/\<br\s*\/?\>/ /gs;
 #    $string=~s/\s/ /gs;
     $string=&HTML::Entities::decode($string);
@@ -238,24 +324,37 @@ sub to_convert {
 
 sub smiley {
     my $expression=shift;
-    if ($env{'browser.imagesuppress'} eq 'on') { return $expression; }
-    my %smileys=('\:\-\)' => 'smiley',
-		 '8\-\)'  => 'coolsmile',
-		 '8\-(I|\|)'   => 'coolindiff',
-		 ':\-(I|\|)'   => 'neutral',
-		 '\:\-(o|O|\(\))' => 'shocked',
-		 ':\-\('  => 'frowny',
-		 '\;\-\)' => 'wink',
-		 '\:\-P'  => 'baeh',
-		 '\:\-(\\\|\\/)' => 'hrrm',
-		 '\:\-D'  => 'bigsmile',
-		 '\:\-C'  => 'angry',
-		 '\:(\'|\`)\-\(' => 'cry',
-		 '\:\-(X|\#)' => 'lipsrsealed',
-		 '\:\-S' => 'huh');
+    my %smileys=(
+    	 '\:\-*\)' => 'face-smile.png',
+		 '8\-\)'  => 'face-cool.png',
+		 '8\-(I|\|)'   => 'face-glasses.png',
+		 '\:\-(I|\|)'   => 'face-plain.png',
+		 '\:\-(o|O|\(\))' => 'face-surprise.png',
+		 ':\-\('  => 'face-sad.png',
+		 '\;\-\)' => 'face-wink.png',
+		 '\:\-(P|p)'  => 'face-raspberry.png',
+		 '\:\-(\\\|\\/)' => 'face-uncertain.png',
+		 '\:\-D'  => 'face-smile-big.png',
+		 '\:\-(C|\@)'  => 'face-angry.png',
+		 '\:(\'|\`)\-*\(' => 'face-crying.png',
+		 '\:\-(X|x|\#)' => 'face-quiet.png',
+		 '\:\-(s|S)' => 'face-uncertain.png',
+		 '\:\-\$' => 'face-embarrassed.png',
+		 '\:\-\*' => 'face-kiss.png',
+		 '\+O\(' => 'face-sick.png',
+		 '(\&lt\;3|\(heart\))' => 'heart.png',
+		 '\(rose\)' => 'rose.png',
+		 '\(pizza\)' => 'food-pizza.png',
+		 '\(cake\)' => 'food-cake.png',
+		 '\(ninja\)' => 'face-ninja.png',
+		 '\(pirate\)' => 'face-pirate.png',
+		 '\((agree|yes)\)' => 'opinion-agree.png',
+		 '\((disagree|nay)\)' => 'opinion-disagree.png',
+		 '(o|O)\-\)' => 'face-angel.png',
+		 );
     my $iconpath=$Apache::lonnet::perlvar{'lonIconsURL'};
-    foreach (keys %smileys) {
-	$expression=~s/$_/\<img src="$iconpath\/$smileys{$_}.gif" \/\>/gs; 
+    foreach my $smiley (keys(%smileys)) {
+	$expression=~s/$smiley/\<img src="$iconpath\/$smileys{$smiley}" \/\>/gs; 
     }
     return $expression;
 }
@@ -263,30 +362,37 @@ sub smiley {
 sub msgtexconverted {
     my ($message,$email) = @_;
     $errorstring='';
-    &init_tth();
     my $outmessage='';
     my $tex=0;
-    foreach (split(/(?:\&lt\;|\<)\/*m\s*(?:\&gt\;|\>)/i,$message)) {
+    foreach my $fragment (split(/(?:\&lt\;|\<)\/*m\s*(?:\&gt\;|\>)/i,$message)) {
 	if ($tex) {
 	    if ($email) {
-		$outmessage.='</pre><tt>'.&to_convert($_).'</tt><pre>'; $tex=0;
+		$outmessage.='</pre><tt>'.&to_convert($fragment).'</tt><pre>';
+		$tex=0;
 	    } else {
-		$outmessage.=&to_convert($_); $tex=0;
+		$outmessage.=&to_convert($fragment);
+		$tex=0;
 	    }
 	} else {
-            $outmessage.=&smiley($_); $tex=1;
+            $outmessage.=&smiley($fragment);
+	    $tex=1;
 	}
     }
     $message=$outmessage; $outmessage=''; $tex=0;
-    foreach (split(/(?:\&lt\;|\<)\/*algebra\s*(?:\&gt\;|\>)/i,$message)) {
+    foreach my $fragment (split(/(?:\&lt\;|\<)\/*algebra\s*(?:\&gt\;|\>)/i,
+				$message)) {
 	if ($tex) {
+        my $algebra = &algebra($fragment, 'web', undef, undef, undef, undef, 'tth');
 	    if ($email) {
-		$outmessage.='</pre><tt>'.&algebra($_,'web').'</tt><pre>'; $tex=0;
+		$outmessage.='</pre><tt>'.$algebra.'</tt><pre>';
+		$tex=0;
 	    } else {
-		$outmessage.=&algebra($_,'web'); $tex=0;
+		$outmessage.=$algebra;
+		$tex=0;
 	    }
 	} else {
-            $outmessage.=$_; $tex=1;
+        $outmessage.=$fragment;
+	    $tex=1;
 	}
     }
     if (wantarray) {
@@ -298,9 +404,9 @@ sub msgtexconverted {
 
 sub algebra {
     use AlgParser;
-
-    my ($string,$target,$style)=@_;
+    my ($string,$target,$style,$parstack,$safeeval,$tth)=@_;
     my $parser = new AlgParserWithImplicitExpand;
+    if ($tth eq 'tth') {&init_tth();}
     $string=&prepare_algebra($string);
     my $ret = $parser->parse($string);
     my $result='['.&mt('Algebra unconverted due to previous errors').']';
@@ -316,7 +422,9 @@ sub algebra {
 	    $latex='\\ensuremath{'.$latex.'}';
 	}
 	if ($target eq 'web' || $target eq 'analyze') {
-	    $result = &converted(\$latex);
+            my $display=&Apache::lonxml::get_param('display',$parstack,$safeeval);
+            $result = &converted(\$latex,$display);
+#	    $result = &converted(\$latex);
 	} else {
 	    $result = $latex;
 	}
@@ -340,9 +448,6 @@ sub postprocess_algebra {
     # moodle had these and I don't know why, ignoring them for now
     # $string =~s/\\fun/ /g;
 
-    # remove the extra () in the denominator of a \frac
-    $string =~s/\\frac{(.+?)}{\\left\((.+?)\\right\)}/\\frac{$1}{$2}/gs;
-    
     # sqrt(3,4) means the 4 root of 3
     $string =~s/\\sqrt{([^,]+),([^\}]+)}/\\sqrt[$2]{$1}/gs;
 
@@ -366,8 +471,111 @@ sub postprocess_algebra {
 __END__
 
 
+=pod
+
+=head1 NAME
+
+Apache::lontexconvert;
+
+=head1 SYNOPSIS
+
+Access to tth/ttm
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+
+=head1 SUBROUTINES
+
+=over
+
+=item init_tth()
+
+Header
+
+=item convert_real()
+
+ we need this routine because &converted can get called from inside
+ of the safespace (through &xmlparse('<m>stuff</m>') which doesn't
+ allow the opcode for alarm, so we need to compile this before we get
+ into the safe space since opcode checks only occur at compile time
+
+=item tth_converted()
+
+
+=item clean_out_math_mode()
+
+
+=item displaystyle()
+
+
+=item jsMath_converted()
+
+
+=item tex_engine()
+
+
+=item init_math_support()
+
+=item mimetex_valign()
+
+ Makes a HEAD call to /cgi-bin/mimetex.cgi via IO:: to retrieve the 
+ vertical alignment, before the subsequent call to mimetex_converted()
+ which generates the <img> tag and the corresponding image.
+
+ Input: 1.  $esc_texstring (escaped TeX to be rendered by mimetex).
+ Output: 1. $valign - number of pixels: positive or negative integer 
+            which will be included in <img> tag for mimetex image to
+            support vertical alignment of image within a line of text.
+
+ If a server is running SSL, and Apache rewrite rules are in place 
+ to rewrite requests for http to https, modification will most likely 
+ be needed for pass through for HEAD requests for /cgi-bin/mimetex.cgi. 
+
+ Example rewrite rules which rewrite all http traffic to https, 
+ except HEAD requests for /cgi-bin/mimetex.cgi are:
+
+ <IfModule mod_rewrite.c>
+     RewriteEngine On
+     RewriteLogLevel 0
+
+     RewriteCond %{HTTPS} off
+     RewriteCond %{HTTP:Host} (.*)
+     RewriteCond %{REQUEST_METHOD} !HEAD 
+     RewriteRule ^/(.*) https://%1/$1 [R=301,L]
+
+     RewriteCond %{HTTPS} off
+     RewriteCond %{HTTP:Host} (.*)
+     RewriteCond %{REQUEST_METHOD} HEAD
+     RewriteCond %{REQUEST_URI} !^/cgi-bin/mimetex.cgi
+     RewriteRule ^/(.*) https://%1/$1 [R=301,L]
+ </IfModule>
+
+=item mimetex_converted()
+
+
+=item converted()
+
+
+=item to_convert()
+
+message display
+
+=item smiley()
+
+???
+
+=item msgtexconverted()
+
+=item algebra()
+
+=item prepare_algebra()
+
+=item postprocess_algebra()
 
+=back
 
+=cut