@@ -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
190201function 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
272278var TAG_STYLES = {
0 commit comments