From 15b392889184f43d5ef89baf0f9aa2487b9af78c Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sat, 14 Feb 2015 15:30:37 -0500 Subject: [PATCH] Break HTML-CSS output into phases so that the final measurement can be done all at once. Restores timing improvements that were lost due to a change made in the testing for v2.5. --- unpacked/MathJax.js | 18 +- unpacked/jax/output/HTML-CSS/jax.js | 279 ++++++++++++++++------------ 2 files changed, 170 insertions(+), 127 deletions(-) diff --git a/unpacked/MathJax.js b/unpacked/MathJax.js index e4df3422a..40cf99b6c 100644 --- a/unpacked/MathJax.js +++ b/unpacked/MathJax.js @@ -2273,15 +2273,23 @@ MathJax.Hub = { var jax = script.MathJax.elementJax; if (!jax) {state.i++; continue} // // Call the output Jax's Process method (which will be its Translate() - // method once loaded). Mark it as complete and remove the preview. + // method once loaded). Mark it as complete and remove the preview unless + // the Process() call returns an explicit false value (in which case, it will + // handle this later during the postProcess phase, as HTML-CSS does). // result = MathJax.OutputJax[jax.outputJax].Process(script,state); - script.MathJax.state = STATE.PROCESSED; state.i++; - if (script.MathJax.preview) {script.MathJax.preview.innerHTML = ""} + if (result !== false) { + script.MathJax.state = STATE.PROCESSED; + if (script.MathJax.preview) {script.MathJax.preview.innerHTML = ""} + // + // Signal that new math is available + // + this.signal.Post(["New Math",jax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) + } // - // Signal that new math is available + // Go on to next math expression // - this.signal.Post(["New Math",jax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) + state.i++; // // Update the processing message, if needed // diff --git a/unpacked/jax/output/HTML-CSS/jax.js b/unpacked/jax/output/HTML-CSS/jax.js index a7f706eda..ca3e3f7f1 100644 --- a/unpacked/jax/output/HTML-CSS/jax.js +++ b/unpacked/jax/output/HTML-CSS/jax.js @@ -176,7 +176,6 @@ loadWebFont: function (font) { HUB.Startup.signal.Post("HTML-CSS Jax - Web-Font "+HTMLCSS.fontInUse+"/"+font.directory); var n = MESSAGE(["LoadWebFont","Loading web-font %1",HTMLCSS.fontInUse+"/"+font.directory]); - if (HTMLCSS.noReflows) return; var done = MathJax.Callback({}); // called when font is loaded var callback = MathJax.Callback(["loadComplete",this,font,n,done]); AJAX.timer.start(AJAX,[this.checkWebFont,font,callback],0,this.timeout); @@ -240,7 +239,7 @@ }); var EVENT, TOUCH, HOVER; // filled in later - + HTMLCSS.Augment({ config: { styles: { @@ -358,8 +357,6 @@ }, settings: HUB.config.menuSettings, - hideProcessedMath: true, // use display:none until all math is processed - Font: null, // created by Config() below webFontDefault: "MathJax_Blank", allowWebFonts: "otf", // assume browser can use OTF web fonts @@ -637,6 +634,8 @@ state.HTMLCSSdelay = false; }, + PHASE: {I: 1, II: 2, III: 3}, // processing phases + Translate: function (script,state) { if (!script.parentNode) return; @@ -658,10 +657,7 @@ // // Set the font metrics // - this.em = MML.mbase.prototype.em = jax.HTMLCSS.em * jax.HTMLCSS.scale; - this.outerEm = jax.HTMLCSS.em; this.scale = jax.HTMLCSS.scale; - this.cwidth = jax.HTMLCSS.cwidth; - this.linebreakWidth = jax.HTMLCSS.lineWidth; + this.getMetrics(jax); if (this.scale !== 1) {span.style.fontSize = jax.HTMLCSS.fontSize} // // Typeset the math @@ -671,7 +667,8 @@ this.savePreview(script); try { math.setTeXclass(); - math.toHTML(span,div); + jax.HTMLCSS.span = span; jax.HTMLCSS.div = div; // save for phase II and III + math.toHTML(span,div,this.PHASE.I); } catch (err) { if (err.restart) {while (span.firstChild) {span.removeChild(span.firstChild)}} this.restorePreview(script); @@ -679,32 +676,21 @@ } this.restorePreview(script); // - // Put it in place, and remove the processing marker + // Put it in place, remove the processing marker, and signal the new math // if (jax.HTMLCSS.isHidden) {script.parentNode.insertBefore(div,script)} - div.className = div.className.split(/ /)[0]; + div.className = div.className.split(/ /)[0] + " MathJax_Processed"; + HUB.signal.Post(["New Math Pending",jax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) // - // Check if we are hiding the math until more is processed + // Check if we should show this chunk of equations // - if (this.hideProcessedMath) { - // - // Hide the math and don't let its preview be removed - // - div.className += " MathJax_Processed"; - if (script.MathJax.preview) { - jax.HTMLCSS.preview = script.MathJax.preview; - delete script.MathJax.preview; - } - // - // Check if we should show this chunk of equations - // - state.HTMLCSSeqn += (state.i - state.HTMLCSSi); state.HTMLCSSi = state.i; - if (state.HTMLCSSeqn >= state.HTMLCSSlast + state.HTMLCSSchunk) { - this.postTranslate(state,true); - state.HTMLCSSchunk = Math.floor(state.HTMLCSSchunk*this.config.EqnChunkFactor); - state.HTMLCSSdelay = true; // delay if there are more scripts - } + state.HTMLCSSeqn += (state.i - state.HTMLCSSi); state.HTMLCSSi = state.i; + if (state.HTMLCSSeqn >= state.HTMLCSSlast + state.HTMLCSSchunk) { + this.postTranslate(state,true); + state.HTMLCSSchunk = Math.floor(state.HTMLCSSchunk*this.config.EqnChunkFactor); + state.HTMLCSSdelay = true; // delay if there are more scripts } + return false; }, // // MathML previews can contain the same ID's as the HTML output, @@ -725,16 +711,47 @@ delete script.MathJax.tmpPreview; } }, + // + // Get the jax metric information + // + getMetrics: function(jax) { + var data = jax.HTMLCSS; + this.em = MML.mbase.prototype.em = data.em * data.scale; + this.outerEm = data.em; + this.scale = data.scale; + this.cwidth = data.cwidth; + this.linebreakWidth = data.lineWidth; + }, postTranslate: function (state,partial) { - var scripts = state.jax[this.id]; - if (!this.hideProcessedMath) return; + var scripts = state.jax[this.id], script, jax, i, m; + // + // Merasure the math in this chunk (toHTML phase II) + // + for (i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { + script = scripts[i]; + if (script && script.MathJax.elementJax) { + jax = script.MathJax.elementJax; this.getMetrics(jax); + jax.root.toHTML(jax.HTMLCSS.span,jax.HTMLCSS.div,this.PHASE.II); + } + } // // Reveal this chunk of math // - for (var i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { + for (i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { var script = scripts[i]; if (script && script.MathJax.elementJax) { + // + // Finish the math with its measured size (toHTML phase III) + // + jax = script.MathJax.elementJax; this.getMetrics(jax); + jax.root.toHTML(jax.HTMLCSS.span,jax.HTMLCSS.div,this.PHASE.III); + delete jax.HTMLCSS.span; delete jax.HTMLCSS.div; + // + // The math is now fully processed + // + script.MathJax.state = jax.STATE.PROCESSED; + HUB.signal.Post(["New Math",script.MathJax.elementJax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) // // Remove the processed marker // @@ -743,11 +760,7 @@ // // Remove the preview, if any // - if (data.preview) { - data.preview.innerHTML = ""; - script.MathJax.preview = data.preview; - delete data.preview; - } + if (script.MathJax.preview) {script.MathJax.preview.innerHTML = ""} } } if (this.forceReflow) { @@ -1113,8 +1126,7 @@ width:0, height:H, verticalAlign:D}, bbox: {h:h, d:d, w:w, rw:w, lw:0, exactW:true}, noAdjust:true, HH:h+d, isMathJax:true }); - // ### FIXME: figure out which IE has this bug and make a flag for it - if (w > 0 && !this.noReflows && rule.offsetWidth == 0) {rule.style.width = this.Em(w)} + if (this.msieRuleBug && w > 0) {rule.style.width = this.Em(w)} if (span.isBox || span.className == "mspace") {span.bbox = rule.bbox, span.HH = h+d} return rule; }, @@ -2820,93 +2832,115 @@ MML.annotation.Augment({toHTML: function (span) {return this.HTMLcreateSpan(span)}}); MML.math.Augment({ - toHTML: function (span,node) { - var nobr = HTMLCSS.addElement(span,"nobr",{isMathJax: true}); - span = this.HTMLcreateSpan(nobr); - var alttext = this.Get("alttext"); - if (alttext && !span.getAttribute("aria-label")) span.setAttribute("aria-label",alttext); - if (!span.getAttribute("role")) span.setAttribute("role","math"); -// span.setAttribute("tabindex",0); // causes focus outline, so disable for now - var stack = HTMLCSS.createStack(span), box = HTMLCSS.createBox(stack), math; - // Move font-size from outer span to stack to avoid line separation - // problem in strict HTML mode - stack.style.fontSize = nobr.parentNode.style.fontSize; nobr.parentNode.style.fontSize = ""; - if (this.data[0] != null) { - if (HTMLCSS.msieColorBug) { - if (this.background) {this.data[0].background = this.background; delete this.background} - if (this.mathbackground) {this.data[0].mathbackground = this.mathbackground; delete this.mathbackground} - } - MML.mbase.prototype.displayAlign = HUB.config.displayAlign; - MML.mbase.prototype.displayIndent = HUB.config.displayIndent; - if (String(HUB.config.displayIndent).match(/^0($|[a-z%])/i)) - MML.mbase.prototype.displayIndent = "0"; - var html = this.data[0].toHTML(box); html.bbox.exactW = false; // force remeasure just to be sure - math = HTMLCSS.Measured(html,box); - } - HTMLCSS.placeBox(box,0,0); + toHTML: function (span,node,phase) { + var stack, box, html, math; // - // Get width right if minimum font size is set: - // Round to nearest pixel (plus a small amount), and convert back to outer-em's. - // Add the width to the span (outside the MathJax class, so uses outer em size, - // which makes it work even when minimum font size is in effect). + // Phase I lays out the math, but doesn't measure the final math yet + // (that is done for a chunk at a time, to avoid reflows) // - span.style.width = HTMLCSS.Em(Math.max(0,Math.round(math.bbox.w*this.em)+.25)/HTMLCSS.outerEm); - span.style.display = "inline-block"; - // - // Adjust bbox to match outer em-size - // - var p = 1/HTMLCSS.em, f = HTMLCSS.em / HTMLCSS.outerEm; HTMLCSS.em /= f; - span.bbox.h *= f; span.bbox.d *= f; span.bbox.w *= f; - span.bbox.lw *= f; span.bbox.rw *= f; - if (span.bbox.H) {span.bbox.H *= f} - if (span.bbox.D) {span.bbox.D *= f} - if (math && math.bbox.width != null) { - span.style.minWidth = (math.bbox.minWidth || span.style.width); - span.style.width = math.bbox.width; - box.style.width = stack.style.width = "100%"; - } - // - // Add color (if any) - // - var color = this.HTMLhandleColor(span); - // - // Make math span be the correct height and depth - // - if (math) {HTMLCSS.createRule(span,(math.bbox.h+p)*f,(math.bbox.d+p)*f,0)} - // - // Handle indentalign and indentshift for single-line display equations - // - if (!this.isMultiline && this.Get("display") === "block" && span.bbox.width == null) { - var values = this.getValues("indentalignfirst","indentshiftfirst","indentalign","indentshift"); - if (values.indentalignfirst !== MML.INDENTALIGN.INDENTALIGN) {values.indentalign = values.indentalignfirst} - if (values.indentalign === MML.INDENTALIGN.AUTO) {values.indentalign = this.displayAlign} - if (values.indentshiftfirst !== MML.INDENTSHIFT.INDENTSHIFT) {values.indentshift = values.indentshiftfirst} - if (values.indentshift === "auto") {values.indentshift = "0"} - var shift = HTMLCSS.length2em(values.indentshift,1,HTMLCSS.scale*HTMLCSS.cwidth); - if (this.displayIndent !== "0") { - var indent = HTMLCSS.length2em(this.displayIndent,1,HTMLCSS.scale*HTMLCSS.cwidth); - shift += (values.indentalign === MML.INDENTALIGN.RIGHT ? -indent : indent); + if (!phase || phase === HTMLCSS.PHASE.I) { + var nobr = HTMLCSS.addElement(span,"nobr",{isMathJax: true}); + span = this.HTMLcreateSpan(nobr); + var alttext = this.Get("alttext"); + if (alttext && !span.getAttribute("aria-label")) span.setAttribute("aria-label",alttext); + if (!span.getAttribute("role")) span.setAttribute("role","math"); +// span.setAttribute("tabindex",0); // causes focus outline, so disable for now + stack = HTMLCSS.createStack(span); box = HTMLCSS.createBox(stack); + // Move font-size from outer span to stack to avoid line separation + // problem in strict HTML mode + stack.style.fontSize = nobr.parentNode.style.fontSize; nobr.parentNode.style.fontSize = ""; + if (this.data[0] != null) { + if (HTMLCSS.msieColorBug) { + if (this.background) {this.data[0].background = this.background; delete this.background} + if (this.mathbackground) {this.data[0].mathbackground = this.mathbackground; delete this.mathbackground} + } + MML.mbase.prototype.displayAlign = HUB.config.displayAlign; + MML.mbase.prototype.displayIndent = HUB.config.displayIndent; + if (String(HUB.config.displayIndent).match(/^0($|[a-z%])/i)) + MML.mbase.prototype.displayIndent = "0"; + html = this.data[0].toHTML(box); html.bbox.exactW = true; // force remeasure just to be sure } - node.style.textAlign = values.indentalign; - // ### FIXME: make percentage widths respond to changes in container - if (shift) { - shift *= HTMLCSS.em/HTMLCSS.outerEm; - HUB.Insert(span.style,({ - left: {marginLeft: HTMLCSS.Em(shift)}, - right: {marginLeft: HTMLCSS.Em(Math.max(0,span.bbox.w+shift)), marginRight: HTMLCSS.Em(-shift)}, - center: {marginLeft: HTMLCSS.Em(shift), marginRight: HTMLCSS.Em(-shift)} - })[values.indentalign]); - // - // Move the background color, of any - // - if (color) { - color.style.marginLeft = HTMLCSS.Em(parseFloat(color.style.marginLeft)+shift); - color.style.marginRight = - HTMLCSS.Em(parseFloat(color.style.marginRight)-shift - + (values.indentalign === "right" ? Math.min(0,span.bbox.w+shift) - span.bbox.w : 0)); + } else { + span = span.firstChild.firstChild; + if (this.href) span = span.firstChild; + stack = span.firstChild; + if (stack.style.position !== "relative") stack = stack.nextSibling; + box = stack.firstChild; + html = box.firstChild; + } + // + // Phase II measures the math (this is done for each one in the chunk at once) + // + math = ((!phase || phase === HTMLCSS.PHASE.II) ? HTMLCSS.Measured(html,box) : html); + // + // Phase III finishes the layout using the measured math + // + if (!phase || phase === HTMLCSS.PHASE.III) { + HTMLCSS.placeBox(box,0,0); + // + // Get width right if minimum font size is set: + // Round to nearest pixel (plus a small amount), and convert back to outer-em's. + // Add the width to the span (outside the MathJax class, so uses outer em size, + // which makes it work even when minimum font size is in effect). + // + span.style.width = HTMLCSS.Em(Math.max(0,Math.round(math.bbox.w*this.em)+.25)/HTMLCSS.outerEm); + span.style.display = "inline-block"; + // + // Adjust bbox to match outer em-size + // + var p = 1/HTMLCSS.em, f = HTMLCSS.em / HTMLCSS.outerEm; HTMLCSS.em /= f; + span.bbox.h *= f; span.bbox.d *= f; span.bbox.w *= f; + span.bbox.lw *= f; span.bbox.rw *= f; + if (span.bbox.H) {span.bbox.H *= f} + if (span.bbox.D) {span.bbox.D *= f} + if (math && math.bbox.width != null) { + span.style.minWidth = (math.bbox.minWidth || span.style.width); + span.style.width = math.bbox.width; + box.style.width = stack.style.width = "100%"; + } + // + // Add color (if any) + // + var color = this.HTMLhandleColor(span); + // + // Make math span be the correct height and depth + // + if (math) {HTMLCSS.createRule(span,(math.bbox.h+p)*f,(math.bbox.d+p)*f,0)} + // + // Handle indentalign and indentshift for single-line display equations + // + if (!this.isMultiline && this.Get("display") === "block" && span.bbox.width == null) { + var values = this.getValues("indentalignfirst","indentshiftfirst","indentalign","indentshift"); + if (values.indentalignfirst !== MML.INDENTALIGN.INDENTALIGN) {values.indentalign = values.indentalignfirst} + if (values.indentalign === MML.INDENTALIGN.AUTO) {values.indentalign = this.displayAlign} + if (values.indentshiftfirst !== MML.INDENTSHIFT.INDENTSHIFT) {values.indentshift = values.indentshiftfirst} + if (values.indentshift === "auto") {values.indentshift = "0"} + var shift = HTMLCSS.length2em(values.indentshift,1,HTMLCSS.scale*HTMLCSS.cwidth); + if (this.displayIndent !== "0") { + var indent = HTMLCSS.length2em(this.displayIndent,1,HTMLCSS.scale*HTMLCSS.cwidth); + shift += (values.indentalign === MML.INDENTALIGN.RIGHT ? -indent : indent); + } + node.style.textAlign = values.indentalign; + // ### FIXME: make percentage widths respond to changes in container + if (shift) { + shift *= HTMLCSS.em/HTMLCSS.outerEm; + HUB.Insert(span.style,({ + left: {marginLeft: HTMLCSS.Em(shift)}, + right: {marginLeft: HTMLCSS.Em(Math.max(0,span.bbox.w+shift)), marginRight: HTMLCSS.Em(-shift)}, + center: {marginLeft: HTMLCSS.Em(shift), marginRight: HTMLCSS.Em(-shift)} + })[values.indentalign]); + // + // Move the background color, of any + // + if (color) { + color.style.marginLeft = HTMLCSS.Em(parseFloat(color.style.marginLeft)+shift); + color.style.marginRight = + HTMLCSS.Em(parseFloat(color.style.marginRight)-shift + + (values.indentalign === "right" ? Math.min(0,span.bbox.w+shift) - span.bbox.w : 0)); + } } } - } + } return span; }, HTMLspanElement: MML.mbase.prototype.HTMLspanElement @@ -2988,6 +3022,7 @@ msiePlaceBoxBug: (isIE8 && !quirks), msieClipRectBug: !isIE8, msieNegativeSpaceBug: quirks, + msieRuleBug: (mode < 7), // rules need to be measured cloneNodeBug: (isIE8 && browser.version === "8.0"), msieItalicWidthBug: true, // can't measure boxes ending in italics correctly initialSkipBug: (mode < 8), // confused by initial left-margin values