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,311 lines (1,237 loc) 114 kB
/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/CommonHTML/jax.js * * Implements the CommonHTML OutputJax that displays mathematics * using HTML and CSS to position the characters from math fonts * in their proper locations. Unlike the HTML-CSS output jax, * this HTML is browser and OS independent. * * --------------------------------------------------------------------- * * Copyright (c) 2013-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,HTML,CHTML) { var MML; var isArray = MathJax.Object.isArray; var EVENT, TOUCH, HOVER; // filled in later var STRUTHEIGHT = 1, EFUZZ = .1, // overlap needed for stretchy delimiters HFUZZ = .025, DFUZZ = .025; // adjustments to bounding box of character boxes var STYLES = { ".mjx-chtml": { display: "inline-block", "line-height": 0, "text-indent": 0, "text-align": "left", "text-transform": "none", "font-style": "normal", "font-weight": "normal", "font-size": "100%", "font-size-adjust":"none", "letter-spacing": "normal", "word-wrap": "normal", "word-spacing": "normal", "white-space": "nowrap", "float": "none", "direction": "ltr", "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, border: 0, margin: 0, padding: "1px 0" }, ".MJXc-display": { display: "block", "text-align": "center", "margin": "1em 0", padding: 0 }, ".mjx-chtml[tabindex]:focus, body :focus .mjx-chtml[tabindex]": { display: "inline-table" // see issues #1282 and #1338 }, ".mjx-full-width": { "text-align": "center", display: "table-cell!important", width: "10000em" }, ".mjx-math": { "display": "inline-block", "border-collapse": "separate", "border-spacing": 0 }, ".mjx-math *": { display:"inline-block", "-webkit-box-sizing": "content-box!important", "-moz-box-sizing": "content-box!important", "box-sizing": "content-box!important", // override bootstrap settings "text-align":"left" }, ".mjx-numerator": {display:"block", "text-align":"center"}, ".mjx-denominator": {display:"block", "text-align":"center"}, ".MJXc-stacked": {height:0, position:"relative"}, ".MJXc-stacked > *": {position: "absolute"}, ".MJXc-bevelled > *": {display:"inline-block"}, ".mjx-stack": {display:"inline-block"}, ".mjx-op": {display:"block"}, ".mjx-under": {display:"table-cell"}, ".mjx-over": {display:"block"}, ".mjx-over > *": {"padding-left":"0px!important", "padding-right":"0px!important"}, ".mjx-under > *": {"padding-left":"0px!important", "padding-right":"0px!important"}, ".mjx-stack > .mjx-sup": {display:"block"}, ".mjx-stack > .mjx-sub": {display:"block"}, ".mjx-prestack > .mjx-presup": {display:"block"}, ".mjx-prestack > .mjx-presub": {display:"block"}, ".mjx-delim-h > .mjx-char": {display:"inline-block"}, ".mjx-surd": {"vertical-align":"top"}, ".mjx-mphantom *": {visibility:"hidden"}, ".mjx-merror": { "background-color":"#FFFF88", color: "#CC0000", border: "1px solid #CC0000", padding: "2px 3px", "font-style": "normal", "font-size": "90%" }, ".mjx-annotation-xml": {"line-height":"normal"}, ".mjx-menclose > svg": {fill:"none", stroke:"currentColor"}, ".mjx-mtr": {display:"table-row"}, ".mjx-mlabeledtr": {display:"table-row"}, ".mjx-mtd": {display:"table-cell", "text-align":"center"}, ".mjx-label": {display:"table-row"}, ".mjx-box": {display:"inline-block"}, ".mjx-block": {display:"block"}, ".mjx-span": {display:"inline"}, ".mjx-char": {display:"block", "white-space":"pre"}, ".mjx-itable": {display:"inline-table", width:"auto"}, ".mjx-row": {display:"table-row"}, ".mjx-cell": {display:"table-cell"}, ".mjx-table": {display:"table", width:"100%"}, ".mjx-line": {display:"block", height:0}, ".mjx-strut": {width:0, "padding-top":STRUTHEIGHT+"em"}, ".mjx-vsize": {width:0}, ".MJXc-space1": {"margin-left":".167em"}, ".MJXc-space2": {"margin-left":".222em"}, ".MJXc-space3": {"margin-left":".278em"}, ".mjx-chartest": { display:"block", visibility: "hidden", position:"absolute", top:0, "line-height":"normal", "font-size":"500%" }, ".mjx-chartest .mjx-char": {display:"inline"}, ".mjx-chartest .mjx-box": {"padding-top": "1000px"}, ".MJXc-processing": { visibility: "hidden", position:"fixed", width: 0, height: 0, overflow:"hidden" }, ".MJXc-processed": {display:"none"}, ".mjx-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" }, ".mjx-test.mjx-test-display": { display: "table!important" }, ".mjx-test.mjx-test-inline": { display: "inline!important", "margin-right": "-1px" }, ".mjx-test.mjx-test-default": { display: "block!important", clear: "both" }, ".mjx-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" }, ".mjx-test-inline .mjx-left-box": { display: "inline-block", width: 0, "float":"left" }, ".mjx-test-inline .mjx-right-box": { display: "inline-block", width: 0, "float":"right" }, ".mjx-test-display .mjx-right-box": { display: "table-cell!important", width: "10000em!important", "min-width":0, "max-width":"none", padding:0, border:0, margin:0 }, "#MathJax_CHTML_Tooltip": { "background-color": "InfoBackground", color: "InfoText", border: "1px solid black", "box-shadow": "2px 2px 5px #AAAAAA", // Opera 10.5 "-webkit-box-shadow": "2px 2px 5px #AAAAAA", // Safari 3 and Chrome "-moz-box-shadow": "2px 2px 5px #AAAAAA", // Firefox 3.5 "-khtml-box-shadow": "2px 2px 5px #AAAAAA", // Konqueror padding: "3px 4px", "z-index": 401, position: "absolute", left: 0, top: 0, width: "auto", height: "auto", display: "none" } }; /************************************************************/ var BIGDIMEN = 1000000; var MAXREMAP = 5; var LINEBREAKS = {}, CONFIG = MathJax.Hub.config; CHTML.Augment({ settings: HUB.config.menuSettings, config: {styles: STYLES}, /********************************************/ Config: function () { if (!this.require) {this.require = []} this.SUPER(arguments).Config.call(this); var settings = this.settings; if (settings.scale) {this.config.scale = settings.scale} this.require.push(this.fontDir+"/TeX/fontdata.js"); this.require.push(MathJax.OutputJax.extensionDir+"/MathEvents.js"); LINEBREAKS = this.config.linebreaks; }, 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; // // Determine pixels per inch // var div = CHTML.addElement(document.body,"mjx-block",{style:{display:"block",width:"5in"}}); this.pxPerInch = div.offsetWidth/5; div.parentNode.removeChild(div); // // Used in preTranslate to get scaling factors and line width // this.TestSpan = CHTML.Element("mjx-test",{style:{left:"1em"}}, [["mjx-left-box"],["mjx-ex-box"],["mjx-right-box"]]); // // Set up styles and preload web fonts // return AJAX.Styles(this.config.styles,["InitializeCHTML",this]); }, InitializeCHTML: function () { 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(["CommonHTML Jax - no default em size"]); return} CHTML.getDefaultExEm(); if (CHTML.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.defaultEm = this.getFontSize(test); this.defaultEx = test.childNodes[1].offsetHeight/60; this.defaultWidth = Math.max(0,test.lastChild.offsetLeft-test.firstChild.offsetLeft-2); document.body.removeChild(test); }, getFontSize: (window.getComputedStyle ? function (node) { var style = window.getComputedStyle(node); return parseFloat(style.fontSize); } : // // IE 8 doesn't do getComputedStyle, so use // an alternative approach // function (node) { return node.style.pixelLeft; } ), getMaxWidth: (window.getComputedStyle ? function (node) { var style = window.getComputedStyle(node); if (style.maxWidth !== "none") return parseFloat(style.maxWidth); return 0; } : // // IE 8 doesn't do getComputedStyle, so use // currentStyle, and a hack to get the pixels for // a non-px max-width // function (node) { var max = node.currentStyle.maxWidth; if (max !== "none") { if (max.match(/\d*px/)) return parseFloat(max); var left = node.style.left; node.style.left = max; max = node.style.pixelLeft; node.style.left = left; return max; } return 0; } ), // // Load data for a font // loadFont: function (font) { HUB.RestartAfter(AJAX.Require(this.fontDir+"/"+font)); }, // // Signal that the font data are loaded // fontLoaded: function (font) { if (!font.match(/-|fontdata/)) font += "-Regular"; if (!font.match(/\.js$/)) font += ".js" MathJax.Callback.Queue( ["Post",HUB.Startup.signal,"CommonHTML - font data loaded for " + font], ["loadComplete",AJAX,this.fontDir+"/"+font] ); }, Element: function (type,def,content) { if (type.substr(0,4) === "mjx-") { if (!def) def = {}; if (def.isMathJax == null) def.isMathJax = true; if (def.className) def.className = type+" "+def.className; else def.className = type; type = "span"; } return this.HTMLElement(type,def,content); }, addElement: function (node,type,def,content) { return node.appendChild(this.Element(type,def,content)); }, HTMLElement: HTML.Element, ucMatch: HTML.ucMatch, setScript: HTML.setScript, // // Look through the direct children of a node for one with the given // type (but if the node has intervening containers for its children, // step into them; note that elements corresponding to MathML nodes // will have id's so we don't step into them). // // This is used by munderover and msubsup to locate their child elements // when they are part of an embellished operator that is being stretched. // We don't use querySelector because we want to find only the direct child // nodes, not nodes that might be nested deeper in the tree (see issue #1447). // getNode: function (node,type) { var name = RegExp("\\b"+type+"\\b"); var nodes = []; while (node) { for (var i = 0, m = node.childNodes.length; i < m; i++) { var child = node.childNodes[i]; if (child) { if (name.test(child.className)) return child; if (child.id === "") nodes.push(child); } } node = nodes.shift(); } return null; }, /********************************************/ preTranslate: function (state) { var scripts = state.jax[this.id], i, m = scripts.length, script, prev, node, test, jax, ex, em, scale; // // Get linebreaking information // var maxwidth = 100000, relwidth = false, cwidth = 0, linebreak = LINEBREAKS.automatic, width = LINEBREAKS.width; if (linebreak) { relwidth = !!width.match(/^\s*(\d+(\.\d*)?%\s*)?container\s*$/); if (relwidth) {width = width.replace(/\s*container\s*/,"")} else {maxwidth = this.defaultWidth} if (width === "") {width = "100%"} } // // 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 && prev.className && String(prev.className).substr(0,9) === "mjx-chtml") prev.parentNode.removeChild(prev); if (script.MathJax.preview) script.MathJax.preview.style.display = "none"; // // Add the node for the math and mark it as being processed // jax = script.MathJax.elementJax; if (!jax) continue; jax.CHTML = { display: (jax.root.Get("display") === "block"), preview: (jax.CHTML||{}).preview // in case typeset calls are interleaved }; node = CHTML.Element("mjx-chtml",{ id:jax.inputID+"-Frame", className:"MathJax_CHTML", 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 (jax.CHTML.display) { // // Zoom box requires an outer container to get the positioning right. // var NODE = CHTML.Element("mjx-chtml",{className:"MJXc-display",isMathJax:false}); NODE.appendChild(node); node = NODE; } if (HUB.Browser.noContextMenu) { node.ontouchstart = TOUCH.start; node.ontouchend = TOUCH.end; } // node.className += " MJXc-processing"; script.parentNode.insertBefore(node,script); // // Add test nodes for determining scales and linebreak widths // test = this.TestSpan.cloneNode(true); test.className += " mjx-test-" + (jax.CHTML.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) // for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; test = script.previousSibling; jax = script.MathJax.elementJax; if (!jax) continue; em = CHTML.getFontSize(test); ex = test.childNodes[1].offsetHeight/60; cwidth = Math.max(0, jax.CHTML.display ? test.lastChild.offsetWidth - 1: test.lastChild.offsetLeft - test.firstChild.offsetLeft - 2); if (ex === 0 || ex === "NaN") { ex = this.defaultEx; cwidth = this.defaultWidth; } if (cwidth === 0 && !jax.CHTML.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.CHTML.scale = scale/100; jax.CHTML.fontSize = scale+"%"; jax.CHTML.outerEm = em; jax.CHTML.em = this.em = em * scale/100; jax.CHTML.ex = ex; jax.CHTML.cwidth = cwidth/this.em; jax.CHTML.lineWidth = (linebreak ? this.length2em(width,maxwidth/this.em,1) : maxwidth); } // // 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 = script.MathJax.elementJax; if (!jax) continue; script.parentNode.removeChild(script.previousSibling); if (script.MathJax.preview) script.MathJax.preview.style.display = ""; } state.CHTMLeqn = state.CHTMLlast = 0; state.CHTMLi = -1; state.CHTMLchunk = this.config.EqnChunk; state.CHTMLdelay = false; }, /********************************************/ Translate: function (script,state) { if (!script.parentNode) return; // // If we are supposed to do a chunk delay, do it // if (state.CHTMLdelay) { state.CHTMLdelay = false; HUB.RestartAfter(MathJax.Callback.Delay(this.config.EqnChunkDelay)); } // // Get the data about the math // var jax = script.MathJax.elementJax, math = jax.root, node = document.getElementById(jax.inputID+"-Frame"); if (!node) return; this.getMetrics(jax); if (this.scale !== 1) node.style.fontSize = jax.CHTML.fontSize; // // Typeset the math // this.initCHTML(math,node); this.savePreview(script); this.CHTMLnode = node; try { math.setTeXclass(); math.toCommonHTML(node); } catch (err) { while (node.firstChild) node.removeChild(node.firstChild); delete this.CHTMLnode; this.restorePreview(script); throw err; } delete this.CHTMLnode; this.restorePreview(script); // // Put it in place, and remove the processing marker // if (jax.CHTML.display) node = node.parentNode; node.className = node.className.replace(/ [^ ]+$/,""); // // Hide the math and don't let its preview be removed // node.className += " MJXc-processed"; if (script.MathJax.preview) { jax.CHTML.preview = script.MathJax.preview; delete script.MathJax.preview; } // // Check if we should show this chunk of equations // state.CHTMLeqn += (state.i - state.CHTMLi); state.CHTMLi = state.i; if (state.CHTMLeqn >= state.CHTMLlast + state.CHTMLchunk) { this.postTranslate(state); state.CHTMLchunk = Math.floor(state.CHTMLchunk*this.config.EqnChunkFactor); state.CHTMLdelay = true; // delay if there are more scripts } }, initCHTML: function (math,node) {}, // // MathML previews can contain the same ID's as the HTML output, // which confuses CHTMLnodeElement(), so remove the preview temporarily // and restore it after typesetting the math. // savePreview: function (script) { var preview = script.MathJax.preview; if (preview && preview.parentNode) { 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.CHTML; this.jax = jax; this.em = data.em; this.outerEm = data.outerEm; this.scale = data.scale; this.cwidth = data.cwidth; this.linebreakWidth = data.lineWidth; }, /********************************************/ postTranslate: function (state) { var scripts = state.jax[this.id]; // // Reveal this chunk of math // for (var i = state.CHTMLlast, m = state.CHTMLeqn; i < m; i++) { var script = scripts[i]; if (script && script.MathJax.elementJax) { // // Remove the processed marker // script.previousSibling.className = script.previousSibling.className.replace(/ [^ ]+$/,""); var data = script.MathJax.elementJax.CHTML; // // Remove the preview, if any // if (data.preview) { data.preview.innerHTML = ""; script.MathJax.preview = data.preview; delete data.preview; } } } // // Save our place so we know what is revealed // state.CHTMLlast = state.CHTMLeqn; }, /********************************************/ getJaxFromMath: function (math) { if (math.parentNode.className.match(/MJXc-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.CHTMLnodeElement()}, getHoverBBox: function (jax,span,math) { var bbox = jax.root.CHTML, em = jax.CHTML.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 // this.getMetrics(jax); var node = CHTML.addElement(span,"mjx-chtml",{style:{"font-size":Math.floor(CHTML.scale*100)+"%"},isMathJax:false}); CHTML.CHTMLnode = node; this.idPostfix = "-zoom"; jax.root.toCommonHTML(node); this.idPostfix = ""; // // Adjust margins to prevent overlaps at the edges // var style = node.style, bbox = jax.root.CHTML; if (bbox.t > bbox.h) style.marginTop = CHTML.Em(bbox.t-bbox.h); if (bbox.b > bbox.d) style.marginBottom = CHTML.Em(bbox.b-bbox.d); if (bbox.l < 0) style.paddingLeft = CHTML.Em(-bbox.l); if (bbox.r > bbox.w) style.marginRight = CHTML.Em(bbox.r-bbox.w); // // Get height and width of zoomed math and original math // style.position = "absolute"; var zW = node.offsetWidth, zH = node.offsetHeight, mH = math.firstChild.offsetHeight, mW = math.firstChild.offsetWidth; node.style.position = ""; // return {Y:-EVENT.getBBox(span).h, mW:mW, mH:mH, zW:zW, zH:zH}; }, Remove: function (jax) { var node = document.getElementById(jax.inputID+"-Frame"); if (node && jax.CHTML.display) node = node.parentNode; if (node) node.parentNode.removeChild(node); delete jax.CHTML; }, /********************************************/ ID: 0, idPostfix: "", GetID: function () {this.ID++; return this.ID}, /********************************************/ MATHSPACE: { veryverythinmathspace: 1/18, verythinmathspace: 2/18, thinmathspace: 3/18, mediummathspace: 4/18, thickmathspace: 5/18, verythickmathspace: 6/18, veryverythickmathspace: 7/18, negativeveryverythinmathspace: -1/18, negativeverythinmathspace: -2/18, negativethinmathspace: -3/18, negativemediummathspace: -4/18, negativethickmathspace: -5/18, negativeverythickmathspace: -6/18, negativeveryverythickmathspace: -7/18, thin: .04, medium: .06, thick: .1, infinity: BIGDIMEN }, SPACECLASS: { thinmathspace: "MJXc-space1", mediummathspace: "MJXc-space2", thickmathspace: "MJXc-space3" }, pxPerInch: 96, em: 16, maxStretchyParts: 1000, // limit the number of parts allowed for // stretchy operators. See issue 366. FONTDEF: {}, TEXDEF: { x_height: .442, quad: 1, num1: .676508, num2: .393732, num3: .44373, denom1: .685951, denom2: .344841, sup1: .412892, sup2: .362892, sup3: .288888, sub1: .15, sub2: .247217, sup_drop: .386108, sub_drop: .05, delim1: 2.39, delim2: 1.0, axis_height: .25, rule_thickness: .06, big_op_spacing1: .111111, big_op_spacing2: .166666, big_op_spacing3: .2, big_op_spacing4: .45, //.6, // better spacing for under arrows and braces big_op_spacing5: .1, surd_height: .075, scriptspace: .05, nulldelimiterspace: .12, delimiterfactor: 901, delimitershortfall: .3, min_rule_thickness: 1.25 // in pixels }, /********************************************************/ // // True if text holds a single (unicode) glyph // isChar: function (text) { if (text.length === 1) return true; if (text.length !== 2) return false; var n = text.charCodeAt(0); return (n >= 0xD800 && n < 0xDBFF); }, // // Get a unicode character by number (even when it takes two character) // unicodeChar: function (n) { if (n < 0xFFFF) return String.fromCharCode(n); n -= 0x10000; return String.fromCharCode((n>>10)+0xD800) + String.fromCharCode((n&0x3FF)+0xDC00); }, // // Get the unicode number of a (possibly multi-character) string // getUnicode: function (string) { var n = string.text.charCodeAt(string.i); string.i++; if (n >= 0xD800 && n < 0xDBFF) { n = (((n-0xD800)<<10)+(string.text.charCodeAt(string.i)-0xDC00))+0x10000; string.i++; } return n; }, // // Get the list of actions for a given character in a given variant // (processing remaps, multi-character results, and so on). Results are // cached so that future lookups for the same variant/n pair will not // require looking through the data again. // getCharList: function (variant,n) { var id, M, cache = variant.cache, nn = n; if (cache[n]) return cache[n]; if (n > 0xFFFF && this.FONTDATA.RemapPlane1) { var nv = this.FONTDATA.RemapPlane1(n,variant); n = nv.n; variant = nv.variant; } var RANGES = this.FONTDATA.RANGES, VARIANT = this.FONTDATA.VARIANT; if (n >= RANGES[0].low && n <= RANGES[RANGES.length-1].high) { for (id = 0, M = RANGES.length; id < M; id++) { if (RANGES[id].name === "alpha" && variant.noLowerCase) continue; var N = variant["offset"+RANGES[id].offset]; if (N && n >= RANGES[id].low && n <= RANGES[id].high) { if (RANGES[id].remap && RANGES[id].remap[n]) { n = N + RANGES[id].remap[n]; } else { n = n - RANGES[id].low + N; if (RANGES[id].add) {n += RANGES[id].add} } if (variant["variant"+RANGES[id].offset]) variant = VARIANT[variant["variant"+RANGES[id].offset]]; break; } } } cache[nn] = this.remapChar(variant,n,0); return cache[nn]; }, remapChar: function (variant,n,N) { var list = [], VARIANT = this.FONTDATA.VARIANT; if (variant.remap && variant.remap[n]) { n = variant.remap[n]; if (variant.remap.variant) {variant = VARIANT[variant.remap.variant]} } else if (this.FONTDATA.REMAP[n] && !variant.noRemap) { n = this.FONTDATA.REMAP[n]; } if (isArray(n)) { if (n[2]) N = MAXREMAP; // stop remapping variant = VARIANT[n[1]]; n = n[0]; } if (typeof(n) === "string") { var string = {text:n, i:0, length:n.length}; while (string.i < string.length) { n = this.getUnicode(string); var chars = this.getCharList(variant,n); if (chars) list.push.apply(list,chars); } } else { if (variant.cache[n]) {list = variant.cache[n]} else {variant.cache[n] = list = this.lookupChar(variant,n,N)} } return list; }, // // After all remapping has been done, look up a character // in the fonts for a given variant, chaining to other // variants as needed. Return an undefined character if // it isn't found in the given variant. // lookupChar: function (variant,n,N) { var VARIANT = variant; while (variant) { for (var i = 0, m = variant.fonts.length; i < m; i++) { var font = this.FONTDATA.FONTS[variant.fonts[i]]; if (typeof(font) === "string") this.loadFont(font); var C = font[n]; if (C) { this.fixChar(C,n); if (C[5].space) return [{type:"space", w:C[2], font:font}]; return [{type:"char", font:font, n:n}]; } else if (font.Extra) { this.findBlock(font,n); } } variant = this.FONTDATA.VARIANT[variant.chain]; if (variant && variant.remap && variant.remap[n] && N++ < MAXREMAP) { return this.remapChar(variant,n,N); } } return [this.unknownChar(VARIANT,n)]; }, fixChar: function (C,n) { if (C.length === 5) C[5] = {}; if (C.c == null) { C[0] /= 1000; C[1] /= 1000; C[2] /= 1000; C[3] /= 1000; C[4] /= 1000; C.c = this.unicodeChar(n); } return C; }, findBlock: function (font,n) { var extra = font.Extra, name = font.file, file; for (var i = 0, m = extra.length; i < m; i++) { if (typeof(extra[i]) === "number") { if (n === extra[i]) {file = name; break} } else { if (n < extra[i][0]) return; if (n <= extra[i][1]) {file = name; break} } } // // Currently this only loads one extra file, but that // might need to be expanded in the future. // if (file) {delete font.Extra; this.loadFont(name)} }, // // Create a fake font entry for an unknown character. // unknownChar: function (variant,n) { HUB.signal.Post(["CommonHTML Jax - unknown char",n,variant]); var id = ""; if (variant.bold) id += "B"; if (variant.italic) id += "I"; var unknown = this.FONTDATA.UNKNOWN[id||"R"]; // cache of previously measured characters if (!unknown[n]) this.getUnknownChar(unknown,n); return {type:"unknown", n:n, font:unknown}; }, getUnknownChar: function (unknown,n) { var c = this.unicodeChar(n); var HDW = this.getHDW(c,unknown.className); // ### FIXME: provide a means of setting the height and depth for individual characters unknown[n] = [.8,.2,HDW.w,0,HDW.w,{a:Math.max(0,(HDW.h-HDW.d)/2), h:HDW.h, d:HDW.d}]; unknown[n].c = c; }, styledText: function (variant,text) { HUB.signal.Post(["CommonHTML Jax - styled text",text,variant]); var style = variant.style; var id = "_"+(style["font-family"]||variant.className||""); if (style["font-weight"]) id += "_"+style["font-weight"]; if (style["font-style"]) id += "_"+style["font-style"]; if (!this.STYLEDTEXT) this.STYLEDTEXT = {}; if (!this.STYLEDTEXT[id]) this.STYLEDTEXT[id] = {className:variant.className||""}; var unknown = this.STYLEDTEXT[id]; if (!unknown["_"+text]) { var HDW = this.getHDW(text,variant.className||"",style); unknown["_"+text] = [.8,.2,HDW.w,0,HDW.w,{a:Math.max(0,(HDW.h-HDW.d)/2), h:HDW.h, d:HDW.d}]; unknown["_"+text].c = text; } return {type:"unknown", n:"_"+text, font:unknown, style:style, rscale:variant.rscale}; }, // // Get the height, depth, and width of a character // (height and depth are of the font, not the character). // WARNING: causes reflow of the page! // getHDW: function (c,name,styles) { var test1 = CHTML.addElement(CHTML.CHTMLnode,"mjx-chartest",{className:name},[["mjx-char",{style:styles},[c]]]); var test2 = CHTML.addElement(CHTML.CHTMLnode,"mjx-chartest",{className:name},[["mjx-char",{style:styles},[c,["mjx-box"]]]]); test1.firstChild.style.fontSize = test2.firstChild.style.fontSize = ""; var em = 5*CHTML.em; var H1 = test1.offsetHeight, H2 = test2.offsetHeight, W = test1.offsetWidth; CHTML.CHTMLnode.removeChild(test1); CHTML.CHTMLnode.removeChild(test2); if (H2 === 0) { em = 5*CHTML.defaultEm; var test = document.body.appendChild(document.createElement("div")); test.appendChild(test1); test.appendChild(test2); H1 = test1.offsetHeight, H2 = test2.offsetHeight, W = test1.offsetWidth; document.body.removeChild(test); } var d = (H2-1000)/em, w = W/em, h = H1/em - d; return {h:h, d:d, w:w} }, /********************************************************/ // // Process a character list into a given node and return // the updated bounding box. // addCharList: function (node,list,bbox) { var state = {text:"", className:null, a:0}; for (var i = 0, m = list.length; i < m; i++) { var item = list[i]; if (this.charList[item.type]) (this.charList[item.type])(item,node,bbox,state,m); } if (state.text !== "") { if (node.childNodes.length) { this.charList.flushText(node,state); } else { HTML.addText(node,state.text); if (node.className) node.className += " "+state.className; else node.className = state.className; } } bbox.b = (state.flushed ? 0 : bbox.a); }, // // The various item types are processed by these // functions. // charList: { // // Character from the known fonts // "char": function (item,node,bbox,state,m) { var font = item.font, remap = (font.remapCombining||{})[item.n]; if (font.className === state.className) { remap = null; } else if (state.className || (remap && state.text !== "")) { this.flushText(node,state); } if (!state.a) state.a = font.centerline/1000; if (state.a > (bbox.a||0)) bbox.a = state.a; state.className = font.className; var C = font[item.n]; if (remap) { var FONT = font; if (isArray(remap)) { FONT = CHTML.FONTDATA.FONTS[remap[1]]; remap = remap[0]; if (typeof(FONT) === 'string') CHTML.loadFont(FONT); } if (FONT[item.n]) CHTML.fixChar(FONT[item.n],item.n); C = CHTML.fixChar(FONT[remap],remap); state.className = FONT.className; } state.text += C.c; if (bbox.h < C[0]+HFUZZ) bbox.t = bbox.h = C[0]+HFUZZ; if (bbox.d < C[1]+DFUZZ) bbox.b = bbox.d = C[1]+DFUZZ; if (bbox.l > bbox.w+C[3]) bbox.l = bbox.w+C[3]; if (bbox.r < bbox.w+C[4]) bbox.r = bbox.w+C[4]; bbox.w += C[2] * (item.rscale||1); if (m == 1 && font.skew && font.skew[item.n]) bbox.skew = font.skew[item.n]; if (C[5] && C[5].rfix) this.flushText(node,state).style.marginRight = CHTML.Em(C[5].rfix/1000); if (remap) { // // Remap combining characters to non-combining versions since Safari // handles them differently from everyone else. (#1709) // var chr = this.flushText(node,state); var r = (FONT[item.n]||font[item.n])[4] - (C[4] - C[2]); chr.style.marginLeft = CHTML.Em(-C[2]-r); if (r < 0) chr.style.marginRight = CHTML.Em(-r); } }, // // Space characters (not actually in the fonts) // space: function (item,node,bbox,state) { if (item.w) { if (state.text === "") state.className = item.font.className; this.flushText(node,state).style.marginRight = CHTML.Em(item.w); bbox.w += item.w; } }, // // An unknown character (one not in the font data) // unknown: function (item,node,bbox,state) { (this["char"])(item,node,bbox,state,0); var C = item.font[item.n]; if (C[5].a) { state.a = C[5].a; if (bbox.a == null || state.a > bbox.a) bbox.a = state.a; } node = this.flushText(node,state,item.style); if (C[2] < 3) node.style.width = CHTML.Em(C[2]); // only force width if not too large (#1718) }, // // Put the pending text into a box of the class, and // reset the data about the text. // flushText: function (node,state,style) { node = CHTML.addElement(node,"mjx-charbox", {className:state.className,style:style},[state.text]); if (state.a) node.style.paddingBottom = CHTML.Em(state.a); state.text = ""; state.className = null; state.a = 0; state.flushed = true; return node; } }, // // Add the given text (in the given variant) into the given node, and // update the bounding box of the result. Make sure the node's DOM // bounding box matches the contents. // handleText: function (node,text,variant,bbox) { if (node.childNodes.length === 0) { CHTML.addElement(node,"mjx-char"); bbox = CHTML.BBOX.empty(bbox); } if (typeof(variant) === "string") variant = this.FONTDATA.VARIANT[variant]; if (!variant) variant = this.FONTDATA.VARIANT[MML.VARIANT.NORMAL]; var string = {text:text, i:0, length:text.length}, list = []; if (variant.style && string.length) { list.push(this.styledText(variant,text)); } else { while (string.i < string.length) { var n = this.getUnicode(string); list.push.apply(list,this.getCharList(variant,n)); } } if (list.length) this.addCharList(node.firstChild,list,bbox); bbox.clean(); if (bbox.d < 0) {bbox.D = bbox.d; bbox.d = 0} if (bbox.h - bbox.a) node.firstChild.style[bbox.h - bbox.a < 0 ? "marginTop" : "paddingTop"] = this.EmRounded(bbox.h-bbox.a); if (bbox.d > -bbox.b) node.firstChild.style.paddingBottom = this.EmRounded(bbox.d+bbox.b); return bbox; }, /********************************************************/ createDelimiter: function (node,code,HW,BBOX,font) { if (!code) { var bbox = this.BBOX.zero(); bbox.w = bbox.r = this.TEX.nulldelimiterspace; CHTML.addElement(node,"mjx-box",{style:{width:bbox.w}}); return bbox; } if (!(HW instanceof Array)) HW = [HW,HW]; var hw = HW[1]; HW = HW[0]; var delim = {alias: code}; while (delim.alias) { code = delim.alias; delim = this.FONTDATA.DELIMITERS[code]; if (!delim) {delim = {HW: [0,this.FONTDATA.VARIANT[MML.VARIANT.NORMAL]]}} } if (delim.load) HUB.RestartAfter(AJAX.Require(this.fontDir+"/TeX/fontdata-"+delim.load+".js")); for (var i = 0, m = delim.HW.length; i < m; i++) { if (delim.HW[i][0] >= HW-.01 || (i == m-1 && !delim.stretch)) { if (delim.HW[i][3]) code = delim.HW[i][3]; bbox = this.createChar(node,[code,delim.HW[i][1]],(delim.HW[i][2]||1),font); bbox.offset = .6 * bbox.w; if (BBOX) {bbox.scale = BBOX.scale; BBOX.rscale = BBOX.rscale} return bbox; } } if (!delim.stretch) return bbox; return this["extendDelimiter"+delim.dir](node,hw,delim.stretch,BBOX,font); }, extendDelimiterV: function (node,H,delim,BBOX,font) { node = CHTML.addElement(node,"mjx-delim-v"); var tmp = CHTML.Element("span"); var top, bot, mid, ext, tbox, bbox, mbox, ebox, k = 1, c; tbox = this.createChar(tmp,(delim.top||delim.ext),1,font); top = tmp.removeChild(tmp.firstChild); bbox = this.createChar(tmp,(delim.bot||delim.ext),1,font); bot = tmp.removeChild(tmp.firstChild); mbox = ebox = CHTML.BBOX.zero(); var h = tbox.h + tbox.d + bbox.h + bbox.d - EFUZZ; node.appendChild(top); if (delim.mid) { mbox = this.createChar(tmp,delim.mid,1,font); mid = tmp.removeChild(tmp.firstChild); h += mbox.h + mbox.d; k = 2; } if (delim.min && H < h*delim.min) H = h*delim.min; if (H > h) { ebox = this.createChar(tmp,delim.ext,1,font); ext = tmp.removeChild(tmp.firstChild); var eH = ebox.h + ebox.d, eh = eH - EFUZZ; var n = Math.min(Math.ceil((H-h)/(k*eh)),this.maxStretchyParts); if (delim.fullExtenders) H = n*k*eh + h; else eh = (H-h)/(k*n); c = ebox.d + ebox.a - eH/2; // for centering of extenders ext.style.margin = ext.style.padding = ""; ext.style.lineHeight = CHTML.Em(eh); ext.style.marginBottom = CHTML.Em(c-EFUZZ/2/k); ext.style.marginTop = CHTML.Em(-c-EFUZZ/2/k); var TEXT = ext.textContent, text = "\n"+TEXT; while (--n > 0) TEXT += text; ext.textContent = TEXT; node.appendChild(ext); if (delim.mid) { node.appendChild(mid); node.appendChild(ext.cloneNode(true)); } } else { c = (H-h-EFUZZ) / k; top.style.marginBottom = CHTML.Em(c+parseFloat(top.style.marginBottom||"0")); if (delim.mid) node.appendChild(mid); bot.style.marginTop = CHTML.Em(c+parseFloat(bot.style.marginTop||"0")); } node.appendChild(bot); var vbox = CHTML.BBOX({ w: Math.max(tbox.w,ebox.w,bbox.w,mbox.w), l: Math.min(tbox.l,ebox.l,bbox.l,mbox.l), r: Math.max(tbox.r,ebox.r,bbox.r,mbox.r), h: H-bbox.d, d: bbox.d, t: H-bbox.d, b: bbox.d }); vbox.offset = .5 * vbox.w; if (BBOX) {vbox.scale = BBOX.scale; vbox.rscale = BBOX.rscale} return vbox; }, extendDelimiterH: function (node,W,delim,BBOX,font) { node = CHTML.addElement(node,"mjx-delim-h"); var tmp = CHTML.Element("span"); var left, right, mid, ext, ext2, lbox, rbox, mbox, ebox, k = 1; lbox = this.createChar(tmp,(delim.left||delim.rep),1,font); left = tmp.removeChild(tmp.firstChild); rbox = this.createChar(tmp,(delim.right||delim.rep),1,font); right = tmp.removeChild(tmp.firstChild); ebox = this.createChar(tmp,delim.rep,1,font); ext = tmp.removeChild(tmp.firstChild); left.style.marginLeft = CHTML.Em(-lbox.l); right.style.marginRight = CHTML.Em(rbox.r-rbox.w); node.appendChild(left); var hbox = CHTML.BBOX.zero(); hbox.h = Math.max(lbox.h,rbox.h,ebox.h); hbox.d = Math.max(lbox.D||lbox.d,rbox.D||rbox.d,ebox.D||ebox.d); var w = (lbox.r - lbox.l) + (rbox.r - rbox.l) - EFUZZ; if (delim.mid) { mbox = this.createChar(tmp,delim.mid,1,font); mid = tmp.removeChild(tmp.firstChild); mid.style.marginleft = CHTML.Em(-mbox.l); mid.style.marginRight = CHTML.Em(mbox.r-mbox.w); w += mbox.r - mbox.l + EFUZZ; k = 2; if (mbox.h > hbox.h) hbox.h = mbox.h; if (mbox.d > hbox.d) hbox.d = mbox.d; } if (delim.min && W < w*delim.min) W = w*delim.min; hbox.w = hbox.r = W; if (W > w) { var eW = ebox.r-ebox.l, ew = eW - EFUZZ; var n = Math.min(Math.ceil((W-w)/(k*ew)),this.maxStretchyParts); if (delim.fullExtenders) W = n*k*ew + w; else ew = (W-w)/(k*n); var c = (eW - ew + EFUZZ/k) / 2; // for centering of extenders ext.style.marginLeft = CHTML.Em(-ebox.l-c); ext.style.marginRight = CHTML.Em(ebox.r-ebox.w+c); ext.style.letterSpacing = CHTML.Em(-(ebox.w-ew)); left.style.marginRight = CHTML.Em(lbox.r-lbox.w); right.style.marginleft = CHTML.Em(-rbox.l); var TEXT = ext.textContent, text = TEXT; while (--n > 0) TEXT += text; ext.textContent = TEXT; node.appendChild(ext); if (delim.mid) { node.appendChild(mid); ext2 = node.appendChild(ext.cloneNode(true)); } } else { c = (W-w-EFUZZ/k) / 2; left.style.marginRight = CHTML.Em(lbox.r-lbox.w+c); if (delim.mid) node.appendChild(mid); right.style.marginLeft = CHTML.Em(-rbox.l+c); } node.appendChild(right); this.adjustHeights([left,ext,mid,ext2,right],[lbox,ebox,mbox,ebox,rbox],hbox); if (BBOX) {hbox.scale = BBOX.scale; hbox.rscale = BBOX.rscale} return hbox; }, adjustHeights: function (nodes,box,bbox) { // // To get alignment right in horizontal delimiters, we force all // the elements to the same height and depth // var T = bbox.h, B = bbox.d; if (bbox.d < 0) {B = -bbox.d; bbox.D = bbox.d; bbox.d = 0} for (var i = 0, m = nodes.length; i < m; i++) if (nodes[i]) { nodes[i].style.paddingTop = CHTML.Em(T-box[i].a); nodes[i].style.paddingBottom = CHTML.Em(B+box[i].a); nodes[i].style.marginTop = nodes[i].style.marginBottom = 0; } }, createChar: function (node,data,scale,font) { // ### FIXME: handle cache better (by data[1] and font) var text = "", variant = {fonts: [data[1]], noRemap:true, cache:{}}; if (font && font === MML.VARIANT.BOLD && this.FONTDATA.FONTS[data[1]+"-Bold"]) variant.fonts = [data[1]+"-Bold",data[1]]; if (typeof(data[1]) !== "string") variant = data[1]; if (data[0] instanceof Array) { for (var i = 0, m = data[0].length; i < m; i++) text += String.fromCharCode(data[0][i]); } else text = String.fromCharCode(data[0]); if (data[4]) scale *= data[4]; var bbox = this.handleText(node,text,variant), style = node.firstChild.style; if (scale !== 1) style.fontSize = this.Percent(scale); if (data[2]) { // x offset style.paddingLeft = this.Em(data[2]); bbox.w += data[2]; bbox.r += data[2]; } if (data[3]) { // y offset style.verticalAlign = this.Em(data[3]); bbox.h += data[3]; if (bbox.h < 0) bbox.h = 0; } if (data[5]) { // extra height style.marginTop = this.Em(data[5]); bbox.h += data[5]; bbox.t += data[5]; } if (data[6]) { // extra depth style.marginBottom = this.Em(data[6]); bbox.d += data[6]; bbox.b += data[6]; } return bbox; }, /********************************************************/ // // ### FIXME: Handle mu's // length2em: function (length,size,scale) { 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 (this.MATHSPACE[length]) return this.MATHSPACE[length]; 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 (!scale) scale = 1; scale = 1 /this.em / scale; if (unit === "em") return m; if (unit === "ex") return m * this.TEX.x_height; if (unit === "%") return m / 100 * size; if (unit === "px") return m * scale; if (unit === "pt") return m / 10; // 10 pt to an em if (unit === "pc") return m * 1.2; // 12 pt to a pc scale *= this.pxPerInch; if (unit === "in") return m * scale; if (unit === "cm") return m * scale / 2.54; // 2.54 cm to an inch if (unit === "mm") return m * scale / 25.4; // 10 mm to a cm if (unit === "mu") return m / 18; // 18mu to an em for the scriptlevel return m*size; // relative to given size (or 1em as default) }, thickness2em: function (length,scale) { var thick = CHTML.TEX.rule_thickness/(scale||1); if (length === MML.LINETHICKNESS.MEDIUM) return thick; if (length === MML.LINETHICKNESS.THIN) return .67*thick; if (length === MML.LINETHICKNESS.THICK) return 1.67*thick; ret