UNPKG

mathjax

Version:

Beautiful math in all browsers. MathJax is an open-source JavaScript display engine for LaTeX, MathML, and AsciiMath notation that works in all browsers.

1,203 lines (1,139 loc) 142 kB
/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/HTML-CSS/jax.js * * Implements the HTML-CSS OutputJax that displays mathematics * using HTML and CSS to position the characters from math fonts * in their proper locations. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2019 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (AJAX,HUB,HTMLCSS) { var MML, isMobile = HUB.Browser.isMobile; var isArray = MathJax.Object.isArray; var MESSAGE = function () { var data = [].slice.call(arguments,0); data[0][0] = ["HTML-CSS",data[0][0]]; return MathJax.Message.Set.apply(MathJax.Message,data); }; var FONTTEST = MathJax.Object.Subclass({ timeout: (isMobile? 15:8)*1000, // timeout for loading web fonts comparisonFont: ["sans-serif","monospace","script","Times","Courier","Arial","Helvetica"], testSize: ["40px","50px","60px","30px","20px"], // // Fedora aliases STIXSizeOneSym to STIX Word, so MathJax thinks STIX is // available, but the fonts aren't actually correct. This is to test if // STIXSizeOneSym has letters in it (so is actually STIX Word). // FedoraSTIXcheck: {family:"STIXSizeOneSym", testString:"abcABC", noStyleChar:true}, Init: function () { // // Wrap the Font_Test DIV in a 0x0 DIV so that it takes no room // this.div = MathJax.HTML.addElement(document.body,"div",{style: { position:"absolute", width:0, height:0, overflow:"hidden", padding:0, border:0, margin:0 }},[["div",{ id: "MathJax_Font_Test", style: {position:"absolute", visibility:"hidden", top:0, left:0, width: "auto", "min-width": 0, "max-width": "none", padding:0, border:0, margin:0, whiteSpace:"nowrap", textAlign:"left", textIndent:0, textTransform:"none", lineHeight:"normal", letterSpacing:"normal", wordSpacing:"normal", fontSize:this.testSize[0], fontWeight:"normal", fontStyle:"normal", fontSizeAdjust:"none"} },[""]]] ).firstChild; this.text = this.div.firstChild; }, findFont: function (fonts,pref) { var found = null; if (pref && this.testCollection(pref)) { found = pref; } else { for (var i = 0, m = fonts.length; i < m; i++) { if (fonts[i] === pref) continue; if (this.testCollection(fonts[i])) {found = fonts[i]; break} } } if (found === "STIX" && this.testFont(this.FedoraSTIXcheck)) {found = null} return found; }, testCollection: function (name) { var font = {testString: "() {} []"}; font.family = {TeX:"MathJax_Size1", STIX:"STIXSizeOneSym"}[name] || name.replace(/-(Math)?/,"")+"MathJax_Size1"; if (name === "STIX") {font.noStyleChar = true} return this.testFont(font); }, testFont: function (font) { if (font.isWebFont && HTMLCSS.FontFaceBug) { this.div.style.fontWeight = this.div.style.fontStyle = "normal"; } else { this.div.style.fontWeight = (font.weight||"normal"); this.div.style.fontStyle = (font.style||"normal"); } // // Hack: Fix up web font names for local access. // (The names for Windows and Mac are different, unlike in the STIX and // TeX fonts, so we have to work out a list of names here.) // // This should be removed when the web fonts are fixed. FIXME // var family = font.familyFixed || font.family; if (!font.isWebFont && !family.match(/^(STIX|MathJax)|'/)) { family = family.replace(/_/g," ").replace(/([a-z])([A-Z])/g,"$1 $2").replace(/ Jax/,"Jax") + "','" + family + "','" + family + "-"; if (font.weight) {family += "Bold"}; if (font.style) {family += "Italic"} if (!font.weight && !font.style) {family += "Regular"} font.familyFixed = family = "'"+family+"'" } var W = this.getComparisonWidths(font.testString,font.noStyleChar); var found = null; if (W) { this.div.style.fontFamily = family+","+this.comparisonFont[0]; if (this.div.offsetWidth == W[0]) { this.div.style.fontFamily = family+","+this.comparisonFont[W[2]]; if (this.div.offsetWidth == W[1]) {found = false} } if (found === null && (this.div.offsetWidth != W[3] || this.div.offsetHeight != W[4])) { if (!font.noStyleChar && HTMLCSS.FONTDATA && HTMLCSS.FONTDATA.hasStyleChar) { for (var i = 0, m = this.testSize.length; i < m; i++) {if (this.testStyleChar(font,this.testSize[i])) {found = true; m = 0}} } else {found = true} } } if (HTMLCSS.safariTextNodeBug) {this.div.innerHTML = ""} else {this.text.nodeValue = ""} return found; }, styleChar: "\uEFFD", // width encodes style versionChar: "\uEFFE", // width encodes version compChar: "\uEFFF", // "standard" width to compare to testStyleChar: function (font,size) { var n = 3 + (font.weight ? 2 : 0) + (font.style ? 4 : 0); var extra = "", dw = 0; var SIZE = this.div.style.fontSize; this.div.style.fontSize = size; if (HTMLCSS.msieItalicWidthBug && font.style === "italic") { this.text.nodeValue = extra = this.compChar; dw = this.div.offsetWidth; } if (HTMLCSS.safariTextNodeBug) {this.div.innerHTML = this.compChar+extra} else {this.text.nodeValue = this.compChar+extra} var W = this.div.offsetWidth-dw; if (HTMLCSS.safariTextNodeBug) {this.div.innerHTML = this.styleChar+extra} else {this.text.nodeValue = this.styleChar+extra} var N = Math.floor((this.div.offsetWidth-dw)/W+.5); if (N === n) { if (HTMLCSS.safariTextNodeBug) {this.div.innerHTML = this.versionChar+extra} else {this.text.nodeValue = this.versionChar+extra} font.version = Math.floor((this.div.offsetWidth-dw)/W+1.5)/2; } this.div.style.fontSize = SIZE; return (N === n); }, getComparisonWidths: function (string,noStyleChar) { if (HTMLCSS.FONTDATA && HTMLCSS.FONTDATA.hasStyleChar && !noStyleChar) {string += this.styleChar + " " + this.compChar} if (HTMLCSS.safariTextNodeBug) {this.div.innerHTML = string} else {this.text.nodeValue = string} this.div.style.fontFamily = this.comparisonFont[0]; var W = this.div.offsetWidth; this.div.style.fontFamily = HTMLCSS.webFontDefault; var sW = this.div.offsetWidth, sH = this.div.offsetHeight; for (var i = 1, m = this.comparisonFont.length; i < m; i++) { this.div.style.fontFamily = this.comparisonFont[i]; if (this.div.offsetWidth != W) {return [W,this.div.offsetWidth,i,sW,sH]} } return null; }, 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]); 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); return done; }, loadComplete: function (font,n,done,status) { MathJax.Message.Clear(n); if (status === AJAX.STATUS.OK) {this.webFontLoaded = true; done(); return} this.loadError(font); if (HUB.Browser.isFirefox && HTMLCSS.allowWebFonts) { var host = document.location.protocol + "//" + document.location.hostname; if (document.location.port != "") {host += ":" + document.location.port} host += "/"; if (AJAX.fileURL(HTMLCSS.webfontDir).substr(0,host.length) !== host) {this.firefoxFontError(font)} } if (!this.webFontLoaded) {HTMLCSS.loadWebFontError(font,done)} else {done()} }, loadError: function (font) { MESSAGE(["CantLoadWebFont","Can't load web font %1",HTMLCSS.fontInUse+"/"+font.directory],null,2000); HUB.Startup.signal.Post("HTML-CSS Jax - web font error for " + HTMLCSS.fontInUse+"/"+font.directory); }, firefoxFontError: function (font) { MESSAGE(["FirefoxCantLoadWebFont","Firefox can't load web fonts from a remote host"],null,3000); HUB.Startup.signal.Post("HTML-CSS Jax - Firefox web fonts on remote host error"); }, checkWebFont: function (check,font,callback) { if (check.time(callback)) return; if (HTMLCSS.Font.testFont(font)) {callback(check.STATUS.OK)} else {setTimeout(check,check.delay)} }, fontFace: function (name) { var type = HTMLCSS.allowWebFonts; var FONT = HTMLCSS.FONTDATA.FONTS[name]; if (HTMLCSS.msieFontCSSBug && !FONT.family.match(/-Web$/)) {FONT.family += "-Web"} if (FONT.isWebFont) delete FONT.familyFixed; var webfonts = HTMLCSS.webfontDir+"/"+type; var dir = AJAX.fileURL(webfonts); var fullname = name.replace(/-b/,"-B").replace(/-i/,"-I").replace(/-Bold-/,"-Bold"); if (!fullname.match(/-/)) {fullname += "-Regular"} if (type === "svg") {fullname += ".svg#"+fullname} else {fullname += "."+type} var rev = AJAX.fileRev(webfonts+"/"+fullname.replace(/#.*/,"")); var def = { "font-family": FONT.family, src: "url('"+dir+"/"+fullname+rev+"')" }; if (type === "otf") { fullname = fullname.replace(/otf$/,"woff"); rev = AJAX.fileRev(webfonts+"/"+fullname); def.src += " format('opentype')"; dir = AJAX.fileURL(HTMLCSS.webfontDir+"/woff"); // add woff fonts as well def.src = "url('"+dir+"/"+fullname+rev+"') format('woff'), "+def.src; } else if (type !== "eot") {def.src += " format('"+type+"')"} if (!(HTMLCSS.FontFaceBug && FONT.isWebFont)) { if (name.match(/-bold/)) {def["font-weight"] = "bold"} if (name.match(/-italic/)) {def["font-style"] = "italic"} } return def; } }); var EVENT, TOUCH, HOVER; // filled in later var oldIE = MathJax.Hub.Browser.isMSIE && (document.documentMode||0) < 8; HTMLCSS.Augment({ config: { styles: { ".MathJax": { "display": "inline", "font-style": "normal", "font-weight": "normal", "line-height": "normal", "font-size": "100%", "font-size-adjust":"none", "text-indent": 0, "text-align": "left", "text-transform": "none", "letter-spacing": "normal", "word-spacing": "normal", "word-wrap": "normal", "white-space": "nowrap", "float": "none", "direction": "ltr", "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, border: 0, padding: 0, margin: 0 }, // Focus elements for keyboard tabbing. ".MathJax:focus, body :focus .MathJax": { display:"inline-table" // see issues #1282 and #1338 }, ".MathJax_Display": { position: "relative", display: "block!important", "text-indent": 0, "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, width: "100%" }, ".MathJax.MathJax_FullWidth": { "text-align": "center", display: (oldIE ? "block" : "table-cell") + "!important", width: (oldIE ? "100%" : "10000em") + "!important" }, ".MathJax img, .MathJax nobr, .MathJax a": { border: 0, padding: 0, margin: 0, "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, "vertical-align": 0, "line-height": "normal", "text-decoration": "none" }, "img.MathJax_strut": { border:"0!important", padding:"0!important", margin:"0!important", "vertical-align": "0!important" }, ".MathJax span": { display: "inline", position: "static", border: 0, padding: 0, margin: 0, "vertical-align": 0, "line-height": "normal", "text-decoration": "none", "box-sizing": "content-box" }, ".MathJax nobr": { "white-space": "nowrap!important" }, ".MathJax img": { display: "inline!important", "float": "none!important" }, ".MathJax *": { transition: "none", "-webkit-transition": "none", "-moz-transition": "none", "-ms-transition": "none", "-o-transition": "none" }, ".MathJax_Processing": { visibility: "hidden", position:"fixed", width: 0, height: 0, overflow:"hidden" }, ".MathJax_Processed": {display:"none!important"}, ".MathJax_test": { "font-style": "normal", "font-weight": "normal", "font-size": "100%", "font-size-adjust":"none", "text-indent": 0, "text-transform": "none", "letter-spacing": "normal", "word-spacing": "normal", overflow: "hidden", height: "1px" }, ".MathJax_test.mjx-test-display": { display: (oldIE ? "block" : "table") + "!important" }, ".MathJax_test.mjx-test-inline": { display: "inline!important", "margin-right": "-1px" }, ".MathJax_test.mjx-test-default": { display: "block!important", clear: "both" }, ".MathJax_ex_box": { display: "inline-block!important", position: "absolute", overflow: "hidden", "min-height": 0, "max-height":"none", padding:0, border: 0, margin: 0, width:"1px", height:"60ex" }, ".MathJax_em_box": { display: "inline-block!important", position: "absolute", overflow: "hidden", "min-height": 0, "max-height":"none", padding:0, border: 0, margin: 0, width:"1px", height:"60em" }, ".mjx-test-inline .MathJax_left_box": { display: "inline-block", width: 0, "float":"left" }, ".mjx-test-inline .MathJax_right_box": { display: "inline-block", width: 0, "float":"right" }, ".mjx-test-display .MathJax_right_box": { display: (oldIE ? "block" : "table-cell") + "!important", width: (oldIE ? "100%" : "10000em") + "!important", "min-width":0, "max-width":"none", padding:0, border:0, margin:0 }, ".MathJax .MathJax_HitBox": { cursor: "text", background: "white", opacity:0, filter:"alpha(opacity=0)" }, ".MathJax .MathJax_HitBox *": { filter: "none", opacity:1, background:"transparent" // for IE }, "#MathJax_Tooltip": { position: "absolute", left: 0, top: 0, width: "auto", height: "auto", display: "none" }, "#MathJax_Tooltip *": { filter: "none", opacity:1, background:"transparent" // for IE }, // // Used for testing web fonts against the default font used while // web fonts are loading // "@font-face": { "font-family": "MathJax_Blank", "src": "url('about:blank')" } } }, settings: HUB.config.menuSettings, Font: null, // created by Config() below webFontDefault: "MathJax_Blank", allowWebFonts: "otf", // assume browser can use OTF web fonts maxStretchyParts: 1000, // limit the number of parts allowed for // stretchy operators. See issue 366. fontName: { TeXLocal: "TeX", TeXWeb: ["","TeX"], TeXImage: ["",""], STIXLocal: ["STIX","STIX-Web"], STIXWeb: "STIX-Web", AsanaMathWeb: "Asana-Math", GyrePagellaWeb: "Gyre-Pagella", GyreTermesWeb: "Gyre-Termes", LatinModernWeb: "Latin-Modern", NeoEulerWeb: "Neo-Euler" }, fontInUse: "generic", FONTDATA: { TeX_factor: 1, baselineskip: 1.2, lineH: .8, lineD: .2, ffLineH: .8, FONTS: {}, VARIANT: {"normal": {fonts:[]}, "-generic-variant": {}, "-largeOp": {}, "-smallOp": {}}, RANGES: [], DELIMITERS: {}, RULECHAR: 0x2D, REMAP: {} }, Config: function () { if (!this.require) {this.require = []} this.Font = FONTTEST(); this.SUPER(arguments).Config.call(this); var settings = this.settings, config = this.config, font = settings.font; if (this.adjustAvailableFonts) {this.adjustAvailableFonts(config.availableFonts)} if (settings.scale) {config.scale = settings.scale} if (font && font !== "Auto" && this.fontName[font]) { config.availableFonts = []; delete config.fonts; if (isArray(this.fontName[font])) { config.preferredFont = this.fontName[font][0]; config.webFont = this.fontName[font][1]; } else { config.preferredFont = config.webFont = this.fontName[font]; } if (config.preferredFont) {config.availableFonts[0] = config.preferredFont} } if (config.fonts) { config.availableFonts = config.fonts; config.preferredFont = config.webFont = config.fonts[0]; if (config.webFont === "STIX") {config.webFont += "-Web"} } font = this.Font.findFont(config.availableFonts,config.preferredFont); if (!font && this.allowWebFonts) {font = config.webFont; if (font) {this.webFonts = true}} if (!font && this.config.imageFont) {font = config.imageFont; this.imgFonts = true} if (font) { this.fontInUse = font; this.fontDir += "/" + font; this.webfontDir += "/" + font; this.require.push(this.fontDir+"/fontdata.js"); if (this.imgFonts) { this.require.push(this.directory+"/imageFonts.js"); HUB.Startup.signal.Post("HTML-CSS Jax - using image fonts"); } } else { MESSAGE(["CantFindFontUsing","Can't find a valid font using %1", "["+this.config.availableFonts.join(", ")+"]"],null,3000); HUB.Startup.signal.Post("HTML-CSS Jax - no valid font"); } this.require.push(MathJax.OutputJax.extensionDir+"/MathEvents.js"); }, Startup: function () { // Set up event handling EVENT = MathJax.Extension.MathEvents.Event; TOUCH = MathJax.Extension.MathEvents.Touch; HOVER = MathJax.Extension.MathEvents.Hover; this.ContextMenu = EVENT.ContextMenu; this.Mousedown = EVENT.AltContextMenu; this.Mouseover = HOVER.Mouseover; this.Mouseout = HOVER.Mouseout; this.Mousemove = HOVER.Mousemove; // Make hidden div for when math is in a display:none block this.hiddenDiv = this.Element("div",{ style:{visibility:"hidden", overflow:"hidden", position:"absolute", top:0, height:"1px", width: "auto", padding:0, border:0, margin:0, textAlign:"left", textIndent:0, textTransform:"none", lineHeight:"normal", letterSpacing:"normal", wordSpacing:"normal"} }); if (!document.body.firstChild) {document.body.appendChild(this.hiddenDiv)} else {document.body.insertBefore(this.hiddenDiv,document.body.firstChild)} this.hiddenDiv = this.addElement(this.hiddenDiv,"div",{id:"MathJax_Hidden"}); // Determine pixels per inch var div = this.addElement(this.hiddenDiv,"div",{style:{width:"5in"}}); this.pxPerInch = div.offsetWidth/5; this.hiddenDiv.removeChild(div); // Markers used by getW this.startMarker = this.createStrut(this.Element("span"),10,true); this.endMarker = this.addText(this.Element("span"),"x").parentNode; // Used in getHD this.HDspan = this.Element("span"); if (this.operaHeightBug) {this.createStrut(this.HDspan,0)} if (this.msieInlineBlockAlignBug) { this.HDimg = this.addElement(this.HDspan,"img",{style:{height:"0px", width:"1px"}}); try {this.HDimg.src = "about:blank"} catch(err) {} } else { this.HDimg = this.createStrut(this.HDspan,0); } // Used in preTranslate to get scaling factors this.TestSpan = this.Element("span", {className:"MathJax_test"}, [ ["span",{className:"MathJax_left_box"}], ["span",{className:"MathJax_ex_box"}], ["span",{className:"MathJax_em_box"}], ["span",{className:"MathJax_right_box"}] ] ); // Set up styles and preload web fonts return AJAX.Styles(this.config.styles,["InitializeHTML",this]); }, removeSTIXfonts: function (fonts) { // // Opera doesn't display large chunks of the STIX fonts, and // Safari/Windows doesn't display Plane1, // so disable STIX for these browsers. // // ### FIXME ### Do we need to disable the other web fonts for these? // for (var i = 0, m = fonts.length; i < m; i++) {if (fonts[i] === "STIX") {fonts.splice(i,1); m--; i--;}} if (this.config.preferredFont === "STIX") {this.config.preferredFont = fonts[0]} }, PreloadWebFonts: function () { if (!HTMLCSS.allowWebFonts || !HTMLCSS.config.preloadWebFonts) return; for (var i = 0, m = HTMLCSS.config.preloadWebFonts.length; i < m; i++) { var FONT = HTMLCSS.FONTDATA.FONTS[HTMLCSS.config.preloadWebFonts[i]]; if (!FONT.available) {HTMLCSS.Font.testFont(FONT)} } }, // // Handle initialization that requires styles to be set up // InitializeHTML: function () { this.PreloadWebFonts(); this.getDefaultExEm(); // // If the defaultEm size is zero, it might be that a web font hasn't // arrived yet, so try to wait for it, but don't wait too long. // if (this.defaultEm) return; var ready = MathJax.Callback(); AJAX.timer.start(AJAX,function (check) { if (check.time(ready)) {HUB.signal.Post(["HTML-CSS Jax - no default em size"]); return} HTMLCSS.getDefaultExEm(); if (HTMLCSS.defaultEm) {ready()} else {setTimeout(check,check.delay)} },this.defaultEmDelay,this.defaultEmTimeout); return ready; }, defaultEmDelay: 100, // initial delay when checking for defaultEm defaultEmTimeout: 1000, // when to stop looking for defaultEm getDefaultExEm: function () { // // Get the default sizes (need styles in place to do this) // var test = document.body.appendChild(this.TestSpan.cloneNode(true)); test.className += " mjx-test-inline mjx-test-default"; this.defaultEx = test.childNodes[1].offsetHeight/60; this.defaultEm = test.childNodes[2].offsetHeight/60; this.defaultWidth = Math.max(0,test.lastChild.offsetLeft-test.firstChild.offsetLeft-2); document.body.removeChild(test); }, preTranslate: function (state) { var scripts = state.jax[this.id], i, m = scripts.length, n, script, prev, span, div, test, jax, ex, em, scale, maxwidth, relwidth = false, cwidth, linebreak = this.config.linebreaks.automatic, width = this.config.linebreaks.width; if (linebreak) { relwidth = (width.match(/^\s*(\d+(\.\d*)?%\s*)?container\s*$/) != null); if (relwidth) {width = width.replace(/\s*container\s*/,"")} else {maxwidth = this.defaultWidth} if (width === "") {width = "100%"} } else {maxwidth = 100000} // a big width, so no implicit line breaks // // Loop through the scripts // for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; // // Remove any existing output // prev = script.previousSibling; if (prev && String(prev.className).match(/^MathJax(_Display)?( MathJax_Process(ing|ed))?$/)) {prev.parentNode.removeChild(prev)} if (script.MathJax.preview) script.MathJax.preview.style.display = "none"; // // Add the span, and a div if in display mode, // then mark it as being processed // jax = script.MathJax.elementJax; if (!jax) continue; jax.HTMLCSS = {display: (jax.root.Get("display") === "block")} span = div = this.Element("span",{ className:"MathJax", id:jax.inputID+"-Frame", isMathJax:true, jaxID:this.id, oncontextmenu:EVENT.Menu, onmousedown: EVENT.Mousedown, onmouseover:EVENT.Mouseover, onmouseout:EVENT.Mouseout, onmousemove:EVENT.Mousemove, onclick:EVENT.Click, ondblclick:EVENT.DblClick, // Added for keyboard accessible menu. onkeydown: EVENT.Keydown, tabIndex: HUB.getTabOrder(jax) }); if (HUB.Browser.noContextMenu) { span.ontouchstart = TOUCH.start; span.ontouchend = TOUCH.end; } if (jax.HTMLCSS.display) { div = this.Element("div",{className:"MathJax_Display"}); div.appendChild(span); } else if (this.msieDisappearingBug) {span.style.display = "inline-block"} div.className += " MathJax_Processing"; script.parentNode.insertBefore(div,script); jax.HTMLCSS.span = span; jax.HTMLCSS.div = div; // save for use in Translate() // // Add the test span for determining scales and linebreak widths // test = this.TestSpan.cloneNode(true); test.className += " mjx-test-" + (jax.HTMLCSS.display ? "display" : "inline"); script.parentNode.insertBefore(test,script); } // // Determine the scaling factors for each script // (this only requires one reflow rather than a reflow for each equation) // Record any that need to be hidden (don't move them now, since that // would cause reflows). // var hidden = []; for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; test = script.previousSibling; div = test.previousSibling; jax = script.MathJax.elementJax; if (!jax) continue; ex = test.childNodes[1].offsetHeight/60; em = test.childNodes[2].offsetHeight/60; cwidth = Math.max(0, jax.HTMLCSS.display ? test.lastChild.offsetWidth - 1: test.lastChild.offsetLeft - test.firstChild.offsetLeft - 2); if (ex === 0 || ex === "NaN") { // can't read width, so move to hidden div for processing hidden.push(div); jax.HTMLCSS.isHidden = true; ex = this.defaultEx; em = this.defaultEm; cwidth = this.defaultWidth; } if (cwidth === 0 && !jax.HTMLCSS.display) cwidth = this.defaultWidth; if (relwidth) {maxwidth = cwidth} scale = (this.config.matchFontHeight ? ex/this.TeX.x_height/em : 1); scale = Math.floor(Math.max(this.config.minScaleAdjust/100,scale)*this.config.scale); jax.HTMLCSS.scale = scale/100; jax.HTMLCSS.fontSize = scale+"%"; jax.HTMLCSS.em = jax.HTMLCSS.outerEm = em; this.em = em * scale/100; jax.HTMLCSS.ex = ex; jax.HTMLCSS.cwidth = cwidth/this.em; jax.HTMLCSS.lineWidth = (linebreak ? this.length2em(width,1,maxwidth/this.em) : 1000000); } for (i = 0, n = hidden.length; i < n; i++) { this.hiddenDiv.appendChild(hidden[i]); this.addElement(this.hiddenDiv,"br"); } // // Remove the test spans used for determining scales and linebreak widths // for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; jax = scripts[i].MathJax.elementJax; if (!jax) continue; script.parentNode.removeChild(script.previousSibling); if (script.MathJax.preview) script.MathJax.preview.style.display = ""; } // // Set state variables used for displaying equations in chunks // state.HTMLCSSeqn = state.HTMLCSSlast = 0; state.HTMLCSSi = -1; state.HTMLCSSchunk = this.config.EqnChunk; state.HTMLCSSdelay = false; }, PHASE: {I: 1, II: 2, III: 3}, // processing phases Translate: function (script,state) { if (!script.parentNode) return; // // If we are supposed to do a chunk delay, do it // if (state.HTMLCSSdelay) { state.HTMLCSSdelay = false; HUB.RestartAfter(MathJax.Callback.Delay(this.config.EqnChunkDelay)); } // // Get the data about the math // var jax = script.MathJax.elementJax, math = jax.root, div = jax.HTMLCSS.div, span = jax.HTMLCSS.span; if (!document.getElementById(span.id)) return; // // Set the font metrics // this.getMetrics(jax); if (this.scale !== 1) {span.style.fontSize = jax.HTMLCSS.fontSize} // // Typeset the math // this.initImg(span); this.initHTML(math,span); this.savePreview(script); try { math.setTeXclass(); math.toHTML(span,div,this.PHASE.I); } catch (err) { if (err.restart) {while (span.firstChild) {span.removeChild(span.firstChild)}} this.restorePreview(script); throw err; } this.restorePreview(script); // // Remove the processing marker, and signal the new math pending // 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 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 } return false; }, // // MathML previews can contain the same ID's as the HTML output, // which confuses HTMLspanElement(), so remove the preview temporarily // and restore it after typesetting the math. // savePreview: function (script) { var preview = script.MathJax.preview; if (preview) { script.MathJax.tmpPreview = document.createElement("span"); preview.parentNode.replaceChild(script.MathJax.tmpPreview,preview); } }, restorePreview: function (script) { var tmpPreview = script.MathJax.tmpPreview; if (tmpPreview) { tmpPreview.parentNode.replaceChild(script.MathJax.preview,tmpPreview); 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], script, jax, i, m; // // Remove the processed markers so that measuring can occur, // and remove the preview, if any, since the math will now be visible. // for (i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { script = scripts[i]; if (script && script.parentNode && script.MathJax.elementJax) { var div = (script.MathJax.elementJax.HTMLCSS||{}).div; if (div) {div.className = div.className.split(/ /)[0]} if (script.MathJax.preview) script.MathJax.preview.innerHTML = ""; } } // // Measure the math in this chunk (toHTML phase II) // for (i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { script = scripts[i]; if (script && script.parentNode && script.MathJax.elementJax) { jax = script.MathJax.elementJax; this.getMetrics(jax); if (jax.HTMLCSS.span && jax.HTMLCSS.div) jax.root.toHTML(jax.HTMLCSS.span,jax.HTMLCSS.div,this.PHASE.II); } } // // Reveal this chunk of math // for (i = state.HTMLCSSlast, m = state.HTMLCSSeqn; i < m; i++) { script = scripts[i]; if (script && script.parentNode && script.MathJax.elementJax) { // // Finish the math with its measured size (toHTML phase III) // jax = script.MathJax.elementJax; this.getMetrics(jax); if (jax.HTMLCSS.span && jax.HTMLCSS.div) { jax.root.toHTML(jax.HTMLCSS.span,jax.HTMLCSS.div,this.PHASE.III); if (jax.HTMLCSS.isHidden) script.parentNode.insertBefore(jax.HTMLCSS.div,script); 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) } } } if (this.forceReflow) { // WebKit can misplace some elements that should wrap to the next line // but gets them right on a reflow, so force reflow by toggling a stylesheet var sheet = (document.styleSheets||[])[0]||{}; sheet.disabled = true; sheet.disabled = false; } // // Save our place so we know what is revealed // state.HTMLCSSlast = state.HTMLCSSeqn; }, getJaxFromMath: function (math) { if (math.parentNode.className.match(/MathJax_Display/)) {math = math.parentNode} do {math = math.nextSibling} while (math && math.nodeName.toLowerCase() !== "script"); return HUB.getJaxFor(math); }, getHoverSpan: function (jax,math) {return jax.root.HTMLspanElement()}, getHoverBBox: function (jax,span,math) { var bbox = span.bbox, em = jax.HTMLCSS.outerEm; var BBOX = {w:bbox.w*em, h:bbox.h*em, d:bbox.d*em}; if (bbox.width) {BBOX.width = bbox.width} return BBOX; }, Zoom: function (jax,span,math,Mw,Mh) { // // Re-render at larger size // span.className = "MathJax"; span.style.fontSize = jax.HTMLCSS.fontSize; // // get em sizes (taken from HTMLCSS.preTranslate) // var emex = span.appendChild(this.TestSpan.cloneNode(true)); var em = emex.childNodes[2].offsetHeight/60; this.em = MML.mbase.prototype.em = em; this.outerEm = em / jax.HTMLCSS.scale; emex.parentNode.removeChild(emex); this.scale = jax.HTMLCSS.scale; this.linebreakWidth = jax.HTMLCSS.lineWidth; this.cwidth = jax.HTMLCSS.cwidth; this.zoomScale = parseInt(HUB.config.menuSettings.zscale) / 100; this.idPostfix = "-zoom"; jax.root.toHTML(span,span); this.idPostfix = ""; this.zoomScale = 1; var bbox = jax.root.HTMLspanElement().bbox, width = bbox.width; if (width) { // Handle full-width displayed equations if (bbox.tw) {Mw = bbox.tw*em} if (bbox.w*em < Mw) {Mw = bbox.w*em} span.style.width = Math.floor(Mw-1.5*HTMLCSS.em)+"px"; span.style.display="inline-block"; var id = (jax.root.id||"MathJax-Span-"+jax.root.spanID)+"-zoom"; var child = document.getElementById(id).firstChild; while (child && child.style.width !== width) {child = child.nextSibling} if (child) { var cwidth = child.offsetWidth; child.style.width = "100%"; if (cwidth > Mw) {span.style.width = (cwidth+100)+"px"} } } // // Adjust margins to prevent overlaps at the edges // child = span.firstChild.firstChild.style; if (bbox.H != null && bbox.H > bbox.h) {child.marginTop = HTMLCSS.Em(bbox.H-Math.max(bbox.h,HTMLCSS.FONTDATA.lineH))} if (bbox.D != null && bbox.D > bbox.d) {child.marginBottom = HTMLCSS.Em(bbox.D-Math.max(bbox.d,HTMLCSS.FONTDATA.lineD))} if (bbox.lw < 0) {child.paddingLeft = HTMLCSS.Em(-bbox.lw)} if (bbox.rw > bbox.w) {child.marginRight = HTMLCSS.Em(bbox.rw-bbox.w)} // // Get height and width of zoomed math and original math // span.style.position = "absolute"; if (!width) {math.style.position = "absolute"} var zW = span.offsetWidth, zH = span.offsetHeight, mH = math.offsetHeight, mW = math.offsetWidth; span.style.position = math.style.position = ""; // return {Y:-EVENT.getBBox(span).h, mW:mW, mH:mH, zW:zW, zH:zH}; }, initImg: function (span) {}, initHTML: function (math,span) {}, initFont: function (name) { var FONTS = HTMLCSS.FONTDATA.FONTS, AVAIL = HTMLCSS.config.availableFonts; if (AVAIL && AVAIL.length && HTMLCSS.Font.testFont(FONTS[name])) { FONTS[name].available = true; if (FONTS[name].familyFixed) { FONTS[name].family = FONTS[name].familyFixed; delete FONTS[name].familyFixed; } return null; } if (!this.allowWebFonts) {return null} FONTS[name].isWebFont = true; if (HTMLCSS.FontFaceBug) { FONTS[name].family = name; if (HTMLCSS.msieFontCSSBug) {FONTS[name].family += "-Web"} } return AJAX.Styles({"@font-face":this.Font.fontFace(name)}); }, Remove: function (jax) { var span = document.getElementById(jax.inputID+"-Frame"); if (span) { if (jax.HTMLCSS.display) {span = span.parentNode} span.parentNode.removeChild(span); } delete jax.HTMLCSS; }, getHD: function (span,force) { if (span.bbox && this.config.noReflows && !force) {return {h:span.bbox.h, d:span.bbox.d}} var position = span.style.position; span.style.position = "absolute"; this.HDimg.style.height = "0px"; span.appendChild(this.HDspan); var HD = {h:span.offsetHeight}; this.HDimg.style.height = HD.h+"px"; HD.d = span.offsetHeight - HD.h; HD.h -= HD.d; HD.h /= this.em; HD.d /= this.em; span.removeChild(this.HDspan); span.style.position = position; return HD; }, getW: function (span) { var W, H, w = (span.bbox||{}).w, start = span; if (span.bbox && this.config.noReflows && span.bbox.exactW !== false) { if (!span.bbox.exactW) { if (span.style.paddingLeft) w += this.unEm(span.style.paddingLeft)*(span.scale||1); if (span.style.paddingRight) w += this.unEm(span.style.paddingRight)*(span.scale||1); } return w; } if (span.bbox && span.bbox.exactW) {return w} if ((span.bbox && w >= 0 && !this.initialSkipBug && !this.msieItalicWidthBug) || this.negativeBBoxes || !span.firstChild) { W = span.offsetWidth; H = span.parentNode.offsetHeight; } else if (span.bbox && w < 0 && this.msieNegativeBBoxBug) { W = -span.offsetWidth, H = span.parentNode.offsetHeight; } else { // IE can't deal with a space at the beginning, so put something else first var position = span.style.position; span.style.position = "absolute"; start = this.startMarker; span.insertBefore(start,span.firstChild) span.appendChild(this.endMarker); W = this.endMarker.offsetLeft - start.offsetLeft; span.removeChild(this.endMarker); span.removeChild(start); span.style.position = position } if (H != null) {span.parentNode.HH = H/this.em} return W/this.em; }, Measured: function (span,parent) { var bbox = span.bbox; if (bbox.width == null && bbox.w && !bbox.isMultiline) { var w = this.getW(span); bbox.rw += w - bbox.w; bbox.w = w; bbox.exactW = true; } if (!parent) {parent = span.parentNode} if (!parent.bbox) {parent.bbox = bbox} return span; }, Remeasured: function (span,parent) { parent.bbox = this.Measured(span,parent).bbox; }, MeasureSpans: function (SPANS) { var spans = [], span, i, m, bbox, start, end, W, parent; // // Insert the needed markers // for (i = 0, m = SPANS.length; i < m; i++) { span = SPANS[i]; if (!span) continue; bbox = span.bbox; parent = this.parentNode(span); if (bbox.exactW || bbox.width || bbox.w === 0 || bbox.isMultiline || (this.config.noReflows && bbox.exactW !== false)) { if (!parent.bbox) {parent.bbox = bbox} continue; } if (this.negativeBBoxes || !span.firstChild || (bbox.w >= 0 && !this.initialSkipBug) || (bbox.w < 0 && this.msieNegativeBBoxBug)) { spans.push([span]); } else if (this.initialSkipBug) { start = this.startMarker.cloneNode(true); end = this.endMarker.cloneNode(true); span.insertBefore(start,span.firstChild); span.appendChild(end); spans.push([span,start,end,span.style.position]); span.style.position = "absolute"; } else { end = this.endMarker.cloneNode(true); span.appendChild(end); spans.push([span,null,end]); } } // // Read the widths and heights // for (i = 0, m = spans.length; i < m; i++) { span = spans[i][0]; bbox = span.bbox; parent = this.parentNode(span); if ((bbox.w >= 0 && !this.initialSkipBug) || this.negativeBBoxes || !span.firstChild) { W = span.offsetWidth; parent.HH = parent.offsetHeight/this.em; } else if (bbox.w < 0 && this.msieNegativeBBoxBug) { W = -span.offsetWidth, parent.HH = parent.offsetHeight/this.em; } else { W = spans[i][2].offsetLeft - ((spans[i][1]||{}).offsetLeft||0); } W /= this.em; bbox.rw += W - bbox.w; bbox.w = W; bbox.exactW = true; if (!parent.bbox) {parent.bbox = bbox} } // // Remove markers // for (i = 0, m = spans.length; i < m; i++) { span = spans[i]; if (span[1]) {span[1].parentNode.removeChild(span[1]), span[0].style.position = span[3]} if (span[2]) {span[2].parentNode.removeChild(span[2])} } }, Em: function (m) { if (Math.abs(m) < .0006) {return "0em"} return m.toFixed(3).replace(/\.?0+$/,"") + "em"; }, EmRounded: function (m) { if (Math.abs(m) < .0006) {return "0em"} m = (Math.round(m*HTMLCSS.em)+.05)/HTMLCSS.em; return m.toFixed(3).replace(/\.?0+$/,"") + "em"; }, unEm: function (m) { return parseFloat(m); }, Px: function (m) { m *= this.em; var s = (m < 0? "-" : ""); return s+Math.abs(m).toFixed(1).replace(/\.?0+$/,"") + "px"; }, unPx: function (m) { return parseFloat(m)/this.em; }, Percent: function (m) { return (100*m).toFixed(1).replace(/\.?0+$/,"") + "%"; }, length2em: function (length,mu,size) { if (typeof(length) !== "string") {length = length.toString()} if (length === "") {return ""} if (length === MML.SIZE.NORMAL) {return 1} if (length === MML.SIZE.BIG) {return 2} if (length === MML.SIZE.SMALL) {return .71} if (length === "infinity") {return HTMLCSS.BIGDIMEN} var factor = this.FONTDATA.TeX_factor, emFactor = (HTMLCSS.zoomScale||1) / HTMLCSS.em; if (length.match(/mathspace$/)) {return HTMLCSS.MATHSPACE[length]*factor} var match = length.match(/^\s*([-+]?(?:\.\d+|\d+(?:\.\d*)?))?(pt|em|ex|mu|px|pc|in|mm|cm|%)?/); var m = parseFloat(match[1]||"1"), unit = match[2]; if (size == null) {size = 1}; if (mu == null) {mu = 1} if (unit === "em") {return m * factor} if (unit === "ex") {return m * HTMLCSS.TeX.x_height * factor} if (unit === "%") {return m / 100 * size} if (unit === "px") {return m * emFactor} if (unit === "pt") {return m / 10 * factor} // 10 pt to an em if (unit === "pc") {return m * 1.2 * factor} // 12 pt to a pc if (unit === "in") {return m * this.pxPerInch * emFactor} if (unit === "cm") {return m * this.pxPerInch * emFactor / 2.54} // 2.54 cm to an inch if (unit === "mm") {return m * this.pxPerInch * emFactor / 25.4} // 10 mm to a cm if (unit === "mu") {return m / 18 * factor * mu} // 18mu to an em for the scriptlevel return m*size; // relative to given size (or 1em as default) }, thickness2em: function (length,mu) { var thick = HTMLCSS.TeX.rule_thickness; if (length === MML.LINETHICKNESS.MEDIUM) {return thick} if (length === MML.LINETHICKNESS.THIN) {return .67*thick} if (length === MML.LINETHICKNESS.THICK) {return 1.67*thick} return this.length2em(length,mu,thick); }, border2em: function (length,mu) { if (length === MML.LINETHICKNESS.THIN) {length = "1px"} if (length === MML.LINETHICKNESS.MEDIUM) {length = "3px"} if (length === MML.LINETHICKNESS.THICK) {length = "5px"} return this.length2em(length,mu); }, getPadding: function (span) { var padding = {top:0, right:0, bottom:0, left:0}, has = false; for (var id in padding) {if (padding.hasOwnProperty(id)) { var pad = span.style["padding"+id.charAt(0).toUpperCase()+id.substr(1)]; if (pad) {padding[id] = this.length2em(pad); has = true;} }} return (has ? padding : false); }, getBorders: function (span) { var border = {top:0, right:0, bottom:0, left:0}, css = {}, has = false; for (var id in border) {if (border.hasOwnProperty(id)) { var ID = "border"+id.charAt(0).toUpperCase()+id.substr(1); var style = span.style[ID+"Style"]; if (style) { has = true; border[id] = this.border2em(span.style[ID+"Width"] || MML.LINETHICKNESS.MEDIUM); css[ID] = [span.style[ID+"Width"],span.style[ID+"Style"],span.style[ID+"Color"]].join(" "); } }} border.css = css; return (has ? border : false); }, setBorders: function (span,borders) { if (borders) { for (var id in borders.css) {if (borders.css.hasOwnProperty(id)) { span.style[id] = borders.css[id]; }} } }, createStrut: function (span,h,before) { var strut = this.Element("span",{ isMathJax: true, style:{display:"inline-block", overflow:"hidden", height:h+"px", width:"1px", marginRight:"-1px"} }); if (before) {span.insertBefore(strut,span.firstChild)} else {span.appendChild(strut)} return strut; }, createBlank: function (span,w,before) { var blank = this.Element("span",{ isMathJax: true, style: {display:"inline-block", overflow:"hidden", height:"1px", width:this.Em(w)} }); if (w < 0) {blank.style.marginRight = blank.style.width; blank.style.width = 0} if (before) {span.insertBefore(blank,span.firstChild)} else {span.appendChild(blank)} return blank; }, createShift: function (span,w,before) { var space = this.Element("span",{style:{marginLeft:this.Em(w)}, isMathJax:true}); if (before) {span.insertBefore(space,span.firstChild)} else {span.appendChild(space)} return space; }, createSpace: function (span,h,d,w,color,isSpace) { if (h < -d) {d = -h} // make sure h is above d var H = this.Em(h+d), D = this.Em(-d); if (this.msieInlineBlockAlignBug) {D = this.Em(HTMLCSS.getHD(span.parentNode,true).d-d)} if (span.isBox || isSpace) { var scale = (span.scale == null ? 1 : span.scale); span.bbox = {exactW: true, h: h*scale, d: d*scale, w: w*scale, rw: w*scale, lw: 0}; span.style.height = H; span.style.verticalAlign = D; span.HH = (h+d)*scale; } else { span = this.addElement(span,"span",{style: {height:H, verticalAlign:D}, isMathJax:true}); } if (w >= 0) { span.style.width = this.Em(w); span.style.display = "inline-block"; span.style.overflow = "hidden"; // for IE in quirks mode } else { if (this.msieNegativeSpaceBug) {span.style.height = ""} span.style.marginLeft = this.Em(w); if (HTMLCSS.safariNegativeSpaceBug && span.parentNode.firstChild == span) {this.createBlank(span,0,true)} } if (color && color !== MML.COLOR.TRANSPARENT) { span.style.backgroundColor = color; span.style.position = "relative"; // make sure it covers earlier items } return span; }, createRule: function (span,h,d,w,color) { if (h < -d) {d = -h} // make sure h is above d var min = HTMLCSS.TeX.min_rule_thickness, f = 1; // If rule is very thin, make it at least min_rule_thickness so it doesn't disappear if (w > 0 && w*this.