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