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