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