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