/************************************************************* * * [Contrib]/a11y/auto-collapse.js * * Implements the ability to have long expressions collapse * automatically on screen size changes. * * --------------------------------------------------------------------- * * Copyright (c) 2016-2017 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 (HUB) { var SETTINGS = HUB.config.menuSettings; var COOKIE = {}; // replaced when menu is available // // Set up the a11y path,if it isn't already in place // var PATH = MathJax.Ajax.config.path; if (!PATH.a11y) PATH.a11y = HUB.config.root + "/extensions/a11y"; var Collapse = MathJax.Extension["auto-collapse"] = { version: "1.2.3", config: HUB.CombineConfig("auto-collapse",{ disabled: false }), dependents: [], // the extensions that depend on this one /*****************************************************************/ Enable: function (update,menu) { SETTINGS.autocollapse = true; if (menu) COOKIE.autocollapse = true this.config.disabled = false; MathJax.Extension.collapsible.Enable(false,menu); if (update) { HUB.Queue( ["Reprocess",HUB], ["CollapseWideMath",this] ); } }, Disable: function (update,menu) { SETTINGS.autocollapse = false; if (menu) COOKIE.autocollapse = false; this.config.disabled = true; for (var i = this.dependents.length-1; i >= 0; i--) { var dependent = this.dependents[i]; if (dependent.Disable) dependent.Disable(false,menu); } if (update) HUB.Queue(["Rerender",HUB]); }, // // Register a dependent // Dependent: function (extension) { this.dependents.push(extension); }, Startup: function () { // // Inform collapsible extension that we are a dependent // var Collapsible = MathJax.Extension.collapsible; if (Collapsible) Collapsible.Dependent(this); // // Add the filter into the post-input hooks (priority 150, so other // hooks run first, in particular, the enrichment and complexity hooks). // HUB.postInputHooks.Add(["Filter",Collapse],150); // // Add the auto-collapsing // HUB.Queue(function () {return Collapse.CollapseWideMath()}); // // Add a resize handler to check for math that needs // to be collapsed or expanded. // if (window.addEventListener) window.addEventListener("resize",Collapse.resizeHandler,false); else if (window.attachEvent) window.attachEvent("onresize",Collapse.resizeHandler); else window.onresize = Collapse.resizeHandler; }, // // If the math is block-level (or in an element "by itself"), then // add the SRE actions for this element. // Filter: function (jax,id,script) { if (!jax.enriched || this.config.disabled) return; if (jax.root.Get("display") === "block" || script.parentNode.childNodes.length <= 3) { jax.root.SRE = {action: this.Actions(jax.root)}; } }, // // Produce an array of collapsible actions // sorted by depth and complexity // Actions: function (node) { var actions = []; this.getActions(node,0,actions); return this.sortActions(actions); }, getActions: function (node,depth,actions) { if (node.isToken || !node.data) return; depth++; for (var i = 0, m = node.data.length; i < m; i++) { if (node.data[i]) { var child = node.data[i]; if (child.collapsible) { if (!actions[depth]) actions[depth] = []; actions[depth].push(child); this.getActions(child.data[1],depth,actions); } else { this.getActions(child,depth,actions); } } } }, sortActions: function (actions) { var ACTIONS = []; for (var i = 0, m = actions.length; i < m; i++) { if (actions[i]) ACTIONS = ACTIONS.concat(actions[i].sort(this.sortActionsBy)); } return ACTIONS; }, sortActionsBy: function (a,b) { a = a.data[1].complexity; b = b.data[1].complexity; return (a < b ? -1 : a > b ? 1 : 0); }, /*****************************************************************/ /* * These routines implement the automatic collapsing of equations * based on container widths. */ // // Find math that is too wide and collapse it. // CollapseWideMath: function (element) { if (this.config.disabled) return; this.GetContainerWidths(element); var jax = HUB.getAllJax(element); var state = {collapse: [], jax: jax, m: jax.length, i: 0, changed:false}; return this.collapseState(state); }, collapseState: function (state) { var collapse = state.collapse; while (state.i < state.m) { var jax = state.jax[state.i]; var SRE = jax.root.SRE; state.changed = false; if (SRE && SRE.action.length) { if (SRE.cwidth < SRE.m || SRE.cwidth > SRE.M) { var restart = this.getActionWidths(jax,state); if (restart) return restart; this.collapseActions(SRE,state); if (state.changed) collapse.push(jax.SourceElement()); } } state.i++; } if (collapse.length === 0) return; if (collapse.length === 1) collapse = collapse[0]; return HUB.Rerender(collapse); }, // // Find the actions that need to be collapsed to acheive // the correct width, and retain the sizes that would cause // the equation to be expanded or collapsed further. // collapseActions: function (SRE,state) { var w = SRE.width, m = w, M = 1000000; for (var j = SRE.action.length-1; j >= 0; j--) { var action = SRE.action[j], selection = action.selection; if (w > SRE.cwidth) { action.selection = 1; m = action.SREwidth; M = w; } else { action.selection = 2; } w = action.SREwidth; if (SRE.DOMupdate) { document.getElementById(action.id).setAttribute("selection",action.selection); } else if (action.selection !== selection) { state.changed = true; } } SRE.m = m; SRE.M = M; }, // // Get the widths of the different collapsings, // trapping any restarts, and restarting the process // when the event has occurred. // getActionWidths: function (jax,state) { if (!jax.root.SRE.actionWidths) { MathJax.OutputJax[jax.outputJax].getMetrics(jax); try {this.computeActionWidths(jax)} catch (err) { if (!err.restart) throw err; return MathJax.Callback.After(["collapseState",this,state],err.restart); } state.changed = true; } return null; }, // // Compute the action widths by collapsing each maction, // and recording the width of the complete equation. // computeActionWidths: function (jax) { var SRE = jax.root.SRE, actions = SRE.action, j, state = {}; SRE.width = jax.sreGetRootWidth(state); for (j = actions.length-1; j >= 0; j--) actions[j].selection = 2; for (j = actions.length-1; j >= 0; j--) { var action = actions[j]; if (action.SREwidth == null) { action.selection = 1; action.SREwidth = jax.sreGetActionWidth(state,action); } } SRE.actionWidths = true; }, // // Get the widths of the containers of tall the math elements // that can be collapsed (so we can tell which ones NEED to be // collapsed). Do this in a way that only causes two reflows. // GetContainerWidths: function (element) { var JAX = HUB.getAllJax(element); var i, m, script, span = MathJax.HTML.Element("span",{style:{display:"block"}}); var math = [], jax, root; for (i = 0, m = JAX.length; i < m; i++) { jax = JAX[i], root = jax.root, SRE = root.SRE; if (SRE && SRE.action.length) { if (SRE.width == null) { jax.sreGetMetrics(); SRE.m = SRE.width; SRE.M = 1000000; } script = jax.SourceElement(); script.previousSibling.style.display = "none"; script.parentNode.insertBefore(span.cloneNode(false),script); math.push([jax,script]); } } for (i = 0, m = math.length; i < m; i++) { jax = math[i][0], script = math[i][1]; if (script.previousSibling.offsetWidth) jax.root.SRE.cwidth = script.previousSibling.offsetWidth * jax.root.SRE.em; } for (i = 0, m = math.length; i < m; i++) { jax = math[i][0], script = math[i][1]; script.parentNode.removeChild(script.previousSibling); script.previousSibling.style.display = ""; } }, /*****************************************************************/ // // A resize handler that can be tied to the window resize event // to collapse math automatically on resize. // timer: null, running: false, retry: false, saved_delay: 0, resizeHandler: function (event) { if (Collapse.config.disabled) return; if (Collapse.running) {Collapse.retry = true; return} if (Collapse.timer) clearTimeout(Collapse.timer); Collapse.timer = setTimeout(Collapse.resizeAction, 100); }, resizeAction: function () { Collapse.timer = null; Collapse.running = true; HUB.Queue( function () { // // Prevent flicker between input and output phases. // Collapse.saved_delay = HUB.processSectionDelay; HUB.processSectionDelay = 0; }, ["CollapseWideMath",Collapse], ["resizeCheck",Collapse] ); }, resizeCheck: function () { Collapse.running = false; HUB.processSectionDelay = Collapse.saved_delay; if (Collapse.retry) { Collapse.retry = false; setTimeout(Collapse.resizeHandler,0); } } }; HUB.Register.StartupHook("End Extensions", function () { if (SETTINGS.autocollapse == null) { SETTINGS.autocollapse = !Collapse.config.disabled; } else { Collapse.config.disabled = !SETTINGS.autocollapse; } HUB.Register.StartupHook("MathMenu Ready", function () { COOKIE = MathJax.Menu.cookie; var Switch = function(menu) { Collapse[SETTINGS.autocollapse ? "Enable" : "Disable"](true,true); MathJax.Menu.saveCookie(); }; var ITEM = MathJax.Menu.ITEM, MENU = MathJax.Menu.menu; var menu = ITEM.CHECKBOX( ['AutoCollapse','Auto Collapse'], 'autocollapse', {action: Switch} ); var submenu = (MENU.FindId('Accessibility')||{}).submenu, index; if (submenu) { index = submenu.IndexOfId('AutoCollapse'); if (index !== null) { submenu.items[index] = menu; } else { index = submenu.IndexOfId('CollapsibleMath'); submenu.items.splice(index+1,0,menu); } } else { index = MENU.IndexOfId('CollapsibleMath'); MENU.items.splice(index+1,0,menu); } var init = function () {Collapse[SETTINGS.autocollapse ? "Enable" : "Disable"]()}; if (MathJax.Extension.collapse) init(); else MathJax.Hub.Register.StartupHook("Auto Collapse Ready", init); },25); // after Assistive-Explore },25); })(MathJax.Hub); /*****************************************************************/ /* * Add methods to the ElementJax and OutputJax to get the * widths of the collapsed elements. */ // // Add SRE methods to ElementJax. // MathJax.ElementJax.Augment({ sreGetMetrics: function () { MathJax.OutputJax[this.outputJax].sreGetMetrics(this,this.root.SRE); }, sreGetRootWidth: function (state) { return MathJax.OutputJax[this.outputJax].sreGetRootWidth(this,state); }, sreGetActionWidth: function (state,action) { return MathJax.OutputJax[this.outputJax].sreGetActionWidth(this,state,action); } }); // // Add default methods to base OutputJax class. // MathJax.OutputJax.Augment({ getMetrics: function () {}, // make sure it is defined sreGetMetrics: function (jax,SRE) {SRE.cwidth = 1000000; SRE.width = 0; SRE.em = 12}, sreGetRootWidth: function (jax,state) {return 0}, sreGetActionWidth: function (jax,state,action) {return 0} }); // // Specific implementations for HTML-CSS output. // MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () { MathJax.OutputJax["HTML-CSS"].Augment({ sreGetMetrics: function (jax,SRE) { SRE.width = jax.root.data[0].HTMLspanElement().parentNode.bbox.w; SRE.em = 1 / jax.HTMLCSS.em / jax.HTMLCSS.scale; }, sreGetRootWidth: function (jax,state) { var html = jax.root.data[0].HTMLspanElement(); state.box = html.parentNode; return state.box.bbox.w; }, sreGetActionWidth: function (jax,state,action) { return jax.root.data[0].toHTML(state.box).bbox.w; } }); }); // // Specific implementations for SVG output. // MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { MathJax.OutputJax.SVG.Augment({ getMetrics: function (jax) { this.em = MathJax.ElementJax.mml.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex; this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth; }, sreGetMetrics: function (jax,SRE) { SRE.width = jax.root.SVGdata.w/1000; SRE.em = 1/jax.SVG.em; }, sreGetRootWidth: function (jax,state) { state.span = document.getElementById(jax.inputID+"-Frame"); return jax.root.SVGdata.w/1000; }, sreGetActionWidth: function (jax,state,action) { this.mathDiv = state.span; state.span.appendChild(this.textSVG); try {var svg = jax.root.data[0].toSVG()} catch(err) {var error = err} state.span.removeChild(this.textSVG); if (error) throw error; // can happen when a restart is needed return jax.root.data[0].SVGdata.w/1000; } }); }); // // Specific implementations for CommonHTML output. // MathJax.Hub.Register.StartupHook("CommonHTML Jax Ready",function () { MathJax.OutputJax.CommonHTML.Augment({ sreGetMetrics: function (jax,SRE) { SRE.width = jax.root.CHTML.w; SRE.em = 1 / jax.CHTML.em / jax.CHTML.scale; }, sreGetRootWidth: function (jax,state) { state.span = document.getElementById(jax.inputID+"-Frame").firstChild; state.tmp = document.createElement("span"); state.tmp.className = state.span.className; return jax.root.CHTML.w / jax.CHTML.scale; }, sreGetActionWidth: function (jax,state,action) { state.span.parentNode.replaceChild(state.tmp,state.span); MathJax.OutputJax.CommonHTML.CHTMLnode = state.tmp; try {jax.root.data[0].toCommonHTML(state.tmp)} catch (err) {var error = err} state.tmp.parentNode.replaceChild(state.span,state.tmp); if (error) throw error; // can happen when a restart is needed return jax.root.data[0].CHTML.w / jax.CHTML.scale; } }); }); // // Specific implementations for NativeMML output. // MathJax.Hub.Register.StartupHook("NativeMML Jax Ready",function () { MathJax.OutputJax.NativeMML.Augment({ sreGetMetrics: function (jax,SRE) { var span = document.getElementById(jax.inputID+"-Frame"); SRE.width = span.offsetWidth; SRE.em = 1; SRE.DOMupdate = true; }, sreGetRootWidth: function (jax,state) { state.span = document.getElementById(jax.inputID+"-Frame").firstChild; return state.span.offsetWidth; }, sreGetActionWidth: function (jax,state,action) { var maction = document.getElementById(action.id); maction.setAttribute("selection",1); var w = state.span.offsetWidth; return w; } }); }); /*****************************************************************/ // // Load the collapsible extension and // signal the start up when that has loaded. // MathJax.Ajax.Require("[a11y]/collapsible.js"); MathJax.Hub.Register.StartupHook("Collapsible Ready", function () { MathJax.Extension["auto-collapse"].Startup(); // Initialize the collapsing process MathJax.Hub.Startup.signal.Post("Auto Collapse Ready"); MathJax.Ajax.loadComplete("[a11y]/auto-collapse.js"); });