File:  [LON-CAPA] / loncom / html / adm / jsMath / plugins / tex2math.js
Revision 1.1: download - view: text, annotated - select for diffs
Wed Dec 7 18:57:49 2005 UTC (18 years, 7 months ago) by albertel
Branches: MAIN
CVS tags: version_2_1_X, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_0_99_1, HEAD
- jsMath version 2.4
- addign the fallback fonts

/*
 *  tex2math.js
 *  
 *  Part of the jsMath package for mathematics on the web.
 *
 *  This file is a plugin that searches text wthin a web page
 *  for \(...\), \[...\], $...$ and $$...$$ and converts them to
 *  the appropriate <SPAN CLASS="math">...</SPAN> or
 *  <DIV CLASS="math">...</DIV> tags.
 *
 *  ---------------------------------------------------------------------
 *
 *  jsMath is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  jsMath is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with jsMath; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

jsMath.Insert(jsMath,{
  
  /*
   *  Call the main conversion routine with 
   *  appropriate flags
   */
  
  ConvertTeX: function (element) {
    jsMath.tex2math.Convert(element,{
      processSingleDollars: 1, processDoubleDollars: 1,
      processSlashParens: 1, processSlashBrackets: 1,
      custom: 0, fixEscapedDollars: 1
    });
  },
  
  ConvertTeX2: function (element) {
    jsMath.tex2math.Convert(element,{
      processSingleDollars: 0, processDoubleDollars: 1,
      processSlashParens: 1, processSlashBrackets: 1,
      custom: 0, fixEscapedDollars: 0
    });
  },
  
  ConvertLaTeX: function (element) {
    jsMath.tex2math.Convert(element,{
      processSingleDollars: 0, processDoubleDollars: 0,
      processSlashParens: 1, processSlashBrackets: 1,
      custom: 0, fixEscapedDollars: 0
    });
  },
  
  ConvertCustom: function (element) {
    jsMath.tex2math.Convert(element,{custom: 1, fixEscapedDollars: 0});
  },

  /*
   *  The main tex2math code
   */
  tex2math: {
    
    /*
     *  Define a custom search by indicating the
     *  strings to use for starting and ending
     *  in-line and display mathematics
     */
    CustomSearch: function (iOpen,iClose,dOpen,dClose) {
      this.inLineOpen = iOpen; this.inLineClose = iClose;
      this.displayOpen = dOpen; this.displayClose = dClose;
      this.createPattern('customPattern',new RegExp(
        '('+this.patternQuote(dOpen)+'|'
           +this.patternQuote(iOpen)+'|'
           +this.patternQuote(dClose)+'|'
           +this.patternQuote(iClose)+'|\\\\.)','g'
      ));
    },
    
    patternQuote: function (s) {
      s = s.replace(/([\^(){}+*?\-|\[\]\:\\])/g,'\\$1');
      return s;
    },


    /*
     *  Set up for the correct type of search, and recursively
     *  convert the mathematics.  Disable tex2math if the cookie
     *  isn't set, or of there is an element with ID of 'tex2math_off'.
     */
    Convert: function (element,flags) {
      if (!element) {element = document.body}
      if (typeof(element) == 'string') {element = document.getElementById(element)}
      if (jsMath.Controls.cookie.tex2math && 
          (!jsMath.tex2math.allowDisableTag || !document.getElementById('tex2math_off'))) {
        this.custom = 0; for (var i in flags) {this[i] = flags[i]}
        if (this.custom) {
          this.pattern = this.customPattern;
          this.ProcessMatch = this.customProcessMatch;
        } else {
          this.pattern = this.stdPattern;
          this.ProcessMatch = this.stdProcessMatch;
        }
        if (this.processDoubleDollars || this.processSingleDollars ||
            this.processSlashParens   || this.processSlashBrackets ||
            this.custom) jsMath.tex2math.ScanElement(element);
      }
    },

    /*
     *  Recursively look through a document for text nodes that could
     *  contain mathematics.
     */
    ScanElement: function (element,ignore) {
      if (!element) {element = document.body}
      if (typeof(element) == 'string') {element = document.getElementById(element)}
      while (element) {
        if (element.nodeName == '#text') {
          if (!ignore) {element = this.ScanText(element)}
        } else if (element.firstChild && element.className != 'math') {
          var off = ignore || element.className == 'tex2math_ignore' ||
             (element.tagName && element.tagName.match(/^(script|noscript|style|textarea|pre)$/i));
          off = off && element.className != 'tex2math_process';
          this.ScanElement(element.firstChild,off);
        }
        if (element) {element = element.nextSibling}
      }
    },
    
    /*
     *  Looks through a text element for math delimiters and
     *  process them.  If <BR> tags are found in the middle, they
     *  are ignored (this is for BBS systems that have editors
     *  that insert these automatically).
     */
    ScanText: function (element) {
      if (element.nodeValue.replace(/\s+/,'') == '') {return element}
      var match; var prev; this.search = {};
      while (element) {
        this.pattern.lastIndex = 0;
        while (element.nodeName == '#text' &&
              (match = this.pattern.exec(element.nodeValue))) {
          element = this.ProcessMatch(match[0],match.index,element);
        }
        if (this.search.matched) {element = this.EncloseMath(element)}
        prev = element; element = element.nextSibling;
        while (element && element.nodeName.toLowerCase() == 'br')
          {prev = element; element = element.nextSibling}
        if (!element || element.nodeName != '#text') {return prev}
      }
      return element;
    },
    
    /*
     *  If a matching end tag has been found, process the mathematics.
     *  Otherwise, update the search data for the given delimiter,
     *  or ignore it, as the item dictates.
     */
    stdProcessMatch: function (match,index,element) {
      if (match == this.search.end) {
        this.search.close = element;
        this.search.clength = match.length;
        this.search.cpos = this.pattern.lastIndex;
        element = this.EncloseMath(element);
      } else {
        switch (match) {
          case '\\(':
            if (this.search.end != '$' && this.search.end != '$$' &&
                this.processSlashParens) {
              this.ScanMark('span',element,'\\)');
            }
            break;

          case '\\[':
            if (this.search.end != '$' && this.search.end != '$$' &&
                this.processSlashBrackets) {
              this.ScanMark('div',element,'\\]');
            }
            break;

          case '$$':
            if (this.processDoubleDollars) {
              var type = (this.doubleDollarsAreInLine? 'span': 'div');
              this.ScanMark(type,element,'$$');
            }
            break;

          case '$':
            if (this.search.end == null && this.processSingleDollars) {
              this.ScanMark('span',element,'$');
            }
            break;

          case '\\$':
            if (this.search.end == null && this.fixEscapedDollars) {
              element.nodeValue = element.nodeValue.substr(0,index)
                                + element.nodeValue.substr(index+1);
            }
            break;
        }
      }
      return element;
    },

    /*
     *  If a matching end tag has been found, process the mathematics.
     *  Otherwise, update the search data for the given delimiter,
     *  or ignore it, as the item dictates.
     */
    customProcessMatch: function (match,index,element) {
      if (match == this.search.end) {
        this.search.close = element;
        this.search.clength = match.length;
        this.search.cpos = this.pattern.lastIndex;
        this.search.matched = 1;
      } else if (match == this.inLineOpen) {
        if (this.search.matched) {element = this.EncloseMath(element)}
        this.ScanMark('span',element,this.inLineClose);
      } else if (match == this.displayOpen) {
        if (this.search.matched) {element = this.EncloseMath(element)}
        this.ScanMark('div',element,this.displayClose);
      }
      return element;
    },

    /*
     *  Return a structure that records the starting location
     *  for the math element, and the end delimiter we want to find.
     */
    ScanMark: function (type,element,end) {
      var len = RegExp.$1.length;
      this.search = {
        type: type, end: end, open: element, olength: len,
        pos: this.pattern.lastIndex - len
      };
    },
    
    /*
     *  Surround the mathematics by an appropriate
     *  SPAN or DIV element marked as CLASS="math".
     */
    EncloseMath: function (element) {
      var search = this.search;
      var close = search.close;
      if (search.cpos == close.length) {close = close.nextSibling}
         else {close = close.splitText(search.cpos)}
      if (!close) {close = document.createTextNode("")}
      if (element == search.close) {element = close}
      var math = search.open.splitText(search.pos);
      while (math.nextSibling && math.nextSibling != close) {
        if (math.nextSibling.nodeValue) {math.nodeValue += math.nextSibling.nodeValue}
        math.parentNode.removeChild(math.nextSibling);
      }
      var TeX = math.nodeValue.substr(search.olength,
        math.nodeValue.length-search.olength-search.clength);
      math.parentNode.removeChild(math);
      math = this.createMathTag(search.type,TeX);
      if (close && close.parentNode) {
        close.parentNode.insertBefore(math,close);
      } else if (search.open.nextSibling) {
        search.open.parentNode.insertBefore(math,search.open.nextSibling);
      } else {
        search.open.parentNode.appendChild(math);
      }
      this.search = {}; this.pattern.lastIndex = 0;
      return element;
    },
    
    /*
     *  Create an element for the mathematics
     */
    createMathTag: function (type,text) {
      var tag = document.createElement(type); tag.className = "math";
      var math = document.createTextNode(text);
      tag.appendChild(math);
      return tag;
    },

    //
    //  MSIE won't let you insert a DIV within tags that are supposed to
    //  contain in-line data (like <P> or <SPAN>), so we have to fake it
    //  using SPAN tags that force the formatting to work like DIV.  We
    //  use a separate SPAN that is the full width of the containing
    //  item, and that has the margins from the div.typeset style
    //  and we name is jsMath.recenter to get jsMath to recenter it when
    //  it is typeset (HACK!!!)
    //
    MSIEcreateMathTag: function (type,text) {
      var tag = document.createElement("span");
      tag.className = "math";
      text = text.replace(/</g,'&lt;').replace(/>/g,'&gt;');
      if (type == 'div') {
        tag.className = (jsMath.tex2math.center)? "jsMath.recenter": "";
        tag.style.width = "100%"; tag.style.margin = jsMath.tex2math.margin;
        tag.style.display = "inline-block";
        text = '<span class="math">\\displaystyle{'+text+'}</span>';
      }
      tag.innerHTML = text;
      return tag;
    }
    
  }
});

