Skip to content

Commit bb216e1

Browse files
committed
bug fixes: disable automatic line breaks, capture text-anchor at start of render, make sure raw input text is hidden at end of MathJax render, start render chain with Promise.resolve()
1 parent 4d199e1 commit bb216e1

1 file changed

Lines changed: 43 additions & 37 deletions

File tree

src/lib/svg_text_utils.js

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ exports.convertToTspans = function(_context, gd, _callback) {
7878
_context.style('display', 'none');
7979
var fontSize = parseInt(_context.node().style.fontSize, 10);
8080
var config = {fontSize: fontSize};
81+
// Capture the text-anchor value now, since the the callback function below
82+
// will run async, and in the meantime the text-anchor may change
83+
// which would result in a wrongly-positioned SVG
84+
var textAnchor = _context.attr('text-anchor');
8185

8286
texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
8387
parent.selectAll('svg.' + svgClass).remove();
@@ -90,6 +94,11 @@ exports.convertToTspans = function(_context, gd, _callback) {
9094
return;
9195
}
9296

97+
// Now that the MathJax render has finished, re-hide the source text.
98+
// We hid it earlier, too, but since this callback runs async,
99+
// another function may have made it visible again
100+
_context.style('display', 'none');
101+
93102
var mathjaxGroup = parent.append('g')
94103
.classed(svgClass + '-group', true)
95104
.attr({
@@ -151,11 +160,9 @@ exports.convertToTspans = function(_context, gd, _callback) {
151160
x = 0;
152161
y = dy;
153162
} else {
154-
var anchor = _context.attr('text-anchor');
155-
156163
x = x - w * (
157-
anchor === 'middle' ? 0.5 :
158-
anchor === 'end' ? 1 : 0
164+
textAnchor === 'middle' ? 0.5 :
165+
textAnchor === 'end' ? 1 : 0
159166
);
160167
y = y + dy - h / 2;
161168
}
@@ -185,7 +192,11 @@ function cleanEscapesForTex(s) {
185192
.replace(GT_MATCH, '\\gt ');
186193
}
187194

188-
var inlineMath = [['$', '$'], ['\\(', '\\)']];
195+
// Create a dedicated MathDocument just for Plotly.js,
196+
// to avoid interfering with any other MathJax on the page.
197+
// mathjaxSVGDocument is initialized on the first call to texToSVG(),
198+
// and reused for subsequent calls.
199+
var mathjaxSVGDocument = null;
189200

190201
function texToSVG(_texString, _config, _callback) {
191202
const MathJaxVersion = parseInt(
@@ -200,37 +211,41 @@ function texToSVG(_texString, _config, _callback) {
200211
return;
201212
}
202213

203-
var originalConfig,
204-
tmpDiv;
205-
206-
const setConfig = function() {
207-
originalConfig = Lib.extendDeepAll({}, MathJax.config);
208-
209-
if(!MathJax.config.tex) {
210-
MathJax.config.tex = {};
211-
}
214+
var tmpDiv;
212215

213-
MathJax.config.tex.inlineMath = inlineMath;
214-
215-
if(MathJax.config.startup.output !== 'svg') {
216-
MathJax.config.startup.output = 'svg';
216+
const initiateMathJax = function() {
217+
if(!mathjaxSVGDocument) {
218+
const SVG = MathJax._.output.svg_ts.SVG;
219+
// fontCache 'local' keeps each rendered svg self-contained
220+
const svgConfig = Lib.extendFlat({}, MathJax.config.svg, {fontCache: 'local'});
221+
// MathJax v4 enables automatic inline linebreaking by default, which
222+
// messes up our layout assumptions. Disabling it gives behavior consistent with v3.
223+
if(MathJaxVersion === 4) {
224+
svgConfig.linebreaks = Lib.extendFlat({}, svgConfig.linebreaks, {inline: false});
225+
}
226+
mathjaxSVGDocument = MathJax._.mathjax.mathjax.document(document, Lib.extendFlat({}, MathJax.config.options, {
227+
InputJax: MathJax.startup.input,
228+
OutputJax: new SVG(svgConfig)
229+
}));
217230
}
218-
};
219231

220-
const initiateMathJax = function() {
221232
const randomID = 'math-output-' + Lib.randstr({}, 64);
222233
tmpDiv = d3.select('body').append('div')
223234
.attr({id: randomID})
224235
.style({
225236
visibility: 'hidden',
226237
position: 'absolute',
227238
'font-size': _config.fontSize + 'px'
228-
})
229-
.text(cleanEscapesForTex(_texString));
230-
231-
const tmpNode = tmpDiv.node();
239+
});
232240

233-
return MathJax.typesetPromise([tmpNode]);
241+
// Strip $…$ delimiters and clean up escape charaters,
242+
// then pass the result to MathDocument.convert() to get an SVG element back
243+
const texMath = cleanEscapesForTex(_texString).replace(/^\$+|\$+$/g, '');
244+
return MathJax._.mathjax.mathjax.handleRetriesFor(function() {
245+
return mathjaxSVGDocument.convert(texMath, {display: false});
246+
}).then(function(node) {
247+
tmpDiv.node().appendChild(node);
248+
});
234249
};
235250

236251
const finalizeMathJax = function() {
@@ -249,24 +264,15 @@ function texToSVG(_texString, _config, _callback) {
249264
tmpDiv.remove();
250265
};
251266

252-
// Restore the original state of the global MathJax config we mutated above
253-
// This also restores the renderer to its original value
254-
const resetConfig = function() {
255-
MathJax.config = originalConfig;
256-
};
257-
258-
// Set up MathJax, render tex, then clean up and return
259-
setConfig();
260-
MathJax.startup.defaultReady();
261-
MathJax.startup.promise
267+
// Initialize, render MathJax, then call _callback()
268+
Promise.resolve()
262269
.then(initiateMathJax)
263270
.then(finalizeMathJax)
264271
.catch((err) => {
265272
Lib.log('MathJax typesetting failed.', _texString, err);
266273
if(tmpDiv) tmpDiv.remove();
267274
_callback();
268-
})
269-
.then(resetConfig);
275+
});
270276
}
271277

272278
var TAG_STYLES = {

0 commit comments

Comments
 (0)