/*
 *  ../../../..//extensions/TeX/text-macros.js
 *
 *  Copyright (c) 2009-2018 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*************************************************************
 *
 *  MathJax/extensions/TeX/text-macros.js
 *
 *  Implements the processing of some text-mode macros inside
 *  \text{} and other text boxes.
 *
 *  ---------------------------------------------------------------------
 *
 *  Copyright (c) 2018 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

MathJax.Extension["TeX/text-macros"] = {
  version: "2.7.5"
};

MathJax.Hub.Register.StartupHook("TeX Jax Ready", function() {
  var MML = MathJax.ElementJax.mml;
  var TEX = MathJax.InputJax.TeX;
  var TEXDEF = TEX.Definitions;

  TEX.Parse.Augment({
    //
    // Replace InternalMath with parser that handles some text macros
    //
    InternalMath: function(text, level) {
      var mml = TextParser(text, {}).Parse();
      if (level != null) {
        mml = [MML.mstyle.apply(MML, mml).With({ displaystyle: false, scriptlevel: level })];
      } else if (mml.length > 1) {
        mml = [MML.mrow.apply(MML, mml)];
      }
      return mml;
    },

    //
    //  Correctly skip newline as well as comment
    //
    Comment: function(c) {
      while (this.i < this.string.length && this.string.charAt(this.i) != "\n") {
        this.i++;
      }
      this.i++;
    },

    //
    // Correctly skip trailing space as well
    //
    GetCS: function() {
      var CS = this.string.slice(this.i).match(/^([a-z]+|.) ?/i);
      if (CS) {
        this.i += CS[0].length;
        return CS[1];
      } else {
        this.i++;
        return " ";
      }
    }
  });

  //
  //  The text parser is a subclass of the math parser, so we can use
  //  some of the existing methods (like GetArgument()), and some of the
  //  control sequence implementations (like Macro, Spacer, etc.)
  //
  var TextParser = (TEX.TextParser = TEX.Parse.Subclass({
    Init: function(text, env) {
      this.env = MathJax.Hub.Insert({}, env);
      this.stack = { env: this.env };
      this.string = text;
      this.i = 0;
      this.mml = []; // the accumulated MathML elements
      this.text = ""; // the accumulated text so far
    },

    //
    //  These are the special characters in text mode
    //
    textSpecial: {
      "\\": "ControlSequence",
      $: "Math",
      "%": "Comment",
      "^": "MathModeOnly",
      _: "MathModeOnly",
      "&": "Misplaced",
      "#": "Misplaced",
      "~": "Tilde",
      " ": "Space",
      "\t": "Space",
      "\r": "Space",
      "\n": "Space",
      "\u00A0": "Tilde",
      "{": "OpenBrace",
      "}": "CloseBrace",
      "`": "OpenQuote",
      "'": "CloseQuote"
    },

    //
    //  These are text-mode macros we support
    //
    textMacros: {
      "(": "Math",

      $: "SelfQuote",
      _: "SelfQuote",
      "%": "SelfQuote",
      "{": "SelfQuote",
      "}": "SelfQuote",
      " ": "SelfQuote",
      "&": "SelfQuote",
      "#": "SelfQuote",
      "\\": "SelfQuote",

      "'": ["Accent", "\u00B4"],
      "`": ["Accent", "\u0060"],
      "^": ["Accent", "^"],
      '"': ["Accent", "\u00A8"],
      "~": ["Accent", "~"],
      "=": ["Accent", "\u00AF"],
      ".": ["Accent", "\u02D9"],
      u: ["Accent", "\u02D8"],
      v: ["Accent", "\u02C7"],

      emph: "Emph",
      rm: ["SetFont", MML.VARIANT.NORMAL],
      mit: ["SetFont", MML.VARIANT.ITALIC],
      oldstyle: ["SetFont", MML.VARIANT.OLDSTYLE],
      cal: ["SetFont", MML.VARIANT.CALIGRAPHIC],
      it: ["SetFont", "-tex-mathit"], // needs special handling
      bf: ["SetFont", MML.VARIANT.BOLD],
      bbFont: ["SetFont", MML.VARIANT.DOUBLESTRUCK],
      scr: ["SetFont", MML.VARIANT.SCRIPT],
      frak: ["SetFont", MML.VARIANT.FRAKTUR],
      sf: ["SetFont", MML.VARIANT.SANSSERIF],
      tt: ["SetFont", MML.VARIANT.MONOSPACE],

      tiny: ["SetSize", 0.5],
      Tiny: ["SetSize", 0.6], // non-standard
      scriptsize: ["SetSize", 0.7],
      small: ["SetSize", 0.85],
      normalsize: ["SetSize", 1.0],
      large: ["SetSize", 1.2],
      Large: ["SetSize", 1.44],
      LARGE: ["SetSize", 1.73],
      huge: ["SetSize", 2.07],
      Huge: ["SetSize", 2.49],

      mathcal: "MathModeOnly",
      mathscr: "MathModeOnly",
      mathrm: "MathModeOnly",
      mathbf: "MathModeOnly",
      mathbb: "MathModeOnly",
      mathit: "MathModeOnly",
      mathfrak: "MathModeOnly",
      mathsf: "MathModeOnly",
      mathtt: "MathModeOnly",
      Bbb: ["Macro", "{\\bbFont #1}", 1],
      textrm: ["Macro", "{\\rm #1}", 1],
      textit: ["Macro", "{\\it #1}", 1],
      textbf: ["Macro", "{\\bf #1}", 1],
      textsf: ["Macro", "{\\sf #1}", 1],
      texttt: ["Macro", "{\\tt #1}", 1],

      dagger: ["Insert", "\u2020"],
      ddagger: ["Insert", "\u2021"],
      S: ["Insert", "\u00A7"]
    },

    //
    //  These are the original macros that are allowed in text mode
    //
    useMathMacros: {
      ",": true,
      ":": true,
      ">": true,
      ";": true,
      "!": true,
      enspace: true,
      quad: true,
      qquad: true,
      thinspace: true,
      negthinspace: true,

      hskip: true,
      hspace: true,
      kern: true,
      mskip: true,
      mspace: true,
      mkern: true,
      rule: true,
      Rule: true,
      Space: true,

      color: true,
      href: true,
      unicode: true,

      ref: true,
      eqref: true
    },

    //
    //  Look through the text for special characters and process them.
    //  Save any accumulated text aat the end and return the MathML
    //   elements produced.
    //
    Parse: function() {
      var c;
      while ((c = this.string.charAt(this.i++))) {
        if (this.textSpecial.hasOwnProperty(c)) {
          this[this.textSpecial[c]](c);
        } else {
          this.text += c;
        }
      }
      this.SaveText();
      return this.mml;
    },

    //
    //  Handle a control sequence name
    //    If it is a text-mode macro, use it.
    //    Otherwise look for it in the math-mode lists
    //      Report an error if it is not there
    //      Otherwise check if it is a macro or one of the allowed control sequences
    //    Run the macro (with arguments if given)
    //
    ControlSequence: function(c) {
      var cs = this.GetCS(),
        name = c + cs,
        cmd;
      if (this.textMacros.hasOwnProperty(cs)) {
        cmd = this.textMacros[cs];
      } else {
        cmd = this.LookupCS(cs);
        if (!cmd) {
          this.Error(["UndefinedControlSequence", "Undefined control sequence %1", name]);
        }
        if ((!(cmd instanceof Array) || cmd[0] !== "Macro") && !this.useMathMacros.hasOwnProperty(cs)) {
          this.Error(["MathMacro", "'%1' is only supported in math mode", name]);
        }
      }
      if (cmd instanceof Array) {
        if (!this.hasOwnProperty[cmd[0]]) this.SaveText();
        this[cmd[0]].apply(this, [name].concat(cmd.slice(1)));
      } else {
        if (!this.hasOwnProperty[cmd]) this.SaveText();
        this[cmd].call(this, name);
      }
    },

    //
    //  Lookup the CS as a math-mode macro
    //
    LookupCS: function(cs) {
      if (TEXDEF.macros.hasOwnProperty(cs)) return TEXDEF.macros[cs];
      if (TEXDEF.mathchar0mi.hasOwnProperty(cs)) return TEXDEF.mathchar0mi[cs];
      if (TEXDEF.mathchar0mo.hasOwnProperty(cs)) return TEXDEF.mathchar0mo[cs];
      if (TEXDEF.mathchar7.hasOwnProperty(cs)) return TEXDEF.mathchar7[cs];
      if (TEXDEF.delimiter.hasOwnProperty("\\" + cs)) return TEXDEF.delimiter["\\" + cs];
      return null;
    },

    //
    //  Handle internal math mode
    //    Look for the close delimiter and process the contents
    //
    Math: function(open) {
      this.SaveText();
      var i = this.i,
        j,
        c;
      var braces = 0;
      while ((c = this.GetNext())) {
        j = this.i++;
        switch (c) {
          case "\\":
            var cs = this.GetCS();
            if (cs === ")") c = "\\(";
          // falls through

          case "$":
            if (braces === 0 && open === c) {
              this.Push(TEX.Parse(this.string.substr(i, j - i), this.env).mml());
              return;
            }
            break;

          case "{":
            braces++;
            break;

          case "}":
            if (braces == 0) {
              this.Error(["ExtraCloseMissingOpen", "Extra close brace or missing open brace"]);
            }
            braces--;
            break;
        }
      }
      this.Error(["MathNotTerminated", "Math not terminated in text box"]);
    },

    //
    //  Character can only be used in math mode
    //
    MathModeOnly: function(c) {
      this.Error(["MathModeOnly", "'%1' allowed only in math mode", c]);
    },

    //
    //  Character is being used out of place
    //
    Misplaced: function(c) {
      this.Error(["Misplaced", "'%1' can not be used here", c]);
    },

    //
    //  Braces start new environments
    //
    OpenBrace: function(c) {
      var env = this.env;
      this.env = MathJax.Hub.Insert({}, env);
      this.env.oldEnv = env;
    },
    CloseBrace: function(c) {
      if (this.env.oldEnv) {
        this.SaveText();
        this.env = this.env.oldEnv;
      } else {
        this.Error(["ExtraCloseMissingOpen", "Extra close brace or missing open brace"]);
      }
    },

    //
    //  Handle open and close quotes
    //
    OpenQuote: function(c) {
      if (this.string.charAt(this.i) === c) {
        this.text += "\u201C";
        this.i++;
      } else {
        this.text += "\u2018";
      }
    },
    CloseQuote: function(c) {
      if (this.string.charAt(this.i) === c) {
        this.text += "\u201D";
        this.i++;
      } else {
        this.text += "\u2019";
      }
    },

    //
    //  Handle non-breaking and regular spaces
    //
    Tilde: function(c) {
      this.text += "\u00A0";
    },
    Space: function(c) {
      this.text += " ";
      while (this.GetNext().match(/\s/)) this.i++;
    },

    //
    //  Insert the escaped characer
    //
    SelfQuote: function(name) {
      this.text += name.substr(1);
    },

    //
    //  Insert a given character
    //
    Insert: function(name, c) {
      this.text += c;
    },

    //
    //  Create an accented character using mover
    //
    Accent: function(name, c) {
      this.SaveText();
      var base = this.ParseArg(name);
      var accent = MML.mo(MML.chars(c));
      if (this.env.mathvariant) accent.mathvariant = this.env.mathvariant;
      this.Push(MML.mover(base, accent));
    },

    //
    //  Switch to/from italics
    //
    Emph: function(name) {
      this.UseFont(name, this.env.mathvariant === "-tex-mathit" ? "normal" : "-tex-mathit");
    },

    //
    //  Use a given font on its argument
    //
    UseFont: function(name, variant) {
      this.SaveText();
      this.Push(this.ParseTextArg(name, { mathvariant: variant }));
    },

    //
    //  Set a font for the rest of the text
    //
    SetFont: function(name, variant) {
      this.SaveText();
      this.env.mathvariant = variant;
    },

    //
    //  Set the size to use
    //
    SetSize: function(name, size) {
      this.SaveText();
      this.env.mathsize = size;
    },

    //
    //  Process the argument as text with the given environment settings
    //
    ParseTextArg: function(name, env) {
      var text = this.GetArgument(name);
      env = MathJax.Hub.Insert(MathJax.Hub.Insert({}, this.env), env);
      delete env.oldEnv;
      return TextParser(text, env).Parse();
    },

    //
    //  Process an argument as text (overrides the math-mode version)
    //
    ParseArg: function(name) {
      var mml = TextParser(this.GetArgument(name), this.env).Parse();
      if (mml.length === 0) return mml[0];
      return MML.mrow.apply(MML.mrow, mml);
    },

    //
    //  Create an mtext element with the accumulated text, if any
    //    and set it variant
    //
    SaveText: function() {
      if (this.text) {
        var text = MML.mtext(MML.chars(this.text));
        if (this.env.mathvariant) text.mathvariant = this.env.mathvariant;
        this.Push(text);
      }
      this.text = "";
    },

    //
    //  Save a MathML element or array, setting its size and color, if any
    //
    Push: function(mml) {
      if (mml instanceof Array) {
        if (this.env.mathsize || this.env.mathcolor) {
          mml = MML.mstyle.apply(MML, mml);
          if (this.env.mathsize) mml.mathsize = this.env.mathsize;
          if (this.env.mathcolor) mml.mathcolor = this.env.mathcolor;
        }
        this.mml.push.apply(this.mml, mml);
      } else {
        if (this.env.mathsize && !mml.mathsize) mml.mathsize = this.env.mathsize;
        if (this.env.mathcolor && !mml.mathcolor) mml.mathcolor = this.env.mathcolor;
        this.mml.push(mml);
      }
    },

    //
    //  Throw an error
    //
    Error: function(message) {
      TEX.Error(message);
    }
  }));

  MathJax.Hub.Startup.signal.Post("TeX text-macros Ready");
});

MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/text-macros.js");