/*
 *  Set the defaults
 */
if (jsMath.Controls.cookie.tex2math == null) {jsMath.Controls.cookie.tex2math = 1}
if (jsMath.tex2math.allowDisableTag == null) {jsMath.tex2math.allowDisableTag = 1}

/*
 *  MSIE can't handle the DIV's properly, so we need to do it by
 *  hand.  Look up the style for typeset math to see if the user
 *  has changed it, and get whether it is centered or indented
 *  so we can mirror that using a SPAN
 */
if (jsMath.browser == 'MSIE' && navigator.platform == 'Win32') {
  jsMath.tex2math.createMathTag = jsMath.tex2math.MSIEcreateMathTag;
  jsMath.Add(jsMath.tex2math,{margin: "", center: 0});
  for (var i = 0; i < document.styleSheets.length; i++) {
    var rules = document.styleSheets[i].cssRules;
    if (!rules) {rules = document.styleSheets[i].rules}
    for (var j = 0; j < rules.length; j++) {
      if (rules[j].selectorText.toLowerCase() == 'div.typeset') {
        if (rules[j].style.margin != "") 
        {jsMath.tex2math.margin = rules[j].style.margin}
        jsMath.tex2math.center =
          (rules[j].style.textAlign == 'center')? 1: 0;
      }
    }
  }
}

/*
 *  MSIE on the mac doesn't handle lastIndex correctly, so
 *  override it and implement it correctly.
 */
if (jsMath.browser == 'MSIE' && navigator.platform == 'MacPPC') {
  jsMath.tex2math.createPattern = function (name,pattern) {
    jsMath.tex2math[name] = pattern;
    pattern.oldExec = pattern.exec;
    pattern.exec = function (string) {
      var pattern = jsMath.tex2math[name];
      if (pattern.lastIndex == null) (pattern.lastIndex = 0);
      var match = pattern.oldExec(string.substr(pattern.lastIndex));
      if (match) {pattern.lastIndex += match.lastIndex} 
            else {pattern.lastIndex = null}
      return match;
    }
  }
} else {
  jsMath.tex2math.createPattern =
    function (name,pattern) {jsMath.tex2math[name] = pattern}
}

/*
 *  The standard pattern for TeX and LaTeX strings
 */
jsMath.tex2math.createPattern('stdPattern',/(\\[\(\)\[\]$]|\$\$|\$)/g);

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>