MathZoom.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
  2. /* vim: set ts=2 et sw=2 tw=80: */
  3. /*************************************************************
  4. *
  5. * MathJax/extensions/MathZoom.js
  6. *
  7. * Implements the zoom feature for enlarging math expressions. It is
  8. * loaded automatically when the Zoom menu selection changes from "None".
  9. *
  10. * ---------------------------------------------------------------------
  11. *
  12. * Copyright (c) 2010-2017 The MathJax Consortium
  13. *
  14. * Licensed under the Apache License, Version 2.0 (the "License");
  15. * you may not use this file except in compliance with the License.
  16. * You may obtain a copy of the License at
  17. *
  18. * http://www.apache.org/licenses/LICENSE-2.0
  19. *
  20. * Unless required by applicable law or agreed to in writing, software
  21. * distributed under the License is distributed on an "AS IS" BASIS,
  22. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. * See the License for the specific language governing permissions and
  24. * limitations under the License.
  25. */
  26. (function (HUB,HTML,AJAX,HTMLCSS,nMML) {
  27. var VERSION = "2.7.2";
  28. var CONFIG = HUB.CombineConfig("MathZoom",{
  29. styles: {
  30. //
  31. // The styles for the MathZoom display box
  32. //
  33. "#MathJax_Zoom": {
  34. position:"absolute", "background-color":"#F0F0F0", overflow:"auto",
  35. display:"block", "z-index":301, padding:".5em", border:"1px solid black", margin:0,
  36. "font-weight":"normal", "font-style":"normal",
  37. "text-align":"left", "text-indent":0, "text-transform":"none",
  38. "line-height":"normal", "letter-spacing":"normal", "word-spacing":"normal",
  39. "word-wrap":"normal", "white-space":"nowrap", "float":"none",
  40. "-webkit-box-sizing":"content-box", // Android ≤ 2.3, iOS ≤ 4
  41. "-moz-box-sizing":"content-box", // Firefox ≤ 28
  42. "box-sizing":"content-box", // Chrome, Firefox 29+, IE 8+, Opera, Safari 5.1
  43. "box-shadow":"5px 5px 15px #AAAAAA", // Opera 10.5 and IE9
  44. "-webkit-box-shadow":"5px 5px 15px #AAAAAA", // Safari 3 and Chrome
  45. "-moz-box-shadow":"5px 5px 15px #AAAAAA", // Forefox 3.5
  46. "-khtml-box-shadow":"5px 5px 15px #AAAAAA", // Konqueror
  47. filter: "progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true')" // IE
  48. },
  49. //
  50. // The styles for the hidden overlay (should not need to be adjusted by the page author)
  51. //
  52. "#MathJax_ZoomOverlay": {
  53. position:"absolute", left:0, top:0, "z-index":300, display:"inline-block",
  54. width:"100%", height:"100%", border:0, padding:0, margin:0,
  55. "background-color":"white", opacity:0, filter:"alpha(opacity=0)"
  56. },
  57. "#MathJax_ZoomFrame": {
  58. position:"relative", display:"inline-block",
  59. height:0, width:0
  60. },
  61. "#MathJax_ZoomEventTrap": {
  62. position:"absolute", left:0, top:0, "z-index":302,
  63. display:"inline-block", border:0, padding:0, margin:0,
  64. "background-color":"white", opacity:0, filter:"alpha(opacity=0)"
  65. }
  66. }
  67. });
  68. var FALSE, HOVER, EVENT;
  69. MathJax.Hub.Register.StartupHook("MathEvents Ready",function () {
  70. EVENT = MathJax.Extension.MathEvents.Event;
  71. FALSE = MathJax.Extension.MathEvents.Event.False;
  72. HOVER = MathJax.Extension.MathEvents.Hover;
  73. });
  74. /*************************************************************/
  75. var ZOOM = MathJax.Extension.MathZoom = {
  76. version: VERSION,
  77. settings: HUB.config.menuSettings,
  78. scrollSize: 18, // width of scrool bars
  79. //
  80. // Process events passed from output jax
  81. //
  82. HandleEvent: function (event,type,math) {
  83. if (ZOOM.settings.CTRL && !event.ctrlKey) return true;
  84. if (ZOOM.settings.ALT && !event.altKey) return true;
  85. if (ZOOM.settings.CMD && !event.metaKey) return true;
  86. if (ZOOM.settings.Shift && !event.shiftKey) return true;
  87. if (!ZOOM[type]) return true;
  88. return ZOOM[type](event,math);
  89. },
  90. //
  91. // Zoom on click
  92. //
  93. Click: function (event,math) {
  94. if (this.settings.zoom === "Click") {return this.Zoom(event,math)}
  95. },
  96. //
  97. // Zoom on double click
  98. //
  99. DblClick: function (event,math) {
  100. if (this.settings.zoom === "Double-Click" || this.settings.zoom === "DoubleClick") {return this.Zoom(event,math)}
  101. },
  102. //
  103. // Zoom on hover (called by MathEvents.Hover)
  104. //
  105. Hover: function (event,math) {
  106. if (this.settings.zoom === "Hover") {this.Zoom(event,math); return true}
  107. return false;
  108. },
  109. //
  110. // Handle the actual zooming
  111. //
  112. Zoom: function (event,math) {
  113. //
  114. // Remove any other zoom and clear timers
  115. //
  116. this.Remove(); HOVER.ClearHoverTimer(); EVENT.ClearSelection();
  117. //
  118. // Find the jax
  119. //
  120. var JAX = MathJax.OutputJax[math.jaxID];
  121. var jax = JAX.getJaxFromMath(math);
  122. if (jax.hover) {HOVER.UnHover(jax)}
  123. //
  124. // Create the DOM elements for the zoom box
  125. //
  126. var container = this.findContainer(math);
  127. var Mw = Math.floor(.85*container.clientWidth),
  128. Mh = Math.max(document.body.clientHeight,document.documentElement.clientHeight);
  129. if (this.getOverflow(container) !== "visible") {Mh = Math.min(container.clientHeight,Mh)}
  130. Mh = Math.floor(.85*Mh);
  131. var div = HTML.Element(
  132. "span",{id:"MathJax_ZoomFrame"},[
  133. ["span",{id:"MathJax_ZoomOverlay", onmousedown:this.Remove}],
  134. ["span",{
  135. id:"MathJax_Zoom", onclick:this.Remove,
  136. style:{visibility:"hidden", fontSize:this.settings.zscale}
  137. },[["span",{style:{display:"inline-block", "white-space":"nowrap"}}]]
  138. ]]
  139. );
  140. var zoom = div.lastChild, span = zoom.firstChild, overlay = div.firstChild;
  141. math.parentNode.insertBefore(div,math); math.parentNode.insertBefore(math,div); // put div after math
  142. if (span.addEventListener) {span.addEventListener("mousedown",this.Remove,true)}
  143. var eW = zoom.offsetWidth || zoom.clientWidth; Mw -= eW; Mh -= eW;
  144. zoom.style.maxWidth = Mw+"px"; zoom.style.maxHeight = Mh+"px";
  145. if (this.msieTrapEventBug) {
  146. var trap = HTML.Element("span",{id:"MathJax_ZoomEventTrap", onmousedown:this.Remove});
  147. div.insertBefore(trap,zoom);
  148. }
  149. //
  150. // Display the zoomed math
  151. //
  152. if (this.msieZIndexBug) {
  153. // MSIE doesn't do z-index properly, so move the div to the document.body,
  154. // and use an image as a tracker for the usual position
  155. var tracker = HTML.addElement(document.body,"img",{
  156. src:"about:blank", id:"MathJax_ZoomTracker", width:0, height:0,
  157. style:{width:0, height:0, position:"relative"}
  158. });
  159. div.style.position = "relative";
  160. div.style.zIndex = CONFIG.styles["#MathJax_ZoomOverlay"]["z-index"];
  161. div = tracker;
  162. }
  163. var bbox = JAX.Zoom(jax,span,math,Mw,Mh);
  164. //
  165. // Fix up size and position for browsers with bugs (IE)
  166. //
  167. if (this.msiePositionBug) {
  168. if (this.msieSizeBug)
  169. {zoom.style.height = bbox.zH+"px"; zoom.style.width = bbox.zW+"px"} // IE8 gets the dimensions completely wrong
  170. if (zoom.offsetHeight > Mh) {zoom.style.height = Mh+"px"; zoom.style.width = (bbox.zW+this.scrollSize)+"px"} // IE doesn't do max-height?
  171. if (zoom.offsetWidth > Mw) {zoom.style.width = Mw+"px"; zoom.style.height = (bbox.zH+this.scrollSize)+"px"}
  172. }
  173. if (this.operaPositionBug) {zoom.style.width = Math.min(Mw,bbox.zW)+"px"} // Opera gets width as 0?
  174. if (zoom.offsetWidth > eW && zoom.offsetWidth-eW < Mw && zoom.offsetHeight-eW < Mh)
  175. {zoom.style.overflow = "visible"} // don't show scroll bars if we don't need to
  176. this.Position(zoom,bbox);
  177. if (this.msieTrapEventBug) {
  178. trap.style.height = zoom.clientHeight+"px"; trap.style.width = zoom.clientWidth+"px";
  179. trap.style.left = (parseFloat(zoom.style.left)+zoom.clientLeft)+"px";
  180. trap.style.top = (parseFloat(zoom.style.top)+zoom.clientTop)+"px";
  181. }
  182. zoom.style.visibility = "";
  183. //
  184. // Add event handlers
  185. //
  186. if (this.settings.zoom === "Hover") {overlay.onmouseover = this.Remove}
  187. if (window.addEventListener) {addEventListener("resize",this.Resize,false)}
  188. else if (window.attachEvent) {attachEvent("onresize",this.Resize)}
  189. else {this.onresize = window.onresize; window.onresize = this.Resize}
  190. //
  191. // Let others know about the zoomed math
  192. //
  193. HUB.signal.Post(["math zoomed",jax]);
  194. //
  195. // Canel further actions
  196. //
  197. return FALSE(event);
  198. },
  199. //
  200. // Set the position of the zoom box and overlay
  201. //
  202. Position: function (zoom,bbox) {
  203. zoom.style.display = "none"; // avoids getting excessive width in Resize()
  204. var XY = this.Resize(), x = XY.x, y = XY.y, W = bbox.mW;
  205. zoom.style.display = "";
  206. var dx = -W-Math.floor((zoom.offsetWidth-W)/2), dy = bbox.Y;
  207. zoom.style.left = Math.max(dx,10-x)+"px"; zoom.style.top = Math.max(dy,10-y)+"px";
  208. if (!ZOOM.msiePositionBug) {ZOOM.SetWH()} // refigure overlay width/height
  209. },
  210. //
  211. // Handle resizing of overlay while zoom is displayed
  212. //
  213. Resize: function (event) {
  214. if (ZOOM.onresize) {ZOOM.onresize(event)}
  215. var div = document.getElementById("MathJax_ZoomFrame"),
  216. overlay = document.getElementById("MathJax_ZoomOverlay");
  217. var xy = ZOOM.getXY(div), obj = ZOOM.findContainer(div);
  218. if (ZOOM.getOverflow(obj) !== "visible") {
  219. overlay.scroll_parent = obj; // Save this for future reference.
  220. var XY = ZOOM.getXY(obj); // Remove container position
  221. xy.x -= XY.x; xy.y -= XY.y;
  222. XY = ZOOM.getBorder(obj); // Remove container border
  223. xy.x -= XY.x; xy.y -= XY.y;
  224. }
  225. overlay.style.left = (-xy.x)+"px"; overlay.style.top = (-xy.y)+"px";
  226. if (ZOOM.msiePositionBug) {setTimeout(ZOOM.SetWH,0)} else {ZOOM.SetWH()}
  227. return xy;
  228. },
  229. SetWH: function () {
  230. var overlay = document.getElementById("MathJax_ZoomOverlay");
  231. if (!overlay) return;
  232. overlay.style.display = "none"; // so scrollWidth/Height will be right below
  233. var doc = overlay.scroll_parent || document.documentElement || document.body;
  234. overlay.style.width = doc.scrollWidth + "px";
  235. overlay.style.height = Math.max(doc.clientHeight,doc.scrollHeight) + "px";
  236. overlay.style.display = "";
  237. },
  238. findContainer: function (obj) {
  239. obj = obj.parentNode;
  240. while (obj.parentNode && obj !== document.body && ZOOM.getOverflow(obj) === "visible")
  241. {obj = obj.parentNode}
  242. return obj;
  243. },
  244. //
  245. // Look up CSS properties (use getComputeStyle if available, or currentStyle if not)
  246. //
  247. getOverflow: (window.getComputedStyle ?
  248. function (obj) {return getComputedStyle(obj).overflow} :
  249. function (obj) {return (obj.currentStyle||{overflow:"visible"}).overflow}),
  250. getBorder: function (obj) {
  251. var size = {thin: 1, medium: 2, thick: 3};
  252. var style = (window.getComputedStyle ? getComputedStyle(obj) :
  253. (obj.currentStyle || {borderLeftWidth:0,borderTopWidth:0}));
  254. var x = style.borderLeftWidth, y = style.borderTopWidth;
  255. if (size[x]) {x = size[x]} else {x = parseInt(x)}
  256. if (size[y]) {y = size[y]} else {y = parseInt(y)}
  257. return {x:x, y:y};
  258. },
  259. //
  260. // Get the position of an element on the page
  261. //
  262. getXY: function (div) {
  263. var x = 0, y = 0, obj;
  264. obj = div; while (obj.offsetParent) {x += obj.offsetLeft; obj = obj.offsetParent}
  265. if (ZOOM.operaPositionBug) {div.style.border = "1px solid"} // to get vertical position right
  266. obj = div; while (obj.offsetParent) {y += obj.offsetTop; obj = obj.offsetParent}
  267. if (ZOOM.operaPositionBug) {div.style.border = ""}
  268. return {x:x, y:y};
  269. },
  270. //
  271. // Remove zoom display and event handlers
  272. //
  273. Remove: function (event) {
  274. var div = document.getElementById("MathJax_ZoomFrame");
  275. if (div) {
  276. var JAX = MathJax.OutputJax[div.previousSibling.jaxID];
  277. var jax = JAX.getJaxFromMath(div.previousSibling);
  278. HUB.signal.Post(["math unzoomed",jax]);
  279. div.parentNode.removeChild(div);
  280. div = document.getElementById("MathJax_ZoomTracker");
  281. if (div) {div.parentNode.removeChild(div)}
  282. if (ZOOM.operaRefreshBug) {
  283. // force a redisplay of the page
  284. // (Opera doesn't refresh properly after the zoom is removed)
  285. var overlay = HTML.addElement(document.body,"div",{
  286. style:{position:"fixed", left:0, top:0, width:"100%", height:"100%",
  287. backgroundColor:"white", opacity:0},
  288. id: "MathJax_OperaDiv"
  289. });
  290. document.body.removeChild(overlay);
  291. }
  292. if (window.removeEventListener) {removeEventListener("resize",ZOOM.Resize,false)}
  293. else if (window.detachEvent) {detachEvent("onresize",ZOOM.Resize)}
  294. else {window.onresize = ZOOM.onresize; delete ZOOM.onresize}
  295. }
  296. return FALSE(event);
  297. }
  298. };
  299. /*************************************************************/
  300. HUB.Browser.Select({
  301. MSIE: function (browser) {
  302. var mode = (document.documentMode || 0);
  303. var isIE9 = (mode >= 9);
  304. ZOOM.msiePositionBug = !isIE9;
  305. ZOOM.msieSizeBug = browser.versionAtLeast("7.0") &&
  306. (!document.documentMode || mode === 7 || mode === 8);
  307. ZOOM.msieZIndexBug = (mode <= 7);
  308. ZOOM.msieInlineBlockAlignBug = (mode <= 7);
  309. ZOOM.msieTrapEventBug = !window.addEventListener;
  310. if (document.compatMode === "BackCompat") {ZOOM.scrollSize = 52} // don't know why this is so far off
  311. if (isIE9) {delete CONFIG.styles["#MathJax_Zoom"].filter}
  312. },
  313. Opera: function (browser) {
  314. ZOOM.operaPositionBug = true;
  315. ZOOM.operaRefreshBug = true;
  316. }
  317. });
  318. ZOOM.topImg = (ZOOM.msieInlineBlockAlignBug ?
  319. HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) :
  320. HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}})
  321. );
  322. if (ZOOM.operaPositionBug || ZOOM.msieTopBug) {ZOOM.topImg.style.border="1px solid"}
  323. /*************************************************************/
  324. MathJax.Callback.Queue(
  325. ["StartupHook",MathJax.Hub.Register,"Begin Styles",{}],
  326. ["Styles",AJAX,CONFIG.styles],
  327. ["Post",HUB.Startup.signal,"MathZoom Ready"],
  328. ["loadComplete",AJAX,"[MathJax]/extensions/MathZoom.js"]
  329. );
  330. })(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.OutputJax["HTML-CSS"],MathJax.OutputJax.NativeMML);