auto-collapse.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*************************************************************
  2. *
  3. * [Contrib]/a11y/auto-collapse.js
  4. *
  5. * Implements the ability to have long expressions collapse
  6. * automatically on screen size changes.
  7. *
  8. * ---------------------------------------------------------------------
  9. *
  10. * Copyright (c) 2016-2017 The MathJax Consortium
  11. *
  12. * Licensed under the Apache License, Version 2.0 (the "License");
  13. * you may not use this file except in compliance with the License.
  14. * You may obtain a copy of the License at
  15. *
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * Unless required by applicable law or agreed to in writing, software
  19. * distributed under the License is distributed on an "AS IS" BASIS,
  20. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. * See the License for the specific language governing permissions and
  22. * limitations under the License.
  23. */
  24. (function (HUB) {
  25. var SETTINGS = HUB.config.menuSettings;
  26. var COOKIE = {}; // replaced when menu is available
  27. //
  28. // Set up the a11y path,if it isn't already in place
  29. //
  30. var PATH = MathJax.Ajax.config.path;
  31. if (!PATH.a11y) PATH.a11y = HUB.config.root + "/extensions/a11y";
  32. var Collapse = MathJax.Extension["auto-collapse"] = {
  33. version: "1.2.3",
  34. config: HUB.CombineConfig("auto-collapse",{
  35. disabled: false
  36. }),
  37. dependents: [], // the extensions that depend on this one
  38. /*****************************************************************/
  39. Enable: function (update,menu) {
  40. SETTINGS.autocollapse = true;
  41. if (menu) COOKIE.autocollapse = true
  42. this.config.disabled = false;
  43. MathJax.Extension.collapsible.Enable(false,menu);
  44. if (update) {
  45. HUB.Queue(
  46. ["Reprocess",HUB],
  47. ["CollapseWideMath",this]
  48. );
  49. }
  50. },
  51. Disable: function (update,menu) {
  52. SETTINGS.autocollapse = false;
  53. if (menu) COOKIE.autocollapse = false;
  54. this.config.disabled = true;
  55. for (var i = this.dependents.length-1; i >= 0; i--) {
  56. var dependent = this.dependents[i];
  57. if (dependent.Disable) dependent.Disable(false,menu);
  58. }
  59. if (update) HUB.Queue(["Rerender",HUB]);
  60. },
  61. //
  62. // Register a dependent
  63. //
  64. Dependent: function (extension) {
  65. this.dependents.push(extension);
  66. },
  67. Startup: function () {
  68. //
  69. // Inform collapsible extension that we are a dependent
  70. //
  71. var Collapsible = MathJax.Extension.collapsible;
  72. if (Collapsible) Collapsible.Dependent(this);
  73. //
  74. // Add the filter into the post-input hooks (priority 150, so other
  75. // hooks run first, in particular, the enrichment and complexity hooks).
  76. //
  77. HUB.postInputHooks.Add(["Filter",Collapse],150);
  78. //
  79. // Add the auto-collapsing
  80. //
  81. HUB.Queue(function () {return Collapse.CollapseWideMath()});
  82. //
  83. // Add a resize handler to check for math that needs
  84. // to be collapsed or expanded.
  85. //
  86. if (window.addEventListener) window.addEventListener("resize",Collapse.resizeHandler,false);
  87. else if (window.attachEvent) window.attachEvent("onresize",Collapse.resizeHandler);
  88. else window.onresize = Collapse.resizeHandler;
  89. },
  90. //
  91. // If the math is block-level (or in an element "by itself"), then
  92. // add the SRE actions for this element.
  93. //
  94. Filter: function (jax,id,script) {
  95. if (!jax.enriched || this.config.disabled) return;
  96. if (jax.root.Get("display") === "block" ||
  97. script.parentNode.childNodes.length <= 3) {
  98. jax.root.SRE = {action: this.Actions(jax.root)};
  99. }
  100. },
  101. //
  102. // Produce an array of collapsible actions
  103. // sorted by depth and complexity
  104. //
  105. Actions: function (node) {
  106. var actions = [];
  107. this.getActions(node,0,actions);
  108. return this.sortActions(actions);
  109. },
  110. getActions: function (node,depth,actions) {
  111. if (node.isToken || !node.data) return;
  112. depth++;
  113. for (var i = 0, m = node.data.length; i < m; i++) {
  114. if (node.data[i]) {
  115. var child = node.data[i];
  116. if (child.collapsible) {
  117. if (!actions[depth]) actions[depth] = [];
  118. actions[depth].push(child);
  119. this.getActions(child.data[1],depth,actions);
  120. } else {
  121. this.getActions(child,depth,actions);
  122. }
  123. }
  124. }
  125. },
  126. sortActions: function (actions) {
  127. var ACTIONS = [];
  128. for (var i = 0, m = actions.length; i < m; i++) {
  129. if (actions[i]) ACTIONS = ACTIONS.concat(actions[i].sort(this.sortActionsBy));
  130. }
  131. return ACTIONS;
  132. },
  133. sortActionsBy: function (a,b) {
  134. a = a.data[1].complexity; b = b.data[1].complexity;
  135. return (a < b ? -1 : a > b ? 1 : 0);
  136. },
  137. /*****************************************************************/
  138. /*
  139. * These routines implement the automatic collapsing of equations
  140. * based on container widths.
  141. */
  142. //
  143. // Find math that is too wide and collapse it.
  144. //
  145. CollapseWideMath: function (element) {
  146. if (this.config.disabled) return;
  147. this.GetContainerWidths(element);
  148. var jax = HUB.getAllJax(element);
  149. var state = {collapse: [], jax: jax, m: jax.length, i: 0, changed:false};
  150. return this.collapseState(state);
  151. },
  152. collapseState: function (state) {
  153. var collapse = state.collapse;
  154. while (state.i < state.m) {
  155. var jax = state.jax[state.i];
  156. var SRE = jax.root.SRE; state.changed = false;
  157. if (SRE && SRE.action.length) {
  158. if (SRE.cwidth < SRE.m || SRE.cwidth > SRE.M) {
  159. var restart = this.getActionWidths(jax,state); if (restart) return restart;
  160. this.collapseActions(SRE,state);
  161. if (state.changed) collapse.push(jax.SourceElement());
  162. }
  163. }
  164. state.i++;
  165. }
  166. if (collapse.length === 0) return;
  167. if (collapse.length === 1) collapse = collapse[0];
  168. return HUB.Rerender(collapse);
  169. },
  170. //
  171. // Find the actions that need to be collapsed to acheive
  172. // the correct width, and retain the sizes that would cause
  173. // the equation to be expanded or collapsed further.
  174. //
  175. collapseActions: function (SRE,state) {
  176. var w = SRE.width, m = w, M = 1000000;
  177. for (var j = SRE.action.length-1; j >= 0; j--) {
  178. var action = SRE.action[j], selection = action.selection;
  179. if (w > SRE.cwidth) {
  180. action.selection = 1;
  181. m = action.SREwidth; M = w;
  182. } else {
  183. action.selection = 2;
  184. }
  185. w = action.SREwidth;
  186. if (SRE.DOMupdate) {
  187. document.getElementById(action.id).setAttribute("selection",action.selection);
  188. } else if (action.selection !== selection) {
  189. state.changed = true;
  190. }
  191. }
  192. SRE.m = m; SRE.M = M;
  193. },
  194. //
  195. // Get the widths of the different collapsings,
  196. // trapping any restarts, and restarting the process
  197. // when the event has occurred.
  198. //
  199. getActionWidths: function (jax,state) {
  200. if (!jax.root.SRE.actionWidths) {
  201. MathJax.OutputJax[jax.outputJax].getMetrics(jax);
  202. try {this.computeActionWidths(jax)} catch (err) {
  203. if (!err.restart) throw err;
  204. return MathJax.Callback.After(["collapseState",this,state],err.restart);
  205. }
  206. state.changed = true;
  207. }
  208. return null;
  209. },
  210. //
  211. // Compute the action widths by collapsing each maction,
  212. // and recording the width of the complete equation.
  213. //
  214. computeActionWidths: function (jax) {
  215. var SRE = jax.root.SRE, actions = SRE.action, j, state = {};
  216. SRE.width = jax.sreGetRootWidth(state);
  217. for (j = actions.length-1; j >= 0; j--) actions[j].selection = 2;
  218. for (j = actions.length-1; j >= 0; j--) {
  219. var action = actions[j];
  220. if (action.SREwidth == null) {
  221. action.selection = 1;
  222. action.SREwidth = jax.sreGetActionWidth(state,action);
  223. }
  224. }
  225. SRE.actionWidths = true;
  226. },
  227. //
  228. // Get the widths of the containers of tall the math elements
  229. // that can be collapsed (so we can tell which ones NEED to be
  230. // collapsed). Do this in a way that only causes two reflows.
  231. //
  232. GetContainerWidths: function (element) {
  233. var JAX = HUB.getAllJax(element);
  234. var i, m, script, span = MathJax.HTML.Element("span",{style:{display:"block"}});
  235. var math = [], jax, root;
  236. for (i = 0, m = JAX.length; i < m; i++) {
  237. jax = JAX[i], root = jax.root, SRE = root.SRE;
  238. if (SRE && SRE.action.length) {
  239. if (SRE.width == null) {
  240. jax.sreGetMetrics();
  241. SRE.m = SRE.width; SRE.M = 1000000;
  242. }
  243. script = jax.SourceElement();
  244. script.previousSibling.style.display = "none";
  245. script.parentNode.insertBefore(span.cloneNode(false),script);
  246. math.push([jax,script]);
  247. }
  248. }
  249. for (i = 0, m = math.length; i < m; i++) {
  250. jax = math[i][0], script = math[i][1];
  251. if (script.previousSibling.offsetWidth)
  252. jax.root.SRE.cwidth = script.previousSibling.offsetWidth * jax.root.SRE.em;
  253. }
  254. for (i = 0, m = math.length; i < m; i++) {
  255. jax = math[i][0], script = math[i][1];
  256. script.parentNode.removeChild(script.previousSibling);
  257. script.previousSibling.style.display = "";
  258. }
  259. },
  260. /*****************************************************************/
  261. //
  262. // A resize handler that can be tied to the window resize event
  263. // to collapse math automatically on resize.
  264. //
  265. timer: null,
  266. running: false,
  267. retry: false,
  268. saved_delay: 0,
  269. resizeHandler: function (event) {
  270. if (Collapse.config.disabled) return;
  271. if (Collapse.running) {Collapse.retry = true; return}
  272. if (Collapse.timer) clearTimeout(Collapse.timer);
  273. Collapse.timer = setTimeout(Collapse.resizeAction, 100);
  274. },
  275. resizeAction: function () {
  276. Collapse.timer = null;
  277. Collapse.running = true;
  278. HUB.Queue(
  279. function () {
  280. //
  281. // Prevent flicker between input and output phases.
  282. //
  283. Collapse.saved_delay = HUB.processSectionDelay;
  284. HUB.processSectionDelay = 0;
  285. },
  286. ["CollapseWideMath",Collapse],
  287. ["resizeCheck",Collapse]
  288. );
  289. },
  290. resizeCheck: function () {
  291. Collapse.running = false;
  292. HUB.processSectionDelay = Collapse.saved_delay;
  293. if (Collapse.retry) {
  294. Collapse.retry = false;
  295. setTimeout(Collapse.resizeHandler,0);
  296. }
  297. }
  298. };
  299. HUB.Register.StartupHook("End Extensions", function () {
  300. if (SETTINGS.autocollapse == null) {
  301. SETTINGS.autocollapse = !Collapse.config.disabled;
  302. } else {
  303. Collapse.config.disabled = !SETTINGS.autocollapse;
  304. }
  305. HUB.Register.StartupHook("MathMenu Ready", function () {
  306. COOKIE = MathJax.Menu.cookie;
  307. var Switch = function(menu) {
  308. Collapse[SETTINGS.autocollapse ? "Enable" : "Disable"](true,true);
  309. MathJax.Menu.saveCookie();
  310. };
  311. var ITEM = MathJax.Menu.ITEM,
  312. MENU = MathJax.Menu.menu;
  313. var menu = ITEM.CHECKBOX(
  314. ['AutoCollapse','Auto Collapse'], 'autocollapse', {action: Switch}
  315. );
  316. var submenu = (MENU.FindId('Accessibility')||{}).submenu, index;
  317. if (submenu) {
  318. index = submenu.IndexOfId('AutoCollapse');
  319. if (index !== null) {
  320. submenu.items[index] = menu;
  321. } else {
  322. index = submenu.IndexOfId('CollapsibleMath');
  323. submenu.items.splice(index+1,0,menu);
  324. }
  325. } else {
  326. index = MENU.IndexOfId('CollapsibleMath');
  327. MENU.items.splice(index+1,0,menu);
  328. }
  329. var init = function () {Collapse[SETTINGS.autocollapse ? "Enable" : "Disable"]()};
  330. if (MathJax.Extension.collapse) init();
  331. else MathJax.Hub.Register.StartupHook("Auto Collapse Ready", init);
  332. },25); // after Assistive-Explore
  333. },25);
  334. })(MathJax.Hub);
  335. /*****************************************************************/
  336. /*
  337. * Add methods to the ElementJax and OutputJax to get the
  338. * widths of the collapsed elements.
  339. */
  340. //
  341. // Add SRE methods to ElementJax.
  342. //
  343. MathJax.ElementJax.Augment({
  344. sreGetMetrics: function () {
  345. MathJax.OutputJax[this.outputJax].sreGetMetrics(this,this.root.SRE);
  346. },
  347. sreGetRootWidth: function (state) {
  348. return MathJax.OutputJax[this.outputJax].sreGetRootWidth(this,state);
  349. },
  350. sreGetActionWidth: function (state,action) {
  351. return MathJax.OutputJax[this.outputJax].sreGetActionWidth(this,state,action);
  352. }
  353. });
  354. //
  355. // Add default methods to base OutputJax class.
  356. //
  357. MathJax.OutputJax.Augment({
  358. getMetrics: function () {}, // make sure it is defined
  359. sreGetMetrics: function (jax,SRE) {SRE.cwidth = 1000000; SRE.width = 0; SRE.em = 12},
  360. sreGetRootWidth: function (jax,state) {return 0},
  361. sreGetActionWidth: function (jax,state,action) {return 0}
  362. });
  363. //
  364. // Specific implementations for HTML-CSS output.
  365. //
  366. MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () {
  367. MathJax.OutputJax["HTML-CSS"].Augment({
  368. sreGetMetrics: function (jax,SRE) {
  369. SRE.width = jax.root.data[0].HTMLspanElement().parentNode.bbox.w;
  370. SRE.em = 1 / jax.HTMLCSS.em / jax.HTMLCSS.scale;
  371. },
  372. sreGetRootWidth: function (jax,state) {
  373. var html = jax.root.data[0].HTMLspanElement();
  374. state.box = html.parentNode;
  375. return state.box.bbox.w;
  376. },
  377. sreGetActionWidth: function (jax,state,action) {
  378. return jax.root.data[0].toHTML(state.box).bbox.w;
  379. }
  380. });
  381. });
  382. //
  383. // Specific implementations for SVG output.
  384. //
  385. MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
  386. MathJax.OutputJax.SVG.Augment({
  387. getMetrics: function (jax) {
  388. this.em = MathJax.ElementJax.mml.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex;
  389. this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth;
  390. },
  391. sreGetMetrics: function (jax,SRE) {
  392. SRE.width = jax.root.SVGdata.w/1000;
  393. SRE.em = 1/jax.SVG.em;
  394. },
  395. sreGetRootWidth: function (jax,state) {
  396. state.span = document.getElementById(jax.inputID+"-Frame");
  397. return jax.root.SVGdata.w/1000;
  398. },
  399. sreGetActionWidth: function (jax,state,action) {
  400. this.mathDiv = state.span;
  401. state.span.appendChild(this.textSVG);
  402. try {var svg = jax.root.data[0].toSVG()} catch(err) {var error = err}
  403. state.span.removeChild(this.textSVG);
  404. if (error) throw error; // can happen when a restart is needed
  405. return jax.root.data[0].SVGdata.w/1000;
  406. }
  407. });
  408. });
  409. //
  410. // Specific implementations for CommonHTML output.
  411. //
  412. MathJax.Hub.Register.StartupHook("CommonHTML Jax Ready",function () {
  413. MathJax.OutputJax.CommonHTML.Augment({
  414. sreGetMetrics: function (jax,SRE) {
  415. SRE.width = jax.root.CHTML.w;
  416. SRE.em = 1 / jax.CHTML.em / jax.CHTML.scale;
  417. },
  418. sreGetRootWidth: function (jax,state) {
  419. state.span = document.getElementById(jax.inputID+"-Frame").firstChild;
  420. state.tmp = document.createElement("span");
  421. state.tmp.className = state.span.className;
  422. return jax.root.CHTML.w / jax.CHTML.scale;
  423. },
  424. sreGetActionWidth: function (jax,state,action) {
  425. state.span.parentNode.replaceChild(state.tmp,state.span);
  426. MathJax.OutputJax.CommonHTML.CHTMLnode = state.tmp;
  427. try {jax.root.data[0].toCommonHTML(state.tmp)} catch (err) {var error = err}
  428. state.tmp.parentNode.replaceChild(state.span,state.tmp);
  429. if (error) throw error; // can happen when a restart is needed
  430. return jax.root.data[0].CHTML.w / jax.CHTML.scale;
  431. }
  432. });
  433. });
  434. //
  435. // Specific implementations for NativeMML output.
  436. //
  437. MathJax.Hub.Register.StartupHook("NativeMML Jax Ready",function () {
  438. MathJax.OutputJax.NativeMML.Augment({
  439. sreGetMetrics: function (jax,SRE) {
  440. var span = document.getElementById(jax.inputID+"-Frame");
  441. SRE.width = span.offsetWidth;
  442. SRE.em = 1; SRE.DOMupdate = true;
  443. },
  444. sreGetRootWidth: function (jax,state) {
  445. state.span = document.getElementById(jax.inputID+"-Frame").firstChild;
  446. return state.span.offsetWidth;
  447. },
  448. sreGetActionWidth: function (jax,state,action) {
  449. var maction = document.getElementById(action.id);
  450. maction.setAttribute("selection",1);
  451. var w = state.span.offsetWidth;
  452. return w;
  453. }
  454. });
  455. });
  456. /*****************************************************************/
  457. //
  458. // Load the collapsible extension and
  459. // signal the start up when that has loaded.
  460. //
  461. MathJax.Ajax.Require("[a11y]/collapsible.js");
  462. MathJax.Hub.Register.StartupHook("Collapsible Ready", function () {
  463. MathJax.Extension["auto-collapse"].Startup(); // Initialize the collapsing process
  464. MathJax.Hub.Startup.signal.Post("Auto Collapse Ready");
  465. MathJax.Ajax.loadComplete("[a11y]/auto-collapse.js");
  466. });