1// Copyright 2013 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Utility functions for the MathJax bridge. It contains
17 * functionality that changes the normal behaviour of MathJax contributed by
18 * Davide Cervone (dpvc@union.edu) and adapted by Volker Sorge
19 * (sorge@google.com).
20 * This is the only file that should contain actual MathJax code!
21 *
22 */
23
24if (typeof(goog) != 'undefined' && goog.provide) {
25  goog.provide('cvox.MathJaxExternalUtil');
26}
27
28
29if (!window['cvox']) {
30   window['cvox'] = {};
31}
32
33/**
34 * @constructor
35 */
36cvox.MathJaxExternalUtil = function() {
37};
38
39
40/**
41 * Returns a string with Mathml attributes for a MathJax object.  This serves as
42 * intermediate store for the original function when we temporarily change
43 * MathJax's output behaviour.
44 * @return {string}
45 */
46cvox.MathJaxExternalUtil.mmlAttr = function() {
47  return '';
48};
49
50
51/**
52 * Rewrites an mfenced expression internally in MathJax to a corresponding mrow.
53 * @param {?string} space The separator expression.
54 * @return {string} The new mrow expression as a string.
55 * @this {MathJax.RootElement}
56 */
57cvox.MathJaxExternalUtil.mfenced = function(space) {
58    if (space == null) {
59      space = '';
60    }
61    var mml = [space + '<mrow mfenced="true"' +
62        this.toMathMLattributes() + '>'];
63    var mspace = space + '  ';
64    if (this.data.open) {
65      mml.push(this.data.open.toMathML(mspace));
66    }
67    if (this.data[0] != null) {
68      mml.push(this.data[0].toMathML(mspace));
69    }
70    for (var i = 1, m = this.data.length; i < m; i++) {
71      if (this.data[i]) {
72        if (this.data['sep' + i]) {
73          mml.push(this.data['sep' + i].toMathML(mspace));
74        }
75        mml.push(this.data[i].toMathML(mspace));
76      }
77    }
78  if (this.data.close) {
79    mml.push(this.data.close.toMathML(mspace));
80  }
81  mml.push(space + '</mrow>');
82  return mml.join('\n');
83};
84
85
86/**
87 * Compute the MathML representation of a MathJax element.
88 * @param {MathJax.Jax} jax MathJax object.
89 * @param {function(string)} callback Callback function.
90 * @return {Function} Callback function for restart.
91 * @this {cvox.MathJaxExternalUtil}
92 */
93cvox.MathJaxExternalUtil.getMathml = function(jax, callback) {
94  var mbaseProt = MathJax.ElementJax.mml.mbase.prototype;
95  var mfencedProt = MathJax.ElementJax.mml.mfenced.prototype;
96  this.mmlAttr = mbaseProt.toMathMLattributes;
97  var mfenced = mfencedProt.toMathML;
98  try {
99      mbaseProt.toMathMLattributes = cvox.MathJaxExternalUtil.mbase;
100      mfencedProt.toMathML = cvox.MathJaxExternalUtil.mfenced;
101      var mml = jax.root.toMathML('');
102      mbaseProt.toMathMLattributes = this.mmlAttr;
103      mfencedProt.toMathML = mfenced;
104      MathJax.Callback(callback)(mml);
105  } catch (err) {
106    mbaseProt.toMathMLattributes = this.mmlAttr;
107    mfencedProt.toMathML = mfenced;
108    if (!err['restart']) {
109      throw err;
110    }
111    return MathJax.Callback.After(
112        [cvox.MathJaxExternalUtil.getMathml, jax, callback], err['restart']);
113  }
114};
115
116
117/**
118 * Compute the special span ID attribute.
119 * @return {string} The MathJax spanID attribute string.
120 * @this {MathJax.RootElement}
121 */
122cvox.MathJaxExternalUtil.mbase = function() {
123  var attr = cvox.MathJaxExternalUtil.mmlAttr.call(this);
124  if (this.spanID != null) {
125    var id = (this.id || 'MathJax-Span-' + this.spanID) +
126        MathJax.OutputJax['HTML-CSS']['idPostfix'];
127    attr += ' spanID="' + id + '"';
128  }
129  if (this.texClass != null) {
130    attr += ' texClass="' + this.texClass + '"';
131  }
132  return attr;
133};
134
135
136/**
137 * Test that ensures that all important parts of MathJax have been initialized
138 * at startup.
139 * @return {boolean} True if MathJax is sufficiently initialised.
140 */
141cvox.MathJaxExternalUtil.isActive = function() {
142  return typeof(MathJax) != 'undefined' &&
143      typeof(MathJax.Hub) != 'undefined' &&
144      typeof(MathJax.ElementJax) != 'undefined' &&
145      typeof(MathJax.InputJax) != 'undefined';
146};
147
148
149/**
150 * Constructs a callback for a MathJax object with the purpose of returning the
151 * MathML representation of a particular jax given by its node id. The callback
152 * can be used by functions passing it to MathJax functions and is invoked by
153 * MathJax.
154 * @param {function(string, string)} callback A function taking a MathML
155 * expression and an id string.
156 * @param {MathJax.Jax} jax The MathJax object.
157 * @private
158 */
159cvox.MathJaxExternalUtil.getMathjaxCallback_ = function(callback, jax) {
160  cvox.MathJaxExternalUtil.getMathml(
161      jax,
162      function(mml) {
163        if (jax.root.inputID) {
164          callback(mml, jax.root.inputID);
165        }
166      });
167};
168
169
170/**
171 * Registers a callback for a particular Mathjax signal.
172 * @param {function(string, string)} callback A function taking an MathML
173 * expression and an id string.
174 * @param {string} signal The Mathjax signal on which to fire the callback.
175 */
176cvox.MathJaxExternalUtil.registerSignal = function(callback, signal) {
177  MathJax.Hub.Register.MessageHook(
178      signal,
179      function(signalAndIdPair) {
180        var jax = MathJax.Hub.getJaxFor(signalAndIdPair[1]);
181        cvox.MathJaxExternalUtil.getMathjaxCallback_(callback, jax);
182      });
183};
184
185
186/**
187 * Compute the MathML representation for all currently available MathJax
188 * nodes.
189 * @param {function(string, string)} callback A function taking a MathML
190 * expression and an id string.
191 */
192cvox.MathJaxExternalUtil.getAllJax = function(callback) {
193  var jaxs = MathJax.Hub.getAllJax();
194  if (jaxs) {
195    jaxs.forEach(function(jax) {
196      if (jax.root.spanID) {
197        cvox.MathJaxExternalUtil.getMathjaxCallback_(callback, jax);
198      }
199    });
200  }
201};
202
203
204// Functionality for direct translation from LaTeX to MathML without rendering.
205/**
206 * Injects a MathJax config script into the page.
207 * This script is picked up by MathJax at load time. It only runs in the page,
208 * thus in case it causes an exception it will not crash ChromeVox. The worst
209 * thing that can happen is that we do not get a MathML object for some
210 * LaTeX alternative text, i.e., we default to the usual behaviour of simply
211 * reading out the alt text directly.
212 */
213cvox.MathJaxExternalUtil.injectConfigScript = function() {
214  var script = document.createElement('script');
215  script.setAttribute('type', 'text/x-mathjax-config');
216  script.textContent =
217      'MathJax.Hub.Config({\n' +
218          // No output needed.
219      '  jax: ["input/AsciiMath", "input/TeX"],\n' +
220          // Load functionality for MathML translation.
221      '  extensions: ["toMathML.js"],\n' +
222          // Do not change any rendering in the page.
223      '  skipStartupTypeset: true,\n' +
224          // Do not display any MathJax status message.
225      '  messageStyle: "none",\n' +
226          // Load AMS math extensions.
227      '  TeX: {extensions: ["AMSmath.js","AMSsymbols.js"]}\n' +
228      '});\n' +
229      'MathJax.Hub.Queue(\n' +
230          // Force InputJax to load.
231      '  function() {MathJax.Hub.inputJax["math/asciimath"].Process();\n' +
232      '  MathJax.Hub.inputJax["math/tex"].Process()}\n' +
233      ');\n' +
234      '//\n' +
235      '// Prevent these from being loaded\n' +
236      '//\n' +
237          // Make sure that no pop up menu is created for the jax.
238      'if (!MathJax.Extension.MathMenu) {MathJax.Extension.MathMenu = {}};\n' +
239          // Make sure that jax is created unzoomed.
240      'if (!MathJax.Extension.MathZoom) {MathJax.Extension.MathZoom = {}};';
241  document.activeElement.appendChild(script);
242};
243
244
245/**
246 * Injects a MathJax load script into the page. This should only be injected
247 * after the config script. While the config script can adapted for different
248 * pages, the load script is generic.
249 *
250 */
251cvox.MathJaxExternalUtil.injectLoadScript = function() {
252  var script = document.createElement('script');
253  script.setAttribute('type', 'text/javascript');
254  script.setAttribute(
255      'src', 'http://cdn.mathjax.org/mathjax/latest/MathJax.js');
256  document.activeElement.appendChild(script);
257};
258
259
260/**
261 * Configures MathJax for MediaWiki pages (e.g., Wikipedia) by adding
262 * some special mappings to MathJax's symbol definitions. The function
263 * can only be successfully executed, once MathJax is injected and
264 * configured in the page.
265 */
266// Adapted from
267// https://en.wikipedia.org/wiki/User:Nageh/mathJax/config/TeX-AMS-texvc_HTML.js
268cvox.MathJaxExternalUtil.configMediaWiki = function() {
269  if (mediaWiki) {
270    MathJax.Hub.Register.StartupHook(
271      'TeX Jax Ready', function() {
272        var MML = MathJax.ElementJax.mml;
273        MathJax.Hub.Insert(
274          MathJax.InputJax.TeX.Definitions,
275          {
276            mathchar0mi: {
277              thetasym: '03B8',
278              koppa: '03DF',
279              stigma: '03DB',
280              varstigma: '03DB',
281              coppa: '03D9',
282              varcoppa: '03D9',
283              sampi: '03E1',
284              C: ['0043', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
285              cnums: ['0043', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
286              Complex: ['0043', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
287              H: ['210D', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
288              N: ['004E', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
289              natnums: ['004E', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
290              Q: ['0051', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
291              R: ['0052', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
292              reals: ['0052', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
293              Reals: ['0052', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
294              Z: ['005A', {mathvariant: MML.VARIANT.DOUBLESTRUCK}],
295              sect: '00A7',
296              P: '00B6',
297              AA: ['00C5', {mathvariant: MML.VARIANT.NORMAL}],
298              alef: ['2135', {mathvariant: MML.VARIANT.NORMAL}],
299              alefsym: ['2135', {mathvariant: MML.VARIANT.NORMAL}],
300              weierp: ['2118', {mathvariant: MML.VARIANT.NORMAL}],
301              real: ['211C', {mathvariant: MML.VARIANT.NORMAL}],
302              part: ['2202', {mathvariant: MML.VARIANT.NORMAL}],
303              infin: ['221E', {mathvariant: MML.VARIANT.NORMAL}],
304              empty: ['2205', {mathvariant: MML.VARIANT.NORMAL}],
305              O: ['2205', {mathvariant: MML.VARIANT.NORMAL}],
306              ang: ['2220', {mathvariant: MML.VARIANT.NORMAL}],
307              exist: ['2203', {mathvariant: MML.VARIANT.NORMAL}],
308              clubs: ['2663', {mathvariant: MML.VARIANT.NORMAL}],
309              diamonds: ['2662', {mathvariant: MML.VARIANT.NORMAL}],
310              hearts: ['2661', {mathvariant: MML.VARIANT.NORMAL}],
311              spades: ['2660', {mathvariant: MML.VARIANT.NORMAL}],
312              textvisiblespace: '2423',
313              geneuro: '20AC',
314              euro: '20AC'
315            },
316            mathchar0mo: {
317              and: '2227',
318              or: '2228',
319              bull: '2219',
320              plusmn: '00B1',
321              sdot: '22C5',
322              Dagger: '2021',
323              sup: '2283',
324              sub: '2282',
325              supe: '2287',
326              sube: '2286',
327              isin: '2208',
328              hAar: '21D4',
329              hArr: '21D4',
330              Harr: '21D4',
331              Lrarr: '21D4',
332              lrArr: '21D4',
333              lArr: '21D0',
334              Larr: '21D0',
335              rArr: '21D2',
336              Rarr: '21D2',
337              harr: '2194',
338              lrarr: '2194',
339              larr: '2190',
340              gets: '2190',
341              rarr: '2192',
342              oiint: ['222F', {texClass: MML.TEXCLASS.OP}],
343              oiiint: ['2230', {texClass: MML.TEXCLASS.OP}]
344            },
345            mathchar7: {
346              Alpha: '0391',
347              Beta: '0392',
348              Epsilon: '0395',
349              Zeta: '0396',
350              Eta: '0397',
351              Iota: '0399',
352              Kappa: '039A',
353              Mu: '039C',
354              Nu: '039D',
355              Omicron: '039F',
356              Rho: '03A1',
357              Tau: '03A4',
358              Chi: '03A7',
359              Koppa: '03DE',
360              Stigma: '03DA',
361              Digamma: '03DC',
362              Coppa: '03D8',
363              Sampi: '03E0'
364            },
365            delimiter: {
366              '\\uarr': '2191',
367              '\\darr': '2193',
368              '\\Uarr': '21D1',
369              '\\uArr': '21D1',
370              '\\Darr': '21D3',
371              '\\dArr': '21D3',
372              '\\rang': '27E9',
373              '\\lang': '27E8'
374            },
375            macros: {
376              sgn: 'NamedFn',
377              arccot: 'NamedFn',
378              arcsec: 'NamedFn',
379              arccsc: 'NamedFn',
380              sen: 'NamedFn',
381              image: ['Macro', '\\Im'],
382              bold: ['Macro', '\\mathbf{#1}', 1],
383              pagecolor: ['Macro', '', 1],
384              emph: ['Macro', '\\textit{#1}', 1],
385              textsf: ['Macro', '\\mathord{\\sf{\\text{#1}}}', 1],
386              texttt: ['Macro', '\\mathord{\\tt{\\text{#1}}}', 1],
387              vline: ['Macro', '\\smash{\\large\\lvert}', 0]
388            }
389          });
390      });
391  }
392};
393
394
395/**
396 * Converts an expression into MathML string.
397 * @param {function(string)} callback Callback function called with the MathML
398 * string after it is produced.
399 * @param {string} math The math Expression.
400 * @param {string} typeString Type of the expression to be converted (e.g.,
401 * "math/tex", "math/asciimath")
402 * @param {string} filterString Name of object specifying the filters to be used
403 * by MathJax (e.g., TeX, AsciiMath)
404 * @param {string} errorString Name of the error object used by MathJax (e.g.,
405 * texError, asciimathError).
406 * @param {!function(string)} parseFunction The MathJax function used for
407 * parsing the particular expression. This depends on the kind of expression we
408 * have.
409 * @return {Function} If a restart occurs, the callback for it is
410 * returned, so this can be used in MathJax.Hub.Queue() calls reliably.
411 */
412cvox.MathJaxExternalUtil.convertToMml = function(
413    callback, math, typeString, filterString, errorString, parseFunction) {
414  //  Make a fake script and pass it to the pre-filters.
415  var script = MathJax.HTML.Element('script', {type: typeString}, [math]);
416  var data = {math: math, script: script};
417  MathJax.InputJax[filterString].prefilterHooks.Execute(data);
418
419  //  Attempt to parse the code, processing any errors.
420  var mml;
421  try {
422    mml = parseFunction(data.math);
423  } catch (err) {
424    if (err[errorString]) {
425      // Put errors into <merror> tags.
426      mml = MathJax.ElementJax.mml.merror(err.message.replace(/\n.*/, ''));
427    } else if (err['restart']) {
428      //  Wait for file to load, then do this routine again.
429      return MathJax.Callback.After(
430          [cvox.MathJaxExternalUtil.convertToMml, callback, math,
431           typeString, filterString, errorString, parseFunction],
432          err['restart']);
433    } else {
434      //  It's an actual error, so pass it on.
435      throw err;
436    }
437  }
438
439  //  Make an ElementJax from the tree, call the post-filters, and get the
440  //  MathML.
441  if (mml.inferred) {
442    mml = MathJax.ElementJax.mml.apply(MathJax.ElementJax, mml.data);
443  } else {
444    mml = MathJax.ElementJax.mml(mml);
445  }
446  mml.root.display = 'block';
447  data.math = mml;
448  // This is necessary to make this function work even if MathJax is already
449  // properly injected into the page, as this object is used in MathJax's
450  // AMSmath.js file.
451  data.script['MathJax'] = {};
452  MathJax.InputJax[filterString].postfilterHooks.Execute(data);
453  return cvox.MathJaxExternalUtil.getMathml(data.math, callback);
454};
455
456
457/**
458 * Converts a LaTeX expression into MathML string.
459 * @param {function(string)} callback Callback function called with the MathML
460 * string after it is produced.
461 * @param {string} math Expression latex.
462 */
463cvox.MathJaxExternalUtil.texToMml = function(callback, math) {
464  cvox.MathJaxExternalUtil.convertToMml(
465      callback, math, 'math/tex;mode=display', 'TeX', 'texError',
466      function(data) {return MathJax.InputJax.TeX.Parse(data).mml();});
467};
468
469
470/**
471 * Converts an AsciiMath expression into MathML string.
472 * @param {function(string)} callback Callback function called with the MathML
473 * string after it is produced.
474 * @param {string} math Expression in AsciiMath.
475 */
476cvox.MathJaxExternalUtil.asciiMathToMml = function(callback, math) {
477  cvox.MathJaxExternalUtil.convertToMml(
478      callback, math, 'math/asciimath', 'AsciiMath', 'asciimathError',
479      MathJax.InputJax.AsciiMath.AM.parseMath);
480};
481