Math/mathoid/engine.js

136 lines
3.7 KiB
JavaScript

// perfect singleton
window.engine = (new (function() {
this.Q = MathJax.Hub.queue;
this.math = null;
this.buffer = [];
function toMathML(jax,callback) {
var mml;
try {
mml = jax.root.toMathML('');
} catch(err) {
if (!err.restart) {throw err;} // an actual error
return MathJax.Callback.After([toMathML,jax,callback],err.restart);
}
MathJax.Callback(callback)(mml);
}
// bind helper.
this.bind = function(method) {
var engine = this;
return function() {
return method.apply(engine, arguments);
};
};
// initialize Engine, after MathJax is loaded, this.math will
// point to our jax.
this.init = function() {
this.Q.Push(this.bind(function () {
this.math = MathJax.Hub.getAllJax('math')[0];
this.processBuffered();
}));
};
// receives input latex string and invokes cb
// function with svg result.
this.processCB = function(latex, cb) {
this.Q.Push(['Text', this.math, latex]);
this.Q.Push(this.bind(function() {
// then, this toSVG call will invoke cb(result).
cb(document.getElementsByTagName('svg')[1].cloneNode(true));
}));
};
// this is a helper for merge, who will want to decide
// whether something went wrong while rendering latex.
// the constant #C00 could be overriden by config!!
this.TextIsError = function(txt) {
return txt.getAttribute('fill') === '#C00' &&
txt.getAttribute('stroke') === 'none';
};
// mathjax keeps parts of SVG symbols in one hidden svg at
// the begining of the DOM, this function should take two
// SVGs and return one stand-alone svg which could be
// displayed like an image on some different page.
this.merge = function(svg) {
var uses,
copied = {},
k,
id,
texts,
i,
tmpDiv,
defs = document.getElementById('MathJax_SVG_Hidden')
.nextSibling.childNodes[0].cloneNode(false);
// clone and copy all used paths into local defs.
// xlink:href in uses FIX
uses = svg.getElementsByTagName('use');
for ( k = 0; k < uses.length; ++k) {
id = uses[k].getAttribute('href');
if (id && copied[id]) {
uses[k].setAttribute('xlink:href', id);
// Already copied, skip
continue;
}
defs.appendChild(
document.getElementById(id.substr(1)).cloneNode(true)
);
uses[k].setAttribute('xlink:href', id);
copied[id] = true;
}
// check for errors in svg.
texts = document.getElementsByTagName('text', svg);
for ( i = 0; i < texts.length; ++i) {
if (this.TextIsError(texts[i])) {
return [texts[i].textContent];
}
}
svg.style.position = 'static';
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
tmpDiv = document.createElement('div');
tmpDiv.appendChild(svg);
svg.insertBefore(defs, svg.firstChild);
return tmpDiv.innerHTML;
};
// if someone calls process before init is complete,
// that call will be stored into buffer. After the init
// is complete, all buffer stuff will get resolved.
this.processBuffered = function() {
for (var i = 0; i < this.buffer.length; ++i) {
this.process(this.buffer[i][0], this.buffer[i][1]);
}
this.buffer = [];
};
// callback will be invoked with array [original latex, SVG output]
// if there is an error during the latex rendering then second
// element (instead of SVG output) will be array again with
// only one string element describing the error message.
this.process = function(latex, cb) {
if (this.math === null) {
this.buffer.push( [latex, cb] );
} else {
try{
this.processCB(latex, this.bind(function( ) {
var jax = MathJax.Hub.getAllJax(),
mergedSVG = this.merge(document.getElementsByTagName('svg')[1].cloneNode(true));
toMathML(jax[0],function (mml) {
cb([latex, mergedSVG, mml]);
});
}));
} catch (err) {
cb([latex, err, err]);
}
}
};
this.init();
})());