1//! openseadragon 2.4.2 2//! Built on 2020-03-05 3//! Git commit: v2.4.2-0-c450749 4//! http://openseadragon.github.io 5//! License: http://openseadragon.github.io/license/ 6 7/* 8 * OpenSeadragon 9 * 10 * Copyright (C) 2009 CodePlex Foundation 11 * Copyright (C) 2010-2013 OpenSeadragon contributors 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are 15 * met: 16 * 17 * - Redistributions of source code must retain the above copyright notice, 18 * this list of conditions and the following disclaimer. 19 * 20 * - Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in the 22 * documentation and/or other materials provided with the distribution. 23 * 24 * - Neither the name of CodePlex Foundation nor the names of its 25 * contributors may be used to endorse or promote products derived from 26 * this software without specific prior written permission. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 31 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 32 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 33 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 34 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 35 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 36 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 37 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 38 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 */ 40 41/* 42 * Portions of this source file taken from jQuery: 43 * 44 * Copyright 2011 John Resig 45 * 46 * Permission is hereby granted, free of charge, to any person obtaining 47 * a copy of this software and associated documentation files (the 48 * "Software"), to deal in the Software without restriction, including 49 * without limitation the rights to use, copy, modify, merge, publish, 50 * distribute, sublicense, and/or sell copies of the Software, and to 51 * permit persons to whom the Software is furnished to do so, subject to 52 * the following conditions: 53 * 54 * The above copyright notice and this permission notice shall be 55 * included in all copies or substantial portions of the Software. 56 * 57 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 58 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 59 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 60 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 61 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 62 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 63 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64 */ 65 66/* 67 * Portions of this source file taken from mattsnider.com: 68 * 69 * Copyright (c) 2006-2013 Matt Snider 70 * 71 * Permission is hereby granted, free of charge, to any person obtaining a 72 * copy of this software and associated documentation files (the "Software"), 73 * to deal in the Software without restriction, including without limitation 74 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 75 * and/or sell copies of the Software, and to permit persons to whom the 76 * Software is furnished to do so, subject to the following conditions: 77 * 78 * The above copyright notice and this permission notice shall be included 79 * in all copies or substantial portions of the Software. 80 * 81 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 82 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 83 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 84 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 85 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 86 * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 87 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. 88 */ 89 90 91/** 92 * @namespace OpenSeadragon 93 * @version openseadragon 2.4.2 94 * @classdesc The root namespace for OpenSeadragon. All utility methods 95 * and classes are defined on or below this namespace. 96 * 97 */ 98 99 100// Typedefs 101
102 /** 103 * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer. 104 * 105 * @typedef {Object} Options 106 * @memberof OpenSeadragon 107 * 108 * @property {String} id 109 * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided. 110 * If both the element and id properties are specified, the viewer is appended to the element provided in the element property. 111 * 112 * @property {Element} element 113 * The element to append the viewer's container element to. If not provided, the 'id' property must be provided. 114 * If both the element and id properties are specified, the viewer is appended to the element provided in the element property. 115 * 116 * @property {Array|String|Function|Object} [tileSources=null] 117 * Tile source(s) to open initially. This is a complex parameter; see 118 * {@link OpenSeadragon.Viewer#open} for details. 119 * 120 * @property {Number} [tabIndex=0] 121 * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0 122 * source order is used. A negative value omits the viewer from the tabbing order. 123 * 124 * @property {Array} overlays Array of objects defining permanent overlays of 125 * the viewer. The overlays added via this option and later removed with 126 * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new 127 * image is opened. 128 * To add overlays which can be definitively removed, one must use 129 * {@link OpenSeadragon.Viewer#addOverlay} 130 * If displaying a sequence of images, the overlays can be associated 131 * with a specific page by passing the overlays array to the page's 132 * tile source configuration. 133 * Expected properties: 134 * * x, y, (or px, py for pixel coordinates) to define the location. 135 * * width, height in point if using x,y or in pixels if using px,py. If width 136 * and height are specified, the overlay size is adjusted when zooming, 137 * otherwise the size stays the size of the content (or the size defined by CSS). 138 * * className to associate a class to the overlay 139 * * id to set the overlay element. If an element with this id already exists, 140 * it is reused, otherwise it is created. If not specified, a new element is 141 * created. 142 * * placement a string to define the relative position to the viewport. 143 * Only used if no width and height are specified. Default: 'TOP_LEFT'. 144 * See {@link OpenSeadragon.Placement} for possible values. 145 * 146 * @property {String} [xmlPath=null] 147 * <strong>DEPRECATED</strong>. A relative path to load a DZI file from the server. 148 * Prefer the newer Options.tileSources. 149 * 150 * @property {String} [prefixUrl='/images/'] 151 * Prepends the prefixUrl to navImages paths, which is very useful 152 * since the default paths are rarely useful for production 153 * environments. 154 * 155 * @property {OpenSeadragon.NavImages} [navImages] 156 * An object with a property for each button or other built-in navigation 157 * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'. 158 * Each of those in turn provides an image path for each state of the button 159 * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the 160 * image paths, by default assume there is a folder on the servers root path 161 * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust 162 * these paths, prefer setting the option.prefixUrl rather than overriding 163 * every image path directly through this setting. 164 * 165 * @property {Boolean} [debugMode=false] 166 * TODO: provide an in-screen panel providing event detail feedback. 167 * 168 * @property {String} [debugGridColor=['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']] 169 * The colors of grids in debug mode. Each tiled image's grid uses a consecutive color. 170 * If there are more tiled images than provided colors, the color vector is recycled. 171 * 172 * @property {Number}
172 [blendTime=0] 173 * Specifies the duration of animation as higher or lower level tiles are 174 * replacing the existing tile. 175 * 176 * @property {Boolean} [alwaysBlend=false] 177 * Forces the tile to always blend. By default the tiles skip blending 178 * when the blendTime is surpassed and the current animation frame would 179 * not complete the blend. 180 * 181 * @property {Boolean} [autoHideControls=true] 182 * If the user stops interacting with the viewport, fade the navigation 183 * controls. Useful for presentation since the controls are by default 184 * floated on top of the image the user is viewing. 185 * 186 * @property {Boolean} [immediateRender=false] 187 * Render the best closest level first, ignoring the lowering levels which 188 * provide the effect of very blurry to sharp. It is recommended to change 189 * setting to true for mobile devices. 190 * 191 * @property {Number} [defaultZoomLevel=0] 192 * Zoom level to use when image is first opened or the home button is clicked. 193 * If 0, adjusts to fit viewer. 194 * 195 * @property {Number} [opacity=1] 196 * Default proportional opacity of the tiled images (1=opaque, 0=hidden) 197 * Hidden images do not draw and only load when preloading is allowed. 198 * 199 * @property {Boolean} [preload=false] 200 * Default switch for loading hidden images (true loads, false blocks) 201 * 202 * @property {String} [compositeOperation=null] 203 * Valid values are 'source-over', 'source-atop', 'source-in', 'source-out', 204 * 'destination-over', 'destination-atop', 'destination-in', 205 * 'destination-out', 'lighter', 'copy' or 'xor' 206 * 207 * @property {Boolean} [imageSmoothingEnabled=true] 208 * Image smoothing for canvas rendering (only if canvas is used). Note: Ignored 209 * by some (especially older) browsers which do not support this canvas property. 210 * This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}. 211 * 212 * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null] 213 * Draws a colored rectangle behind the tile if it is not loaded yet. 214 * You can pass a CSS color value like "#FF8800". 215 * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern. 216 * 217 * @property {Number} [degrees=0] 218 * Initial rotation. 219 * 220 * @property {Boolean} [flipped=false] 221 * Initial flip state. 222 * 223 * @property {Number} [minZoomLevel=null] 224 * 225 * @property {Number} [maxZoomLevel=null] 226 * 227 * @property {Boolean} [homeFillsViewer=false] 228 * Make the 'home' button fill the viewer and clip the image, instead 229 * of fitting the image to the viewer and letterboxing. 230 * 231 * @property {Boolean} [panHorizontal=true] 232 * Allow horizontal pan. 233 * 234 * @property {Boolean} [panVertical=true] 235 * Allow vertical pan. 236 * 237 * @property {Boolean} [constrainDuringPan=false] 238 * 239 * @property {Boolean} [wrapHorizontal=false] 240 * Set to true to force the image to wrap horizontally within the viewport. 241 * Useful for maps or images representing the surface of a sphere or cylinder. 242 * 243 * @property {Boolean} [wrapVertical=false] 244 * Set to true to force the image to wrap vertically within the viewport. 245 * Useful for maps or images representing the surface of a sphere or cylinder. 246 * 247 * @property {Number} [minZoomImageRatio=0.9] 248 * The minimum percentage ( expressed as a number between 0 and 1 ) of 249 * the viewport height or width at which the zoom out will be constrained. 250 * Setting it to 0, for example will allow you to zoom out infinity. 251 * 252 * @property {Number} [maxZoomPixelRatio=1.1] 253 * The maximum ratio to allow a zoom-in to affect the highest level pixel 254 * ratio. This can be set to Infinity to allow 'infinite' zooming into the 255 * image though it is less effective visually if the HTML5 Canvas is not 256 * available on the viewing device. 257 * 258 * @property {Number} [smoothTileEdgesMinZoom=1.1] 259 * A zoom percentage ( where 1 is 100% ) of the highest resolution level. 260 * When zoomed in beyond this value alternative compositing will be used to 261 * smooth out the edges between tiles. This will have a performance impact. 262 * Can be set to Infinity to turn it off. 263 * Note: This setting is ignored on iOS devices due to a known bug (See {@link https://github.com/openseadragon/openseadragon/issues/952}) 264 * 265 * @property {Boolean} [iOSDevice=?] 266 * True if running on an iOS device, false otherwise. 267 * Used to disable certain features that behave differently on iOS devices. 268 * 269 * @property {Boolean} [autoResize=true] 270 * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior. 271 * 272 * @property {Boolean} [preserveImageSizeOnResize=false] 273 * Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default). 274 * 275 * @property {Number}
275 [minScrollDeltaTime=50] 276 * Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll 277 * events between different devices, causing the faster devices to slow down enough to make the zoom control 278 * more manageable. 279 * 280 * @property {Number} [rotationIncrement=90] 281 * The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated. 282 * 283 * @property {Number} [pixelsPerWheelLine=40] 284 * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. 285 * 286 * @property {Number} [pixelsPerArrowPress=40] 287 * The number of pixels viewport moves when an arrow key is pressed. 288 * 289 * @property {Number} [visibilityRatio=0.5] 290 * The percentage ( as a number from 0 to 1 ) of the source image which 291 * must be kept within the viewport. If the image is dragged beyond that 292 * limit, it will 'bounce' back until the minimum visibility ratio is 293 * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to 294 * true will provide the effect of an infinitely scrolling viewport. 295 * 296 * @property {Object} [viewportMargins={}] 297 * Pushes the "home" region in from the sides by the specified amounts. 298 * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom. 299 * 300 * @property {Number} [imageLoaderLimit=0] 301 * The maximum number of image requests to make concurrently. By default 302 * it is set to 0 allowing the browser to make the maximum number of 303 * image requests in parallel as allowed by the browsers policy. 304 * 305 * @property {Number} [clickTimeThreshold=300] 306 * The number of milliseconds within which a pointer down-up event combination 307 * will be treated as a click gesture. 308 * 309 * @property {Number} [clickDistThreshold=5] 310 * The maximum distance allowed between a pointer down event and a pointer up event 311 * to be treated as a click gesture. 312 * 313 * @property {Number} [dblClickTimeThreshold=300] 314 * The number of milliseconds within which two pointer down-up event combinations 315 * will be treated as a double-click gesture. 316 * 317 * @property {Number} [dblClickDistThreshold=20] 318 * The maximum distance allowed between two pointer click events 319 * to be treated as a double-click gesture. 320 * 321 * @property {Number} [springStiffness=6.5] 322 * 323 * @property {Number} [animationTime=1.2] 324 * Specifies the animation duration per each {@link OpenSeadragon.Spring} 325 * which occur when the image is dragged or zoomed. 326 * 327 * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse] 328 * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings}) 329 * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture 330 * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture 331 * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true 332 * then clickToZoom should be set to false to prevent multiple zooms. 333 * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture 334 * @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise, 335 * the zoom is centered at the canvas center. 336 * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture 337 * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 338 * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 339 * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers. 340 * 341 * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch] 342 * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings}) 343 * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture 344 * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture 345 * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true 346 * then clickToZoom should be set to false to prevent multiple zooms. 347 * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture 348 * @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise, 349 * the zoom is centered at the canvas center. 350 * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture 351 * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 352 * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 353 * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers. 354 * 355 * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen] 356 * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings}) 357 * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture 358 * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture 359 * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true 360 * then clickToZoom should be set to false to prevent multiple zooms. 361 * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture 362 * @property {Boolean} [gestureSettingsPen.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise, 363 * the zoom is centered at the canvas center. 364 * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture 365 * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 366 * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 367 * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers. 368 * 369 * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown] 370 * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings}) 371 * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture 372 * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture 373 * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true 374 * then clickToZoom should be set to false to prevent multiple zooms. 375 * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture 376 * @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise, 377 * the zoom is centered at the canvas center. 378 * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture 379 * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 380 * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 381 * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers. 382 * 383 * @property {Number} [zoomPerClick=2.0]
384 * The "zoom distance" per mouse click or touch tap. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).</em> 385 * 386 * @property {Number} [zoomPerScroll=1.2] 387 * The "zoom distance" per mouse scroll or touch pinch. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).</em> 388 * 389 * @property {Number} [zoomPerSecond=1.0] 390 * The number of seconds to animate a single zoom event over. 391 * 392 * @property {Boolean} [showNavigator=false] 393 * Set to true to make the navigator minimap appear. 394 * 395 * @property {String} [navigatorId=navigator-GENERATED DATE] 396 * The ID of a div to hold the navigator minimap. 397 * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored. 398 * If an ID is not specified, a div element will be generated and placed on top of the main image. 399 * 400 * @property {String} [navigatorPosition='TOP_RIGHT'] 401 * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.<br> 402 * If 'ABSOLUTE' is specified, then navigator[Top|Left|Height|Width] determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br> 403 * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigator[Height|Width] values determine the size of the navigator minimap. 404 * 405 * @property {Number} [navigatorSizeRatio=0.2] 406 * Ratio of navigator size to viewer size. Ignored if navigator[Height|Width] are specified. 407 * 408 * @property {Boolean} [navigatorMaintainSizeRatio=false] 409 * If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes. 410 * 411 * @property {Number|String} [navigatorTop=null] 412 * Specifies the location of the navigator minimap (see navigatorPosition). 413 * 414 * @property {Number|String} [navigatorLeft=null] 415 * Specifies the location of the navigator minimap (see navigatorPosition). 416 * 417 * @property {Number|String} [navigatorHeight=null] 418 * Specifies the size of the navigator minimap (see navigatorPosition). 419 * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored. 420 * 421 * @property {Number|String} [navigatorWidth=null] 422 * Specifies the size of the navigator minimap (see navigatorPosition). 423 * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored. 424 * 425 * @property {Boolean} [navigatorAutoResize=true] 426 * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior. 427 * Setting to false can also improve performance when the navigator is configured to a fixed size. 428 * 429 * @property {Boolean} [navigatorAutoFade=true] 430 * If the user stops interacting with the viewport, fade the navigator minimap. 431 * Setting to false will make the navigator minimap always visible. 432 * 433 * @property {Boolean} [navigatorRotate=true] 434 * If true, the navigator will be rotated together with the viewer. 435 * 436 * @property {String} [navigatorBackground='#000'] 437 * Specifies the background color of the navigator minimap 438 * 439 * @property {Number} [navigatorOpacity=0.8] 440 * Specifies the opacity of the navigator minimap. 441 * 442 * @property {String} [navigatorBorderColor='#555'] 443 * Specifies the border color of the navigator minimap 444 * 445 * @property {String} [navigatorDisplayRegionColor='#900'] 446 * Specifies the border color of the display region rectangle of the navigator minimap 447 * 448 * @property {Number} [controlsFadeDelay=2000] 449 * The number of milliseconds to wait once the user has stopped interacting 450 * with the interface before beginning to fade the controls. Assumes 451 * showNavigationControl and autoHideControls are both true. 452 * 453 * @property {Number} [controlsFadeLength=1500] 454 * The number of milliseconds to animate the controls fading out. 455 * 456 * @property {Number} [maxImageCacheCount=200] 457 * The max number of images we should keep in memory (per drawer). 458 * 459 * @property {Number} [timeout=30000] 460 * The max number of milliseconds that an image job may take to complete. 461 * 462 * @property {Boolean} [useCanvas=true] 463 * Set to false to not use an HTML canvas element for image rendering even if canvas is supported. 464 * 465 * @property {Number} [minPixelRatio=0.5] 466 * The higher the minPixelRatio, the lower the quality of the image that 467 * is considered sufficient to stop rendering a given zoom level. For 468 * example, if you are targeting mobile devices with less bandwidth you may 469 * try setting this to 1.5 or higher. 470 * 471 * @property {Boolean} [mouseNavEnabled=true] 472 * Is the user able to interact with the image via mouse or touch. Default 473 * interactions include draging the image in a plane, and zooming in toward 474 * and away from the image. 475 * 476 * @property {Boolean}
476 [showNavigationControl=true] 477 * Set to false to prevent the appearance of the default navigation controls.<br> 478 * Note that if set to false, the customs buttons set by the options 479 * zoomInButton, zoomOutButton etc, are rendered inactive. 480 * 481 * @property {OpenSeadragon.ControlAnchor} [navigationControlAnchor=TOP_LEFT] 482 * Placement of the default navigation controls. 483 * To set the placement of the sequence controls, see the 484 * sequenceControlAnchor option. 485 * 486 * @property {Boolean} [showZoomControl=true] 487 * If true then + and - buttons to zoom in and out are displayed.<br> 488 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 489 * this setting when set to false. 490 * 491 * @property {Boolean} [showHomeControl=true] 492 * If true then the 'Go home' button is displayed to go back to the original 493 * zoom and pan.<br> 494 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 495 * this setting when set to false. 496 * 497 * @property {Boolean} [showFullPageControl=true] 498 * If true then the 'Toggle full page' button is displayed to switch 499 * between full page and normal mode.<br> 500 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 501 * this setting when set to false. 502 * 503 * @property {Boolean} [showRotationControl=false] 504 * If true then the rotate left/right controls will be displayed as part of the 505 * standard controls. This is also subject to the browser support for rotate 506 * (e.g. viewer.drawer.canRotate()).<br> 507 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 508 * this setting when set to false. 509 * 510 * @property {Boolean} [showFlipControl=false] 511 * If true then the flip controls will be displayed as part of the 512 * standard controls. 513 * 514 * @property {Boolean} [showSequenceControl=true] 515 * If sequenceMode is true, then provide buttons for navigating forward and 516 * backward through the images. 517 * 518 * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT] 519 * Placement of the default sequence controls. 520 * 521 * @property {Boolean} [navPrevNextWrap=false] 522 * If true then the 'previous' button will wrap to the last image when 523 * viewing the first image and the 'next' button will wrap to the first 524 * image when viewing the last image. 525 * 526 * @property {String} zoomInButton 527 * Set the id of the custom 'Zoom in' button to use. 528 * This is useful to have a custom button anywhere in the web page.<br> 529 * To only change the button images, consider using 530 * {@link OpenSeadragon.Options.navImages} 531 * 532 * @property {String} zoomOutButton 533 * Set the id of the custom 'Zoom out' button to use. 534 * This is useful to have a custom button anywhere in the web page.<br> 535 * To only change the button images, consider using 536 * {@link OpenSeadragon.Options.navImages} 537 * 538 * @property {String} homeButton 539 * Set the id of the custom 'Go home' button to use. 540 * This is useful to have a custom button anywhere in the web page.<br> 541 * To only change the button images, consider using 542 * {@link OpenSeadragon.Options.navImages} 543 * 544 * @property {String} fullPageButton 545 * Set the id of the custom 'Toggle full page' button to use. 546 * This is useful to have a custom button anywhere in the web page.<br> 547 * To only change the button images, consider using 548 * {@link OpenSeadragon.Options.navImages} 549 * 550 * @property {String} rotateLeftButton 551 * Set the id of the custom 'Rotate left' button to use. 552 * This is useful to have a custom button anywhere in the web page.<br> 553 * To only change the button images, consider using 554 * {@link OpenSeadragon.Options.navImages} 555 * 556 * @property {String} rotateRightButton 557 * Set the id of the custom 'Rotate right' button to use. 558 * This is useful to have a custom button anywhere in the web page.<br> 559 * To only change the button images, consider using 560 * {@link OpenSeadragon.Options.navImages} 561 * 562 * @property {String} previousButton 563 * Set the id of the custom 'Previous page' button to use. 564 * This is useful to have a custom button anywhere in the web page.<br> 565 * To only change the button images, consider using 566 * {@link OpenSeadragon.Options.navImages} 567 * 568 * @property {String} nextButton 569 * Set the id
vendor: 3,425 bytes, lines 569-636
569 of the custom 'Next page' button to use. 570 * This is useful to have a custom button anywhere in the web page.<br> 571 * To only change the button images, consider using 572 * {@link OpenSeadragon.Options.navImages} 573 * 574 * @property {Boolean} [sequenceMode=false] 575 * Set to true to have the viewer treat your tilesources as a sequence of images to 576 * be opened one at a time rather than all at once. 577 * 578 * @property {Number} [initialPage=0] 579 * If sequenceMode is true, display this page initially. 580 * 581 * @property {Boolean} [preserveViewport=false] 582 * If sequenceMode is true, then normally navigating through each image resets the 583 * viewport to 'home' position. If preserveViewport is set to true, then the viewport 584 * position is preserved when navigating between images in the sequence. 585 * 586 * @property {Boolean} [preserveOverlays=false] 587 * If sequenceMode is true, then normally navigating through each image 588 * resets the overlays. 589 * If preserveOverlays is set to true, then the overlays added with {@link OpenSeadragon.Viewer#addOverlay} 590 * are preserved when navigating between images in the sequence. 591 * Note: setting preserveOverlays overrides any overlays specified in the global 592 * "overlays" option for the Viewer. It's also not compatible with specifying 593 * per-tileSource overlays via the options, as those overlays will persist 594 * even after the tileSource is closed. 595 * 596 * @property {Boolean} [showReferenceStrip=false] 597 * If sequenceMode is true, then display a scrolling strip of image thumbnails for 598 * navigating through the images. 599 * 600 * @property {String} [referenceStripScroll='horizontal'] 601 * 602 * @property {Element} [referenceStripElement=null] 603 * 604 * @property {Number} [referenceStripHeight=null] 605 * 606 * @property {Number} [referenceStripWidth=null] 607 * 608 * @property {String} [referenceStripPosition='BOTTOM_LEFT'] 609 * 610 * @property {Number} [referenceStripSizeRatio=0.2] 611 * 612 * @property {Boolean} [collectionMode=false] 613 * Set to true to have the viewer arrange your TiledImages in a grid or line. 614 * 615 * @property {Number} [collectionRows=3] 616 * If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line. 617 * If collectionLayout is 'vertical', specifies how many columns instead. 618 * 619 * @property {Number} [collectionColumns=0] 620 * If collectionMode is true, specifies how many columns the grid should have. Use 1 to make a line. 621 * If collectionLayout is 'vertical', specifies how many rows instead. Ignored if collectionRows is not set to a falsy value. 622 * 623 * @property {String} [collectionLayout='horizontal'] 624 * If collectionMode is true, specifies whether to arrange vertically or horizontally. 625 * 626 * @property {Number} [collectionTileSize=800] 627 * If collectionMode is true, specifies the size, in viewport coordinates, for each TiledImage to fit into. 628 * The TiledImage will be centered within a square of the specified size. 629 * 630 * @property {Number} [collectionTileMargin=80] 631 * If collectionMode is true, specifies the margin, in viewport coordinates, between each TiledImage. 632 * 633 * @property {String|Boolean} [crossOriginPolicy=false] 634 * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will 635 * not use CORS, and the canvas will be tainted. 636 *
636 637 * @property {Boolean} [ajaxWithCredentials=false] 638 * Whether to set the withCredentials XHR flag for AJAX requests. 639 * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level. 640 * 641 * @property {Boolean} [loadTilesWithAjax=false] 642 * Whether to load tile data using AJAX requests. 643 * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level. 644 * 645 * @property {Object} [ajaxHeaders={}] 646 * A set of headers to include when making AJAX requests for tile sources or tiles. 647 * 648 */ 649 650 /** 651 * Settings for gestures generated by a pointer device. 652 * 653 * @typedef {Object} GestureSettings 654 * @memberof OpenSeadragon 655 * 656 * @property {Boolean} scrollToZoom 657 * Set to false to disable zooming on scroll gestures. 658 * 659 * @property {Boolean} clickToZoom 660 * Set to false to disable zooming on click gestures. 661 * 662 * @property {Boolean} dblClickToZoom 663 * Set to false to disable zooming on double-click gestures. Note: If set to true 664 * then clickToZoom should be set to false to prevent multiple zooms. 665 * 666 * @property {Boolean} pinchToZoom 667 * Set to false to disable zooming on pinch gestures. 668 * 669 * @property {Boolean} flickEnabled 670 * Set to false to disable the kinetic panning effect (flick) at the end of a drag gesture. 671 * 672 * @property {Number} flickMinSpeed 673 * If flickEnabled is true, the minimum speed (in pixels-per-second) required to cause the kinetic panning effect (flick) at the end of a drag gesture. 674 * 675 * @property {Number} flickMomentum 676 * If flickEnabled is true, a constant multiplied by the velocity to determine the distance of the kinetic panning effect (flick) at the end of a drag gesture. 677 * A larger value will make the flick feel "lighter", while a smaller value will make the flick feel "heavier". 678 * Note: springStiffness and animationTime also affect the "spring" used to stop the flick animation. 679 * 680 */ 681 682/** 683 * The names for the image resources used for the image navigation buttons. 684 * 685 * @typedef {Object} NavImages 686 * @memberof OpenSeadragon 687 * 688 * @property {Object} zoomIn - Images for the zoom-in button. 689 * @property {String} zoomIn.REST 690 * @property {String} zoomIn.GROUP 691 * @property {String} zoomIn.HOVER 692 * @property {String} zoomIn.DOWN 693 * 694 * @property {Object} zoomOut - Images for the zoom-out button. 695 * @property {String} zoomOut.REST 696 * @property {String} zoomOut.GROUP 697 * @property {String} zoomOut.HOVER 698 * @property {String} zoomOut.DOWN 699 * 700 * @property {Object} home - Images for the home button. 701 * @property {String} home.REST 702 * @property {String} home.GROUP 703 * @property {String} home.HOVER 704 * @property {String} home.DOWN 705 * 706 * @property {Object} fullpage - Images for the full-page button. 707 * @property {String} fullpage.REST 708 * @property {String} fullpage.GROUP 709 * @property {String} fullpage.HOVER 710 * @property {String} fullpage.DOWN 711 * 712 * @property {Object} rotateleft - Images for the rotate left button. 713 * @property {String} rotateleft.REST 714 * @property {String} rotateleft.GROUP 715 * @property {String} rotateleft.HOVER 716 * @property {String} rotateleft.DOWN 717 * 718 * @property {Object} rotateright - Images for the rotate right button. 719 * @property {String} rotateright.REST 720 * @property {String} rotateright.GROUP 721 * @property {String} rotateright.HOVER 722 * @property {String} rotateright.DOWN 723 * 724 * @property {Object} flip - Images for the flip button. 725 * @property {String} flip.REST 726 * @property {String} flip.GROUP 727 * @property {String} flip.HOVER 728 * @property {String} flip.DOWN 729 * 730 * @property {Object} previous - Images for the previous button. 731 * @property {String} previous.REST 732 * @property {String} previous.GROUP 733 * @property {String} previous.HOVER 734 * @property {String} previous.DOWN 735 * 736 * @property {Object} next - Images for the next button. 737 * @property {String} next.REST 738 * @property {String} next.GROUP 739 * @property {String} next.HOVER 740 * @property {String} next.DOWN 741 * 742 */ 743 744/* eslint-disable no-redeclare */ 745function OpenSeadragon( options ){ 746 return new OpenSeadragon.Viewer( options ); 747} 748 749(function( $ ){ 750 751 752 /** 753 * The OpenSeadragon version. 754 * 755 * @member {Object} OpenSeadragon.version 756 * @property {String} versionStr - The version number as a string ('major.minor.revision'). 757 * @property {Number} major - The major version number. 758 * @property {Number} minor - The minor version number. 759 * @property {Number} revision - The revision number. 760 * @since 1.0.0 761 */ 762 $.version = { 763 versionStr: '2.4.2', 764 major: parseInt('2', 10), 765 minor: parseInt('4', 10), 766 revision: parseInt('2', 10) 767 }; 768 769 770 /** 771 * Taken from jquery 1.6.1 772 * [[Class]] -> type pairs 773 * @private 774 */ 775 var class2type = { 776 '[object Boolean]': 'boolean', 777 '[object Number]': 'number', 778 '[object String]': 'string', 779 '[object Function]': 'function', 780 '[object Array]': 'array', 781 '[object Date]': 'date', 782 '[object RegExp]': 'regexp', 783 '[object Object]': 'object' 784 }, 785 // Save a reference to some core methods 786 toString = Object.prototype.toString, 787 hasOwn = Object.prototype.hasOwnProperty; 788 789 /** 790 * Taken from jQuery 1.6.1 791 * @function isFunction 792 * @memberof OpenSeadragon 793 * @see {@link http://www.jquery.com/ jQuery} 794 */ 795 $.isFunction = function( obj ) { 796 return $.type(obj) === "function"; 797 }; 798 799 800 /** 801 * Taken from jQuery 1.6.1 802 * @function isArray 803 * @memberof OpenSeadragon 804 * @see {@link http://www.jquery.com/ jQuery} 805 */ 806 $.isArray = Array.isArray || function( obj ) { 807 return $.type(obj) === "array"; 808 }; 809 810 811 /** 812 * A crude way of determining if an object is a window. 813 * Taken from jQuery 1.6.1 814 * @function isWindow 815 * @memberof OpenSeadragon 816 * @see {@link http://www.jquery.com/ jQuery} 817 */ 818 $.isWindow = function( obj ) { 819 return obj && typeof obj === "object" && "setInterval" in obj; 820 }; 821 822 823 /** 824 * Taken from jQuery 1.6.1 825 * @function type 826 * @memberof OpenSeadragon 827 * @see {@link http://www.jquery.com/ jQuery} 828 */ 829 $.type = function( obj ) { 830 return ( obj === null ) || ( obj === undefined ) ? 831 String( obj ) : 832 class2type[ toString.call(obj) ] || "object"; 833 }; 834 835 836 /** 837 * Taken from jQuery 1.6.1 838 * @function isPlainObject 839 * @memberof OpenSeadragon 840 * @see {@link http://www.jquery.com/ jQuery} 841 */ 842 $.isPlainObject = function( obj ) { 843 // Must be an Object. 844 // Because of IE, we also have to check the presence of the constructor property. 845 // Make sure that DOM nodes and window objects don't pass through, as well 846 if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) { 847 return false;
848 } 849 850 // Not own constructor property must be Object 851 if ( obj.constructor && 852 !hasOwn.call(obj, "constructor") && 853 !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { 854 return false; 855 } 856 857 // Own properties are enumerated firstly, so to speed up, 858 // if last one is own, then all properties are own. 859 860 var lastKey; 861 for (var key in obj ) { 862 lastKey = key; 863 } 864 865 return lastKey === undefined || hasOwn.call( obj, lastKey ); 866 }; 867 868 869 /** 870 * Taken from jQuery 1.6.1 871 * @function isEmptyObject 872 * @memberof OpenSeadragon 873 * @see {@link http://www.jquery.com/ jQuery} 874 */ 875 $.isEmptyObject = function( obj ) { 876 for ( var name in obj ) { 877 return false; 878 } 879 return true; 880 }; 881 882 /** 883 * Shim around Object.freeze. Does nothing if Object.freeze is not supported. 884 * @param {Object} obj The object to freeze. 885 * @return {Object} obj The frozen object. 886 */ 887 $.freezeObject = function(obj) { 888 if (Object.freeze) { 889 $.freezeObject = Object.freeze; 890 } else { 891 $.freezeObject = function(obj) { 892 return obj; 893 }; 894 } 895 return $.freezeObject(obj); 896 }; 897 898 /** 899 * True if the browser supports the HTML5 canvas element 900 * @member {Boolean} supportsCanvas 901 * @memberof OpenSeadragon 902 */ 903 $.supportsCanvas = (function () { 904 var canvasElement = document.createElement( 'canvas' ); 905 return !!( $.isFunction( canvasElement.getContext ) && 906 canvasElement.getContext( '2d' ) ); 907 }()); 908 909 /** 910 * Test whether the submitted canvas is tainted or not. 911 * @argument {Canvas} canvas The canvas to test. 912 * @returns {Boolean} True if the canvas is tainted. 913 */ 914 $.isCanvasTainted = function(canvas) { 915 var isTainted = false; 916 try { 917 // We test if the canvas is tainted by retrieving data from it. 918 // An exception will be raised if the canvas is tainted. 919 canvas.getContext('2d').getImageData(0, 0, 1, 1); 920 } catch (e) { 921 isTainted = true; 922 } 923 return isTainted; 924 }; 925 926 /** 927 * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density, 928 * clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser. 929 * @member {Number} pixelDensityRatio 930 * @memberof OpenSeadragon 931 */ 932 $.pixelDensityRatio = (function () { 933 if ( $.supportsCanvas ) { 934 var context = document.createElement('canvas').getContext('2d'); 935 var devicePixelRatio = window.devicePixelRatio || 1; 936 var backingStoreRatio = context.webkitBackingStorePixelRatio || 937 context.mozBackingStorePixelRatio || 938 context.msBackingStorePixelRatio || 939 context.oBackingStorePixelRatio || 940 context.backingStorePixelRatio || 1; 941 return Math.max(devicePixelRatio, 1) / backingStoreRatio; 942 } else { 943 return 1; 944 } 945 }()); 946 947}( OpenSeadragon )); 948 949/** 950 * This closure defines all static methods available to the OpenSeadragon 951 * namespace. Many, if not most, are taked directly from jQuery for use 952 * to simplify and reduce common programming patterns. More static methods 953 * from jQuery may eventually make their way into this though we are 954 * attempting to avoid an explicit dependency on jQuery only because 955 * OpenSeadragon is a broadly useful code base and would be made less broad 956 * by requiring jQuery fully. 957 * 958 * Some static methods have also been refactored from the original OpenSeadragon 959 * project. 960 */ 961(function( $ ){ 962 963 /** 964 * Taken from jQuery 1.6.1 965 * @function extend 966 * @memberof OpenSeadragon 967 * @see {@link http://www.jquery.com/ jQuery} 968 */ 969 $.extend = function() { 970 var options, 971 name, 972 src, 973 copy, 974 copyIsArray, 975 clone, 976 target = arguments[ 0 ] || {}, 977 length = arguments.length, 978 deep = false, 979 i = 1; 980 981 // Handle a deep copy situation 982 if ( typeof target === "boolean" ) { 983 deep = target; 984 target = arguments[ 1 ] || {}; 985 // skip the boolean and the target 986 i = 2; 987 } 988 989 // Handle case when target is a string or something (possible in deep copy) 990 if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) { 991 target = {}; 992 } 993 994 // extend jQuery itself if only one argument is passed 995 if ( length === i ) { 996 target = this; 997 --i; 998 } 999 1000 for ( ; i < length; i++ ) { 1001 // Only deal with non-null/undefined values 1002 options = arguments[ i ]; 1003 if ( options !== null || options !== undefined ) { 1004 // Extend the base object 1005 for ( name in options ) { 1006 src = target[ name ]; 1007 copy = options[ name ]; 1008 1009 // Prevent never-ending loop 1010 if ( target === copy ) { 1011 continue; 1012 } 1013 1014 // Recurse if we're merging plain objects or arrays 1015 if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) { 1016 if ( copyIsArray ) { 1017 copyIsArray = false;
vendor: 4,928 bytes, lines 1018-1147
1018 clone = src && OpenSeadragon.isArray( src ) ? src : []; 1019 1020 } else { 1021 clone = src && OpenSeadragon.isPlainObject( src ) ? src : {}; 1022 } 1023 1024 // Never move original objects, clone them 1025 target[ name ] = OpenSeadragon.extend( deep, clone, copy ); 1026 1027 // Don't bring in undefined values 1028 } else if ( copy !== undefined ) { 1029 target[ name ] = copy; 1030 } 1031 } 1032 } 1033 } 1034 1035 // Return the modified object 1036 return target; 1037 }; 1038 1039 var isIOSDevice = function () { 1040 if (typeof navigator !== 'object') { 1041 return false; 1042 } 1043 var userAgent = navigator.userAgent; 1044 if (typeof userAgent !== 'string') { 1045 return false; 1046 } 1047 return userAgent.indexOf('iPhone') !== -1 || 1048 userAgent.indexOf('iPad') !== -1 || 1049 userAgent.indexOf('iPod') !== -1; 1050 }; 1051 1052 $.extend( $, /** @lends OpenSeadragon */{ 1053 /** 1054 * The default values for the optional settings documented at {@link OpenSeadragon.Options}. 1055 * @static 1056 * @type {Object} 1057 */ 1058 DEFAULT_SETTINGS: { 1059 //DATA SOURCE DETAILS 1060 xmlPath: null, 1061 tileSources: null, 1062 tileHost: null, 1063 initialPage: 0, 1064 crossOriginPolicy: false, 1065 ajaxWithCredentials: false, 1066 loadTilesWithAjax: false, 1067 ajaxHeaders: {}, 1068 1069 //PAN AND ZOOM SETTINGS AND CONSTRAINTS 1070 panHorizontal: true, 1071 panVertical: true, 1072 constrainDuringPan: false, 1073 wrapHorizontal: false, 1074 wrapVertical: false, 1075 visibilityRatio: 0.5, //-> how much of the viewer can be negative space 1076 minPixelRatio: 0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom 1077 defaultZoomLevel: 0, 1078 minZoomLevel: null, 1079 maxZoomLevel: null, 1080 homeFillsViewer: false, 1081 1082 //UI RESPONSIVENESS AND FEEL 1083 clickTimeThreshold: 300, 1084 clickDistThreshold: 5, 1085 dblClickTimeThreshold: 300, 1086 dblClickDistThreshold: 20, 1087 springStiffness: 6.5, 1088 animationTime: 1.2, 1089 gestureSettingsMouse: { 1090 scrollToZoom: true, 1091 clickToZoom: true, 1092 dblClickToZoom: false, 1093 pinchToZoom: false, 1094 zoomToRefPoint: true, 1095 flickEnabled: false, 1096 flickMinSpeed: 120, 1097 flickMomentum: 0.25, 1098 pinchRotate: false 1099 }, 1100 gestureSettingsTouch: { 1101 scrollToZoom: false, 1102 clickToZoom: false, 1103 dblClickToZoom: true, 1104 pinchToZoom: true, 1105 zoomToRefPoint: true, 1106 flickEnabled: true, 1107 flickMinSpeed: 120, 1108 flickMomentum: 0.25, 1109 pinchRotate: false 1110 }, 1111 gestureSettingsPen: { 1112 scrollToZoom: false, 1113 clickToZoom: true, 1114 dblClickToZoom: false, 1115 pinchToZoom: false, 1116 zoomToRefPoint: true, 1117 flickEnabled: false, 1118 flickMinSpeed: 120, 1119 flickMomentum: 0.25, 1120 pinchRotate: false 1121 }, 1122 gestureSettingsUnknown: { 1123 scrollToZoom: false, 1124 clickToZoom: false, 1125 dblClickToZoom: true, 1126 pinchToZoom: true, 1127 zoomToRefPoint: true, 1128 flickEnabled: true, 1129 flickMinSpeed: 120, 1130 flickMomentum: 0.25, 1131 pinchRotate: false 1132 }, 1133 zoomPerClick: 2, 1134 zoomPerScroll: 1.2, 1135 zoomPerSecond: 1.0, 1136 blendTime: 0, 1137 alwaysBlend: false, 1138 autoHideControls: true, 1139 immediateRender: false, 1140 minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity 1141 maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels 1142 smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it 1143 iOSDevice: isIOSDevice(), 1144 pixelsPerWheelLine: 40, 1145 pixelsPerArrowPress: 40, 1146 autoResize: true, 1147 preserveImageSizeOnResize: false, // requires autoResize=true
1148 minScrollDeltaTime: 50, 1149 rotationIncrement: 90, 1150 1151 //DEFAULT CONTROL SETTINGS 1152 showSequenceControl: true, //SEQUENCE 1153 sequenceControlAnchor: null, //SEQUENCE 1154 preserveViewport: false, //SEQUENCE 1155 preserveOverlays: false, //SEQUENCE 1156 navPrevNextWrap: false, //SEQUENCE 1157 showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION 1158 navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION 1159 showZoomControl: true, //ZOOM 1160 showHomeControl: true, //HOME 1161 showFullPageControl: true, //FULL 1162 showRotationControl: false, //ROTATION 1163 showFlipControl: false, //FLIP 1164 controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE 1165 controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE 1166 mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY 1167 1168 //VIEWPORT NAVIGATOR SETTINGS 1169 showNavigator: false, 1170 navigatorId: null, 1171 navigatorPosition: null, 1172 navigatorSizeRatio: 0.2, 1173 navigatorMaintainSizeRatio: false, 1174 navigatorTop: null, 1175 navigatorLeft: null, 1176 navigatorHeight: null, 1177 navigatorWidth: null, 1178 navigatorAutoResize: true, 1179 navigatorAutoFade: true, 1180 navigatorRotate: true, 1181 navigatorBackground: '#000', 1182 navigatorOpacity: 0.8, 1183 navigatorBorderColor: '#555', 1184 navigatorDisplayRegionColor: '#900', 1185 1186 // INITIAL ROTATION 1187 degrees: 0, 1188 1189 // INITIAL FLIP STATE 1190 flipped: false, 1191 1192 // APPEARANCE 1193 opacity: 1, 1194 preload: false, 1195 compositeOperation: null, 1196 imageSmoothingEnabled: true, 1197 placeholderFillStyle: null, 1198 1199 //REFERENCE STRIP SETTINGS 1200 showReferenceStrip: false, 1201 referenceStripScroll: 'horizontal', 1202 referenceStripElement: null, 1203 referenceStripHeight: null, 1204 referenceStripWidth: null, 1205 referenceStripPosition: 'BOTTOM_LEFT', 1206 referenceStripSizeRatio: 0.2, 1207 1208 //COLLECTION VISUALIZATION SETTINGS 1209 collectionRows: 3, //or columns depending on layout 1210 collectionColumns: 0, //columns in horizontal layout, rows in vertical layout 1211 collectionLayout: 'horizontal', //vertical 1212 collectionMode: false, 1213 collectionTileSize: 800, 1214 collectionTileMargin: 80, 1215 1216 //PERFORMANCE SETTINGS 1217 imageLoaderLimit: 0, 1218 maxImageCacheCount: 200, 1219 timeout: 30000, 1220 useCanvas: true, // Use canvas element for drawing if available 1221 1222 //INTERFACE RESOURCE SETTINGS 1223 prefixUrl: "/images/", 1224 navImages: { 1225 zoomIn: { 1226 REST: 'zoomin_rest.png', 1227 GROUP: 'zoomin_grouphover.png', 1228 HOVER: 'zoomin_hover.png', 1229 DOWN: 'zoomin_pressed.png' 1230 }, 1231 zoomOut: { 1232 REST: 'zoomout_rest.png', 1233 GROUP: 'zoomout_grouphover.png', 1234 HOVER: 'zoomout_hover.png', 1235 DOWN: 'zoomout_pressed.png' 1236 }, 1237 home: { 1238 REST: 'home_rest.png', 1239 GROUP: 'home_grouphover.png', 1240 HOVER: 'home_hover.png', 1241 DOWN: 'home_pressed.png' 1242 }, 1243 fullpage: { 1244 REST: 'fullpage_rest.png', 1245 GROUP: 'fullpage_grouphover.png', 1246 HOVER: 'fullpage_hover.png', 1247 DOWN: 'fullpage_pressed.png' 1248 }, 1249 rotateleft: { 1250 REST: 'rotateleft_rest.png', 1251 GROUP: 'rotateleft_grouphover.png', 1252 HOVER: 'rotateleft_hover.png', 1253 DOWN: 'rotateleft_pressed.png' 1254 }, 1255 rotateright: { 1256 REST: 'rotateright_rest.png', 1257 GROUP: 'rotateright_grouphover.png', 1258 HOVER: 'rotateright_hover.png', 1259 DOWN: 'rotateright_pressed.png' 1260 }, 1261 flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos [email protected], https://thenounproject.com/term/flip/136289/ 1262 REST: 'flip_rest.png', 1263 GROUP: 'flip_grouphover.png', 1264 HOVER: 'flip_hover.png', 1265 DOWN: 'flip_pressed.png' 1266 }, 1267 previous: { 1268 REST: 'previous_rest.png', 1269 GROUP: 'previous_grouphover.png', 1270 HOVER: 'previous_hover.png', 1271 DOWN: 'previous_pressed.png' 1272 }, 1273 next: { 1274 REST: 'next_rest.png', 1275 GROUP: 'next_grouphover.png', 1276 HOVER: 'next_hover.png', 1277 DOWN: 'next_pressed.png' 1278 } 1279 }, 1280 1281 //DEVELOPER SETTINGS 1282 debugMode: false, 1283 debugGridColor: ['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666'] 1284 }, 1285 1286 1287 /** 1288 * TODO: get rid of this. I can't see how it's required at all. Looks 1289 * like an early legacy code artifact. 1290 * @static 1291 * @ignore 1292 */ 1293 SIGNAL: "----seadragon----", 1294 1295 1296 /** 1297 * Returns a function which invokes the method as if it were a method belonging to the object. 1298 * @function 1299 * @param {Object} object 1300 * @param {Function} method 1301 * @returns {Function} 1302 */ 1303 delegate: function( object, method ) { 1304 return function(){ 1305 var args = arguments; 1306 if ( args === undefined ){ 1307 args = []; 1308 } 1309 return method.apply( object, args ); 1310 }; 1311 }, 1312 1313 1314 /** 1315 * An enumeration of Browser vendors. 1316 * @static 1317 * @type {Object} 1318 * @property {Number} UNKNOWN 1319 * @property {Number} IE 1320 * @property {Number}
vendor: 15,928 bytes, lines 1320-1775
1320 FIREFOX 1321 * @property {Number} SAFARI 1322 * @property {Number} CHROME 1323 * @property {Number} OPERA 1324 */ 1325 BROWSERS: { 1326 UNKNOWN: 0, 1327 IE: 1, 1328 FIREFOX: 2, 1329 SAFARI: 3, 1330 CHROME: 4, 1331 OPERA: 5 1332 }, 1333 1334 1335 /** 1336 * Returns a DOM Element for the given id or element. 1337 * @function 1338 * @param {String|Element} element Accepts an id or element. 1339 * @returns {Element} The element with the given id, null, or the element itself. 1340 */ 1341 getElement: function( element ) { 1342 if ( typeof ( element ) == "string" ) { 1343 element = document.getElementById( element ); 1344 } 1345 return element; 1346 }, 1347 1348 1349 /** 1350 * Determines the position of the upper-left corner of the element. 1351 * @function 1352 * @param {Element|String} element - the element we want the position for. 1353 * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element. 1354 */ 1355 getElementPosition: function( element ) { 1356 var result = new $.Point(), 1357 isFixed, 1358 offsetParent; 1359 1360 element = $.getElement( element ); 1361 isFixed = $.getElementStyle( element ).position == "fixed"; 1362 offsetParent = getOffsetParent( element, isFixed ); 1363 1364 while ( offsetParent ) { 1365 1366 result.x += element.offsetLeft; 1367 result.y += element.offsetTop; 1368 1369 if ( isFixed ) { 1370 result = result.plus( $.getPageScroll() ); 1371 } 1372 1373 element = offsetParent; 1374 isFixed = $.getElementStyle( element ).position == "fixed"; 1375 offsetParent = getOffsetParent( element, isFixed ); 1376 } 1377 1378 return result; 1379 }, 1380 1381 1382 /** 1383 * Determines the position of the upper-left corner of the element adjusted for current page and/or element scroll. 1384 * @function 1385 * @param {Element|String} element - the element we want the position for. 1386 * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll. 1387 */ 1388 getElementOffset: function( element ) { 1389 element = $.getElement( element ); 1390 1391 var doc = element && element.ownerDocument, 1392 docElement, 1393 win, 1394 boundingRect = { top: 0, left: 0 }; 1395 1396 if ( !doc ) { 1397 return new $.Point(); 1398 } 1399 1400 docElement = doc.documentElement; 1401 1402 if ( typeof element.getBoundingClientRect !== typeof undefined ) { 1403 boundingRect = element.getBoundingClientRect(); 1404 } 1405 1406 win = ( doc == doc.window ) ? 1407 doc : 1408 ( doc.nodeType === 9 ) ? 1409 doc.defaultView || doc.parentWindow : 1410 false; 1411 1412 return new $.Point( 1413 boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ), 1414 boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 ) 1415 ); 1416 }, 1417 1418 1419 /** 1420 * Determines the height and width of the given element. 1421 * @function 1422 * @param {Element|String} element 1423 * @returns {OpenSeadragon.Point} 1424 */ 1425 getElementSize: function( element ) { 1426 element = $.getElement( element ); 1427 1428 return new $.Point( 1429 element.clientWidth, 1430 element.clientHeight 1431 ); 1432 }, 1433 1434 1435 /** 1436 * Returns the CSSStyle object for the given element. 1437 * @function 1438 * @param {Element|String} element 1439 * @returns {CSSStyle} 1440 */ 1441 getElementStyle: 1442 document.documentElement.currentStyle ? 1443 function( element ) { 1444 element = $.getElement( element ); 1445 return element.currentStyle; 1446 } : 1447 function( element ) { 1448 element = $.getElement( element ); 1449 return window.getComputedStyle( element, "" ); 1450 }, 1451 1452 /** 1453 * Returns the property with the correct vendor prefix appended. 1454 * @param {String} property the property name 1455 * @returns {String} the property with the correct prefix or null if not 1456 * supported. 1457 */ 1458 getCssPropertyWithVendorPrefix: function(property) { 1459 var memo = {}; 1460 1461 $.getCssPropertyWithVendorPrefix = function(property) { 1462 if (memo[property] !== undefined) { 1463 return memo[property]; 1464 } 1465 var style = document.createElement('div').style; 1466 var result = null; 1467 if (style[property] !== undefined) { 1468 result = property; 1469 } else { 1470 var prefixes = ['Webkit', 'Moz', 'MS', 'O', 1471 'webkit', 'moz', 'ms', 'o']; 1472 var suffix = $.capitalizeFirstLetter(property); 1473 for (var i = 0; i < prefixes.length; i++) { 1474 var prop = prefixes[i] + suffix; 1475 if (style[prop] !== undefined) { 1476 result = prop; 1477 break; 1478 } 1479 } 1480 } 1481 memo[property] = result; 1482 return result; 1483 }; 1484 return $.getCssPropertyWithVendorPrefix(property); 1485 }, 1486 1487 /** 1488 * Capitalizes the first letter of a string 1489 * @param {String} string 1490 * @returns {String} The string with the first letter capitalized 1491 */ 1492 capitalizeFirstLetter: function(string) { 1493 return string.charAt(0).toUpperCase() + string.slice(1); 1494 }, 1495 1496 /** 1497 * Compute the modulo of a number but makes sure to always return 1498 * a positive value. 1499 * @param {Number} number the number to computes the modulo of 1500 * @param {Number} modulo the modulo 1501 * @returns {Number} the result of the modulo of number 1502 */ 1503 positiveModulo: function(number, modulo) { 1504 var result = number % modulo; 1505 if (result < 0) { 1506 result += modulo; 1507 } 1508 return result; 1509 }, 1510 1511 /** 1512 * Determines if a point is within the bounding rectangle of the given element (hit-test). 1513 * @function 1514 * @param {Element|String} element 1515 * @param {OpenSeadragon.Point} point 1516 * @returns {Boolean} 1517 */ 1518 pointInElement: function( element, point ) { 1519 element = $.getElement( element ); 1520 var offset = $.getElementOffset( element ), 1521 size = $.getElementSize( element ); 1522 return point.x >= offset.x && point.x < offset.x + size.x && point.y < offset.y + size.y && point.y >= offset.y; 1523 }, 1524 1525 1526 /** 1527 * Gets the latest event, really only useful internally since its 1528 * specific to IE behavior. 1529 * @function 1530 * @param {Event} [event] 1531 * @returns {Event} 1532 * @deprecated For internal use only 1533 * @private 1534 */ 1535 getEvent: function( event ) { 1536 if( event ){ 1537 $.getEvent = function( event ) { 1538 return event; 1539 }; 1540 } else { 1541 $.getEvent = function() { 1542 return window.event; 1543 }; 1544 } 1545 return $.getEvent( event ); 1546 }, 1547 1548 1549 /** 1550 * Gets the position of the mouse on the screen for a given event. 1551 * @function 1552 * @param {Event} [event] 1553 * @returns {OpenSeadragon.Point} 1554 */ 1555 getMousePosition: function( event ) { 1556 1557 if ( typeof ( event.pageX ) == "number" ) { 1558 $.getMousePosition = function( event ){ 1559 var result = new $.Point(); 1560 1561 event = $.getEvent( event ); 1562 result.x = event.pageX; 1563 result.y = event.pageY; 1564 1565 return result; 1566 }; 1567 } else if ( typeof ( event.clientX ) == "number" ) { 1568 $.getMousePosition = function( event ){ 1569 var result = new $.Point(); 1570 1571 event = $.getEvent( event ); 1572 result.x = 1573 event.clientX + 1574 document.body.scrollLeft + 1575 document.documentElement.scrollLeft; 1576 result.y = 1577 event.clientY + 1578 document.body.scrollTop + 1579 document.documentElement.scrollTop; 1580 1581 return result; 1582 }; 1583 } else { 1584 throw new Error( 1585 "Unknown event mouse position, no known technique." 1586 ); 1587 } 1588 1589 return $.getMousePosition( event ); 1590 }, 1591 1592 1593 /** 1594 * Determines the page's current scroll position. 1595 * @function 1596 * @returns {OpenSeadragon.Point} 1597 */ 1598 getPageScroll: function() { 1599 var docElement = document.documentElement || {}, 1600 body = document.body || {}; 1601 1602 if ( typeof ( window.pageXOffset ) == "number" ) { 1603 $.getPageScroll = function(){ 1604 return new $.Point( 1605 window.pageXOffset, 1606 window.pageYOffset 1607 ); 1608 }; 1609 } else if ( body.scrollLeft || body.scrollTop ) { 1610 $.getPageScroll = function(){ 1611 return new $.Point( 1612 document.body.scrollLeft, 1613 document.body.scrollTop 1614 ); 1615 }; 1616 } else if ( docElement.scrollLeft || docElement.scrollTop ) { 1617 $.getPageScroll = function(){ 1618 return new $.Point( 1619 document.documentElement.scrollLeft, 1620 document.documentElement.scrollTop 1621 ); 1622 }; 1623 } else { 1624 // We can't reassign the function yet, as there was no scroll. 1625 return new $.Point(0, 0); 1626 } 1627 1628 return $.getPageScroll(); 1629 }, 1630 1631 /** 1632 * Set the page scroll position. 1633 * @function 1634 * @returns {OpenSeadragon.Point} 1635 */ 1636 setPageScroll: function( scroll ) { 1637 if ( typeof ( window.scrollTo ) !== "undefined" ) { 1638 $.setPageScroll = function( scroll ) { 1639 window.scrollTo( scroll.x, scroll.y ); 1640 }; 1641 } else { 1642 var originalScroll = $.getPageScroll(); 1643 if ( originalScroll.x === scroll.x && 1644 originalScroll.y === scroll.y ) { 1645 // We are already correctly positioned and there 1646 // is no way to detect the correct method. 1647 return; 1648 } 1649 1650 document.body.scrollLeft = scroll.x; 1651 document.body.scrollTop = scroll.y; 1652 var currentScroll = $.getPageScroll(); 1653 if ( currentScroll.x !== originalScroll.x && 1654 currentScroll.y !== originalScroll.y ) { 1655 $.setPageScroll = function( scroll ) { 1656 document.body.scrollLeft = scroll.x; 1657 document.body.scrollTop = scroll.y; 1658 }; 1659 return; 1660 } 1661 1662 document.documentElement.scrollLeft = scroll.x; 1663 document.documentElement.scrollTop = scroll.y; 1664 currentScroll = $.getPageScroll(); 1665 if ( currentScroll.x !== originalScroll.x && 1666 currentScroll.y !== originalScroll.y ) { 1667 $.setPageScroll = function( scroll ) { 1668 document.documentElement.scrollLeft = scroll.x; 1669 document.documentElement.scrollTop = scroll.y; 1670 }; 1671 return; 1672 } 1673 1674 // We can't find anything working, so we do nothing. 1675 $.setPageScroll = function( scroll ) { 1676 }; 1677 } 1678 1679 return $.setPageScroll( scroll ); 1680 }, 1681 1682 /** 1683 * Determines the size of the browsers window. 1684 * @function 1685 * @returns {OpenSeadragon.Point} 1686 */ 1687 getWindowSize: function() { 1688 var docElement = document.documentElement || {}, 1689 body = document.body || {}; 1690 1691 if ( typeof ( window.innerWidth ) == 'number' ) { 1692 $.getWindowSize = function(){ 1693 return new $.Point( 1694 window.innerWidth, 1695 window.innerHeight 1696 ); 1697 }; 1698 } else if ( docElement.clientWidth || docElement.clientHeight ) { 1699 $.getWindowSize = function(){ 1700 return new $.Point( 1701 document.documentElement.clientWidth, 1702 document.documentElement.clientHeight 1703 ); 1704 }; 1705 } else if ( body.clientWidth || body.clientHeight ) { 1706 $.getWindowSize = function(){ 1707 return new $.Point( 1708 document.body.clientWidth, 1709 document.body.clientHeight 1710 ); 1711 }; 1712 } else { 1713 throw new Error("Unknown window size, no known technique."); 1714 } 1715 1716 return $.getWindowSize(); 1717 }, 1718 1719 1720 /** 1721 * Wraps the given element in a nest of divs so that the element can 1722 * be easily centered using CSS tables 1723 * @function 1724 * @param {Element|String} element 1725 * @returns {Element} outermost wrapper element 1726 */ 1727 makeCenteredNode: function( element ) { 1728 // Convert a possible ID to an actual HTMLElement 1729 element = $.getElement( element ); 1730 1731 /* 1732 CSS tables require you to have a display:table/row/cell hierarchy so we need to create 1733 three nested wrapper divs: 1734 */ 1735 1736 var wrappers = [ 1737 $.makeNeutralElement( 'div' ), 1738 $.makeNeutralElement( 'div' ), 1739 $.makeNeutralElement( 'div' ) 1740 ]; 1741 1742 // It feels like we should be able to pass style dicts to makeNeutralElement: 1743 $.extend(wrappers[0].style, { 1744 display: "table", 1745 height: "100%", 1746 width: "100%" 1747 }); 1748 1749 $.extend(wrappers[1].style, { 1750 display: "table-row" 1751 }); 1752 1753 $.extend(wrappers[2].style, { 1754 display: "table-cell", 1755 verticalAlign: "middle", 1756 textAlign: "center" 1757 }); 1758 1759 wrappers[0].appendChild(wrappers[1]); 1760 wrappers[1].appendChild(wrappers[2]); 1761 wrappers[2].appendChild(element); 1762 1763 return wrappers[0]; 1764 }, 1765 1766 1767 /** 1768 * Creates an easily positionable element of the given type that therefor 1769 * serves as an excellent container element. 1770 * @function 1771 * @param {String} tagName 1772 * @returns {Element} 1773 */ 1774 makeNeutralElement: function( tagName ) { 1775 var element = document.createElement( tagName ),
1776 style = element.style; 1777 1778 style.background = "transparent none"; 1779 style.border = "none"; 1780 style.margin = "0px"; 1781 style.padding = "0px"; 1782 style.position = "static"; 1783 1784 return element; 1785 }, 1786 1787 1788 /** 1789 * Returns the current milliseconds, using Date.now() if available 1790 * @function 1791 */ 1792 now: function( ) { 1793 if (Date.now) { 1794 $.now = Date.now; 1795 } else { 1796 $.now = function() { 1797 return new Date().getTime(); 1798 }; 1799 } 1800 1801 return $.now(); 1802 }, 1803 1804 1805 /** 1806 * Ensures an image is loaded correctly to support alpha transparency. 1807 * Generally only IE has issues doing this correctly for formats like 1808 * png. 1809 * @function 1810 * @param {String} src 1811 * @returns {Element} 1812 */ 1813 makeTransparentImage: function( src ) { 1814 1815 $.makeTransparentImage = function( src ){ 1816 var img = $.makeNeutralElement( "img" ); 1817 1818 img.src = src; 1819 1820 return img; 1821 }; 1822 1823 if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) { 1824 1825 $.makeTransparentImage = function( src ){ 1826 var img = $.makeNeutralElement( "img" ), 1827 element = null; 1828 1829 element = $.makeNeutralElement("span"); 1830 element.style.display = "inline-block"; 1831 1832 img.onload = function() { 1833 element.style.width = element.style.width || img.width + "px"; 1834 element.style.height = element.style.height || img.height + "px"; 1835 1836 img.onload = null; 1837 img = null; // to prevent memory leaks in IE 1838 }; 1839 1840 img.src = src; 1841 element.style.filter = 1842 "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + 1843 src + 1844 "', sizingMethod='scale')"; 1845 1846 return element; 1847 }; 1848 1849 } 1850 1851 return $.makeTransparentImage( src ); 1852 }, 1853 1854 1855 /** 1856 * Sets the opacity of the specified element. 1857 * @function 1858 * @param {Element|String} element 1859 * @param {Number} opacity 1860 * @param {Boolean} [usesAlpha] 1861 */ 1862 setElementOpacity: function( element, opacity, usesAlpha ) { 1863 1864 var ieOpacity, 1865 ieFilter; 1866 1867 element = $.getElement( element ); 1868 1869 if ( usesAlpha && !$.Browser.alpha ) { 1870 opacity = Math.round( opacity ); 1871 } 1872 1873 if ( $.Browser.opacity ) { 1874 element.style.opacity = opacity < 1 ? opacity : ""; 1875 } else { 1876 if ( opacity < 1 ) { 1877 ieOpacity = Math.round( 100 * opacity ); 1878 ieFilter = "alpha(opacity=" + ieOpacity + ")"; 1879 element.style.filter = ieFilter; 1880 } else { 1881 element.style.filter = ""; 1882 } 1883 } 1884 }, 1885 1886 1887 /** 1888 * Sets the specified element's touch-action style attribute to 'none'. 1889 * @function 1890 * @param {Element|String} element 1891 */ 1892 setElementTouchActionNone: function( element ) { 1893 element = $.getElement( element ); 1894 if ( typeof element.style.touchAction !== 'undefined' ) { 1895 element.style.touchAction = 'none'; 1896 } else if ( typeof element.style.msTouchAction !== 'undefined' ) { 1897 element.style.msTouchAction = 'none'; 1898 } 1899 }, 1900 1901 1902 /** 1903 * Add the specified CSS class to the element if not present. 1904 * @function 1905 * @param {Element|String} element 1906 * @param {String} className 1907 */ 1908 addClass: function( element, className ) { 1909 element = $.getElement( element ); 1910 1911 if (!element.className) { 1912 element.className = className; 1913 } else if ( ( ' ' + element.className + ' ' ). 1914 indexOf( ' ' + className + ' ' ) === -1 ) { 1915 element.className += ' ' + className; 1916 } 1917 }, 1918 1919 /** 1920 * Find the first index at which an element is found in an array or -1 1921 * if not present. 1922 * 1923 * Code taken and adapted from 1924 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility 1925 * 1926 * @function 1927 * @param {Array} array The array from which to find the element 1928 * @param {Object} searchElement The element to find 1929 * @param {Number} [fromIndex=0] Index to start research. 1930 * @returns {Number} The index of the element in the array. 1931 */ 1932 indexOf: function( array, searchElement, fromIndex ) { 1933 if ( Array.prototype.indexOf ) { 1934 this.indexOf = function( array, searchElement, fromIndex ) { 1935 return array.indexOf( searchElement, fromIndex ); 1936 }; 1937 } else { 1938 this.indexOf = function( array, searchElement, fromIndex ) { 1939 var i, 1940 pivot = ( fromIndex ) ? fromIndex : 0, 1941 length; 1942 if ( !array ) { 1943 throw new TypeError( ); 1944 } 1945 1946 length = array.length; 1947 if ( length === 0 || pivot >= length ) { 1948 return -1; 1949 } 1950 1951 if ( pivot < 0 ) { 1952 pivot = length - Math.abs( pivot ); 1953 } 1954 1955 for ( i = pivot; i < length; i++ ) { 1956 if ( array[i] === searchElement ) { 1957 return i; 1958 } 1959 } 1960 return -1; 1961 }; 1962 } 1963 return this.indexOf( array, searchElement, fromIndex ); 1964 }, 1965 1966 /** 1967 * Remove the specified CSS class from the element. 1968 * @function 1969 * @param {Element|String} element 1970 * @param {String} className 1971 */ 1972 removeClass: function( element, className ) {
1973 var oldClasses, 1974 newClasses = [], 1975 i; 1976 1977 element = $.getElement( element ); 1978 oldClasses = element.className.split( /\s+/ ); 1979 for ( i = 0; i < oldClasses.length; i++ ) { 1980 if ( oldClasses[ i ] && oldClasses[ i ] !== className ) { 1981 newClasses.push( oldClasses[ i ] ); 1982 } 1983 } 1984 element.className = newClasses.join(' '); 1985 }, 1986 1987 1988 /** 1989 * Adds an event listener for the given element, eventName and handler. 1990 * @function 1991 * @param {Element|String} element 1992 * @param {String} eventName 1993 * @param {Function} handler 1994 * @param {Boolean} [useCapture] 1995 */ 1996 addEvent: (function () { 1997 if ( window.addEventListener ) { 1998 return function ( element, eventName, handler, useCapture ) { 1999 element = $.getElement( element ); 2000 element.addEventListener( eventName, handler, useCapture ); 2001 }; 2002 } else if ( window.attachEvent ) { 2003 return function ( element, eventName, handler, useCapture ) { 2004 element = $.getElement( element ); 2005 element.attachEvent( 'on' + eventName, handler ); 2006 }; 2007 } else { 2008 throw new Error( "No known event model." ); 2009 } 2010 }()), 2011 2012 2013 /** 2014 * Remove a given event listener for the given element, event type and 2015 * handler. 2016 * @function 2017 * @param {Element|String} element 2018 * @param {String} eventName 2019 * @param {Function} handler 2020 * @param {Boolean} [useCapture] 2021 */ 2022 removeEvent: (function () { 2023 if ( window.removeEventListener ) { 2024 return function ( element, eventName, handler, useCapture ) { 2025 element = $.getElement( element ); 2026 element.removeEventListener( eventName, handler, useCapture ); 2027 }; 2028 } else if ( window.detachEvent ) { 2029 return function( element, eventName, handler, useCapture ) { 2030 element = $.getElement( element ); 2031 element.detachEvent( 'on' + eventName, handler ); 2032 }; 2033 } else { 2034 throw new Error( "No known event model." ); 2035 } 2036 }()), 2037 2038 2039 /** 2040 * Cancels the default browser behavior had the event propagated all 2041 * the way up the DOM to the window object. 2042 * @function 2043 * @param {Event} [event] 2044 */ 2045 cancelEvent: function( event ) { 2046 event = $.getEvent( event ); 2047 2048 if ( event.preventDefault ) { 2049 $.cancelEvent = function( event ){ 2050 // W3C for preventing default 2051 event.preventDefault(); 2052 }; 2053 } else { 2054 $.cancelEvent = function( event ){ 2055 event = $.getEvent( event ); 2056 // legacy for preventing default 2057 event.cancel = true; 2058 // IE for preventing default 2059 event.returnValue = false; 2060 }; 2061 } 2062 $.cancelEvent( event ); 2063 }, 2064 2065 2066 /** 2067 * Stops the propagation of the event up the DOM. 2068 * @function 2069 * @param {Event} [event] 2070 */ 2071 stopEvent: function( event ) { 2072 event = $.getEvent( event ); 2073 2074 if ( event.stopPropagation ) { 2075 // W3C for stopping propagation 2076 $.stopEvent = function( event ){ 2077 event.stopPropagation(); 2078 }; 2079 } else { 2080 // IE for stopping propagation 2081 $.stopEvent = function( event ){ 2082 event = $.getEvent( event ); 2083 event.cancelBubble = true; 2084 }; 2085 2086 } 2087 2088 $.stopEvent( event ); 2089 }, 2090 2091 2092 /** 2093 * Similar to OpenSeadragon.delegate, but it does not immediately call 2094 * the method on the object, returning a function which can be called 2095 * repeatedly to delegate the method. It also allows additional arguments 2096 * to be passed during construction which will be added during each 2097 * invocation, and each invocation can add additional arguments as well. 2098 * 2099 * @function 2100 * @param {Object} object 2101 * @param {Function} method 2102 * @param [args] any additional arguments are passed as arguments to the 2103 * created callback 2104 * @returns {Function} 2105 */ 2106 createCallback: function( object, method ) { 2107 //TODO: This pattern is painful to use and debug. It's much cleaner 2108 // to use pinning plus anonymous functions. Get rid of this 2109 // pattern! 2110 var initialArgs = [], 2111 i; 2112 for ( i = 2; i < arguments.length; i++ ) { 2113 initialArgs.push( arguments[ i ] ); 2114 } 2115 2116 return function() { 2117 var args = initialArgs.concat( [] ), 2118 i; 2119 for ( i = 0; i < arguments.length; i++ ) { 2120 args.push( arguments[ i ] ); 2121 } 2122 2123 return method.apply( object, args ); 2124 }; 2125 }, 2126 2127 2128 /** 2129 * Retrieves the value of a url parameter from the window.location string. 2130 * @function 2131 * @param {String} key 2132 * @returns {String} The value of the url parameter or null if no param matches. 2133 */ 2134 getUrlParameter: function( key ) { 2135 // eslint-disable-next-line no-use-before-define 2136 var value = URLPARAMS[ key ]; 2137 return value ? value : null; 2138 }, 2139 2140 /** 2141 * Retrieves the protocol used by the url. The url can either be absolute 2142 * or relative. 2143 * @function 2144 * @private 2145 * @param {String} url The url to retrieve the protocol from. 2146 * @return {String} The protocol (http:, https:, file:, ftp: ...) 2147 */ 2148 getUrlProtocol: function( url ) { 2149 var match = url.match(/^([a-z]+:)\/\//i); 2150 if ( match === null ) { 2151 // Relative URL, retrive the protocol from window.location 2152 return window.location.protocol; 2153 } 2154 return match[1].toLowerCase(); 2155 }, 2156 2157 /** 2158 * Create an XHR object 2159 * @private 2160 * @param {type} [local] If set to true, the XHR will be file: protocol 2161 * compatible if possible (but may raise a warning in the browser). 2162 * @returns {XMLHttpRequest} 2163 */ 2164 createAjaxRequest: function( local ) { 2165 // IE11 does not support window.ActiveXObject so we just try to 2166 // create one to see if it is supported.
2167 // See: http://msdn.microsoft.com/en-us/library/ie/dn423948%28v=vs.85%29.aspx 2168 var supportActiveX; 2169 try { 2170 /* global ActiveXObject:true */ 2171 supportActiveX = !!new ActiveXObject( "Microsoft.XMLHTTP" ); 2172 } catch( e ) { 2173 supportActiveX = false; 2174 } 2175 2176 if ( supportActiveX ) { 2177 if ( window.XMLHttpRequest ) { 2178 $.createAjaxRequest = function( local ) { 2179 if ( local ) { 2180 return new ActiveXObject( "Microsoft.XMLHTTP" ); 2181 } 2182 return new XMLHttpRequest(); 2183 }; 2184 } else { 2185 $.createAjaxRequest = function() { 2186 return new ActiveXObject( "Microsoft.XMLHTTP" ); 2187 }; 2188 } 2189 } else if ( window.XMLHttpRequest ) { 2190 $.createAjaxRequest = function() { 2191 return new XMLHttpRequest(); 2192 }; 2193 } else { 2194 throw new Error( "Browser doesn't support XMLHttpRequest." ); 2195 } 2196 return $.createAjaxRequest( local ); 2197 }, 2198 2199 /** 2200 * Makes an AJAX request. 2201 * @param {Object} options 2202 * @param {String} options.url - the url to request 2203 * @param {Function} options.success - a function to call on a successful response 2204 * @param {Function} options.error - a function to call on when an error occurs 2205 * @param {Object} options.headers - headers to add to the AJAX request 2206 * @param {String} options.responseType - the response type of the the AJAX request 2207 * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials 2208 * @throws {Error} 2209 * @returns {XMLHttpRequest} 2210 */ 2211 makeAjaxRequest: function( url, onSuccess, onError ) { 2212 var withCredentials; 2213 var headers; 2214 var responseType; 2215 2216 // Note that our preferred API is that you pass in a single object; the named 2217 // arguments are for legacy support. 2218 if( $.isPlainObject( url ) ){ 2219 onSuccess = url.success; 2220 onError = url.error; 2221 withCredentials = url.withCredentials; 2222 headers = url.headers; 2223 responseType = url.responseType || null; 2224 url = url.url; 2225 } 2226 2227 var protocol = $.getUrlProtocol( url ); 2228 var request = $.createAjaxRequest( protocol === "file:" ); 2229 2230 if ( !$.isFunction( onSuccess ) ) { 2231 throw new Error( "makeAjaxRequest requires a success callback" ); 2232 } 2233 2234 request.onreadystatechange = function() { 2235 // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties) 2236 if ( request.readyState == 4 ) { 2237 request.onreadystatechange = function(){}; 2238
2239 // With protocols other than http/https, a successful request status is in 2240 // the 200's on Firefox and 0 on other browsers 2241 if ( (request.status >= 200 && request.status < 300) || 2242 ( request.status === 0 && 2243 protocol !== "http:" && 2244 protocol !== "https:" )) { 2245 onSuccess( request ); 2246 } else { 2247 $.console.log( "AJAX request returned %d: %s", request.status, url ); 2248 2249 if ( $.isFunction( onError ) ) { 2250 onError( request ); 2251 } 2252 } 2253 } 2254 }; 2255 2256 try { 2257 request.open( "GET", url, true ); 2258 2259 if (responseType) { 2260 request.responseType = responseType; 2261 } 2262 2263 if (headers) { 2264 for (var headerName in headers) { 2265 if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) { 2266 request.setRequestHeader(headerName, headers[headerName]); 2267 } 2268 } 2269 } 2270 2271 if (withCredentials) { 2272 request.withCredentials = true; 2273 } 2274 2275 request.send(null); 2276 } catch (e) { 2277 var msg = e.message; 2278 2279 /* 2280 IE < 10 does not support CORS and an XHR request to a different origin will fail as soon 2281 as send() is called. This is particularly easy to miss during development and appear in 2282 production if you use a CDN or domain sharding and the security policy is likely to break 2283 exception handlers since any attempt to access a property of the request object will 2284 raise an access denied TypeError inside the catch block. 2285 2286 To be friendlier, we'll check for this specific error and add a documentation pointer 2287 to point developers in the right direction. We test the exception number because IE's 2288 error messages are localized. 2289 */ 2290 var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10; 2291 if ( oldIE && typeof ( e.number ) != "undefined" && e.number == -2147024891 ) { 2292 msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain"; 2293 } 2294 2295 $.console.log( "%s while making AJAX request: %s", e.name, msg ); 2296 2297 request.onreadystatechange = function(){}; 2298 2299 if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest 2300 var xdr = new window.XDomainRequest(); 2301 if (xdr) { 2302 xdr.onload = function (e) { 2303 if ( $.isFunction( onSuccess ) ) { 2304 onSuccess({ // Faking an xhr object 2305 responseText: xdr.responseText, 2306 status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/ 2307 statusText: 'OK' 2308 }); 2309 } 2310 }; 2311 xdr.onerror = function (e) { 2312 if ($.isFunction(onError)) { 2313 onError({ // Faking an xhr object 2314 responseText: xdr.responseText, 2315 status: 444, // 444 No Response 2316 statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.' 2317 }); 2318 } 2319 }; 2320 try {
2321 xdr.open('GET', url); 2322 xdr.send(); 2323 } catch (e2) { 2324 if ( $.isFunction( onError ) ) { 2325 onError( request, e ); 2326 } 2327 } 2328 } 2329 } else { 2330 if ( $.isFunction( onError ) ) { 2331 onError( request, e ); 2332 } 2333 } 2334 } 2335 2336 return request; 2337 }, 2338 2339 /** 2340 * Taken from jQuery 1.6.1 2341 * @function 2342 * @param {Object} options 2343 * @param {String} options.url 2344 * @param {Function} options.callback 2345 * @param {String} [options.param='callback'] The name of the url parameter 2346 * to request the jsonp provider with. 2347 * @param {String} [options.callbackName=] The name of the callback to 2348 * request the jsonp provider with. 2349 */ 2350 jsonp: function( options ){ 2351 var script, 2352 url = options.url, 2353 head = document.head || 2354 document.getElementsByTagName( "head" )[ 0 ] || 2355 document.documentElement, 2356 jsonpCallback = options.callbackName || 'openseadragon' + $.now(), 2357 previous = window[ jsonpCallback ], 2358 replace = "$1" + jsonpCallback + "$2", 2359 callbackParam = options.param || 'callback', 2360 callback = options.callback; 2361 2362 url = url.replace( /(\=)\?(&|$)|\?\?/i, replace ); 2363 // Add callback manually 2364 url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback; 2365 2366 // Install callback 2367 window[ jsonpCallback ] = function( response ) { 2368 if ( !previous ){ 2369 try{ 2370 delete window[ jsonpCallback ]; 2371 }catch(e){ 2372 //swallow 2373 } 2374 } else { 2375 window[ jsonpCallback ] = previous; 2376 } 2377 if( callback && $.isFunction( callback ) ){ 2378 callback( response ); 2379 } 2380 }; 2381 2382 script = document.createElement( "script" ); 2383 2384 //TODO: having an issue with async info requests 2385 if( undefined !== options.async || false !== options.async ){ 2386 script.async = "async"; 2387 } 2388 2389 if ( options.scriptCharset ) { 2390 script.charset = options.scriptCharset; 2391 } 2392 2393 script.src = url; 2394 2395 // Attach handlers for all browsers 2396 script.onload = script.onreadystatechange = function( _, isAbort ) { 2397 2398 if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { 2399 2400 // Handle memory leak in IE 2401 script.onload = script.onreadystatechange = null; 2402 2403 // Remove the script 2404 if ( head && script.parentNode ) { 2405 head.removeChild( script ); 2406 } 2407 2408 // Dereference the script 2409 script = undefined; 2410 } 2411 }; 2412 // Use insertBefore instead of appendChild to circumvent an IE6 bug. 2413 // This arises when a base node is used (#2709 and #4378). 2414 head.insertBefore( script, head.firstChild ); 2415 2416 }, 2417 2418 2419 /** 2420 * Fully deprecated. Will throw an error. 2421 * @function 2422 * @deprecated use {@link OpenSeadragon.Viewer#open} 2423 */ 2424 createFromDZI: function() { 2425 throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open."; 2426 }, 2427 2428 /** 2429 * Parses an XML string into a DOM Document. 2430 * @function 2431 * @param {String} string 2432 * @returns {Document} 2433 */ 2434 parseXml: function( string ) { 2435 if ( window.DOMParser ) { 2436 2437 $.parseXml = function( string ) { 2438 var xmlDoc = null, 2439 parser; 2440 2441 parser = new DOMParser(); 2442 xmlDoc = parser.parseFromString( string, "text/xml" ); 2443 return xmlDoc; 2444 }; 2445 2446 } else if ( window.ActiveXObject ) { 2447 2448 $.parseXml = function( string ) { 2449 var xmlDoc = null; 2450 2451 xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" ); 2452 xmlDoc.async = false;
2453 xmlDoc.loadXML( string ); 2454 return xmlDoc; 2455 }; 2456 2457 } else { 2458 throw new Error( "Browser doesn't support XML DOM." ); 2459 } 2460 2461 return $.parseXml( string ); 2462 }, 2463 2464 /** 2465 * Parses a JSON string into a Javascript object. 2466 * @function 2467 * @param {String} string 2468 * @returns {Object} 2469 */ 2470 parseJSON: function(string) { 2471 if (window.JSON && window.JSON.parse) { 2472 $.parseJSON = window.JSON.parse; 2473 } else { 2474 // Should only be used by IE8 in non standards mode 2475 $.parseJSON = function(string) { 2476 /*jshint evil:true*/ 2477 //eslint-disable-next-line no-eval 2478 return eval('(' + string + ')'); 2479 }; 2480 } 2481 return $.parseJSON(string); 2482 }, 2483 2484 /** 2485 * Reports whether the image format is supported for tiling in this 2486 * version. 2487 * @function 2488 * @param {String} [extension] 2489 * @returns {Boolean} 2490 */ 2491 imageFormatSupported: function( extension ) { 2492 extension = extension ? extension : ""; 2493 // eslint-disable-next-line no-use-before-define 2494 return !!FILEFORMATS[ extension.toLowerCase() ]; 2495 } 2496 2497 }); 2498 2499 2500 //TODO: $.console is often used inside a try/catch block which generally 2501 // prevents allowings errors to occur with detection until a debugger 2502 // is attached. Although I've been guilty of the same anti-pattern 2503 // I eventually was convinced that errors should naturally propagate in 2504 // all but the most special cases. 2505 /** 2506 * A convenient alias for console when available, and a simple null 2507 * function when console is unavailable. 2508 * @static 2509 * @private 2510 */ 2511 var nullfunction = function( msg ){ 2512 //document.location.hash = msg; 2513 }; 2514 2515 $.console = window.console || { 2516 log: nullfunction, 2517 debug: nullfunction, 2518 info: nullfunction, 2519 warn: nullfunction, 2520 error: nullfunction, 2521 assert: nullfunction 2522 }; 2523 2524 2525 /** 2526 * The current browser vendor, version, and related information regarding detected features. 2527 * @member {Object} Browser 2528 * @memberof OpenSeadragon 2529 * @static 2530 * @type {Object} 2531 * @property {OpenSeadragon.BROWSERS} vendor - One of the {@link OpenSeadragon.BROWSERS} enumeration values. 2532 * @property {Number} version 2533 * @property {Boolean} alpha - Does the browser support image alpha transparency. 2534 */ 2535 $.Browser = { 2536 vendor: $.BROWSERS.UNKNOWN, 2537 version: 0, 2538 alpha: true 2539 }; 2540 2541 2542 var FILEFORMATS = { 2543 "bmp": false, 2544 "jpeg": true, 2545 "jpg": true, 2546 "png": true, 2547 "tif": false, 2548 "wdp": false 2549 }, 2550 URLPARAMS = {}; 2551 2552 (function() { 2553 //A small auto-executing routine to determine the browser vendor, 2554 //version and supporting feature sets. 2555 var ver = navigator.appVersion, 2556 ua = navigator.userAgent, 2557 regex; 2558 2559 //console.error( 'appName: ' + navigator.appName ); 2560 //console.error( 'appVersion: ' + navigator.appVersion ); 2561 //console.error( 'userAgent: ' + navigator.userAgent ); 2562 2563 switch( navigator.appName ){ 2564 case "Microsoft Internet Explorer": 2565 if( !!window.attachEvent && 2566 !!window.ActiveXObject ) { 2567 2568 $.Browser.vendor = $.BROWSERS.IE; 2569 $.Browser.version = parseFloat( 2570 ua.substring( 2571 ua.indexOf( "MSIE" ) + 5, 2572 ua.indexOf( ";", ua.indexOf( "MSIE" ) ) ) 2573 ); 2574 } 2575 break; 2576 case "Netscape": 2577 if (window.addEventListener) { 2578 if ( ua.indexOf( "Firefox" ) >= 0 ) { 2579 $.Browser.vendor = $.BROWSERS.FIREFOX; 2580 $.Browser.version = parseFloat( 2581 ua.substring( ua.indexOf( "Firefox" ) + 8 ) 2582 ); 2583 } else if ( ua.indexOf( "Safari" ) >= 0 ) { 2584 $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ? 2585 $.BROWSERS.CHROME : 2586 $.BROWSERS.SAFARI; 2587 $.Browser.version = parseFloat( 2588 ua.substring( 2589 ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1, 2590 ua.indexOf( "Safari" ) 2591 ) 2592 ); 2593 } else { 2594 regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,})"); 2595 if ( regex.exec( ua ) !== null ) { 2596 $.Browser.vendor = $.BROWSERS.IE; 2597 $.Browser.version = parseFloat( RegExp.$1 ); 2598 } 2599 } 2600 } 2601 break; 2602 case "Opera": 2603 $.Browser.vendor = $.BROWSERS.OPERA; 2604 $.Browser.version = parseFloat( ver ); 2605 break; 2606 } 2607 2608 // ignore '?' portion of query string 2609 var query = window.location.search.substring( 1 ), 2610 parts = query.split('&'), 2611 part, 2612 sep, 2613 i; 2614 2615 for ( i = 0; i < parts.length; i++ ) { 2616 part = parts[ i ]; 2617 sep = part.indexOf( '=' ); 2618 2619 if ( sep > 0 ) { 2620 var key = part.substring( 0, sep ), 2621 value = part.substring( sep + 1 ); 2622 try { 2623 URLPARAMS[ key ] = decodeURIComponent( value ); 2624 } catch (e) { 2625 $.console.error( "Ignoring malformed URL parameter: %s=%s", key, value ); 2626 } 2627 } 2628 } 2629 2630 //determine if this browser supports image alpha transparency 2631 $.Browser.alpha = !( 2632 ( 2633 $.Browser.vendor == $.BROWSERS.IE && 2634 $.Browser.version < 9 2635 ) || ( 2636 $.Browser.vendor == $.BROWSERS.CHROME && 2637 $.Browser.version < 2 2638 ) 2639 ); 2640 2641 //determine if this browser supports element.style.opacity 2642 $.Browser.opacity = !( 2643 $.Browser.vendor == $.BROWSERS.IE && 2644 $.Browser.version < 9 2645 ); 2646 2647 })(); 2648 2649 2650 // Adding support for HTML5's requestAnimationFrame as suggested by acdha. 2651 // Implementation taken from matt synder's post here: 2652 // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/ 2653 (function( w ) { 2654 2655 // most browsers have an implementation 2656 var requestAnimationFrame = w.requestAnimationFrame || 2657 w.mozRequestAnimationFrame || 2658 w.webkitRequestAnimationFrame || 2659 w.msRequestAnimationFrame; 2660 2661 var cancelAnimationFrame = w.cancelAnimationFrame || 2662 w.mozCancelAnimationFrame || 2663 w.webkitCancelAnimationFrame || 2664 w.msCancelAnimationFrame; 2665 2666 // polyfill, when necessary 2667 if ( requestAnimationFrame && cancelAnimationFrame ) { 2668 // We can't assign these window methods directly to $ because they 2669 // expect their "this" to be "window", so we call them in wrappers. 2670 $.requestAnimationFrame = function(){ 2671 return requestAnimationFrame.apply( w, arguments ); 2672 }; 2673 $.cancelAnimationFrame = function(){ 2674 return cancelAnimationFrame.apply( w, arguments ); 2675 }; 2676 } else {
2677 var aAnimQueue = [], 2678 processing = [], 2679 iRequestId = 0, 2680 iIntervalId; 2681 2682 // create a mock requestAnimationFrame function 2683 $.requestAnimationFrame = function( callback ) { 2684 aAnimQueue.push( [ ++iRequestId, callback ] ); 2685 2686 if ( !iIntervalId ) { 2687 iIntervalId = setInterval( function() { 2688 if ( aAnimQueue.length ) { 2689 var time = $.now(); 2690 // Process all of the currently outstanding frame 2691 // requests, but none that get added during the 2692 // processing. 2693 // Swap the arrays so we don't have to create a new 2694 // array every frame. 2695 var temp = processing; 2696 processing = aAnimQueue; 2697 aAnimQueue = temp; 2698 while ( processing.length ) { 2699 processing.shift()[ 1 ]( time ); 2700 } 2701 } else { 2702 // don't continue the interval, if unnecessary 2703 clearInterval( iIntervalId ); 2704 iIntervalId = undefined; 2705 } 2706 }, 1000 / 50); // estimating support for 50 frames per second 2707 } 2708 2709 return iRequestId; 2710 }; 2711 2712 // create a mock cancelAnimationFrame function 2713 $.cancelAnimationFrame = function( requestId ) { 2714 // find the request ID and remove it 2715 var i, j; 2716 for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) { 2717 if ( aAnimQueue[ i ][ 0 ] === requestId ) { 2718 aAnimQueue.splice( i, 1 ); 2719 return; 2720 } 2721 } 2722 2723 // If it's not in the queue, it may be in the set we're currently 2724 // processing (if cancelAnimationFrame is called from within a 2725 // requestAnimationFrame callback). 2726 for ( i = 0, j = processing.length; i < j; i += 1 ) { 2727 if ( processing[ i ][ 0 ] === requestId ) { 2728 processing.splice( i, 1 ); 2729 return; 2730 } 2731 } 2732 }; 2733 } 2734 })( window ); 2735 2736 /** 2737 * @private 2738 * @inner 2739 * @function 2740 * @param {Element} element 2741 * @param {Boolean} [isFixed] 2742 * @returns {Element} 2743 */ 2744 function getOffsetParent( element, isFixed ) { 2745 if ( isFixed && element != document.body ) { 2746 return document.body; 2747 } else { 2748 return element.offsetParent; 2749 } 2750 } 2751 2752}(OpenSeadragon)); 2753 2754 2755// Universal Module Definition, supports CommonJS, AMD and simple script tag 2756(function (root, factory) { 2757 if (typeof define === 'function' && define.amd) { 2758 // expose as amd module 2759 define([], factory); 2760 } else if (typeof module === 'object' && module.exports) { 2761 // expose as commonjs module 2762 module.exports = factory(); 2763 } else { 2764 // expose as window.OpenSeadragon 2765 root.OpenSeadragon = factory(); 2766 } 2767}(this, function () { 2768 return OpenSeadragon; 2769})); 2770 2771/* 2772 * OpenSeadragon - full-screen support functions 2773 * 2774 * Copyright (C) 2009 CodePlex Foundation 2775 * Copyright (C) 2010-2013 OpenSeadragon contributors 2776 * 2777 * Redistribution and use in source and binary forms, with or without 2778 * modification, are permitted provided that the following conditions are 2779 * met: 2780 * 2781 * - Redistributions of source code must retain the above copyright notice, 2782 * this list of conditions and the following disclaimer. 2783 * 2784 * - Redistributions in binary form must reproduce the above copyright 2785 * notice, this list of conditions and the following disclaimer in the 2786 * documentation and/or other materials provided with the distribution. 2787 * 2788 * - Neither the name of CodePlex Foundation nor the names of its 2789 * contributors may be used to endorse or promote products derived from 2790 * this software without specific prior written permission. 2791 * 2792 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2793 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2794 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2795 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2796 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2797 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2798 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2799 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2800 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2801 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2802 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2803 */ 2804 2805(function( $ ) { 2806 /** 2807 * Determine native full screen support we can get from the browser. 2808 * @member fullScreenApi 2809 * @memberof OpenSeadragon 2810 * @type {object} 2811 * @property {Boolean} supportsFullScreen Return true if full screen API is supported. 2812 * @property {Function} isFullScreen Return true if currently in full screen mode. 2813 * @property {Function} getFullScreenElement Return the element currently in full screen mode. 2814 * @property {Function} requestFullScreen Make a request to go in full screen mode. 2815 * @property {Function} exitFullScreen Make a request to exit full screen mode. 2816 * @property {Function} cancelFullScreen Deprecated, use exitFullScreen instead. 2817 * @property {String} fullScreenEventName Event fired when the full screen mode change. 2818 * @property {String} fullScreenErrorEventName Event fired when a request to go 2819 * in full screen mode failed. 2820 */ 2821 var fullScreenApi = { 2822 supportsFullScreen: false, 2823 isFullScreen: function() { return false; }, 2824 getFullScreenElement: function() { return null; }, 2825 requestFullScreen: function() {}, 2826 exitFullScreen: function() {}, 2827 cancelFullScreen: function() {}, 2828 fullScreenEventName: '', 2829 fullScreenErrorEventName: '' 2830 }; 2831 2832 // check for native support 2833 if ( document.exitFullscreen ) { 2834 // W3C standard 2835 fullScreenApi.supportsFullScreen = true; 2836 fullScreenApi.getFullScreenElement = function() { 2837 return document.fullscreenElement; 2838 }; 2839 fullScreenApi.requestFullScreen = function( element ) { 2840 return element.requestFullscreen(); 2841 }; 2842 fullScreenApi.exitFullScreen = function() { 2843 document.exitFullscreen(); 2844 }; 2845 fullScreenApi.fullScreenEventName = "fullscreenchange"; 2846 fullScreenApi.fullScreenErrorEventName = "fullscreenerror"; 2847 } else if ( document.msExitFullscreen ) { 2848 // IE 11 2849 fullScreenApi.supportsFullScreen = true; 2850 fullScreenApi.getFullScreenElement = function() { 2851 return document.msFullscreenElement; 2852 }; 2853 fullScreenApi.requestFullScreen = function( element ) { 2854 return element.msRequestFullscreen(); 2855 }; 2856 fullScreenApi.exitFullScreen = function() { 2857 document.msExitFullscreen(); 2858 }; 2859 fullScreenApi.fullScreenEventName = "MSFullscreenChange"; 2860 fullScreenApi.fullScreenErrorEventName = "MSFullscreenError"; 2861 } else if ( document.webkitExitFullscreen ) { 2862 // Recent webkit 2863 fullScreenApi.supportsFullScreen = true; 2864 fullScreenApi.getFullScreenElement = function() { 2865 return document.webkitFullscreenElement; 2866 }; 2867 fullScreenApi.requestFullScreen = function( element ) { 2868 return element.webkitRequestFullscreen(); 2869 }; 2870 fullScreenApi.exitFullScreen = function() { 2871 document.webkitExitFullscreen(); 2872 }; 2873 fullScreenApi.fullScreenEventName = "webkitfullscreenchange"; 2874 fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror"; 2875 } else if ( document.webkitCancelFullScreen ) { 2876 // Old webkit 2877 fullScreenApi.supportsFullScreen = true; 2878 fullScreenApi.getFullScreenElement = function() { 2879 return document.webkitCurrentFullScreenElement; 2880 }; 2881 fullScreenApi.requestFullScreen = function( element ) { 2882 return element.webkitRequestFullScreen(); 2883 }; 2884 fullScreenApi.exitFullScreen = function() { 2885 document.webkitCancelFullScreen(); 2886 }; 2887 fullScreenApi.fullScreenEventName = "webkitfullscreenchange"; 2888 fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror"; 2889 } else if ( document.mozCancelFullScreen ) { 2890 // Firefox 2891 fullScreenApi.supportsFullScreen = true; 2892 fullScreenApi.getFullScreenElement = function() { 2893 return document.mozFullScreenElement; 2894 }; 2895 fullScreenApi.requestFullScreen = function( element ) { 2896 return element.mozRequestFullScreen(); 2897 }; 2898 fullScreenApi.exitFullScreen = function() { 2899 document.mozCancelFullScreen(); 2900 }; 2901 fullScreenApi.fullScreenEventName = "mozfullscreenchange"; 2902 fullScreenApi.fullScreenErrorEventName = "mozfullscreenerror"; 2903 } 2904 fullScreenApi.isFullScreen = function() { 2905 return fullScreenApi.getFullScreenElement() !== null; 2906 }; 2907 fullScreenApi.cancelFullScreen = function() { 2908 $.console.error("cancelFullScreen is deprecated. Use exitFullScreen instead."); 2909 fullScreenApi.exitFullScreen(); 2910 }; 2911 2912 // export api 2913 $.extend( $, fullScreenApi ); 2914 2915})( OpenSeadragon ); 2916 2917/* 2918 * OpenSeadragon - EventSource 2919 * 2920 * Copyright (C) 2009 CodePlex Foundation 2921 * Copyright (C) 2010-2013 OpenSeadragon contributors 2922 * 2923 * Redistribution and use in source and binary forms, with or without 2924 * modification, are permitted provided that the following conditions are 2925 * met: 2926 * 2927 * - Redistributions of source code must retain the above copyright notice, 2928 * this list of conditions and the following disclaimer. 2929 * 2930 * - Redistributions in binary form must reproduce the above copyright 2931 * notice, this list of conditions and the following disclaimer in the 2932 * documentation and/or other materials provided with the distribution. 2933 * 2934 * - Neither the name of CodePlex Foundation nor the names of its 2935 * contributors may be used to endorse or promote products derived from 2936 * this software without specific prior written permission. 2937 * 2938 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2939 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2940 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2941 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2942 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2943 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2944 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2945 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2946 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2947 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2948 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2949 */ 2950 2951(function($){ 2952 2953/** 2954 * Event handler method signature used by all OpenSeadragon events. 2955 * 2956 * @callback EventHandler 2957 * @memberof OpenSeadragon 2958 * @param {Object} event - See individual events for event-specific properties. 2959 */ 2960 2961 2962/** 2963 * @class EventSource 2964 * @classdesc For use by classes which want to support custom, non-browser events. 2965 * 2966 * @memberof OpenSeadragon 2967 */ 2968$.EventSource = function() { 2969 this.events = {}; 2970}; 2971 2972/** @lends OpenSeadragon.EventSource.prototype */ 2973$.EventSource.prototype = { 2974 2975 /** 2976 * Add an event handler to be triggered only once (or a given number of times) 2977 * for a given event. 2978 * @function 2979 * @param {String} eventName - Name of event to register. 2980 * @param {OpenSeadragon.EventHandler} handler - Function to call when event 2981 * is triggered. 2982 * @param {Object} [userData=null] - Arbitrary object to be passed unchanged 2983 * to the handler. 2984 * @param {Number} [times=1] - The number of times to handle the event 2985 * before removing it. 2986 */ 2987 addOnceHandler: function(eventName, handler, userData, times) { 2988 var self = this; 2989 times = times || 1; 2990 var count = 0; 2991 var onceHandler = function(event) { 2992 count++; 2993 if (count === times) { 2994 self.removeHandler(eventName, onceHandler); 2995 } 2996 handler(event); 2997 }; 2998 this.addHandler(eventName, onceHandler, userData); 2999 }, 3000 3001 /** 3002 * Add an event handler for a given event. 3003 * @function 3004 * @param {String} eventName - Name of event to register. 3005 * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered. 3006 * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler. 3007 */ 3008 addHandler: function ( eventName, handler, userData ) { 3009 var events = this.events[ eventName ]; 3010 if ( !events ) { 3011 this.events[ eventName ] = events = []; 3012 } 3013 if ( handler && $.isFunction( handler ) ) { 3014 events[ events.length ] = { handler: handler, userData: userData || null }; 3015 } 3016 }, 3017 3018 /** 3019 * Remove a specific event handler for a given event. 3020 * @function 3021 * @param {String} eventName - Name of event for which the handler is to be removed. 3022 * @param {OpenSeadragon.EventHandler} handler - Function to be removed. 3023 */ 3024 removeHandler: function ( eventName, handler ) { 3025 var events = this.events[ eventName ], 3026 handlers = [], 3027 i; 3028 if ( !events ) { 3029 return; 3030 } 3031 if ( $.isArray( events ) ) { 3032 for ( i = 0; i < events.length; i++ ) { 3033 if ( events[i].handler !== handler ) { 3034 handlers.push( events[ i ] ); 3035 } 3036 } 3037 this.events[ eventName ] = handlers; 3038 } 3039 }, 3040 3041 3042 /** 3043 * Remove all event handlers for a given event type. If no type is given all 3044 * event handlers for every event type are removed. 3045 * @function 3046 * @param {String} eventName - Name of event for which all handlers are to be removed. 3047 */ 3048 removeAllHandlers: function( eventName ) { 3049 if ( eventName ){ 3050 this.events[ eventName ] = []; 3051 } else{ 3052 for ( var eventType in this.events ) { 3053 this.events[ eventType ] = []; 3054 } 3055 } 3056 }, 3057 3058 /** 3059 * Get a function which iterates the list of all handlers registered for a given event, calling the handler for each. 3060 * @function 3061 * @param {String} eventName - Name of event to get handlers for. 3062 */ 3063 getHandler: function ( eventName ) { 3064 var events = this.events[ eventName ]; 3065 if ( !events || !events.length ) { 3066 return null; 3067 } 3068 events = events.length === 1 ? 3069 [ events[ 0 ] ] : 3070 Array.apply( null, events ); 3071 return function ( source, args ) { 3072 var i, 3073 length = events.length; 3074 for ( i = 0; i < length; i++ ) { 3075 if ( events[ i ] ) { 3076 args.eventSource = source; 3077 args.userData = events[ i ].userData; 3078 events[ i ].handler( args ); 3079 } 3080 } 3081 }; 3082 }, 3083 3084 /** 3085 * Trigger an event, optionally passing additional information. 3086 * @function 3087 * @param {String} eventName - Name of event to register. 3088 * @param {Object} eventArgs - Event-specific data. 3089 */ 3090 raiseEvent: function( eventName, eventArgs ) { 3091 //uncomment if you want to get a log of all events 3092 //$.console.log( eventName ); 3093 var handler = this.getHandler( eventName ); 3094 3095 if ( handler ) { 3096 if ( !eventArgs ) { 3097 eventArgs = {}; 3098 } 3099 3100 handler( this, eventArgs ); 3101 } 3102 } 3103}; 3104 3105}( OpenSeadragon )); 3106 3107/* 3108 * OpenSeadragon - MouseTracker 3109 * 3110 * Copyright (C) 2009 CodePlex Foundation 3111 * Copyright (C) 2010-2013 OpenSeadragon contributors 3112 * 3113 * Redistribution and use in source and binary forms, with or without 3114 * modification, are permitted provided that the following conditions are 3115 * met: 3116 * 3117 * - Redistributions of source code must retain the above copyright notice, 3118 * this list of conditions and the following disclaimer. 3119 * 3120 * - Redistributions in binary form must reproduce the above copyright 3121 * notice, this list of conditions and the following disclaimer in the 3122 * documentation and/or other materials provided with the distribution. 3123 * 3124 * - Neither the name of CodePlex Foundation nor the names of its 3125 * contributors may be used to endorse or promote products derived from 3126 * this software without specific prior written permission. 3127 * 3128 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 3129 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 3130 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 3131 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 3132 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3133 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 3134 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 3135 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 3136 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 3137 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 3138 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 3139 */ 3140 3141(function ( $ ) { 3142 3143 // All MouseTracker instances 3144 var MOUSETRACKERS = []; 3145 3146 // dictionary from hash to private properties 3147 var THIS = {}; 3148 3149 3150 /** 3151 * @class MouseTracker 3152 * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) gestures 3153 * and keyboard events on a specified element. 3154 * @memberof OpenSeadragon 3155 * @param {Object} options 3156 * Allows configurable properties to be entirely specified by passing 3157 * an options object to the constructor. The constructor also supports 3158 * the original positional arguments 'element', 'clickTimeThreshold', 3159 * and 'clickDistThreshold' in that order. 3160 * @param {Element|String} options.element 3161 * A reference to an element or an element id for which the pointer/key 3162 * events will be monitored. 3163 * @param {Boolean} [options.startDisabled=false] 3164 * If true, event tracking on the element will not start until 3165 * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called. 3166 * @param {Number} options.clickTimeThreshold 3167 * The number of milliseconds within which a pointer down-up event combination 3168 * will be treated as a click gesture. 3169 * @param {Number} options.clickDistThreshold 3170 * The maximum distance allowed between a pointer down event and a pointer up event 3171 * to be treated as a click gesture. 3172 * @param {Number} options.dblClickTimeThreshold 3173 * The number of milliseconds within which two pointer down-up event combinations 3174 * will be treated as a double-click gesture. 3175 * @param {Number} options.dblClickDistThreshold 3176 * The maximum distance allowed between two pointer click events 3177 * to be treated as a click gesture. 3178 * @param {Number} [options.stopDelay=50] 3179 * The number of milliseconds without pointer move before the stop 3180 * event is fired. 3181 * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] 3182 * An optional handler for pointer enter. 3183 * @param {OpenSeadragon.EventHandler} [options.exitHandler=null] 3184 * An optional handler for pointer exit. 3185 * @param {OpenSeadragon.EventHandler} [options.pressHandler=null] 3186 * An optional handler for pointer press. 3187 * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null] 3188 * An optional handler for pointer non-primary button press. 3189 * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null] 3190 * An optional handler for pointer release. 3191 * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null] 3192 * An optional handler for pointer non-primary button release. 3193 * @param {OpenSeadragon.EventHandler} [options.moveHandler=null] 3194 * An optional handler for pointer move. 3195 * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null] 3196 * An optional handler for mouse wheel scroll. 3197 * @param {OpenSeadragon.EventHandler} [options.clickHandler=null] 3198 * An optional handler for pointer click. 3199 * @param {OpenSeadragon.EventHandler} [options.dblClickHandler=null] 3200 * An optional handler for pointer double-click. 3201 * @param {OpenSeadragon.EventHandler} [options.dragHandler=null] 3202 * An optional handler for the drag gesture. 3203 * @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null] 3204 * An optional handler for after a drag gesture. 3205 * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null] 3206 * An optional handler for the pinch gesture. 3207 * @param {OpenSeadragon.EventHandler}
3207 [options.keyDownHandler=null] 3208 * An optional handler for keydown. 3209 * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null] 3210 * An optional handler for keyup. 3211 * @param {OpenSeadragon.EventHandler} [options.keyHandler=null] 3212 * An optional handler for keypress. 3213 * @param {OpenSeadragon.EventHandler} [options.focusHandler=null] 3214 * An optional handler for focus. 3215 * @param {OpenSeadragon.EventHandler} [options.blurHandler=null] 3216 * An optional handler for blur. 3217 * @param {Object} [options.userData=null] 3218 * Arbitrary object to be passed unchanged to any attached handler methods. 3219 */ 3220 $.MouseTracker = function ( options ) { 3221 3222 MOUSETRACKERS.push( this ); 3223 3224 var args = arguments; 3225 3226 if ( !$.isPlainObject( options ) ) { 3227 options = { 3228 element: args[ 0 ], 3229 clickTimeThreshold: args[ 1 ], 3230 clickDistThreshold: args[ 2 ] 3231 }; 3232 } 3233 3234 this.hash = Math.random(); // An unique hash for this tracker. 3235 /** 3236 * The element for which pointer events are being monitored. 3237 * @member {Element} element 3238 * @memberof OpenSeadragon.MouseTracker# 3239 */ 3240 this.element = $.getElement( options.element ); 3241 /** 3242 * The number of milliseconds within which a pointer down-up event combination 3243 * will be treated as a click gesture. 3244 * @member {Number} clickTimeThreshold 3245 * @memberof OpenSeadragon.MouseTracker# 3246 */ 3247 this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold; 3248 /** 3249 * The maximum distance allowed between a pointer down event and a pointer up event 3250 * to be treated as a click gesture. 3251 * @member {Number} clickDistThreshold 3252 * @memberof OpenSeadragon.MouseTracker# 3253 */ 3254 this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold; 3255 /** 3256 * The number of milliseconds within which two pointer down-up event combinations 3257 * will be treated as a double-click gesture. 3258 * @member {Number} dblClickTimeThreshold 3259 * @memberof OpenSeadragon.MouseTracker# 3260 */ 3261 this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold; 3262 /** 3263 * The maximum distance allowed between two pointer click events 3264 * to be treated as a click gesture. 3265 * @member {Number} clickDistThreshold 3266 * @memberof OpenSeadragon.MouseTracker# 3267 */ 3268 this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold; 3269 /*eslint-disable no-multi-spaces*/ 3270 this.userData = options.userData || null; 3271 this.stopDelay = options.stopDelay || 50; 3272 3273 this.enterHandler = options.enterHandler || null; 3274 this.exitHandler = options.exitHandler || null; 3275 this.pressHandler = options.pressHandler || null; 3276 this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null; 3277 this.releaseHandler = options.releaseHandler || null; 3278 this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null; 3279 this.moveHandler = options.moveHandler || null; 3280 this.scrollHandler = options.scrollHandler || null; 3281 this.clickHandler = options.clickHandler || null; 3282 this.dblClickHandler = options.dblClickHandler || null; 3283 this.dragHandler = options.dragHandler || null; 3284 this.dragEndHandler = options.dragEndHandler || null; 3285 this.pinchHandler = options.pinchHandler || null; 3286 this.stopHandler = options.stopHandler || null;
3287 this.keyDownHandler = options.keyDownHandler || null; 3288 this.keyUpHandler = options.keyUpHandler || null; 3289 this.keyHandler = options.keyHandler || null; 3290 this.focusHandler = options.focusHandler || null; 3291 this.blurHandler = options.blurHandler || null; 3292 /*eslint-enable no-multi-spaces*/ 3293 3294 //Store private properties in a scope sealed hash map 3295 var _this = this; 3296 3297 /** 3298 * @private 3299 * @property {Boolean} tracking 3300 * Are we currently tracking pointer events for this element. 3301 */ 3302 THIS[ this.hash ] = { 3303 click: function ( event ) { onClick( _this, event ); }, 3304 dblclick: function ( event ) { onDblClick( _this, event ); }, 3305 keydown: function ( event ) { onKeyDown( _this, event ); }, 3306 keyup: function ( event ) { onKeyUp( _this, event ); }, 3307 keypress: function ( event ) { onKeyPress( _this, event ); }, 3308 focus: function ( event ) { onFocus( _this, event ); }, 3309 blur: function ( event ) { onBlur( _this, event ); }, 3310 3311 wheel: function ( event ) { onWheel( _this, event ); }, 3312 mousewheel: function ( event ) { onMouseWheel( _this, event ); }, 3313 DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, 3314 MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, 3315 3316 mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only 3317 mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only 3318 mouseover: function ( event ) { onMouseOver( _this, event ); }, 3319 mouseout: function ( event ) { onMouseOut( _this, event ); }, 3320 mousedown: function ( event ) { onMouseDown( _this, event ); }, 3321 mouseup: function ( event ) { onMouseUp( _this, event ); }, 3322 mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, 3323 mousemove: function ( event ) { onMouseMove( _this, event ); }, 3324 mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, 3325 3326 touchstart: function ( event ) { onTouchStart( _this, event ); }, 3327 touchend: function ( event ) { onTouchEnd( _this, event ); }, 3328 touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); }, 3329 touchmove: function ( event ) { onTouchMove( _this, event ); }, 3330 touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); }, 3331 touchcancel: function ( event ) { onTouchCancel( _this, event ); }, 3332 3333 gesturestart: function ( event ) { onGestureStart( _this, event ); }, 3334 gesturechange: function ( event ) { onGestureChange( _this, event ); }, 3335 3336 pointerover: function ( event ) { onPointerOver( _this, event ); }, 3337 MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, 3338 pointerout: function ( event ) { onPointerOut( _this, event ); }, 3339 MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, 3340 pointerdown: function ( event ) { onPointerDown( _this, event ); }, 3341 MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, 3342 pointerup: function ( event ) { onPointerUp( _this, event ); }, 3343 MSPointerUp: function ( event ) { onPointerUp( _this, event ); }, 3344 pointermove: function ( event ) { onPointerMove( _this, event ); }, 3345 MSPointerMove: function ( event ) { onPointerMove( _this, event ); }, 3346 pointercancel: function ( event ) { onPointerCancel( _this, event ); }, 3347 MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); }, 3348 pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); }, 3349 pointermovecaptured: function ( event ) { onPointerMoveCaptured( _this, event ); }, 3350 3351 tracking: false, 3352 3353 // Active pointers lists. Array of GesturePointList objects, one for each pointer device type. 3354 // GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()). 3355 // Active pointers are any pointer being tracked for this element which are in the hit-test area 3356 // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. 3357 activePointersLists: [], 3358 3359 // Tracking for double-click gesture 3360 lastClickPos: null, 3361 dblClickTimeOut: null, 3362 3363 // Tracking for pinch gesture 3364 pinchGPoints: [], 3365 lastPinchDist: 0, 3366 currentPinchDist: 0, 3367 lastPinchCenter: null, 3368 currentPinchCenter: null 3369 }; 3370 3371 if ( !options.startDisabled ) { 3372 this.setTracking( true ); 3373 } 3374 }; 3375 3376 /** @lends OpenSeadragon.MouseTracker.prototype */ 3377 $.MouseTracker.prototype = { 3378 3379 /** 3380 * Clean up any events or objects created by the tracker. 3381 * @function 3382 */ 3383 destroy: function () { 3384 var i; 3385 3386 stopTracking( this ); 3387 this.element = null; 3388 3389 for ( i = 0; i < MOUSETRACKERS.length; i++ ) { 3390 if ( MOUSETRACKERS[ i ] === this ) { 3391 MOUSETRACKERS.splice( i, 1 ); 3392 break; 3393 } 3394 } 3395 3396 THIS[ this.hash ] = null; 3397 delete THIS[ this.hash ]; 3398 }, 3399 3400 /** 3401 * Are we currently tracking events on this element. 3402 * @deprecated Just use this.tracking 3403 * @function 3404 * @returns {Boolean} Are we currently tracking events on this element. 3405 */ 3406 isTracking: function () { 3407 return THIS[ this.hash ].tracking; 3408 }, 3409 3410 /** 3411 * Enable or disable whether or not we are tracking events on this element. 3412 * @function 3413 * @param {Boolean} track True to start tracking, false to stop tracking. 3414 * @returns {OpenSeadragon.MouseTracker} Chainable. 3415 */ 3416 setTracking: function ( track ) { 3417 if ( track ) { 3418 startTracking( this ); 3419 } else { 3420 stopTracking( this ); 3421 } 3422 //chain 3423 return this; 3424 }, 3425 3426 /** 3427 * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type. 3428 * @function 3429 * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. 3430 * @returns {Array.<OpenSeadragon.MouseTracker.GesturePointList>} 3431 */ 3432 getActivePointersListsExceptType: function ( type ) { 3433 var delegate = THIS[ this.hash ]; 3434 var listArray = []; 3435 3436 for (var i = 0; i < delegate.activePointersLists.length; ++i) { 3437 if (delegate.activePointersLists[i].type !== type) { 3438 listArray.push(delegate.activePointersLists[i]); 3439 } 3440 } 3441 3442 return listArray; 3443 }, 3444 3445 /** 3446 * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type, 3447 * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type. 3448 * @function 3449 * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. 3450 * @returns {OpenSeadragon.MouseTracker.GesturePointList} 3451 */ 3452 getActivePointersListByType: function ( type ) { 3453 var delegate = THIS[ this.hash ], 3454 i, 3455 len = delegate.activePointersLists.length, 3456 list; 3457 3458 for ( i = 0; i < len; i++ ) { 3459 if ( delegate.activePointersLists[ i ].type === type ) { 3460 return delegate.activePointersLists[ i ]; 3461 } 3462 } 3463 3464 list = new $.MouseTracker.GesturePointList( type ); 3465 delegate.activePointersLists.push( list ); 3466 return list; 3467 }, 3468 3469 /** 3470 * Returns the total number of pointers currently active on the tracked element. 3471 * @function 3472 * @returns {Number} 3473 */ 3474 getActivePointerCount: function () { 3475 var delegate = THIS[ this.hash ], 3476 i, 3477 len = delegate.activePointersLists.length, 3478 count = 0; 3479 3480 for ( i = 0; i < len; i++ ) { 3481 count += delegate.activePointersLists[ i ].getLength(); 3482 } 3483 3484 return count; 3485 }, 3486 3487 /** 3488 * Implement or assign implementation to these handlers during or after 3489 * calling the constructor. 3490 * @function 3491 * @param {Object} event 3492 * @param {OpenSeadragon.MouseTracker} event.eventSource 3493 * A reference to the tracker instance. 3494 * @param {String} event.pointerType 3495 * "mouse", "touch", "pen", etc. 3496 * @param {OpenSeadragon.Point} event.position 3497 * The position of the event relative to the tracked element. 3498 * @param {Number} event.buttons 3499 * Current buttons pressed. 3500 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3501 * @param {Number} event.pointers 3502 * Number of pointers (all types) active in the tracked element. 3503 * @param {Boolean} event.insideElementPressed 3504 * True if the left mouse button is currently being pressed and was 3505 * initiated inside the tracked element, otherwise false. 3506 * @param {Boolean} event.buttonDownAny 3507 * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 3508 * @param {Boolean} event.isTouchEvent 3509 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3510 * @param {Object} event.originalEvent 3511 * The original event object. 3512 * @param {Boolean} event.preventDefaultAction 3513 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3514 * @param {Object} event.userData 3515 * Arbitrary user-defined object. 3516 */ 3517 enterHandler: function () { }, 3518 3519 /** 3520 * Implement or assign implementation to these handlers during or after 3521 * calling the constructor. 3522 * @function 3523 * @param {Object} event 3524 * @param {OpenSeadragon.MouseTracker} event.eventSource 3525 * A reference to the tracker instance. 3526 * @param {String} event.pointerType 3527 * "mouse", "touch", "pen", etc. 3528 * @param {OpenSeadragon.Point} event.position 3529 * The position of the event relative to the tracked element. 3530 * @param {Number} event.buttons 3531 * Current buttons pressed. 3532 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3533 * @param {Number} event.pointers 3534 * Number of pointers (all types) active in the tracked element. 3535 * @param {Boolean} event.insideElementPressed 3536 * True if the left mouse button is currently being pressed and was 3537 * initiated inside the tracked element, otherwise false. 3538 * @param {Boolean} event.buttonDownAny 3539 * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 3540 * @param {Boolean} event.isTouchEvent 3541 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3542 * @param {Object} event.originalEvent 3543 * The original event object. 3544 * @param {Boolean} event.preventDefaultAction 3545 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3546 * @param {Object} event.userData 3547 * Arbitrary user-defined object. 3548 */ 3549 exitHandler: function () { }, 3550 3551 /** 3552 * Implement or assign implementation to these handlers during or after 3553 * calling the constructor. 3554 * @function 3555 * @param {Object} event 3556 * @param {OpenSeadragon.MouseTracker} event.eventSource 3557 * A reference to the tracker instance. 3558 * @param {String} event.pointerType 3559 * "mouse", "touch", "pen", etc. 3560 * @param {OpenSeadragon.Point} event.position 3561 * The position of the event relative to the tracked element. 3562 * @param {Number} event.buttons 3563 * Current buttons pressed. 3564 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3565 * @param {Boolean} event.isTouchEvent 3566 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3567 * @param {Object} event.originalEvent 3568 * The original event object. 3569 * @param {Boolean} event.preventDefaultAction 3570 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3571 * @param {Object} event.userData 3572 * Arbitrary user-defined object. 3573 */ 3574 pressHandler: function () { }, 3575 3576 /** 3577 * Implement or assign implementation to these handlers during or after 3578 * calling the constructor. 3579 * @function 3580 * @param {Object} event 3581 * @param {OpenSeadragon.MouseTracker} event.eventSource 3582 * A reference to the tracker instance. 3583 * @param {String} event.pointerType 3584 * "mouse", "touch", "pen", etc. 3585 * @param {OpenSeadragon.Point} event.position 3586 * The position of the event relative to the tracked element. 3587 * @param {Number} event.button 3588 * Button which caused the event. 3589 * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 3590 * @param {Number} event.buttons 3591 * Current buttons pressed. 3592 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3593 * @param {Boolean} event.isTouchEvent 3594 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3595 * @param {Object} event.originalEvent 3596 * The original event object. 3597 * @param {Boolean} event.preventDefaultAction 3598 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3599 * @param {Object} event.userData 3600 * Arbitrary user-defined object. 3601 */ 3602 nonPrimaryPressHandler: function () { }, 3603 3604 /** 3605
3605 * Implement or assign implementation to these handlers during or after 3606 * calling the constructor. 3607 * @function 3608 * @param {Object} event 3609 * @param {OpenSeadragon.MouseTracker} event.eventSource 3610 * A reference to the tracker instance. 3611 * @param {String} event.pointerType 3612 * "mouse", "touch", "pen", etc. 3613 * @param {OpenSeadragon.Point} event.position 3614 * The position of the event relative to the tracked element. 3615 * @param {Number} event.buttons 3616 * Current buttons pressed. 3617 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3618 * @param {Boolean} event.insideElementPressed 3619 * True if the left mouse button is currently being pressed and was 3620 * initiated inside the tracked element, otherwise false. 3621 * @param {Boolean} event.insideElementReleased 3622 * True if the cursor inside the tracked element when the button was released. 3623 * @param {Boolean} event.isTouchEvent 3624 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3625 * @param {Object} event.originalEvent 3626 * The original event object. 3627 * @param {Boolean} event.preventDefaultAction 3628 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3629 * @param {Object} event.userData 3630 * Arbitrary user-defined object. 3631 */ 3632 releaseHandler: function () { }, 3633 3634 /** 3635 * Implement or assign implementation to these handlers during or after 3636 * calling the constructor. 3637 * @function 3638 * @param {Object} event 3639 * @param {OpenSeadragon.MouseTracker} event.eventSource 3640 * A reference to the tracker instance. 3641 * @param {String} event.pointerType 3642 * "mouse", "touch", "pen", etc. 3643 * @param {OpenSeadragon.Point} event.position 3644 * The position of the event relative to the tracked element. 3645 * @param {Number} event.button 3646 * Button which caused the event. 3647 * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 3648 * @param {Number} event.buttons 3649 * Current buttons pressed. 3650 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3651 * @param {Boolean} event.isTouchEvent 3652 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3653 * @param {Object} event.originalEvent 3654 * The original event object. 3655 * @param {Boolean} event.preventDefaultAction 3656 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3657 * @param {Object} event.userData 3658 * Arbitrary user-defined object. 3659 */ 3660 nonPrimaryReleaseHandler: function () { }, 3661 3662 /** 3663 * Implement or assign implementation to these handlers during or after 3664 * calling the constructor. 3665 * @function 3666 * @param {Object} event 3667 * @param {OpenSeadragon.MouseTracker} event.eventSource 3668 * A reference to the tracker instance. 3669 * @param {String} event.pointerType 3670 * "mouse", "touch", "pen", etc. 3671 * @param {OpenSeadragon.Point} event.position 3672 * The position of the event relative to the tracked element. 3673 * @param {Number} event.buttons 3674 * Current buttons pressed. 3675 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3676 * @param {Boolean} event.isTouchEvent 3677 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3678 * @param {Object} event.originalEvent 3679 * The original event object. 3680 * @param {Boolean} event.preventDefaultAction 3681 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3682 * @param {Object} event.userData 3683 * Arbitrary user-defined object. 3684 */ 3685 moveHandler: function () { }, 3686 3687 /** 3688 * Implement or assign implementation to these handlers during or after 3689 * calling the constructor. 3690 * @function 3691 * @param {Object} event 3692 * @param {OpenSeadragon.MouseTracker} event.eventSource 3693 * A reference to the tracker instance. 3694 * @param {String} event.pointerType 3695 * "mouse", "touch", "pen", etc. 3696 * @param {OpenSeadragon.Point} event.position 3697 * The position of the event relative to the tracked element. 3698 * @param {Number} event.scroll 3699 * The scroll delta for the event. 3700 * @param {Boolean} event.shift 3701 * True if the shift key was pressed during this event. 3702 * @param {Boolean} event.isTouchEvent 3703 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead. Touch devices no longer generate scroll event.</span> 3704 * @param {Object} event.originalEvent 3705 * The original event object. 3706 * @param {Boolean} event.preventDefaultAction 3707 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3708 * @param {Object} event.userData 3709 * Arbitrary user-defined object. 3710 */ 3711 scrollHandler: function () { }, 3712 3713 /** 3714 * Implement or assign implementation to these handlers during or after 3715 * calling the constructor. 3716 * @function 3717 * @param {Object} event 3718 * @param {OpenSeadragon.MouseTracker} event.eventSource 3719 * A reference to the tracker instance. 3720 * @param {String} event.pointerType 3721 * "mouse", "touch", "pen", etc. 3722 * @param {OpenSeadragon.Point} event.position 3723 * The position of the event relative to the tracked element. 3724 * @param {Boolean} event.quick 3725 * True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for ignoring drag events. 3726 * @param {Boolean} event.shift 3727 * True if the shift key was pressed during this event. 3728 * @param {Boolean} event.isTouchEvent 3729 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3730 * @param {Object} event.originalEvent 3731 * The original event object. 3732 * @param {Boolean} event.preventDefaultAction 3733 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3734 * @param {Object} event.userData 3735 * Arbitrary user-defined object. 3736 */ 3737 clickHandler: function () { }, 3738 3739 /** 3740 * Implement or assign implementation to these handlers during or after 3741 * calling the constructor. 3742 * @function 3743 * @param {Object} event 3744 * @param {OpenSeadragon.MouseTracker} event.eventSource 3745 * A reference to the tracker instance. 3746 * @param {String} event.pointerType 3747 * "mouse", "touch", "pen", etc. 3748 * @param {OpenSeadragon.Point} event.position 3749 * The position of the event relative to the tracked element. 3750 * @param {Boolean} event.shift 3751 * True if the shift key was pressed during this event. 3752 * @param {Boolean} event.isTouchEvent 3753 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3754 * @param {Object} event.originalEvent 3755 * The original event object. 3756 * @param {Boolean} event.preventDefaultAction 3757 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3758 * @param {Object} event.userData 3759 * Arbitrary user-defined object. 3760 */ 3761 dblClickHandler: function () { }, 3762 3763 /** 3764 * Implement or assign implementation to these handlers during or after 3765 * calling the constructor. 3766 * @function 3767 * @param {Object} event 3768 * @param {OpenSeadragon.MouseTracker} event.eventSource 3769 * A reference to the tracker instance. 3770 * @param {String} event.pointerType 3771 * "mouse", "touch", "pen", etc. 3772 * @param {OpenSeadragon.Point} event.position 3773 * The position of the event relative to the tracked element. 3774 * @param {Number} event.buttons 3775 * Current buttons pressed. 3776 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3777 * @param {OpenSeadragon.Point} event.delta 3778 * The x,y components of the difference between the current position and the last drag event position. Useful for ignoring or weighting the events. 3779 * @param {Number} event.speed 3780 * Current computed speed, in pixels per second. 3781 * @param {Number} event.direction 3782 * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 3783 * @param {Boolean} event.shift 3784 * True if the shift key was pressed during this event. 3785 * @param {Boolean} event.isTouchEvent 3786 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3787 * @param {Object} event.originalEvent 3788 * The original event object. 3789 * @param {Boolean} event.preventDefaultAction 3790 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3791 * @param {Object} event.userData 3792 * Arbitrary user-defined object. 3793 */ 3794 dragHandler: function () { }, 3795 3796 /** 3797 * Implement or assign implementation to these handlers during or after 3798 * calling the constructor. 3799 * @function 3800 * @param {Object} event 3801 * @param {OpenSeadragon.MouseTracker} event.eventSource 3802 * A reference to the tracker instance. 3803 * @param {String} event.pointerType 3804 * "mouse", "touch", "pen", etc. 3805 * @param {OpenSeadragon.Point} event.position 3806 * The position of the event relative to the tracked element. 3807 * @param {Number} event.speed 3808 * Speed at the end of a drag gesture, in pixels per second. 3809 * @param {Number} event.direction 3810 * Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 3811 * @param {Boolean} event.shift 3812 * True if the shift key was pressed during this event. 3813 * @param {Boolean} event.isTouchEvent 3814 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3815 * @param {Object} event.originalEvent 3816 * The original event object. 3817 * @param {Boolean} event.preventDefaultAction 3818 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3819 * @param {Object} event.userData 3820 * Arbitrary user-defined object. 3821 */ 3822 dragEndHandler: function () { }, 3823 3824 /** 3825 * Implement or assign implementation to these handlers during or after 3826 * calling the constructor. 3827 * @function 3828 * @param {Object} event 3829 * @param {OpenSeadragon.MouseTracker} event.eventSource 3830 * A reference to the tracker instance. 3831 * @param {String} event.pointerType 3832 * "mouse", "touch", "pen", etc. 3833 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} event.gesturePoints 3834 * Gesture points associated with the gesture. Velocity data can be found here. 3835 * @param {OpenSeadragon.Point} event.lastCenter 3836 * The previous center point of the two pinch contact points relative to the tracked element. 3837 * @param {OpenSeadragon.Point} event.center 3838 * The center point of the two pinch contact points relative to the tracked element. 3839 * @param {Number} event.lastDistance 3840 * The previous distance between the two pinch contact points in CSS pixels. 3841 * @param {Number} event.distance 3842 * The distance between the two pinch contact points in CSS pixels. 3843 * @param {Boolean} event.shift 3844 * True if the shift key was pressed during this event. 3845 * @param {Object} event.originalEvent 3846 * The original event object. 3847 * @param {Boolean} event.preventDefaultAction 3848 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3849 * @param {Object} event.userData 3850 * Arbitrary user-defined object. 3851 */ 3852 pinchHandler: function () { }, 3853 3854 /** 3855 * Implement or assign implementation to these handlers during or after 3856 * calling the constructor. 3857 * @function 3858 * @param {Object} event 3859 * @param {OpenSeadragon.MouseTracker} event.eventSource 3860 * A reference to the tracker instance. 3861 * @param {String} event.pointerType 3862 * "mouse", "touch", "pen", etc. 3863 * @param {OpenSeadragon.Point} event.position 3864 * The position of the event relative to the tracked element. 3865 * @param {Number} event.buttons 3866 * Current buttons pressed. 3867 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 3868 * @param {Boolean} event.isTouchEvent 3869 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3870 * @param {Object} event.originalEvent 3871 * The original event object. 3872 * @param {Boolean} event.preventDefaultAction 3873 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3874 * @param {Object} event.userData 3875 * Arbitrary user-defined object. 3876 */ 3877 stopHandler: function () { }, 3878 3879 /** 3880 * Implement or assign implementation to these handlers during or after 3881 * calling the constructor. 3882 * @function 3883 * @param {Object} event 3884 * @param {OpenSeadragon.MouseTracker} event.eventSource 3885 * A reference to the tracker instance. 3886 * @param {Number} event.keyCode 3887 * The key code that was pressed. 3888 * @param {Boolean} event.ctrl 3889 * True if the ctrl key was pressed during this event. 3890 * @param {Boolean} event.shift 3891 * True if the shift key
3891was pressed during this event. 3892 * @param {Boolean} event.alt 3893 * True if the alt key was pressed during this event. 3894 * @param {Boolean} event.meta 3895 * True if the meta key was pressed during this event. 3896 * @param {Object} event.originalEvent 3897 * The original event object. 3898 * @param {Boolean} event.preventDefaultAction 3899 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3900 * @param {Object} event.userData 3901 * Arbitrary user-defined object. 3902 */ 3903 keyDownHandler: function () { }, 3904 3905 /** 3906 * Implement or assign implementation to these handlers during or after 3907 * calling the constructor. 3908 * @function 3909 * @param {Object} event 3910 * @param {OpenSeadragon.MouseTracker} event.eventSource 3911 * A reference to the tracker instance. 3912 * @param {Number} event.keyCode 3913 * The key code that was pressed. 3914 * @param {Boolean} event.ctrl 3915 * True if the ctrl key was pressed during this event. 3916 * @param {Boolean} event.shift 3917 * True if the shift key was pressed during this event. 3918 * @param {Boolean} event.alt 3919 * True if the alt key was pressed during this event. 3920 * @param {Boolean} event.meta 3921 * True if the meta key was pressed during this event. 3922 * @param {Object} event.originalEvent 3923 * The original event object. 3924 * @param {Boolean} event.preventDefaultAction 3925 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3926 * @param {Object} event.userData 3927 * Arbitrary user-defined object. 3928 */ 3929 keyUpHandler: function () { }, 3930 3931 /** 3932 * Implement or assign implementation to these handlers during or after 3933 * calling the constructor. 3934 * @function 3935 * @param {Object} event 3936 * @param {OpenSeadragon.MouseTracker} event.eventSource 3937 * A reference to the tracker instance. 3938 * @param {Number} event.keyCode 3939 * The key code that was pressed. 3940 * @param {Boolean} event.ctrl 3941 * True if the ctrl key was pressed during this event. 3942 * @param {Boolean} event.shift 3943 * True if the shift key was pressed during this event. 3944 * @param {Boolean} event.alt 3945 * True if the alt key was pressed during this event. 3946 * @param {Boolean} event.meta 3947 * True if the meta key was pressed during this event. 3948 * @param {Object} event.originalEvent 3949 * The original event object. 3950 * @param {Boolean} event.preventDefaultAction 3951 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3952 * @param {Object} event.userData 3953 * Arbitrary user-defined object. 3954 */ 3955 keyHandler: function () { }, 3956 3957 /** 3958 * Implement or assign implementation to these handlers during or after 3959 * calling the constructor. 3960 * @function 3961 * @param {Object} event 3962 * @param {OpenSeadragon.MouseTracker} event.eventSource 3963 * A reference to the tracker instance. 3964 * @param {Object} event.originalEvent 3965 * The original event object. 3966 * @param {Boolean} event.preventDefaultAction 3967 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3968 * @param {Object} event.userData 3969 * Arbitrary user-defined object. 3970 */ 3971 focusHandler: function () { }, 3972 3973 /** 3974 * Implement or assign implementation to these handlers during or after 3975 * calling the constructor. 3976 * @function 3977 * @param {Object} event 3978 * @param {OpenSeadragon.MouseTracker} event.eventSource 3979 * A reference to the tracker instance. 3980 * @param {Object} event.originalEvent 3981 * The original event object. 3982 * @param {Boolean} event.preventDefaultAction 3983 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3984 * @param {Object} event.userData 3985 * Arbitrary user-defined object. 3986 */ 3987 blurHandler: function () { } 3988 }; 3989 3990 /** 3991 * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick") 3992 * 3993 * @private 3994 * @member resetAllMouseTrackers 3995 * @memberof OpenSeadragon.MouseTracker 3996 */ 3997 $.MouseTracker.resetAllMouseTrackers = function(){ 3998 for(var i = 0; i < MOUSETRACKERS.length; i++){ 3999 if (MOUSETRACKERS[i].isTracking()){ 4000 MOUSETRACKERS[i].setTracking(false); 4001 MOUSETRACKERS[i].setTracking(true); 4002 } 4003 } 4004 }; 4005 4006 /** 4007 * Provides continuous computation of velocity (speed and direction) of active pointers. 4008 * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than 4009 * two active gesture pointers at a time. 4010 * 4011 * @private 4012 * @member gesturePointVelocityTracker 4013 * @memberof OpenSeadragon.MouseTracker 4014 */ 4015 $.MouseTracker.gesturePointVelocityTracker = (function () { 4016 var trackerPoints = [], 4017 intervalId = 0, 4018 lastTime = 0; 4019 4020 // Generates a unique identifier for a tracked gesture point 4021 var _generateGuid = function ( tracker, gPoint ) { 4022 return tracker.hash.toString() + gPoint.type + gPoint.id.toString(); 4023 }; 4024 4025 // Interval timer callback. Computes velocity for all tracked gesture points. 4026 var _doTracking = function () { 4027 var i, 4028 len = trackerPoints.length, 4029 trackPoint, 4030 gPoint, 4031 now = $.now(), 4032 elapsedTime, 4033 distance, 4034 speed; 4035 4036 elapsedTime = now - lastTime;
4037 lastTime = now; 4038 4039 for ( i = 0; i < len; i++ ) { 4040 trackPoint = trackerPoints[ i ]; 4041 gPoint = trackPoint.gPoint; 4042 // Math.atan2 gives us just what we need for a velocity vector, as we can simply 4043 // use cos()/sin() to extract the x/y velocity components. 4044 gPoint.direction = Math.atan2( gPoint.currentPos.y - trackPoint.lastPos.y, gPoint.currentPos.x - trackPoint.lastPos.x ); 4045 // speed = distance / elapsed time 4046 distance = trackPoint.lastPos.distanceTo( gPoint.currentPos ); 4047 trackPoint.lastPos = gPoint.currentPos; 4048 speed = 1000 * distance / ( elapsedTime + 1 ); 4049 // Simple biased average, favors the most recent speed computation. Smooths out erratic gestures a bit. 4050 gPoint.speed = 0.75 * speed + 0.25 * gPoint.speed; 4051 } 4052 }; 4053 4054 // Public. Add a gesture point to be tracked 4055 var addPoint = function ( tracker, gPoint ) { 4056 var guid = _generateGuid( tracker, gPoint ); 4057 4058 trackerPoints.push( 4059 { 4060 guid: guid, 4061 gPoint: gPoint, 4062 lastPos: gPoint.currentPos 4063 } ); 4064 4065 // Only fire up the interval timer when there's gesture pointers to track 4066 if ( trackerPoints.length === 1 ) { 4067 lastTime = $.now(); 4068 intervalId = window.setInterval( _doTracking, 50 ); 4069 } 4070 }; 4071 4072 // Public. Stop tracking a gesture point 4073 var removePoint = function ( tracker, gPoint ) { 4074 var guid = _generateGuid( tracker, gPoint ), 4075 i, 4076 len = trackerPoints.length; 4077 for ( i = 0; i < len; i++ ) { 4078 if ( trackerPoints[ i ].guid === guid ) { 4079 trackerPoints.splice( i, 1 ); 4080 // Only run the interval timer if theres gesture pointers to track 4081 len--; 4082 if ( len === 0 ) { 4083 window.clearInterval( intervalId ); 4084 } 4085 break; 4086 } 4087 } 4088 }; 4089 4090 return { 4091 addPoint: addPoint, 4092 removePoint: removePoint 4093 }; 4094 } )(); 4095 4096 4097/////////////////////////////////////////////////////////////////////////////// 4098// Pointer event model and feature detection 4099/////////////////////////////////////////////////////////////////////////////// 4100 4101 $.MouseTracker.captureElement = document; 4102 4103 /** 4104 * Detect available mouse wheel event name. 4105 */ 4106 $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) || 4107 ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel' 4108 document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel' 4109 'DOMMouseScroll'; // Assume old Firefox 4110 4111 /** 4112 * Detect legacy mouse capture support. 4113 */ 4114 $.MouseTracker.supportsMouseCapture = (function () { 4115 var divElement = document.createElement( 'div' ); 4116 return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); 4117 }()); 4118 4119 /** 4120 * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. 4121 */ 4122 $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; 4123 4124 if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { 4125 // Older Firefox 4126 $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); 4127 } 4128 4129 // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec. 4130 if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) { 4131 // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents) 4132 $.MouseTracker.havePointerEvents = true; 4133 $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); 4134 $.MouseTracker.unprefixedPointerEvents = true; 4135 if( navigator.maxTouchPoints ) { 4136 $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints; 4137 } else { 4138 $.MouseTracker.maxTouchPoints = 0; 4139 } 4140 $.MouseTracker.haveMouseEnter = false;
4141 } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) { 4142 // IE10 4143 $.MouseTracker.havePointerEvents = true; 4144 $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); 4145 $.MouseTracker.unprefixedPointerEvents = false; 4146 if( navigator.msMaxTouchPoints ) { 4147 $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints; 4148 } else { 4149 $.MouseTracker.maxTouchPoints = 0; 4150 } 4151 $.MouseTracker.haveMouseEnter = false; 4152 } else { 4153 // Legacy W3C mouse events 4154 $.MouseTracker.havePointerEvents = false; 4155 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 4156 $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); 4157 $.MouseTracker.haveMouseEnter = true; 4158 } else { 4159 $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); 4160 $.MouseTracker.haveMouseEnter = false; 4161 } 4162 $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); 4163 if ( 'ontouchstart' in window ) { 4164 // iOS, Android, and other W3c Touch Event implementations 4165 // (see http://www.w3.org/TR/touch-events/) 4166 // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) 4167 // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) 4168 $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); 4169 } 4170 if ( 'ongesturestart' in window ) { 4171 // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) 4172 // Subscribe to these to prevent default gesture handling 4173 $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); 4174 } 4175 $.MouseTracker.mousePointerId = "legacy-mouse"; 4176 $.MouseTracker.maxTouchPoints = 10; 4177 } 4178 4179 4180/////////////////////////////////////////////////////////////////////////////// 4181// Classes and typedefs 4182/////////////////////////////////////////////////////////////////////////////// 4183 4184 /** 4185 * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device. 4186 * 4187 * @typedef {Object} GesturePoint 4188 * @memberof OpenSeadragon.MouseTracker 4189 * 4190 * @property {Number} id 4191 * Identifier unique from all other active GesturePoints for a given pointer device. 4192 * @property {String} type 4193 * The pointer device type: "mouse", "touch", "pen", etc. 4194 * @property {Boolean} captured 4195 * True if events for the gesture point are captured to the tracked element. 4196 * @property {Boolean} isPrimary 4197 * True if the gesture point is a master pointer amongst the set of active pointers for each pointer type. True for mouse and primary (first) touch/pen pointers. 4198 * @property {Boolean} insideElementPressed 4199 * True if button pressed or contact point initiated inside the screen area of the tracked element. 4200 * @property {Boolean} insideElement 4201 * True if pointer or contact point is currently inside the bounds of the tracked element. 4202 * @property {Number} speed 4203 * Current computed speed, in pixels per second. 4204 * @property {Number} direction 4205 * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 4206 * @property {OpenSeadragon.Point} contactPos 4207 * The initial pointer contact position, relative to the page including any scrolling. Only valid if the pointer has contact (pressed, touch contact, pen contact). 4208 * @property {Number} contactTime 4209 * The initial pointer contact time, in milliseconds. Only valid if the pointer has contact (pressed, touch contact, pen contact). 4210 * @property {OpenSeadragon.Point} lastPos 4211 * The last pointer position, relative to the page including any scrolling. 4212 * @property {Number} lastTime 4213 * The last pointer contact time, in milliseconds. 4214 * @property {OpenSeadragon.Point} currentPos 4215 * The current pointer position, relative to the page including any scrolling. 4216 * @property {Number} currentTime 4217 * The current pointer contact time, in milliseconds. 4218 */ 4219 4220 4221 /** 4222 * @class GesturePointList 4223 * @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type. 4224 * Active pointers are any pointer being tracked for this element which are in the hit-test area 4225 * of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. 4226 * @memberof OpenSeadragon.MouseTracker 4227 * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. 4228 */ 4229 $.MouseTracker.GesturePointList = function ( type ) { 4230 this._gPoints = []; 4231 /** 4232 * The pointer device type: "mouse", "touch", "pen", etc. 4233 * @member {String} type 4234 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 4235 */ 4236 this.type = type; 4237 /** 4238 * Current buttons pressed for the device. 4239 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 4240 * @member {Number} buttons 4241 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 4242 */ 4243 this.buttons = 0; 4244 /** 4245 * Current number of contact points (touch points, mouse down, etc.) for the device. 4246 * @member {Number} contacts 4247 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 4248 */ 4249 this.contacts = 0; 4250 /** 4251 * Current number of clicks for the device. Used for multiple click gesture tracking. 4252 * @member {Number} clicks 4253 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 4254 */ 4255 this.clicks = 0; 4256 /** 4257 * Current number of captured pointers for the device. 4258 * @member {Number} captureCount 4259 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 4260 */ 4261 this.captureCount = 0; 4262 }; 4263 4264 /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */ 4265 $.MouseTracker.GesturePointList.prototype = { 4266 /** 4267 * @function 4268 * @returns {Number} Number of gesture points in the list. 4269 */ 4270 getLength: function () { 4271 return this._gPoints.length; 4272 }, 4273 /** 4274 * @function 4275 * @returns {Array.<OpenSeadragon.MouseTracker.GesturePoint>} The list of gesture points in the list as an array (read-only). 4276 */ 4277 asArray: function () { 4278 return this._gPoints; 4279 }, 4280 /** 4281 * @function 4282 * @param {OpenSeadragon.MouseTracker.GesturePoint} gesturePoint - A gesture point to add to the list. 4283 * @returns {Number} Number of gesture points in the list. 4284 */ 4285 add: function ( gp ) { 4286 return this._gPoints.push( gp ); 4287 }, 4288 /** 4289 * @function 4290 * @param {Number} id - The id of the gesture point to remove from the list. 4291 * @returns {Number} Number of gesture points in the list. 4292 */ 4293 removeById: function ( id ) { 4294 var i, 4295 len = this._gPoints.length; 4296 for ( i = 0; i < len; i++ ) { 4297 if ( this._gPoints[ i ].id === id ) { 4298 this._gPoints.splice( i, 1 ); 4299 break; 4300 } 4301 } 4302 return this._gPoints.length; 4303 }, 4304 /** 4305 * @function 4306 * @param {Number} index - The index of the gesture point to retrieve from the list. 4307 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point at the given index, or null if not found. 4308 */ 4309 getByIndex: function ( index ) { 4310 if ( index < this._gPoints.length) { 4311 return this._gPoints[ index ]; 4312 } 4313 4314 return null; 4315 }, 4316 /** 4317 * @function 4318 * @param {Number} id - The id of the gesture point to retrieve from the list. 4319 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point with the given id, or null if not found. 4320 */ 4321 getById: function ( id ) { 4322 var i, 4323 len = this._gPoints.length; 4324 for ( i = 0; i < len; i++ ) { 4325 if ( this._gPoints[ i ].id === id ) { 4326 return this._gPoints[ i ]; 4327 } 4328 } 4329 return null; 4330 }, 4331 /** 4332 * @function 4333 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The primary gesture point in the list, or null if not found. 4334 */ 4335 getPrimary: function ( id ) { 4336 var i, 4337 len = this._gPoints.length; 4338 for ( i = 0; i < len; i++ ) { 4339 if ( this._gPoints[ i ].isPrimary ) { 4340 return this._gPoints[ i ]; 4341 } 4342 } 4343 return null; 4344 }, 4345 4346 /** 4347 * Increment this pointer's contact count. 4348 * It will evaluate whether this pointer type is allowed to have multiple contacts. 4349 * @function 4350 */ 4351 addContact: function() { 4352 ++this.contacts; 4353 4354 if (this.contacts >
4354 1 && (this.type === "mouse" || this.type === "pen")) { 4355 this.contacts = 1; 4356 } 4357 }, 4358 4359 /** 4360 * Decrement this pointer's contact count. 4361 * It will make sure the count does not go below 0. 4362 * @function 4363 */ 4364 removeContact: function() { 4365 --this.contacts; 4366 4367 if (this.contacts < 0) { 4368 this.contacts = 0; 4369 } 4370 } 4371 }; 4372 4373 4374/////////////////////////////////////////////////////////////////////////////// 4375// Utility functions 4376/////////////////////////////////////////////////////////////////////////////// 4377 4378 /** 4379 * Removes all tracked pointers. 4380 * @private 4381 * @inner 4382 */ 4383 function clearTrackedPointers( tracker ) { 4384 var delegate = THIS[ tracker.hash ], 4385 i, 4386 pointerListCount = delegate.activePointersLists.length; 4387 4388 for ( i = 0; i < pointerListCount; i++ ) { 4389 if ( delegate.activePointersLists[ i ].captureCount > 0 ) { 4390 $.removeEvent( 4391 $.MouseTracker.captureElement, 4392 'mousemove', 4393 delegate.mousemovecaptured, 4394 true 4395 ); 4396 $.removeEvent( 4397 $.MouseTracker.captureElement, 4398 'mouseup', 4399 delegate.mouseupcaptured, 4400 true 4401 ); 4402 $.removeEvent( 4403 $.MouseTracker.captureElement, 4404 $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', 4405 delegate.pointermovecaptured, 4406 true 4407 ); 4408 $.removeEvent( 4409 $.MouseTracker.captureElement, 4410 $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', 4411 delegate.pointerupcaptured, 4412 true 4413 ); 4414 $.removeEvent( 4415 $.MouseTracker.captureElement, 4416 'touchmove', 4417 delegate.touchmovecaptured, 4418 true 4419 ); 4420 $.removeEvent( 4421 $.MouseTracker.captureElement, 4422 'touchend', 4423 delegate.touchendcaptured, 4424 true 4425 ); 4426 4427 delegate.activePointersLists[ i ].captureCount = 0; 4428 } 4429 } 4430 4431 for ( i = 0; i < pointerListCount; i++ ) { 4432 delegate.activePointersLists.pop(); 4433 } 4434 } 4435 4436 /** 4437 * Starts tracking pointer events on the tracked element. 4438 * @private 4439 * @inner 4440 */ 4441 function startTracking( tracker ) { 4442 var delegate = THIS[ tracker.hash ], 4443 event, 4444 i; 4445 4446 if ( !delegate.tracking ) { 4447 for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { 4448 event = $.MouseTracker.subscribeEvents[ i ]; 4449 $.addEvent( 4450 tracker.element, 4451 event, 4452 delegate[ event ], 4453 false 4454 ); 4455 } 4456 4457 clearTrackedPointers( tracker ); 4458 4459 delegate.tracking = true; 4460 } 4461 } 4462 4463 /** 4464 * Stops tracking pointer events on the tracked element. 4465 * @private 4466 * @inner 4467 */ 4468 function stopTracking( tracker ) { 4469 var delegate = THIS[ tracker.hash ], 4470 event, 4471 i; 4472 4473 if ( delegate.tracking ) { 4474 for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { 4475 event = $.MouseTracker.subscribeEvents[ i ]; 4476 $.removeEvent( 4477 tracker.element, 4478 event, 4479 delegate[ event ], 4480 false 4481 ); 4482 } 4483 4484 clearTrackedPointers( tracker ); 4485 4486 delegate.tracking = false; 4487 } 4488 } 4489 4490 /** 4491 * @private 4492 * @inner 4493 */ 4494 function getCaptureEventParams( tracker, pointerType ) { 4495 var delegate = THIS[ tracker.hash ]; 4496 4497 if ( pointerType === 'pointerevent' ) { 4498 return { 4499 upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', 4500 upHandler: delegate.pointerupcaptured, 4501 moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', 4502 moveHandler: delegate.pointermovecaptured 4503 }; 4504 } else if ( pointerType === 'mouse' ) { 4505 return { 4506 upName: 'mouseup', 4507 upHandler: delegate.mouseupcaptured, 4508 moveName: 'mousemove', 4509 moveHandler: delegate.mousemovecaptured 4510 }; 4511 } else if ( pointerType === 'touch' ) { 4512 return { 4513 upName: 'touchend', 4514 upHandler: delegate.touchendcaptured, 4515 moveName: 'touchmove', 4516 moveHandler: delegate.touchmovecaptured 4517 }; 4518 } else {
4519 throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." ); 4520 } 4521 } 4522 4523 /** 4524 * Begin capturing pointer events to the tracked element. 4525 * @private 4526 * @inner 4527 */ 4528 function capturePointer( tracker, pointerType, pointerCount ) { 4529 var pointsList = tracker.getActivePointersListByType( pointerType ), 4530 eventParams; 4531 4532 pointsList.captureCount += (pointerCount || 1); 4533 4534 if ( pointsList.captureCount === 1 ) { 4535 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 4536 tracker.element.setCapture( true ); 4537 } else { 4538 eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); 4539 // We emulate mouse capture by hanging listeners on the document object. 4540 // (Note we listen on the capture phase so the captured handlers will get called first) 4541 // eslint-disable-next-line no-use-before-define 4542 if (isInIframe && canAccessEvents(window.top)) { 4543 $.addEvent( 4544 window.top, 4545 eventParams.upName, 4546 eventParams.upHandler, 4547 true 4548 ); 4549 } 4550 $.addEvent( 4551 $.MouseTracker.captureElement, 4552 eventParams.upName, 4553 eventParams.upHandler, 4554 true 4555 ); 4556 $.addEvent( 4557 $.MouseTracker.captureElement, 4558 eventParams.moveName, 4559 eventParams.moveHandler, 4560 true 4561 ); 4562 } 4563 } 4564 } 4565 4566 4567 /** 4568 * Stop capturing pointer events to the tracked element. 4569 * @private 4570 * @inner 4571 */ 4572 function releasePointer( tracker, pointerType, pointerCount ) { 4573 var pointsList = tracker.getActivePointersListByType( pointerType ), 4574 eventParams; 4575 4576 pointsList.captureCount -= (pointerCount || 1); 4577 4578 if ( pointsList.captureCount === 0 ) { 4579 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 4580 tracker.element.releaseCapture(); 4581 } else { 4582 eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType ); 4583 // We emulate mouse capture by hanging listeners on the document object. 4584 // (Note we listen on the capture phase so the captured handlers will get called first) 4585 // eslint-disable-next-line no-use-before-define 4586 if (isInIframe && canAccessEvents(window.top)) { 4587 $.removeEvent( 4588 window.top, 4589 eventParams.upName, 4590 eventParams.upHandler, 4591 true 4592 ); 4593 } 4594 $.removeEvent( 4595 $.MouseTracker.captureElement, 4596 eventParams.moveName, 4597 eventParams.moveHandler, 4598 true 4599 ); 4600 $.removeEvent( 4601 $.MouseTracker.captureElement, 4602 eventParams.upName, 4603 eventParams.upHandler, 4604 true 4605 ); 4606 } 4607 } 4608 } 4609 4610 4611 /** 4612 * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event. 4613 * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc. 4614 * @private 4615 * @inner 4616 */ 4617 function getPointerType( event ) { 4618 var pointerTypeStr; 4619 if ( $.MouseTracker.unprefixedPointerEvents ) { 4620 pointerTypeStr = event.pointerType; 4621 } else { 4622 // IE10 4623 // MSPOINTER_TYPE_TOUCH: 0x00000002 4624 // MSPOINTER_TYPE_PEN: 0x00000003 4625 // MSPOINTER_TYPE_MOUSE: 0x00000004 4626 switch( event.pointerType ) 4627 { 4628 case 0x00000002: 4629 pointerTypeStr = 'touch'; 4630 break; 4631 case 0x00000003: 4632 pointerTypeStr = 'pen'; 4633 break; 4634 case 0x00000004: 4635 pointerTypeStr = 'mouse'; 4636 break; 4637 default: 4638 pointerTypeStr = ''; 4639 } 4640 } 4641 return pointerTypeStr; 4642 } 4643 4644 4645 /** 4646 * @private 4647 * @inner 4648 */ 4649 function getMouseAbsolute( event ) { 4650 return $.getMousePosition( event ); 4651 } 4652 4653 /** 4654 * @private 4655 * @inner 4656 */ 4657 function getMouseRelative( event, element ) { 4658 return getPointRelativeToAbsolute( getMouseAbsolute( event ), element ); 4659 } 4660 4661 /** 4662 * @private 4663 * @inner 4664 */ 4665 function getPointRelativeToAbsolute( point, element ) { 4666 var offset = $.getElementOffset( element ); 4667 return point.minus( offset ); 4668 } 4669 4670 /** 4671 * @private 4672 * @inner 4673 */ 4674 function getCenterPoint( point1, point2 ) { 4675 return new $.Point( ( point1.x + point2.x ) / 2, ( point1.y + point2.y ) / 2 ); 4676 } 4677 4678 4679/////////////////////////////////////////////////////////////////////////////// 4680// Device-specific DOM event handlers 4681/////////////////////////////////////////////////////////////////////////////// 4682 4683 /** 4684 * @private 4685 * @inner 4686 */ 4687 function onClick( tracker, event ) { 4688 if ( tracker.clickHandler ) { 4689 $.cancelEvent( event ); 4690 } 4691 } 4692 4693 4694 /** 4695 * @private 4696 * @inner 4697 */ 4698 function onDblClick( tracker, event ) { 4699 if ( tracker.dblClickHandler ) { 4700 $.cancelEvent( event ); 4701 } 4702 } 4703 4704 4705 /** 4706 * @private 4707 * @inner 4708 */ 4709 function onKeyDown( tracker, event ) { 4710 //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); 4711 var propagate; 4712 if ( tracker.keyDownHandler ) { 4713 event = $.getEvent( event ); 4714 propagate = tracker.keyDownHandler( 4715 { 4716 eventSource: tracker, 4717 keyCode: event.keyCode ? event.keyCode : event.charCode, 4718 ctrl: event.ctrlKey, 4719 shift: event.shiftKey, 4720 alt: event.altKey, 4721 meta: event.metaKey, 4722 originalEvent: event, 4723 preventDefaultAction: false, 4724 userData: tracker.userData 4725 } 4726 ); 4727 if ( !propagate ) { 4728 $.cancelEvent( event ); 4729 } 4730 } 4731 } 4732 4733 4734 /** 4735 * @private 4736 * @inner 4737 */ 4738 function onKeyUp( tracker, event ) { 4739 //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); 4740 var propagate; 4741 if ( tracker.keyUpHandler ) { 4742 event = $.getEvent( event ); 4743 propagate = tracker.keyUpHandler( 4744 { 4745 eventSource: tracker, 4746 keyCode: event.keyCode ? event.keyCode : event.charCode, 4747 ctrl: event.ctrlKey, 4748 shift: event.shiftKey, 4749 alt: event.altKey, 4750 meta: event.metaKey, 4751 originalEvent: event, 4752 preventDefaultAction: false, 4753 userData: tracker.userData 4754 } 4755 ); 4756 if ( !propagate ) { 4757 $.cancelEvent( event ); 4758 } 4759 } 4760 } 4761 4762 4763 /** 4764 * @private 4765 * @inner 4766 */ 4767 function onKeyPress( tracker, event ) { 4768 //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); 4769 var propagate; 4770 if ( tracker.keyHandler ) { 4771 event = $.getEvent( event ); 4772 propagate = tracker.keyHandler( 4773 { 4774 eventSource: tracker, 4775 keyCode: event.keyCode ? event.keyCode : event.charCode, 4776 ctrl: event.ctrlKey, 4777 shift: event.shiftKey, 4778 alt: event.altKey, 4779 meta: event.metaKey, 4780 originalEvent: event, 4781 preventDefaultAction: false, 4782 userData: tracker.userData 4783 } 4784 ); 4785 if ( !propagate ) { 4786 $.cancelEvent( event ); 4787 } 4788 } 4789 } 4790 4791 4792 /** 4793 * @private 4794 * @inner 4795 */ 4796 function onFocus( tracker, event ) { 4797 //console.log( "focus %s", event ); 4798 var propagate; 4799 if ( tracker.focusHandler ) { 4800 event = $.getEvent( event ); 4801 propagate = tracker.focusHandler( 4802 { 4803 eventSource: tracker, 4804 originalEvent: event, 4805 preventDefaultAction: false, 4806 userData: tracker.userData 4807 } 4808 ); 4809 if ( propagate === false ) { 4810 $.cancelEvent( event ); 4811 } 4812 } 4813 } 4814 4815 4816 /** 4817 * @private 4818 * @inner 4819 */ 4820 function onBlur( tracker, event ) { 4821 //console.log( "blur %s", event ); 4822 var propagate; 4823 if ( tracker.blurHandler ) { 4824 event = $.getEvent( event ); 4825 propagate = tracker.blurHandler( 4826 { 4827 eventSource: tracker, 4828 originalEvent: event, 4829 preventDefaultAction: false, 4830 userData: tracker.userData 4831 } 4832 ); 4833 if ( propagate === false ) { 4834 $.cancelEvent( event ); 4835 } 4836 } 4837 } 4838 4839 4840 /** 4841 * Handler for 'wheel' events 4842 * 4843 * @private 4844 * @inner 4845 */ 4846 function onWheel( tracker, event ) { 4847 handleWheelEvent( tracker, event, event ); 4848 } 4849 4850 4851 /** 4852 * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events 4853 * 4854 * @private 4855 * @inner 4856 */ 4857 function onMouseWheel( tracker, event ) { 4858 event = $.getEvent( event ); 4859 4860 // Simulate a 'wheel' event 4861 var simulatedEvent = { 4862 target: event.target || event.srcElement, 4863 type: "wheel",
4864 shiftKey: event.shiftKey || false, 4865 clientX: event.clientX, 4866 clientY: event.clientY, 4867 pageX: event.pageX ? event.pageX : event.clientX, 4868 pageY: event.pageY ? event.pageY : event.clientY, 4869 deltaMode: event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page 4870 deltaX: 0, 4871 deltaZ: 0 4872 }; 4873 4874 // Calculate deltaY 4875 if ( $.MouseTracker.wheelEventName == "mousewheel" ) { 4876 simulatedEvent.deltaY = -event.wheelDelta / $.DEFAULT_SETTINGS.pixelsPerWheelLine; 4877 } else { 4878 simulatedEvent.deltaY = event.detail; 4879 } 4880 4881 handleWheelEvent( tracker, simulatedEvent, event ); 4882 } 4883 4884 4885 /** 4886 * Handles 'wheel' events. 4887 * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()). 4888 * 4889 * @private 4890 * @inner 4891 */ 4892 function handleWheelEvent( tracker, event, originalEvent ) { 4893 var nDelta = 0, 4894 propagate; 4895 4896 // The nDelta variable is gated to provide smooth z-index scrolling 4897 // since the mouse wheel allows for substantial deltas meant for rapid 4898 // y-index scrolling. 4899 // event.deltaMode: 0=pixel, 1=line, 2=page 4900 // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached 4901 nDelta = event.deltaY < 0 ? 1 : -1; 4902 4903 if ( tracker.scrollHandler ) { 4904 propagate = tracker.scrollHandler( 4905 { 4906 eventSource: tracker, 4907 pointerType: 'mouse', 4908 position: getMouseRelative( event, tracker.element ), 4909 scroll: nDelta, 4910 shift: event.shiftKey, 4911 isTouchEvent: false, 4912 originalEvent: originalEvent, 4913 preventDefaultAction: false, 4914 userData: tracker.userData 4915 } 4916 ); 4917 if ( propagate === false ) { 4918 $.cancelEvent( originalEvent ); 4919 } 4920 } 4921 } 4922 4923 4924 /** 4925 * @private 4926 * @inner 4927 */ 4928 function isParentChild( parent, child ) 4929 { 4930 if ( parent === child ) { 4931 return false; 4932 } 4933 while ( child && child !== parent ) { 4934 child = child.parentNode; 4935 } 4936 return child === parent; 4937 } 4938 4939 4940 /** 4941 * Only used on IE 8 4942 * 4943 * @private 4944 * @inner 4945 */ 4946 function onMouseEnter( tracker, event ) { 4947 event = $.getEvent( event ); 4948 4949 handleMouseEnter( tracker, event ); 4950 } 4951 4952 4953 /** 4954 * @private 4955 * @inner 4956 */ 4957 function onMouseOver( tracker, event ) { 4958 event = $.getEvent( event ); 4959 4960 if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { 4961 return; 4962 } 4963 4964 handleMouseEnter( tracker, event ); 4965 } 4966 4967 4968 /** 4969 * @private 4970 * @inner 4971 */ 4972 function handleMouseEnter( tracker, event ) { 4973 var gPoint = { 4974 id: $.MouseTracker.mousePointerId, 4975 type: 'mouse', 4976 isPrimary: true, 4977 currentPos: getMouseAbsolute( event ), 4978 currentTime: $.now() 4979 }; 4980 4981 updatePointersEnter( tracker, event, [ gPoint ] ); 4982 } 4983 4984 4985 /** 4986 * Only used on IE 8 4987 * 4988 * @private 4989 * @inner 4990 */ 4991 function onMouseLeave( tracker, event ) { 4992 event = $.getEvent( event ); 4993 4994 handleMouseExit( tracker, event ); 4995 } 4996 4997 4998 /** 4999 * @private 5000 * @inner 5001 */ 5002 function onMouseOut( tracker, event ) { 5003 event = $.getEvent( event ); 5004 5005 if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { 5006 return; 5007 } 5008 5009 handleMouseExit( tracker, event ); 5010 } 5011 5012 5013 /** 5014 * @private 5015 * @inner 5016 */ 5017 function handleMouseExit( tracker, event ) { 5018 var gPoint = { 5019 id: $.MouseTracker.mousePointerId, 5020 type: 'mouse', 5021 isPrimary: true, 5022 currentPos: getMouseAbsolute( event ), 5023 currentTime: $.now() 5024 }; 5025 5026 updatePointersExit( tracker, event, [ gPoint ] ); 5027 } 5028 5029 5030 /** 5031 * Returns a W3C DOM level 3 standard button value given an event.button property: 5032 * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen) 5033 * @private 5034 * @inner 5035 */ 5036 function getStandardizedButton( button ) { 5037 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 5038 // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three
5039 // TODO: Support chorded (multiple) button presses on IE 8? 5040 if ( button === 1 ) { 5041 return 0; 5042 } else if ( button === 2 ) { 5043 return 2; 5044 } else if ( button === 4 ) { 5045 return 1; 5046 } else { 5047 return -1; 5048 } 5049 } else { 5050 return button; 5051 } 5052 } 5053 5054 5055 /** 5056 * @private 5057 * @inner 5058 */ 5059 function onMouseDown( tracker, event ) { 5060 var gPoint; 5061 5062 event = $.getEvent( event ); 5063 5064 gPoint = { 5065 id: $.MouseTracker.mousePointerId, 5066 type: 'mouse', 5067 isPrimary: true, 5068 currentPos: getMouseAbsolute( event ), 5069 currentTime: $.now() 5070 }; 5071 5072 if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { 5073 $.stopEvent( event ); 5074 capturePointer( tracker, 'mouse' ); 5075 } 5076 5077 if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { 5078 $.cancelEvent( event ); 5079 } 5080 } 5081 5082 5083 /** 5084 * @private 5085 * @inner 5086 */ 5087 function onMouseUp( tracker, event ) { 5088 handleMouseUp( tracker, event ); 5089 } 5090 5091 /** 5092 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 5093 * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice. 5094 * 5095 * @private 5096 * @inner 5097 */ 5098 function onMouseUpCaptured( tracker, event ) { 5099 handleMouseUp( tracker, event ); 5100 $.stopEvent( event ); 5101 } 5102 5103 5104 /** 5105 * @private 5106 * @inner 5107 */ 5108 function handleMouseUp( tracker, event ) { 5109 var gPoint; 5110 5111 event = $.getEvent( event ); 5112 5113 gPoint = { 5114 id: $.MouseTracker.mousePointerId, 5115 type: 'mouse', 5116 isPrimary: true, 5117 currentPos: getMouseAbsolute( event ), 5118 currentTime: $.now() 5119 }; 5120 5121 if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) { 5122 releasePointer( tracker, 'mouse' ); 5123 } 5124 } 5125 5126 5127 /** 5128 * @private 5129 * @inner 5130 */ 5131 function onMouseMove( tracker, event ) { 5132 handleMouseMove( tracker, event ); 5133 } 5134 5135 5136 /** 5137 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 5138 * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice. 5139 * 5140 * @private 5141 * @inner 5142 */ 5143 function onMouseMoveCaptured( tracker, event ) { 5144 handleMouseMove( tracker, event ); 5145 $.stopEvent( event ); 5146 } 5147 5148 5149 /** 5150 * @private 5151 * @inner 5152 */ 5153 function handleMouseMove( tracker, event ) { 5154 var gPoint; 5155 5156 event = $.getEvent( event ); 5157 5158 gPoint = { 5159 id: $.MouseTracker.mousePointerId, 5160 type: 'mouse', 5161 isPrimary: true, 5162 currentPos: getMouseAbsolute( event ), 5163 currentTime: $.now() 5164 }; 5165 5166 updatePointersMove( tracker, event, [ gPoint ] ); 5167 } 5168 5169 5170 /** 5171 * @private 5172 * @inner 5173 */ 5174 function abortContacts( tracker, event, pointsList ) { 5175 var i, 5176 gPointCount = pointsList.getLength(), 5177 abortGPoints = []; 5178 5179 // Check contact count for hoverable pointer types before aborting 5180 if (pointsList.type === 'touch' || pointsList.contacts > 0) { 5181 for ( i = 0; i < gPointCount; i++ ) { 5182 abortGPoints.push( pointsList.getByIndex( i ) ); 5183 } 5184 5185 if ( abortGPoints.length > 0 ) { 5186 // simulate touchend/mouseup 5187 updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact 5188 // release pointer capture 5189 pointsList.captureCount = 1; 5190 releasePointer( tracker, pointsList.type ); 5191 // simulate touchleave/mouseout 5192 updatePointersExit( tracker, event, abortGPoints ); 5193 } 5194 } 5195 } 5196 5197 5198 /** 5199 * @private 5200 * @inner 5201 */ 5202 function onTouchStart( tracker, event ) { 5203 var time, 5204 i, 5205 j, 5206 touchCount = event.changedTouches.length, 5207 gPoints = [], 5208 parentGPoints, 5209 pointsList = tracker.getActivePointersListByType( 'touch' ); 5210 5211 time = $.now(); 5212 5213 if ( pointsList.getLength() > event.touches.length - touchCount ) { 5214 $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); 5215 abortContacts( tracker, event, pointsList ); 5216 } 5217 5218 for ( i = 0; i < touchCount; i++ ) { 5219 gPoints.push( { 5220 id: event.changedTouches[ i ].identifier, 5221 type: 'touch', 5222 // isPrimary not set - let the updatePointers functions determine it 5223 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 5224 currentTime: time 5225 } ); 5226 } 5227 5228 // simulate touchenter on our tracked element 5229 updatePointersEnter( tracker, event, gPoints ); 5230 5231 // simulate touchenter on our tracked element's tracked ancestor elements 5232 for ( i = 0; i < MOUSETRACKERS.length; i++ ) { 5233 if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { 5234 parentGPoints = []; 5235 for ( j = 0; j < touchCount; j++ ) { 5236 parentGPoints.push( { 5237 id: event.changedTouches[ j ].identifier, 5238 type: 'touch', 5239 // isPrimary not set - let the updatePointers functions determine it 5240 currentPos: getMouseAbsolute( event.changedTouches[ j ] ), 5241 currentTime: time 5242 } ); 5243 } 5244 updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints ); 5245 } 5246 } 5247 5248 if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact 5249 $.stopEvent( event ); 5250 capturePointer( tracker, 'touch', touchCount ); 5251 } 5252 5253 $.cancelEvent( event ); 5254 } 5255 5256 5257 /** 5258 * @private 5259 * @inner 5260 */ 5261 function onTouchEnd( tracker, event ) { 5262 handleTouchEnd( tracker, event ); 5263 } 5264 5265 5266 /** 5267 * This handler is attached to the window object (on the capture phase) to emulate pointer capture. 5268 * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice. 5269 * 5270 * @private 5271 * @inner 5272 */ 5273 function onTouchEndCaptured( tracker, event ) { 5274 handleTouchEnd( tracker, event ); 5275 $.stopEvent( event ); 5276 } 5277 5278 5279 /** 5280 * @private 5281 * @inner 5282 */ 5283 function handleTouchEnd( tracker, event ) { 5284 var time, 5285 i, 5286 j, 5287 touchCount = event.changedTouches.length, 5288 gPoints = [], 5289 parentGPoints; 5290 5291 time = $.now(); 5292 5293 for ( i = 0; i < touchCount; i++ ) { 5294 gPoints.push( { 5295 id: event.changedTouches[ i ].identifier, 5296 type: 'touch', 5297 // isPrimary not set - let the updatePointers functions determine it 5298 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 5299 currentTime: time 5300 } ); 5301 } 5302 5303 if ( updatePointersUp( tracker, event, gPoints, 0 ) ) { 5304 releasePointer( tracker, 'touch', touchCount ); 5305 } 5306 5307 // simulate touchleave on our tracked element 5308 updatePointersExit( tracker, event, gPoints ); 5309 5310 // simulate touchleave on our tracked element's tracked ancestor elements 5311 for ( i = 0; i < MOUSETRACKERS.length; i++ ) { 5312 if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { 5313 parentGPoints = []; 5314 for ( j = 0; j < touchCount; j++ ) { 5315 parentGPoints.push( { 5316 id: event.changedTouches[ j ].identifier, 5317 type: 'touch', 5318 // isPrimary not set - let the updatePointers functions determine it 5319 currentPos: getMouseAbsolute( event.changedTouches[ j ] ), 5320 currentTime: time 5321 } ); 5322 } 5323 updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints ); 5324 } 5325 } 5326 5327 $.cancelEvent( event ); 5328 } 5329 5330 5331 /** 5332 * @private 5333 * @inner 5334 */ 5335 function onTouchMove( tracker, event ) { 5336 handleTouchMove( tracker, event ); 5337 } 5338 5339 5340 /** 5341 * This handler is attached to the window object (on the capture phase) to emulate pointer capture. 5342 * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice. 5343 * 5344 * @private 5345 * @inner 5346 */ 5347 function onTouchMoveCaptured( tracker, event ) { 5348 handleTouchMove( tracker, event ); 5349 $.stopEvent( event ); 5350 } 5351 5352 5353 /** 5354 * @private 5355 * @inner 5356 */ 5357 function handleTouchMove( tracker, event ) { 5358 var i, 5359 touchCount = event.changedTouches.length, 5360 gPoints = []; 5361 5362 for ( i = 0; i < touchCount; i++ ) { 5363 gPoints.push( { 5364 id: event.changedTouches[ i ].identifier, 5365 type: 'touch', 5366 // isPrimary not set - let the updatePointers functions determine it 5367 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 5368 currentTime: $.now() 5369 } ); 5370 } 5371 5372 updatePointersMove( tracker, event, gPoints ); 5373 5374 $.cancelEvent( event ); 5375 } 5376 5377 5378 /** 5379 * @private 5380 * @inner 5381 */ 5382 function onTouchCancel( tracker, event ) { 5383 var pointsList = tracker.getActivePointersListByType('touch'); 5384 5385 abortContacts( tracker, event, pointsList ); 5386 } 5387 5388 5389 /** 5390 * @private 5391 * @inner 5392 */ 5393 function onGestureStart( tracker, event ) { 5394 event.stopPropagation(); 5395 event.preventDefault(); 5396 return false;
5397 } 5398 5399 5400 /** 5401 * @private 5402 * @inner 5403 */ 5404 function onGestureChange( tracker, event ) { 5405 event.stopPropagation(); 5406 event.preventDefault(); 5407 return false; 5408 } 5409 5410 5411 /** 5412 * @private 5413 * @inner 5414 */ 5415 function onPointerOver( tracker, event ) { 5416 var gPoint; 5417 5418 if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { 5419 return; 5420 } 5421 5422 gPoint = { 5423 id: event.pointerId, 5424 type: getPointerType( event ), 5425 isPrimary: event.isPrimary, 5426 currentPos: getMouseAbsolute( event ), 5427 currentTime: $.now() 5428 }; 5429 5430 updatePointersEnter( tracker, event, [ gPoint ] ); 5431 } 5432 5433 5434 /** 5435 * @private 5436 * @inner 5437 */ 5438 function onPointerOut( tracker, event ) { 5439 var gPoint; 5440 5441 if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { 5442 return; 5443 } 5444 5445 gPoint = { 5446 id: event.pointerId, 5447 type: getPointerType( event ), 5448 isPrimary: event.isPrimary, 5449 currentPos: getMouseAbsolute( event ), 5450 currentTime: $.now() 5451 }; 5452 5453 updatePointersExit( tracker, event, [ gPoint ] ); 5454 } 5455 5456 5457 /** 5458 * @private 5459 * @inner 5460 */ 5461 function onPointerDown( tracker, event ) { 5462 var gPoint; 5463 5464 gPoint = { 5465 id: event.pointerId, 5466 type: getPointerType( event ), 5467 isPrimary: event.isPrimary, 5468 currentPos: getMouseAbsolute( event ), 5469 currentTime: $.now() 5470 }; 5471 5472 if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { 5473 $.stopEvent( event ); 5474 capturePointer( tracker, gPoint.type ); 5475 } 5476 5477 if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 5478 $.cancelEvent( event ); 5479 } 5480 } 5481 5482 5483 /** 5484 * @private 5485 * @inner 5486 */ 5487 function onPointerUp( tracker, event ) { 5488 handlePointerUp( tracker, event ); 5489 } 5490 5491 5492 /** 5493 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 5494 * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice. 5495 * 5496 * @private 5497 * @inner 5498 */ 5499 function onPointerUpCaptured( tracker, event ) { 5500 var pointsList = tracker.getActivePointersListByType( getPointerType( event ) ); 5501 if ( pointsList.getById( event.pointerId ) ) { 5502 handlePointerUp( tracker, event ); 5503 } 5504 $.stopEvent( event ); 5505 } 5506 5507 5508 /** 5509 * @private 5510 * @inner 5511 */ 5512 function handlePointerUp( tracker, event ) { 5513 var gPoint; 5514 5515 gPoint = { 5516 id: event.pointerId, 5517 type: getPointerType( event ), 5518 isPrimary: event.isPrimary, 5519 currentPos: getMouseAbsolute( event ), 5520 currentTime: $.now() 5521 }; 5522 5523 if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { 5524 releasePointer( tracker, gPoint.type ); 5525 } 5526 } 5527 5528 5529 /** 5530 * @private 5531 * @inner 5532 */ 5533 function onPointerMove( tracker, event ) { 5534 handlePointerMove( tracker, event ); 5535 } 5536 5537 5538 /** 5539 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 5540 * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice. 5541 * 5542 * @private 5543 * @inner 5544 */ 5545 function onPointerMoveCaptured( tracker, event ) { 5546 var pointsList = tracker.getActivePointersListByType( getPointerType( event ) ); 5547 if ( pointsList.getById( event.pointerId ) ) { 5548 handlePointerMove( tracker, event ); 5549 } 5550 $.stopEvent( event ); 5551 } 5552 5553 5554 /** 5555 * @private 5556 * @inner 5557 */ 5558 function handlePointerMove( tracker, event ) { 5559 // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) 5560 var gPoint; 5561 5562 gPoint = { 5563 id: event.pointerId, 5564 type: getPointerType( event ), 5565 isPrimary: event.isPrimary, 5566 currentPos: getMouseAbsolute( event ), 5567 currentTime: $.now() 5568 }; 5569 5570 updatePointersMove( tracker, event, [ gPoint ] ); 5571 } 5572 5573 5574 /** 5575 * @private 5576 * @inner 5577 */ 5578 function onPointerCancel( tracker, event ) { 5579 var gPoint; 5580 5581 gPoint = { 5582 id: event.pointerId, 5583 type: getPointerType( event ) 5584 }; 5585 5586 updatePointersCancel( tracker, event, [ gPoint ] ); 5587 } 5588 5589 5590/////////////////////////////////////////////////////////////////////////////// 5591// Device-agnostic DOM event handlers 5592/////////////////////////////////////////////////////////////////////////////// 5593 5594 /** 5595 * @function 5596 * @private 5597 * @inner 5598 * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList 5599 * The GesturePointList to track the pointer in. 5600 * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint 5601 * Gesture point to track. 5602 * @returns {Number} Number of gesture points in pointsList. 5603 */ 5604 function startTrackingPointer( pointsList, gPoint ) { 5605 5606 // If isPrimary is not known for the pointer then set it according to our rules: 5607 // true if the first pointer in the gesture, otherwise false 5608 if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { 5609 if ( pointsList.getLength() === 0 ) { 5610 gPoint.isPrimary = true; 5611 } else { 5612 gPoint.isPrimary = false;
5613 } 5614 } 5615 gPoint.speed = 0; 5616 gPoint.direction = 0; 5617 gPoint.contactPos = gPoint.currentPos; 5618 gPoint.contactTime = gPoint.currentTime; 5619 gPoint.lastPos = gPoint.currentPos; 5620 gPoint.lastTime = gPoint.currentTime; 5621 5622 return pointsList.add( gPoint ); 5623 } 5624 5625 5626 /** 5627 * @function 5628 * @private 5629 * @inner 5630 * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList 5631 * The GesturePointList to stop tracking the pointer on. 5632 * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint 5633 * Gesture point to stop tracking. 5634 * @returns {Number} Number of gesture points in pointsList. 5635 */ 5636 function stopTrackingPointer( pointsList, gPoint ) { 5637 var listLength, 5638 primaryPoint; 5639 5640 if ( pointsList.getById( gPoint.id ) ) { 5641 listLength = pointsList.removeById( gPoint.id ); 5642 5643 // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary 5644 if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) { 5645 primaryPoint = pointsList.getPrimary(); 5646 if ( !primaryPoint ) { 5647 primaryPoint = pointsList.getByIndex( 0 ); 5648 if ( primaryPoint ) { 5649 primaryPoint.isPrimary = true; 5650 } 5651 } 5652 } 5653 } else { 5654 listLength = pointsList.getLength(); 5655 } 5656 5657 return listLength; 5658 } 5659 5660 5661 /** 5662 * @function 5663 * @private 5664 * @inner 5665 * @param {OpenSeadragon.MouseTracker} tracker 5666 * A reference to the MouseTracker instance. 5667 * @param {Object} event 5668 * A reference to the originating DOM event. 5669 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5670 * Gesture points associated with the event. 5671 */ 5672 function updatePointersEnter( tracker, event, gPoints ) { 5673 var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 5674 i, 5675 gPointCount = gPoints.length, 5676 curGPoint, 5677 updateGPoint, 5678 propagate; 5679 5680 for ( i = 0; i < gPointCount; i++ ) { 5681 curGPoint = gPoints[ i ]; 5682 updateGPoint = pointsList.getById( curGPoint.id ); 5683 5684 if ( updateGPoint ) { 5685 // Already tracking the pointer...update it 5686 updateGPoint.insideElement = true; 5687 updateGPoint.lastPos = updateGPoint.currentPos; 5688 updateGPoint.lastTime = updateGPoint.currentTime; 5689 updateGPoint.currentPos = curGPoint.currentPos; 5690 updateGPoint.currentTime = curGPoint.currentTime; 5691 5692 curGPoint = updateGPoint; 5693 } else { 5694 // Initialize for tracking and add to the tracking list 5695 curGPoint.captured = false; 5696 curGPoint.insideElementPressed = false; 5697 curGPoint.insideElement = true; 5698 startTrackingPointer( pointsList, curGPoint ); 5699 } 5700 5701 // Enter 5702 if ( tracker.enterHandler ) { 5703 propagate = tracker.enterHandler( 5704 { 5705 eventSource: tracker, 5706 pointerType: curGPoint.type, 5707 position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), 5708 buttons: pointsList.buttons, 5709 pointers: tracker.getActivePointerCount(), 5710 insideElementPressed: curGPoint.insideElementPressed, 5711 buttonDownAny: pointsList.buttons !== 0, 5712 isTouchEvent: curGPoint.type === 'touch', 5713 originalEvent: event, 5714 preventDefaultAction: false, 5715 userData: tracker.userData 5716 } 5717 ); 5718 if ( propagate === false ) { 5719 $.cancelEvent( event ); 5720 } 5721 } 5722 } 5723 } 5724 5725 5726 /** 5727 * @function 5728 * @private 5729 * @inner 5730 * @param {OpenSeadragon.MouseTracker} tracker 5731 * A reference to the MouseTracker instance. 5732 * @param {Object} event 5733 * A reference to the originating DOM event. 5734 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5735 * Gesture points associated with the event. 5736 */ 5737 function updatePointersExit( tracker, event, gPoints ) { 5738 var pointsList = tracker.getActivePointersListByType(gPoints[0].type), 5739 i, 5740 gPointCount = gPoints.length, 5741 curGPoint, 5742 updateGPoint, 5743 propagate; 5744 5745 for ( i = 0; i < gPointCount; i++ ) { 5746 curGPoint = gPoints[ i ]; 5747 updateGPoint = pointsList.getById( curGPoint.id ); 5748 5749 if ( updateGPoint ) { 5750 // Already tracking the pointer. If captured then update it, else stop tracking it 5751 if ( updateGPoint.captured ) { 5752 updateGPoint.insideElement = false;
5753 updateGPoint.lastPos = updateGPoint.currentPos; 5754 updateGPoint.lastTime = updateGPoint.currentTime; 5755 updateGPoint.currentPos = curGPoint.currentPos; 5756 updateGPoint.currentTime = curGPoint.currentTime; 5757 } else { 5758 stopTrackingPointer( pointsList, updateGPoint ); 5759 } 5760 5761 curGPoint = updateGPoint; 5762 } 5763 5764 // Exit 5765 if ( tracker.exitHandler ) { 5766 propagate = tracker.exitHandler( 5767 { 5768 eventSource: tracker, 5769 pointerType: curGPoint.type, 5770 position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), 5771 buttons: pointsList.buttons, 5772 pointers: tracker.getActivePointerCount(), 5773 insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, 5774 buttonDownAny: pointsList.buttons !== 0, 5775 isTouchEvent: curGPoint.type === 'touch', 5776 originalEvent: event, 5777 preventDefaultAction: false, 5778 userData: tracker.userData 5779 } 5780 ); 5781 5782 if ( propagate === false ) { 5783 $.cancelEvent( event ); 5784 } 5785 } 5786 } 5787 } 5788 5789 5790 /** 5791 * @function 5792 * @private 5793 * @inner 5794 * @param {OpenSeadragon.MouseTracker} tracker 5795 * A reference to the MouseTracker instance. 5796 * @param {Object} event 5797 * A reference to the originating DOM event. 5798 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5799 * Gesture points associated with the event. 5800 * @param {Number} buttonChanged 5801 * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 5802 * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, 5803 * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. 5804 * 5805 * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false. 5806 */ 5807 function updatePointersDown( tracker, event, gPoints, buttonChanged ) { 5808 var delegate = THIS[ tracker.hash ], 5809 propagate, 5810 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 5811 i, 5812 gPointCount = gPoints.length, 5813 curGPoint, 5814 updateGPoint; 5815 5816 if ( typeof event.buttons !== 'undefined' ) { 5817 pointsList.buttons = event.buttons; 5818 } else { 5819 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 5820 if ( buttonChanged === 0 ) { 5821 // Primary 5822 pointsList.buttons += 1; 5823 } else if ( buttonChanged === 1 ) { 5824 // Aux 5825 pointsList.buttons += 4; 5826 } else if ( buttonChanged === 2 ) { 5827 // Secondary 5828 pointsList.buttons += 2; 5829 } else if ( buttonChanged === 3 ) { 5830 // X1 (Back) 5831 pointsList.buttons += 8; 5832 } else if ( buttonChanged === 4 ) { 5833 // X2 (Forward) 5834 pointsList.buttons += 16; 5835 } else if ( buttonChanged === 5 ) { 5836 // Pen Eraser 5837 pointsList.buttons += 32; 5838 } 5839 } else { 5840 if ( buttonChanged === 0 ) { 5841 // Primary 5842 pointsList.buttons |= 1; 5843 } else if ( buttonChanged === 1 ) { 5844 // Aux 5845 pointsList.buttons |= 4; 5846 } else if ( buttonChanged === 2 ) { 5847 // Secondary 5848 pointsList.buttons |= 2; 5849 } else if ( buttonChanged === 3 ) { 5850 // X1 (Back) 5851 pointsList.buttons |= 8; 5852 } else if ( buttonChanged === 4 ) { 5853 // X2 (Forward) 5854 pointsList.buttons |= 16; 5855 } else if ( buttonChanged === 5 ) { 5856 // Pen Eraser 5857 pointsList.buttons |= 32; 5858 } 5859 } 5860 } 5861 5862 // Some pointers may steal control from another pointer without firing the appropriate release events 5863 // e.g. Touching a screen while click-dragging with certain mice. 5864 var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type); 5865 for (i = 0; i < otherPointsLists.length; i++) { 5866 //If another pointer has contact, simulate the release 5867 abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer 5868 } 5869 5870 // Only capture and track primary button, pen, and touch contacts 5871 if ( buttonChanged !== 0 ) { 5872 // Aux Press 5873 if ( tracker.nonPrimaryPressHandler ) { 5874 propagate = tracker.nonPrimaryPressHandler( 5875 { 5876 eventSource: tracker, 5877 pointerType: gPoints[ 0 ].type, 5878 position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), 5879 button: buttonChanged, 5880 buttons: pointsList.buttons, 5881 isTouchEvent: gPoints[ 0 ].type === 'touch', 5882 originalEvent: event, 5883 preventDefaultAction: false, 5884 userData: tracker.userData 5885 } 5886 ); 5887 if ( propagate === false ) { 5888 $.cancelEvent( event ); 5889 } 5890 } 5891 5892 return false;
5893 } 5894 5895 for ( i = 0; i < gPointCount; i++ ) { 5896 curGPoint = gPoints[ i ]; 5897 updateGPoint = pointsList.getById( curGPoint.id ); 5898 5899 if ( updateGPoint ) { 5900 // Already tracking the pointer...update it 5901 updateGPoint.captured = true; 5902 updateGPoint.insideElementPressed = true; 5903 updateGPoint.insideElement = true; 5904 updateGPoint.contactPos = curGPoint.currentPos; 5905 updateGPoint.contactTime = curGPoint.currentTime; 5906 updateGPoint.lastPos = updateGPoint.currentPos; 5907 updateGPoint.lastTime = updateGPoint.currentTime; 5908 updateGPoint.currentPos = curGPoint.currentPos; 5909 updateGPoint.currentTime = curGPoint.currentTime; 5910 5911 curGPoint = updateGPoint; 5912 } else { 5913 // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) 5914 curGPoint.captured = true; 5915 curGPoint.insideElementPressed = true; 5916 curGPoint.insideElement = true; 5917 startTrackingPointer( pointsList, curGPoint ); 5918 } 5919 5920 pointsList.addContact(); 5921 //$.console.log('contacts++ ', pointsList.contacts); 5922 5923 if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 5924 $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint ); 5925 } 5926 5927 if ( pointsList.contacts === 1 ) { 5928 // Press 5929 if ( tracker.pressHandler ) { 5930 propagate = tracker.pressHandler( 5931 { 5932 eventSource: tracker, 5933 pointerType: curGPoint.type, 5934 position: getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ), 5935 buttons: pointsList.buttons, 5936 isTouchEvent: curGPoint.type === 'touch', 5937 originalEvent: event, 5938 preventDefaultAction: false, 5939 userData: tracker.userData 5940 } 5941 ); 5942 if ( propagate === false ) { 5943 $.cancelEvent( event ); 5944 } 5945 } 5946 } else if ( pointsList.contacts === 2 ) { 5947 if ( tracker.pinchHandler && curGPoint.type === 'touch' ) { 5948 // Initialize for pinch 5949 delegate.pinchGPoints = pointsList.asArray(); 5950 delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 5951 delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 5952 } 5953 } 5954 } 5955 5956 return true; 5957 } 5958 5959 5960 /** 5961 * @function 5962 * @private 5963 * @inner 5964 * @param {OpenSeadragon.MouseTracker} tracker 5965 * A reference to the MouseTracker instance. 5966 * @param {Object} event 5967 * A reference to the originating DOM event. 5968 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5969 * Gesture points associated with the event. 5970 * @param {Number} buttonChanged 5971 * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 5972 * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, 5973 * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. 5974 * 5975 * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false. 5976 */ 5977 function updatePointersUp( tracker, event, gPoints, buttonChanged ) { 5978 var delegate = THIS[ tracker.hash ], 5979 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 5980 propagate, 5981 releasePoint, 5982 releaseTime, 5983 i, 5984 gPointCount = gPoints.length, 5985 curGPoint, 5986 updateGPoint, 5987 releaseCapture = false, 5988 wasCaptured = false, 5989 quick; 5990 5991 if ( typeof event.buttons !== 'undefined' ) { 5992 pointsList.buttons = event.buttons; 5993 } else { 5994 if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) { 5995 if ( buttonChanged === 0 ) { 5996 // Primary 5997 pointsList.buttons -= 1; 5998 } else if ( buttonChanged === 1 ) { 5999 // Aux 6000 pointsList.buttons -= 4; 6001 } else if ( buttonChanged === 2 ) { 6002 // Secondary 6003 pointsList.buttons -= 2; 6004 } else if ( buttonChanged === 3 ) { 6005 // X1 (Back) 6006 pointsList.buttons -= 8; 6007 } else if ( buttonChanged === 4 ) { 6008 // X2 (Forward) 6009 pointsList.buttons -= 16; 6010 } else if ( buttonChanged === 5 ) { 6011 // Pen Eraser 6012 pointsList.buttons -= 32; 6013 } 6014 } else { 6015 if ( buttonChanged === 0 ) { 6016 // Primary 6017 pointsList.buttons ^= ~1; 6018 } else if ( buttonChanged === 1 ) { 6019 // Aux 6020 pointsList.buttons ^= ~4; 6021 } else if ( buttonChanged === 2 ) { 6022 // Secondary 6023 pointsList.buttons ^= ~2; 6024 } else if ( buttonChanged === 3 ) { 6025 // X1 (Back) 6026 pointsList.buttons ^= ~8; 6027 } else if ( buttonChanged === 4 ) { 6028 // X2 (Forward) 6029 pointsList.buttons ^= ~16; 6030 } else if ( buttonChanged === 5 ) { 6031 // Pen Eraser 6032 pointsList.buttons ^= ~32; 6033 } 6034 } 6035 } 6036 6037 // Only capture and track primary button, pen, and touch contacts 6038 if ( buttonChanged !== 0 ) { 6039 // Aux Release 6040 if ( tracker.nonPrimaryReleaseHandler ) { 6041 propagate = tracker.nonPrimaryReleaseHandler( 6042 { 6043 eventSource: tracker, 6044 pointerType: gPoints[ 0 ].type, 6045 position: getPointRelativeToAbsolute(gPoints[0].currentPos, tracker.element), 6046 button: buttonChanged, 6047 buttons: pointsList.buttons, 6048 isTouchEvent: gPoints[ 0 ].type === 'touch', 6049 originalEvent: event, 6050 preventDefaultAction: false, 6051 userData: tracker.userData 6052 } 6053 ); 6054 if ( propagate === false ) { 6055 $.cancelEvent( event ); 6056 } 6057 } 6058 6059 // A primary mouse button may have been released while the non-primary button was down 6060 var otherPointsList = tracker.getActivePointersListByType("mouse"); 6061 // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223 6062 abortContacts(tracker, event, otherPointsList); // No-op if no active pointer 6063 6064 return false;
6065 } 6066 6067 // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13) 6068 if (typeof gPoints[ 0 ].currentPos === "undefined") { 6069 abortContacts(tracker, event, pointsList); 6070 6071 return false; 6072 } 6073 6074 for ( i = 0; i < gPointCount; i++ ) { 6075 curGPoint = gPoints[ i ]; 6076 updateGPoint = pointsList.getById( curGPoint.id ); 6077 if ( updateGPoint ) { 6078 // Update the pointer, stop tracking it if not still in this element 6079 if ( updateGPoint.captured ) { 6080 updateGPoint.captured = false; 6081 releaseCapture = true; 6082 wasCaptured = true; 6083 } 6084 updateGPoint.lastPos = updateGPoint.currentPos; 6085 updateGPoint.lastTime = updateGPoint.currentTime; 6086 updateGPoint.currentPos = curGPoint.currentPos; 6087 updateGPoint.currentTime = curGPoint.currentTime; 6088 if ( !updateGPoint.insideElement ) { 6089 stopTrackingPointer( pointsList, updateGPoint ); 6090 } 6091 6092 releasePoint = updateGPoint.currentPos; 6093 releaseTime = updateGPoint.currentTime; 6094 6095 if ( wasCaptured ) { 6096 // Pointer was activated in our element but could have been removed in any element since events are captured to our element 6097 6098 pointsList.removeContact(); 6099 //$.console.log('contacts-- ', pointsList.contacts); 6100 6101 if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 6102 $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); 6103 } 6104 6105 if ( pointsList.contacts === 0 ) { 6106 6107 // Release (pressed in our element) 6108 if ( tracker.releaseHandler ) { 6109 propagate = tracker.releaseHandler( 6110 { 6111 eventSource: tracker, 6112 pointerType: updateGPoint.type, 6113 position: getPointRelativeToAbsolute( releasePoint, tracker.element ), 6114 buttons: pointsList.buttons, 6115 insideElementPressed: updateGPoint.insideElementPressed, 6116 insideElementReleased: updateGPoint.insideElement, 6117 isTouchEvent: updateGPoint.type === 'touch', 6118 originalEvent: event, 6119 preventDefaultAction: false, 6120 userData: tracker.userData 6121 } 6122 ); 6123 if ( propagate === false ) { 6124 $.cancelEvent( event ); 6125 } 6126 } 6127 6128 // Drag End 6129 if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) { 6130 propagate = tracker.dragEndHandler( 6131 { 6132 eventSource: tracker, 6133 pointerType: updateGPoint.type, 6134 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 6135 speed: updateGPoint.speed, 6136 direction: updateGPoint.direction, 6137 shift: event.shiftKey, 6138 isTouchEvent: updateGPoint.type === 'touch', 6139 originalEvent: event, 6140 preventDefaultAction: false, 6141 userData: tracker.userData 6142 } 6143 ); 6144 if ( propagate === false ) { 6145 $.cancelEvent( event ); 6146 } 6147 } 6148 6149 // Click / Double-Click 6150 if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) { 6151 quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold && 6152 updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold; 6153 6154 // Click 6155 if ( tracker.clickHandler ) { 6156 propagate = tracker.clickHandler( 6157 { 6158 eventSource: tracker, 6159 pointerType: updateGPoint.type, 6160 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 6161 quick: quick, 6162 shift: event.shiftKey, 6163 isTouchEvent: updateGPoint.type === 'touch', 6164 originalEvent: event, 6165 preventDefaultAction: false, 6166 userData: tracker.userData 6167 } 6168 ); 6169 if ( propagate === false ) { 6170 $.cancelEvent( event ); 6171 } 6172 } 6173 6174 // Double-Click 6175 if ( tracker.dblClickHandler && quick ) { 6176 pointsList.clicks++; 6177 if ( pointsList.clicks === 1 ) { 6178 delegate.lastClickPos = releasePoint; 6179 /*jshint loopfunc:true*/ 6180 delegate.dblClickTimeOut = setTimeout( function() { 6181 pointsList.clicks = 0; 6182 }, tracker.dblClickTimeThreshold ); 6183 /*jshint loopfunc:false*/ 6184 } else if ( pointsList.clicks === 2 ) { 6185 clearTimeout( delegate.dblClickTimeOut ); 6186 pointsList.clicks = 0; 6187 if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) { 6188 propagate = tracker.dblClickHandler( 6189 { 6190 eventSource: tracker, 6191 pointerType: updateGPoint.type, 6192 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 6193 shift: event.shiftKey, 6194 isTouchEvent: updateGPoint.type === 'touch', 6195 originalEvent: event, 6196 preventDefaultAction: false, 6197 userData: tracker.userData 6198 } 6199 ); 6200 if ( propagate === false ) { 6201 $.cancelEvent( event ); 6202 } 6203 } 6204 delegate.lastClickPos = null; 6205 } 6206 } 6207 } 6208 } else if ( pointsList.contacts === 2 ) { 6209 if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) { 6210 // Reset for pinch 6211 delegate.pinchGPoints = pointsList.asArray(); 6212 delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 6213 delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 6214 } 6215 } 6216 } else { 6217 // Pointer was activated in another element but removed in our element 6218 6219 // Release (pressed in another element) 6220 if ( tracker.releaseHandler ) { 6221 propagate = tracker.releaseHandler( 6222 { 6223 eventSource: tracker, 6224 pointerType: updateGPoint.type, 6225 position: getPointRelativeToAbsolute( releasePoint, tracker.element ), 6226 buttons: pointsList.buttons, 6227 insideElementPressed: updateGPoint.insideElementPressed, 6228 insideElementReleased: updateGPoint.insideElement, 6229 isTouchEvent: updateGPoint.type === 'touch', 6230 originalEvent: event, 6231 preventDefaultAction: false, 6232 userData: tracker.userData 6233 } 6234 ); 6235 if ( propagate === false ) { 6236 $.cancelEvent( event ); 6237 } 6238 } 6239 } 6240 } 6241 } 6242 6243 return releaseCapture; 6244 } 6245 6246 6247 /** 6248 * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) 6249 * 6250 * @function 6251 * @private 6252 * @inner 6253 * @param {OpenSeadragon.MouseTracker} tracker 6254 * A reference to the MouseTracker instance. 6255 * @param {Object} event 6256 * A reference to the originating DOM event. 6257 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 6258 * Gesture points associated with the event. 6259 */ 6260 function updatePointersMove( tracker, event, gPoints ) { 6261 var delegate = THIS[ tracker.hash ], 6262 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 6263 i, 6264 gPointCount = gPoints.length, 6265 curGPoint, 6266 updateGPoint, 6267 gPointArray, 6268 delta, 6269 propagate; 6270 6271 if ( typeof event.buttons !== 'undefined' ) { 6272 pointsList.buttons = event.buttons; 6273 } 6274 6275 for ( i = 0; i < gPointCount; i++ ) { 6276 curGPoint = gPoints[ i ]; 6277 updateGPoint = pointsList.getById( curGPoint.id ); 6278 6279 if ( updateGPoint ) { 6280 // Already tracking the pointer...update it 6281 if ( Object.prototype.hasOwnProperty.call( curGPoint, 'isPrimary' ) ) { 6282 updateGPoint.isPrimary = curGPoint.isPrimary; 6283 }
6284 updateGPoint.lastPos = updateGPoint.currentPos; 6285 updateGPoint.lastTime = updateGPoint.currentTime; 6286 updateGPoint.currentPos = curGPoint.currentPos; 6287 updateGPoint.currentTime = curGPoint.currentTime; 6288 } else { 6289 // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) 6290 curGPoint.captured = false; 6291 curGPoint.insideElementPressed = false; 6292 curGPoint.insideElement = true; 6293 startTrackingPointer( pointsList, curGPoint ); 6294 } 6295 } 6296 6297 // Stop (mouse only) 6298 if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) { 6299 clearTimeout( tracker.stopTimeOut ); 6300 tracker.stopTimeOut = setTimeout( function() { 6301 handlePointerStop( tracker, event, gPoints[ 0 ].type ); 6302 }, tracker.stopDelay ); 6303 } 6304 6305 if ( pointsList.contacts === 0 ) { 6306 // Move (no contacts: hovering mouse or other hover-capable device) 6307 if ( tracker.moveHandler ) { 6308 propagate = tracker.moveHandler( 6309 { 6310 eventSource: tracker, 6311 pointerType: gPoints[ 0 ].type, 6312 position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), 6313 buttons: pointsList.buttons, 6314 isTouchEvent: gPoints[ 0 ].type === 'touch', 6315 originalEvent: event, 6316 preventDefaultAction: false, 6317 userData: tracker.userData 6318 } 6319 ); 6320 if ( propagate === false ) { 6321 $.cancelEvent( event ); 6322 } 6323 } 6324 } else if ( pointsList.contacts === 1 ) { 6325 // Move (1 contact) 6326 if ( tracker.moveHandler ) { 6327 updateGPoint = pointsList.asArray()[ 0 ]; 6328 propagate = tracker.moveHandler( 6329 { 6330 eventSource: tracker, 6331 pointerType: updateGPoint.type, 6332 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 6333 buttons: pointsList.buttons, 6334 isTouchEvent: updateGPoint.type === 'touch', 6335 originalEvent: event, 6336 preventDefaultAction: false, 6337 userData: tracker.userData 6338 } 6339 ); 6340 if ( propagate === false ) { 6341 $.cancelEvent( event ); 6342 } 6343 } 6344 6345 // Drag 6346 if ( tracker.dragHandler ) { 6347 updateGPoint = pointsList.asArray()[ 0 ]; 6348 delta = updateGPoint.currentPos.minus( updateGPoint.lastPos ); 6349 propagate = tracker.dragHandler( 6350 { 6351 eventSource: tracker, 6352 pointerType: updateGPoint.type, 6353 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 6354 buttons: pointsList.buttons, 6355 delta: delta, 6356 speed: updateGPoint.speed, 6357 direction: updateGPoint.direction, 6358 shift: event.shiftKey, 6359 isTouchEvent: updateGPoint.type === 'touch', 6360 originalEvent: event, 6361 preventDefaultAction: false, 6362 userData: tracker.userData 6363 } 6364 ); 6365 if ( propagate === false ) { 6366 $.cancelEvent( event ); 6367 } 6368 } 6369 } else if ( pointsList.contacts === 2 ) { 6370 // Move (2 contacts, use center) 6371 if ( tracker.moveHandler ) { 6372 gPointArray = pointsList.asArray(); 6373 propagate = tracker.moveHandler( 6374 { 6375 eventSource: tracker, 6376 pointerType: gPointArray[ 0 ].type, 6377 position: getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ), 6378 buttons: pointsList.buttons, 6379 isTouchEvent: gPointArray[ 0 ].type === 'touch', 6380 originalEvent: event, 6381 preventDefaultAction: false, 6382 userData: tracker.userData 6383 } 6384 ); 6385 if ( propagate === false ) { 6386 $.cancelEvent( event ); 6387 } 6388 } 6389 6390 // Pinch 6391 if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) { 6392 delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 6393 if ( delta != delegate.currentPinchDist ) { 6394 delegate.lastPinchDist = delegate.currentPinchDist; 6395 delegate.currentPinchDist = delta; 6396 delegate.lastPinchCenter = delegate.currentPinchCenter; 6397 delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 6398 propagate = tracker.pinchHandler( 6399 { 6400 eventSource: tracker, 6401 pointerType: 'touch', 6402 gesturePoints: delegate.pinchGPoints, 6403 lastCenter: getPointRelativeToAbsolute( delegate.lastPinchCenter, tracker.element ), 6404 center: getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ), 6405 lastDistance: delegate.lastPinchDist, 6406 distance: delegate.currentPinchDist, 6407 shift: event.shiftKey, 6408 originalEvent: event, 6409 preventDefaultAction: false, 6410 userData: tracker.userData 6411 } 6412 ); 6413 if ( propagate === false ) { 6414 $.cancelEvent( event ); 6415 } 6416 } 6417 } 6418 } 6419 } 6420 6421 6422 /** 6423 * @function 6424 * @private 6425 * @inner 6426 * @param {OpenSeadragon.MouseTracker} tracker 6427 * A reference to the MouseTracker instance. 6428 * @param {Object} event 6429 * A reference to the originating DOM event. 6430 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 6431 * Gesture points associated with the event. 6432 */ 6433 function updatePointersCancel( tracker, event, gPoints ) { 6434 updatePointersUp( tracker, event, gPoints, 0 ); 6435 updatePointersExit( tracker, event, gPoints ); 6436 } 6437 6438 6439 /** 6440 * @private 6441 * @inner 6442 */ 6443 function handlePointerStop( tracker, originalMoveEvent, pointerType ) { 6444 if ( tracker.stopHandler ) { 6445 tracker.stopHandler( { 6446 eventSource: tracker, 6447 pointerType: pointerType, 6448 position: getMouseRelative( originalMoveEvent, tracker.element ), 6449 buttons: tracker.getActivePointersListByType( pointerType ).buttons, 6450 isTouchEvent: pointerType === 'touch', 6451 originalEvent: originalMoveEvent, 6452 preventDefaultAction: false, 6453 userData: tracker.userData 6454 } ); 6455 } 6456 } 6457 6458 /** 6459 * True if inside an iframe, otherwise false. 6460 * @member {Boolean} isInIframe 6461 * @private 6462 * @inner 6463 */ 6464 var isInIframe = (function() { 6465 try { 6466 return window.self !== window.top; 6467 } catch (e) { 6468 return true; 6469 } 6470 })(); 6471 6472 /** 6473 * @function 6474 * @private 6475 * @inner 6476 * @returns {Boolean} True if the target has access rights to events, otherwise false. 6477 */ 6478 function canAccessEvents (target) { 6479 try { 6480 return target.addEventListener && target.removeEventListener; 6481 } catch (e) { 6482 return false;
vendor: 4,944 bytes, lines 6483-6605
6483 } 6484 } 6485 6486}(OpenSeadragon)); 6487 6488/* 6489 * OpenSeadragon - Control 6490 * 6491 * Copyright (C) 2009 CodePlex Foundation 6492 * Copyright (C) 2010-2013 OpenSeadragon contributors 6493 * 6494 * Redistribution and use in source and binary forms, with or without 6495 * modification, are permitted provided that the following conditions are 6496 * met: 6497 * 6498 * - Redistributions of source code must retain the above copyright notice, 6499 * this list of conditions and the following disclaimer. 6500 * 6501 * - Redistributions in binary form must reproduce the above copyright 6502 * notice, this list of conditions and the following disclaimer in the 6503 * documentation and/or other materials provided with the distribution. 6504 * 6505 * - Neither the name of CodePlex Foundation nor the names of its 6506 * contributors may be used to endorse or promote products derived from 6507 * this software without specific prior written permission. 6508 * 6509 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6510 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 6511 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 6512 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 6513 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 6514 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 6515 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 6516 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 6517 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 6518 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 6519 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6520 */ 6521 6522(function( $ ){ 6523 6524/** 6525 * An enumeration of supported locations where controls can be anchored. 6526 * The anchoring is always relative to the container. 6527 * @member ControlAnchor 6528 * @memberof OpenSeadragon 6529 * @static 6530 * @type {Object} 6531 * @property {Number} NONE 6532 * @property {Number} TOP_LEFT 6533 * @property {Number} TOP_RIGHT 6534 * @property {Number} BOTTOM_LEFT 6535 * @property {Number} BOTTOM_RIGHT 6536 * @property {Number} ABSOLUTE 6537 */ 6538$.ControlAnchor = { 6539 NONE: 0, 6540 TOP_LEFT: 1, 6541 TOP_RIGHT: 2, 6542 BOTTOM_RIGHT: 3, 6543 BOTTOM_LEFT: 4, 6544 ABSOLUTE: 5 6545}; 6546 6547/** 6548 * @class Control 6549 * @classdesc A Control represents any interface element which is meant to allow the user 6550 * to interact with the zoomable interface. Any control can be anchored to any 6551 * element. 6552 * 6553 * @memberof OpenSeadragon 6554 * @param {Element} element - the control element to be anchored in the container. 6555 * @param {Object } options - All required and optional settings for configuring a control element. 6556 * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - the position of the control 6557 * relative to the container. 6558 * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or 6559 * directly to the container 6560 * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior 6561 * @param {Element} container - the element to control will be anchored too. 6562 */ 6563$.Control = function ( element, options, container ) { 6564 var parent = element.parentNode; 6565 if (typeof options === 'number') 6566 { 6567 $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " + 6568 "please use an options object instead. " + 6569 "Support for this deprecated variant is scheduled for removal in December 2013"); 6570 options = {anchor: options}; 6571 } 6572 options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer; 6573 /** 6574 * True if the control should have autofade behavior. 6575 * @member {Boolean} autoFade 6576 * @memberof OpenSeadragon.Control# 6577 */ 6578 this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade; 6579 /** 6580 * The element providing the user interface with some type of control (e.g. a zoom-in button). 6581 * @member {Element} element 6582 * @memberof OpenSeadragon.Control# 6583 */ 6584 this.element = element; 6585 /** 6586 * The position of the Control relative to its container. 6587 * @member {OpenSeadragon.ControlAnchor} anchor 6588 * @memberof OpenSeadragon.Control# 6589 */ 6590 this.anchor = options.anchor; 6591 /** 6592 * The Control's containing element. 6593 * @member {Element} container 6594 * @memberof OpenSeadragon.Control# 6595 */ 6596 this.container = container; 6597 /** 6598 * A neutral element surrounding the control element. 6599 * @member {Element} wrapper 6600 * @memberof OpenSeadragon.Control# 6601 */ 6602 if ( this.anchor == $.ControlAnchor.ABSOLUTE ) { 6603 this.wrapper = $.makeNeutralElement( "div" ); 6604 this.wrapper.style.position = "absolute"; 6605 this.wrapper.style.top = typeof (options.top) == "number" ? (options.top + 'px') : options.top;
6606 this.wrapper.style.left = typeof (options.left) == "number" ? (options.left + 'px') : options.left; 6607 this.wrapper.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height; 6608 this.wrapper.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width; 6609 this.wrapper.style.margin = "0px"; 6610 this.wrapper.style.padding = "0px"; 6611 6612 this.element.style.position = "relative"; 6613 this.element.style.top = "0px"; 6614 this.element.style.left = "0px"; 6615 this.element.style.height = "100%"; 6616 this.element.style.width = "100%"; 6617 } else { 6618 this.wrapper = $.makeNeutralElement( "div" ); 6619 this.wrapper.style.display = "inline-block"; 6620 if ( this.anchor == $.ControlAnchor.NONE ) { 6621 // IE6 fix 6622 this.wrapper.style.width = this.wrapper.style.height = "100%"; 6623 } 6624 } 6625 this.wrapper.appendChild( this.element ); 6626 6627 if (options.attachToViewer ) { 6628 if ( this.anchor == $.ControlAnchor.TOP_RIGHT || 6629 this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) { 6630 this.container.insertBefore( 6631 this.wrapper, 6632 this.container.firstChild 6633 ); 6634 } else { 6635 this.container.appendChild( this.wrapper ); 6636 } 6637 } else { 6638 parent.appendChild( this.wrapper ); 6639 } 6640}; 6641 6642/** @lends OpenSeadragon.Control.prototype */ 6643$.Control.prototype = { 6644 6645 /** 6646 * Removes the control from the container. 6647 * @function 6648 */ 6649 destroy: function() { 6650 this.wrapper.removeChild( this.element ); 6651 this.container.removeChild( this.wrapper ); 6652 }, 6653 6654 /** 6655 * Determines if the control is currently visible. 6656 * @function 6657 * @return {Boolean} true if currently visible, false otherwise. 6658 */ 6659 isVisible: function() { 6660 return this.wrapper.style.display != "none"; 6661 }, 6662 6663 /** 6664 * Toggles the visibility of the control. 6665 * @function 6666 * @param {Boolean} visible - true to make visible, false to hide. 6667 */ 6668 setVisible: function( visible ) { 6669 this.wrapper.style.display = visible ? 6670 ( this.anchor == $.ControlAnchor.ABSOLUTE ? 'block' : 'inline-block' ) : 6671 "none"; 6672 }, 6673 6674 /** 6675 * Sets the opacity level for the control. 6676 * @function 6677 * @param {Number} opactiy - a value between 1 and 0 inclusively. 6678 */ 6679 setOpacity: function( opacity ) { 6680 if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) { 6681 $.setElementOpacity( this.element, opacity, true ); 6682 } else { 6683 $.setElementOpacity( this.wrapper, opacity, true ); 6684 } 6685 } 6686}; 6687 6688}( OpenSeadragon )); 6689 6690/* 6691 * OpenSeadragon - ControlDock 6692 * 6693 * Copyright (C) 2009 CodePlex Foundation 6694 * Copyright (C) 2010-2013 OpenSeadragon contributors 6695 * 6696 * Redistribution and use in source and binary forms, with or without 6697 * modification, are permitted provided that the following conditions are 6698 * met: 6699 * 6700 * - Redistributions of source code must retain the above copyright notice, 6701 * this list of conditions and the following disclaimer. 6702 * 6703 * - Redistributions in binary form must reproduce the above copyright 6704 * notice, this list of conditions and the following disclaimer in the 6705 * documentation and/or other materials provided with the distribution. 6706 * 6707 * - Neither the name of CodePlex Foundation nor the names of its 6708 * contributors may be used to endorse or promote products derived from 6709 * this software without specific prior written permission. 6710 * 6711 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6712 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 6713 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 6714 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 6715 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 6716 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 6717 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 6718 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 6719 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 6720 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 6721 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6722 */ 6723 6724(function( $ ){ 6725 /** 6726 * @class ControlDock 6727 * @classdesc Provides a container element (a <form> element) with support for the layout of control elements. 6728 * 6729 * @memberof OpenSeadragon 6730 */ 6731 $.ControlDock = function( options ){ 6732 var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'], 6733 layout, 6734 i; 6735 6736 $.extend( true, this, { 6737 id: 'controldock-' + $.now() + '-' + Math.floor(Math.random() * 1000000), 6738 container: $.makeNeutralElement( 'div' ), 6739 controls: [] 6740 }, options ); 6741 6742 // Disable the form's submit; otherwise button clicks and return keys 6743 // can trigger it. 6744 this.container.onsubmit = function() { 6745 return false;
vendor: 11,484 bytes, lines 6746-7084
6746 }; 6747 6748 if( this.element ){ 6749 this.element = $.getElement( this.element ); 6750 this.element.appendChild( this.container ); 6751 this.element.style.position = 'relative'; 6752 this.container.style.width = '100%'; 6753 this.container.style.height = '100%'; 6754 } 6755 6756 for( i = 0; i < layouts.length; i++ ){ 6757 layout = layouts[ i ]; 6758 this.controls[ layout ] = $.makeNeutralElement( "div" ); 6759 this.controls[ layout ].style.position = 'absolute'; 6760 if ( layout.match( 'left' ) ){ 6761 this.controls[ layout ].style.left = '0px'; 6762 } 6763 if ( layout.match( 'right' ) ){ 6764 this.controls[ layout ].style.right = '0px'; 6765 } 6766 if ( layout.match( 'top' ) ){ 6767 this.controls[ layout ].style.top = '0px'; 6768 } 6769 if ( layout.match( 'bottom' ) ){ 6770 this.controls[ layout ].style.bottom = '0px'; 6771 } 6772 } 6773 6774 this.container.appendChild( this.controls.topleft ); 6775 this.container.appendChild( this.controls.topright ); 6776 this.container.appendChild( this.controls.bottomright ); 6777 this.container.appendChild( this.controls.bottomleft ); 6778 }; 6779 6780 /** @lends OpenSeadragon.ControlDock.prototype */ 6781 $.ControlDock.prototype = { 6782 6783 /** 6784 * @function 6785 */ 6786 addControl: function ( element, controlOptions ) { 6787 element = $.getElement( element ); 6788 var div = null; 6789 6790 if ( getControlIndex( this, element ) >= 0 ) { 6791 return; // they're trying to add a duplicate control 6792 } 6793 6794 switch ( controlOptions.anchor ) { 6795 case $.ControlAnchor.TOP_RIGHT: 6796 div = this.controls.topright; 6797 element.style.position = "relative"; 6798 element.style.paddingRight = "0px"; 6799 element.style.paddingTop = "0px"; 6800 break; 6801 case $.ControlAnchor.BOTTOM_RIGHT: 6802 div = this.controls.bottomright; 6803 element.style.position = "relative"; 6804 element.style.paddingRight = "0px"; 6805 element.style.paddingBottom = "0px"; 6806 break; 6807 case $.ControlAnchor.BOTTOM_LEFT: 6808 div = this.controls.bottomleft; 6809 element.style.position = "relative"; 6810 element.style.paddingLeft = "0px"; 6811 element.style.paddingBottom = "0px"; 6812 break; 6813 case $.ControlAnchor.TOP_LEFT: 6814 div = this.controls.topleft; 6815 element.style.position = "relative"; 6816 element.style.paddingLeft = "0px"; 6817 element.style.paddingTop = "0px"; 6818 break; 6819 case $.ControlAnchor.ABSOLUTE: 6820 div = this.container; 6821 element.style.margin = "0px"; 6822 element.style.padding = "0px"; 6823 break; 6824 default: 6825 case $.ControlAnchor.NONE: 6826 div = this.container; 6827 element.style.margin = "0px"; 6828 element.style.padding = "0px"; 6829 break; 6830 } 6831 6832 this.controls.push( 6833 new $.Control( element, controlOptions, div ) 6834 ); 6835 element.style.display = "inline-block"; 6836 }, 6837 6838 6839 /** 6840 * @function 6841 * @return {OpenSeadragon.ControlDock} Chainable. 6842 */ 6843 removeControl: function ( element ) { 6844 element = $.getElement( element ); 6845 var i = getControlIndex( this, element ); 6846 6847 if ( i >= 0 ) { 6848 this.controls[ i ].destroy(); 6849 this.controls.splice( i, 1 ); 6850 } 6851 6852 return this; 6853 }, 6854 6855 /** 6856 * @function 6857 * @return {OpenSeadragon.ControlDock} Chainable. 6858 */ 6859 clearControls: function () { 6860 while ( this.controls.length > 0 ) { 6861 this.controls.pop().destroy(); 6862 } 6863 6864 return this; 6865 }, 6866 6867 6868 /** 6869 * @function 6870 * @return {Boolean} 6871 */ 6872 areControlsEnabled: function () { 6873 var i; 6874 6875 for ( i = this.controls.length - 1; i >= 0; i-- ) { 6876 if ( this.controls[ i ].isVisible() ) { 6877 return true; 6878 } 6879 } 6880 6881 return false; 6882 }, 6883 6884 6885 /** 6886 * @function 6887 * @return {OpenSeadragon.ControlDock} Chainable. 6888 */ 6889 setControlsEnabled: function( enabled ) { 6890 var i; 6891 6892 for ( i = this.controls.length - 1; i >= 0; i-- ) { 6893 this.controls[ i ].setVisible( enabled ); 6894 } 6895 6896 return this; 6897 } 6898 6899 }; 6900 6901 6902 /////////////////////////////////////////////////////////////////////////////// 6903 // Utility methods 6904 /////////////////////////////////////////////////////////////////////////////// 6905 function getControlIndex( dock, element ) { 6906 var controls = dock.controls, 6907 i; 6908 6909 for ( i = controls.length - 1; i >= 0; i-- ) { 6910 if ( controls[ i ].element == element ) { 6911 return i; 6912 } 6913 } 6914 6915 return -1; 6916 } 6917 6918}( OpenSeadragon )); 6919 6920/* 6921 * OpenSeadragon - Placement 6922 * 6923 * Copyright (C) 2010-2016 OpenSeadragon contributors 6924 * 6925 * Redistribution and use in source and binary forms, with or without 6926 * modification, are permitted provided that the following conditions are 6927 * met: 6928 * 6929 * - Redistributions of source code must retain the above copyright notice, 6930 * this list of conditions and the following disclaimer. 6931 * 6932 * - Redistributions in binary form must reproduce the above copyright 6933 * notice, this list of conditions and the following disclaimer in the 6934 * documentation and/or other materials provided with the distribution. 6935 * 6936 * - Neither the name of CodePlex Foundation nor the names of its 6937 * contributors may be used to endorse or promote products derived from 6938 * this software without specific prior written permission. 6939 * 6940 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6941 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 6942 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 6943 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 6944 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 6945 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 6946 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 6947 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 6948 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 6949 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 6950 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6951 */ 6952 6953(function($) { 6954 6955 /** 6956 * An enumeration of positions to anchor an element. 6957 * @member Placement 6958 * @memberOf OpenSeadragon 6959 * @static 6960 * @readonly 6961 * @property {OpenSeadragon.Placement} CENTER 6962 * @property {OpenSeadragon.Placement} TOP_LEFT 6963 * @property {OpenSeadragon.Placement} TOP 6964 * @property {OpenSeadragon.Placement} TOP_RIGHT 6965 * @property {OpenSeadragon.Placement} RIGHT 6966 * @property {OpenSeadragon.Placement} BOTTOM_RIGHT 6967 * @property {OpenSeadragon.Placement} BOTTOM 6968 * @property {OpenSeadragon.Placement} BOTTOM_LEFT 6969 * @property {OpenSeadragon.Placement} LEFT 6970 */ 6971 $.Placement = $.freezeObject({ 6972 CENTER: 0, 6973 TOP_LEFT: 1, 6974 TOP: 2, 6975 TOP_RIGHT: 3, 6976 RIGHT: 4, 6977 BOTTOM_RIGHT: 5, 6978 BOTTOM: 6, 6979 BOTTOM_LEFT: 7, 6980 LEFT: 8, 6981 properties: { 6982 0: { 6983 isLeft: false, 6984 isHorizontallyCentered: true, 6985 isRight: false, 6986 isTop: false, 6987 isVerticallyCentered: true, 6988 isBottom: false 6989 }, 6990 1: { 6991 isLeft: true, 6992 isHorizontallyCentered: false, 6993 isRight: false, 6994 isTop: true, 6995 isVerticallyCentered: false, 6996 isBottom: false 6997 }, 6998 2: { 6999 isLeft: false, 7000 isHorizontallyCentered: true, 7001 isRight: false, 7002 isTop: true, 7003 isVerticallyCentered: false, 7004 isBottom: false 7005 }, 7006 3: { 7007 isLeft: false, 7008 isHorizontallyCentered: false, 7009 isRight: true, 7010 isTop: true, 7011 isVerticallyCentered: false, 7012 isBottom: false 7013 }, 7014 4: { 7015 isLeft: false, 7016 isHorizontallyCentered: false, 7017 isRight: true, 7018 isTop: false, 7019 isVerticallyCentered: true, 7020 isBottom: false 7021 }, 7022 5: { 7023 isLeft: false, 7024 isHorizontallyCentered: false, 7025 isRight: true, 7026 isTop: false, 7027 isVerticallyCentered: false, 7028 isBottom: true 7029 }, 7030 6: { 7031 isLeft: false, 7032 isHorizontallyCentered: true, 7033 isRight: false, 7034 isTop: false, 7035 isVerticallyCentered: false, 7036 isBottom: true 7037 }, 7038 7: { 7039 isLeft: true, 7040 isHorizontallyCentered: false, 7041 isRight: false, 7042 isTop: false, 7043 isVerticallyCentered: false, 7044 isBottom: true 7045 }, 7046 8: { 7047 isLeft: true, 7048 isHorizontallyCentered: false, 7049 isRight: false, 7050 isTop: false, 7051 isVerticallyCentered: true, 7052 isBottom: false 7053 } 7054 } 7055 }); 7056 7057}(OpenSeadragon)); 7058 7059/* 7060 * OpenSeadragon - Viewer 7061 * 7062 * Copyright (C) 2009 CodePlex Foundation 7063 * Copyright (C) 2010-2013 OpenSeadragon contributors 7064 * 7065 * Redistribution and use in source and binary forms, with or without 7066 * modification, are permitted provided that the following conditions are 7067 * met: 7068 * 7069 * - Redistributions of source code must retain the above copyright notice, 7070 * this list of conditions and the following disclaimer. 7071 * 7072 * - Redistributions in binary form must reproduce the above copyright 7073 * notice, this list of conditions and the following disclaimer in the 7074 * documentation and/or other materials provided with the distribution. 7075 * 7076 * - Neither the name of CodePlex Foundation nor the names of its 7077 * contributors may be used to endorse or promote products derived from 7078 * this software without specific prior written permission. 7079 * 7080 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7081 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 7082 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 7083 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 7084 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
7085 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 7086 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 7087 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 7088 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 7089 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 7090 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7091 */ 7092 7093(function( $ ){ 7094 7095// dictionary from hash to private properties 7096var THIS = {}; 7097var nextHash = 1; 7098 7099/** 7100 * 7101 * The main point of entry into creating a zoomable image on the page.<br> 7102 * <br> 7103 * We have provided an idiomatic javascript constructor which takes 7104 * a single object, but still support the legacy positional arguments.<br> 7105 * <br> 7106 * The options below are given in order that they appeared in the constructor 7107 * as arguments and we translate a positional call into an idiomatic call.<br> 7108 * <br> 7109 * To create a viewer, you can use either of this methods:<br> 7110 * <ul> 7111 * <li><code>var viewer = new OpenSeadragon.Viewer(options);</code></li> 7112 * <li><code>var viewer = OpenSeadragon(options);</code></li> 7113 * </ul> 7114 * @class Viewer 7115 * @classdesc The main OpenSeadragon viewer class. 7116 * 7117 * @memberof OpenSeadragon 7118 * @extends OpenSeadragon.EventSource 7119 * @extends OpenSeadragon.ControlDock 7120 * @param {OpenSeadragon.Options} options - Viewer options. 7121 * 7122 **/ 7123$.Viewer = function( options ) { 7124 7125 var args = arguments, 7126 _this = this, 7127 i; 7128 7129 7130 //backward compatibility for positional args while preferring more 7131 //idiomatic javascript options object as the only argument 7132 if( !$.isPlainObject( options ) ){ 7133 options = { 7134 id: args[ 0 ], 7135 xmlPath: args.length > 1 ? args[ 1 ] : undefined, 7136 prefixUrl: args.length > 2 ? args[ 2 ] : undefined, 7137 controls: args.length > 3 ? args[ 3 ] : undefined, 7138 overlays: args.length > 4 ? args[ 4 ] : undefined 7139 }; 7140 } 7141 7142 //options.config and the general config argument are deprecated 7143 //in favor of the more direct specification of optional settings 7144 //being pass directly on the options object 7145 if ( options.config ){ 7146 $.extend( true, options, options.config ); 7147 delete options.config; 7148 } 7149 7150 //Public properties 7151 //Allow the options object to override global defaults 7152 $.extend( true, this, { 7153 7154 //internal state and dom identifiers 7155 id: options.id, 7156 hash: options.hash || nextHash++, 7157 /** 7158 * Index for page to be shown first next time open() is called (only used in sequenceMode). 7159 * @member {Number} initialPage 7160 * @memberof OpenSeadragon.Viewer# 7161 */ 7162 initialPage: 0, 7163 7164 //dom nodes 7165 /** 7166 * The parent element of this Viewer instance, passed in when the Viewer was created. 7167 * @member {Element} element 7168 * @memberof OpenSeadragon.Viewer# 7169 */ 7170 element: null, 7171 /** 7172 * A <div> element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.<br><br> 7173 * Child element of {@link OpenSeadragon.Viewer#element}. 7174 * @member {Element} container 7175 * @memberof OpenSeadragon.Viewer# 7176 */ 7177 container: null, 7178 /** 7179 * A <div> element, the element where user-input events are handled for panning and zooming.<br><br> 7180 * Child element of {@link OpenSeadragon.Viewer#container}, 7181 * positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.<br><br> 7182 * The parent of {@link OpenSeadragon.Drawer#canvas} instances. 7183 * @member {Element} canvas 7184 * @memberof OpenSeadragon.Viewer# 7185 */ 7186 canvas: null, 7187 7188 // Overlays list. An overlay allows to add html on top of the viewer. 7189 overlays: [], 7190 // Container inside the canvas where overlays are drawn. 7191 overlaysContainer: null, 7192 7193 //private state properties 7194 previousBody: [], 7195 7196 //This was originally initialized in the constructor and so could never 7197 //have anything in it. now it can because we allow it to be specified 7198 //in the options and is only empty by default if not specified. Also 7199 //this array was returned from get_controls which I find confusing 7200 //since this object has a controls property which is treated in other 7201 //functions like clearControls. I'm removing the accessors. 7202 customControls: [], 7203 7204 //These are originally not part options but declared as members 7205 //in initialize. It's still considered idiomatic to put them here 7206 //source is here for backwards compatibility. It is not an official 7207 //part of the API and should not be relied upon. 7208 source: null, 7209 /** 7210 * Handles rendering of tiles in the viewer. Created for each TileSource opened. 7211 * @member {OpenSeadragon.Drawer} drawer 7212 * @memberof OpenSeadragon.Viewer# 7213 */ 7214 drawer: null, 7215 /** 7216 * Keeps track of all of the tiled images in the scene. 7217 * @member {OpenSeadragon.World} world 7218 * @memberof OpenSeadragon.Viewer# 7219 */ 7220 world: null, 7221 /** 7222 * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened. 7223 * @member {OpenSeadragon.Viewport} viewport 7224 * @memberof OpenSeadragon.Viewer# 7225 */ 7226 viewport: null, 7227 /** 7228 * @member {OpenSeadragon.Navigator} navigator 7229 * @memberof OpenSeadragon.Viewer# 7230 */ 7231 navigator: null, 7232 7233 //A collection viewport is a separate viewport used to provide 7234 //simultaneous rendering of sets of tiles 7235 collectionViewport: null, 7236 collectionDrawer: null, 7237 7238 //UI image resources 7239 //TODO: rename navImages to uiImages 7240 navImages: null, 7241 7242 //interface button controls 7243 buttons: null, 7244 7245 //TODO: this is defunct so safely remove it 7246 profiler: null 7247 7248 }, $.DEFAULT_SETTINGS, options ); 7249 7250 // cookie for domain not exlibrisgroup.com 7251 $.DEFAULT_SETTINGS.iiifToken = options.iiifToken; 7252 7253 if ( typeof ( this.hash) === "undefined" ) { 7254 throw new Error("A hash must be defined, either by specifying options.id or options.hash."); 7255 } 7256 if ( typeof ( THIS[ this.hash ] ) !== "undefined" ) { 7257 // We don't want to throw an error here, as the user might have discarded 7258 // the previous viewer with the same hash and now want to re
7258create it. 7259 $.console.warn("Hash " + this.hash + " has already been used."); 7260 } 7261 7262 //Private state properties 7263 THIS[ this.hash ] = { 7264 "fsBoundsDelta": new $.Point( 1, 1 ), 7265 "prevContainerSize": null, 7266 "animating": false, 7267 "forceRedraw": false, 7268 "mouseInside": false, 7269 "group": null, 7270 // whether we should be continuously zooming 7271 "zooming": false, 7272 // how much we should be continuously zooming by 7273 "zoomFactor": null, 7274 "lastZoomTime": null, 7275 "fullPage": false, 7276 "onfullscreenchange": null 7277 }; 7278 7279 this._sequenceIndex = 0; 7280 this._firstOpen = true; 7281 this._updateRequestId = null; 7282 this._loadQueue = []; 7283 this.currentOverlays = []; 7284 7285 this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices 7286 7287 //Inherit some behaviors and properties 7288 $.EventSource.call( this ); 7289 7290 this.addHandler( 'open-failed', function ( event ) { 7291 var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message); 7292 _this._showMessage( msg ); 7293 }); 7294 7295 $.ControlDock.call( this, options ); 7296 7297 //Deal with tile sources 7298 if (this.xmlPath) { 7299 //Deprecated option. Now it is preferred to use the tileSources option 7300 this.tileSources = [ this.xmlPath ]; 7301 } 7302 7303 this.element = this.element || document.getElementById( this.id ); 7304 this.canvas = $.makeNeutralElement( "div" ); 7305 7306 this.canvas.className = "openseadragon-canvas"; 7307 (function( style ){ 7308 style.width = "100%"; 7309 style.height = "100%"; 7310 style.overflow = "hidden"; 7311 style.position = "absolute"; 7312 style.top = "0px"; 7313 style.left = "0px"; 7314 }(this.canvas.style)); 7315 $.setElementTouchActionNone( this.canvas ); 7316 if (options.tabIndex !== "") { 7317 this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex); 7318 } 7319 7320 //the container is created through applying the ControlDock constructor above 7321 this.container.className = "openseadragon-container"; 7322 (function( style ){ 7323 style.width = "100%"; 7324 style.height = "100%"; 7325 style.position = "relative"; 7326 style.overflow = "hidden"; 7327 style.left = "0px"; 7328 style.top = "0px"; 7329 style.textAlign = "left"; // needed to protect against 7330 }( this.container.style )); 7331 7332 this.container.insertBefore( this.canvas, this.container.firstChild ); 7333 this.element.appendChild( this.container ); 7334 7335 //Used for toggling between fullscreen and default container size 7336 //TODO: these can be closure private and shared across Viewer 7337 // instances. 7338 this.bodyWidth = document.body.style.width; 7339 this.bodyHeight = document.body.style.height; 7340 this.bodyOverflow = document.body.style.overflow; 7341 this.docOverflow = document.documentElement.style.overflow; 7342 7343 this.innerTracker = new $.MouseTracker({ 7344 element: this.canvas, 7345 startDisabled: !this.mouseNavEnabled, 7346 clickTimeThreshold: this.clickTimeThreshold, 7347 clickDistThreshold: this.clickDistThreshold, 7348 dblClickTimeThreshold: this.dblClickTimeThreshold, 7349 dblClickDistThreshold: this.dblClickDistThreshold, 7350 keyDownHandler: $.delegate( this, onCanvasKeyDown ), 7351 keyHandler: $.delegate( this, onCanvasKeyPress ), 7352 clickHandler: $.delegate( this, onCanvasClick ), 7353 dblClickHandler: $.delegate( this, onCanvasDblClick ), 7354 dragHandler: $.delegate( this, onCanvasDrag ), 7355 dragEndHandler: $.delegate( this, onCanvasDragEnd ), 7356 enterHandler: $.delegate( this, onCanvasEnter ), 7357 exitHandler: $.delegate( this, onCanvasExit ), 7358 pressHandler: $.delegate( this, onCanvasPress ), 7359 releaseHandler: $.delegate( this, onCanvasRelease ), 7360 nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ), 7361 nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ), 7362 scrollHandler: $.delegate( this, onCanvasScroll ), 7363 pinchHandler: $.delegate( this, onCanvasPinch ) 7364 }); 7365 7366 this.outerTracker = new $.MouseTracker({ 7367 element: this.container, 7368 startDisabled: !this.mouseNavEnabled, 7369 clickTimeThreshold: this.clickTimeThreshold, 7370 clickDistThreshold: this.clickDistThreshold, 7371 dblClickTimeThreshold: this.dblClickTimeThreshold, 7372 dblClickDistThreshold: this.dblClickDistThreshold, 7373 enterHandler: $.delegate( this, onContainerEnter ), 7374 exitHandler: $.delegate( this, onContainerExit ) 7375 }); 7376 7377 if( this.toolbar ){ 7378 this.toolbar = new $.ControlDock({ element: this.toolbar }); 7379 } 7380 7381 this.bindStandardControls(); 7382
vendor: 4,433 bytes, lines 7383-7499
7383 THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container ); 7384 7385 // Create the world 7386 this.world = new $.World({ 7387 viewer: this 7388 }); 7389 7390 this.world.addHandler('add-item', function(event) { 7391 // For backwards compatibility, we maintain the source property 7392 _this.source = _this.world.getItemAt(0).source; 7393 7394 THIS[ _this.hash ].forceRedraw = true; 7395 7396 if (!_this._updateRequestId) { 7397 _this._updateRequestId = scheduleUpdate( _this, updateMulti ); 7398 } 7399 }); 7400 7401 this.world.addHandler('remove-item', function(event) { 7402 // For backwards compatibility, we maintain the source property 7403 if (_this.world.getItemCount()) { 7404 _this.source = _this.world.getItemAt(0).source; 7405 } else { 7406 _this.source = null; 7407 } 7408 7409 THIS[ _this.hash ].forceRedraw = true; 7410 }); 7411 7412 this.world.addHandler('metrics-change', function(event) { 7413 if (_this.viewport) { 7414 _this.viewport._setContentBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); 7415 } 7416 }); 7417 7418 this.world.addHandler('item-index-change', function(event) { 7419 // For backwards compatibility, we maintain the source property 7420 _this.source = _this.world.getItemAt(0).source; 7421 }); 7422 7423 // Create the viewport 7424 this.viewport = new $.Viewport({ 7425 containerSize: THIS[ this.hash ].prevContainerSize, 7426 springStiffness: this.springStiffness, 7427 animationTime: this.animationTime, 7428 minZoomImageRatio: this.minZoomImageRatio, 7429 maxZoomPixelRatio: this.maxZoomPixelRatio, 7430 visibilityRatio: this.visibilityRatio, 7431 wrapHorizontal: this.wrapHorizontal, 7432 wrapVertical: this.wrapVertical, 7433 defaultZoomLevel: this.defaultZoomLevel, 7434 minZoomLevel: this.minZoomLevel, 7435 maxZoomLevel: this.maxZoomLevel, 7436 viewer: this, 7437 degrees: this.degrees, 7438 flipped: this.flipped, 7439 navigatorRotate: this.navigatorRotate, 7440 homeFillsViewer: this.homeFillsViewer, 7441 margins: this.viewportMargins 7442 }); 7443 7444 this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor()); 7445 7446 // Create the image loader 7447 this.imageLoader = new $.ImageLoader({ 7448 jobLimit: this.imageLoaderLimit, 7449 timeout: options.timeout 7450 }); 7451 7452 // Create the tile cache 7453 this.tileCache = new $.TileCache({ 7454 maxImageCacheCount: this.maxImageCacheCount 7455 }); 7456 7457 // Create the drawer 7458 this.drawer = new $.Drawer({ 7459 viewer: this, 7460 viewport: this.viewport, 7461 element: this.canvas, 7462 debugGridColor: this.debugGridColor 7463 }); 7464 7465 // Overlay container 7466 this.overlaysContainer = $.makeNeutralElement( "div" ); 7467 this.canvas.appendChild( this.overlaysContainer ); 7468 7469 // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons 7470 if (!this.drawer.canRotate()) { 7471 // Disable/remove the rotate left/right buttons since they aren't supported 7472 if (this.rotateLeft) { 7473 i = this.buttons.buttons.indexOf(this.rotateLeft); 7474 this.buttons.buttons.splice(i, 1); 7475 this.buttons.element.removeChild(this.rotateLeft.element); 7476 } 7477 if (this.rotateRight) { 7478 i = this.buttons.buttons.indexOf(this.rotateRight); 7479 this.buttons.buttons.splice(i, 1); 7480 this.buttons.element.removeChild(this.rotateRight.element); 7481 } 7482 } 7483 7484 //Instantiate a navigator if configured 7485 if ( this.showNavigator){ 7486 this.navigator = new $.Navigator({ 7487 id: this.navigatorId, 7488 position: this.navigatorPosition, 7489 sizeRatio: this.navigatorSizeRatio, 7490 maintainSizeRatio: this.navigatorMaintainSizeRatio, 7491 top: this.navigatorTop, 7492 left: this.navigatorLeft, 7493 width: this.navigatorWidth, 7494 height: this.navigatorHeight, 7495 autoResize: this.navigatorAutoResize, 7496 autoFade: this.navigatorAutoFade, 7497 prefixUrl: this.prefixUrl, 7498 viewer: this, 7499 navigatorRotate: this.navigatorRotate,
7500 background: this.navigatorBackground, 7501 opacity: this.navigatorOpacity, 7502 borderColor: this.navigatorBorderColor, 7503 displayRegionColor: this.navigatorDisplayRegionColor, 7504 crossOriginPolicy: this.crossOriginPolicy 7505 }); 7506 } 7507 7508 // Sequence mode 7509 if (this.sequenceMode) { 7510 this.bindSequenceControls(); 7511 } 7512 7513 // Open initial tilesources 7514 if (this.tileSources) { 7515 this.open( this.tileSources ); 7516 } 7517 7518 // Add custom controls 7519 for ( i = 0; i < this.customControls.length; i++ ) { 7520 this.addControl( 7521 this.customControls[ i ].id, 7522 {anchor: this.customControls[ i ].anchor} 7523 ); 7524 } 7525 7526 // Initial fade out 7527 $.requestAnimationFrame( function(){ 7528 beginControlsAutoHide( _this ); 7529 } ); 7530 7531 // Initial canvas options 7532 if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){ 7533 this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled); 7534 } 7535 7536}; 7537 7538$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{ 7539 7540 7541 /** 7542 * @function 7543 * @return {Boolean} 7544 */ 7545 isOpen: function () { 7546 return !!this.world.getItemCount(); 7547 }, 7548 7549 // deprecated 7550 openDzi: function ( dzi ) { 7551 $.console.error( "[Viewer.openDzi] this function is deprecated; use Viewer.open() instead." ); 7552 return this.open( dzi ); 7553 }, 7554 7555 // deprecated 7556 openTileSource: function ( tileSource ) { 7557 $.console.error( "[Viewer.openTileSource] this function is deprecated; use Viewer.open() instead." ); 7558 return this.open( tileSource ); 7559 }, 7560 7561 /** 7562 * Open tiled images into the viewer, closing any others. 7563 * To get the TiledImage instance created by open, add an event listener for 7564 * {@link OpenSeadragon.Viewer.html#.event:open}, which when fired can be used to get access 7565 * to the instance, i.e., viewer.world.getItemAt(0). 7566 * @function 7567 * @param {Array|String|Object|Function} tileSources - This can be a TiledImage 7568 * specifier, a TileSource specifier, or an array of either. A TiledImage specifier 7569 * is the same as the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}, 7570 * except for the index property; images are added in sequence. 7571 * A TileSource specifier is anything you could pass as the tileSource property 7572 * of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}. 7573 * @param {Number} initialPage - If sequenceMode is true, display this page initially 7574 * for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property. 7575 * @return {OpenSeadragon.Viewer} Chainable. 7576 * @fires OpenSeadragon.Viewer.event:open 7577 * @fires OpenSeadragon.Viewer.event:open-failed 7578 */ 7579 open: function (tileSources, initialPage) { 7580 var _this = this; 7581 7582 this.close(); 7583 7584 if (!tileSources) { 7585 return; 7586 } 7587 7588 if (this.sequenceMode && $.isArray(tileSources)) { 7589 if (this.referenceStrip) { 7590 this.referenceStrip.destroy(); 7591 this.referenceStrip = null; 7592 } 7593 7594 if (typeof initialPage != 'undefined' && !isNaN(initialPage)) { 7595 this.initialPage = initialPage; 7596 } 7597 7598 this.tileSources = tileSources; 7599 this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage)); 7600 if (this.tileSources.length) { 7601 this.open(this.tileSources[this._sequenceIndex]); 7602 7603 if ( this.showReferenceStrip ){ 7604 this.addReferenceStrip(); 7605 } 7606 } 7607 7608 this._updateSequenceButtons( this._sequenceIndex ); 7609 return; 7610 } 7611 7612 if (!$.isArray(tileSources)) { 7613 tileSources = [tileSources]; 7614 } 7615 7616 if (!tileSources.length) { 7617 return; 7618 } 7619 7620 this._opening = true; 7621 7622 var expected = tileSources.length; 7623 var successes = 0; 7624 var failures = 0; 7625 var failEvent; 7626 7627 var checkCompletion = function() { 7628 if (successes + failures === expected) { 7629 if (successes) { 7630 if (_this._firstOpen || !_this.preserveViewport) { 7631 _this.viewport.goHome( true );
7632 _this.viewport.update(); 7633 } 7634 7635 _this._firstOpen = false; 7636 7637 var source = tileSources[0]; 7638 if (source.tileSource) { 7639 source = source.tileSource; 7640 } 7641 7642 // Global overlays 7643 if( _this.overlays && !_this.preserveOverlays ){ 7644 for ( var i = 0; i < _this.overlays.length; i++ ) { 7645 _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] ); 7646 } 7647 } 7648 7649 _this._drawOverlays(); 7650 _this._opening = false; 7651 7652 /** 7653 * Raised when the viewer has opened and loaded one or more TileSources. 7654 * 7655 * @event open 7656 * @memberof OpenSeadragon.Viewer 7657 * @type {object} 7658 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7659 * @property {OpenSeadragon.TileSource} source - The tile source that was opened. 7660 * @property {?Object} userData - Arbitrary subscriber-defined object. 7661 */ 7662 // TODO: what if there are multiple sources? 7663 _this.raiseEvent( 'open', { source: source } ); 7664 } else { 7665 _this._opening = false; 7666 7667 /** 7668 * Raised when an error occurs loading a TileSource. 7669 * 7670 * @event open-failed 7671 * @memberof OpenSeadragon.Viewer 7672 * @type {object} 7673 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7674 * @property {String} message - Information about what failed. 7675 * @property {String} source - The tile source that failed. 7676 * @property {?Object} userData - Arbitrary subscriber-defined object. 7677 */ 7678 _this.raiseEvent( 'open-failed', failEvent ); 7679 } 7680 } 7681 }; 7682 7683 var doOne = function(options) { 7684 if (!$.isPlainObject(options) || !options.tileSource) { 7685 options = { 7686 tileSource: options 7687 }; 7688 } 7689 7690 if (options.index !== undefined) { 7691 $.console.error('[Viewer.open] setting indexes here is not supported; use addTiledImage instead'); 7692 delete options.index; 7693 } 7694 7695 if (options.collectionImmediately === undefined) { 7696 options.collectionImmediately = true; 7697 } 7698 7699 var originalSuccess = options.success; 7700 options.success = function(event) { 7701 successes++; 7702 7703 // TODO: now that options has other things besides tileSource, the overlays 7704 // should probably be at the options level, not the tileSource level. 7705 if (options.tileSource.overlays) { 7706 for (var i = 0; i < options.tileSource.overlays.length; i++) { 7707 _this.addOverlay(options.tileSource.overlays[i]); 7708 } 7709 } 7710 7711 if (originalSuccess) { 7712 originalSuccess(event); 7713 } 7714 7715 checkCompletion(); 7716 }; 7717 7718 var originalError = options.error; 7719 options.error = function(event) { 7720 failures++; 7721 7722 if (!failEvent) { 7723 failEvent = event; 7724 } 7725 7726 if (originalError) { 7727 originalError(event); 7728 } 7729 7730 checkCompletion(); 7731 }; 7732 7733 _this.addTiledImage(options); 7734 }; 7735 7736 // TileSources 7737 for (var i = 0; i < tileSources.length; i++) { 7738 doOne(tileSources[i]); 7739 } 7740 7741 return this; 7742 }, 7743 7744 7745 /** 7746 * @function 7747 * @return {OpenSeadragon.Viewer} Chainable. 7748 * @fires OpenSeadragon.Viewer.event:close 7749 */ 7750 close: function ( ) { 7751 if ( !THIS[ this.hash ] ) { 7752 //this viewer has already been destroyed: returning immediately 7753 return this; 7754 } 7755 7756 this._opening = false;
vendor: 18,052 bytes, lines 7757-8250
7757 7758 if ( this.navigator ) { 7759 this.navigator.close(); 7760 } 7761 7762 if (!this.preserveOverlays) { 7763 this.clearOverlays(); 7764 this.overlaysContainer.innerHTML = ""; 7765 } 7766 7767 THIS[ this.hash ].animating = false; 7768 this.world.removeAll(); 7769 this.imageLoader.clear(); 7770 7771 /** 7772 * Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}). 7773 * 7774 * @event close 7775 * @memberof OpenSeadragon.Viewer 7776 * @type {object} 7777 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7778 * @property {?Object} userData - Arbitrary subscriber-defined object. 7779 */ 7780 this.raiseEvent( 'close' ); 7781 7782 return this; 7783 }, 7784 7785 7786 /** 7787 * Function to destroy the viewer and clean up everything created by OpenSeadragon. 7788 * 7789 * Example: 7790 * var viewer = OpenSeadragon({ 7791 * [...] 7792 * }); 7793 * 7794 * //when you are done with the viewer: 7795 * viewer.destroy(); 7796 * viewer = null; //important 7797 * 7798 * @function 7799 */ 7800 destroy: function( ) { 7801 if ( !THIS[ this.hash ] ) { 7802 //this viewer has already been destroyed: returning immediately 7803 return; 7804 } 7805 7806 this.close(); 7807 7808 this.clearOverlays(); 7809 this.overlaysContainer.innerHTML = ""; 7810 7811 //TODO: implement this... 7812 //this.unbindSequenceControls() 7813 //this.unbindStandardControls() 7814 7815 if (this.referenceStrip) { 7816 this.referenceStrip.destroy(); 7817 this.referenceStrip = null; 7818 } 7819 7820 if ( this._updateRequestId !== null ) { 7821 $.cancelAnimationFrame( this._updateRequestId ); 7822 this._updateRequestId = null; 7823 } 7824 7825 if ( this.drawer ) { 7826 this.drawer.destroy(); 7827 } 7828 7829 this.removeAllHandlers(); 7830 7831 // Go through top element (passed to us) and remove all children 7832 // Use removeChild to make sure it handles SVG or any non-html 7833 // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15 7834 if (this.element){ 7835 while (this.element.firstChild) { 7836 this.element.removeChild(this.element.firstChild); 7837 } 7838 } 7839 7840 // destroy the mouse trackers 7841 if (this.innerTracker){ 7842 this.innerTracker.destroy(); 7843 } 7844 if (this.outerTracker){ 7845 this.outerTracker.destroy(); 7846 } 7847 7848 THIS[ this.hash ] = null; 7849 delete THIS[ this.hash ]; 7850 7851 // clear all our references to dom objects 7852 this.canvas = null; 7853 this.container = null; 7854 7855 // clear our reference to the main element - they will need to pass it in again, creating a new viewer 7856 this.element = null; 7857 }, 7858 7859 /** 7860 * @function 7861 * @return {Boolean} 7862 */ 7863 isMouseNavEnabled: function () { 7864 return this.innerTracker.isTracking(); 7865 }, 7866 7867 /** 7868 * @function 7869 * @param {Boolean} enabled - true to enable, false to disable 7870 * @return {OpenSeadragon.Viewer} Chainable. 7871 * @fires OpenSeadragon.Viewer.event:mouse-enabled 7872 */ 7873 setMouseNavEnabled: function( enabled ){ 7874 this.innerTracker.setTracking( enabled ); 7875 this.outerTracker.setTracking( enabled ); 7876 /** 7877 * Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}). 7878 * 7879 * @event mouse-enabled 7880 * @memberof OpenSeadragon.Viewer 7881 * @type {object} 7882 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7883 * @property {Boolean} enabled 7884 * @property {?Object} userData - Arbitrary subscriber-defined object. 7885 */ 7886 this.raiseEvent( 'mouse-enabled', { enabled: enabled } ); 7887 return this; 7888 }, 7889 7890 7891 /** 7892 * @function 7893 * @return {Boolean} 7894 */ 7895 areControlsEnabled: function () { 7896 var enabled = this.controls.length, 7897 i; 7898 for( i = 0; i < this.controls.length; i++ ){ 7899 enabled = enabled && this.controls[ i ].isVisible(); 7900 } 7901 return enabled; 7902 }, 7903 7904 7905 /** 7906 * Shows or hides the controls (e.g. the default navigation buttons). 7907 * 7908 * @function 7909 * @param {Boolean} true to show, false to hide. 7910 * @return {OpenSeadragon.Viewer} Chainable. 7911 * @fires OpenSeadragon.Viewer.event:controls-enabled 7912 */ 7913 setControlsEnabled: function( enabled ) { 7914 if( enabled ){ 7915 abortControlsAutoHide( this ); 7916 } else { 7917 beginControlsAutoHide( this ); 7918 } 7919 /** 7920 * Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}). 7921 * 7922 * @event controls-enabled 7923 * @memberof OpenSeadragon.Viewer 7924 * @type {object} 7925 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7926 * @property {Boolean} enabled 7927 * @property {?Object} userData - Arbitrary subscriber-defined object. 7928 */ 7929 this.raiseEvent( 'controls-enabled', { enabled: enabled } ); 7930 return this; 7931 }, 7932 7933 /** 7934 * Turns debugging mode on or off for this viewer. 7935 * 7936 * @function 7937 * @param {Boolean} true to turn debug on, false to turn debug off. 7938 */ 7939 setDebugMode: function(debugMode){ 7940 7941 for (var i = 0; i < this.world.getItemCount(); i++) { 7942 this.world.getItemAt(i).debugMode = debugMode; 7943 } 7944 7945 this.debugMode = debugMode; 7946 this.forceRedraw(); 7947 }, 7948 7949 /** 7950 * @function 7951 * @return {Boolean} 7952 */ 7953 isFullPage: function () { 7954 return THIS[ this.hash ].fullPage; 7955 }, 7956 7957 7958 /** 7959 * Toggle full page mode. 7960 * @function 7961 * @param {Boolean} fullPage 7962 * If true, enter full page mode. If false, exit full page mode. 7963 * @return {OpenSeadragon.Viewer} Chainable. 7964 * @fires OpenSeadragon.Viewer.event:pre-full-page 7965 * @fires OpenSeadragon.Viewer.event:full-page 7966 */ 7967 setFullPage: function( fullPage ) { 7968 7969 var body = document.body, 7970 bodyStyle = body.style, 7971 docStyle = document.documentElement.style, 7972 _this = this, 7973 nodes, 7974 i; 7975 7976 //don't bother modifying the DOM if we are already in full page mode. 7977 if ( fullPage == this.isFullPage() ) { 7978 return this; 7979 } 7980 7981 var fullPageEventArgs = { 7982 fullPage: fullPage, 7983 preventDefaultAction: false 7984 }; 7985 /** 7986 * Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}). 7987 * 7988 * @event pre-full-page 7989 * @memberof OpenSeadragon.Viewer 7990 * @type {object} 7991 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7992 * @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode. 7993 * @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false. 7994 * @property {?Object} userData - Arbitrary subscriber-defined object. 7995 */ 7996 this.raiseEvent( 'pre-full-page', fullPageEventArgs ); 7997 if ( fullPageEventArgs.preventDefaultAction ) { 7998 return this; 7999 } 8000 8001 if ( fullPage ) { 8002 8003 this.elementSize = $.getElementSize( this.element ); 8004 this.pageScroll = $.getPageScroll(); 8005 8006 this.elementMargin = this.element.style.margin; 8007 this.element.style.margin = "0"; 8008 this.elementPadding = this.element.style.padding; 8009 this.element.style.padding = "0"; 8010 8011 this.bodyMargin = bodyStyle.margin; 8012 this.docMargin = docStyle.margin; 8013 bodyStyle.margin = "0"; 8014 docStyle.margin = "0"; 8015 8016 this.bodyPadding = bodyStyle.padding; 8017 this.docPadding = docStyle.padding; 8018 bodyStyle.padding = "0"; 8019 docStyle.padding = "0"; 8020 8021 this.bodyWidth = bodyStyle.width; 8022 this.docWidth = docStyle.width; 8023 bodyStyle.width = "100%"; 8024 docStyle.width = "100%"; 8025 8026 this.bodyHeight = bodyStyle.height; 8027 this.docHeight = docStyle.height; 8028 bodyStyle.height = "100%"; 8029 docStyle.height = "100%"; 8030 8031 //when entering full screen on the ipad it wasn't sufficient to leave 8032 //the body intact as only only the top half of the screen would 8033 //respond to touch events on the canvas, while the bottom half treated 8034 //them as touch events on the document body. Thus we remove and store 8035 //the bodies elements and replace them when we leave full screen. 8036 this.previousBody = []; 8037 THIS[ this.hash ].prevElementParent = this.element.parentNode; 8038 THIS[ this.hash ].prevNextSibling = this.element.nextSibling; 8039 THIS[ this.hash ].prevElementWidth = this.element.style.width; 8040 THIS[ this.hash ].prevElementHeight = this.element.style.height; 8041 nodes = body.childNodes.length; 8042 for ( i = 0; i < nodes; i++ ) { 8043 this.previousBody.push( body.childNodes[ 0 ] ); 8044 body.removeChild( body.childNodes[ 0 ] ); 8045 } 8046 8047 //If we've got a toolbar, we need to enable the user to use css to 8048 //preserve it in fullpage mode 8049 if ( this.toolbar && this.toolbar.element ) { 8050 //save a reference to the parent so we can put it back 8051 //in the long run we need a better strategy 8052 this.toolbar.parentNode = this.toolbar.element.parentNode; 8053 this.toolbar.nextSibling = this.toolbar.element.nextSibling; 8054 body.appendChild( this.toolbar.element ); 8055 8056 //Make sure the user has some ability to style the toolbar based 8057 //on the mode 8058 $.addClass( this.toolbar.element, 'fullpage' ); 8059 } 8060 8061 $.addClass( this.element, 'fullpage' ); 8062 body.appendChild( this.element ); 8063 8064 this.element.style.height = $.getWindowSize().y + 'px'; 8065 this.element.style.width = $.getWindowSize().x + 'px'; 8066 8067 if ( this.toolbar && this.toolbar.element ) { 8068 this.element.style.height = ( 8069 $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y 8070 ) + 'px'; 8071 } 8072 8073 THIS[ this.hash ].fullPage = true; 8074 8075 // mouse will be inside container now 8076 $.delegate( this, onContainerEnter )( {} ); 8077 8078 } else { 8079 8080 this.element.style.margin = this.elementMargin; 8081 this.element.style.padding = this.elementPadding; 8082 8083 bodyStyle.margin = this.bodyMargin; 8084 docStyle.margin = this.docMargin; 8085 8086 bodyStyle.padding = this.bodyPadding; 8087 docStyle.padding = this.docPadding; 8088 8089 bodyStyle.width = this.bodyWidth; 8090 docStyle.width = this.docWidth; 8091 8092 bodyStyle.height = this.bodyHeight; 8093 docStyle.height = this.docHeight; 8094 8095 body.removeChild( this.element ); 8096 nodes = this.previousBody.length; 8097 for ( i = 0; i < nodes; i++ ) { 8098 body.appendChild( this.previousBody.shift() ); 8099 } 8100 8101 $.removeClass( this.element, 'fullpage' ); 8102 THIS[ this.hash ].prevElementParent.insertBefore( 8103 this.element, 8104 THIS[ this.hash ].prevNextSibling 8105 ); 8106 8107 //If we've got a toolbar, we need to enable the user to use css to 8108 //reset it to its original state 8109 if ( this.toolbar && this.toolbar.element ) { 8110 body.removeChild( this.toolbar.element ); 8111 8112 //Make sure the user has some ability to style the toolbar based 8113 //on the mode 8114 $.removeClass( this.toolbar.element, 'fullpage' ); 8115 8116 this.toolbar.parentNode.insertBefore( 8117 this.toolbar.element, 8118 this.toolbar.nextSibling 8119 ); 8120 delete this.toolbar.parentNode; 8121 delete this.toolbar.nextSibling; 8122 } 8123 8124 this.element.style.width = THIS[ this.hash ].prevElementWidth; 8125 this.element.style.height = THIS[ this.hash ].prevElementHeight; 8126 8127 // After exiting fullPage or fullScreen, it can take some time 8128 // before the browser can actually set the scroll. 8129 var restoreScrollCounter = 0; 8130 var restoreScroll = function() { 8131 $.setPageScroll( _this.pageScroll ); 8132 var pageScroll = $.getPageScroll(); 8133 restoreScrollCounter++; 8134 if (restoreScrollCounter < 10 && 8135 (pageScroll.x !== _this.pageScroll.x || 8136 pageScroll.y !== _this.pageScroll.y)) { 8137 $.requestAnimationFrame( restoreScroll ); 8138 } 8139 }; 8140 $.requestAnimationFrame( restoreScroll ); 8141 8142 THIS[ this.hash ].fullPage = false; 8143 8144 // mouse will likely be outside now 8145 $.delegate( this, onContainerExit )( { } ); 8146 8147 } 8148 8149 if ( this.navigator && this.viewport ) { 8150 this.navigator.update( this.viewport ); 8151 } 8152 8153 /** 8154 * Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}). 8155 * 8156 * @event full-page 8157 * @memberof OpenSeadragon.Viewer 8158 * @type {object} 8159 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8160 * @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode. 8161 * @property {?Object} userData - Arbitrary subscriber-defined object. 8162 */ 8163 this.raiseEvent( 'full-page', { fullPage: fullPage } ); 8164 8165 return this; 8166 }, 8167 8168 /** 8169 * Toggle full screen mode if supported. Toggle full page mode otherwise. 8170 * @function 8171 * @param {Boolean} fullScreen 8172 * If true, enter full screen mode. If false, exit full screen mode. 8173 * @return {OpenSeadragon.Viewer} Chainable. 8174 * @fires OpenSeadragon.Viewer.event:pre-full-screen 8175 * @fires OpenSeadragon.Viewer.event:full-screen 8176 */ 8177 setFullScreen: function( fullScreen ) { 8178 var _this = this; 8179 8180 if ( !$.supportsFullScreen ) { 8181 return this.setFullPage( fullScreen ); 8182 } 8183 8184 if ( $.isFullScreen() === fullScreen ) { 8185 return this; 8186 } 8187 8188 var fullScreeEventArgs = { 8189 fullScreen: fullScreen, 8190 preventDefaultAction: false 8191 }; 8192 /** 8193 * Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}). 8194 * Note: the pre-full-screen event is not raised when the user is exiting 8195 * full-screen mode by pressing the Esc key. In that case, consider using 8196 * the full-screen, pre-full-page or full-page events. 8197 * 8198 * @event pre-full-screen 8199 * @memberof OpenSeadragon.Viewer 8200 * @type {object} 8201 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8202 * @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode. 8203 * @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false. 8204 * @property {?Object} userData - Arbitrary subscriber-defined object. 8205 */ 8206 this.raiseEvent( 'pre-full-screen', fullScreeEventArgs ); 8207 if ( fullScreeEventArgs.preventDefaultAction ) { 8208 return this; 8209 } 8210 8211 if ( fullScreen ) { 8212 8213 this.setFullPage( true ); 8214 // If the full page mode is not actually entered, we need to prevent 8215 // the full screen mode. 8216 if ( !this.isFullPage() ) { 8217 return this; 8218 } 8219 8220 this.fullPageStyleWidth = this.element.style.width; 8221 this.fullPageStyleHeight = this.element.style.height; 8222 this.element.style.width = '100%'; 8223 this.element.style.height = '100%'; 8224 8225 var onFullScreenChange = function() { 8226 var isFullScreen = $.isFullScreen(); 8227 if ( !isFullScreen ) { 8228 $.removeEvent( document, $.fullScreenEventName, onFullScreenChange ); 8229 $.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange ); 8230 8231 _this.setFullPage( false ); 8232 if ( _this.isFullPage() ) { 8233 _this.element.style.width = _this.fullPageStyleWidth; 8234 _this.element.style.height = _this.fullPageStyleHeight; 8235 } 8236 } 8237 if ( _this.navigator && _this.viewport ) { 8238 //09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout 8239 setTimeout(function(){ 8240 _this.navigator.update( _this.viewport ); 8241 }); 8242 } 8243 /** 8244 * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}). 8245 * 8246 * @event full-screen 8247 * @memberof OpenSeadragon.Viewer 8248 * @type {object} 8249 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8250 * @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode.
8251 * @property {?Object} userData - Arbitrary subscriber-defined object. 8252 */ 8253 _this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } ); 8254 }; 8255 $.addEvent( document, $.fullScreenEventName, onFullScreenChange ); 8256 $.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange ); 8257 8258 $.requestFullScreen( document.body ); 8259 8260 } else { 8261 $.exitFullScreen(); 8262 } 8263 return this; 8264 }, 8265 8266 /** 8267 * @function 8268 * @return {Boolean} 8269 */ 8270 isVisible: function () { 8271 return this.container.style.visibility != "hidden"; 8272 }, 8273 8274 8275 /** 8276 * @function 8277 * @param {Boolean} visible 8278 * @return {OpenSeadragon.Viewer} Chainable. 8279 * @fires OpenSeadragon.Viewer.event:visible 8280 */ 8281 setVisible: function( visible ){ 8282 this.container.style.visibility = visible ? "" : "hidden"; 8283 /** 8284 * Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}). 8285 * 8286 * @event visible 8287 * @memberof OpenSeadragon.Viewer 8288 * @type {object} 8289 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8290 * @property {Boolean} visible 8291 * @property {?Object} userData - Arbitrary subscriber-defined object. 8292 */ 8293 this.raiseEvent( 'visible', { visible: visible } ); 8294 return this; 8295 }, 8296 8297 /** 8298 * Add a tiled image to the viewer. 8299 * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open} 8300 * supports except arrays of images. 8301 * Note that you can specify options.width or options.height, but not both. 8302 * The other dimension will be calculated according to the item's aspect ratio. 8303 * If collectionMode is on (see {@link OpenSeadragon.Options}), the new image is 8304 * automatically arranged with the others. 8305 * @function 8306 * @param {Object} options 8307 * @param {String|Object|Function} options.tileSource - The TileSource specifier. 8308 * A String implies a url used to determine the tileSource implementation 8309 * based on the file extension of url. JSONP is implied by *.js, 8310 * otherwise the url is retrieved as text and the resulting text is 8311 * introspected to determine if its json, xml, or text and parsed. 8312 * An Object implies an inline configuration which has a single 8313 * property sufficient for being able to determine tileSource 8314 * implementation. If the object has a property which is a function 8315 * named 'getTileUrl', it is treated as a custom TileSource. 8316 * @param {Number} [options.index] The index of the item. Added on top of 8317 * all other items if not specified. 8318 * @param {Boolean} [options.replace=false] If true, the item at options.index will be 8319 * removed and the new item is added in its place. options.tileSource will be 8320 * interpreted and fetched if necessary before the old item is removed to avoid leaving 8321 * a gap in the world. 8322 * @param {Number} [options.x=0] The X position for the image in viewport coordinates. 8323 * @param {Number} [options.y=0] The Y position for the image in viewport coordinates. 8324 * @param {Number} [options.width=1] The width for the image in viewport coordinates. 8325 * @param {Number} [options.height] The height for the image in viewport coordinates. 8326 * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates 8327 * to fit the image into. If specified, x, y, width and height get ignored. 8328 * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER] 8329 * How to anchor the image in the bounds if options.fitBounds is set. 8330 * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to 8331 * (portions of the image outside of this area will not be visible). Only works on 8332 * browsers that support the HTML5 canvas. 8333 * @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden) 8334 * @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks) 8335 * @param {Number} [options.degrees=0] Initial rotation of the tiled image around 8336 * its top left corner in degrees. 8337 * @param {String} [options.compositeOperation] How the image is composited onto other images. 8338 * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image, 8339 * overriding viewer.crossOriginPolicy. 8340 * @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX 8341 * @param {Boolean} [options.loadTilesWithAjax] 8342 * Whether to load tile data using AJAX requests. 8343 * Defaults to the setting in {@link OpenSeadragon.Options}. 8344 * @param {Object} [options.ajaxHeaders] 8345 * A set of headers to include when making tile AJAX requests. 8346 * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}. 8347 * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any). 8348 * requests. 8349 * @param {Function} [options.success] A function that gets called when the image is 8350 * successfully added. It's passed the event object which contains a single property: 8351 * "item", which is the resulting instance of TiledImage. 8352 * @param {Function}
8352 [options.error] A function that gets called if the image is 8353 * unable to be added. It's passed the error event object, which contains "message" 8354 * and "source" properties. 8355 * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on, 8356 * specifies whether to snap to the new arrangement immediately or to animate to it. 8357 * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. 8358 * @fires OpenSeadragon.World.event:add-item 8359 * @fires OpenSeadragon.Viewer.event:add-item-failed 8360 */ 8361 addTiledImage: function( options ) { 8362 $.console.assert(options, "[Viewer.addTiledImage] options is required"); 8363 $.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required"); 8364 $.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()), 8365 "[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world"); 8366 8367 var _this = this; 8368 8369 if (options.replace) { 8370 options.replaceItem = _this.world.getItemAt(options.index); 8371 } 8372 8373 this._hideMessage(); 8374 8375 if (options.placeholderFillStyle === undefined) { 8376 options.placeholderFillStyle = this.placeholderFillStyle; 8377 } 8378 if (options.opacity === undefined) { 8379 options.opacity = this.opacity; 8380 } 8381 if (options.preload === undefined) { 8382 options.preload = this.preload; 8383 } 8384 if (options.compositeOperation === undefined) { 8385 options.compositeOperation = this.compositeOperation; 8386 } 8387 if (options.crossOriginPolicy === undefined) { 8388 options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy; 8389 } 8390 if (options.ajaxWithCredentials === undefined) { 8391 options.ajaxWithCredentials = this.ajaxWithCredentials; 8392 } 8393 if (options.loadTilesWithAjax === undefined) { 8394 options.loadTilesWithAjax = this.loadTilesWithAjax; 8395 } 8396 if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) { 8397 options.ajaxHeaders = this.ajaxHeaders; 8398 } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) { 8399 options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders); 8400 } 8401 8402 var myQueueItem = { 8403 options: options 8404 }; 8405 8406 function raiseAddItemFailed( event ) { 8407 for (var i = 0; i < _this._loadQueue.length; i++) { 8408 if (_this._loadQueue[i] === myQueueItem) { 8409 _this._loadQueue.splice(i, 1); 8410 break; 8411 } 8412 } 8413 8414 if (_this._loadQueue.length === 0) { 8415 refreshWorld(myQueueItem); 8416 } 8417 8418 /** 8419 * Raised when an error occurs while adding a item. 8420 * @event add-item-failed 8421 * @memberOf OpenSeadragon.Viewer 8422 * @type {object} 8423 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8424 * @property {String} message 8425 * @property {String} source 8426 * @property {Object} options The options passed to the addTiledImage method. 8427 * @property {?Object} userData - Arbitrary subscriber-defined object. 8428 */ 8429 _this.raiseEvent( 'add-item-failed', event ); 8430 8431 if (options.error) { 8432 options.error(event); 8433 } 8434 } 8435 8436 function refreshWorld(theItem) { 8437 if (_this.collectionMode) { 8438 _this.world.arrange({ 8439 immediately: theItem.options.collectionImmediately, 8440 rows: _this.collectionRows, 8441 columns: _this.collectionColumns, 8442 layout: _this.collectionLayout, 8443 tileSize: _this.collectionTileSize, 8444 tileMargin: _this.collectionTileMargin 8445 }); 8446 _this.world.setAutoRefigureSizes(true); 8447 } 8448 } 8449 8450 if ($.isArray(options.tileSource)) { 8451 setTimeout(function() { 8452 raiseAddItemFailed({ 8453 message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.", 8454 source: options.tileSource, 8455 options: options 8456 }); 8457 }); 8458 return; 8459 } 8460 8461 this._loadQueue.push(myQueueItem); 8462 8463 function processReadyItems() { 8464 var queueItem, tiledImage, optionsClone; 8465 while (_this._loadQueue.length) { 8466 queueItem = _this._loadQueue[0]; 8467 if (!queueItem.tileSource) { 8468 break; 8469 } 8470 8471 _this._loadQueue.splice(0, 1); 8472 8473 if (queueItem.options.replace) { 8474 var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem); 8475 if (newIndex != -1) { 8476 queueItem.options.index = newIndex; 8477 } 8478 _this.world.removeItem(queueItem.options.replaceItem); 8479 } 8480 8481 tiledImage = new $.TiledImage({ 8482 viewer: _this, 8483 source: queueItem.tileSource, 8484 viewport: _this.viewport, 8485 drawer: _this.drawer,
8486 tileCache: _this.tileCache, 8487 imageLoader: _this.imageLoader, 8488 x: queueItem.options.x, 8489 y: queueItem.options.y, 8490 width: queueItem.options.width, 8491 height: queueItem.options.height, 8492 fitBounds: queueItem.options.fitBounds, 8493 fitBoundsPlacement: queueItem.options.fitBoundsPlacement, 8494 clip: queueItem.options.clip, 8495 placeholderFillStyle: queueItem.options.placeholderFillStyle, 8496 opacity: queueItem.options.opacity, 8497 preload: queueItem.options.preload, 8498 degrees: queueItem.options.degrees, 8499 compositeOperation: queueItem.options.compositeOperation, 8500 springStiffness: _this.springStiffness, 8501 animationTime: _this.animationTime, 8502 minZoomImageRatio: _this.minZoomImageRatio, 8503 wrapHorizontal: _this.wrapHorizontal, 8504 wrapVertical: _this.wrapVertical, 8505 immediateRender: _this.immediateRender, 8506 blendTime: _this.blendTime, 8507 alwaysBlend: _this.alwaysBlend, 8508 minPixelRatio: _this.minPixelRatio, 8509 smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom, 8510 iOSDevice: _this.iOSDevice, 8511 crossOriginPolicy: queueItem.options.crossOriginPolicy, 8512 ajaxWithCredentials: queueItem.options.ajaxWithCredentials, 8513 loadTilesWithAjax: queueItem.options.loadTilesWithAjax, 8514 ajaxHeaders: queueItem.options.ajaxHeaders, 8515 debugMode: _this.debugMode 8516 }); 8517 8518 if (_this.collectionMode) { 8519 _this.world.setAutoRefigureSizes(false); 8520 } 8521 _this.world.addItem( tiledImage, { 8522 index: queueItem.options.index 8523 }); 8524 8525 if (_this._loadQueue.length === 0) { 8526 //this restores the autoRefigureSizes flag to true. 8527 refreshWorld(queueItem); 8528 } 8529 8530 if (_this.world.getItemCount() === 1 && !_this.preserveViewport) { 8531 _this.viewport.goHome(true); 8532 } 8533 8534 if (_this.navigator) { 8535 optionsClone = $.extend({}, queueItem.options, { 8536 replace: false, // navigator already removed the layer, nothing to replace 8537 originalTiledImage: tiledImage, 8538 tileSource: queueItem.tileSource 8539 }); 8540 8541 _this.navigator.addTiledImage(optionsClone); 8542 } 8543 8544 if (queueItem.options.success) { 8545 queueItem.options.success({ 8546 item: tiledImage 8547 }); 8548 } 8549 } 8550 } 8551 8552 getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) { 8553 8554 myQueueItem.tileSource = tileSource; 8555 8556 // add everybody at the front of the queue that's ready to go 8557 processReadyItems(); 8558 }, function( event ) { 8559 event.options = options; 8560 raiseAddItemFailed(event); 8561 8562 // add everybody at the front of the queue that's ready to go 8563 processReadyItems(); 8564 } ); 8565 }, 8566 8567 /** 8568 * Add a simple image to the viewer. 8569 * The options are the same as the ones in {@link OpenSeadragon.Viewer#addTiledImage} 8570 * except for options.tileSource which is replaced by options.url. 8571 * @function 8572 * @param {Object} options - See {@link OpenSeadragon.Viewer#addTiledImage} 8573 * for all the options 8574 * @param {String} options.url - The URL of the image to add. 8575 * @fires OpenSeadragon.World.event:add-item 8576 * @fires OpenSeadragon.Viewer.event:add-item-failed 8577 */ 8578 addSimpleImage: function(options) { 8579 $.console.assert(options, "[Viewer.addSimpleImage] options is required");
8580 $.console.assert(options.url, "[Viewer.addSimpleImage] options.url is required"); 8581 8582 var opts = $.extend({}, options, { 8583 tileSource: { 8584 type: 'image', 8585 url: options.url 8586 } 8587 }); 8588 delete opts.url; 8589 this.addTiledImage(opts); 8590 }, 8591 8592 // deprecated 8593 addLayer: function( options ) { 8594 var _this = this; 8595 8596 $.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." ); 8597 8598 var optionsClone = $.extend({}, options, { 8599 success: function(event) { 8600 _this.raiseEvent("add-layer", { 8601 options: options, 8602 drawer: event.item 8603 }); 8604 }, 8605 error: function(event) { 8606 _this.raiseEvent("add-layer-failed", event); 8607 } 8608 }); 8609 8610 this.addTiledImage(optionsClone); 8611 return this; 8612 }, 8613 8614 // deprecated 8615 getLayerAtLevel: function( level ) { 8616 $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." ); 8617 return this.world.getItemAt(level); 8618 }, 8619 8620 // deprecated 8621 getLevelOfLayer: function( drawer ) { 8622 $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated;
8622 use World.getIndexOfItem() instead." ); 8623 return this.world.getIndexOfItem(drawer); 8624 }, 8625 8626 // deprecated 8627 getLayersCount: function() { 8628 $.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." ); 8629 return this.world.getItemCount(); 8630 }, 8631 8632 // deprecated 8633 setLayerLevel: function( drawer, level ) { 8634 $.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." ); 8635 return this.world.setItemIndex(drawer, level); 8636 }, 8637 8638 // deprecated 8639 removeLayer: function( drawer ) { 8640 $.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." ); 8641 return this.world.removeItem(drawer); 8642 }, 8643 8644 /** 8645 * Force the viewer to redraw its contents. 8646 * @returns {OpenSeadragon.Viewer} Chainable. 8647 */ 8648 forceRedraw: function() { 8649 THIS[ this.hash ].forceRedraw = true; 8650 return this; 8651 }, 8652 8653 /** 8654 * @function 8655 * @return {OpenSeadragon.Viewer} Chainable. 8656 */ 8657 bindSequenceControls: function(){ 8658
vendor: 14,242 bytes, lines 8659-8954
8659 ////////////////////////////////////////////////////////////////////////// 8660 // Image Sequence Controls 8661 ////////////////////////////////////////////////////////////////////////// 8662 var onFocusHandler = $.delegate( this, onFocus ), 8663 onBlurHandler = $.delegate( this, onBlur ), 8664 onNextHandler = $.delegate( this, onNext ), 8665 onPreviousHandler = $.delegate( this, onPrevious ), 8666 navImages = this.navImages, 8667 useGroup = true; 8668 8669 if( this.showSequenceControl ){ 8670 8671 if( this.previousButton || this.nextButton ){ 8672 //if we are binding to custom buttons then layout and 8673 //grouping is the responsibility of the page author 8674 useGroup = false; 8675 } 8676 8677 this.previousButton = new $.Button({ 8678 element: this.previousButton ? $.getElement( this.previousButton ) : null, 8679 clickTimeThreshold: this.clickTimeThreshold, 8680 clickDistThreshold: this.clickDistThreshold, 8681 tooltip: $.getString( "Tooltips.PreviousPage" ), 8682 srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ), 8683 srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ), 8684 srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ), 8685 srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ), 8686 onRelease: onPreviousHandler, 8687 onFocus: onFocusHandler, 8688 onBlur: onBlurHandler 8689 }); 8690 8691 this.nextButton = new $.Button({ 8692 element: this.nextButton ? $.getElement( this.nextButton ) : null, 8693 clickTimeThreshold: this.clickTimeThreshold, 8694 clickDistThreshold: this.clickDistThreshold, 8695 tooltip: $.getString( "Tooltips.NextPage" ), 8696 srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ), 8697 srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ), 8698 srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ), 8699 srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ), 8700 onRelease: onNextHandler, 8701 onFocus: onFocusHandler, 8702 onBlur: onBlurHandler 8703 }); 8704 8705 if( !this.navPrevNextWrap ){ 8706 this.previousButton.disable(); 8707 } 8708 8709 if (!this.tileSources || !this.tileSources.length) { 8710 this.nextButton.disable(); 8711 } 8712 8713 if( useGroup ){ 8714 this.paging = new $.ButtonGroup({ 8715 buttons: [ 8716 this.previousButton, 8717 this.nextButton 8718 ], 8719 clickTimeThreshold: this.clickTimeThreshold, 8720 clickDistThreshold: this.clickDistThreshold 8721 }); 8722 8723 this.pagingControl = this.paging.element; 8724 8725 if( this.toolbar ){ 8726 this.toolbar.addControl( 8727 this.pagingControl, 8728 {anchor: $.ControlAnchor.BOTTOM_RIGHT} 8729 ); 8730 }else{ 8731 this.addControl( 8732 this.pagingControl, 8733 {anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT} 8734 ); 8735 } 8736 } 8737 } 8738 return this; 8739 }, 8740 8741 8742 /** 8743 * @function 8744 * @return {OpenSeadragon.Viewer} Chainable. 8745 */ 8746 bindStandardControls: function(){ 8747 ////////////////////////////////////////////////////////////////////////// 8748 // Navigation Controls 8749 ////////////////////////////////////////////////////////////////////////// 8750 var beginZoomingInHandler = $.delegate( this, beginZoomingIn ), 8751 endZoomingHandler = $.delegate( this, endZooming ), 8752 doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ), 8753 beginZoomingOutHandler = $.delegate( this, beginZoomingOut ), 8754 doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ), 8755 onHomeHandler = $.delegate( this, onHome ), 8756 onFullScreenHandler = $.delegate( this, onFullScreen ), 8757 onRotateLeftHandler = $.delegate( this, onRotateLeft ), 8758 onRotateRightHandler = $.delegate( this, onRotateRight ), 8759 onFlipHandler = $.delegate( this, onFlip), 8760 onFocusHandler = $.delegate( this, onFocus ), 8761 onBlurHandler = $.delegate( this, onBlur ), 8762 navImages = this.navImages, 8763 buttons = [], 8764 useGroup = true; 8765 8766 8767 if ( this.showNavigationControl ) { 8768 8769 if( this.zoomInButton || this.zoomOutButton || 8770 this.homeButton || this.fullPageButton || 8771 this.rotateLeftButton || this.rotateRightButton || 8772 this.flipButton ) { 8773 //if we are binding to custom buttons then layout and 8774 //grouping is the responsibility of the page author 8775 useGroup = false; 8776 } 8777 8778 if ( this.showZoomControl ) { 8779 buttons.push( this.zoomInButton = new $.Button({ 8780 element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null, 8781 clickTimeThreshold: this.clickTimeThreshold, 8782 clickDistThreshold: this.clickDistThreshold, 8783 tooltip: $.getString( "Tooltips.ZoomIn" ), 8784 srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ), 8785 srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ), 8786 srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ), 8787 srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ), 8788 onPress: beginZoomingInHandler, 8789 onRelease: endZoomingHandler, 8790 onClick: doSingleZoomInHandler, 8791 onEnter: beginZoomingInHandler, 8792 onExit: endZoomingHandler, 8793 onFocus: onFocusHandler, 8794 onBlur: onBlurHandler 8795 })); 8796 8797 buttons.push( this.zoomOutButton = new $.Button({ 8798 element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null, 8799 clickTimeThreshold: this.clickTimeThreshold, 8800 clickDistThreshold: this.clickDistThreshold, 8801 tooltip: $.getString( "Tooltips.ZoomOut" ), 8802 srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ), 8803 srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ), 8804 srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ), 8805 srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ), 8806 onPress: beginZoomingOutHandler, 8807 onRelease: endZoomingHandler, 8808 onClick: doSingleZoomOutHandler, 8809 onEnter: beginZoomingOutHandler, 8810 onExit: endZoomingHandler, 8811 onFocus: onFocusHandler, 8812 onBlur: onBlurHandler 8813 })); 8814 } 8815 8816 if ( this.showHomeControl ) { 8817 buttons.push( this.homeButton = new $.Button({ 8818 element: this.homeButton ? $.getElement( this.homeButton ) : null, 8819 clickTimeThreshold: this.clickTimeThreshold, 8820 clickDistThreshold: this.clickDistThreshold, 8821 tooltip: $.getString( "Tooltips.Home" ), 8822 srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ), 8823 srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ), 8824 srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ), 8825 srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ), 8826 onRelease: onHomeHandler, 8827 onFocus: onFocusHandler, 8828 onBlur: onBlurHandler 8829 })); 8830 } 8831 8832 if ( this.showFullPageControl ) { 8833 buttons.push( this.fullPageButton = new $.Button({ 8834 element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null, 8835 clickTimeThreshold: this.clickTimeThreshold, 8836 clickDistThreshold: this.clickDistThreshold, 8837 tooltip: $.getString( "Tooltips.FullPage" ), 8838 srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ), 8839 srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ), 8840 srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ), 8841 srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ), 8842 onRelease: onFullScreenHandler, 8843 onFocus: onFocusHandler, 8844 onBlur: onBlurHandler 8845 })); 8846 } 8847 8848 if ( this.showRotationControl ) { 8849 buttons.push( this.rotateLeftButton = new $.Button({ 8850 element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null, 8851 clickTimeThreshold: this.clickTimeThreshold, 8852 clickDistThreshold: this.clickDistThreshold, 8853 tooltip: $.getString( "Tooltips.RotateLeft" ), 8854 srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ), 8855 srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ), 8856 srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ), 8857 srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ), 8858 onRelease: onRotateLeftHandler, 8859 onFocus: onFocusHandler, 8860 onBlur: onBlurHandler 8861 })); 8862 8863 buttons.push( this.rotateRightButton = new $.Button({ 8864 element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null, 8865 clickTimeThreshold: this.clickTimeThreshold, 8866 clickDistThreshold: this.clickDistThreshold, 8867 tooltip: $.getString( "Tooltips.RotateRight" ), 8868 srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ), 8869 srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ), 8870 srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ), 8871 srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ), 8872 onRelease: onRotateRightHandler, 8873 onFocus: onFocusHandler, 8874 onBlur: onBlurHandler 8875 })); 8876 } 8877 8878 if ( this.showFlipControl ) { 8879 buttons.push( this.flipButton = new $.Button({ 8880 element: this.flipButton ? $.getElement( this.flipButton ) : null, 8881 clickTimeThreshold: this.clickTimeThreshold, 8882 clickDistThreshold: this.clickDistThreshold, 8883 tooltip: $.getString( "Tooltips.Flip" ), 8884 srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ), 8885 srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ), 8886 srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ), 8887 srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ), 8888 onRelease: onFlipHandler, 8889 onFocus: onFocusHandler, 8890 onBlur: onBlurHandler 8891 })); 8892 } 8893 8894 if ( useGroup ) { 8895 this.buttons = new $.ButtonGroup({ 8896 buttons: buttons, 8897 clickTimeThreshold: this.clickTimeThreshold, 8898 clickDistThreshold: this.clickDistThreshold 8899 }); 8900 8901 this.navControl = this.buttons.element; 8902 this.addHandler( 'open', $.delegate( this, lightUp ) ); 8903 8904 if( this.toolbar ){ 8905 this.toolbar.addControl( 8906 this.navControl, 8907 {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT} 8908 ); 8909 } else { 8910 this.addControl( 8911 this.navControl, 8912 {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT} 8913 ); 8914 } 8915 } 8916 8917 } 8918 return this; 8919 }, 8920 8921 /** 8922 * Gets the active page of a sequence 8923 * @function 8924 * @return {Number} 8925 */ 8926 currentPage: function() { 8927 return this._sequenceIndex; 8928 }, 8929 8930 /** 8931 * @function 8932 * @return {OpenSeadragon.Viewer} Chainable. 8933 * @fires OpenSeadragon.Viewer.event:page 8934 */ 8935 goToPage: function( page ){ 8936 if( this.tileSources && page >= 0 && page < this.tileSources.length ){ 8937 this._sequenceIndex = page; 8938 8939 this._updateSequenceButtons( page ); 8940 8941 this.open( this.tileSources[ page ] ); 8942 8943 if( this.referenceStrip ){ 8944 this.referenceStrip.setFocus( page ); 8945 } 8946 8947 /** 8948 * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}). 8949 * 8950 * @event page 8951 * @memberof OpenSeadragon.Viewer 8952 * @type {Object} 8953 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8954 * @property {Number} page - The page index.
8955 * @property {?Object} userData - Arbitrary subscriber-defined object. 8956 */ 8957 this.raiseEvent( 'page', { page: page } ); 8958 } 8959 8960 return this; 8961 }, 8962 8963 /** 8964 * Adds an html element as an overlay to the current viewport. Useful for 8965 * highlighting words or areas of interest on an image or other zoomable 8966 * interface. The overlays added via this method are removed when the viewport 8967 * is closed which include when changing page. 8968 * @method 8969 * @param {Element|String|Object} element - A reference to an element or an id for 8970 * the element which will be overlaid. Or an Object specifying the configuration for the overlay. 8971 * If using an object, see {@link OpenSeadragon.Overlay} for a list of 8972 * all available options. 8973 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 8974 * rectangle which will be overlaid. This is a viewport relative location. 8975 * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the 8976 * viewport which the location coordinates will be treated as relative 8977 * to. 8978 * @param {function} [onDraw] - If supplied the callback is called when the overlay 8979 * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. 8980 * It is passed position, size and element. 8981 * @return {OpenSeadragon.Viewer} Chainable. 8982 * @fires OpenSeadragon.Viewer.event:add-overlay 8983 */ 8984 addOverlay: function( element, location, placement, onDraw ) { 8985 var options; 8986 if( $.isPlainObject( element ) ){ 8987 options = element; 8988 } else { 8989 options = { 8990 element: element, 8991 location: location, 8992 placement: placement, 8993 onDraw: onDraw 8994 }; 8995 } 8996 8997 element = $.getElement( options.element ); 8998 8999 if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) { 9000 // they're trying to add a duplicate overlay 9001 return this; 9002 } 9003 9004 var overlay = getOverlayObject( this, options); 9005 this.currentOverlays.push(overlay); 9006 overlay.drawHTML( this.overlaysContainer, this.viewport ); 9007 9008 /** 9009 * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}). 9010 * 9011 * @event add-overlay 9012 * @memberof OpenSeadragon.Viewer 9013 * @type {object} 9014 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 9015 * @property {Element} element - The overlay element. 9016 * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location 9017 * @property {OpenSeadragon.Placement} placement 9018 * @property {?Object} userData - Arbitrary subscriber-defined object. 9019 */ 9020 this.raiseEvent( 'add-overlay', { 9021 element: element, 9022 location: options.location, 9023 placement: options.placement 9024 }); 9025 return this; 9026 }, 9027 9028 /** 9029 * Updates the overlay represented by the reference to the element or 9030 * element id moving it to the new location, relative to the new placement. 9031 * @method 9032 * @param {Element|String} element - A reference to an element or an id for 9033 * the element which is overlaid. 9034 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 9035 * rectangle which will be overlaid. This is a viewport relative location. 9036 * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the 9037 * viewport which the location coordinates will be treated as relative 9038 * to. 9039 * @return {OpenSeadragon.Viewer} Chainable. 9040 * @fires OpenSeadragon.Viewer.event:update-overlay 9041 */ 9042 updateOverlay: function( element, location, placement ) { 9043 var i; 9044 9045 element = $.getElement( element ); 9046 i = getOverlayIndex( this.currentOverlays, element ); 9047 9048 if ( i >= 0 ) { 9049 this.currentOverlays[ i ].update( location, placement ); 9050 THIS[ this.hash ].forceRedraw = true; 9051 /** 9052 * Raised when an overlay's location or placement changes 9053 * (see {@link OpenSeadragon.Viewer#updateOverlay}). 9054 * 9055 * @event update-overlay 9056 * @memberof OpenSeadragon.Viewer 9057 * @type {object} 9058 * @property {OpenSeadragon.Viewer} eventSource - A reference to the 9059 * Viewer which raised the event. 9060 * @property {Element} element 9061 * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location 9062 * @property {OpenSeadragon.Placement} placement
vendor: 4,336 bytes, lines 9063-9188
9063 * @property {?Object} userData - Arbitrary subscriber-defined object. 9064 */ 9065 this.raiseEvent( 'update-overlay', { 9066 element: element, 9067 location: location, 9068 placement: placement 9069 }); 9070 } 9071 return this; 9072 }, 9073 9074 /** 9075 * Removes an overlay identified by the reference element or element id 9076 * and schedules an update. 9077 * @method 9078 * @param {Element|String} element - A reference to the element or an 9079 * element id which represent the ovelay content to be removed. 9080 * @return {OpenSeadragon.Viewer} Chainable. 9081 * @fires OpenSeadragon.Viewer.event:remove-overlay 9082 */ 9083 removeOverlay: function( element ) { 9084 var i; 9085 9086 element = $.getElement( element ); 9087 i = getOverlayIndex( this.currentOverlays, element ); 9088 9089 if ( i >= 0 ) { 9090 this.currentOverlays[ i ].destroy(); 9091 this.currentOverlays.splice( i, 1 ); 9092 THIS[ this.hash ].forceRedraw = true; 9093 /** 9094 * Raised when an overlay is removed from the viewer 9095 * (see {@link OpenSeadragon.Viewer#removeOverlay}). 9096 * 9097 * @event remove-overlay 9098 * @memberof OpenSeadragon.Viewer 9099 * @type {object} 9100 * @property {OpenSeadragon.Viewer} eventSource - A reference to the 9101 * Viewer which raised the event. 9102 * @property {Element} element - The overlay element. 9103 * @property {?Object} userData - Arbitrary subscriber-defined object. 9104 */ 9105 this.raiseEvent( 'remove-overlay', { 9106 element: element 9107 }); 9108 } 9109 return this; 9110 }, 9111 9112 /** 9113 * Removes all currently configured Overlays from this Viewer and schedules 9114 * an update. 9115 * @method 9116 * @return {OpenSeadragon.Viewer} Chainable. 9117 * @fires OpenSeadragon.Viewer.event:clear-overlay 9118 */ 9119 clearOverlays: function() { 9120 while ( this.currentOverlays.length > 0 ) { 9121 this.currentOverlays.pop().destroy(); 9122 } 9123 THIS[ this.hash ].forceRedraw = true; 9124 /** 9125 * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}). 9126 * 9127 * @event clear-overlay 9128 * @memberof OpenSeadragon.Viewer 9129 * @type {object} 9130 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 9131 * @property {?Object} userData - Arbitrary subscriber-defined object. 9132 */ 9133 this.raiseEvent( 'clear-overlay', {} ); 9134 return this; 9135 }, 9136 9137 /** 9138 * Finds an overlay identified by the reference element or element id 9139 * and returns it as an object, return null if not found. 9140 * @method 9141 * @param {Element|String} element - A reference to the element or an 9142 * element id which represents the overlay content. 9143 * @return {OpenSeadragon.Overlay} the matching overlay or null if none found. 9144 */ 9145 getOverlayById: function( element ) { 9146 var i; 9147 9148 element = $.getElement( element ); 9149 i = getOverlayIndex( this.currentOverlays, element ); 9150 9151 if (i >= 0) { 9152 return this.currentOverlays[i]; 9153 } else { 9154 return null; 9155 } 9156 }, 9157 9158 /** 9159 * Updates the sequence buttons. 9160 * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons 9161 * @private 9162 * @param {Number} Sequence Value 9163 */ 9164 _updateSequenceButtons: function( page ) { 9165 9166 if ( this.nextButton ) { 9167 if(!this.tileSources || this.tileSources.length - 1 === page) { 9168 //Disable next button 9169 if ( !this.navPrevNextWrap ) { 9170 this.nextButton.disable(); 9171 } 9172 } else { 9173 this.nextButton.enable(); 9174 } 9175 } 9176 if ( this.previousButton ) { 9177 if ( page > 0 ) { 9178 //Enable previous button 9179 this.previousButton.enable(); 9180 } else { 9181 if ( !this.navPrevNextWrap ) { 9182 this.previousButton.disable(); 9183 } 9184 } 9185 } 9186 }, 9187 9188 /**
9189 * Display a message in the viewport 9190 * @function OpenSeadragon.Viewer.prototype._showMessage 9191 * @private 9192 * @param {String} text message 9193 */ 9194 _showMessage: function ( message ) { 9195 this._hideMessage(); 9196 9197 var div = $.makeNeutralElement( "div" ); 9198 div.appendChild( document.createTextNode( message ) ); 9199 9200 this.messageDiv = $.makeCenteredNode( div ); 9201 9202 $.addClass(this.messageDiv, "openseadragon-message"); 9203 9204 this.container.appendChild( this.messageDiv ); 9205 }, 9206 9207 /** 9208 * Hide any currently displayed viewport message 9209 * @function OpenSeadragon.Viewer.prototype._hideMessage 9210 * @private 9211 */ 9212 _hideMessage: function () { 9213 var div = this.messageDiv; 9214 if (div) { 9215 div.parentNode.removeChild(div); 9216 delete this.messageDiv; 9217 } 9218 }, 9219 9220 /** 9221 * Gets this viewer's gesture settings for the given pointer device type. 9222 * @method 9223 * @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.). 9224 * @return {OpenSeadragon.GestureSettings} 9225 */ 9226 gestureSettingsByDeviceType: function ( type ) { 9227 switch ( type ) { 9228 case 'mouse': 9229 return this.gestureSettingsMouse; 9230 case 'touch': 9231 return this.gestureSettingsTouch; 9232 case 'pen': 9233 return this.gestureSettingsPen; 9234 default: 9235 return this.gestureSettingsUnknown; 9236 } 9237 }, 9238 9239 // private 9240 _drawOverlays: function() { 9241 var i, 9242 length = this.currentOverlays.length; 9243 for ( i = 0; i < length; i++ ) { 9244 this.currentOverlays[ i ].drawHTML( this.overlaysContainer, this.viewport ); 9245 } 9246 }, 9247 9248 /** 9249 * Cancel the "in flight" images. 9250 */ 9251 _cancelPendingImages: function() { 9252 this._loadQueue = []; 9253 }, 9254 9255 /** 9256 * Removes the reference strip and disables displaying it. 9257 * @function 9258 */ 9259 removeReferenceStrip: function() { 9260 this.showReferenceStrip = false; 9261 9262 if (this.referenceStrip) { 9263 this.referenceStrip.destroy(); 9264 this.referenceStrip = null; 9265 } 9266 }, 9267 9268 /** 9269 * Enables and displays the reference strip based on the currently set tileSources. 9270 * Works only when the Viewer has sequenceMode set to true. 9271 * @function 9272 */ 9273 addReferenceStrip: function() { 9274 this.showReferenceStrip = true; 9275 9276 if (this.sequenceMode) { 9277 if (this.referenceStrip) { 9278 return; 9279 } 9280 9281 if (this.tileSources.length && this.tileSources.length > 1) { 9282 this.referenceStrip = new $.ReferenceStrip({ 9283 id: this.referenceStripElement, 9284 position: this.referenceStripPosition, 9285 sizeRatio: this.referenceStripSizeRatio, 9286 scroll: this.referenceStripScroll, 9287 height: this.referenceStripHeight, 9288 width: this.referenceStripWidth, 9289 tileSources: this.tileSources, 9290 prefixUrl: this.prefixUrl, 9291 useCanvas: this.useCanvas, 9292 viewer: this 9293 }); 9294 9295 this.referenceStrip.setFocus( this._sequenceIndex ); 9296 } 9297 } else { 9298 $.console.warn('Attempting to display a reference strip while "sequenceMode" is off.'); 9299 } 9300 } 9301}); 9302 9303 9304/** 9305 * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y, 9306 * which was causing some calling operations to return NaN. 9307 * @returns {Point} 9308 * @private 9309 */ 9310function _getSafeElemSize (oElement) { 9311 oElement = $.getElement( oElement ); 9312 9313 return new $.Point( 9314 (oElement.clientWidth === 0 ? 1 : oElement.clientWidth), 9315 (oElement.clientHeight === 0 ? 1 : oElement.clientHeight) 9316 ); 9317} 9318 9319 9320/** 9321 * @function 9322 * @private 9323 */ 9324function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback, 9325 failCallback ) { 9326 var _this = viewer; 9327 9328 //allow plain xml strings or json strings to be parsed here 9329 if ( $.type( tileSource ) == 'string' ) { 9330 //xml should start with "<" and end with ">" 9331 if ( tileSource.match( /^\s*<.*>\s*$/ ) ) { 9332 tileSource = $.parseXml( tileSource ); 9333 //json should start with "{" or "[" and end with "}" or "]" 9334 } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) { 9335 try { 9336 var tileSourceJ = $.parseJSON(tileSource); 9337 tileSource = tileSourceJ; 9338 } catch (e) { 9339 //tileSource = tileSource; 9340 } 9341 } 9342 } 9343 9344 function waitUntilReady(tileSource, originalTileSource) { 9345 if (tileSource.ready) { 9346 successCallback(tileSource); 9347 } else { 9348 tileSource.addHandler('ready', function () { 9349 successCallback(tileSource); 9350 }); 9351 tileSource.addHandler('open-failed', function (event) { 9352 failCallback({ 9353 message: event.message, 9354 source: originalTileSource 9355 }); 9356 }); 9357 } 9358 } 9359 9360 setTimeout( function() { 9361 if ( $.type( tileSource ) == 'string' ) { 9362 //If its still a string it means it must be a url at this point 9363 tileSource = new $.TileSource({ 9364 url: tileSource, 9365 crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ? 9366 imgOptions.crossOriginPolicy : viewer.crossOriginPolicy, 9367 ajaxWithCredentials: viewer.ajaxWithCredentials, 9368 ajaxHeaders: viewer.ajaxHeaders, 9369 useCanvas: viewer.useCanvas, 9370 success: function( event ) { 9371 successCallback( event.tileSource ); 9372 } 9373 }); 9374 tileSource.addHandler( 'open-failed', function( event ) { 9375 failCallback( event ); 9376 } ); 9377 9378 } else if ($.isPlainObject(tileSource) || tileSource.nodeType) { 9379 if (tileSource.crossOriginPolicy === undefined && 9380 (imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) { 9381 tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ? 9382 imgOptions.crossOriginPolicy : viewer.crossOriginPolicy; 9383 } 9384 if (tileSource.ajaxWithCredentials === undefined) { 9385 tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; 9386 } 9387 if (tileSource.useCanvas === undefined) { 9388 tileSource.useCanvas = viewer.useCanvas; 9389 } 9390 9391 if ( $.isFunction( tileSource.getTileUrl ) ) { 9392 //Custom tile source 9393 var customTileSource = new $.TileSource( tileSource ); 9394 customTileSource.getTileUrl = tileSource.getTileUrl; 9395 successCallback( customTileSource ); 9396 } else { 9397 //inline configuration 9398 var $TileSource = $.TileSource.determineType( _this, tileSource ); 9399 if ( !$TileSource ) { 9400 failCallback( { 9401 message: "Unable to load TileSource", 9402 source: tileSource 9403 }); 9404 return; 9405 } 9406 var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); 9407 waitUntilReady(new $TileSource(options), tileSource); 9408 } 9409 } else { 9410 //can assume it's already a tile source implementation 9411 waitUntilReady(tileSource, tileSource); 9412 } 9413 }); 9414} 9415 9416function getOverlayObject( viewer, overlay ) { 9417 if ( overlay instanceof $.Overlay ) { 9418 return overlay; 9419 } 9420 9421 var element = null; 9422 if ( overlay.element ) { 9423 element = $.getElement( overlay.element ); 9424 } else { 9425 var id = overlay.id ?
vendor: 5,162 bytes, lines 9426-9597
9426 overlay.id : 9427 "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 ); 9428 9429 element = $.getElement( overlay.id ); 9430 if ( !element ) { 9431 element = document.createElement( "a" ); 9432 element.href = "#/overlay/" + id; 9433 } 9434 element.id = id; 9435 $.addClass( element, overlay.className ? 9436 overlay.className : 9437 "openseadragon-overlay" 9438 ); 9439 } 9440 9441 var location = overlay.location; 9442 var width = overlay.width; 9443 var height = overlay.height; 9444 if (!location) { 9445 var x = overlay.x; 9446 var y = overlay.y; 9447 if (overlay.px !== undefined) { 9448 var rect = viewer.viewport.imageToViewportRectangle(new $.Rect( 9449 overlay.px, 9450 overlay.py, 9451 width || 0, 9452 height || 0)); 9453 x = rect.x; 9454 y = rect.y; 9455 width = width !== undefined ? rect.width : undefined; 9456 height = height !== undefined ? rect.height : undefined; 9457 } 9458 location = new $.Point(x, y); 9459 } 9460 9461 var placement = overlay.placement; 9462 if (placement && $.type(placement) === "string") { 9463 placement = $.Placement[overlay.placement.toUpperCase()]; 9464 } 9465 9466 return new $.Overlay({ 9467 element: element, 9468 location: location, 9469 placement: placement, 9470 onDraw: overlay.onDraw, 9471 checkResize: overlay.checkResize, 9472 width: width, 9473 height: height, 9474 rotationMode: overlay.rotationMode 9475 }); 9476} 9477 9478/** 9479 * @private 9480 * @inner 9481 * Determines the index of the given overlay in the given overlays array. 9482 */ 9483function getOverlayIndex( overlays, element ) { 9484 var i; 9485 for ( i = overlays.length - 1; i >= 0; i-- ) { 9486 if ( overlays[ i ].element === element ) { 9487 return i; 9488 } 9489 } 9490 9491 return -1; 9492} 9493 9494/////////////////////////////////////////////////////////////////////////////// 9495// Schedulers provide the general engine for animation 9496/////////////////////////////////////////////////////////////////////////////// 9497function scheduleUpdate( viewer, updateFunc ){ 9498 return $.requestAnimationFrame( function(){ 9499 updateFunc( viewer ); 9500 } ); 9501} 9502 9503 9504//provides a sequence in the fade animation 9505function scheduleControlsFade( viewer ) { 9506 $.requestAnimationFrame( function(){ 9507 updateControlsFade( viewer ); 9508 }); 9509} 9510 9511 9512//initiates an animation to hide the controls 9513function beginControlsAutoHide( viewer ) { 9514 if ( !viewer.autoHideControls ) { 9515 return; 9516 } 9517 viewer.controlsShouldFade = true; 9518 viewer.controlsFadeBeginTime = 9519 $.now() + 9520 viewer.controlsFadeDelay; 9521 9522 window.setTimeout( function(){ 9523 scheduleControlsFade( viewer ); 9524 }, viewer.controlsFadeDelay ); 9525} 9526 9527 9528//determines if fade animation is done or continues the animation 9529function updateControlsFade( viewer ) { 9530 var currentTime, 9531 deltaTime, 9532 opacity, 9533 i; 9534 if ( viewer.controlsShouldFade ) { 9535 currentTime = $.now(); 9536 deltaTime = currentTime - viewer.controlsFadeBeginTime; 9537 opacity = 1.0 - deltaTime / viewer.controlsFadeLength; 9538 9539 opacity = Math.min( 1.0, opacity ); 9540 opacity = Math.max( 0.0, opacity ); 9541 9542 for ( i = viewer.controls.length - 1; i >= 0; i--) { 9543 if (viewer.controls[ i ].autoFade) { 9544 viewer.controls[ i ].setOpacity( opacity ); 9545 } 9546 } 9547 9548 if ( opacity > 0 ) { 9549 // fade again 9550 scheduleControlsFade( viewer ); 9551 } 9552 } 9553} 9554 9555 9556//stop the fade animation on the controls and show them 9557function abortControlsAutoHide( viewer ) { 9558 var i; 9559 viewer.controlsShouldFade = false; 9560 for ( i = viewer.controls.length - 1; i >= 0; i-- ) { 9561 viewer.controls[ i ].setOpacity( 1.0 ); 9562 } 9563} 9564 9565 9566 9567/////////////////////////////////////////////////////////////////////////////// 9568// Default view event handlers. 9569/////////////////////////////////////////////////////////////////////////////// 9570function onFocus(){ 9571 abortControlsAutoHide( this ); 9572} 9573 9574function onBlur(){ 9575 beginControlsAutoHide( this ); 9576 9577} 9578 9579function onCanvasKeyDown( event ) { 9580 var canvasKeyDownEventArgs = { 9581 originalEvent: event.originalEvent, 9582 preventDefaultAction: event.preventDefaultAction, 9583 preventVerticalPan: event.preventVerticalPan, 9584 preventHorizontalPan: event.preventHorizontalPan 9585 }; 9586 9587 /** 9588 * Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element. 9589 * 9590 * @event canvas-key 9591 * @memberof OpenSeadragon.Viewer 9592 * @type {object} 9593 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9594 * @property {Object} originalEvent - The original DOM event. 9595 * @property {Boolean} preventDefaultAction - Set to true to prevent default keyboard behaviour. Default: false. 9596 * @property {Boolean} preventVerticalPan - Set to true to prevent keyboard vertical panning. Default: false. 9597 * @property {Boolean} preventHorizontalPan - Set to true to prevent keyboard horizontal panning. Default: false.
9598 * @property {?Object} userData - Arbitrary subscriber-defined object. 9599 */ 9600 9601 this.raiseEvent('canvas-key', canvasKeyDownEventArgs); 9602 9603 if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { 9604 switch( event.keyCode ){ 9605 case 38://up arrow 9606 if (!canvasKeyDownEventArgs.preventVerticalPan) { 9607 if ( event.shift ) { 9608 this.viewport.zoomBy(1.1); 9609 } else { 9610 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -this.pixelsPerArrowPress))); 9611 } 9612 this.viewport.applyConstraints(); 9613 } 9614 return false; 9615 case 40://down arrow 9616 if (!canvasKeyDownEventArgs.preventVerticalPan) { 9617 if ( event.shift ) { 9618 this.viewport.zoomBy(0.9); 9619 } else { 9620 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, this.pixelsPerArrowPress))); 9621 } 9622 this.viewport.applyConstraints(); 9623 } 9624 return false; 9625 case 37://left arrow 9626 if (!canvasKeyDownEventArgs.preventHorizontalPan) { 9627 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-this.pixelsPerArrowPress, 0))); 9628 this.viewport.applyConstraints(); 9629 } 9630 return false; 9631 case 39://right arrow 9632 if (!canvasKeyDownEventArgs.preventHorizontalPan) { 9633 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(this.pixelsPerArrowPress, 0))); 9634 this.viewport.applyConstraints(); 9635 } 9636 return false; 9637 default: 9638 //console.log( 'navigator keycode %s', event.keyCode ); 9639 return true; 9640 } 9641 } else { 9642 return true; 9643 } 9644} 9645function onCanvasKeyPress( event ) { 9646 var canvasKeyPressEventArgs = { 9647 originalEvent: event.originalEvent, 9648 preventDefaultAction: event.preventDefaultAction, 9649 preventVerticalPan: event.preventVerticalPan, 9650 preventHorizontalPan: event.preventHorizontalPan 9651 }; 9652 9653 // This event is documented in onCanvasKeyDown 9654 this.raiseEvent('canvas-key', canvasKeyPressEventArgs); 9655 9656 if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { 9657 switch( event.keyCode ){ 9658 case 43://=|+ 9659 case 61://=|+ 9660 this.viewport.zoomBy(1.1); 9661 this.viewport.applyConstraints(); 9662 return false; 9663 case 45://-|_ 9664 this.viewport.zoomBy(0.9); 9665 this.viewport.applyConstraints(); 9666 return false; 9667 case 48://0|) 9668 this.viewport.goHome(); 9669 this.viewport.applyConstraints(); 9670 return false; 9671 case 119://w 9672 case 87://W 9673 if (!canvasKeyPressEventArgs.preventVerticalPan) { 9674 if ( event.shift ) { 9675 this.viewport.zoomBy(1.1); 9676 } else { 9677 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40))); 9678 } 9679 this.viewport.applyConstraints(); 9680 } 9681 return false; 9682 case 115://s 9683 case 83://S 9684 if (!canvasKeyPressEventArgs.preventVerticalPan) { 9685 if ( event.shift ) { 9686 this.viewport.zoomBy(0.9); 9687 } else { 9688 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40))); 9689 } 9690 this.viewport.applyConstraints(); 9691 } 9692 return false; 9693 case 97://a 9694 if (!canvasKeyPressEventArgs.preventHorizontalPan) { 9695 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0
9695))); 9696 this.viewport.applyConstraints(); 9697 } 9698 return false; 9699 case 100://d 9700 if (!canvasKeyPressEventArgs.preventHorizontalPan) { 9701 this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0))); 9702 this.viewport.applyConstraints(); 9703 } 9704 return false; 9705 case 114: //r - clockwise rotation 9706 if(this.viewport.flipped){ 9707 this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360)); 9708 } else{ 9709 this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360)); 9710 } 9711 this.viewport.applyConstraints(); 9712 return false; 9713 case 82: //R - counterclockwise rotation 9714 if(this.viewport.flipped){ 9715 this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360)); 9716 } else{ 9717 this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360)); 9718 } 9719 this.viewport.applyConstraints(); 9720 return false; 9721 case 102: //f 9722 this.viewport.toggleFlip(); 9723 return false; 9724 default: 9725 // console.log( 'navigator keycode %s', event.keyCode ); 9726 return true; 9727 } 9728 } else { 9729 return true; 9730 } 9731} 9732 9733function onCanvasClick( event ) { 9734 var gestureSettings; 9735 9736 var haveKeyboardFocus = document.activeElement == this.canvas; 9737 9738 // If we don't have keyboard focus, request it. 9739 if ( !haveKeyboardFocus ) { 9740 this.canvas.focus(); 9741 } 9742 if(this.viewport.flipped){ 9743 event.position.x = this.viewport.getContainerSize().x - event.position.x; 9744 } 9745 9746 var canvasClickEventArgs = { 9747 tracker: event.eventSource, 9748 position: event.position, 9749 quick: event.quick, 9750 shift: event.shift, 9751 originalEvent: event.originalEvent, 9752 preventDefaultAction: event.preventDefaultAction 9753 }; 9754 9755 /** 9756 * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element. 9757 * 9758 * @event canvas-click 9759 * @memberof OpenSeadragon.Viewer 9760 * @type {object} 9761 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9762 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9763 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9764 * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags. 9765 * @property {Boolean} shift - True if the shift key was pressed during this event. 9766 * @property {Object} originalEvent - The original DOM event. 9767 * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false. 9768 * @property {?Object} userData - Arbitrary subscriber-defined object. 9769 */ 9770 this.raiseEvent( 'canvas-click', canvasClickEventArgs); 9771 9772 if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) { 9773 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 9774 if ( gestureSettings.clickToZoom ) { 9775 this.viewport.zoomBy( 9776 event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick, 9777 gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null 9778 ); 9779 this.viewport.applyConstraints(); 9780 } 9781 } 9782} 9783 9784function onCanvasDblClick( event ) { 9785 var gestureSettings; 9786 9787 var canvasDblClickEventArgs = { 9788 tracker: event.eventSource, 9789 position: event.position, 9790 shift: event.shift, 9791 originalEvent: event.originalEvent, 9792 preventDefaultAction: event.preventDefaultAction 9793 }; 9794 9795 /** 9796 * Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element. 9797 * 9798 * @event canvas-double-click 9799 * @memberof OpenSeadragon.Viewer 9800 * @type {object} 9801 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9802 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9803 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9804 * @property {Boolean} shift - True if the shift key was pressed during this event. 9805 * @property {Object} originalEvent - The original DOM event. 9806 * @property {Boolean} preventDefaultAction - Set to true to prevent default double tap to zoom behaviour. Default: false.
9807 * @property {?Object} userData - Arbitrary subscriber-defined object. 9808 */ 9809 this.raiseEvent( 'canvas-double-click', canvasDblClickEventArgs); 9810 9811 if ( !canvasDblClickEventArgs.preventDefaultAction && this.viewport ) { 9812 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 9813 if ( gestureSettings.dblClickToZoom ) { 9814 this.viewport.zoomBy( 9815 event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick, 9816 gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null 9817 ); 9818 this.viewport.applyConstraints(); 9819 } 9820 } 9821} 9822 9823function onCanvasDrag( event ) { 9824 var gestureSettings; 9825 9826 var canvasDragEventArgs = { 9827 tracker: event.eventSource, 9828 position: event.position, 9829 delta: event.delta, 9830 speed: event.speed, 9831 direction: event.direction, 9832 shift: event.shift, 9833 originalEvent: event.originalEvent, 9834 preventDefaultAction: event.preventDefaultAction 9835 }; 9836 9837 /** 9838 * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element. 9839 * 9840 * @event canvas-drag 9841 * @memberof OpenSeadragon.Viewer 9842 * @type {object} 9843 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9844 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9845 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9846 * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag. 9847 * @property {Number} speed - Current computed speed, in pixels per second. 9848 * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 9849 * @property {Boolean} shift - True if the shift key was pressed during this event. 9850 * @property {Object} originalEvent - The original DOM event. 9851 * @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false. 9852 * @property {?Object} userData - Arbitrary subscriber-defined object. 9853 */ 9854 this.raiseEvent( 'canvas-drag', canvasDragEventArgs); 9855 9856 if ( !canvasDragEventArgs.preventDefaultAction && this.viewport ) { 9857 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 9858 if( !this.panHorizontal ){ 9859 event.delta.x = 0; 9860 } 9861 if( !this.panVertical ){ 9862 event.delta.y = 0; 9863 } 9864 if(this.viewport.flipped){ 9865 event.delta.x = -event.delta.x; 9866 } 9867 9868 if( this.constrainDuringPan ){ 9869 var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() ); 9870 9871 this.viewport.centerSpringX.target.value += delta.x; 9872 this.viewport.centerSpringY.target.value += delta.y; 9873 9874 var bounds = this.viewport.getBounds(); 9875 var constrainedBounds = this.viewport.getConstrainedBounds(); 9876 9877 this.viewport.centerSpringX.target.value -= delta.x; 9878 this.viewport.centerSpringY.target.value -= delta.y; 9879 9880 if (bounds.x != constrainedBounds.x) { 9881 event.delta.x = 0; 9882 } 9883 9884 if (bounds.y != constrainedBounds.y) { 9885 event.delta.y = 0; 9886 } 9887 } 9888 9889 this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan); 9890 } 9891} 9892 9893function onCanvasDragEnd( event ) { 9894 if (!event.preventDefaultAction && this.viewport) { 9895 var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType); 9896 if (gestureSettings.flickEnabled && 9897 event.speed >= gestureSettings.flickMinSpeed) { 9898 var amplitudeX = 0; 9899 if (this.panHorizontal) { 9900 amplitudeX = gestureSettings.flickMomentum * event.speed * 9901 Math.cos(event.direction); 9902 } 9903 var amplitudeY = 0; 9904 if (this.panVertical) { 9905 amplitudeY = gestureSettings.flickMomentum * event.speed * 9906 Math.sin(event.direction); 9907 } 9908 var center = this.viewport.pixelFromPoint( 9909 this.viewport.getCenter(true)); 9910 var target = this.viewport.pointFromPixel( 9911 new $.Point(center.x - amplitudeX, center.y - amplitudeY)); 9912 this.viewport.panTo(target, false); 9913 } 9914 this.viewport.applyConstraints(); 9915 } 9916 /** 9917 * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element. 9918 * 9919 * @event canvas-drag-end 9920 * @memberof OpenSeadragon.Viewer 9921 * @type {object} 9922 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9923 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9924 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9925 * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second. 9926 * @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 9927 * @property {Boolean} shift - True if the shift key was pressed during this event. 9928 * @property {Object} originalEvent - The original DOM event.
9929 * @property {?Object} userData - Arbitrary subscriber-defined object. 9930 */ 9931 this.raiseEvent('canvas-drag-end', { 9932 tracker: event.eventSource, 9933 position: event.position, 9934 speed: event.speed, 9935 direction: event.direction, 9936 shift: event.shift, 9937 originalEvent: event.originalEvent 9938 }); 9939} 9940 9941function onCanvasEnter( event ) { 9942 /** 9943 * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element. 9944 * 9945 * @event canvas-enter 9946 * @memberof OpenSeadragon.Viewer 9947 * @type {object} 9948 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9949 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9950 * @property {String} pointerType - "mouse", "touch", "pen", etc. 9951 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9952 * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 9953 * @property {Number} pointers - Number of pointers (all types) active in the tracked element. 9954 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 9955 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 9956 * @property {Object} originalEvent - The original DOM event. 9957 * @property {?Object} userData - Arbitrary subscriber-defined object. 9958 */ 9959 this.raiseEvent( 'canvas-enter', { 9960 tracker: event.eventSource, 9961 pointerType: event.pointerType, 9962 position: event.position, 9963 buttons: event.buttons, 9964 pointers: event.pointers, 9965 insideElementPressed: event.insideElementPressed, 9966 buttonDownAny: event.buttonDownAny, 9967 originalEvent: event.originalEvent 9968 }); 9969} 9970 9971function onCanvasExit( event ) { 9972 9973 if (window.location != window.parent.location){ 9974 $.MouseTracker.resetAllMouseTrackers(); 9975 } 9976 9977 /** 9978 * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element. 9979 * 9980 * @event canvas-exit 9981 * @memberof OpenSeadragon.Viewer 9982 * @type {object} 9983 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9984 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9985 * @property {String} pointerType - "mouse", "touch", "pen", etc. 9986 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9987 * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 9988 * @property {Number} pointers - Number of pointers (all types) active in the tracked element. 9989 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 9990 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 9991 * @property {Object} originalEvent - The original DOM event. 9992 * @property {?Object} userData - Arbitrary subscriber-defined object. 9993 */ 9994 this.raiseEvent( 'canvas-exit', { 9995 tracker: event.eventSource, 9996 pointerType: event.pointerType, 9997 position: event.position, 9998 buttons: event.buttons, 9999 pointers: event.pointers, 10000 insideElementPressed: event.insideElementPressed, 10001 buttonDownAny: event.buttonDownAny, 10002 originalEvent: event.originalEvent 10003 }); 10004} 10005 10006function onCanvasPress( event ) { 10007 /** 10008 * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element. 10009 * 10010 * @event canvas-press 10011 * @memberof OpenSeadragon.Viewer 10012 * @type {object} 10013 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10014 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10015 * @property {String} pointerType - "mouse", "touch", "pen", etc. 10016 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10017 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 10018 * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. 10019 * @property {Object} originalEvent - The original DOM event.
10020 * @property {?Object} userData - Arbitrary subscriber-defined object. 10021 */ 10022 this.raiseEvent( 'canvas-press', { 10023 tracker: event.eventSource, 10024 pointerType: event.pointerType, 10025 position: event.position, 10026 insideElementPressed: event.insideElementPressed, 10027 insideElementReleased: event.insideElementReleased, 10028 originalEvent: event.originalEvent 10029 }); 10030} 10031 10032function onCanvasRelease( event ) { 10033 /** 10034 * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element. 10035 * 10036 * @event canvas-release 10037 * @memberof OpenSeadragon.Viewer 10038 * @type {object} 10039 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10040 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10041 * @property {String} pointerType - "mouse", "touch", "pen", etc. 10042 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10043 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 10044 * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. 10045 * @property {Object} originalEvent - The original DOM event. 10046 * @property {?Object} userData - Arbitrary subscriber-defined object. 10047 */ 10048 this.raiseEvent( 'canvas-release', { 10049 tracker: event.eventSource, 10050 pointerType: event.pointerType, 10051 position: event.position, 10052 insideElementPressed: event.insideElementPressed, 10053 insideElementReleased: event.insideElementReleased, 10054 originalEvent: event.originalEvent 10055 }); 10056} 10057 10058function onCanvasNonPrimaryPress( event ) { 10059 /** 10060 * Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element. 10061 * 10062 * @event canvas-nonprimary-press 10063 * @memberof OpenSeadragon.Viewer 10064 * @type {object} 10065 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10066 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10067 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10068 * @property {String} pointerType - "mouse", "touch", "pen", etc. 10069 * @property {Number} button - Button which caused the event. 10070 * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 10071 * @property {Number} buttons - Current buttons pressed. 10072 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 10073 * @property {Object} originalEvent - The original DOM event. 10074 * @property {?Object} userData - Arbitrary subscriber-defined object. 10075 */ 10076 this.raiseEvent( 'canvas-nonprimary-press', { 10077 tracker: event.eventSource, 10078 position: event.position, 10079 pointerType: event.pointerType, 10080 button: event.button, 10081 buttons: event.buttons, 10082 originalEvent: event.originalEvent 10083 }); 10084} 10085 10086function onCanvasNonPrimaryRelease( event ) { 10087 /** 10088 * Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element. 10089 * 10090 * @event canvas-nonprimary-release 10091 * @memberof OpenSeadragon.Viewer 10092 * @type {object} 10093 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10094 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10095 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10096 * @property {String} pointerType - "mouse", "touch", "pen", etc. 10097 * @property {Number} button - Button which caused the event. 10098 * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. 10099 * @property {Number} buttons - Current buttons pressed. 10100 * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 10101 * @property {Object} originalEvent - The original DOM event.
10102 * @property {?Object} userData - Arbitrary subscriber-defined object. 10103 */ 10104 this.raiseEvent( 'canvas-nonprimary-release', { 10105 tracker: event.eventSource, 10106 position: event.position, 10107 pointerType: event.pointerType, 10108 button: event.button, 10109 buttons: event.buttons, 10110 originalEvent: event.originalEvent 10111 }); 10112} 10113 10114function onCanvasPinch( event ) { 10115 var gestureSettings, 10116 centerPt, 10117 lastCenterPt, 10118 panByPt; 10119 10120 if ( !event.preventDefaultAction && this.viewport ) { 10121 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 10122 if ( gestureSettings.pinchToZoom ) { 10123 centerPt = this.viewport.pointFromPixel( event.center, true ); 10124 lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true ); 10125 panByPt = lastCenterPt.minus( centerPt ); 10126 if( !this.panHorizontal ) { 10127 panByPt.x = 0; 10128 } 10129 if( !this.panVertical ) { 10130 panByPt.y = 0; 10131 } 10132 this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true ); 10133 if ( gestureSettings.zoomToRefPoint ) { 10134 this.viewport.panBy(panByPt, true); 10135 } 10136 this.viewport.applyConstraints(); 10137 } 10138 if ( gestureSettings.pinchRotate ) { 10139 // Pinch rotate 10140 var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y, 10141 event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x); 10142 var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y, 10143 event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x); 10144 this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI))); 10145 } 10146 } 10147 /** 10148 * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element. 10149 * 10150 * @event canvas-pinch 10151 * @memberof OpenSeadragon.Viewer 10152 * @type {object} 10153 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10154 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10155 * @property {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here. 10156 * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element. 10157 * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element. 10158 * @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels. 10159 * @property {Number} distance - The distance between the two pinch contact points in CSS pixels. 10160 * @property {Boolean} shift - True if the shift key was pressed during this event. 10161 * @property {Object} originalEvent - The original DOM event. 10162 * @property {?Object} userData - Arbitrary subscriber-defined object. 10163 */ 10164 this.raiseEvent('canvas-pinch', { 10165 tracker: event.eventSource, 10166 gesturePoints: event.gesturePoints, 10167 lastCenter: event.lastCenter, 10168 center: event.center, 10169 lastDistance: event.lastDistance, 10170 distance: event.distance, 10171 shift: event.shift, 10172 originalEvent: event.originalEvent 10173 }); 10174 //cancels event 10175 return false; 10176} 10177 10178function onCanvasScroll( event ) { 10179 var gestureSettings, 10180 factor, 10181 thisScrollTime, 10182 deltaScrollTime; 10183 10184 /* Certain scroll devices fire the scroll event way too fast so we are injecting a simple adjustment to keep things 10185 * partially normalized. If we have already fired an event within the last 'minScrollDelta' milliseconds we skip 10186 * this one and wait for the next event. */ 10187 thisScrollTime = $.now(); 10188 deltaScrollTime = thisScrollTime - this._lastScrollTime; 10189 if (deltaScrollTime >
10189 this.minScrollDeltaTime) { 10190 this._lastScrollTime = thisScrollTime; 10191 10192 if(this.viewport.flipped){ 10193 event.position.x = this.viewport.getContainerSize().x - event.position.x; 10194 } 10195 10196 if ( !event.preventDefaultAction && this.viewport ) { 10197 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 10198 if ( gestureSettings.scrollToZoom ) { 10199 factor = Math.pow( this.zoomPerScroll, event.scroll ); 10200 this.viewport.zoomBy( 10201 factor, 10202 gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null 10203 ); 10204 this.viewport.applyConstraints(); 10205 } 10206 } 10207 /** 10208 * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel). 10209 * 10210 * @event canvas-scroll 10211 * @memberof OpenSeadragon.Viewer 10212 * @type {object} 10213 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10214 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10215 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10216 * @property {Number} scroll - The scroll delta for the event. 10217 * @property {Boolean} shift - True if the shift key was pressed during this event. 10218 * @property {Object} originalEvent - The original DOM event. 10219 * @property {?Object} userData - Arbitrary subscriber-defined object. 10220 */ 10221 this.raiseEvent( 'canvas-scroll', { 10222 tracker: event.eventSource, 10223 position: event.position, 10224 scroll: event.scroll, 10225 shift: event.shift, 10226 originalEvent: event.originalEvent 10227 }); 10228 if (gestureSettings && gestureSettings.scrollToZoom) { 10229 //cancels event 10230 return false; 10231 } 10232 } 10233 else { 10234 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 10235 if (gestureSettings && gestureSettings.scrollToZoom) { 10236 return false; // We are swallowing this event 10237 } 10238 } 10239} 10240 10241function onContainerEnter( event ) { 10242 THIS[ this.hash ].mouseInside = true; 10243 abortControlsAutoHide( this ); 10244 /** 10245 * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element. 10246 * 10247 * @event container-enter 10248 * @memberof OpenSeadragon.Viewer 10249 * @type {object} 10250 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10251 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10252 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10253 * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 10254 * @property {Number} pointers - Number of pointers (all types) active in the tracked element. 10255 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 10256 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 10257 * @property {Object} originalEvent - The original DOM event. 10258 * @property {?Object} userData - Arbitrary subscriber-defined object. 10259 */ 10260 this.raiseEvent( 'container-enter', { 10261 tracker: event.eventSource, 10262 position: event.position, 10263 buttons: event.buttons, 10264 pointers: event.pointers, 10265 insideElementPressed: event.insideElementPressed, 10266 buttonDownAny: event.buttonDownAny, 10267 originalEvent: event.originalEvent 10268 }); 10269} 10270 10271function onContainerExit( event ) { 10272 if ( event.pointers < 1 ) { 10273 THIS[ this.hash ].mouseInside = false;
10274 if ( !THIS[ this.hash ].animating ) { 10275 beginControlsAutoHide( this ); 10276 } 10277 } 10278 /** 10279 * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element. 10280 * 10281 * @event container-exit 10282 * @memberof OpenSeadragon.Viewer 10283 * @type {object} 10284 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10285 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 10286 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 10287 * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. 10288 * @property {Number} pointers - Number of pointers (all types) active in the tracked element. 10289 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 10290 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 10291 * @property {Object} originalEvent - The original DOM event. 10292 * @property {?Object} userData - Arbitrary subscriber-defined object. 10293 */ 10294 this.raiseEvent( 'container-exit', { 10295 tracker: event.eventSource, 10296 position: event.position, 10297 buttons: event.buttons, 10298 pointers: event.pointers, 10299 insideElementPressed: event.insideElementPressed, 10300 buttonDownAny: event.buttonDownAny, 10301 originalEvent: event.originalEvent 10302 }); 10303} 10304 10305 10306/////////////////////////////////////////////////////////////////////////////// 10307// Page update routines ( aka Views - for future reference ) 10308/////////////////////////////////////////////////////////////////////////////// 10309 10310function updateMulti( viewer ) { 10311 updateOnce( viewer ); 10312 10313 // Request the next frame, unless we've been closed 10314 if ( viewer.isOpen() ) { 10315 viewer._updateRequestId = scheduleUpdate( viewer, updateMulti ); 10316 } else { 10317 viewer._updateRequestId = false; 10318 } 10319} 10320 10321function updateOnce( viewer ) { 10322 10323 //viewer.profiler.beginUpdate(); 10324 10325 if (viewer._opening) { 10326 return; 10327 } 10328 10329 if (viewer.autoResize) { 10330 var containerSize = _getSafeElemSize(viewer.container); 10331 var prevContainerSize = THIS[viewer.hash].prevContainerSize; 10332 if (!containerSize.equals(prevContainerSize)) { 10333 var viewport = viewer.viewport; 10334 if (viewer.preserveImageSizeOnResize) { 10335 var resizeRatio = prevContainerSize.x / containerSize.x; 10336 var zoom = viewport.getZoom() * resizeRatio; 10337 var center = viewport.getCenter(); 10338 viewport.resize(containerSize, false); 10339 viewport.zoomTo(zoom, null, true); 10340 viewport.panTo(center, true); 10341 } else { 10342 // maintain image position 10343 var oldBounds = viewport.getBounds(); 10344 viewport.resize(containerSize, true); 10345 viewport.fitBoundsWithConstraints(oldBounds, true); 10346 } 10347 THIS[viewer.hash].prevContainerSize = containerSize; 10348 THIS[viewer.hash].forceRedraw = true; 10349 } 10350 } 10351 10352 var viewportChange = viewer.viewport.update(); 10353 var animated = viewer.world.update() || viewportChange; 10354 10355 if (viewportChange) { 10356 /** 10357 * Raised when any spring animation update occurs (zoom, pan, etc.), 10358 * before the viewer has drawn the new location. 10359 * 10360 * @event viewport-change 10361 * @memberof OpenSeadragon.Viewer 10362 * @type {object} 10363 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10364 * @property {?Object} userData - Arbitrary subscriber-defined object. 10365 */ 10366 viewer.raiseEvent('viewport-change'); 10367 } 10368 10369 if( viewer.referenceStrip ){ 10370 animated = viewer.referenceStrip.update( viewer.viewport ) || animated; 10371 } 10372 10373 if ( !THIS[ viewer.hash ].animating && animated ) { 10374 /** 10375 * Raised when any spring animation starts (zoom, pan, etc.). 10376 * 10377 * @event animation-start 10378 * @memberof OpenSeadragon.Viewer 10379 * @type {object} 10380 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10381 * @property {?Object} userData - Arbitrary subscriber-defined object. 10382 */ 10383 viewer.raiseEvent( "animation-start" ); 10384 abortControlsAutoHide( viewer ); 10385 } 10386 10387 if ( animated || THIS[ viewer.hash ].forceRedraw || viewer.world.needsDraw() ) { 10388 drawWorld( viewer ); 10389 viewer._drawOverlays(); 10390 if( viewer.navigator ){ 10391 viewer.navigator.update( viewer.viewport ); 10392 } 10393 10394 THIS[ viewer.hash ].forceRedraw = false; 10395 10396 if (animated) { 10397 /** 10398 * Raised when any spring animation update occurs (zoom, pan, etc.), 10399 * after the viewer has drawn the new location. 10400 * 10401 * @event animation 10402 * @memberof OpenSeadragon.Viewer 10403 * @type {object} 10404 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10405 * @property {?Object} userData - Arbitrary subscriber-defined object. 10406 */ 10407 viewer.raiseEvent( "animation" ); 10408 } 10409 } 10410 10411 if ( THIS[ viewer.hash ].animating && !animated ) { 10412 /** 10413 * Raised when any spring animation ends (zoom, pan, etc.). 10414 * 10415 * @event animation-finish 10416 * @memberof OpenSeadragon.Viewer 10417 * @type {object} 10418 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 10419 * @property {?Object} userData - Arbitrary subscriber-defined object. 10420 */ 10421 viewer.raiseEvent( "animation-finish" ); 10422 10423 if ( !THIS[ viewer.hash ].mouseInside ) { 10424 beginControlsAutoHide( viewer ); 10425 } 10426 } 10427 10428 THIS[ viewer.hash ].animating = animated; 10429 10430 //viewer.profiler.endUpdate(); 10431} 10432 10433function drawWorld( viewer ) { 10434 viewer.imageLoader.clear(); 10435 viewer.drawer.clear(); 10436 viewer.world.draw(); 10437 10438 /** 10439 * <em>- Needs documentation -</em> 10440 * 10441 * @event update-viewport 10442 * @memberof OpenSeadragon.Viewer 10443 * @type {object} 10444 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 10445 * @property {?Object} userData - Arbitrary subscriber-defined object. 10446 */ 10447 viewer.raiseEvent( 'update-viewport', {} ); 10448} 10449 10450/////////////////////////////////////////////////////////////////////////////// 10451// Navigation Controls 10452/////////////////////////////////////////////////////////////////////////////// 10453function resolveUrl( prefix, url ) { 10454 return prefix ? prefix + url : url; 10455} 10456 10457 10458 10459function beginZoomingIn() { 10460 THIS[ this.hash ].lastZoomTime = $.now(); 10461 THIS[ this.hash ].zoomFactor = this.zoomPerSecond; 10462 THIS[ this.hash ].zooming = true; 10463 scheduleZoom( this ); 10464} 10465 10466 10467function beginZoomingOut() { 10468 THIS[ this.hash ].lastZoomTime = $.now(); 10469 THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond; 10470 THIS[ this.hash ].zooming = true; 10471 scheduleZoom( this ); 10472} 10473 10474 10475function endZooming() { 10476 THIS[ this.hash ].zooming = false; 10477} 10478 10479 10480function scheduleZoom( viewer ) { 10481 $.requestAnimationFrame( $.delegate( viewer, doZoom ) ); 10482} 10483 10484 10485function doZoom() { 10486 var currentTime, 10487 deltaTime, 10488 adjustedFactor; 10489 10490 if ( THIS[ this.hash ].zooming && this.viewport) { 10491 currentTime = $.now(); 10492 deltaTime = currentTime - THIS[ this.hash ].lastZoomTime; 10493 adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 ); 10494 10495 this.viewport.zoomBy( adjustedFactor ); 10496 this.viewport.applyConstraints(); 10497 THIS[ this.hash ].lastZoomTime = currentTime; 10498 scheduleZoom( this ); 10499 } 10500} 10501 10502 10503function doSingleZoomIn() { 10504 if ( this.viewport ) { 10505 THIS[ this.hash ].zooming = false; 10506 this.viewport.zoomBy( 10507 this.zoomPerClick / 1.0 10508 ); 10509 this.viewport.applyConstraints(); 10510 } 10511} 10512 10513 10514function doSingleZoomOut() { 10515 if ( this.viewport ) { 10516 THIS[ this.hash ].zooming = false; 10517 this.viewport.zoomBy( 10518 1.0 / this.zoomPerClick 10519 ); 10520 this.viewport.applyConstraints(); 10521 } 10522} 10523 10524 10525function lightUp() { 10526 this.buttons.emulateEnter(); 10527 this.buttons.emulateExit(); 10528} 10529 10530 10531function onHome() { 10532 if ( this.viewport ) { 10533 this.viewport.goHome(); 10534 } 10535} 10536 10537 10538function onFullScreen() { 10539 if ( this.isFullPage() && !$.isFullScreen() ) { 10540 // Is fullPage but not fullScreen 10541 this.setFullPage( false ); 10542 } else { 10543 this.setFullScreen( !this.isFullPage() ); 10544 } 10545 // correct for no mouseout event on change 10546 if ( this.buttons ) { 10547 this.buttons.emulateExit(); 10548 } 10549 this.fullPageButton.element.focus(); 10550 if ( this.viewport ) { 10551 this.viewport.applyConstraints(); 10552 } 10553} 10554 10555function onRotateLeft() { 10556 if ( this.viewport ) { 10557 var currRotation = this.viewport.getRotation(); 10558 10559 if ( this.viewport.flipped ){ 10560 currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360); 10561 } else { 10562 currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360); 10563 } 10564 this.viewport.setRotation(currRotation); 10565 } 10566} 10567 10568function onRotateRight() { 10569 if ( this.viewport ) { 10570 var currRotation = this.viewport.getRotation(); 10571 10572 if ( this.viewport.flipped ){ 10573 currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360); 10574 } else { 10575 currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360); 10576 } 10577 this.viewport.setRotation(currRotation); 10578 } 10579} 10580/** 10581 * Note: When pressed flip control button 10582 */ 10583function onFlip() { 10584 this.viewport.toggleFlip(); 10585} 10586 10587function onPrevious(){ 10588 var previous = this._sequenceIndex - 1; 10589 if(this.navPrevNextWrap && previous < 0){ 10590 previous += this.tileSources.length; 10591 } 10592 this.goToPage( previous ); 10593} 10594 10595 10596function onNext(){ 10597 var next = this._sequenceIndex + 1; 10598 if(this.navPrevNextWrap && next >= this.tileSources.length){ 10599 next = 0; 10600 } 10601 this.goToPage( next ); 10602} 10603 10604 10605}( OpenSeadragon )); 10606 10607/* 10608 * OpenSeadragon - Navigator 10609 * 10610 * Copyright (C) 2009 CodePlex Foundation 10611 * Copyright (C) 2010-2013 OpenSeadragon contributors 10612 * 10613 * Redistribution and use in source and binary forms, with or without 10614 * modification, are permitted provided that the following conditions are 10615 * met: 10616 * 10617 * - Redistributions of source code must retain the above copyright notice, 10618 * this list of conditions and the following disclaimer. 10619 * 10620 * - Redistributions in binary form must reproduce the above copyright 10621 * notice, this list of conditions and the following disclaimer in the 10622 * documentation and/or other materials provided with the distribution. 10623 * 10624 * - Neither the name of CodePlex Foundation nor the names of its 10625 * contributors may be used to endorse or promote products derived from 10626 * this software without specific prior written permission. 10627 * 10628 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 10629 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 10630 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 10631 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10632 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10633 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 10634 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 10635 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 10636 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 10637 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 10638 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10639 */ 10640 10641(function( $ ){ 10642 10643/** 10644 * @class Navigator 10645 * @classdesc The Navigator provides a small view of the current image as fixed 10646 * while representing the viewport as a moving box serving as a frame 10647 * of reference in the larger viewport as to which portion of the image 10648 * is currently being examined. The navigator's viewport can be interacted 10649 * with using the keyboard or the mouse. 10650 * 10651 * @memberof OpenSeadragon 10652 * @extends OpenSeadragon.Viewer 10653 * @extends OpenSeadragon.EventSource 10654 * @param {Object} options 10655 */ 10656$.Navigator = function( options ){ 10657 10658 var viewer = options.viewer, 10659 _this = this,
vendor: 5,031 bytes, lines 10660-10777
10660 viewerSize, 10661 navigatorSize; 10662 10663 //We may need to create a new element and id if they did not 10664 //provide the id for the existing element 10665 if( !options.id ){ 10666 options.id = 'navigator-' + $.now(); 10667 this.element = $.makeNeutralElement( "div" ); 10668 options.controlOptions = { 10669 anchor: $.ControlAnchor.TOP_RIGHT, 10670 attachToViewer: true, 10671 autoFade: options.autoFade 10672 }; 10673 10674 if( options.position ){ 10675 if( 'BOTTOM_RIGHT' == options.position ){ 10676 options.controlOptions.anchor = $.ControlAnchor.BOTTOM_RIGHT; 10677 } else if( 'BOTTOM_LEFT' == options.position ){ 10678 options.controlOptions.anchor = $.ControlAnchor.BOTTOM_LEFT; 10679 } else if( 'TOP_RIGHT' == options.position ){ 10680 options.controlOptions.anchor = $.ControlAnchor.TOP_RIGHT; 10681 } else if( 'TOP_LEFT' == options.position ){ 10682 options.controlOptions.anchor = $.ControlAnchor.TOP_LEFT; 10683 } else if( 'ABSOLUTE' == options.position ){ 10684 options.controlOptions.anchor = $.ControlAnchor.ABSOLUTE; 10685 options.controlOptions.top = options.top; 10686 options.controlOptions.left = options.left; 10687 options.controlOptions.height = options.height; 10688 options.controlOptions.width = options.width; 10689 } 10690 } 10691 10692 } else { 10693 this.element = document.getElementById( options.id ); 10694 options.controlOptions = { 10695 anchor: $.ControlAnchor.NONE, 10696 attachToViewer: false, 10697 autoFade: false 10698 }; 10699 } 10700 this.element.id = options.id; 10701 this.element.className += ' navigator'; 10702 10703 options = $.extend( true, { 10704 sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio 10705 }, options, { 10706 element: this.element, 10707 tabIndex: -1, // No keyboard navigation, omit from tab order 10708 //These need to be overridden to prevent recursion since 10709 //the navigator is a viewer and a viewer has a navigator 10710 showNavigator: false, 10711 mouseNavEnabled: false, 10712 showNavigationControl: false, 10713 showSequenceControl: false, 10714 immediateRender: true, 10715 blendTime: 0, 10716 animationTime: 0, 10717 autoResize: options.autoResize, 10718 // prevent resizing the navigator from adding unwanted space around the image 10719 minZoomImageRatio: 1.0, 10720 background: options.background, 10721 opacity: options.opacity, 10722 borderColor: options.borderColor, 10723 displayRegionColor: options.displayRegionColor 10724 }); 10725 10726 options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio; 10727 10728 $.setElementTouchActionNone( this.element ); 10729 10730 this.borderWidth = 2; 10731 //At some browser magnification levels the display regions lines up correctly, but at some there appears to 10732 //be a one pixel gap. 10733 this.fudge = new $.Point(1, 1); 10734 this.totalBorderWidths = new $.Point(this.borderWidth * 2, this.borderWidth * 2).minus(this.fudge); 10735 10736 10737 if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) { 10738 (function( style, borderWidth ){ 10739 style.margin = '0px'; 10740 style.border = borderWidth + 'px solid ' + options.borderColor; 10741 style.padding = '0px'; 10742 style.background = options.background; 10743 style.opacity = options.opacity; 10744 style.overflow = 'hidden'; 10745 }( this.element.style, this.borderWidth)); 10746 } 10747 10748 this.displayRegion = $.makeNeutralElement( "div" ); 10749 this.displayRegion.id = this.element.id + '-displayregion'; 10750 this.displayRegion.className = 'displayregion'; 10751 10752 (function( style, borderWidth ){ 10753 style.position = 'relative'; 10754 style.top = '0px'; 10755 style.left = '0px'; 10756 style.fontSize = '0px'; 10757 style.overflow = 'hidden'; 10758 style.border = borderWidth + 'px solid ' + options.displayRegionColor; 10759 style.margin = '0px'; 10760 style.padding = '0px'; 10761 //TODO: IE doesn't like this property being set 10762 //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/} 10763 10764 style.background = 'transparent'; 10765 10766 // We use square bracket notation on the statement below, because float is a keyword. 10767 // This is important for the Google Closure compiler, if nothing else. 10768 /*jshint sub:true */ 10769 style['float'] = 'left'; //Webkit 10770 10771 style.cssFloat = 'left'; //Firefox 10772 style.styleFloat = 'left'; //IE 10773 style.zIndex = 999999999; 10774 style.cursor = 'default'; 10775 }( this.displayRegion.style, this.borderWidth )); 10776 10777 this.displayRegionContainer = $.makeNeutralElement("div");
10778 this.displayRegionContainer.id = this.element.id + '-displayregioncontainer'; 10779 this.displayRegionContainer.className = "displayregioncontainer"; 10780 this.displayRegionContainer.style.width = "100%"; 10781 this.displayRegionContainer.style.height = "100%"; 10782 10783 viewer.addControl( 10784 this.element, 10785 options.controlOptions 10786 ); 10787 10788 this._resizeWithViewer = options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE && 10789 options.controlOptions.anchor != $.ControlAnchor.NONE; 10790 10791 if (options.width && options.height) { 10792 this.setWidth(options.width); 10793 this.setHeight(options.height); 10794 } else if ( this._resizeWithViewer ) { 10795 viewerSize = $.getElementSize( viewer.element ); 10796 this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px'; 10797 this.element.style.width = Math.round( viewerSize.x * options.sizeRatio ) + 'px'; 10798 this.oldViewerSize = viewerSize; 10799 navigatorSize = $.getElementSize( this.element ); 10800 this.elementArea = navigatorSize.x * navigatorSize.y; 10801 } 10802 10803 this.oldContainerSize = new $.Point( 0, 0 ); 10804 10805 $.Viewer.apply( this, [ options ] ); 10806 10807 this.displayRegionContainer.appendChild(this.displayRegion); 10808 this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer); 10809 10810 function rotate(degrees) { 10811 _setTransformRotate(_this.displayRegionContainer, degrees); 10812 _setTransformRotate(_this.displayRegion, -degrees); 10813 _this.viewport.setRotation(degrees); 10814 } 10815 if (options.navigatorRotate) { 10816 var degrees = options.viewer.viewport ? 10817 options.viewer.viewport.getRotation() : 10818 options.viewer.degrees || 0; 10819 10820 rotate(degrees); 10821 options.viewer.addHandler("rotate", function (args) { 10822 rotate(args.degrees); 10823 }); 10824 } 10825 10826 10827 // Remove the base class' (Viewer's) innerTracker and replace it with our own 10828 this.innerTracker.destroy(); 10829 this.innerTracker = new $.MouseTracker({ 10830 element: this.element, 10831 dragHandler: $.delegate( this, onCanvasDrag ), 10832 clickHandler: $.delegate( this, onCanvasClick ), 10833 releaseHandler: $.delegate( this, onCanvasRelease ), 10834 scrollHandler: $.delegate( this, onCanvasScroll ) 10835 }); 10836 10837 this.addHandler("reset-size", function() { 10838 if (_this.viewport) { 10839 _this.viewport.goHome(true); 10840 } 10841 }); 10842 10843 viewer.world.addHandler("item-index-change", function(event) { 10844 window.setTimeout(function(){ 10845 var item = _this.world.getItemAt(event.previousIndex); 10846 _this.world.setItemIndex(item, event.newIndex); 10847 }, 1); 10848 }); 10849 10850 viewer.world.addHandler("remove-item", function(event) { 10851 var theirItem = event.item; 10852 var myItem = _this._getMatchingItem(theirItem); 10853 if (myItem) { 10854 _this.world.removeItem(myItem); 10855 } 10856 }); 10857 10858 this.update(viewer.viewport); 10859}; 10860 10861$.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{ 10862 10863 /** 10864 * Used to notify the navigator when its size has changed. 10865 * Especially useful when {@link OpenSeadragon.Options}.navigatorAutoResize is set to false and the navigator is resizable. 10866 * @function 10867 */ 10868 updateSize: function () { 10869 if ( this.viewport ) { 10870 var containerSize = new $.Point( 10871 (this.container.clientWidth === 0 ? 1 : this.container.clientWidth), 10872 (this.container.clientHeight === 0 ? 1 : this.container.clientHeight) 10873 ); 10874 10875 if ( !containerSize.equals( this.oldContainerSize ) ) { 10876 this.viewport.resize( containerSize, true ); 10877 this.viewport.goHome(true); 10878 this.oldContainerSize = containerSize; 10879 this.drawer.clear(); 10880 this.world.draw(); 10881 } 10882 } 10883 }, 10884 10885 /** 10886 * Explicitly sets the width of the navigator, in web coordinates. Disables automatic resizing. 10887 * @param {Number|String} width - the new width, either a number of pixels or a CSS string, such as "100%" 10888 */ 10889 setWidth: function(width) { 10890 this.width = width; 10891 this.element.style.width = typeof (width) == "number" ? (width + 'px') : width; 10892 this._resizeWithViewer = false;
10893 }, 10894 10895 /** 10896 * Explicitly sets the height of the navigator, in web coordinates. Disables automatic resizing. 10897 * @param {Number|String} height - the new height, either a number of pixels or a CSS string, such as "100%" 10898 */ 10899 setHeight: function(height) { 10900 this.height = height; 10901 this.element.style.height = typeof (height) == "number" ? (height + 'px') : height; 10902 this._resizeWithViewer = false; 10903 }, 10904 10905 /** 10906 * Flip navigator element 10907 * @param {Boolean} state - Flip state to set. 10908 */ 10909 setFlip: function(state) { 10910 this.viewport.setFlip(state); 10911 10912 this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)"); 10913 return this; 10914 }, 10915 10916 setDisplayTransform: function(rule) { 10917 setElementTransform(this.displayRegion, rule); 10918 setElementTransform(this.canvas, rule); 10919 setElementTransform(this.element, rule); 10920 }, 10921 10922 /** 10923 * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs. 10924 * @function 10925 * @param {OpenSeadragon.Viewport} The viewport this navigator is tracking. 10926 */ 10927 update: function( viewport ) { 10928 10929 var viewerSize, 10930 newWidth, 10931 newHeight, 10932 bounds, 10933 topleft, 10934 bottomright; 10935
10936 viewerSize = $.getElementSize( this.viewer.element ); 10937 if ( this._resizeWithViewer && viewerSize.x && viewerSize.y && !viewerSize.equals( this.oldViewerSize ) ) { 10938 this.oldViewerSize = viewerSize; 10939 10940 if ( this.maintainSizeRatio || !this.elementArea) { 10941 newWidth = viewerSize.x * this.sizeRatio; 10942 newHeight = viewerSize.y * this.sizeRatio; 10943 } else { 10944 newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y)); 10945 newHeight = this.elementArea / newWidth; 10946 } 10947 10948 this.element.style.width = Math.round( newWidth ) + 'px'; 10949 this.element.style.height = Math.round( newHeight ) + 'px'; 10950 10951 if (!this.elementArea) { 10952 this.elementArea = newWidth * newHeight; 10953 } 10954 10955 this.updateSize(); 10956 } 10957 10958 if (viewport && this.viewport) { 10959 bounds = viewport.getBoundsNoRotate(true); 10960 topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false); 10961 bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false) 10962 .minus( this.totalBorderWidths ); 10963 10964 //update style for navigator-box 10965 var style = this.displayRegion.style; 10966 style.display = this.world.getItemCount() ? 'block' : 'none'; 10967 10968 style.top = Math.round( topleft.y ) + 'px'; 10969 style.left = Math.round( topleft.x ) + 'px'; 10970 10971 var width = Math.abs( topleft.x - bottomright.x ); 10972 var height = Math.abs( topleft.y - bottomright.y ); 10973 // make sure width and height are non-negative so IE doesn't throw 10974 style.width = Math.round( Math.max( width, 0 ) ) + 'px'; 10975 style.height = Math.round( Math.max( height, 0 ) ) + 'px'; 10976 } 10977 10978 }, 10979 10980 // overrides Viewer.addTiledImage 10981 addTiledImage: function(options) { 10982 var _this = this; 10983 10984 var original = options.originalTiledImage; 10985 delete options.original; 10986 10987 var optionsClone = $.extend({}, options, { 10988 success: function(event) { 10989 var myItem = event.item; 10990 myItem._originalForNavigator = original; 10991 _this._matchBounds(myItem, original, true); 10992 10993 function matchBounds() { 10994 _this._matchBounds(myItem, original); 10995 } 10996 10997 function matchOpacity() { 10998 _this._matchOpacity(myItem, original); 10999 } 11000 11001 function matchCompositeOperation() { 11002 _this._matchCompositeOperation(myItem, original); 11003 } 11004 11005 original.addHandler('bounds-change', matchBounds); 11006 original.addHandler('clip-change', matchBounds); 11007 original.addHandler('opacity-change', matchOpacity); 11008 original.addHandler('composite-operation-change', matchCompositeOperation); 11009 } 11010 }); 11011 11012 return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]); 11013 }, 11014 11015 // private 11016 _getMatchingItem: function(theirItem) { 11017 var count = this.world.getItemCount(); 11018 var item; 11019 for (var i = 0; i < count; i++) { 11020 item = this.world.getItemAt(i); 11021 if (item._originalForNavigator === theirItem) { 11022 return item; 11023 } 11024 } 11025 11026 return null; 11027 }, 11028 11029 // private 11030 _matchBounds: function(myItem, theirItem, immediately) { 11031 var bounds = theirItem.getBoundsNoRotate(); 11032 myItem.setPosition(bounds.getTopLeft(), immediately); 11033 myItem.setWidth(bounds.width, immediately); 11034 myItem.setRotation(theirItem.getRotation(), immediately); 11035 myItem.setClip(theirItem.getClip()); 11036 }, 11037 11038 // private 11039 _matchOpacity: function(myItem, theirItem) { 11040 myItem.setOpacity(theirItem.opacity); 11041 }, 11042 11043 // private 11044 _matchCompositeOperation: function(myItem, theirItem) { 11045 myItem.setCompositeOperation(theirItem.compositeOperation); 11046 } 11047}); 11048 11049 11050/** 11051 * @private 11052 * @inner 11053 * @function 11054 */ 11055function onCanvasClick( event ) { 11056 var canvasClickEventArgs = { 11057 tracker: event.eventSource, 11058 position: event.position, 11059 quick: event.quick, 11060 shift: event.shift, 11061 originalEvent: event.originalEvent, 11062 preventDefaultAction: event.preventDefaultAction 11063 }; 11064 /** 11065 * Raised when a click event occurs on the {@link OpenSeadragon.Viewer#navigator} element. 11066 * 11067 * @event navigator-click 11068 * @memberof OpenSeadragon.Viewer 11069 * @type {object} 11070 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 11071 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 11072 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 11073 * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags. 11074 * @property {Boolean} shift - True if the shift key was pressed during this event. 11075 * @property {Object} originalEvent - The original DOM event.
vendor: 4,327 bytes, lines 11076-11188
11076 * @property {?Object} userData - Arbitrary subscriber-defined object. 11077 * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false. 11078 */ 11079 11080 this.viewer.raiseEvent('navigator-click', canvasClickEventArgs); 11081 11082 if ( !canvasClickEventArgs.preventDefaultAction && event.quick && this.viewer.viewport && (this.panVertical || this.panHorizontal)) { 11083 if(this.viewer.viewport.flipped) { 11084 event.position.x = this.viewport.getContainerSize().x - event.position.x; 11085 } 11086 var target = this.viewport.pointFromPixel(event.position); 11087 if (!this.panVertical) { 11088 // perform only horizonal pan 11089 target.y = this.viewer.viewport.getCenter(true).y; 11090 } else if (!this.panHorizontal) { 11091 // perform only vertical pan 11092 target.x = this.viewer.viewport.getCenter(true).x; 11093 } 11094 this.viewer.viewport.panTo(target); 11095 this.viewer.viewport.applyConstraints(); 11096 } 11097 11098} 11099 11100/** 11101 * @private 11102 * @inner 11103 * @function 11104 */ 11105function onCanvasDrag( event ) { 11106 var canvasDragEventArgs = { 11107 tracker: event.eventSource, 11108 position: event.position, 11109 delta: event.delta, 11110 speed: event.speed, 11111 direction: event.direction, 11112 shift: event.shift, 11113 originalEvent: event.originalEvent, 11114 preventDefaultAction: event.preventDefaultAction 11115 }; 11116 /** 11117 * Raised when a drag event occurs on the {@link OpenSeadragon.Viewer#navigator} element. 11118 * 11119 * @event navigator-drag 11120 * @memberof OpenSeadragon.Viewer 11121 * @type {object} 11122 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 11123 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 11124 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 11125 * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag. 11126 * @property {Number} speed - Current computed speed, in pixels per second. 11127 * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 11128 * @property {Boolean} shift - True if the shift key was pressed during this event. 11129 * @property {Object} originalEvent - The original DOM event. 11130 * @property {?Object} userData - Arbitrary subscriber-defined object. 11131 * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false. 11132 */ 11133 this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs); 11134 11135 if ( !canvasDragEventArgs.preventDefaultAction && this.viewer.viewport ) { 11136 if( !this.panHorizontal ){ 11137 event.delta.x = 0; 11138 } 11139 if( !this.panVertical ){ 11140 event.delta.y = 0; 11141 } 11142 11143 if(this.viewer.viewport.flipped){ 11144 event.delta.x = -event.delta.x; 11145 } 11146 11147 this.viewer.viewport.panBy( 11148 this.viewport.deltaPointsFromPixels( 11149 event.delta 11150 ) 11151 ); 11152 if( this.viewer.constrainDuringPan ){ 11153 this.viewer.viewport.applyConstraints(); 11154 } 11155 } 11156} 11157 11158 11159/** 11160 * @private 11161 * @inner 11162 * @function 11163 */ 11164function onCanvasRelease( event ) { 11165 if ( event.insideElementPressed && this.viewer.viewport ) { 11166 this.viewer.viewport.applyConstraints(); 11167 } 11168} 11169 11170 11171/** 11172 * @private 11173 * @inner 11174 * @function 11175 */ 11176function onCanvasScroll( event ) { 11177 /** 11178 * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#navigator} element (mouse wheel, touch pinch, etc.). 11179 * 11180 * @event navigator-scroll 11181 * @memberof OpenSeadragon.Viewer 11182 * @type {object} 11183 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 11184 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 11185 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 11186 * @property {Number} scroll - The scroll delta for the event. 11187 * @property {Boolean} shift - True if the shift key was pressed during this event. 11188 * @property {Object} originalEvent - The original DOM event.
11189 * @property {?Object} userData - Arbitrary subscriber-defined object. 11190 */ 11191 this.viewer.raiseEvent( 'navigator-scroll', { 11192 tracker: event.eventSource, 11193 position: event.position, 11194 scroll: event.scroll, 11195 shift: event.shift, 11196 originalEvent: event.originalEvent 11197 }); 11198 11199 //don't scroll the page up and down if the user is scrolling 11200 //in the navigator 11201 return false; 11202} 11203 11204/** 11205 * @function 11206 * @private 11207 * @param {Object} element 11208 * @param {Number} degrees 11209 */ 11210function _setTransformRotate( element, degrees ) { 11211 setElementTransform(element, "rotate(" + degrees + "deg)"); 11212} 11213 11214function setElementTransform( element, rule ) { 11215 element.style.webkitTransform = rule; 11216 element.style.mozTransform = rule; 11217 element.style.msTransform = rule; 11218 element.style.oTransform = rule; 11219 element.style.transform = rule; 11220} 11221 11222}( OpenSeadragon )); 11223 11224/* 11225 * OpenSeadragon - getString/setString 11226 * 11227 * Copyright (C) 2009 CodePlex Foundation 11228 * Copyright (C) 2010-2013 OpenSeadragon contributors 11229 * 11230 * Redistribution and use in source and binary forms, with or without 11231 * modification, are permitted provided that the following conditions are 11232 * met: 11233 * 11234 * - Redistributions of source code must retain the above copyright notice, 11235 * this list of conditions and the following disclaimer. 11236 * 11237 * - Redistributions in binary form must reproduce the above copyright 11238 * notice, this list of conditions and the following disclaimer in the 11239 * documentation and/or other materials provided with the distribution. 11240 * 11241 * - Neither the name of CodePlex Foundation nor the names of its 11242 * contributors may be used to endorse or promote products derived from 11243 * this software without specific prior written permission. 11244 * 11245 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11246 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11247 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11248 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11249 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11250 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11251 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11252 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11253 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11254 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11255 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11256 */ 11257 11258(function( $ ){ 11259 11260//TODO: I guess this is where the i18n needs to be reimplemented. I'll look 11261// into existing patterns for i18n in javascript but i think that mimicking 11262// pythons gettext might be a reasonable approach. 11263var I18N = { 11264 Errors: { 11265 Dzc: "Sorry, we don't support Deep Zoom Collections!", 11266 Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.", 11267 Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.", 11268 ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.", 11269 Security: "It looks like a security restriction stopped us from " + 11270 "loading this Deep Zoom Image.", 11271 Status: "This space unintentionally left blank ({0} {1}).", 11272 OpenFailed: "Unable to open {0}: {1}" 11273 }, 11274 11275 Tooltips: { 11276 FullPage: "Toggle full page", 11277 Home: "Go home", 11278 ZoomIn: "Zoom in", 11279 ZoomOut: "Zoom out", 11280 NextPage: "Next page", 11281 PreviousPage: "Previous page", 11282 RotateLeft: "Rotate left", 11283 RotateRight: "Rotate right", 11284 Flip: "Flip Horizontally" 11285 } 11286}; 11287 11288$.extend( $, /** @lends OpenSeadragon */{ 11289 11290 /** 11291 * @function 11292 * @param {String} property 11293 */ 11294 getString: function( prop ) { 11295 11296 var props = prop.split('.'), 11297 string = null, 11298 args = arguments, 11299 container = I18N, 11300 i; 11301 11302 for (i = 0; i < props.length - 1; i++) { 11303 // in case not a subproperty 11304 container = container[ props[ i ] ] || {}; 11305 }
vendor: 4,527 bytes, lines 11306-11445
11306 string = container[ props[ i ] ]; 11307 11308 if ( typeof ( string ) != "string" ) { 11309 $.console.log( "Untranslated source string:", prop ); 11310 string = ""; // FIXME: this breaks gettext()-style convention, which would return source 11311 } 11312 11313 return string.replace(/\{\d+\}/g, function(capture) { 11314 var i = parseInt( capture.match( /\d+/ ), 10 ) + 1; 11315 return i < args.length ? 11316 args[ i ] : 11317 ""; 11318 }); 11319 }, 11320 11321 /** 11322 * @function 11323 * @param {String} property 11324 * @param {*} value 11325 */ 11326 setString: function( prop, value ) { 11327 11328 var props = prop.split('.'), 11329 container = I18N, 11330 i; 11331 11332 for ( i = 0; i < props.length - 1; i++ ) { 11333 if ( !container[ props[ i ] ] ) { 11334 container[ props[ i ] ] = {}; 11335 } 11336 container = container[ props[ i ] ]; 11337 } 11338 11339 container[ props[ i ] ] = value; 11340 } 11341 11342}); 11343 11344}( OpenSeadragon )); 11345 11346/* 11347 * OpenSeadragon - Point 11348 * 11349 * Copyright (C) 2009 CodePlex Foundation 11350 * Copyright (C) 2010-2013 OpenSeadragon contributors 11351 * 11352 * Redistribution and use in source and binary forms, with or without 11353 * modification, are permitted provided that the following conditions are 11354 * met: 11355 * 11356 * - Redistributions of source code must retain the above copyright notice, 11357 * this list of conditions and the following disclaimer. 11358 * 11359 * - Redistributions in binary form must reproduce the above copyright 11360 * notice, this list of conditions and the following disclaimer in the 11361 * documentation and/or other materials provided with the distribution. 11362 * 11363 * - Neither the name of CodePlex Foundation nor the names of its 11364 * contributors may be used to endorse or promote products derived from 11365 * this software without specific prior written permission. 11366 * 11367 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11368 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11369 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11370 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11371 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11372 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11373 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11374 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11375 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11376 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11377 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11378 */ 11379 11380(function( $ ){ 11381 11382/** 11383 * @class Point 11384 * @classdesc A Point is really used as a 2-dimensional vector, equally useful for 11385 * representing a point on a plane, or the height and width of a plane 11386 * not requiring any other frame of reference. 11387 * 11388 * @memberof OpenSeadragon 11389 * @param {Number} [x] The vector component 'x'. Defaults to the origin at 0. 11390 * @param {Number} [y] The vector component 'y'. Defaults to the origin at 0. 11391 */ 11392$.Point = function( x, y ) { 11393 /** 11394 * The vector component 'x'. 11395 * @member {Number} x 11396 * @memberof OpenSeadragon.Point# 11397 */ 11398 this.x = typeof ( x ) == "number" ? x : 0; 11399 /** 11400 * The vector component 'y'. 11401 * @member {Number} y 11402 * @memberof OpenSeadragon.Point# 11403 */ 11404 this.y = typeof ( y ) == "number" ? y : 0; 11405}; 11406 11407/** @lends OpenSeadragon.Point.prototype */ 11408$.Point.prototype = { 11409 /** 11410 * @function 11411 * @returns {OpenSeadragon.Point} a duplicate of this Point 11412 */ 11413 clone: function() { 11414 return new $.Point(this.x, this.y); 11415 }, 11416 11417 /** 11418 * Add another Point to this point and return a new Point. 11419 * @function 11420 * @param {OpenSeadragon.Point} point The point to add vector components. 11421 * @returns {OpenSeadragon.Point} A new point representing the sum of the 11422 * vector components 11423 */ 11424 plus: function( point ) { 11425 return new $.Point( 11426 this.x + point.x, 11427 this.y + point.y 11428 ); 11429 }, 11430 11431 /** 11432 * Subtract another Point to this point and return a new Point. 11433 * @function 11434 * @param {OpenSeadragon.Point} point The point to subtract vector components. 11435 * @returns {OpenSeadragon.Point} A new point representing the subtraction of the 11436 * vector components 11437 */ 11438 minus: function( point ) { 11439 return new $.Point( 11440 this.x - point.x, 11441 this.y - point.y 11442 ); 11443 }, 11444 11445 /**
11446 * Multiply this point by a factor and return a new Point. 11447 * @function 11448 * @param {Number} factor The factor to multiply vector components. 11449 * @returns {OpenSeadragon.Point} A new point representing the multiplication 11450 * of the vector components by the factor 11451 */ 11452 times: function( factor ) { 11453 return new $.Point( 11454 this.x * factor, 11455 this.y * factor 11456 ); 11457 }, 11458 11459 /** 11460 * Divide this point by a factor and return a new Point. 11461 * @function 11462 * @param {Number} factor The factor to divide vector components. 11463 * @returns {OpenSeadragon.Point} A new point representing the division of the 11464 * vector components by the factor 11465 */ 11466 divide: function( factor ) { 11467 return new $.Point( 11468 this.x / factor, 11469 this.y / factor 11470 ); 11471 }, 11472 11473 /** 11474 * Compute the opposite of this point and return a new Point. 11475 * @function 11476 * @returns {OpenSeadragon.Point} A new point representing the opposite of the 11477 * vector components 11478 */ 11479 negate: function() { 11480 return new $.Point( -this.x, -this.y ); 11481 }, 11482 11483 /** 11484 * Compute the distance between this point and another point. 11485 * @function 11486 * @param {OpenSeadragon.Point} point The point to compute the distance with. 11487 * @returns {Number} The distance between the 2 points 11488 */ 11489 distanceTo: function( point ) { 11490 return Math.sqrt( 11491 Math.pow( this.x - point.x, 2 ) + 11492 Math.pow( this.y - point.y, 2 ) 11493 ); 11494 }, 11495 11496 /** 11497 * Compute the squared distance between this point and another point. 11498 * Useful for optimizing things like comparing distances. 11499 * @function 11500 * @param {OpenSeadragon.Point} point The point to compute the squared distance with. 11501 * @returns {Number} The squared distance between the 2 points 11502 */ 11503 squaredDistanceTo: function( point ) { 11504 return Math.pow( this.x - point.x, 2 ) + 11505 Math.pow( this.y - point.y, 2 ); 11506 }, 11507 11508 /** 11509 * Apply a function to each coordinate of this point and return a new point. 11510 * @function 11511 * @param {function} func The function to apply to each coordinate. 11512 * @returns {OpenSeadragon.Point} A new point with the coordinates computed 11513 * by the specified function 11514 */ 11515 apply: function( func ) { 11516 return new $.Point( func( this.x ), func( this.y ) ); 11517 }, 11518 11519 /** 11520 * Check if this point is equal to another one. 11521 * @function 11522 * @param {OpenSeadragon.Point} point The point to compare this point with. 11523 * @returns {Boolean} true if they are equal, false otherwise. 11524 */ 11525 equals: function( point ) { 11526 return ( 11527 point instanceof $.Point 11528 ) && ( 11529 this.x === point.x 11530 ) && ( 11531 this.y === point.y 11532 ); 11533 }, 11534 11535 /** 11536 * Rotates the point around the specified pivot 11537 * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point 11538 * @function 11539 * @param {Number} degress to rotate around the pivot. 11540 * @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate. 11541 * Defaults to the origin. 11542 * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot 11543 */ 11544 rotate: function (degrees, pivot) { 11545 pivot = pivot || new $.Point(0, 0); 11546 var cos; 11547 var sin; 11548 // Avoid float computations when possible 11549 if (degrees % 90 === 0) { 11550 var d = $.positiveModulo(degrees, 360); 11551 switch (d) { 11552 case 0: 11553 cos = 1; 11554 sin = 0; 11555 break; 11556 case 90: 11557 cos = 0; 11558 sin = 1; 11559 break; 11560 case 180: 11561 cos = -1; 11562 sin = 0; 11563 break; 11564 case 270: 11565 cos = 0; 11566 sin = -1; 11567 break; 11568 } 11569 } else { 11570 var angle = degrees * Math.PI / 180.0; 11571 cos = Math.cos(angle); 11572 sin = Math.sin(angle); 11573 } 11574 var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x; 11575 var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y; 11576 return new $.Point(x, y); 11577 }, 11578 11579 /** 11580 * Convert this point to a string in the format (x,y) where x and y are 11581 * rounded to the nearest integer. 11582 * @function 11583 * @returns {String} A string representation of this point. 11584 */ 11585 toString: function() { 11586 return "(" + (Math.round(this.x * 100) / 100) + "," + (Math.round(this.y * 100) / 100) + ")"; 11587 } 11588}; 11589 11590}( OpenSeadragon )); 11591 11592/* 11593 * OpenSeadragon - TileSource 11594 * 11595 * Copyright (C) 2009 CodePlex Foundation 11596 * Copyright (C) 2010-2013 OpenSeadragon contributors 11597 * 11598 * Redistribution and use in source and binary forms, with or without 11599 * modification, are permitted provided that the following conditions are 11600 * met: 11601 * 11602 * - Redistributions of source code must retain the above copyright notice, 11603 * this list of conditions and the following disclaimer. 11604 * 11605 * - Redistributions in binary form must reproduce the above copyright 11606 * notice, this list of conditions and the following disclaimer in the 11607 * documentation and/or other materials provided with the distribution. 11608 * 11609 * - Neither the name of CodePlex Foundation nor the names of its 11610 * contributors may be used to endorse or promote products derived from 11611 * this software without specific prior written permission. 11612 * 11613 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11614 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11615 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11616 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11617 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11618 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11619 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11620 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11621 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11622 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11623 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11624 */ 11625 11626(function( $ ){ 11627 11628 11629/** 11630 * @class TileSource 11631 * @classdesc The TileSource contains the most basic implementation required to create a 11632 * smooth transition between layers in an image pyramid. It has only a single key 11633 * interface that must be implemented to complete its key functionality: 11634 * 'getTileUrl'. It also has several optional interfaces that can be 11635 * implemented if a new TileSource wishes to support configuration via a simple 11636 * object or array ('configure') and if the tile source supports or requires 11637 * configuration via retrieval of a document on the network ala AJAX or JSONP, 11638 * ('getImageInfo'). 11639 * <br/> 11640 * By default the image pyramid is split into N layers where the image's longest 11641 * side in M (in pixels), where N is the smallest integer which satisfies 11642 * <strong>2^(N+1) >= M</strong>. 11643 * 11644 * @memberof OpenSeadragon 11645 * @extends OpenSeadragon.EventSource 11646 * @param {Object} options 11647 * You can either specify a URL, or literally define the TileSource (by specifying 11648 * width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former, 11649 * the extending class is expected to implement 'getImageInfo' and 'configure'. 11650 * For the latter, the construction is assumed to occur through 11651 * the extending classes implementation of 'configure'. 11652 * @param {String} [options.url] 11653 * The URL for the data necessary for this TileSource. 11654 * @param {String} [options.referenceStripThumbnailUrl] 11655 * The URL for a thumbnail image to be used by the reference strip 11656 * @param {Function} [options.success] 11657 * A function to be called upon successful creation. 11658 * @param {Boolean} [options.ajaxWithCredentials] 11659 * If this TileSource needs to make an AJAX call, this specifies whether to set 11660 * the XHR's withCredentials (for accessing secure data). 11661 * @param {Object} [options.ajaxHeaders] 11662 * A set of headers to include in AJAX requests. 11663 * @param {Number} [options.width] 11664 * Width of the source image at max resolution in pixels. 11665 * @param {Number} [options.height] 11666 * Height of the source image at max resolution in pixels. 11667 * @param {Number} [options.tileSize] 11668 * The size of the tiles to assumed to make up each pyramid layer in pixels. 11669 * Tile size determines the point at which the image pyramid must be 11670 * divided into a matrix of smaller images. 11671 * Use options.tileWidth and options.tileHeight to support non-square tiles. 11672 * @param {Number} [options.tileWidth] 11673 * The width of the tiles to assumed to make up each pyramid layer in pixels. 11674 * @param {Number} [options.tileHeight] 11675 * The height of the tiles to assumed to make up each pyramid layer in pixels. 11676 * @param {Number} [options.tileOverlap] 11677 * The number of pixels each tile is expected to overlap touching tiles. 11678 * @param {Number} [options.minLevel] 11679 * The minimum level to attempt to load. 11680 * @param {Number} [options.maxLevel] 11681 * The maximum level to attempt to load. 11682 */ 11683$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) { 11684 var _this = this; 11685 11686 var args = arguments, 11687 options, 11688 i; 11689 11690 if( $.isPlainObject( width ) ){ 11691 options = width; 11692 }else{ 11693 options = { 11694 width: args[0], 11695 height: args[1], 11696 tileSize: args[2], 11697 tileOverlap: args[3], 11698 minLevel: args[4], 11699 maxLevel: args[5] 11700 }; 11701 } 11702 11703 //Tile sources supply some events, namely 'ready' when they must be configured 11704 //by asynchronously fetching their configuration data. 11705 $.EventSource.call( this ); 11706 11707 //we allow options to override anything we don't treat as 11708 //required via idiomatic options or which is functionally 11709 //set depending on the state of the readiness of this tile 11710 //source 11711 $.extend( true, this, options ); 11712 11713 if (!this.success) { 11714 //Any functions that are passed as arguments are bound to the ready callback 11715 for ( i = 0; i < arguments.length; i++ ) { 11716 if ( $.isFunction( arguments[ i ] ) ) { 11717 this.success = arguments[ i ]; 11718 //only one callback per constructor 11719 break; 11720 } 11721 } 11722 } 11723 11724 if (this.success) { 11725 this.addHandler( 'ready', function ( event ) { 11726 _this.success( event ); 11727 } ); 11728 } 11729 11730 /** 11731 * Ratio of width to height 11732 * @member {Number} aspectRatio 11733 * @memberof OpenSeadragon.TileSource# 11734 */ 11735 /** 11736 * Vector storing x and y dimensions ( width and height respectively ).
11737 * @member {OpenSeadragon.Point} dimensions 11738 * @memberof OpenSeadragon.TileSource# 11739 */ 11740 /** 11741 * The overlap in pixels each tile shares with its adjacent neighbors. 11742 * @member {Number} tileOverlap 11743 * @memberof OpenSeadragon.TileSource# 11744 */ 11745 /** 11746 * The minimum pyramid level this tile source supports or should attempt to load. 11747 * @member {Number} minLevel 11748 * @memberof OpenSeadragon.TileSource# 11749 */ 11750 /** 11751 * The maximum pyramid level this tile source supports or should attempt to load. 11752 * @member {Number} maxLevel 11753 * @memberof OpenSeadragon.TileSource# 11754 */ 11755 /** 11756 * 11757 * @member {Boolean} ready 11758 * @memberof OpenSeadragon.TileSource# 11759 */ 11760 11761 if( 'string' == $.type( arguments[ 0 ] ) ){ 11762 this.url = arguments[0]; 11763 } 11764 11765 if (this.url) { 11766 //in case the getImageInfo method is overridden and/or implies an 11767 //async mechanism set some safe defaults first 11768 this.aspectRatio = 1; 11769 this.dimensions = new $.Point( 10, 10 ); 11770 this._tileWidth = 0; 11771 this._tileHeight = 0; 11772 this.tileOverlap = 0; 11773 this.minLevel = 0; 11774 this.maxLevel = 0; 11775 this.ready = false; 11776 //configuration via url implies the extending class 11777 //implements and 'configure' 11778 this.getImageInfo( this.url ); 11779 11780 } else { 11781 11782 //explicit configuration via positional args in constructor 11783 //or the more idiomatic 'options' object 11784 this.ready = true; 11785 this.aspectRatio = (options.width && options.height) ? 11786 (options.width / options.height) : 1; 11787 this.dimensions = new $.Point( options.width, options.height ); 11788 11789 if ( this.tileSize ){ 11790 this._tileWidth = this._tileHeight = this.tileSize; 11791 delete this.tileSize; 11792 } else { 11793 if( this.tileWidth ){ 11794 // We were passed tileWidth in options, but we want to rename it 11795 // with a leading underscore to make clear that it is not safe to directly modify it 11796 this._tileWidth = this.tileWidth; 11797 delete this.tileWidth; 11798 } else { 11799 this._tileWidth = 0; 11800 } 11801 11802 if( this.tileHeight ){ 11803 // See note above about renaming this.tileWidth 11804 this._tileHeight = this.tileHeight; 11805 delete this.tileHeight; 11806 } else { 11807 this._tileHeight = 0; 11808 } 11809 } 11810 11811 this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0; 11812 this.minLevel = options.minLevel ? options.minLevel : 0; 11813 this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ? 11814 options.maxLevel : ( 11815 ( options.width && options.height ) ? Math.ceil( 11816 Math.log( Math.max( options.width, options.height ) ) / 11817 Math.log( 2 ) 11818 ) : 0 11819 ); 11820 if( this.success && $.isFunction( this.success ) ){ 11821 this.success( this ); 11822 } 11823 } 11824 11825 11826}; 11827 11828/** @lends OpenSeadragon.TileSource.prototype */ 11829$.TileSource.prototype = { 11830 11831 getTileSize: function( level ) { 11832 $.console.error( 11833 "[TileSource.getTileSize] is deprecated. " + 11834 "Use TileSource.getTileWidth() and TileSource.getTileHeight() instead" 11835 ); 11836 return this._tileWidth; 11837 }, 11838 11839 /** 11840 * Return the tileWidth for a given level. 11841 * Subclasses should override this if tileWidth can be different at different levels 11842 * such as in IIIFTileSource. Code should use this function rather than reading 11843 * from ._tileWidth directly. 11844 * @function 11845 * @param {Number} level 11846 */ 11847 getTileWidth: function( level ) { 11848 if (!this._tileWidth) { 11849 return this.getTileSize(level); 11850 } 11851 return this._tileWidth; 11852 }, 11853 11854 /** 11855 * Return the tileHeight for a given level. 11856 * Subclasses should override this if tileHeight can be different at different levels 11857 * such as in IIIFTileSource. Code should use this function rather than reading 11858 * from ._tileHeight directly. 11859 * @function 11860 * @param {Number} level 11861 */ 11862 getTileHeight: function( level ) { 11863 if (!this._tileHeight) { 11864 return this.getTileSize(level); 11865 } 11866 return this._tileHeight; 11867 }, 11868 11869 /** 11870 * @function 11871 * @param {Number} level 11872 */ 11873 getLevelScale: function( level ) { 11874 11875 // see https://github.com/openseadragon/openseadragon/issues/22 11876 // we use the tilesources implementation of getLevelScale to generate 11877 // a memoized re-implementation 11878 var levelScaleCache = {}, 11879 i; 11880 for( i = 0; i <= this.maxLevel; i++ ){ 11881 levelScaleCache[ i ] = 1 / Math.pow(2, this.maxLevel - i); 11882 } 11883 this.getLevelScale = function( _level ){ 11884 return levelScaleCache[ _level ]; 11885 }; 11886 return this.getLevelScale( level ); 11887 }, 11888 11889 /** 11890 * @function 11891 * @param {Number} level 11892 */ 11893 getNumTiles: function( level ) { 11894 var scale = this.getLevelScale( level ), 11895 x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ), 11896 y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) ); 11897 11898 return new $.Point( x, y ); 11899 }, 11900 11901 /** 11902 * @function 11903 * @param {Number} level 11904 */ 11905 getPixelRatio: function( level ) { 11906 var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ), 11907 rx = 1.0 / imageSizeScaled.x, 11908 ry = 1.0 / imageSizeScaled.y; 11909 11910 return new $.Point(rx, ry); 11911 }, 11912 11913 11914 /** 11915 * @function 11916 * @returns {Number} The highest level in this tile source that can be contained in a single tile. 11917 */ 11918 getClosestLevel: function() { 11919 var i, 11920 tiles; 11921 11922 for (i = this.minLevel + 1; i <= this.maxLevel; i++){ 11923 tiles = this.getNumTiles(i); 11924 if (tiles.x > 1 || tiles.y > 1) { 11925 break; 11926 } 11927 } 11928 11929 return i - 1; 11930 }, 11931 11932 /** 11933 * @function 11934 * @param {Number} level 11935 * @param {OpenSeadragon.Point} point 11936 */ 11937 getTileAtPoint: function(level, point) {
11938 var validPoint = point.x >= 0 && point.x <= 1 && 11939 point.y >= 0 && point.y <= 1 / this.aspectRatio; 11940 $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point."); 11941 11942 var widthScaled = this.dimensions.x * this.getLevelScale(level); 11943 var pixelX = point.x * widthScaled; 11944 var pixelY = point.y * widthScaled; 11945 11946 var x = Math.floor(pixelX / this.getTileWidth(level)); 11947 var y = Math.floor(pixelY / this.getTileHeight(level)); 11948 11949 // When point.x == 1 or point.y == 1 / this.aspectRatio we want to 11950 // return the last tile of the row/column 11951 if (point.x >= 1) { 11952 x = this.getNumTiles(level).x - 1; 11953 } 11954 var EPSILON = 1e-15; 11955 if (point.y >= 1 / this.aspectRatio - EPSILON) { 11956 y = this.getNumTiles(level).y - 1; 11957 } 11958 11959 return new $.Point(x, y); 11960 }, 11961 11962 /** 11963 * @function 11964 * @param {Number} level 11965 * @param {Number} x 11966 * @param {Number} y 11967 * @param {Boolean} [isSource=false] Whether to return the source bounds of the tile. 11968 * @returns {OpenSeadragon.Rect} Either where this tile fits (in normalized coordinates) or the 11969 * portion of the tile to use as the source of the drawing operation (in pixels), depending on 11970 * the isSource parameter. 11971 */ 11972 getTileBounds: function( level, x, y, isSource ) { 11973 var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ), 11974 tileWidth = this.getTileWidth(level), 11975 tileHeight = this.getTileHeight(level), 11976 px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap, 11977 py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap, 11978 sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap, 11979 sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap, 11980 scale = 1.0 / dimensionsScaled.x; 11981 11982 sx = Math.min( sx, dimensionsScaled.x - px ); 11983 sy = Math.min( sy, dimensionsScaled.y - py ); 11984 11985 if (isSource) { 11986 return new $.Rect(0, 0, sx, sy); 11987 } 11988 11989 return new $.Rect( px * scale, py * scale, sx * scale, sy * scale ); 11990 }, 11991 11992 11993 /** 11994 * Responsible for retrieving, and caching the 11995 * image metadata pertinent to this TileSources implementation. 11996 * @function 11997 * @param {String} url 11998 * @throws {Error} 11999 */ 12000 getImageInfo: function( url ) { 12001 var _this = this, 12002 callbackName, 12003 callback, 12004 readySource, 12005 options, 12006 urlParts, 12007 filename, 12008 lastDot; 12009 12010 12011 if( url ) { 12012 urlParts = url.split( '/' ); 12013 filename = urlParts[ urlParts.length - 1 ]; 12014 lastDot = filename.lastIndexOf( '.' ); 12015 if ( lastDot > -1 ) { 12016 urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot ); 12017 } 12018 } 12019 12020 callback = function( data ){ 12021 if( typeof (data) === "string" ) { 12022 data = $.parseXml( data ); 12023 } 12024 var $TileSource = $.TileSource.determineType( _this, data, url ); 12025 if ( !$TileSource ) { 12026 /** 12027 * Raised when an error occurs loading a TileSource. 12028 * 12029 * @event open-failed 12030 * @memberof OpenSeadragon.TileSource 12031 * @type {object} 12032 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 12033 * @property {String} message 12034 * @property {String} source 12035 * @property {?Object} userData - Arbitrary subscriber-defined object. 12036 */ 12037 _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } ); 12038 return; 12039 } 12040 12041 options = $TileSource.prototype.configure.apply( _this, [ data, url ]); 12042 if (options.ajaxWithCredentials === undefined) { 12043 options.ajaxWithCredentials = _this.ajaxWithCredentials; 12044 } 12045 12046 readySource = new $TileSource( options ); 12047 _this.ready = true; 12048 /** 12049 * Raised when a TileSource is opened and initialized. 12050 * 12051 * @event ready 12052 * @memberof OpenSeadragon.TileSource 12053 * @type {object} 12054 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 12055 * @property {Object} tileSource
vendor: 9,701 bytes, lines 12056-12314
12056 * @property {?Object} userData - Arbitrary subscriber-defined object. 12057 */ 12058 _this.raiseEvent( 'ready', { tileSource: readySource } ); 12059 }; 12060 12061 if( url.match(/\.js$/) ){ 12062 //TODO: Its not very flexible to require tile sources to end jsonp 12063 // request for info with a url that ends with '.js' but for 12064 // now it's the only way I see to distinguish uniformly. 12065 callbackName = url.split('/').pop().replace('.js', ''); 12066 $.jsonp({ 12067 url: url, 12068 async: false, 12069 callbackName: callbackName, 12070 callback: callback 12071 }); 12072 } else { 12073 // request info via xhr asynchronously. 12074 $.makeAjaxRequest( { 12075 url: url, 12076 withCredentials: this.ajaxWithCredentials, 12077 headers: this.ajaxHeaders, 12078 success: function( xhr ) { 12079 var data = processResponse( xhr ); 12080 callback( data ); 12081 }, 12082 error: function ( xhr, exc ) { 12083 var msg; 12084 12085 /* 12086 IE < 10 will block XHR requests to different origins. Any property access on the request 12087 object will raise an exception which we'll attempt to handle by formatting the original 12088 exception rather than the second one raised when we try to access xhr.status 12089 */ 12090 try { 12091 msg = "HTTP " + xhr.status + " attempting to load TileSource"; 12092 } catch ( e ) { 12093 var formattedExc; 12094 if ( typeof ( exc ) == "undefined" || !exc.toString ) { 12095 formattedExc = "Unknown error"; 12096 } else { 12097 formattedExc = exc.toString(); 12098 } 12099 12100 msg = formattedExc + " attempting to load TileSource"; 12101 } 12102 12103 /*** 12104 * Raised when an error occurs loading a TileSource. 12105 * 12106 * @event open-failed 12107 * @memberof OpenSeadragon.TileSource 12108 * @type {object} 12109 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 12110 * @property {String} message 12111 * @property {String} source 12112 * @property {?Object} userData - Arbitrary subscriber-defined object. 12113 */ 12114 _this.raiseEvent( 'open-failed', { 12115 message: msg, 12116 source: url 12117 }); 12118 } 12119 }); 12120 } 12121 12122 }, 12123 12124 /** 12125 * Responsible determining if a the particular TileSource supports the 12126 * data format ( and allowed to apply logic against the url the data was 12127 * loaded from, if any ). Overriding implementations are expected to do 12128 * something smart with data and / or url to determine support. Also 12129 * understand that iteration order of TileSources is not guarunteed so 12130 * please make sure your data or url is expressive enough to ensure a simple 12131 * and sufficient mechanisim for clear determination. 12132 * @function 12133 * @param {String|Object|Array|Document} data 12134 * @param {String} url - the url the data was loaded 12135 * from if any. 12136 * @return {Boolean} 12137 */ 12138 supports: function( data, url ) { 12139 return false; 12140 }, 12141 12142 /** 12143 * Responsible for parsing and configuring the 12144 * image metadata pertinent to this TileSources implementation. 12145 * This method is not implemented by this class other than to throw an Error 12146 * announcing you have to implement it. Because of the variety of tile 12147 * server technologies, and various specifications for building image 12148 * pyramids, this method is here to allow easy integration. 12149 * @function 12150 * @param {String|Object|Array|Document} data 12151 * @param {String} url - the url the data was loaded 12152 * from if any. 12153 * @return {Object} options - A dictionary of keyword arguments sufficient 12154 * to configure this tile sources constructor. 12155 * @throws {Error} 12156 */ 12157 configure: function( data, url ) { 12158 throw new Error( "Method not implemented." ); 12159 }, 12160 12161 /** 12162 * Responsible for retrieving the url which will return an image for the 12163 * region specified by the given x, y, and level components. 12164 * This method is not implemented by this class other than to throw an Error 12165 * announcing you have to implement it. Because of the variety of tile 12166 * server technologies, and various specifications for building image 12167 * pyramids, this method is here to allow easy integration. 12168 * @function 12169 * @param {Number} level 12170 * @param {Number} x 12171 * @param {Number} y 12172 * @throws {Error} 12173 */ 12174 getTileUrl: function( level, x, y ) { 12175 throw new Error( "Method not implemented." ); 12176 }, 12177 12178 /** 12179 * Responsible for retrieving the headers which will be attached to the image request for the 12180 * region specified by the given x, y, and level components. 12181 * This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true. 12182 * The headers returned here will override headers specified at the Viewer or TiledImage level. 12183 * Specifying a falsy value for a header will clear its existing value set at the Viewer or 12184 * TiledImage level (if any). 12185 * @function 12186 * @param {Number} level 12187 * @param {Number} x 12188 * @param {Number} y 12189 * @returns {Object} 12190 */ 12191 getTileAjaxHeaders: function( level, x, y ) { 12192 return {}; 12193 }, 12194 12195 /** 12196 * @function 12197 * @param {Number} level 12198 * @param {Number} x 12199 * @param {Number} y 12200 */ 12201 tileExists: function( level, x, y ) { 12202 var numTiles = this.getNumTiles( level ); 12203 return level >= this.minLevel && 12204 level <= this.maxLevel && 12205 x >= 0 && 12206 y >= 0 && 12207 x < numTiles.x && 12208 y < numTiles.y; 12209 } 12210}; 12211 12212 12213$.extend( true, $.TileSource.prototype, $.EventSource.prototype ); 12214 12215 12216/** 12217 * Decides whether to try to process the response as xml, json, or hand back 12218 * the text 12219 * @private 12220 * @inner 12221 * @function 12222 * @param {XMLHttpRequest} xhr - the completed network request 12223 */ 12224function processResponse( xhr ){ 12225 var responseText = xhr.responseText, 12226 status = xhr.status, 12227 statusText, 12228 data; 12229 12230 if ( !xhr ) { 12231 throw new Error( $.getString( "Errors.Security" ) ); 12232 } else if ( xhr.status !== 200 && xhr.status !== 0 ) { 12233 status = xhr.status; 12234 statusText = ( status == 404 ) ? 12235 "Not Found" : 12236 xhr.statusText; 12237 throw new Error( $.getString( "Errors.Status", status, statusText ) ); 12238 } 12239 12240 if( responseText.match(/\s*<.*/) ){ 12241 try{ 12242 data = ( xhr.responseXML && xhr.responseXML.documentElement ) ? 12243 xhr.responseXML : 12244 $.parseXml( responseText ); 12245 } catch (e){ 12246 data = xhr.responseText; 12247 } 12248 }else if( responseText.match(/\s*[\{\[].*/) ){ 12249 try{ 12250 data = $.parseJSON(responseText); 12251 } catch(e){ 12252 data = responseText; 12253 } 12254 }else{ 12255 data = responseText; 12256 } 12257 return data; 12258} 12259 12260 12261/** 12262 * Determines the TileSource Implementation by introspection of OpenSeadragon 12263 * namespace, calling each TileSource implementation of 'isType' 12264 * @private 12265 * @inner 12266 * @function 12267 * @param {Object|Array|Document} data - the tile source configuration object 12268 * @param {String} url - the url where the tile source configuration object was 12269 * loaded from, if any. 12270 */ 12271$.TileSource.determineType = function( tileSource, data, url ){ 12272 var property; 12273 for( property in OpenSeadragon ){ 12274 if( property.match(/.+TileSource$/) && 12275 $.isFunction( OpenSeadragon[ property ] ) && 12276 $.isFunction( OpenSeadragon[ property ].prototype.supports ) && 12277 OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url ) 12278 ){ 12279 return OpenSeadragon[ property ]; 12280 } 12281 } 12282 12283 $.console.error( "No TileSource was able to open %s %s", url, data ); 12284}; 12285 12286 12287}( OpenSeadragon )); 12288 12289/* 12290 * OpenSeadragon - DziTileSource 12291 * 12292 * Copyright (C) 2009 CodePlex Foundation 12293 * Copyright (C) 2010-2013 OpenSeadragon contributors 12294 * 12295 * Redistribution and use in source and binary forms, with or without 12296 * modification, are permitted provided that the following conditions are 12297 * met: 12298 * 12299 * - Redistributions of source code must retain the above copyright notice, 12300 * this list of conditions and the following disclaimer. 12301 * 12302 * - Redistributions in binary form must reproduce the above copyright 12303 * notice, this list of conditions and the following disclaimer in the 12304 * documentation and/or other materials provided with the distribution. 12305 * 12306 * - Neither the name of CodePlex Foundation nor the names of its 12307 * contributors may be used to endorse or promote products derived from 12308 * this software without specific prior written permission. 12309 * 12310 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12311 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12312 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 12313 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 12314 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
12315 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 12316 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12317 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 12318 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 12319 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 12320 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12321 */ 12322 12323(function( $ ){ 12324 12325/** 12326 * @class DziTileSource 12327 * @memberof OpenSeadragon 12328 * @extends OpenSeadragon.TileSource 12329 * @param {Number|Object} width - the pixel width of the image or the idiomatic 12330 * options object which is used instead of positional arguments. 12331 * @param {Number} height 12332 * @param {Number} tileSize 12333 * @param {Number} tileOverlap 12334 * @param {String} tilesUrl 12335 * @param {String} fileFormat 12336 * @param {OpenSeadragon.DisplayRect[]} displayRects 12337 * @property {String} tilesUrl 12338 * @property {String} fileFormat 12339 * @property {OpenSeadragon.DisplayRect[]} displayRects 12340 */ 12341$.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects, minLevel, maxLevel ) { 12342 var i, 12343 rect, 12344 level, 12345 options; 12346 12347 if( $.isPlainObject( width ) ){ 12348 options = width; 12349 }else{ 12350 options = { 12351 width: arguments[ 0 ], 12352 height: arguments[ 1 ], 12353 tileSize: arguments[ 2 ], 12354 tileOverlap: arguments[ 3 ], 12355 tilesUrl: arguments[ 4 ], 12356 fileFormat: arguments[ 5 ], 12357 displayRects: arguments[ 6 ], 12358 minLevel: arguments[ 7 ], 12359 maxLevel: arguments[ 8 ] 12360 }; 12361 } 12362 12363 this._levelRects = {}; 12364 this.tilesUrl = options.tilesUrl; 12365 this.fileFormat = options.fileFormat; 12366 this.displayRects = options.displayRects; 12367 12368 if ( this.displayRects ) { 12369 for ( i = this.displayRects.length - 1; i >= 0; i-- ) { 12370 rect = this.displayRects[ i ]; 12371 for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) { 12372 if ( !this._levelRects[ level ] ) { 12373 this._levelRects[ level ] = []; 12374 } 12375 this._levelRects[ level ].push( rect ); 12376 } 12377 } 12378 } 12379 12380 $.TileSource.apply( this, [ options ] ); 12381 12382}; 12383 12384$.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.DziTileSource.prototype */{ 12385 12386 12387 /** 12388 * Determine if the data and/or url imply the image service is supported by 12389 * this tile source. 12390 * @function 12391 * @param {Object|Array} data 12392 * @param {String} optional - url 12393 */ 12394 supports: function( data, url ){ 12395 var ns; 12396 if ( data.Image ) { 12397 ns = data.Image.xmlns; 12398 } else if ( data.documentElement) { 12399 if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) { 12400 ns = data.documentElement.namespaceURI; 12401 } 12402 } 12403 12404 ns = (ns || '').toLowerCase(); 12405 12406 return (ns.indexOf('schemas.microsoft.com/deepzoom/2008') !== -1 || 12407 ns.indexOf('schemas.microsoft.com/deepzoom/2009') !== -1); 12408 }, 12409 12410 /** 12411 * 12412 * @function 12413 * @param {Object|XMLDocument} data - the raw configuration 12414 * @param {String} url - the url the data was retrieved from if any. 12415 * @return {Object} options - A dictionary of keyword arguments sufficient 12416 * to configure this tile sources constructor. 12417 */ 12418 configure: function( data, url ){ 12419 12420 var options; 12421 12422 if( !$.isPlainObject(data) ){ 12423 12424 options = configureFromXML( this, data ); 12425 12426 }else{ 12427 12428 options = configureFromObject( this, data ); 12429 } 12430 12431 if (url && !options.tilesUrl) { 12432 options.tilesUrl = url.replace( 12433 /([^\/]+?)(\.(dzi|xml|js)?(\?[^\/]*)?)?\/?$/, '$1_files/'); 12434 12435 if (url.search(/\.(dzi|xml|js)\?/) != -1) { 12436 options.queryParams = url.match(/\?.*/); 12437 }else{ 12438 options.queryParams = ''; 12439 } 12440 } 12441 12442 return options; 12443 }, 12444 12445 12446 /** 12447 * @function 12448 * @param {Number} level 12449 * @param {Number} x 12450 * @param {Number} y 12451 */ 12452 getTileUrl: function( level, x, y ) { 12453 return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat, this.queryParams ].join( '' ); 12454 }, 12455 12456 12457 /** 12458 * @function 12459 * @param {Number} level 12460 * @param {Number} x 12461 * @param {Number} y 12462 */ 12463 tileExists: function( level, x, y ) { 12464 var rects = this._levelRects[ level ], 12465 rect, 12466 scale, 12467 xMin, 12468 yMin, 12469 xMax, 12470 yMax, 12471 i; 12472 12473 if ((this.minLevel && level < this.minLevel) || (this.maxLevel && level > this.maxLevel)) { 12474 return false;
12475 } 12476 12477 if ( !rects || !rects.length ) { 12478 return true; 12479 } 12480 12481 for ( i = rects.length - 1; i >= 0; i-- ) { 12482 rect = rects[ i ]; 12483 12484 if ( level < rect.minLevel || level > rect.maxLevel ) { 12485 continue; 12486 } 12487 12488 scale = this.getLevelScale( level ); 12489 xMin = rect.x * scale; 12490 yMin = rect.y * scale; 12491 xMax = xMin + rect.width * scale; 12492 yMax = yMin + rect.height * scale; 12493 12494 xMin = Math.floor( xMin / this._tileWidth ); 12495 yMin = Math.floor( yMin / this._tileWidth ); // DZI tiles are square, so we just use _tileWidth 12496 xMax = Math.ceil( xMax / this._tileWidth ); 12497 yMax = Math.ceil( yMax / this._tileWidth ); 12498 12499 if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) { 12500 return true; 12501 } 12502 } 12503 12504 return false; 12505 } 12506}); 12507 12508 12509/** 12510 * @private 12511 * @inner 12512 * @function 12513 */ 12514function configureFromXML( tileSource, xmlDoc ){ 12515 12516 if ( !xmlDoc || !xmlDoc.documentElement ) { 12517 throw new Error( $.getString( "Errors.Xml" ) ); 12518 } 12519 12520 var root = xmlDoc.documentElement, 12521 rootName = root.localName || root.tagName, 12522 ns = xmlDoc.documentElement.namespaceURI, 12523 configuration = null, 12524 displayRects = [], 12525 dispRectNodes, 12526 dispRectNode, 12527 rectNode, 12528 sizeNode, 12529 i; 12530 12531 if ( rootName == "Image" ) { 12532 12533 try { 12534 sizeNode = root.getElementsByTagName("Size" )[ 0 ]; 12535 if (sizeNode === undefined) { 12536 sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ]; 12537 } 12538 12539 configuration = { 12540 Image: { 12541 xmlns: "http://schemas.microsoft.com/deepzoom/2008", 12542 Url: root.getAttribute( "Url" ), 12543 Format: root.getAttribute( "Format" ), 12544 DisplayRect: null, 12545 Overlap: parseInt( root.getAttribute( "Overlap" ), 10 ), 12546 TileSize: parseInt( root.getAttribute( "TileSize" ), 10 ), 12547 Size: { 12548 Height: parseInt( sizeNode.getAttribute( "Height" ), 10 ), 12549 Width: parseInt( sizeNode.getAttribute( "Width" ), 10 ) 12550 } 12551 } 12552 }; 12553 12554 if ( !$.imageFormatSupported( configuration.Image.Format ) ) { 12555 throw new Error( 12556 $.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() ) 12557 ); 12558 } 12559 12560 dispRectNodes = root.getElementsByTagName("DisplayRect" ); 12561 if (dispRectNodes === undefined) { 12562 dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ]; 12563 } 12564 12565 for ( i = 0; i < dispRectNodes.length; i++ ) { 12566 dispRectNode = dispRectNodes[ i ]; 12567 rectNode = dispRectNode.getElementsByTagName("Rect" )[ 0 ]; 12568 if (rectNode === undefined) { 12569 rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ]; 12570 } 12571 12572 displayRects.push({ 12573 Rect: { 12574 X: parseInt( rectNode.getAttribute( "X" ), 10 ), 12575 Y: parseInt( rectNode.getAttribute( "Y" ), 10 ), 12576 Width: parseInt( rectNode.getAttribute( "Width" ), 10 ), 12577 Height: parseInt( rectNode.getAttribute( "Height" ), 10 ), 12578 MinLevel: parseInt( dispRectNode.getAttribute( "MinLevel" ), 10 ), 12579 MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 ) 12580 } 12581 }); 12582 } 12583 12584 if( displayRects.length ){ 12585 configuration.Image.DisplayRect = displayRects; 12586 } 12587 12588 return configureFromObject( tileSource, configuration ); 12589 12590 } catch ( e ) { 12591 throw (e instanceof Error) ? 12592 e : 12593 new Error( $.getString("Errors.Dzi") ); 12594 } 12595 } else if ( rootName == "Collection" ) { 12596 throw new Error( $.getString( "Errors.Dzc" ) ); 12597 } else if ( rootName == "Error" ) { 12598 var messageNode = root.getElementsByTagName("Message")[0]; 12599 var message = messageNode.firstChild.nodeValue; 12600 throw new Error(message); 12601 } 12602 12603 throw new Error( $.getString( "Errors.Dzi" ) ); 12604} 12605 12606/** 12607 * @private 12608 * @inner 12609 * @function 12610 */ 12611function configureFromObject( tileSource, configuration ){ 12612 var imageData = configuration.Image, 12613 tilesUrl = imageData.Url, 12614 fileFormat = imageData.Format, 12615 sizeData = imageData.Size, 12616 dispRectData = imageData.DisplayRect || [], 12617 width = parseInt( sizeData.Width, 10 ), 12618 height = parseInt( sizeData.Height, 10 ), 12619 tileSize = parseInt( imageData.TileSize, 10 ), 12620 tileOverlap = parseInt( imageData.Overlap, 10 ), 12621 displayRects = [], 12622 rectData, 12623 i; 12624 12625 //TODO: need to figure out out to better handle image format compatibility 12626 // which actually includes additional file formats like xml and pdf 12627 // and plain text for various tilesource implementations to avoid low 12628 // level errors. 12629 // 12630 // For now, just don't perform the check. 12631 // 12632 /*if ( !imageFormatSupported( fileFormat ) ) { 12633 throw new Error( 12634 $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) 12635 ); 12636 }*/ 12637 12638 for ( i = 0; i < dispRectData.length; i++ ) {
vendor: 8,231 bytes, lines 12639-12844
12639 rectData = dispRectData[ i ].Rect; 12640 12641 displayRects.push( new $.DisplayRect( 12642 parseInt( rectData.X, 10 ), 12643 parseInt( rectData.Y, 10 ), 12644 parseInt( rectData.Width, 10 ), 12645 parseInt( rectData.Height, 10 ), 12646 parseInt( rectData.MinLevel, 10 ), 12647 parseInt( rectData.MaxLevel, 10 ) 12648 )); 12649 } 12650 12651 return $.extend(true, { 12652 width: width, /* width *required */ 12653 height: height, /* height *required */ 12654 tileSize: tileSize, /* tileSize *required */ 12655 tileOverlap: tileOverlap, /* tileOverlap *required */ 12656 minLevel: null, /* minLevel */ 12657 maxLevel: null, /* maxLevel */ 12658 tilesUrl: tilesUrl, /* tilesUrl */ 12659 fileFormat: fileFormat, /* fileFormat */ 12660 displayRects: displayRects /* displayRects */ 12661 }, configuration ); 12662 12663} 12664 12665}( OpenSeadragon )); 12666 12667/* 12668 * OpenSeadragon - IIIFTileSource 12669 * 12670 * Copyright (C) 2009 CodePlex Foundation 12671 * Copyright (C) 2010-2013 OpenSeadragon contributors 12672 * 12673 * Redistribution and use in source and binary forms, with or without 12674 * modification, are permitted provided that the following conditions are 12675 * met: 12676 * 12677 * - Redistributions of source code must retain the above copyright notice, 12678 * this list of conditions and the following disclaimer. 12679 * 12680 * - Redistributions in binary form must reproduce the above copyright 12681 * notice, this list of conditions and the following disclaimer in the 12682 * documentation and/or other materials provided with the distribution. 12683 * 12684 * - Neither the name of CodePlex Foundation nor the names of its 12685 * contributors may be used to endorse or promote products derived from 12686 * this software without specific prior written permission. 12687 * 12688 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12689 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12690 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 12691 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 12692 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 12693 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 12694 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12695 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 12696 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 12697 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 12698 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12699 */ 12700 12701(function( $ ){ 12702 12703/** 12704 * @class IIIFTileSource 12705 * @classdesc A client implementation of the International Image Interoperability Framework 12706 * Format: Image API 1.0 - 2.1 12707 * 12708 * @memberof OpenSeadragon 12709 * @extends OpenSeadragon.TileSource 12710 * @see http://iiif.io/api/image/ 12711 * @param {String} [options.tileFormat='jpg'] 12712 * The extension that will be used when requiring tiles. 12713 */ 12714$.IIIFTileSource = function( options ){ 12715 12716 /* eslint-disable camelcase */ 12717 12718 $.extend( true, this, options ); 12719 12720 if ( !( this.height && this.width && this['@id'] ) ) { 12721 throw new Error( 'IIIF required parameters not provided.' ); 12722 } 12723 12724 options.tileSizePerScaleFactor = {}; 12725 12726 this.tileFormat = this.tileFormat || 'jpg'; 12727 12728 this.version = options.version; 12729 12730 // N.B. 2.0 renamed scale_factors to scaleFactors 12731 if ( this.tile_width && this.tile_height ) { 12732 options.tileWidth = this.tile_width; 12733 options.tileHeight = this.tile_height; 12734 } else if ( this.tile_width ) { 12735 options.tileSize = this.tile_width; 12736 } else if ( this.tile_height ) { 12737 options.tileSize = this.tile_height; 12738 } else if ( this.tiles ) { 12739 // Version 2.0 forwards 12740 if ( this.tiles.length == 1 ) { 12741 options.tileWidth = this.tiles[0].width; 12742 // Use height if provided, otherwise assume square tiles and use width. 12743 options.tileHeight = this.tiles[0].height || this.tiles[0].width; 12744 this.scale_factors = this.tiles[0].scaleFactors; 12745 } else { 12746 // Multiple tile sizes at different levels 12747 this.scale_factors = []; 12748 for (var t = 0; t < this.tiles.length; t++ ) { 12749 for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) { 12750 var scaleFactor = this.tiles[t].scaleFactors[sf]; 12751 this.scale_factors.push(scaleFactor); 12752 options.tileSizePerScaleFactor[scaleFactor] = { 12753 width: this.tiles[t].width, 12754 height: this.tiles[t].height || this.tiles[t].width 12755 }; 12756 } 12757 } 12758 } 12759 } else if ( canBeTiled(options) ) { 12760 // use the largest of tileOptions that is smaller than the short dimension 12761 var shortDim = Math.min( this.height, this.width ), 12762 tileOptions = [256, 512, 1024], 12763 smallerTiles = []; 12764 12765 for ( var c = 0; c < tileOptions.length; c++ ) { 12766 if ( tileOptions[c] <= shortDim ) { 12767 smallerTiles.push( tileOptions[c] ); 12768 } 12769 } 12770 12771 if ( smallerTiles.length > 0 ) { 12772 options.tileSize = Math.max.apply( null, smallerTiles ); 12773 } else { 12774 // If we're smaller than 256, just use the short side. 12775 options.tileSize = shortDim; 12776 } 12777 } else if (this.sizes && this.sizes.length > 0) { 12778 // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array. 12779 // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the 12780 // LegacyTileSource instead of performing IIIF tiling. 12781 this.emulateLegacyImagePyramid = true; 12782 12783 options.levels = constructLevels( this ); 12784 // use the largest available size to define tiles 12785 $.extend( true, options, { 12786 width: options.levels[ options.levels.length - 1 ].width, 12787 height: options.levels[ options.levels.length - 1 ].height, 12788 tileSize: Math.max( options.height, options.width ), 12789 tileOverlap: 0, 12790 minLevel: 0, 12791 maxLevel: options.levels.length - 1 12792 }); 12793 this.levels = options.levels; 12794 } else { 12795 $.console.error("Nothing in the info.json to construct image pyramids from"); 12796 } 12797 12798 if (!options.maxLevel && !this.emulateLegacyImagePyramid) { 12799 if (!this.scale_factors) { 12800 options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); 12801 } else { 12802 var maxScaleFactor = Math.max.apply(null, this.scale_factors); 12803 options.maxLevel = Math.round(Math.log(maxScaleFactor) * Math.LOG2E); 12804 } 12805 } 12806 12807 $.TileSource.apply( this, [ options ] ); 12808}; 12809 12810$.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{ 12811 /** 12812 * Determine if the data and/or url imply the image service is supported by 12813 * this tile source. 12814 * @function 12815 * @param {Object|Array} data 12816 * @param {String} optional - url 12817 */ 12818 12819 supports: function( data, url ) { 12820 // Version 2.0 and forwards 12821 if (data.protocol && data.protocol == 'http://iiif.io/api/image') { 12822 return true; 12823 // Version 1.1 12824 } else if ( data['@context'] && ( 12825 data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" || 12826 data['@context'] == "http://iiif.io/api/image/1/context.json") ) { 12827 // N.B. the iiif.io context is wrong, but where the representation lives so likely to be used 12828 return true; 12829 12830 // Version 1.0 12831 } else if ( data.profile && 12832 data.profile.indexOf("http://library.stanford.edu/iiif/image-api/compliance.html") === 0) { 12833 return true; 12834 } else if ( data.identifier && data.width && data.height ) { 12835 return true; 12836 } else if ( data.documentElement && 12837 "info" == data.documentElement.tagName && 12838 "http://library.stanford.edu/iiif/image-api/ns/" == 12839 data.documentElement.namespaceURI) { 12840 return true; 12841 12842 // Not IIIF 12843 } else { 12844 return false;
12845 } 12846 }, 12847 12848 /** 12849 * 12850 * @function 12851 * @param {Object} data - the raw configuration 12852 * @example <caption>IIIF 1.1 Info Looks like this</caption> 12853 * { 12854 * "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json", 12855 * "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C", 12856 * "width" : 6000, 12857 * "height" : 4000, 12858 * "scale_factors" : [ 1, 2, 4 ], 12859 * "tile_width" : 1024, 12860 * "tile_height" : 1024, 12861 * "formats" : [ "jpg", "png" ], 12862 * "qualities" : [ "native", "grey" ], 12863 * "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" 12864 * } 12865 */ 12866 configure: function( data, url ){ 12867 // Try to deduce our version and fake it upwards if needed 12868 if ( !$.isPlainObject(data) ) { 12869 var options = configureFromXml10( data ); 12870 options['@context'] = "http://iiif.io/api/image/1.0/context.json"; 12871 options['@id'] = url.replace('/info.xml', ''); 12872 options.version = 1; 12873 return options; 12874 } else { 12875 if ( !data['@context'] ) { 12876 data['@context'] = 'http://iiif.io/api/image/1.0/context.json'; 12877 data['@id'] = url.replace('/info.json', ''); 12878 data.version = 1; 12879 } else { 12880 var context = data['@context']; 12881 if (Array.isArray(context)) { 12882 for (var i = 0; i < context.length; i++) { 12883 if (typeof context[i] === 'string' && 12884 ( /^http:\/\/iiif\.io\/api\/image\/[1-3]\/context\.json$/.test(context[i]) || 12885 context[i] === 'http://library.stanford.edu/iiif/image-api/1.1/context.json' ) ) { 12886 context = context[i]; 12887 break; 12888 } 12889 } 12890 } 12891 switch (context) { 12892 case 'http://iiif.io/api/image/1/context.json': 12893 case 'http://library.stanford.edu/iiif/image-api/1.1/context.json': 12894 data.version = 1; 12895 break; 12896 case 'http://iiif.io/api/image/2/context.json': 12897 data.version = 2; 12898 break; 12899 case 'http://iiif.io/api/image/3/context.json': 12900 data.version = 3; 12901 break; 12902 default: 12903 $.console.error('Data has a @context property which contains no known IIIF context URI.'); 12904 } 12905 } 12906 if ( !data['@id'] && data['id'] ) { 12907 data['@id'] = data['id']; 12908 } 12909 if(data.preferredFormats) { 12910 for (var f = 0; f < data.preferredFormats.length; f++ ) { 12911 if ( OpenSeadragon.imageFormatSupported(data.preferredFormats[f]) ) { 12912 data.tileFormat = data.preferredFormats[f]; 12913 break; 12914 } 12915 } 12916 } 12917 return data; 12918 } 12919 }, 12920 12921 /** 12922 * Return the tileWidth for the given level. 12923 * @function 12924 * @param {Number} level 12925 */ 12926 getTileWidth: function( level ) { 12927 12928 if(this.emulateLegacyImagePyramid) { 12929 return $.TileSource.prototype.getTileWidth.call(this, level); 12930 } 12931 12932 var scaleFactor = Math.pow(2, this.maxLevel - level); 12933 12934 if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) { 12935 return this.tileSizePerScaleFactor[scaleFactor].width; 12936 } 12937 return this._tileWidth; 12938 }, 12939 12940 /** 12941 * Return the tileHeight for the given level. 12942 * @function 12943 * @param {Number} level 12944 */ 12945 getTileHeight: function( level ) { 12946 12947 if(this.emulateLegacyImagePyramid) { 12948 return $.TileSource.prototype.getTileHeight.call(this, level); 12949 } 12950 12951 var scaleFactor = Math.pow(2, this.maxLevel - level); 12952 12953 if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) { 12954 return this.tileSizePerScaleFactor[scaleFactor].height; 12955 } 12956 return this._tileHeight; 12957 }, 12958 12959 /** 12960 * @function 12961 * @param {Number} level 12962 */ 12963 getLevelScale: function ( level ) { 12964 12965 if(this.emulateLegacyImagePyramid) { 12966 var levelScale = NaN; 12967 if (this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel) { 12968 levelScale = 12969 this.levels[level].width / 12970 this.levels[this.maxLevel].width; 12971 } 12972 return levelScale; 12973 } 12974 12975 return $.TileSource.prototype.getLevelScale.call(this, level); 12976 }, 12977 12978 /** 12979 * @function 12980 * @param {Number} level 12981 */ 12982 getNumTiles: function( level ) { 12983 12984 if(this.emulateLegacyImagePyramid) { 12985 var scale = this.getLevelScale(level); 12986 if (scale) { 12987 return new $.Point(1, 1); 12988 } else { 12989 return new $.Point(0, 0); 12990 } 12991 } 12992 12993 return $.TileSource.prototype.getNumTiles.call(this, level); 12994 }, 12995 12996 12997 /** 12998 * @function 12999 * @param {Number} level 13000 * @param {OpenSeadragon.Point} point 13001 */ 13002 getTileAtPoint: function( level, point ) { 13003 13004 if(this.emulateLegacyImagePyramid) { 13005 return new $.Point(0, 0); 13006 } 13007 13008 return $.TileSource.prototype.getTileAtPoint.call(this, level, point); 13009 }, 13010 13011 13012 /** 13013 * Responsible for retrieving the url which will return an image for the 13014 * region specified by the given x, y, and level components. 13015 * @function 13016 * @param {Number} level - z index 13017 * @param {Number} x 13018 * @param {Number} y 13019 * @throws {Error} 13020 */ 13021 getTileUrl: function( level, x, y ){ 13022 13023 if(this.emulateLegacyImagePyramid) { 13024 var url = null; 13025 if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { 13026 url = this.levels[ level ].url; 13027 } 13028 return url; 13029 } 13030 13031 //# constants 13032 var IIIF_ROTATION = '0', 13033 //## get the scale (level as a decimal) 13034 scale = Math.pow( 0.5, this.maxLevel - level ), 13035 13036 //# image dimensions at this level 13037 levelWidth = Math.ceil( this.width * scale ),
13038 levelHeight = Math.ceil( this.height * scale ), 13039 13040 //## iiif region 13041 tileWidth, 13042 tileHeight, 13043 iiifTileSizeWidth, 13044 iiifTileSizeHeight, 13045 iiifRegion, 13046 iiifTileX, 13047 iiifTileY, 13048 iiifTileW, 13049 iiifTileH, 13050 iiifSize, 13051 iiifSizeW, 13052 iiifSizeH, 13053 iiifQuality, 13054 uri; 13055 13056 tileWidth = this.getTileWidth(level); 13057 tileHeight = this.getTileHeight(level); 13058 iiifTileSizeWidth = Math.ceil( tileWidth / scale ); 13059 iiifTileSizeHeight = Math.ceil( tileHeight / scale ); 13060 if (this.version === 1) { 13061 iiifQuality = "native." + this.tileFormat; 13062 } else { 13063 iiifQuality = "default." + this.tileFormat; 13064 } 13065 if ( levelWidth < tileWidth && levelHeight < tileHeight ){ 13066 if ( this.version === 2 && levelWidth === this.width ) { 13067 iiifSize = "max"; 13068 } else if ( this.version === 3 && levelWidth === this.width && levelHeight === this.height ) { 13069 iiifSize = "max"; 13070 } else if ( this.version === 3 ) { 13071 iiifSize = levelWidth + "," + levelHeight; 13072 } else { 13073 iiifSize = levelWidth + ","; 13074 } 13075 iiifRegion = 'full'; 13076 } else { 13077 iiifTileX = x * iiifTileSizeWidth; 13078 iiifTileY = y * iiifTileSizeHeight; 13079 iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX ); 13080 iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY ); 13081 if ( x === 0 && y === 0 && iiifTileW === this.width && iiifTileH === this.height ) { 13082 iiifRegion = "full"; 13083 } else { 13084 iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' ); 13085 } 13086 iiifSizeW = Math.ceil( iiifTileW * scale ); 13087 iiifSizeH = Math.ceil( iiifTileH * scale ); 13088 if ( this.version === 2 && iiifSizeW === this.width ) { 13089 iiifSize = "max"; 13090 } else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) { 13091 iiifSize = "max"; 13092 } else if (this.version === 3) { 13093 iiifSize = iiifSizeW + "," + iiifSizeH; 13094 } else { 13095 iiifSize = iiifSizeW + ","; 13096 } 13097 } 13098 uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' ) + "?token=" + $.DEFAULT_SETTINGS.iiifToken; 13099 13100 return uri; 13101 }, 13102 13103 __testonly__: { 13104 canBeTiled: canBeTiled, 13105 constructLevels: constructLevels 13106 } 13107 13108 }); 13109 13110 /** 13111 * Determine whether arbitrary tile requests can be made against a service with the given profile 13112 * @function 13113 * @param {array} profile - IIIF profile array 13114 * @throws {Error} 13115 */ 13116 function canBeTiled ( options ) { 13117 var level0Profiles = [ 13118 "http://library.stanford.edu/iiif/image-api/compliance.html#level0", 13119 "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0", 13120 "http://iiif.io/api/image/2/level0.json", 13121 "level0", 13122 "https://iiif.io/api/image/3/level0.json" 13123 ]; 13124 var profileLevel = Array.isArray(options.profile) ? options.profile[0] : options.profile; 13125 var isLevel0 = (level0Profiles.indexOf(profileLevel) !== -1); 13126 var hasCanoncicalSizeFeature = false; 13127 if ( options.version === 2 && options.profile.length > 1 && options.profile[1].supports ) { 13128 hasCanoncicalSizeFeature = options.profile[1].supports.indexOf( "sizeByW" ) !== -1; 13129 } 13130 if ( options.version === 3 && options.extraFeatures ) { 13131 hasCanoncicalSizeFeature = options.extraFeatures.indexOf( "sizeByWh" ) !== -1; 13132 } 13133 return !isLevel0 || hasCanoncicalSizeFeature; 13134 } 13135 13136 /** 13137 * Build the legacy pyramid URLs (one tile per level) 13138 * @function 13139 * @param {object} options - infoJson 13140 * @throws {Error} 13141 */ 13142 function constructLevels(options) { 13143 var levels = []; 13144 for(var i = 0; i < options.sizes.length; i++) { 13145 levels.push({ 13146 url: options['@id'] + '/full/' + options.sizes[i].width + ',' + 13147 (options.version === 3 ? options.sizes[i].height : '') + 13148 '/0/default.' + options.tileFormat, 13149 width: options.sizes[i].width, 13150 height: options.sizes[i].height 13151 }); 13152 } 13153 return levels.sort(function(a, b) { 13154 return a.width - b.width; 13155 }); 13156 } 13157 13158 13159 function configureFromXml10(xmlDoc) { 13160 //parse the xml 13161 if ( !xmlDoc || !xmlDoc.documentElement ) { 13162 throw new Error( $.getString( "Errors.Xml" ) ); 13163 } 13164 13165 var root = xmlDoc.documentElement, 13166 rootName = root.tagName, 13167 configuration = null; 13168 13169 if ( rootName == "info" ) { 13170 try { 13171 configuration = {}; 13172 parseXML10( root, configuration ); 13173 return configuration; 13174 13175 } catch ( e ) { 13176 throw (e instanceof Error) ? 13177 e : 13178 new Error( $.getString("Errors.IIIF") ); 13179 } 13180 } 13181 throw new Error( $.getString( "Errors.IIIF" ) ); 13182 } 13183 13184 function parseXML10( node, configuration, property ) { 13185 var i, 13186 value; 13187 if ( node.nodeType == 3 && property ) {//text node 13188 value = node.nodeValue.trim(); 13189 if( value.match(/^\d*$/)){ 13190 value = Number( value ); 13191 } 13192 if( !configuration[ property ] ){ 13193 configuration[ property ] = value; 13194 }else{ 13195 if( !$.isArray( configuration[ property ] ) ){ 13196 configuration[ property ] = [ configuration[ property ] ]; 13197 } 13198 configuration[ property ].push( value ); 13199 } 13200 } else if( node.nodeType == 1 ){ 13201 for( i = 0; i < node.childNodes.length; i++ ){ 13202 parseXML10( node.childNodes[ i ], configuration, node.nodeName ); 13203 } 13204 } 13205 } 13206 13207 13208 13209}( OpenSeadragon )); 13210 13211/* 13212 * OpenSeadragon - OsmTileSource 13213 * 13214 * Copyright (C) 2009 CodePlex Foundation 13215 * Copyright (C) 2010-2013 OpenSeadragon contributors 13216 * 13217 * Redistribution and use in source and binary forms, with or without 13218 * modification, are permitted provided that the following conditions are 13219 * met: 13220 * 13221 * - Redistributions of source code must retain the above copyright notice, 13222 * this list of conditions and the following disclaimer. 13223 * 13224 * - Redistributions in binary form must reproduce the above copyright 13225 * notice, this list of conditions and the following disclaimer in the 13226 * documentation and/or other materials provided with the distribution. 13227 * 13228 * - Neither the name of CodePlex Foundation nor the names of its 13229 * contributors may be used to endorse or promote products derived from 13230 * this software without specific prior written permission. 13231 * 13232 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13233 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13234 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13235 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13236 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13237 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13238 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13239 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13240 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13241 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13242 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13243 */ 13244 13245/* 13246 * Derived from the OSM tile source in Rainer Simon's seajax-utils project 13247 * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed 13248 * the included code to the OpenSeadragon project under the New BSD license; 13249 * see <https://github.com/openseadragon/openseadragon/issues/58>. 13250 */ 13251 13252 13253(function( $ ){ 13254 13255/** 13256 * @class OsmTileSource 13257 * @classdesc A tilesource implementation for OpenStreetMap.<br><br> 13258 * 13259 * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep 13260 * Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of 13261 * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a 13262 * difference of log2(256)=8 levels.<br><br> 13263 * 13264 * Note 2. Image dimension. According to the OSM Wiki 13265 * (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels) 13266 * the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256 13267 * pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864 13268 * pixels. 13269 * 13270 * @memberof OpenSeadragon 13271 * @extends OpenSeadragon.TileSource 13272 * @param {Number|Object} width - the pixel width of the image or the idiomatic 13273 * options object which is used instead of positional arguments. 13274 * @param {Number} height 13275 * @param {Number} tileSize 13276 * @param {Number} tileOverlap 13277 * @param {String} tilesUrl 13278 */ 13279$.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) { 13280 var options; 13281 13282 if( $.isPlainObject( width ) ){ 13283 options = width; 13284 }else{ 13285 options = { 13286 width: arguments[0], 13287 height: arguments[1], 13288 tileSize: arguments[2], 13289 tileOverlap: arguments[3], 13290 tilesUrl: arguments[4] 13291 }; 13292 } 13293 //apply default setting for standard public OpenStreatMaps service 13294 //but allow them to be specified so fliks can host there own instance 13295 //or apply against other services supportting the same standard 13296 if( !options.width || !options.height ){ 13297 options.width = 65572864; 13298 options.height = 65572864; 13299 } 13300 if( !options.tileSize ){ 13301 options.tileSize = 256; 13302 options.tileOverlap = 0; 13303 } 13304 if( !options.tilesUrl ){ 13305 options.tilesUrl = "http://tile.openstreetmap.org/"; 13306 } 13307 options.minLevel = 8; 13308 13309 $.TileSource.apply( this, [ options ] ); 13310 13311}; 13312 13313$.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.OsmTileSource.prototype */{ 13314 13315 13316 /** 13317 * Determine if the data and/or url imply the image service is supported by 13318 * this tile source. 13319 * @function 13320 * @param {Object|Array} data 13321 * @param {String} optional - url 13322 */ 13323 supports: function( data, url ){ 13324 return ( 13325 data.type && 13326 "openstreetmaps" == data.type 13327 ); 13328 }, 13329 13330 /** 13331 * 13332 * @function 13333 * @param {Object} data - the raw configuration 13334 * @param {String} url - the url the data was retrieved from if any. 13335 * @return {Object} options - A dictionary of keyword arguments sufficient 13336 * to configure this tile sources constructor. 13337 */ 13338 configure: function( data, url ){ 13339 return data; 13340 }, 13341 13342 13343 /** 13344 * @function 13345 * @param {Number} level 13346 * @param {Number} x 13347 * @param {Number} y 13348 */ 13349 getTileUrl: function( level, x, y ) { 13350 return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png"; 13351 } 13352}); 13353 13354 13355}( OpenSeadragon )); 13356 13357/* 13358 * OpenSeadragon - TmsTileSource 13359 * 13360 * Copyright (C) 2009 CodePlex Foundation 13361 * Copyright (C) 2010-2013 OpenSeadragon contributors 13362 * 13363 * Redistribution and use in source and binary forms, with or without 13364 * modification, are permitted provided that the following conditions are 13365 * met: 13366 * 13367 * - Redistributions of source code must retain the above copyright notice, 13368 * this list of conditions and the following disclaimer. 13369 * 13370 * - Redistributions in binary form must reproduce the above copyright 13371 * notice, this list of conditions and the following disclaimer in the 13372 * documentation and/or other materials provided with the distribution. 13373 * 13374 * - Neither the name of CodePlex Foundation nor the names of its 13375 * contributors may be used to endorse or promote products derived from 13376 * this software without specific prior written permission. 13377 * 13378 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13379 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13380 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13381 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13382 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13383 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13384 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13385 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13386 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13387 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13388 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13389 */ 13390 13391/* 13392 * Derived from the TMS tile source in Rainer Simon's seajax-utils project 13393 * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed 13394 * the included code to the OpenSeadragon project under the New BSD license; 13395 * see <https://github.com/openseadragon/openseadragon/issues/58>. 13396 */ 13397 13398 13399(function( $ ){ 13400 13401/** 13402 * @class TmsTileSource 13403 * @classdesc A tilesource implementation for Tiled Map Services (TMS). 13404 * TMS tile scheme ( [ as supported by OpenLayers ] is described here 13405 * ( http://openlayers.org/dev/examples/tms.html ). 13406 * 13407 * @memberof OpenSeadragon 13408 * @extends OpenSeadragon.TileSource 13409 * @param {Number|Object} width - the pixel width of the image or the idiomatic 13410 * options object which is used instead of positional arguments. 13411 * @param {Number} height 13412 * @param {Number} tileSize 13413 * @param {Number} tileOverlap 13414 * @param {String} tilesUrl 13415 */ 13416$.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) { 13417 var options; 13418 13419 if( $.isPlainObject( width ) ){ 13420 options = width; 13421 }else{ 13422 options = { 13423 width: arguments[0], 13424 height: arguments[1], 13425 tileSize: arguments[2], 13426 tileOverlap: arguments[3], 13427 tilesUrl: arguments[4] 13428 }; 13429 } 13430 // TMS has integer multiples of 256 for width/height and adds buffer 13431 // if necessary -> account for this! 13432 var bufferedWidth = Math.ceil(options.width / 256) * 256, 13433 bufferedHeight = Math.ceil(options.height / 256) * 256, 13434 max; 13435 13436 // Compute number of zoomlevels in this tileset 13437 if (bufferedWidth > bufferedHeight) { 13438 max = bufferedWidth / 256; 13439 } else { 13440 max = bufferedHeight / 256; 13441 } 13442 options.maxLevel = Math.ceil(Math.log(max) / Math.log(2)) - 1; 13443 options.tileSize = 256; 13444 options.width = bufferedWidth; 13445 options.height = bufferedHeight; 13446 13447 $.TileSource.apply( this, [ options ] ); 13448 13449}; 13450 13451$.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TmsTileSource.prototype */{ 13452 13453 13454 /** 13455 * Determine if the data and/or url imply the image service is supported by 13456 * this tile source. 13457 * @function 13458 * @param {Object|Array} data 13459 * @param {String} optional - url 13460 */ 13461 supports: function( data, url ){ 13462 return ( data.type && "tiledmapservice" == data.type ); 13463 }, 13464 13465 /** 13466 * 13467 * @function 13468 * @param {Object} data - the raw configuration 13469 * @param {String} url - the url the data was retrieved from if any. 13470 * @return {Object} options - A dictionary of keyword arguments sufficient 13471 * to configure this tile sources constructor. 13472 */ 13473 configure: function( data, url ){ 13474 return data; 13475 }, 13476 13477 13478 /** 13479 * @function 13480 * @param {Number} level 13481 * @param {Number} x 13482 * @param {Number} y 13483 */ 13484 getTileUrl: function( level, x, y ) { 13485 // Convert from Deep Zoom definition to TMS zoom definition 13486 var yTiles = this.getNumTiles( level ).y - 1; 13487 13488 return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png"; 13489 } 13490}); 13491 13492 13493}( OpenSeadragon )); 13494 13495(function($) { 13496 13497 /** 13498 * @class ZoomifyTileSource 13499 * @classdesc A tilesource implementation for the zoomify format. 13500 * 13501 * A description of the format can be found here: 13502 * https://ecommons.cornell.edu/bitstream/handle/1813/5410/Introducing_Zoomify_Image.pdf 13503 * 13504 * There are two ways of creating a zoomify tilesource for openseadragon 13505 * 13506 * 1) Supplying all necessary information in the tilesource object. A minimal example object for this method looks like this: 13507 * 13508 * { 13509 * type: "zoomifytileservice", 13510 * width: 1000, 13511 * height: 1000, 13512 * tilesUrl: "/test/data/zoomify/" 13513 * } 13514 * 13515 * The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the path to the image _directory_. 13516 * 13517 * 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED) 13518 * 13519 * When creating zoomify formatted images one "xml" like file with name ImageProperties.xml 13520 * will be created as well. Here is an example of such a file: 13521 * 13522 * <IMAGE_PROPERTIES WIDTH="1000" HEIGHT="1000" NUMTILES="21" NUM
13522IMAGES="1" VERSION="1.8" TILESIZE="256" /> 13523 * 13524 * To use this xml file as metadata source you must supply the path to the ImageProperties.xml file and leave out all other parameters: 13525 * As stated above, this method of loading a zoomify tilesource is currently not supported 13526 * 13527 * { 13528 * type: "zoomifytileservice", 13529 * tilesUrl: "/test/data/zoomify/ImageProperties.xml" 13530 * } 13531 13532 * 13533 * @memberof OpenSeadragon 13534 * @extends OpenSeadragon.TileSource 13535 * @param {Number} width - the pixel width of the image. 13536 * @param {Number} height 13537 * @param {Number} tileSize 13538 * @param {String} tilesUrl 13539 */ 13540 $.ZoomifyTileSource = function(options) { 13541 options.tileSize = 256; 13542 13543 var currentImageSize = { 13544 x: options.width, 13545 y: options.height 13546 }; 13547 options.imageSizes = [{ 13548 x: options.width, 13549 y: options.height 13550 }]; 13551 options.gridSize = [this._getGridSize(options.width, options.height, options.tileSize)]; 13552 13553 while (parseInt(currentImageSize.x, 10) > options.tileSize || parseInt(currentImageSize.y, 10) > options.tileSize) { 13554 currentImageSize.x = Math.floor(currentImageSize.x / 2); 13555 currentImageSize.y = Math.floor(currentImageSize.y / 2); 13556 options.imageSizes.push({ 13557 x: currentImageSize.x, 13558 y: currentImageSize.y 13559 }); 13560 options.gridSize.push(this._getGridSize(currentImageSize.x, currentImageSize.y, options.tileSize)); 13561 } 13562 options.imageSizes.reverse(); 13563 options.gridSize.reverse(); 13564 options.minLevel = 0; 13565 options.maxLevel = options.gridSize.length - 1; 13566 13567 OpenSeadragon.TileSource.apply(this, [options]); 13568 }; 13569 13570 $.extend($.ZoomifyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ZoomifyTileSource.prototype */ { 13571 13572 //private 13573 _getGridSize: function(width, height, tileSize) { 13574 return { 13575 x: Math.ceil(width / tileSize), 13576 y: Math.ceil(height / tileSize) 13577 }; 13578 }, 13579 13580 //private 13581 _calculateAbsoluteTileNumber: function(level, x, y) { 13582 var num = 0; 13583 var size = {}; 13584 13585 //Sum up all tiles below the level we want the number of tiles 13586 for (var z = 0; z < level; z++) { 13587 size = this.gridSize[z]; 13588 num += size.x * size.y; 13589 } 13590 //Add the tiles of the level 13591 size = this.gridSize[level]; 13592 num += size.x * y + x; 13593 return num; 13594 }, 13595 13596 /** 13597 * Determine if the data and/or url imply the image service is supported by 13598 * this tile source. 13599 * @function 13600 * @param {Object|Array} data 13601 * @param {String} optional - url 13602 */ 13603 supports: function(data, url) { 13604 return (data.type && "zoomifytileservice" == data.type); 13605 }, 13606 13607 /** 13608 * 13609 * @function 13610 * @param {Object} data - the raw configuration 13611 * @param {String} url - the url the data was retrieved from if any. 13612 * @return {Object} options - A dictionary of keyword arguments sufficient 13613 * to configure this tile sources constructor. 13614 */ 13615 configure: function(data, url) { 13616 return data; 13617 }, 13618 13619 /** 13620 * @function 13621 * @param {Number} level 13622 * @param {Number} x 13623 * @param {Number} y 13624 */ 13625 getTileUrl: function(level, x, y) { 13626 //console.log(level); 13627 var result = 0; 13628 var num = this._calculateAbsoluteTileNumber(level, x, y); 13629 result = Math.floor(num / 256); 13630 return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.jpg'; 13631 13632 } 13633 }); 13634 13635}(OpenSeadragon)); 13636 13637 13638/* 13639 * OpenSeadragon - LegacyTileSource 13640 * 13641 * Copyright (C) 2009 CodePlex Foundation 13642 * Copyright (C) 2010-2013 OpenSeadragon contributors 13643 * 13644 * Redistribution and use in source and binary forms, with or without 13645 * modification, are permitted provided that the following conditions are 13646 * met: 13647 * 13648 * - Redistributions of source code must retain the above copyright notice, 13649 * this list of conditions and the following disclaimer. 13650 * 13651 * - Redistributions in binary form must reproduce the above copyright 13652 * notice, this list of conditions and the following disclaimer in the 13653 * documentation and/or other materials provided with the distribution. 13654 * 13655 * - Neither the name of CodePlex Foundation nor the names of its 13656 * contributors may be used to endorse or promote products derived from 13657 * this software without specific prior written permission. 13658 * 13659 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13660 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13661 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13662 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13663 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13664 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13665 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13666 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13667 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13668 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13669 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13670 */ 13671 13672(function( $ ){ 13673 13674/** 13675 * @class LegacyTileSource 13676 * @classdesc The LegacyTileSource allows simple, traditional image pyramids to be loaded 13677 * into an OpenSeadragon Viewer. Basically, this translates to the historically 13678 * common practice of starting with a 'master' image, maybe a tiff for example, 13679 * and generating a set of 'service' images like one or more thumbnails, a medium 13680 * resolution image and a high resolution image in standard web formats like 13681 * png or jpg. 13682 * 13683 * @memberof OpenSeadragon 13684 * @extends OpenSeadragon.TileSource 13685 * @param {Array} levels An array of file descriptions, each is an object with 13686 * a 'url', a 'width', and a 'height'. Overriding classes can expect more 13687 * properties but these properties are sufficient for this implementation. 13688 * Additionally, the levels are required to be listed in order from 13689 * smallest to largest. 13690 * @property {Number} aspectRatio 13691 * @property {Number} dimensions 13692 * @property {Number} tileSize 13693 * @property {Number} tileOverlap 13694 * @property {Number} minLevel 13695 * @property {Number} maxLevel 13696 * @property {Array} levels 13697 */ 13698$.LegacyTileSource = function( levels ) { 13699 13700 var options, 13701 width, 13702 height; 13703 13704 if( $.isArray( levels ) ){ 13705 options = { 13706 type: 'legacy-image-pyramid', 13707 levels: levels 13708 }; 13709 } 13710 13711 //clean up the levels to make sure we support all formats 13712 options.levels = filterFiles( options.levels ); 13713 13714 if ( options.levels.length > 0 ) { 13715 width = options.levels[ options.levels.length - 1 ].width; 13716 height = options.levels[ options.levels.length - 1 ].height; 13717 } 13718 else { 13719 width = 0; 13720 height = 0; 13721 $.console.error( "No supported image formats found" ); 13722 } 13723 13724 $.extend( true, options, { 13725 width: width, 13726 height: height, 13727 tileSize: Math.max( height, width ), 13728 tileOverlap: 0, 13729 minLevel: 0, 13730 maxLevel: options.levels.length > 0 ? options.levels.length - 1 : 0 13731 } ); 13732 13733 $.TileSource.apply( this, [ options ] ); 13734 13735 this.levels = options.levels; 13736}; 13737 13738$.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.LegacyTileSource.prototype */{ 13739 /** 13740 * Determine if the data and/or url imply the image service is supported by 13741 * this tile source. 13742 * @function 13743 * @param {Object|Array} data 13744 * @param {String} optional - url 13745 */ 13746 supports: function( data, url ){ 13747 return ( 13748 data.type && 13749 "legacy-image-pyramid" == data.type 13750 ) || ( 13751 data.documentElement && 13752 "legacy-image-pyramid" == data.documentElement.getAttribute('type') 13753 ); 13754 }, 13755 13756 13757 /** 13758 * 13759 * @function 13760 * @param {Object|XMLDocument} configuration - the raw configuration 13761 * @param {String} dataUrl - the url the data was retrieved from if any. 13762 * @return {Object} options - A dictionary of keyword arguments sufficient 13763 * to configure this tile sources constructor. 13764 */ 13765 configure: function( configuration, dataUrl ){ 13766 13767 var options; 13768 13769 if( !$.isPlainObject(configuration) ){ 13770 13771 options = configureFromXML( this, configuration ); 13772 13773 }else{ 13774 13775 options = configureFromObject( this, configuration ); 13776 } 13777 13778 return options; 13779 13780 }, 13781 13782 /** 13783 * @function 13784 * @param {Number} level 13785 */ 13786 getLevelScale: function ( level ) { 13787 var levelScale = NaN; 13788 if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { 13789 levelScale = 13790 this.levels[ level ].width / 13791 this.levels[ this.maxLevel ].width; 13792 } 13793 return levelScale; 13794 }, 13795 13796 /** 13797 * @function 13798 * @param {Number} level 13799 */ 13800 getNumTiles: function( level ) { 13801 var scale = this.getLevelScale( level ); 13802 if ( scale ){ 13803 return new $.Point( 1, 1 ); 13804 } else { 13805 return new $.Point( 0, 0 ); 13806 } 13807 }, 13808 13809 /** 13810 * This method is not implemented by this class other than to throw an Error 13811 * announcing you have to implement it. Because of the variety of tile 13812 * server technologies, and various specifications for building image 13813 * pyramids, this method is here to allow easy integration. 13814 * @function 13815 * @param {Number} level 13816 * @param {Number} x 13817 * @param {Number} y 13818 * @throws {Error} 13819 */ 13820 getTileUrl: function ( level, x, y ) { 13821 var url = null; 13822 if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { 13823 url = this.levels[ level ].url; 13824 } 13825 return url; 13826 } 13827} ); 13828 13829/** 13830 * This method removes any files from the Array which don't conform to our 13831 * basic requirements for a 'level' in the LegacyTileSource. 13832 * @private 13833 * @inner 13834 * @function 13835 */ 13836function filterFiles( files ){ 13837 var filtered = [], 13838 file, 13839 i; 13840 for( i = 0; i < files.length; i++ ){ 13841 file = files[ i ]; 13842 if( file.height && 13843 file.width && 13844 file.url ){ 13845 //This is sufficient to serve as a level 13846 filtered.push({ 13847 url: file.url, 13848 width: Number( file.width ), 13849 height: Number( file.height ) 13850 }); 13851 } 13852 else { 13853 $.console.error( 'Unsupported image format: %s', file.url ? file.url : '<no URL>' ); 13854 } 13855 } 13856 13857 return filtered.sort(function(a, b) { 13858 return a.height - b.height; 13859 }); 13860 13861} 13862 13863/** 13864 * @private 13865 * @inner 13866 * @function 13867 */ 13868function configureFromXML( tileSource, xmlDoc ){ 13869 13870 if ( !xmlDoc || !xmlDoc.documentElement ) { 13871 throw new Error( $.getString( "Errors.Xml" ) ); 13872 } 13873 13874 var root = xmlDoc.documentElement, 13875 rootName = root.tagName, 13876 conf = null, 13877 levels = [], 13878 level, 13879 i; 13880 13881 if ( rootName == "image" ) { 13882 13883 try { 13884 conf = { 13885 type: root.getAttribute( "type" ), 13886 levels: [] 13887 }; 13888 13889 levels = root.getElementsByTagName( "level" ); 13890 for ( i = 0; i < levels.length; i++ ) { 13891 level = levels[ i ]; 13892 13893 conf.levels.push({ 13894 url: level.getAttribute( "url" ), 13895 width: parseInt( level.getAttribute( "width" ), 10 ), 13896 height: parseInt( level.getAttribute( "height" ), 10 ) 13897 }); 13898 } 13899 13900 return configureFromObject( tileSource, conf ); 13901 13902 } catch ( e ) { 13903 throw (e instanceof Error) ? 13904 e : 13905 new Error( 'Unknown error parsing Legacy Image Pyramid XML.' ); 13906 } 13907 } else if ( rootName == "collection" ) { 13908 throw new Error( 'Legacy Image Pyramid Collections not yet supported.' ); 13909 } else if ( rootName == "error" ) { 13910 throw new Error( 'Error: ' + xmlDoc ); 13911 } 13912 13913 throw new Error( 'Unknown element ' + rootName ); 13914} 13915 13916/** 13917 * @private 13918 * @inner 13919 * @function 13920 */ 13921function configureFromObject( tileSource, configuration ){ 13922 13923 return configuration.levels; 13924 13925} 13926 13927}( OpenSeadragon )); 13928 13929/* 13930 * OpenSeadragon - ImageTileSource 13931 * 13932 * Copyright (C) 2009 CodePlex Foundation 13933 * Copyright (C) 2010-2013 OpenSeadragon contributors 13934 * 13935 * Redistribution and use in source and binary forms, with or without 13936 * modification, are permitted provided that the following conditions are 13937 * met: 13938 * 13939 * - Redistributions of source code must retain the above copyright notice, 13940 * this list of conditions and the following disclaimer. 13941 * 13942 * - Redistributions in binary form must reproduce the above copyright 13943 * notice, this list of conditions and the following disclaimer in the 13944 * documentation and/or other materials provided with the distribution. 13945 * 13946 * - Neither the name of CodePlex Foundation nor the names of its 13947 * contributors may be used to endorse or promote products derived from 13948 * this software without specific prior written permission. 13949 * 13950 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13951 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13952 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13953 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13954 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13955 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13956 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13957 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13958 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13959 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13960 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13961 */ 13962 13963(function ($) { 13964 13965 /** 13966 * @class ImageTileSource 13967 * @classdesc The ImageTileSource allows a simple image to be loaded 13968 * into an OpenSeadragon Viewer. 13969 * There are 2 ways to open an ImageTileSource: 13970 * 1. viewer.open({type: 'image', url: fooUrl}); 13971 * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl})); 13972 * 13973 * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and 13974 * useCanvas options are inherited from the viewer if they are not 13975 * specified directly in the options object. 13976 * 13977 * @memberof OpenSeadragon 13978 * @extends OpenSeadragon.TileSource 13979 * @param {Object} options Options object. 13980 * @param {String} options.url URL of the image 13981 * @param {Boolean} [options.buildPyramid=true] If set to true (default), a 13982 * pyramid will be built internally to provide a better downsampling. 13983 * @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are 13984 * 'Anonymous', 'use-credentials', and false. If false, image requests will 13985 * not use CORS preventing internal pyramid building for images from other 13986 * domains. 13987 * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set 13988 * the withCredentials XHR flag for AJAX requests (when loading tile sources). 13989 * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use 13990 * of the canvas API. 13991 */ 13992 $.ImageTileSource = function (options) { 13993 13994 options = $.extend({ 13995 buildPyramid: true, 13996 crossOriginPolicy: false, 13997 ajaxWithCredentials: false, 13998 useCanvas: true 13999 }, options); 14000 $.TileSource.apply(this, [options]); 14001 14002 }; 14003 14004 $.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{ 14005 /** 14006 * Determine if the data and/or url imply the image service is supported by 14007 * this tile source. 14008 * @function 14009 * @param {Object|Array} data 14010 * @param {String} optional - url 14011 */ 14012 supports: function (data, url) { 14013 return data.type && data.type === "image"; 14014 }, 14015 /** 14016 * 14017 * @function 14018 * @param {Object} options - the options 14019 * @param {String} dataUrl - the url the image was retrieved from, if any. 14020 * @return {Object} options - A dictionary of keyword arguments sufficient 14021 * to configure this tile sources constructor. 14022 */ 14023 configure: function (options, dataUrl) { 14024 return options; 14025 }, 14026 /** 14027 * Responsible for retrieving, and caching the 14028 * image metadata pertinent to this TileSources implementation. 14029 * @function 14030 * @param {String} url 14031 * @throws {Error} 14032 */ 14033 getImageInfo: function (url) { 14034 var image = this._image = new Image(); 14035 var _this = this; 14036 14037 if (this.crossOriginPolicy) { 14038 image.crossOrigin = this.crossOriginPolicy; 14039 } 14040 if (this.ajaxWithCredentials) { 14041 image.useCredentials = this.ajaxWithCredentials; 14042 } 14043 14044 $.addEvent(image, 'load', function () { 14045 /* IE8 fix since it has no naturalWidth and naturalHeight */ 14046 _this.width = Object.prototype.hasOwnProperty.call(image, 'naturalWidth') ? image.naturalWidth : image.width; 14047 _this.height = Object.prototype.hasOwnProperty.call(image, 'naturalHeight') ? image.naturalHeight : image.height; 14048 _this.aspectRatio = _this.width / _this.height; 14049 _this.dimensions = new $.Point(_this.width, _this.height); 14050 _this._tileWidth = _this.width; 14051 _this._tileHeight = _this.height; 14052 _this.tileOverlap = 0; 14053 _this.minLevel = 0; 14054 _this.levels = _this._buildLevels(); 14055 _this.maxLevel = _this.levels.length - 1; 14056 14057 _this.ready = true; 14058 14059 // Note: this event is documented elsewhere, in TileSource 14060 _this.raiseEvent('ready', {tileSource: _this}); 14061 }); 14062 14063 $.addEvent(image, 'error', function () { 14064 // Note: this event is documented elsewhere, in TileSource 14065 _this.raiseEvent('open-failed', { 14066 message: "Error loading image at " + url, 14067 source: url 14068 }); 14069 }); 14070 14071 image.src = url; 14072 }, 14073 /** 14074 * @function 14075 * @param {Number} level 14076 */ 14077 getLevelScale: function (level) { 14078 var levelScale = NaN; 14079 if (level >= this.minLevel && level <= this.maxLevel) { 14080 levelScale = 14081 this.levels[level].width / 14082 this.levels[this.maxLevel].width; 14083 } 14084 return levelScale; 14085 }, 14086 /** 14087 * @function 14088 * @param {Number} level 14089 */ 14090 getNumTiles: function (level) { 14091 var scale = this.getLevelScale(level); 14092 if (scale) { 14093 return new $.Point(1, 1); 14094 } else { 14095 return new $.Point(0, 0); 14096 } 14097 }, 14098 /** 14099 * Retrieves a tile url 14100 * @function 14101 * @param {Number} level Level of the tile 14102 * @param {Number} x x coordinate of the tile 14103 * @param {Number} y y coordinate of the tile 14104 */ 14105 getTileUrl: function (level, x, y) { 14106 var url = null; 14107 if (level >= this.minLevel && level <= this.maxLevel) { 14108 url = this.levels[level].url; 14109 } 14110 return url; 14111 }, 14112 /** 14113 * Retrieves a tile context 2D 14114 * @function 14115 * @param {Number} level Level of the tile 14116 * @param {Number} x x coordinate of the tile 14117 * @param {Number} y y coordinate of the tile 14118 */ 14119 getContext2D: function (level, x, y) { 14120 var context = null; 14121 if (level >= this.minLevel && level <= this.maxLevel) { 14122 context = this.levels[level].context2D; 14123 } 14124 return context; 14125 }, 14126 14127 // private 14128 // 14129 // Builds the different levels of the pyramid if possible 14130 // (i.e. if canvas API enabled and no canvas tainting issue). 14131 _buildLevels: function () { 14132 var levels = [{ 14133 url: this._image.src,
14134 /* IE8 fix since it has no naturalWidth and naturalHeight */ 14135 width: Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width, 14136 height: Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height 14137 }]; 14138 14139 if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) { 14140 // We don't need the image anymore. Allows it to be GC. 14141 delete this._image; 14142 return levels; 14143 } 14144 14145 /* IE8 fix since it has no naturalWidth and naturalHeight */ 14146 var currentWidth = Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width; 14147 var currentHeight = Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height; 14148 14149 14150 var bigCanvas = document.createElement("canvas"); 14151 var bigContext = bigCanvas.getContext("2d"); 14152 14153 bigCanvas.width = currentWidth; 14154 bigCanvas.height = currentHeight; 14155 bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight); 14156 // We cache the context of the highest level because the browser 14157 // is a lot faster at downsampling something it already has 14158 // downsampled before. 14159 levels[0].context2D = bigContext; 14160 // We don't need the image anymore. Allows it to be GC. 14161 delete this._image; 14162 14163 if ($.isCanvasTainted(bigCanvas)) { 14164 // If the canvas is tainted, we can't compute the pyramid. 14165 return levels; 14166 } 14167 14168 // We build smaller levels until either width or height becomes 14169 // 1 pixel wide. 14170 while (currentWidth >= 2 && currentHeight >= 2) { 14171 currentWidth = Math.floor(currentWidth / 2); 14172 currentHeight = Math.floor(currentHeight / 2); 14173 var smallCanvas = document.createElement("canvas"); 14174 var smallContext = smallCanvas.getContext("2d"); 14175 smallCanvas.width = currentWidth; 14176 smallCanvas.height = currentHeight; 14177 smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight); 14178 14179 levels.splice(0, 0, { 14180 context2D: smallContext, 14181 width: currentWidth, 14182 height: currentHeight 14183 }); 14184 14185 bigCanvas = smallCanvas; 14186 bigContext = smallContext; 14187 } 14188 return levels; 14189 } 14190 }); 14191 14192}(OpenSeadragon)); 14193 14194/* 14195 * OpenSeadragon - TileSourceCollection 14196 * 14197 * Copyright (C) 2009 CodePlex Foundation 14198 * Copyright (C) 2010-2013 OpenSeadragon contributors 14199 * 14200 * Redistribution and use in source and binary forms, with or without 14201 * modification, are permitted provided that the following conditions are 14202 * met: 14203 * 14204 * - Redistributions of source code must retain the above copyright notice, 14205 * this list of conditions and the following disclaimer. 14206 * 14207 * - Redistributions in binary form must reproduce the above copyright 14208 * notice, this list of conditions and the following disclaimer in the 14209 * documentation and/or other materials provided with the distribution. 14210 * 14211 * - Neither the name of CodePlex Foundation nor the names of its 14212 * contributors may be used to endorse or promote products derived from 14213 * this software without specific prior written permission. 14214 * 14215 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14216 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14217 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14218 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14219 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14220 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14221 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14222 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14223 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14224 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14225 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14226 */ 14227 14228(function($) { 14229 14230// deprecated 14231$.TileSourceCollection = function(tileSize, tileSources, rows, layout) { 14232 $.console.error('TileSourceCollection is deprecated; use World instead'); 14233}; 14234 14235}(OpenSeadragon)); 14236 14237/* 14238 * OpenSeadragon - Button 14239 * 14240 * Copyright (C) 2009 CodePlex Foundation 14241 * Copyright (C) 2010-2013 OpenSeadragon contributors 14242 * 14243 * Redistribution and use in source and binary forms, with or without 14244 * modification, are permitted provided that the following conditions are 14245 * met: 14246 * 14247 * - Redistributions of source code must retain the above copyright notice, 14248 * this list of conditions and the following disclaimer. 14249 * 14250 * - Redistributions in binary form must reproduce the above copyright 14251 * notice, this list of conditions and the following disclaimer in the 14252 * documentation and/or other materials provided with the distribution. 14253 * 14254 * - Neither the name of CodePlex Foundation nor the names of its 14255 * contributors may be used to endorse or promote products derived from 14256 * this software without specific prior written permission. 14257 * 14258 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14259 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14260 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14261 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14262 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14263 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14264 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14265 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14266 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14267 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14268 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14269 */ 14270 14271(function( $ ){ 14272 14273/** 14274 * An enumeration of button states 14275 * @member ButtonState 14276 * @memberof OpenSeadragon 14277 * @static 14278 * @type {Object} 14279 * @property {Number} REST 14280 * @property {Number} GROUP 14281 * @property {Number} HOVER 14282 * @property {Number} DOWN 14283 */ 14284$.ButtonState = { 14285 REST: 0, 14286 GROUP: 1, 14287 HOVER: 2, 14288 DOWN: 3 14289}; 14290 14291/** 14292 * @class Button 14293 * @classdesc Manages events, hover states for individual buttons, tool-tips, as well 14294 * as fading the buttons out when the user has not interacted with them 14295 * for a specified period. 14296 * 14297 * @memberof OpenSeadragon 14298 * @extends OpenSeadragon.EventSource 14299 * @param {Object} options 14300 * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML <div> element is created. 14301 * @param {String} [options.tooltip=null] Provides context help for the button when the 14302 * user hovers over it. 14303 * @param {String} [options.srcRest=null] URL of image to use in 'rest' state. 14304 * @param {String} [options.srcGroup=null] URL of image to use in 'up' state. 14305 * @param {String} [options.srcHover=null] URL of image to use in 'hover' state. 14306 * @param {String} [options.srcDown=null] URL of image to use in 'down' state. 14307 * @param {Number} [options.fadeDelay=0] How long to wait before fading. 14308 * @param {Number} [options.fadeLength=2000] How long should it take to fade the button. 14309 * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}. 14310 * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}. 14311 * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}. 14312 * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}. 14313 * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}. 14314 * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}. 14315 * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}. 14316 */ 14317$.Button = function( options ) { 14318 14319 var _this = this; 14320 14321 $.EventSource.call( this ); 14322 14323 $.extend( true, this, { 14324 14325 tooltip: null, 14326 srcRest: null, 14327 srcGroup: null, 14328 srcHover: null, 14329 srcDown: null, 14330 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold, 14331 clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold, 14332 /** 14333 * How long to wait before fading. 14334 * @member {Number} fadeDelay 14335 * @memberof OpenSeadragon.Button# 14336 */ 14337 fadeDelay: 0, 14338 /** 14339 * How long should it take to fade the button. 14340 * @member {Number} fadeLength 14341 * @memberof OpenSeadragon.Button# 14342 */ 14343 fadeLength: 2000, 14344 onPress: null, 14345 onRelease: null, 14346 onClick: null, 14347 onEnter: null, 14348 onExit: null, 14349 onFocus: null, 14350 onBlur: null 14351 14352 }, options ); 14353 14354 /** 14355 * The button element. 14356 * @member {Element} element 14357 * @memberof OpenSeadragon.Button# 14358 */ 14359 this.element = options.element || $.makeNeutralElement("div"); 14360 14361 //if the user has specified the element to bind the control to explicitly 14362 //then do not add the default control images 14363 if ( !options.element ) { 14364 this.imgRest = $.makeTransparentImage( this.srcRest ); 14365 this.imgGroup = $.makeTransparentImage( this.srcGroup ); 14366 this.imgHover = $.makeTransparentImage( this.srcHover ); 14367 this.imgDown = $.makeTransparentImage( this.srcDown ); 14368 14369 this.imgRest.alt = 14370 this.imgGroup.alt = 14371 this.imgHover.alt = 14372 this.imgDown.alt = 14373 this.tooltip; 14374 14375 this.element.style.position = "relative"; 14376 $.setElementTouchActionNone( this.element ); 14377 14378 this.imgGroup.style.position = 14379 this.imgHover.style.position = 14380 this.imgDown.style.position = 14381 "absolute"; 14382 14383 this.imgGroup.style.top = 14384 this.imgHover.style.top = 14385 this.imgDown.style.top = 14386 "0px"; 14387 14388 this.imgGroup.style.left = 14389 this.imgHover.style.left = 14390 this.imgDown.style.left = 14391 "0px"; 14392 14393 this.imgHover.style.visibility = 14394 this.imgDown.style.visibility = 14395 "hidden"; 14396 14397 if ($.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3) { 14398 this.imgGroup.style.top = 14399 this.imgHover.style.top = 14400 this.imgDown.style.top = 14401 ""; 14402 } 14403 14404 this.element.appendChild( this.imgRest ); 14405 this.element.appendChild( this.imgGroup ); 14406 this.element.appendChild( this.imgHover ); 14407 this.element.appendChild( this.imgDown ); 14408 } 14409 14410 14411 this.addHandler("press", this.onPress); 14412 this.addHandler("release", this.onRelease); 14413 this.addHandler("click", this.onClick); 14414 this.addHandler("enter", this.onEnter); 14415 this.addHandler("exit", this.onExit); 14416 this.addHandler("focus", this.onFocus); 14417 this.addHandler("blur", this.onBlur); 14418 14419 /** 14420 * The button's current state. 14421 * @member {OpenSeadragon.ButtonState} currentState 14422 * @memberof OpenSeadragon.Button# 14423 */ 14424 this.currentState = $.ButtonState.GROUP; 14425 14426 // When the button last began to fade. 14427 this.fadeBeginTime = null; 14428 // Whether this button should fade after user stops interacting with the viewport. 14429 this.shouldFade = false;
vendor: 16,114 bytes, lines 14430-14887
14430 14431 this.element.style.display = "inline-block"; 14432 this.element.style.position = "relative"; 14433 this.element.title = this.tooltip; 14434 14435 /** 14436 * Tracks mouse/touch/key events on the button. 14437 * @member {OpenSeadragon.MouseTracker} tracker 14438 * @memberof OpenSeadragon.Button# 14439 */ 14440 this.tracker = new $.MouseTracker({ 14441 14442 element: this.element, 14443 clickTimeThreshold: this.clickTimeThreshold, 14444 clickDistThreshold: this.clickDistThreshold, 14445 14446 enterHandler: function( event ) { 14447 if ( event.insideElementPressed ) { 14448 inTo( _this, $.ButtonState.DOWN ); 14449 /** 14450 * Raised when the cursor enters the Button element. 14451 * 14452 * @event enter 14453 * @memberof OpenSeadragon.Button 14454 * @type {object} 14455 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14456 * @property {Object} originalEvent - The original DOM event. 14457 * @property {?Object} userData - Arbitrary subscriber-defined object. 14458 */ 14459 _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); 14460 } else if ( !event.buttonDownAny ) { 14461 inTo( _this, $.ButtonState.HOVER ); 14462 } 14463 }, 14464 14465 focusHandler: function ( event ) { 14466 this.enterHandler( event ); 14467 /** 14468 * Raised when the Button element receives focus. 14469 * 14470 * @event focus 14471 * @memberof OpenSeadragon.Button 14472 * @type {object} 14473 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14474 * @property {Object} originalEvent - The original DOM event. 14475 * @property {?Object} userData - Arbitrary subscriber-defined object. 14476 */ 14477 _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); 14478 }, 14479 14480 exitHandler: function( event ) { 14481 outTo( _this, $.ButtonState.GROUP ); 14482 if ( event.insideElementPressed ) { 14483 /** 14484 * Raised when the cursor leaves the Button element. 14485 * 14486 * @event exit 14487 * @memberof OpenSeadragon.Button 14488 * @type {object} 14489 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14490 * @property {Object} originalEvent - The original DOM event. 14491 * @property {?Object} userData - Arbitrary subscriber-defined object. 14492 */ 14493 _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); 14494 } 14495 }, 14496 14497 blurHandler: function ( event ) { 14498 this.exitHandler( event ); 14499 /** 14500 * Raised when the Button element loses focus. 14501 * 14502 * @event blur 14503 * @memberof OpenSeadragon.Button 14504 * @type {object} 14505 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14506 * @property {Object} originalEvent - The original DOM event. 14507 * @property {?Object} userData - Arbitrary subscriber-defined object. 14508 */ 14509 _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); 14510 }, 14511 14512 pressHandler: function ( event ) { 14513 inTo( _this, $.ButtonState.DOWN ); 14514 /** 14515 * Raised when a mouse button is pressed or touch occurs in the Button element. 14516 * 14517 * @event press 14518 * @memberof OpenSeadragon.Button 14519 * @type {object} 14520 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14521 * @property {Object} originalEvent - The original DOM event. 14522 * @property {?Object} userData - Arbitrary subscriber-defined object. 14523 */ 14524 _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); 14525 }, 14526 14527 releaseHandler: function( event ) { 14528 if ( event.insideElementPressed && event.insideElementReleased ) { 14529 outTo( _this, $.ButtonState.HOVER ); 14530 /** 14531 * Raised when the mouse button is released or touch ends in the Button element. 14532 * 14533 * @event release 14534 * @memberof OpenSeadragon.Button 14535 * @type {object} 14536 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14537 * @property {Object} originalEvent - The original DOM event. 14538 * @property {?Object} userData - Arbitrary subscriber-defined object. 14539 */ 14540 _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); 14541 } else if ( event.insideElementPressed ) { 14542 outTo( _this, $.ButtonState.GROUP ); 14543 } else { 14544 inTo( _this, $.ButtonState.HOVER ); 14545 } 14546 }, 14547 14548 clickHandler: function( event ) { 14549 if ( event.quick ) { 14550 /** 14551 * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold. 14552 * 14553 * @event click 14554 * @memberof OpenSeadragon.Button 14555 * @type {object} 14556 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14557 * @property {Object} originalEvent - The original DOM event. 14558 * @property {?Object} userData - Arbitrary subscriber-defined object. 14559 */ 14560 _this.raiseEvent("click", { originalEvent: event.originalEvent }); 14561 } 14562 }, 14563 14564 keyHandler: function( event ){ 14565 //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode); 14566 if( 13 === event.keyCode ){ 14567 /*** 14568 * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold. 14569 * 14570 * @event click 14571 * @memberof OpenSeadragon.Button 14572 * @type {object} 14573 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14574 * @property {Object} originalEvent - The original DOM event. 14575 * @property {?Object} userData - Arbitrary subscriber-defined object. 14576 */ 14577 _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); 14578 /*** 14579 * Raised when the mouse button is released or touch ends in the Button element. 14580 * 14581 * @event release 14582 * @memberof OpenSeadragon.Button 14583 * @type {object} 14584 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 14585 * @property {Object} originalEvent - The original DOM event. 14586 * @property {?Object} userData - Arbitrary subscriber-defined object. 14587 */ 14588 _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); 14589 return false; 14590 } 14591 return true; 14592 } 14593 14594 }); 14595 14596 outTo( this, $.ButtonState.REST ); 14597}; 14598 14599$.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{ 14600 14601 /** 14602 * TODO: Determine what this function is intended to do and if it's actually 14603 * useful as an API point. 14604 * @function 14605 */ 14606 notifyGroupEnter: function() { 14607 inTo( this, $.ButtonState.GROUP ); 14608 }, 14609 14610 /** 14611 * TODO: Determine what this function is intended to do and if it's actually 14612 * useful as an API point. 14613 * @function 14614 */ 14615 notifyGroupExit: function() { 14616 outTo( this, $.ButtonState.REST ); 14617 }, 14618 14619 /** 14620 * @function 14621 */ 14622 disable: function(){ 14623 this.notifyGroupExit(); 14624 this.element.disabled = true; 14625 $.setElementOpacity( this.element, 0.2, true ); 14626 }, 14627 14628 /** 14629 * @function 14630 */ 14631 enable: function(){ 14632 this.element.disabled = false; 14633 $.setElementOpacity( this.element, 1.0, true ); 14634 this.notifyGroupEnter(); 14635 } 14636 14637}); 14638 14639 14640function scheduleFade( button ) { 14641 $.requestAnimationFrame(function(){ 14642 updateFade( button ); 14643 }); 14644} 14645 14646function updateFade( button ) { 14647 var currentTime, 14648 deltaTime, 14649 opacity; 14650 14651 if ( button.shouldFade ) { 14652 currentTime = $.now(); 14653 deltaTime = currentTime - button.fadeBeginTime; 14654 opacity = 1.0 - deltaTime / button.fadeLength; 14655 opacity = Math.min( 1.0, opacity ); 14656 opacity = Math.max( 0.0, opacity ); 14657 14658 if( button.imgGroup ){ 14659 $.setElementOpacity( button.imgGroup, opacity, true ); 14660 } 14661 if ( opacity > 0 ) { 14662 // fade again 14663 scheduleFade( button ); 14664 } 14665 } 14666} 14667 14668function beginFading( button ) { 14669 button.shouldFade = true; 14670 button.fadeBeginTime = $.now() + button.fadeDelay; 14671 window.setTimeout( function(){ 14672 scheduleFade( button ); 14673 }, button.fadeDelay ); 14674} 14675 14676function stopFading( button ) { 14677 button.shouldFade = false; 14678 if( button.imgGroup ){ 14679 $.setElementOpacity( button.imgGroup, 1.0, true ); 14680 } 14681} 14682 14683function inTo( button, newState ) { 14684 14685 if( button.element.disabled ){ 14686 return; 14687 } 14688 14689 if ( newState >= $.ButtonState.GROUP && 14690 button.currentState == $.ButtonState.REST ) { 14691 stopFading( button ); 14692 button.currentState = $.ButtonState.GROUP; 14693 } 14694 14695 if ( newState >= $.ButtonState.HOVER && 14696 button.currentState == $.ButtonState.GROUP ) { 14697 if( button.imgHover ){ 14698 button.imgHover.style.visibility = ""; 14699 } 14700 button.currentState = $.ButtonState.HOVER; 14701 } 14702 14703 if ( newState >= $.ButtonState.DOWN && 14704 button.currentState == $.ButtonState.HOVER ) { 14705 if( button.imgDown ){ 14706 button.imgDown.style.visibility = ""; 14707 } 14708 button.currentState = $.ButtonState.DOWN; 14709 } 14710} 14711 14712 14713function outTo( button, newState ) { 14714 14715 if( button.element.disabled ){ 14716 return; 14717 } 14718 14719 if ( newState <= $.ButtonState.HOVER && 14720 button.currentState == $.ButtonState.DOWN ) { 14721 if( button.imgDown ){ 14722 button.imgDown.style.visibility = "hidden"; 14723 } 14724 button.currentState = $.ButtonState.HOVER; 14725 } 14726 14727 if ( newState <= $.ButtonState.GROUP && 14728 button.currentState == $.ButtonState.HOVER ) { 14729 if( button.imgHover ){ 14730 button.imgHover.style.visibility = "hidden"; 14731 } 14732 button.currentState = $.ButtonState.GROUP; 14733 } 14734 14735 if ( newState <= $.ButtonState.REST && 14736 button.currentState == $.ButtonState.GROUP ) { 14737 beginFading( button ); 14738 button.currentState = $.ButtonState.REST; 14739 } 14740} 14741 14742 14743 14744}( OpenSeadragon )); 14745 14746/* 14747 * OpenSeadragon - ButtonGroup 14748 * 14749 * Copyright (C) 2009 CodePlex Foundation 14750 * Copyright (C) 2010-2013 OpenSeadragon contributors 14751 * 14752 * Redistribution and use in source and binary forms, with or without 14753 * modification, are permitted provided that the following conditions are 14754 * met: 14755 * 14756 * - Redistributions of source code must retain the above copyright notice, 14757 * this list of conditions and the following disclaimer. 14758 * 14759 * - Redistributions in binary form must reproduce the above copyright 14760 * notice, this list of conditions and the following disclaimer in the 14761 * documentation and/or other materials provided with the distribution. 14762 * 14763 * - Neither the name of CodePlex Foundation nor the names of its 14764 * contributors may be used to endorse or promote products derived from 14765 * this software without specific prior written permission. 14766 * 14767 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14768 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14769 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14770 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14771 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14772 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14773 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14774 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14775 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14776 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14777 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14778 */ 14779 14780(function( $ ){ 14781/** 14782 * @class ButtonGroup 14783 * @classdesc Manages events on groups of buttons. 14784 * 14785 * @memberof OpenSeadragon 14786 * @param {Object} options - A dictionary of settings applied against the entire group of buttons. 14787 * @param {Array} options.buttons Array of buttons 14788 * @param {Element} [options.element] Element to use as the container 14789 **/ 14790$.ButtonGroup = function( options ) { 14791 14792 $.extend( true, this, { 14793 /** 14794 * An array containing the buttons themselves. 14795 * @member {Array} buttons 14796 * @memberof OpenSeadragon.ButtonGroup# 14797 */ 14798 buttons: [], 14799 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold, 14800 clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold, 14801 labelText: "" 14802 }, options ); 14803 14804 // copy the button elements TODO: Why? 14805 var buttons = this.buttons.concat([]), 14806 _this = this, 14807 i; 14808 14809 /** 14810 * The shared container for the buttons. 14811 * @member {Element} element 14812 * @memberof OpenSeadragon.ButtonGroup# 14813 */ 14814 this.element = options.element || $.makeNeutralElement( "div" ); 14815 14816 // TODO What if there IS an options.group specified? 14817 if( !options.group ){ 14818 this.element.style.display = "inline-block"; 14819 //this.label = $.makeNeutralElement( "label" ); 14820 //TODO: support labels for ButtonGroups 14821 //this.label.innerHTML = this.labelText; 14822 //this.element.appendChild( this.label ); 14823 for ( i = 0; i < buttons.length; i++ ) { 14824 this.element.appendChild( buttons[ i ].element ); 14825 } 14826 } 14827 14828 $.setElementTouchActionNone( this.element ); 14829 14830 /** 14831 * Tracks mouse/touch/key events across the group of buttons. 14832 * @member {OpenSeadragon.MouseTracker} tracker 14833 * @memberof OpenSeadragon.ButtonGroup# 14834 */ 14835 this.tracker = new $.MouseTracker({ 14836 element: this.element, 14837 clickTimeThreshold: this.clickTimeThreshold, 14838 clickDistThreshold: this.clickDistThreshold, 14839 enterHandler: function ( event ) { 14840 var i; 14841 for ( i = 0; i < _this.buttons.length; i++ ) { 14842 _this.buttons[ i ].notifyGroupEnter(); 14843 } 14844 }, 14845 exitHandler: function ( event ) { 14846 var i; 14847 if ( !event.insideElementPressed ) { 14848 for ( i = 0; i < _this.buttons.length; i++ ) { 14849 _this.buttons[ i ].notifyGroupExit(); 14850 } 14851 } 14852 }, 14853 }); 14854}; 14855 14856/** @lends OpenSeadragon.ButtonGroup.prototype */ 14857$.ButtonGroup.prototype = { 14858 14859 /** 14860 * TODO: Figure out why this is used on the public API and if a more useful 14861 * api can be created. 14862 * @function 14863 * @private 14864 */ 14865 emulateEnter: function() { 14866 this.tracker.enterHandler( { eventSource: this.tracker } ); 14867 }, 14868 14869 /** 14870 * TODO: Figure out why this is used on the public API and if a more useful 14871 * api can be created. 14872 * @function 14873 * @private 14874 */ 14875 emulateExit: function() { 14876 this.tracker.exitHandler( { eventSource: this.tracker } ); 14877 } 14878}; 14879 14880 14881}( OpenSeadragon )); 14882 14883/* 14884 * OpenSeadragon - Rect 14885 * 14886 * Copyright (C) 2009 CodePlex Foundation 14887 * Copyright (C) 2010-2013
vendor: 3,365 bytes, lines 14887-14972
14887 OpenSeadragon contributors 14888 * 14889 * Redistribution and use in source and binary forms, with or without 14890 * modification, are permitted provided that the following conditions are 14891 * met: 14892 * 14893 * - Redistributions of source code must retain the above copyright notice, 14894 * this list of conditions and the following disclaimer. 14895 * 14896 * - Redistributions in binary form must reproduce the above copyright 14897 * notice, this list of conditions and the following disclaimer in the 14898 * documentation and/or other materials provided with the distribution. 14899 * 14900 * - Neither the name of CodePlex Foundation nor the names of its 14901 * contributors may be used to endorse or promote products derived from 14902 * this software without specific prior written permission. 14903 * 14904 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14905 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14906 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14907 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14908 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14909 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14910 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14911 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14912 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14913 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14914 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14915 */ 14916 14917(function($) { 14918 14919/** 14920 * @class Rect 14921 * @classdesc A Rectangle is described by it top left coordinates (x, y), width, 14922 * height and degrees of rotation around (x, y). 14923 * Note that the coordinate system used is the one commonly used with images: 14924 * x increases when going to the right 14925 * y increases when going to the bottom 14926 * degrees increases clockwise with 0 being the horizontal 14927 * 14928 * The constructor normalizes the rectangle to always have 0 <= degrees < 90 14929 * 14930 * @memberof OpenSeadragon 14931 * @param {Number} [x=0] The vector component 'x'. 14932 * @param {Number} [y=0] The vector component 'y'. 14933 * @param {Number} [width=0] The vector component 'width'. 14934 * @param {Number} [height=0] The vector component 'height'. 14935 * @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees. 14936 */ 14937$.Rect = function(x, y, width, height, degrees) { 14938 /** 14939 * The vector component 'x'. 14940 * @member {Number} x 14941 * @memberof OpenSeadragon.Rect# 14942 */ 14943 this.x = typeof (x) === "number" ? x : 0; 14944 /** 14945 * The vector component 'y'. 14946 * @member {Number} y 14947 * @memberof OpenSeadragon.Rect# 14948 */ 14949 this.y = typeof (y) === "number" ? y : 0; 14950 /** 14951 * The vector component 'width'. 14952 * @member {Number} width 14953 * @memberof OpenSeadragon.Rect# 14954 */ 14955 this.width = typeof (width) === "number" ? width : 0; 14956 /** 14957 * The vector component 'height'. 14958 * @member {Number} height 14959 * @memberof OpenSeadragon.Rect# 14960 */ 14961 this.height = typeof (height) === "number" ? height : 0; 14962 14963 /** 14964 * The rotation of the rectangle, in degrees. 14965 * @member {Number} degrees 14966 * @memberof OpenSeadragon.Rect# 14967 */ 14968 this.degrees = typeof (degrees) === "number" ? degrees : 0; 14969 14970 // Normalizes the rectangle. 14971 this.degrees = $.positiveModulo(this.degrees, 360); 14972
14972var newTopLeft, newWidth; 14973 if (this.degrees >= 270) { 14974 newTopLeft = this.getTopRight(); 14975 this.x = newTopLeft.x; 14976 this.y = newTopLeft.y; 14977 newWidth = this.height; 14978 this.height = this.width; 14979 this.width = newWidth; 14980 this.degrees -= 270; 14981 } else if (this.degrees >= 180) { 14982 newTopLeft = this.getBottomRight(); 14983 this.x = newTopLeft.x; 14984 this.y = newTopLeft.y; 14985 this.degrees -= 180; 14986 } else if (this.degrees >= 90) { 14987 newTopLeft = this.getBottomLeft(); 14988 this.x = newTopLeft.x; 14989 this.y = newTopLeft.y; 14990 newWidth = this.height; 14991 this.height = this.width; 14992 this.width = newWidth; 14993 this.degrees -= 90; 14994 } 14995}; 14996 14997/** 14998 * Builds a rectangle having the 3 specified points as summits. 14999 * @static 15000 * @memberof OpenSeadragon.Rect 15001 * @param {OpenSeadragon.Point} topLeft 15002 * @param {OpenSeadragon.Point} topRight 15003 * @param {OpenSeadragon.Point} bottomLeft 15004 * @returns {OpenSeadragon.Rect} 15005 */
vendor: 10,271 bytes, lines 15006-15313
15006$.Rect.fromSummits = function(topLeft, topRight, bottomLeft) { 15007 var width = topLeft.distanceTo(topRight); 15008 var height = topLeft.distanceTo(bottomLeft); 15009 var diff = topRight.minus(topLeft); 15010 var radians = Math.atan(diff.y / diff.x); 15011 if (diff.x < 0) { 15012 radians += Math.PI; 15013 } else if (diff.y < 0) { 15014 radians += 2 * Math.PI; 15015 } 15016 return new $.Rect( 15017 topLeft.x, 15018 topLeft.y, 15019 width, 15020 height, 15021 radians / Math.PI * 180); 15022}; 15023 15024/** @lends OpenSeadragon.Rect.prototype */ 15025$.Rect.prototype = { 15026 /** 15027 * @function 15028 * @returns {OpenSeadragon.Rect} a duplicate of this Rect 15029 */ 15030 clone: function() { 15031 return new $.Rect( 15032 this.x, 15033 this.y, 15034 this.width, 15035 this.height, 15036 this.degrees); 15037 }, 15038 15039 /** 15040 * The aspect ratio is simply the ratio of width to height. 15041 * @function 15042 * @returns {Number} The ratio of width to height. 15043 */ 15044 getAspectRatio: function() { 15045 return this.width / this.height; 15046 }, 15047 15048 /** 15049 * Provides the coordinates of the upper-left corner of the rectangle as a 15050 * point. 15051 * @function 15052 * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of 15053 * the rectangle. 15054 */ 15055 getTopLeft: function() { 15056 return new $.Point( 15057 this.x, 15058 this.y 15059 ); 15060 }, 15061 15062 /** 15063 * Provides the coordinates of the bottom-right corner of the rectangle as a 15064 * point. 15065 * @function 15066 * @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of 15067 * the rectangle. 15068 */ 15069 getBottomRight: function() { 15070 return new $.Point(this.x + this.width, this.y + this.height) 15071 .rotate(this.degrees, this.getTopLeft()); 15072 }, 15073 15074 /** 15075 * Provides the coordinates of the top-right corner of the rectangle as a 15076 * point. 15077 * @function 15078 * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of 15079 * the rectangle. 15080 */ 15081 getTopRight: function() { 15082 return new $.Point(this.x + this.width, this.y) 15083 .rotate(this.degrees, this.getTopLeft()); 15084 }, 15085 15086 /** 15087 * Provides the coordinates of the bottom-left corner of the rectangle as a 15088 * point. 15089 * @function 15090 * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of 15091 * the rectangle. 15092 */ 15093 getBottomLeft: function() { 15094 return new $.Point(this.x, this.y + this.height) 15095 .rotate(this.degrees, this.getTopLeft()); 15096 }, 15097 15098 /** 15099 * Computes the center of the rectangle. 15100 * @function 15101 * @returns {OpenSeadragon.Point} The center of the rectangle as represented 15102 * as represented by a 2-dimensional vector (x,y) 15103 */ 15104 getCenter: function() { 15105 return new $.Point( 15106 this.x + this.width / 2.0, 15107 this.y + this.height / 2.0 15108 ).rotate(this.degrees, this.getTopLeft()); 15109 }, 15110 15111 /** 15112 * Returns the width and height component as a vector OpenSeadragon.Point 15113 * @function 15114 * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the 15115 * the width and height of the rectangle. 15116 */ 15117 getSize: function() { 15118 return new $.Point(this.width, this.height); 15119 }, 15120 15121 /** 15122 * Determines if two Rectangles have equivalent components. 15123 * @function 15124 * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to. 15125 * @return {Boolean} 'true' if all components are equal, otherwise 'false'. 15126 */ 15127 equals: function(other) { 15128 return (other instanceof $.Rect) && 15129 this.x === other.x && 15130 this.y === other.y && 15131 this.width === other.width && 15132 this.height === other.height && 15133 this.degrees === other.degrees; 15134 }, 15135 15136 /** 15137 * Multiply all dimensions (except degrees) in this Rect by a factor and 15138 * return a new Rect. 15139 * @function 15140 * @param {Number} factor The factor to multiply vector components. 15141 * @returns {OpenSeadragon.Rect} A new rect representing the multiplication 15142 * of the vector components by the factor 15143 */ 15144 times: function(factor) { 15145 return new $.Rect( 15146 this.x * factor, 15147 this.y * factor, 15148 this.width * factor, 15149 this.height * factor, 15150 this.degrees); 15151 }, 15152 15153 /** 15154 * Translate/move this Rect by a vector and return new Rect. 15155 * @function 15156 * @param {OpenSeadragon.Point} delta The translation vector. 15157 * @returns {OpenSeadragon.Rect} A new rect with altered position 15158 */ 15159 translate: function(delta) { 15160 return new $.Rect( 15161 this.x + delta.x, 15162 this.y + delta.y, 15163 this.width, 15164 this.height, 15165 this.degrees); 15166 }, 15167 15168 /** 15169 * Returns the smallest rectangle that will contain this and the given 15170 * rectangle bounding boxes. 15171 * @param {OpenSeadragon.Rect} rect 15172 * @return {OpenSeadragon.Rect} The new rectangle. 15173 */ 15174 union: function(rect) { 15175 var thisBoundingBox = this.getBoundingBox(); 15176 var otherBoundingBox = rect.getBoundingBox(); 15177 15178 var left = Math.min(thisBoundingBox.x, otherBoundingBox.x); 15179 var top = Math.min(thisBoundingBox.y, otherBoundingBox.y); 15180 var right = Math.max( 15181 thisBoundingBox.x + thisBoundingBox.width, 15182 otherBoundingBox.x + otherBoundingBox.width); 15183 var bottom = Math.max( 15184 thisBoundingBox.y + thisBoundingBox.height, 15185 otherBoundingBox.y + otherBoundingBox.height); 15186 15187 return new $.Rect( 15188 left, 15189 top, 15190 right - left, 15191 bottom - top); 15192 }, 15193 15194 /** 15195 * Returns the bounding box of the intersection of this rectangle with the 15196 * given rectangle. 15197 * @param {OpenSeadragon.Rect} rect 15198 * @return {OpenSeadragon.Rect} the bounding box of the intersection 15199 * or null if the rectangles don't intersect. 15200 */ 15201 intersection: function(rect) { 15202 // Simplified version of Weiler Atherton clipping algorithm 15203 // https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm 15204 // Because we just want the bounding box of the intersection, 15205 // we can just compute the bounding box of: 15206 // 1. all the summits of this which are inside rect 15207 // 2. all the summits of rect which are inside this 15208 // 3. all the intersections of rect and this 15209 var EPSILON = 0.0000000001; 15210 15211 var intersectionPoints = []; 15212 15213 var thisTopLeft = this.getTopLeft(); 15214 if (rect.containsPoint(thisTopLeft, EPSILON)) { 15215 intersectionPoints.push(thisTopLeft); 15216 } 15217 var thisTopRight = this.getTopRight(); 15218 if (rect.containsPoint(thisTopRight, EPSILON)) { 15219 intersectionPoints.push(thisTopRight); 15220 } 15221 var thisBottomLeft = this.getBottomLeft(); 15222 if (rect.containsPoint(thisBottomLeft, EPSILON)) { 15223 intersectionPoints.push(thisBottomLeft); 15224 } 15225 var thisBottomRight = this.getBottomRight(); 15226 if (rect.containsPoint(thisBottomRight, EPSILON)) { 15227 intersectionPoints.push(thisBottomRight); 15228 } 15229 15230 var rectTopLeft = rect.getTopLeft(); 15231 if (this.containsPoint(rectTopLeft, EPSILON)) { 15232 intersectionPoints.push(rectTopLeft); 15233 } 15234 var rectTopRight = rect.getTopRight(); 15235 if (this.containsPoint(rectTopRight, EPSILON)) { 15236 intersectionPoints.push(rectTopRight); 15237 } 15238 var rectBottomLeft = rect.getBottomLeft(); 15239 if (this.containsPoint(rectBottomLeft, EPSILON)) { 15240 intersectionPoints.push(rectBottomLeft); 15241 } 15242 var rectBottomRight = rect.getBottomRight(); 15243 if (this.containsPoint(rectBottomRight, EPSILON)) { 15244 intersectionPoints.push(rectBottomRight); 15245 } 15246 15247 var thisSegments = this._getSegments(); 15248 var rectSegments = rect._getSegments(); 15249 for (var i = 0; i < thisSegments.length; i++) { 15250 var thisSegment = thisSegments[i]; 15251 for (var j = 0; j < rectSegments.length; j++) { 15252 var rectSegment = rectSegments[j]; 15253 var intersect = getIntersection(thisSegment[0], thisSegment[1], 15254 rectSegment[0], rectSegment[1]); 15255 if (intersect) { 15256 intersectionPoints.push(intersect); 15257 } 15258 } 15259 } 15260 15261 // Get intersection point of segments [a,b] and [c,d] 15262 function getIntersection(a, b, c, d) { 15263 // http://stackoverflow.com/a/1968345/1440403 15264 var abVector = b.minus(a); 15265 var cdVector = d.minus(c); 15266 15267 var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y; 15268 if (denom === 0) { 15269 return null; 15270 } 15271 15272 var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom; 15273 var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom; 15274 15275 if (-EPSILON <= s && s <= 1 - EPSILON && 15276 -EPSILON <= t && t <= 1 - EPSILON) { 15277 return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y); 15278 } 15279 return null; 15280 } 15281 15282 if (intersectionPoints.length === 0) { 15283 return null; 15284 } 15285 15286 var minX = intersectionPoints[0].x; 15287 var maxX = intersectionPoints[0].x; 15288 var minY = intersectionPoints[0].y; 15289 var maxY = intersectionPoints[0].y; 15290 for (var k = 1; k < intersectionPoints.length; k++) { 15291 var point = intersectionPoints[k]; 15292 if (point.x < minX) { 15293 minX = point.x; 15294 } 15295 if (point.x > maxX) { 15296 maxX = point.x; 15297 } 15298 if (point.y < minY) { 15299 minY = point.y; 15300 } 15301 if (point.y > maxY) { 15302 maxY = point.y; 15303 } 15304 } 15305 return new $.Rect(minX, minY, maxX - minX, maxY - minY); 15306 }, 15307 15308 // private 15309 _getSegments: function() { 15310 var topLeft = this.getTopLeft(); 15311 var topRight = this.getTopRight(); 15312 var bottomLeft = this.getBottomLeft(); 15313 var bottomRight = this.getBottomRight();
15314 return [[topLeft, topRight], 15315 [topRight, bottomRight], 15316 [bottomRight, bottomLeft], 15317 [bottomLeft, topLeft]]; 15318 }, 15319 15320 /** 15321 * Rotates a rectangle around a point. 15322 * @function 15323 * @param {Number} degrees The angle in degrees to rotate. 15324 * @param {OpenSeadragon.Point} [pivot] The point about which to rotate. 15325 * Defaults to the center of the rectangle. 15326 * @return {OpenSeadragon.Rect} 15327 */ 15328 rotate: function(degrees, pivot) { 15329 degrees = $.positiveModulo(degrees, 360); 15330 if (degrees === 0) { 15331 return this.clone(); 15332 } 15333 15334 pivot = pivot || this.getCenter(); 15335 var newTopLeft = this.getTopLeft().rotate(degrees, pivot); 15336 var newTopRight = this.getTopRight().rotate(degrees, pivot); 15337 15338 var diff = newTopRight.minus(newTopLeft); 15339 // Handle floating point error 15340 diff = diff.apply(function(x) { 15341 var EPSILON = 1e-15; 15342 return Math.abs(x) < EPSILON ? 0 : x; 15343 }); 15344 var radians = Math.atan(diff.y / diff.x); 15345 if (diff.x < 0) { 15346 radians += Math.PI; 15347 } else if (diff.y < 0) { 15348 radians += 2 * Math.PI; 15349 } 15350 return new $.Rect( 15351 newTopLeft.x, 15352 newTopLeft.y, 15353 this.width, 15354 this.height, 15355 radians / Math.PI * 180); 15356 }, 15357 15358 /** 15359 * Retrieves the smallest horizontal (degrees=0) rectangle which contains 15360 * this rectangle. 15361 * @returns {OpenSeadragon.Rect} 15362 */ 15363 getBoundingBox: function() { 15364 if (this.degrees === 0) { 15365 return this.clone(); 15366 } 15367 var topLeft = this.getTopLeft(); 15368 var topRight = this.getTopRight(); 15369 var bottomLeft = this.getBottomLeft(); 15370 var bottomRight = this.getBottomRight(); 15371 var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); 15372 var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); 15373 var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); 15374 var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); 15375 return new $.Rect( 15376 minX, 15377 minY, 15378 maxX - minX, 15379 maxY - minY); 15380 }, 15381 15382 /** 15383 * Retrieves the smallest horizontal (degrees=0) rectangle which contains 15384 * this rectangle and has integers x, y, width and height 15385 * @returns {OpenSeadragon.Rect} 15386 */ 15387 getIntegerBoundingBox: function() { 15388 var boundingBox = this.getBoundingBox(); 15389 var x = Math.floor(boundingBox.x); 15390 var y = Math.floor(boundingBox.y); 15391 var width = Math.ceil(boundingBox.width + boundingBox.x - x); 15392 var height = Math.ceil(boundingBox.height + boundingBox.y - y); 15393 return new $.Rect(x, y, width, height); 15394 }, 15395 15396 /** 15397 * Determines whether a point is inside this rectangle (edge included). 15398 * @function 15399 * @param {OpenSeadragon.Point} point 15400 * @param {Number} [epsilon=0] the margin of error allowed 15401 * @returns {Boolean} true if the point is inside this rectangle, false 15402 * otherwise. 15403 */ 15404 containsPoint: function(point, epsilon) { 15405 epsilon = epsilon || 0; 15406 15407 // See http://stackoverflow.com/a/2752754/1440403 for explanation 15408 var topLeft = this.getTopLeft(); 15409 var topRight = this.getTopRight(); 15410 var bottomLeft = this.getBottomLeft(); 15411 var topDiff = topRight.minus(topLeft); 15412 var leftDiff = bottomLeft.minus(topLeft); 15413 15414 return ((point.x - topLeft.x) * topDiff.x + 15415 (point.y - topLeft.y) * topDiff.y >= -epsilon) && 15416 15417 ((point.x - topRight.x) * topDiff.x + 15418 (point.y - topRight.y) * topDiff.y <= epsilon) && 15419 15420 ((point.x - topLeft.x) * leftDiff.x + 15421 (point.y - topLeft.y) * leftDiff.y >= -epsilon) && 15422 15423 ((point.x - bottomLeft.x) * leftDiff.x + 15424 (point.y - bottomLeft.y) * leftDiff.y <= epsilon); 15425 }, 15426 15427 /** 15428 * Provides a string representation of the rectangle which is useful for 15429 * debugging. 15430 * @function 15431 * @returns {String} A string representation of the rectangle. 15432 */ 15433 toString: function() { 15434 return "[" + 15435 (Math.round(this.x * 100) / 100) + ", " + 15436 (Math.round(this.y * 100) / 100) + ", " + 15437 (Math.round(this.width * 100) / 100) + "x" + 15438 (Math.round(this.height * 100) / 100) + ", " + 15439 (Math.round(this.degrees * 100) / 100) + "deg" + 15440 "]"; 15441 } 15442}; 15443 15444 15445}(OpenSeadragon)); 15446 15447/* 15448 * OpenSeadragon - ReferenceStrip 15449 * 15450 * Copyright (C) 2009 CodePlex Foundation 15451 * Copyright (C) 2010-2013 OpenSeadragon contributors 15452 * 15453 * Redistribution and use in source and binary forms, with or without 15454 * modification, are permitted provided that the following conditions are 15455 * met: 15456 * 15457 * - Redistributions of source code must retain the above copyright notice, 15458 * this list of conditions and the following disclaimer. 15459 * 15460 * - Redistributions in binary form must reproduce the above copyright 15461 * notice, this list of conditions and the following disclaimer in the 15462 * documentation and/or other materials provided with the distribution. 15463 * 15464 * - Neither the name of CodePlex Foundation nor the names of its 15465 * contributors may be used to endorse or promote products derived from 15466 * this software without specific prior written permission. 15467 * 15468 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15469 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15470 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15471 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 15472 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
15473 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 15474 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 15475 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 15476 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 15477 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 15478 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15479 */ 15480 15481(function ( $ ) { 15482 15483// dictionary from id to private properties 15484var THIS = {}; 15485 15486/** 15487 * The CollectionDrawer is a reimplementation if the Drawer API that 15488 * focuses on allowing a viewport to be redefined as a collection 15489 * of smaller viewports, defined by a clear number of rows and / or 15490 * columns of which each item in the matrix of viewports has its own 15491 * source. 15492 * 15493 * This idea is a reexpression of the idea of dzi collections 15494 * which allows a clearer algorithm to reuse the tile sources already 15495 * supported by OpenSeadragon, in heterogenious or homogenious 15496 * sequences just like mixed groups already supported by the viewer 15497 * for the purpose of image sequnces. 15498 * 15499 * TODO: The difficult part of this feature is figuring out how to express 15500 * this functionality as a combination of the functionality already 15501 * provided by Drawer, Viewport, TileSource, and Navigator. It may 15502 * require better abstraction at those points in order to efficiently 15503 * reuse those paradigms. 15504 */ 15505/** 15506 * @class ReferenceStrip 15507 * @memberof OpenSeadragon 15508 * @param {Object} options 15509 */ 15510$.ReferenceStrip = function ( options ) { 15511 15512 var _this = this, 15513 viewer = options.viewer,
15514 viewerSize = $.getElementSize( viewer.element ), 15515 element, 15516 style, 15517 i; 15518 15519 //We may need to create a new element and id if they did not 15520 //provide the id for the existing element 15521 if ( !options.id ) { 15522 options.id = 'referencestrip-' + $.now(); 15523 this.element = $.makeNeutralElement( "div" ); 15524 this.element.id = options.id; 15525 this.element.className = 'referencestrip'; 15526 } 15527 15528 options = $.extend( true, { 15529 sizeRatio: $.DEFAULT_SETTINGS.referenceStripSizeRatio, 15530 position: $.DEFAULT_SETTINGS.referenceStripPosition, 15531 scroll: $.DEFAULT_SETTINGS.referenceStripScroll, 15532 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold 15533 }, options, { 15534 //required overrides 15535 element: this.element, 15536 //These need to be overridden to prevent recursion since 15537 //the navigator is a viewer and a viewer has a navigator 15538 showNavigator: false, 15539 mouseNavEnabled: false, 15540 showNavigationControl: false, 15541 showSequenceControl: false 15542 } ); 15543 15544 $.extend( this, options ); 15545 //Private state properties 15546 THIS[this.id] = { 15547 "animating": false 15548 }; 15549 15550 this.minPixelRatio = this.viewer.minPixelRatio; 15551 15552 style = this.element.style; 15553 style.marginTop = '0px'; 15554 style.marginRight = '0px'; 15555 style.marginBottom = '0px'; 15556 style.marginLeft = '0px'; 15557 style.left = '0px'; 15558 style.bottom = '0px'; 15559 style.border = '0px'; 15560 style.background = '#000'; 15561 style.position = 'relative'; 15562 15563 $.setElementTouchActionNone( this.element ); 15564 15565 $.setElementOpacity( this.element, 0.8 ); 15566 15567 this.viewer = viewer; 15568 this.innerTracker = new $.MouseTracker( { 15569 element: this.element, 15570 dragHandler: $.delegate( this, onStripDrag ), 15571 scrollHandler: $.delegate( this, onStripScroll ), 15572 enterHandler: $.delegate( this, onStripEnter ), 15573 exitHandler: $.delegate( this, onStripExit ), 15574 keyDownHandler: $.delegate( this, onKeyDown ), 15575 keyHandler: $.delegate( this, onKeyPress ) 15576 } ); 15577 15578 //Controls the position and orientation of the reference strip and sets the 15579 //appropriate width and height 15580 if ( options.width && options.height ) { 15581 this.element.style.width = options.width + 'px'; 15582 this.element.style.height = options.height + 'px'; 15583 viewer.addControl( 15584 this.element, 15585 { anchor: $.ControlAnchor.BOTTOM_LEFT } 15586 ); 15587 } else { 15588 if ( "horizontal" == options.scroll ) { 15589 this.element.style.width = (
15590 viewerSize.x * 15591 options.sizeRatio * 15592 viewer.tileSources.length 15593 ) + ( 12 * viewer.tileSources.length ) + 'px'; 15594 15595 this.element.style.height = ( 15596 viewerSize.y * 15597 options.sizeRatio 15598 ) + 'px'; 15599 15600 viewer.addControl( 15601 this.element, 15602 { anchor: $.ControlAnchor.BOTTOM_LEFT } 15603 ); 15604 } else { 15605 this.element.style.height = ( 15606 viewerSize.y * 15607 options.sizeRatio * 15608 viewer.tileSources.length 15609 ) + ( 12 * viewer.tileSources.length ) + 'px'; 15610 15611 this.element.style.width = ( 15612 viewerSize.x * 15613 options.sizeRatio 15614 ) + 'px'; 15615 15616 viewer.addControl( 15617 this.element, 15618 { anchor: $.ControlAnchor.TOP_LEFT } 15619 ); 15620 15621 } 15622 } 15623 15624 this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8; 15625 this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8; 15626 this.panels = []; 15627 this.miniViewers = {}; 15628 15629 /*jshint loopfunc:true*/ 15630 for ( i = 0; i < viewer.tileSources.length; i++ ) { 15631 15632 element = $.makeNeutralElement( 'div' ); 15633 element.id = this.element.id + "-" + i; 15634 15635 element.style.width = _this.panelWidth + 'px'; 15636 element.style.height = _this.panelHeight + 'px'; 15637 element.style.display = 'inline'; 15638 element.style.float = 'left'; //Webkit 15639 element.style.cssFloat = 'left'; //Firefox 15640 element.style.styleFloat = 'left'; //IE 15641 element.style.padding = '2px'; 15642 $.setElementTouchActionNone( element ); 15643 15644 element.innerTracker = new $.MouseTracker( { 15645 element: element, 15646 clickTimeThreshold: this.clickTimeThreshold, 15647 clickDistThreshold: this.clickDistThreshold, 15648 pressHandler: function ( event ) { 15649 event.eventSource.dragging = $.now(); 15650 }, 15651 releaseHandler: function ( event ) { 15652 var tracker = event.eventSource, 15653 id = tracker.element.id, 15654 page = Number( id.split( '-' )[2] ), 15655 now = $.now(); 15656 15657 if ( event.insideElementPressed && 15658 event.insideElementReleased && 15659 tracker.dragging && 15660 ( now - tracker.dragging ) < tracker.clickTimeThreshold ) { 15661 tracker.dragging = null; 15662 viewer.goToPage( page ); 15663 } 15664 } 15665 } ); 15666 15667 this.element.appendChild( element ); 15668 15669 element.activePanel = false; 15670 15671 this.panels.push( element ); 15672 15673 } 15674 loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.x, 0 ); 15675 this.setFocus( 0 ); 15676 15677}; 15678 15679$.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.prototype */{ 15680 15681 /** 15682 * @function 15683 */ 15684 setFocus: function ( page ) { 15685 var element = this.element.querySelector('#' + this.element.id + '-' + page ),
15686 viewerSize = $.getElementSize( this.viewer.canvas ), 15687 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 15688 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ), 15689 offsetLeft = -Number( this.element.style.marginLeft.replace( 'px', '' ) ), 15690 offsetTop = -Number( this.element.style.marginTop.replace( 'px', '' ) ), 15691 offset; 15692 15693 if ( this.currentSelected !== element ) { 15694 if ( this.currentSelected ) { 15695 this.currentSelected.style.background = '#000'; 15696 } 15697 this.currentSelected = element; 15698 this.currentSelected.style.background = '#999'; 15699 15700 if ( 'horizontal' == this.scroll ) { 15701 //right left 15702 offset = ( Number( page ) ) * ( this.panelWidth + 3 ); 15703 if ( offset > offsetLeft + viewerSize.x - this.panelWidth ) { 15704 offset = Math.min( offset, ( scrollWidth - viewerSize.x ) ); 15705 this.element.style.marginLeft = -offset + 'px'; 15706 loadPanels( this, viewerSize.x, -offset ); 15707 } else if ( offset < offsetLeft ) { 15708 offset = Math.max( 0, offset - viewerSize.x / 2 ); 15709 this.element.style.marginLeft = -offset + 'px'; 15710 loadPanels( this, viewerSize.x, -offset ); 15711 } 15712 } else { 15713 offset = ( Number( page ) ) * ( this.panelHeight + 3 ); 15714 if ( offset > offsetTop + viewerSize.y - this.panelHeight ) { 15715 offset = Math.min( offset, ( scrollHeight - viewerSize.y ) ); 15716 this.element.style.marginTop = -offset + 'px'; 15717 loadPanels( this, viewerSize.y, -offset ); 15718 } else if ( offset < offsetTop ) { 15719 offset = Math.max( 0, offset - viewerSize.y / 2 ); 15720 this.element.style.marginTop = -offset + 'px'; 15721 loadPanels( this, viewerSize.y, -offset ); 15722 } 15723 } 15724 15725 this.currentPage = page; 15726 onStripEnter.call( this, { eventSource: this.innerTracker } ); 15727 } 15728 }, 15729 15730 /** 15731 * @function 15732 */ 15733 update: function () { 15734 if ( THIS[this.id].animating ) { 15735 $.console.log( 'image reference strip update' ); 15736 return true; 15737 } 15738 return false; 15739 }, 15740 15741 // Overrides Viewer.destroy 15742 destroy: function() { 15743 if (this.miniViewers) { 15744 for (var key in this.miniViewers) { 15745 this.miniViewers[key].destroy(); 15746 } 15747 } 15748 15749 if (this.element) { 15750 this.element.parentNode.removeChild(this.element); 15751 } 15752 } 15753 15754} ); 15755 15756 15757 15758 15759/** 15760 * @private 15761 * @inner 15762 * @function 15763 */ 15764function onStripDrag( event ) { 15765 15766 var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), 15767 offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), 15768 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 15769 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
15770 viewerSize = $.getElementSize( this.viewer.canvas ); 15771 this.dragging = true; 15772 if ( this.element ) { 15773 if ( 'horizontal' == this.scroll ) { 15774 if ( -event.delta.x > 0 ) { 15775 //forward 15776 if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) { 15777 this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px'; 15778 loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) ); 15779 } 15780 } else if ( -event.delta.x < 0 ) { 15781 //reverse 15782 if ( offsetLeft < 0 ) { 15783 this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px'; 15784 loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) ); 15785 } 15786 } 15787 } else { 15788 if ( -event.delta.y > 0 ) { 15789 //forward 15790 if ( offsetTop > -( scrollHeight - viewerSize.y ) ) { 15791 this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px'; 15792 loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) ); 15793 } 15794 } else if ( -event.delta.y < 0 ) { 15795 //reverse 15796 if ( offsetTop < 0 ) { 15797 this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px'; 15798 loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) ); 15799 } 15800 } 15801 } 15802 } 15803 return false; 15804 15805} 15806 15807 15808 15809/** 15810 * @private 15811 * @inner 15812 * @function 15813 */ 15814function onStripScroll( event ) { 15815 var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), 15816 offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), 15817 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 15818 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
vendor: 3,308 bytes, lines 15819-15898
15819 viewerSize = $.getElementSize( this.viewer.canvas ); 15820 if ( this.element ) { 15821 if ( 'horizontal' == this.scroll ) { 15822 if ( event.scroll > 0 ) { 15823 //forward 15824 if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) { 15825 this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px'; 15826 loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) ); 15827 } 15828 } else if ( event.scroll < 0 ) { 15829 //reverse 15830 if ( offsetLeft < 0 ) { 15831 this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px'; 15832 loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) ); 15833 } 15834 } 15835 } else { 15836 if ( event.scroll < 0 ) { 15837 //scroll up 15838 if ( offsetTop > viewerSize.y - scrollHeight ) { 15839 this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px'; 15840 loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) ); 15841 } 15842 } else if ( event.scroll > 0 ) { 15843 //scroll dowm 15844 if ( offsetTop < 0 ) { 15845 this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px'; 15846 loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) ); 15847 } 15848 } 15849 } 15850 } 15851 //cancels event 15852 return false; 15853} 15854 15855 15856function loadPanels( strip, viewerSize, scroll ) { 15857 var panelSize, 15858 activePanelsStart, 15859 activePanelsEnd, 15860 miniViewer, 15861 style, 15862 i, 15863 element; 15864 if ( 'horizontal' == strip.scroll ) { 15865 panelSize = strip.panelWidth; 15866 } else { 15867 panelSize = strip.panelHeight; 15868 } 15869 activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5; 15870 activePanelsEnd = Math.ceil( ( Math.abs( scroll ) + viewerSize ) / panelSize ) + 1; 15871 activePanelsStart = activePanelsEnd - activePanelsStart; 15872 activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart; 15873 15874 for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) { 15875 element = strip.panels[i]; 15876 if ( !element.activePanel ) { 15877 var miniTileSource; 15878 var originalTileSource = strip.viewer.tileSources[i]; 15879 if (originalTileSource.referenceStripThumbnailUrl) { 15880 miniTileSource = { 15881 type: 'image', 15882 url: originalTileSource.referenceStripThumbnailUrl 15883 }; 15884 } else { 15885 miniTileSource = originalTileSource; 15886 } 15887 miniViewer = new $.Viewer( { 15888 id: element.id, 15889 tileSources: [miniTileSource], 15890 element: element, 15891 navigatorSizeRatio: strip.sizeRatio, 15892 showNavigator: false, 15893 mouseNavEnabled: false, 15894 showNavigationControl: false, 15895 showSequenceControl: false, 15896 immediateRender: true, 15897 blendTime: 0, 15898 animationTime: 0,
15899 loadTilesWithAjax: strip.viewer.loadTilesWithAjax, 15900 ajaxHeaders: strip.viewer.ajaxHeaders, 15901 useCanvas: strip.useCanvas 15902 } ); 15903 15904 miniViewer.displayRegion = $.makeNeutralElement( "div" ); 15905 miniViewer.displayRegion.id = element.id + '-displayregion'; 15906 miniViewer.displayRegion.className = 'displayregion'; 15907 15908 style = miniViewer.displayRegion.style; 15909 style.position = 'relative'; 15910 style.top = '0px'; 15911 style.left = '0px'; 15912 style.fontSize = '0px'; 15913 style.overflow = 'hidden'; 15914 style.float = 'left'; //Webkit 15915 style.cssFloat = 'left'; //Firefox 15916 style.styleFloat = 'left'; //IE 15917 style.zIndex = 999999999; 15918 style.cursor = 'default'; 15919 style.width = ( strip.panelWidth - 4 ) + 'px'; 15920 style.height = ( strip.panelHeight - 4 ) + 'px'; 15921 15922 // TODO: What is this for? Future keyboard navigation support? 15923 miniViewer.displayRegion.innerTracker = new $.MouseTracker( { 15924 element: miniViewer.displayRegion, 15925 startDisabled: true 15926 } ); 15927 15928 element.getElementsByTagName( 'div' )[0].appendChild( 15929 miniViewer.displayRegion 15930 ); 15931 15932 strip.miniViewers[element.id] = miniViewer; 15933 15934 element.activePanel = true; 15935 } 15936 } 15937} 15938 15939 15940/** 15941 * @private 15942 * @inner 15943 * @function 15944 */ 15945function onStripEnter( event ) { 15946 var element = event.eventSource.element; 15947 15948 //$.setElementOpacity(element, 0.8); 15949 15950 //element.style.border = '1px solid #555'; 15951 //element.style.background = '#000'; 15952 15953 if ( 'horizontal' == this.scroll ) { 15954 15955 //element.style.paddingTop = "0px"; 15956 element.style.marginBottom = "0px"; 15957 15958 } else { 15959 15960 //element.style.paddingRight = "0px"; 15961 element.style.marginLeft = "0px"; 15962 15963 } 15964 return false; 15965} 15966 15967 15968/** 15969 * @private 15970 * @inner 15971 * @function 15972 */ 15973function onStripExit( event ) { 15974 var element = event.eventSource.element; 15975 15976 if ( 'horizontal' == this.scroll ) { 15977 15978 //element.style.paddingTop = "10px"; 15979 element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px"; 15980 15981 } else { 15982 15983 //element.style.paddingRight = "10px"; 15984 element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px"; 15985 15986 } 15987 return false; 15988} 15989 15990 15991/** 15992 * @private 15993 * @inner 15994 * @function 15995 */ 15996function onKeyDown( event ) { 15997 //console.log( event.keyCode ); 15998 15999 if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { 16000 switch ( event.keyCode ) { 16001 case 38: //up arrow 16002 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 16003 return false; 16004 case 40: //down arrow 16005 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 16006 return false; 16007 case 37: //left arrow 16008 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 16009 return false; 16010 case 39: //right arrow 16011 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 16012 return false; 16013 default: 16014 //console.log( 'navigator keycode %s', event.keyCode ); 16015 return true; 16016 } 16017 } else { 16018 return true; 16019 } 16020} 16021 16022 16023/** 16024 * @private 16025 * @inner 16026 * @function 16027 */ 16028function onKeyPress( event ) { 16029 //console.log( event.keyCode ); 16030 16031 if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { 16032 switch ( event.keyCode ) { 16033 case 61: //=|+ 16034 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 16035 return false;
16036 case 45: //-|_ 16037 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 16038 return false; 16039 case 48: //0|) 16040 case 119: //w 16041 case 87: //W 16042 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 16043 return false; 16044 case 115: //s 16045 case 83: //S 16046 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 16047 return false; 16048 case 97: //a 16049 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 16050 return false; 16051 case 100: //d 16052 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 16053 return false; 16054 default: 16055 //console.log( 'navigator keycode %s', event.keyCode ); 16056 return true; 16057 } 16058 } else { 16059 return true; 16060 } 16061} 16062 16063}(OpenSeadragon)); 16064 16065/* 16066 * OpenSeadragon - DisplayRect 16067 * 16068 * Copyright (C) 2009 CodePlex Foundation 16069 * Copyright (C) 2010-2013 OpenSeadragon contributors 16070 * 16071 * Redistribution and use in source and binary forms, with or without 16072 * modification, are permitted provided that the following conditions are 16073 * met: 16074 * 16075 * - Redistributions of source code must retain the above copyright notice, 16076 * this list of conditions and the following disclaimer. 16077 * 16078 * - Redistributions in binary form must reproduce the above copyright 16079 * notice, this list of conditions and the following disclaimer in the 16080 * documentation and/or other materials provided with the distribution. 16081 * 16082 * - Neither the name of CodePlex Foundation nor the names of its 16083 * contributors may be used to endorse or promote products derived from 16084 * this software without specific prior written permission. 16085 * 16086 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16087 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16088 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16089 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16090 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 16091 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16092 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 16093 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 16094 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 16095 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 16096 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16097 */ 16098 16099(function( $ ){ 16100 16101/** 16102 * @class DisplayRect 16103 * @classdesc A display rectangle is very similar to {@link OpenSeadragon.Rect} but adds two 16104 * fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels 16105 * for this rectangle. 16106 * 16107 * @memberof OpenSeadragon 16108 * @extends OpenSeadragon.Rect 16109 * @param {Number} x The vector component 'x'. 16110 * @param {Number} y The vector component 'y'. 16111 * @param {Number} width The vector component 'height'. 16112 * @param {Number} height The vector component 'width'. 16113 * @param {Number} minLevel The lowest zoom level supported. 16114 * @param {Number} maxLevel The highest zoom level supported. 16115 */ 16116$.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) { 16117 $.Rect.apply( this, [ x, y, width, height ] ); 16118 16119 /** 16120 * The lowest zoom level supported. 16121 * @member {Number} minLevel 16122 * @memberof OpenSeadragon.DisplayRect# 16123 */ 16124 this.minLevel = minLevel; 16125 /** 16126 * The highest zoom level supported. 16127 * @member {Number} maxLevel 16128 * @memberof OpenSeadragon.DisplayRect# 16129 */ 16130 this.maxLevel = maxLevel; 16131}; 16132 16133$.extend( $.DisplayRect.prototype, $.Rect.prototype ); 16134 16135}( OpenSeadragon )); 16136 16137/* 16138 * OpenSeadragon - Spring 16139 * 16140 * Copyright (C) 2009 CodePlex Foundation 16141 * Copyright (C) 2010-2013 OpenSeadragon contributors 16142 * 16143 * Redistribution and use in source and binary forms, with or without 16144 * modification, are permitted provided that the following conditions are 16145 * met: 16146 * 16147 * - Redistributions of source code must retain the above copyright notice, 16148 * this list of conditions and the following disclaimer. 16149 * 16150 * - Redistributions in binary form must reproduce the above copyright 16151 * notice, this list of conditions and the following disclaimer in the 16152 * documentation and/or other materials provided with the distribution. 16153 * 16154 * - Neither the name of CodePlex Foundation nor the names of its 16155 * contributors may be used to endorse or promote products derived from 16156 * this software without specific prior written permission. 16157 * 16158 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16159 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16160 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16161 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16162 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16163 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16164 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 16165 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 16166 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 16167 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 16168 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16169 */ 16170 16171(function( $ ){ 16172 16173/** 16174 * @class Spring 16175 * @memberof OpenSeadragon 16176 * @param {Object} options - Spring configuration settings. 16177 * @param {Number} options.springStiffness - Spring stiffness. Must be greater than zero. 16178 * The closer to zero, the closer to linear animation. 16179 * @param {Number} options.animationTime - Animation duration per spring, in seconds. 16180 * Must be zero or greater. 16181 * @param {Number} [options.initial=0] - Initial value of spring. 16182 * @param {Boolean} [options.exponential=false] - Whether this spring represents 16183 * an exponential scale (such as zoom) and should be animated accordingly. Note that 16184 * exponential springs must have non-zero values. 16185 */ 16186$.Spring = function( options ) { 16187 var args = arguments; 16188 16189 if( typeof ( options ) != 'object' ){ 16190 //allows backward compatible use of ( initialValue, config ) as 16191 //constructor parameters 16192 options = { 16193 initial: args.length && typeof ( args[ 0 ] ) == "number" ? 16194 args[ 0 ] : 16195 undefined, 16196 /** 16197 * Spring stiffness. 16198 * @member {Number} springStiffness 16199 * @memberof OpenSeadragon.Spring# 16200 */ 16201 springStiffness: args.length > 1 ? 16202 args[ 1 ].springStiffness : 16203 5.0, 16204 /** 16205 * Animation duration per spring. 16206 * @member {Number} animationTime 16207 * @memberof OpenSeadragon.Spring# 16208 */ 16209 animationTime: args.length > 1 ? 16210 args[ 1 ].animationTime : 16211 1.5 16212 }; 16213 } 16214 16215 $.console.assert(typeof options.springStiffness === "number" && options.springStiffness !== 0, 16216 "[OpenSeadragon.Spring] options.springStiffness must be a non-zero number"); 16217 16218 $.console.assert(typeof options.animationTime === "number" && options.animationTime >= 0, 16219 "[OpenSeadragon.Spring] options.animationTime must be a number greater than or equal to 0"); 16220 16221 if (options.exponential) { 16222 this._exponential = true; 16223 delete options.exponential; 16224 } 16225 16226 $.extend( true, this, options); 16227 16228 /** 16229 * @member {Object} current 16230 * @memberof OpenSeadragon.Spring# 16231 * @property {Number} value 16232 * @property {Number} time 16233 */ 16234 this.current = { 16235 value: typeof ( this.initial ) == "number" ? 16236 this.initial : 16237 (this._exponential ? 0 : 1), 16238 time: $.now() // always work in milliseconds 16239 }; 16240 16241 $.console.assert(!this._exponential || this.current.value !== 0, 16242 "[OpenSeadragon.Spring] value must be non-zero for exponential springs"); 16243 16244 /** 16245 * @member {Object} start 16246 * @memberof OpenSeadragon.Spring# 16247 * @property {Number} value 16248 * @property {Number} time 16249 */ 16250 this.start = { 16251 value: this.current.value, 16252 time: this.current.time 16253 }; 16254 16255 /** 16256 * @member {Object} target 16257 * @memberof OpenSeadragon.Spring# 16258 * @property {Number} value 16259 * @property {Number} time 16260 */ 16261 this.target = { 16262 value: this.current.value, 16263 time: this.current.time 16264 }; 16265 16266 if (this._exponential) { 16267 this.start._logValue = Math.log(this.start.value); 16268 this.target._logValue = Math.log(this.target.value); 16269 this.current._logValue = Math.log(this.current.value); 16270 } 16271}; 16272 16273/** @lends OpenSeadragon.Spring.prototype */ 16274$.Spring.prototype = { 16275 16276 /** 16277 * @function 16278 * @param {Number} target 16279 */ 16280 resetTo: function( target ) { 16281 $.console.assert(!this._exponential || target !== 0, 16282 "[OpenSeadragon.Spring.resetTo] target must be non-zero for exponential springs"); 16283 16284 this.start.value = this.target.value = this.current.value = target; 16285 this.start.time = this.target.time = this.current.time = $.now(); 16286 16287 if (this._exponential) {
vendor: 4,653 bytes, lines 16288-16424
16288 this.start._logValue = Math.log(this.start.value); 16289 this.target._logValue = Math.log(this.target.value); 16290 this.current._logValue = Math.log(this.current.value); 16291 } 16292 }, 16293 16294 /** 16295 * @function 16296 * @param {Number} target 16297 */ 16298 springTo: function( target ) { 16299 $.console.assert(!this._exponential || target !== 0, 16300 "[OpenSeadragon.Spring.springTo] target must be non-zero for exponential springs"); 16301 16302 this.start.value = this.current.value; 16303 this.start.time = this.current.time; 16304 this.target.value = target; 16305 this.target.time = this.start.time + 1000 * this.animationTime; 16306 16307 if (this._exponential) { 16308 this.start._logValue = Math.log(this.start.value); 16309 this.target._logValue = Math.log(this.target.value); 16310 } 16311 }, 16312 16313 /** 16314 * @function 16315 * @param {Number} delta 16316 */ 16317 shiftBy: function( delta ) { 16318 this.start.value += delta; 16319 this.target.value += delta; 16320 16321 if (this._exponential) { 16322 $.console.assert(this.target.value !== 0 && this.start.value !== 0, 16323 "[OpenSeadragon.Spring.shiftBy] spring value must be non-zero for exponential springs"); 16324 16325 this.start._logValue = Math.log(this.start.value); 16326 this.target._logValue = Math.log(this.target.value); 16327 } 16328 }, 16329 16330 setExponential: function(value) { 16331 this._exponential = value; 16332 16333 if (this._exponential) { 16334 $.console.assert(this.current.value !== 0 && this.target.value !== 0 && this.start.value !== 0, 16335 "[OpenSeadragon.Spring.setExponential] spring value must be non-zero for exponential springs"); 16336 16337 this.start._logValue = Math.log(this.start.value); 16338 this.target._logValue = Math.log(this.target.value); 16339 this.current._logValue = Math.log(this.current.value); 16340 } 16341 }, 16342 16343 /** 16344 * @function 16345 * @returns true if the value got updated, false otherwise 16346 */ 16347 update: function() { 16348 this.current.time = $.now(); 16349 16350 var startValue, targetValue; 16351 if (this._exponential) { 16352 startValue = this.start._logValue; 16353 targetValue = this.target._logValue; 16354 } else { 16355 startValue = this.start.value; 16356 targetValue = this.target.value; 16357 } 16358 16359 var currentValue = (this.current.time >= this.target.time) ? 16360 targetValue : 16361 startValue + 16362 ( targetValue - startValue ) * 16363 transform( 16364 this.springStiffness, 16365 ( this.current.time - this.start.time ) / 16366 ( this.target.time - this.start.time ) 16367 ); 16368 16369 var oldValue = this.current.value; 16370 if (this._exponential) { 16371 this.current.value = Math.exp(currentValue); 16372 } else { 16373 this.current.value = currentValue; 16374 } 16375 16376 return oldValue != this.current.value; 16377 }, 16378 16379 /** 16380 * Returns whether the spring is at the target value 16381 * @function 16382 * @returns {Boolean} True if at target value, false otherwise 16383 */ 16384 isAtTargetValue: function() { 16385 return this.current.value === this.target.value; 16386 } 16387}; 16388 16389/** 16390 * @private 16391 */ 16392function transform( stiffness, x ) { 16393 return ( 1.0 - Math.exp( stiffness * -x ) ) / 16394 ( 1.0 - Math.exp( -stiffness ) ); 16395} 16396 16397}( OpenSeadragon )); 16398 16399/* 16400 * OpenSeadragon - ImageLoader 16401 * 16402 * Copyright (C) 2009 CodePlex Foundation 16403 * Copyright (C) 2010-2013 OpenSeadragon contributors 16404 16405 * Redistribution and use in source and binary forms, with or without 16406 * modification, are permitted provided that the following conditions are 16407 * met: 16408 * 16409 * - Redistributions of source code must retain the above copyright notice, 16410 * this list of conditions and the following disclaimer. 16411 * 16412 * - Redistributions in binary form must reproduce the above copyright 16413 * notice, this list of conditions and the following disclaimer in the 16414 * documentation and/or other materials provided with the distribution. 16415 * 16416 * - Neither the name of CodePlex Foundation nor the names of its 16417 * contributors may be used to endorse or promote products derived from 16418 * this software without specific prior written permission. 16419 * 16420 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16421 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16422 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16423 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16424 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16425 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16426 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 16427 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 16428 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 16429 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 16430 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16431 */ 16432 16433(function($){ 16434 16435/** 16436 * @private 16437 * @class ImageJob 16438 * @classdesc Handles downloading of a single image. 16439 * @param {Object} options - Options for this ImageJob. 16440 * @param {String} [options.src] - URL of image to download. 16441 * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX. 16442 * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX. 16443 * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads 16444 * @param {Function} [options.callback] - Called once image has been downloaded. 16445 * @param {Function} [options.abort] - Called when this image job is aborted. 16446 * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete. 16447 */ 16448function ImageJob (options) { 16449 16450 $.extend(true, this, { 16451 timeout: $.DEFAULT_SETTINGS.timeout, 16452 jobId: null 16453 }, options); 16454 16455 /** 16456 * Image object which will contain downloaded image. 16457 * @member {Image} image 16458 * @memberof OpenSeadragon.ImageJob# 16459 */ 16460 this.image = null; 16461} 16462 16463ImageJob.prototype = { 16464 errorMsg: null, 16465 16466 /** 16467 * Starts the image job. 16468 * @method 16469 */ 16470 start: function(){ 16471 var self = this; 16472 var selfAbort = this.abort; 16473 16474 this.image = new Image(); 16475 16476 this.image.onload = function(){ 16477 self.finish(true); 16478 }; 16479 this.image.onabort = this.image.onerror = function() { 16480 self.errorMsg = "Image load aborted"; 16481 self.finish(false); 16482 }; 16483 16484 this.jobId = window.setTimeout(function(){ 16485 self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)"; 16486 self.finish(false); 16487 }, this.timeout); 16488 16489 // Load the tile with an AJAX request if the loadWithAjax option is 16490 // set. Otherwise load the image by setting the source proprety of the image object. 16491 if (this.loadWithAjax) { 16492 this.request = $.makeAjaxRequest({ 16493 url: this.src, 16494 withCredentials: this.ajaxWithCredentials, 16495 headers: this.ajaxHeaders, 16496 responseType: "arraybuffer", 16497 success: function(request) { 16498 var blb; 16499 // Make the raw data into a blob. 16500 // BlobBuilder fallback adapted from 16501 // http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility 16502 try { 16503 blb = new window.Blob([request.response]); 16504 } catch (e) { 16505 var BlobBuilder = ( 16506 window.BlobBuilder || 16507 window.WebKitBlobBuilder || 16508 window.MozBlobBuilder || 16509 window.MSBlobBuilder 16510 ); 16511 if (e.name === 'TypeError' && BlobBuilder) { 16512 var bb = new BlobBuilder(); 16513 bb.append(request.response); 16514 blb = bb.getBlob(); 16515 } 16516 } 16517 // If the blob is empty for some reason consider the image load a failure. 16518 if (blb.size === 0) { 16519 self.errorMsg = "Empty image response."; 16520 self.finish(false); 16521 } 16522 // Create a URL for the blob data and make it the source of the image object. 16523 // This will still trigger Image.onload to indicate a successful tile load. 16524 var url = (window.URL || window.webkitURL).createObjectURL(blb); 16525 self.image.src = url; 16526 }, 16527 error: function(request) { 16528 self.errorMsg = "Image load aborted - XHR error"; 16529 self.finish(false); 16530 } 16531 }); 16532 16533 // Provide a function to properly abort the request. 16534 this.abort = function() { 16535 self.request.abort(); 16536 16537 // Call the existing abort function if available 16538 if (typeof selfAbort === "function") { 16539 selfAbort(); 16540 } 16541 }; 16542 } else { 16543 if (this.crossOriginPolicy !== false) { 16544 this.image.crossOrigin = this.crossOriginPolicy; 16545 } 16546 16547 this.image.src = this.src; 16548 } 16549 }, 16550 16551 finish: function(successful) { 16552 this.image.onload = this.image.onerror = this.image.onabort = null; 16553 if (!successful) { 16554 this.image = null; 16555 } 16556 16557 if (this.jobId) { 16558 window.clearTimeout(this.jobId); 16559 } 16560 16561 this.callback(this); 16562 } 16563 16564}; 16565 16566/** 16567 * @class ImageLoader 16568 * @memberof OpenSeadragon 16569 * @classdesc Handles downloading of a set of images using asynchronous queue pattern. 16570 * You generally won't have to interact with the ImageLoader directly. 16571 * @param {Object} options - Options for this ImageLoader. 16572 * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details. 16573 * @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete. 16574 */ 16575$.ImageLoader = function(options) { 16576 16577 $.extend(true, this, { 16578 jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit, 16579 timeout: $.DEFAULT_SETTINGS.timeout, 16580 jobQueue: [], 16581 jobsInProgress: 0 16582 }, options); 16583 16584}; 16585 16586/** @lends OpenSeadragon.ImageLoader.prototype */ 16587$.ImageLoader.prototype = { 16588 16589 /** 16590 * Add an unloaded image to the loader queue. 16591 * @method 16592 * @param {Object} options - Options for this job. 16593 * @param {String} [options.src] - URL of image to download. 16594 * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX. 16595 * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX. 16596 * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads 16597 * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX 16598 * requests. 16599 * @param {Function} [options.callback] - Called once image has been downloaded.
16600 * @param {Function} [options.abort] - Called when this image job is aborted. 16601 */ 16602 addJob: function(options) { 16603 var _this = this, 16604 complete = function(job) { 16605 completeJob(_this, job, options.callback); 16606 }, 16607 jobOptions = { 16608 src: options.src, 16609 loadWithAjax: options.loadWithAjax, 16610 ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null, 16611 crossOriginPolicy: options.crossOriginPolicy, 16612 ajaxWithCredentials: options.ajaxWithCredentials, 16613 callback: complete, 16614 abort: options.abort, 16615 timeout: this.timeout 16616 }, 16617 newJob = new ImageJob(jobOptions); 16618 16619 if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) { 16620 newJob.start(); 16621 this.jobsInProgress++; 16622 } 16623 else { 16624 this.jobQueue.push( newJob ); 16625 } 16626 }, 16627 16628 /** 16629 * Clear any unstarted image loading jobs from the queue. 16630 * @method 16631 */ 16632 clear: function() { 16633 for( var i = 0; i < this.jobQueue.length; i++ ) { 16634 var job = this.jobQueue[i]; 16635 if ( typeof job.abort === "function" ) { 16636 job.abort(); 16637 } 16638 } 16639 16640 this.jobQueue = []; 16641 } 16642}; 16643 16644/** 16645 * Cleans up ImageJob once completed. 16646 * @method 16647 * @private 16648 * @param loader - ImageLoader used to start job. 16649 * @param job - The ImageJob that has completed. 16650 * @param callback - Called once cleanup is finished. 16651 */ 16652function completeJob(loader, job, callback) { 16653 var nextJob; 16654 16655 loader.jobsInProgress--; 16656 16657 if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) { 16658 nextJob = loader.jobQueue.shift(); 16659 nextJob.start(); 16660 loader.jobsInProgress++; 16661 } 16662 16663 callback(job.image, job.errorMsg, job.request); 16664} 16665 16666}(OpenSeadragon)); 16667 16668/* 16669 * OpenSeadragon - Tile 16670 * 16671 * Copyright (C) 2009 CodePlex Foundation 16672 * Copyright (C) 2010-2013 OpenSeadragon contributors 16673 * 16674 * Redistribution and use in source and binary forms, with or without 16675 * modification, are permitted provided that the following conditions are 16676 * met: 16677 * 16678 * - Redistributions of source code must retain the above copyright notice, 16679 * this list of conditions and the following disclaimer. 16680 * 16681 * - Redistributions in binary form must reproduce the above copyright 16682 * notice, this list of conditions and the following disclaimer in the 16683 * documentation and/or other materials provided with the distribution. 16684 * 16685 * - Neither the name of CodePlex Foundation nor the names of its 16686 * contributors may be used to endorse or promote products derived from 16687 * this software without specific prior written permission. 16688 * 16689 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16690 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16691 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16692 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16693 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 16694 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16695 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 16696 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 16697 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 16698 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 16699 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16700 */ 16701 16702(function( $ ){ 16703 16704/** 16705 * @class Tile 16706 * @memberof OpenSeadragon 16707 * @param {Number} level The zoom level this tile belongs to. 16708 * @param {Number} x The vector component 'x'. 16709 * @param {Number} y The vector component 'y'. 16710 * @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized 16711 * coordinates. 16712 * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has 16713 * this tile failed to load? ) 16714 * @param {String} url The URL of this tile's image. 16715 * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it 16716 * is provided directly by the tile source. 16717 * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request . 16718 * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable). 16719 * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the 16720 * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing 16721 * with HTML the entire tile is always used. 16722 */ 16723$.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds) { 16724 /** 16725 * The zoom level this tile belongs to. 16726 * @member {Number} level 16727 * @memberof OpenSeadragon.Tile# 16728 */ 16729 this.level = level; 16730 /** 16731 * The vector component 'x'. 16732 * @member {Number} x 16733 * @memberof OpenSeadragon.Tile# 16734 */ 16735 this.x = x; 16736 /** 16737 * The vector component 'y'. 16738 * @member {Number} y 16739 * @memberof OpenSeadragon.Tile# 16740 */ 16741 this.y = y; 16742 /** 16743 * Where this tile fits, in normalized coordinates 16744 * @member {OpenSeadragon.Rect} bounds 16745 * @memberof OpenSeadragon.Tile# 16746 */ 16747 this.bounds = bounds; 16748 /** 16749 * The portion of the tile to use as the source of the drawing operation, in pixels. Note that 16750 * this only works when drawing with canvas; when drawing with HTML the entire tile is always used. 16751 * @member {OpenSeadragon.Rect} sourceBounds 16752 * @memberof OpenSeadragon.Tile# 16753 */ 16754 this.sourceBounds = sourceBounds; 16755 /** 16756 * Is this tile a part of a sparse image? Also has this tile failed to load? 16757 * @member {Boolean} exists 16758 * @memberof OpenSeadragon.Tile# 16759 */ 16760 this.exists = exists; 16761 /** 16762 * The URL of this tile's image. 16763 * @member {String} url 16764 * @memberof OpenSeadragon.Tile# 16765 */ 16766 this.url = url; 16767 /** 16768 * The context2D of this tile if it is provided directly by the tile source. 16769 * @member {CanvasRenderingContext2D} context2D 16770 * @memberOf OpenSeadragon.Tile# 16771 */ 16772 this.context2D = context2D; 16773 /** 16774 * Whether to load this tile's image with an AJAX request. 16775 * @member {Boolean} loadWithAjax 16776 * @memberof OpenSeadragon.Tile# 16777 */ 16778 this.loadWithAjax = loadWithAjax; 16779 /** 16780 * The headers to be used in requesting this tile's image. 16781 * Only used if loadWithAjax is set to true. 16782 * @member {Object} ajaxHeaders 16783 * @memberof OpenSeadragon.Tile# 16784 */ 16785 this.ajaxHeaders = ajaxHeaders; 16786 /** 16787 * The unique cache key for this tile. 16788 * @member {String} cacheKey 16789 * @memberof OpenSeadragon.Tile# 16790 */ 16791 if (this.ajaxHeaders) { 16792 this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders); 16793 } else { 16794 this.cacheKey = this.url; 16795 } 16796 /** 16797 * Is this tile loaded? 16798 * @member {Boolean} loaded 16799 * @memberof OpenSeadragon.Tile# 16800 */ 16801 this.loaded = false;
16802 /** 16803 * Is this tile loading? 16804 * @member {Boolean} loading 16805 * @memberof OpenSeadragon.Tile# 16806 */ 16807 this.loading = false; 16808 16809 /** 16810 * The HTML div element for this tile 16811 * @member {Element} element 16812 * @memberof OpenSeadragon.Tile# 16813 */ 16814 this.element = null; 16815 /** 16816 * The HTML img element for this tile. 16817 * @member {Element} imgElement 16818 * @memberof OpenSeadragon.Tile# 16819 */ 16820 this.imgElement = null; 16821 /** 16822 * The Image object for this tile. 16823 * @member {Object} image 16824 * @memberof OpenSeadragon.Tile# 16825 */ 16826 this.image = null; 16827 16828 /** 16829 * The alias of this.element.style. 16830 * @member {String} style 16831 * @memberof OpenSeadragon.Tile# 16832 */ 16833 this.style = null; 16834 /** 16835 * This tile's position on screen, in pixels. 16836 * @member {OpenSeadragon.Point} position 16837 * @memberof OpenSeadragon.Tile# 16838 */ 16839 this.position = null; 16840 /** 16841 * This tile's size on screen, in pixels. 16842 * @member {OpenSeadragon.Point} size 16843 * @memberof OpenSeadragon.Tile# 16844 */ 16845 this.size = null; 16846 /** 16847 * The start time of this tile's blending. 16848 * @member {Number} blendStart 16849 * @memberof OpenSeadragon.Tile# 16850 */ 16851 this.blendStart = null; 16852 /** 16853 * The current opacity this tile should be. 16854 * @member {Number} opacity 16855 * @memberof OpenSeadragon.Tile# 16856 */ 16857 this.opacity = null; 16858 /** 16859 * The squared distance of this tile to the viewport center. 16860 * Use for comparing tiles. 16861 * @private 16862 * @member {Number} squaredDistance 16863 * @memberof OpenSeadragon.Tile# 16864 */ 16865 this.squaredDistance = null; 16866 /** 16867 * The visibility score of this tile. 16868 * @member {Number} visibility 16869 * @memberof OpenSeadragon.Tile# 16870 */ 16871 this.visibility = null; 16872 16873 /** 16874 * Whether this tile is currently being drawn. 16875 * @member {Boolean} beingDrawn 16876 * @memberof OpenSeadragon.Tile# 16877 */ 16878 this.beingDrawn = false; 16879 16880 /** 16881 * Timestamp the tile was last touched. 16882 * @member {Number} lastTouchTime 16883 * @memberof OpenSeadragon.Tile# 16884 */ 16885 this.lastTouchTime = 0; 16886 16887 /** 16888 * Whether this tile is in the right-most column for its level. 16889 * @member {Boolean} isRightMost 16890 * @memberof OpenSeadragon.Tile# 16891 */ 16892 this.isRightMost = false; 16893 16894 /** 16895 * Whether this tile is in the bottom-most row for its level. 16896 * @member {Boolean} isBottomMost 16897 * @memberof OpenSeadragon.Tile# 16898 */ 16899 this.isBottomMost = false; 16900}; 16901 16902/** @lends OpenSeadragon.Tile.prototype */ 16903$.Tile.prototype = { 16904 16905 /** 16906 * Provides a string representation of this tiles level and (x,y) 16907 * components. 16908 * @function 16909 * @returns {String} 16910 */ 16911 toString: function() { 16912 return this.level + "/" + this.x + "_" + this.y; 16913 }, 16914 16915 // private 16916 _hasTransparencyChannel: function() { 16917 return !!this.context2D || this.url.match('.png'); 16918 }, 16919 16920 /** 16921 * Renders the tile in an html container. 16922 * @function 16923 * @param {Element} container 16924 */ 16925 drawHTML: function( container ) { 16926 if (!this.cacheImageRecord) { 16927 $.console.warn( 16928 '[Tile.drawHTML] attempting to draw tile %s when it\'s not cached', 16929 this.toString()); 16930 return; 16931 } 16932 16933 if ( !this.loaded ) { 16934 $.console.warn( 16935 "Attempting to draw tile %s when it's not yet loaded.", 16936 this.toString() 16937 ); 16938 return; 16939 } 16940 16941 //EXPERIMENTAL - trying to figure out how to scale the container 16942 // content during animation of the container size. 16943 16944 if ( !this.element ) { 16945 this.element = $.makeNeutralElement( "div" ); 16946 this.imgElement = this.cacheImageRecord.getImage().cloneNode(); 16947 this.imgElement.style.msInterpolationMode = "nearest-neighbor"; 16948 this.imgElement.style.width = "100%"; 16949 this.imgElement.style.height = "100%"; 16950 16951 this.style = this.element.style; 16952 this.style.position = "absolute"; 16953 } 16954 if ( this.element.parentNode != container ) { 16955 container.appendChild( this.element ); 16956 }
16957 if ( this.imgElement.parentNode != this.element ) { 16958 this.element.appendChild( this.imgElement ); 16959 } 16960 16961 this.style.top = this.position.y + "px"; 16962 this.style.left = this.position.x + "px"; 16963 this.style.height = this.size.y + "px"; 16964 this.style.width = this.size.x + "px"; 16965 16966 $.setElementOpacity( this.element, this.opacity ); 16967 }, 16968 16969 /** 16970 * Renders the tile in a canvas-based context. 16971 * @function 16972 * @param {Canvas} context 16973 * @param {Function} drawingHandler - Method for firing the drawing event. 16974 * drawingHandler({context, tile, rendered}) 16975 * where <code>rendered</code> is the context with the pre-drawn image. 16976 * @param {Number} [scale=1] - Apply a scale to position and size 16977 * @param {OpenSeadragon.Point} [translate] - A translation vector 16978 */ 16979 drawCanvas: function( context, drawingHandler, scale, translate ) { 16980 16981 var position = this.position.times($.pixelDensityRatio), 16982 size = this.size.times($.pixelDensityRatio), 16983 rendered; 16984 16985 if (!this.context2D && !this.cacheImageRecord) { 16986 $.console.warn( 16987 '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', 16988 this.toString()); 16989 return; 16990 } 16991 16992 rendered = this.context2D || this.cacheImageRecord.getRenderedContext(); 16993 16994 if ( !this.loaded || !rendered ){ 16995 $.console.warn( 16996 "Attempting to draw tile %s when it's not yet loaded.", 16997 this.toString() 16998 ); 16999 17000 return; 17001 } 17002 17003 context.save(); 17004 17005 context.globalAlpha = this.opacity; 17006 17007 if (typeof scale === 'number' && scale !== 1) { 17008 // draw tile at a different scale 17009 position = position.times(scale); 17010 size = size.times(scale); 17011 } 17012 17013 if (translate instanceof $.Point) { 17014 // shift tile position slightly 17015 position = position.plus(translate); 17016 } 17017 17018 //if we are supposed to be rendering fully opaque rectangle, 17019 //ie its done fading or fading is turned off, and if we are drawing 17020 //an image with an alpha channel, then the only way 17021 //to avoid seeing the tile underneath is to clear the rectangle 17022 if (context.globalAlpha === 1 && this._hasTransparencyChannel()) { 17023 //clearing only the inside of the rectangle occupied 17024 //by the png prevents edge flikering 17025 context.clearRect( 17026 position.x, 17027 position.y, 17028 size.x, 17029 size.y 17030 ); 17031 } 17032 17033 // This gives the application a chance to make image manipulation 17034 // changes as we are rendering the image 17035 drawingHandler({context: context, tile: this, rendered: rendered}); 17036 17037 var sourceWidth, sourceHeight; 17038 if (this.sourceBounds) { 17039 sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width); 17040 sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height); 17041 } else { 17042 sourceWidth = rendered.canvas.width; 17043 sourceHeight = rendered.canvas.height; 17044 } 17045 17046 context.drawImage( 17047 rendered.canvas, 17048 0, 17049 0, 17050 sourceWidth, 17051 sourceHeight, 17052 position.x, 17053 position.y, 17054 size.x, 17055 size.y 17056 ); 17057 17058 context.restore(); 17059 }, 17060 17061 /** 17062 * Get the ratio between current and original size. 17063 * @function 17064 * @return {Float} 17065 */ 17066 getScaleForEdgeSmoothing: function() { 17067 var context; 17068 if (this.cacheImageRecord) { 17069 context = this.cacheImageRecord.getRenderedContext(); 17070 } else if (this.context2D) { 17071 context = this.context2D; 17072 } else { 17073 $.console.warn( 17074 '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached', 17075 this.toString()); 17076 return 1; 17077 } 17078 return context.canvas.width / (this.size.x * $.pixelDensityRatio); 17079 }, 17080 17081 /** 17082 * Get a translation vector that when applied to the tile position produces integer coordinates. 17083 * Needed to avoid swimming and twitching. 17084 * @function 17085 * @param {Number} [scale=1] - Scale to be applied to position. 17086 * @return {OpenSeadragon.Point} 17087 */ 17088 getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) { 17089 // The translation vector must have positive values, otherwise the image goes a bit off 17090 // the sketch canvas to the top and left and we must use negative coordinates to repaint it 17091 // to the main canvas. In that case, some browsers throw: 17092 // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value. 17093 var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
vendor: 21,041 bytes, lines 17094-17630
17094 var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2)); 17095 return new $.Point(x, y).minus( 17096 this.position 17097 .times($.pixelDensityRatio) 17098 .times(scale || 1) 17099 .apply(function(x) { 17100 return x % 1; 17101 }) 17102 ); 17103 }, 17104 17105 /** 17106 * Removes tile from its container. 17107 * @function 17108 */ 17109 unload: function() { 17110 if ( this.imgElement && this.imgElement.parentNode ) { 17111 this.imgElement.parentNode.removeChild( this.imgElement ); 17112 } 17113 if ( this.element && this.element.parentNode ) { 17114 this.element.parentNode.removeChild( this.element ); 17115 } 17116 17117 this.element = null; 17118 this.imgElement = null; 17119 this.loaded = false; 17120 this.loading = false; 17121 } 17122}; 17123 17124}( OpenSeadragon )); 17125 17126/* 17127 * OpenSeadragon - Overlay 17128 * 17129 * Copyright (C) 2009 CodePlex Foundation 17130 * Copyright (C) 2010-2013 OpenSeadragon contributors 17131 * 17132 * Redistribution and use in source and binary forms, with or without 17133 * modification, are permitted provided that the following conditions are 17134 * met: 17135 * 17136 * - Redistributions of source code must retain the above copyright notice, 17137 * this list of conditions and the following disclaimer. 17138 * 17139 * - Redistributions in binary form must reproduce the above copyright 17140 * notice, this list of conditions and the following disclaimer in the 17141 * documentation and/or other materials provided with the distribution. 17142 * 17143 * - Neither the name of CodePlex Foundation nor the names of its 17144 * contributors may be used to endorse or promote products derived from 17145 * this software without specific prior written permission. 17146 * 17147 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17148 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17149 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17150 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17151 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17152 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 17153 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 17154 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 17155 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 17156 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 17157 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17158 */ 17159 17160(function($) { 17161 17162 /** 17163 * An enumeration of positions that an overlay may be assigned relative to 17164 * the viewport. 17165 * It is identical to OpenSeadragon.Placement but is kept for backward 17166 * compatibility. 17167 * @member OverlayPlacement 17168 * @memberof OpenSeadragon 17169 * @see OpenSeadragon.Placement 17170 * @static 17171 * @readonly 17172 * @type {Object} 17173 * @property {Number} CENTER 17174 * @property {Number} TOP_LEFT 17175 * @property {Number} TOP 17176 * @property {Number} TOP_RIGHT 17177 * @property {Number} RIGHT 17178 * @property {Number} BOTTOM_RIGHT 17179 * @property {Number} BOTTOM 17180 * @property {Number} BOTTOM_LEFT 17181 * @property {Number} LEFT 17182 */ 17183 $.OverlayPlacement = $.Placement; 17184 17185 /** 17186 * An enumeration of possible ways to handle overlays rotation 17187 * @member OverlayRotationMode 17188 * @memberOf OpenSeadragon 17189 * @static 17190 * @readonly 17191 * @property {Number} NO_ROTATION The overlay ignore the viewport rotation. 17192 * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with 17193 * the viewport. If the overlay contains text, it will get rotated as well. 17194 * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by 17195 * taking the size of the bounding box of the rotated bounds. 17196 * Only valid for overlays with Rect location and scalable in both directions. 17197 */ 17198 $.OverlayRotationMode = $.freezeObject({ 17199 NO_ROTATION: 1, 17200 EXACT: 2, 17201 BOUNDING_BOX: 3 17202 }); 17203 17204 /** 17205 * @class Overlay 17206 * @classdesc Provides a way to float an HTML element on top of the viewer element. 17207 * 17208 * @memberof OpenSeadragon 17209 * @param {Object} options 17210 * @param {Element} options.element 17211 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The 17212 * location of the overlay on the image. If a {@link OpenSeadragon.Point} 17213 * is specified, the overlay will be located at this location with respect 17214 * to the placement option. If a {@link OpenSeadragon.Rect} is specified, 17215 * the overlay will be placed at this location with the corresponding width 17216 * and height and placement TOP_LEFT. 17217 * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT] 17218 * Defines what part of the overlay should be at the specified options.location 17219 * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw] 17220 * @param {Boolean} [options.checkResize=true] Set to false to avoid to 17221 * check the size of the overlay every time it is drawn in the directions 17222 * which are not scaled. It will improve performances but will cause a 17223 * misalignment if the overlay size changes. 17224 * @param {Number} [options.width] The width of the overlay in viewport 17225 * coordinates. If specified, the width of the overlay will be adjusted when 17226 * the zoom changes. 17227 * @param {Number} [options.height] The height of the overlay in viewport 17228 * coordinates. If specified, the height of the overlay will be adjusted when 17229 * the zoom changes. 17230 * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT] 17231 * How to handle the rotation of the viewport. 17232 */ 17233 $.Overlay = function(element, location, placement) { 17234 17235 /** 17236 * onDraw callback signature used by {@link OpenSeadragon.Overlay}. 17237 * 17238 * @callback OnDrawCallback 17239 * @memberof OpenSeadragon.Overlay 17240 * @param {OpenSeadragon.Point} position 17241 * @param {OpenSeadragon.Point} size 17242 * @param {Element} element 17243 */ 17244 17245 var options; 17246 if ($.isPlainObject(element)) { 17247 options = element; 17248 } else { 17249 options = { 17250 element: element, 17251 location: location, 17252 placement: placement 17253 }; 17254 } 17255 17256 this.element = options.element; 17257 this.style = options.element.style; 17258 this._init(options); 17259 }; 17260 17261 /** @lends OpenSeadragon.Overlay.prototype */ 17262 $.Overlay.prototype = { 17263 17264 // private 17265 _init: function(options) { 17266 this.location = options.location; 17267 this.placement = options.placement === undefined ? 17268 $.Placement.TOP_LEFT : options.placement; 17269 this.onDraw = options.onDraw; 17270 this.checkResize = options.checkResize === undefined ? 17271 true : options.checkResize; 17272 17273 // When this.width is not null, the overlay get scaled horizontally 17274 this.width = options.width === undefined ? null : options.width; 17275 17276 // When this.height is not null, the overlay get scaled vertically 17277 this.height = options.height === undefined ? null : options.height; 17278 17279 this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; 17280 17281 // Having a rect as location is a syntactic sugar 17282 if (this.location instanceof $.Rect) { 17283 this.width = this.location.width; 17284 this.height = this.location.height; 17285 this.location = this.location.getTopLeft(); 17286 this.placement = $.Placement.TOP_LEFT; 17287 } 17288 17289 // Deprecated properties kept for backward compatibility. 17290 this.scales = this.width !== null && this.height !== null; 17291 this.bounds = new $.Rect( 17292 this.location.x, this.location.y, this.width, this.height); 17293 this.position = this.location; 17294 }, 17295 17296 /** 17297 * Internal function to adjust the position of an overlay 17298 * depending on it size and placement. 17299 * @function 17300 * @param {OpenSeadragon.Point} position 17301 * @param {OpenSeadragon.Point} size 17302 */ 17303 adjust: function(position, size) { 17304 var properties = $.Placement.properties[this.placement]; 17305 if (!properties) { 17306 return; 17307 } 17308 if (properties.isHorizontallyCentered) { 17309 position.x -= size.x / 2; 17310 } else if (properties.isRight) { 17311 position.x -= size.x; 17312 } 17313 if (properties.isVerticallyCentered) { 17314 position.y -= size.y / 2; 17315 } else if (properties.isBottom) { 17316 position.y -= size.y; 17317 } 17318 }, 17319 17320 /** 17321 * @function 17322 */ 17323 destroy: function() { 17324 var element = this.element; 17325 var style = this.style; 17326 17327 if (element.parentNode) { 17328 element.parentNode.removeChild(element); 17329 //this should allow us to preserve overlays when required between 17330 //pages 17331 if (element.prevElementParent) { 17332 style.display = 'none'; 17333 //element.prevElementParent.insertBefore( 17334 // element, 17335 // element.prevNextSibling 17336 //); 17337 document.body.appendChild(element); 17338 } 17339 } 17340 17341 // clear the onDraw callback 17342 this.onDraw = null; 17343 17344 style.top = ""; 17345 style.left = ""; 17346 style.position = ""; 17347 17348 if (this.width !== null) { 17349 style.width = ""; 17350 } 17351 if (this.height !== null) { 17352 style.height = ""; 17353 } 17354 var transformOriginProp = $.getCssPropertyWithVendorPrefix( 17355 'transformOrigin'); 17356 var transformProp = $.getCssPropertyWithVendorPrefix( 17357 'transform'); 17358 if (transformOriginProp && transformProp) { 17359 style[transformOriginProp] = ""; 17360 style[transformProp] = ""; 17361 } 17362 }, 17363 17364 /** 17365 * @function 17366 * @param {Element} container 17367 */ 17368 drawHTML: function(container, viewport) { 17369 var element = this.element; 17370 if (element.parentNode !== container) { 17371 //save the source parent for later if we need it 17372 element.prevElementParent = element.parentNode; 17373 element.prevNextSibling = element.nextSibling; 17374 container.appendChild(element); 17375 17376 // have to set position before calculating size, fix #1116 17377 this.style.position = "absolute"; 17378 // this.size is used by overlays which don't get scaled in at 17379 // least one direction when this.checkResize is set to false. 17380 this.size = $.getElementSize(element); 17381 } 17382 17383 var positionAndSize = this._getOverlayPositionAndSize(viewport); 17384 17385 var position = positionAndSize.position; 17386 var size = this.size = positionAndSize.size; 17387 var rotate = positionAndSize.rotate; 17388 17389 // call the onDraw callback if it exists to allow one to overwrite 17390 // the drawing/positioning/sizing of the overlay 17391 if (this.onDraw) { 17392 this.onDraw(position, size, this.element); 17393 } else { 17394 var style = this.style; 17395 style.left = position.x + "px"; 17396 style.top = position.y + "px"; 17397 if (this.width !== null) { 17398 style.width = size.x + "px"; 17399 } 17400 if (this.height !== null) { 17401 style.height = size.y + "px"; 17402 } 17403 var transformOriginProp = $.getCssPropertyWithVendorPrefix( 17404 'transformOrigin'); 17405 var transformProp = $.getCssPropertyWithVendorPrefix( 17406 'transform'); 17407 if (transformOriginProp && transformProp) { 17408 if (rotate) { 17409 style[transformOriginProp] = this._getTransformOrigin(); 17410 style[transformProp] = "rotate(" + rotate + "deg)"; 17411 } else { 17412 style[transformOriginProp] = ""; 17413 style[transformProp] = ""; 17414 } 17415 } 17416 17417 if (style.display !== 'none') { 17418 style.display = 'block'; 17419 } 17420 } 17421 }, 17422 17423 // private 17424 _getOverlayPositionAndSize: function(viewport) { 17425 var position = viewport.pixelFromPoint(this.location, true); 17426 var size = this._getSizeInPixels(viewport); 17427 this.adjust(position, size); 17428 17429 var rotate = 0; 17430 if (viewport.degrees && 17431 this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { 17432 // BOUNDING_BOX is only valid if both directions get scaled. 17433 // Get replaced by EXACT otherwise. 17434 if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && 17435 this.width !== null && this.height !== null) { 17436 var rect = new $.Rect(position.x, position.y, size.x, size.y); 17437 var boundingBox = this._getBoundingBox(rect, viewport.degrees); 17438 position = boundingBox.getTopLeft(); 17439 size = boundingBox.getSize(); 17440 } else { 17441 rotate = viewport.degrees; 17442 } 17443 } 17444 17445 return { 17446 position: position, 17447 size: size, 17448 rotate: rotate 17449 }; 17450 }, 17451 17452 // private 17453 _getSizeInPixels: function(viewport) { 17454 var width = this.size.x; 17455 var height = this.size.y; 17456 if (this.width !== null || this.height !== null) { 17457 var scaledSize = viewport.deltaPixelsFromPointsNoRotate( 17458 new $.Point(this.width || 0, this.height || 0), true); 17459 if (this.width !== null) { 17460 width = scaledSize.x; 17461 } 17462 if (this.height !== null) { 17463 height = scaledSize.y; 17464 } 17465 } 17466 if (this.checkResize && 17467 (this.width === null || this.height === null)) { 17468 var eltSize = this.size = $.getElementSize(this.element); 17469 if (this.width === null) { 17470 width = eltSize.x; 17471 } 17472 if (this.height === null) { 17473 height = eltSize.y; 17474 } 17475 } 17476 return new $.Point(width, height); 17477 }, 17478 17479 // private 17480 _getBoundingBox: function(rect, degrees) { 17481 var refPoint = this._getPlacementPoint(rect); 17482 return rect.rotate(degrees, refPoint).getBoundingBox(); 17483 }, 17484 17485 // private 17486 _getPlacementPoint: function(rect) { 17487 var result = new $.Point(rect.x, rect.y); 17488 var properties = $.Placement.properties[this.placement]; 17489 if (properties) { 17490 if (properties.isHorizontallyCentered) { 17491 result.x += rect.width / 2; 17492 } else if (properties.isRight) { 17493 result.x += rect.width; 17494 } 17495 if (properties.isVerticallyCentered) { 17496 result.y += rect.height / 2; 17497 } else if (properties.isBottom) { 17498 result.y += rect.height; 17499 } 17500 } 17501 return result; 17502 }, 17503 17504 // private 17505 _getTransformOrigin: function() { 17506 var result = ""; 17507 var properties = $.Placement.properties[this.placement]; 17508 if (!properties) { 17509 return result; 17510 } 17511 if (properties.isLeft) { 17512 result = "left"; 17513 } else if (properties.isRight) { 17514 result = "right"; 17515 } 17516 if (properties.isTop) { 17517 result += " top"; 17518 } else if (properties.isBottom) { 17519 result += " bottom"; 17520 } 17521 return result; 17522 }, 17523 17524 /** 17525 * Changes the overlay settings. 17526 * @function 17527 * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location 17528 * If an object is specified, the options are the same than the constructor 17529 * except for the element which can not be changed. 17530 * @param {OpenSeadragon.Placement} placement 17531 */ 17532 update: function(location, placement) { 17533 var options = $.isPlainObject(location) ? location : { 17534 location: location, 17535 placement: placement 17536 }; 17537 this._init({ 17538 location: options.location || this.location, 17539 placement: options.placement !== undefined ? 17540 options.placement : this.placement, 17541 onDraw: options.onDraw || this.onDraw, 17542 checkResize: options.checkResize || this.checkResize, 17543 width: options.width !== undefined ? options.width : this.width, 17544 height: options.height !== undefined ? options.height : this.height, 17545 rotationMode: options.rotationMode || this.rotationMode 17546 }); 17547 }, 17548 17549 /** 17550 * Returns the current bounds of the overlay in viewport coordinates 17551 * @function 17552 * @param {OpenSeadragon.Viewport} viewport the viewport 17553 * @returns {OpenSeadragon.Rect} overlay bounds 17554 */ 17555 getBounds: function(viewport) { 17556 $.console.assert(viewport, 17557 'A viewport must now be passed to Overlay.getBounds.'); 17558 var width = this.width; 17559 var height = this.height; 17560 if (width === null || height === null) { 17561 var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true); 17562 if (width === null) { 17563 width = size.x; 17564 } 17565 if (height === null) { 17566 height = size.y; 17567 } 17568 } 17569 var location = this.location.clone(); 17570 this.adjust(location, new $.Point(width, height)); 17571 return this._adjustBoundsForRotation( 17572 viewport, new $.Rect(location.x, location.y, width, height)); 17573 }, 17574 17575 // private 17576 _adjustBoundsForRotation: function(viewport, bounds) { 17577 if (!viewport || 17578 viewport.degrees === 0 || 17579 this.rotationMode === $.OverlayRotationMode.EXACT) { 17580 return bounds; 17581 } 17582 if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) { 17583 // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT 17584 if (this.width === null || this.height === null) { 17585 return bounds; 17586 } 17587 // It is easier to just compute the position and size and 17588 // convert to viewport coordinates. 17589 var positionAndSize = this._getOverlayPositionAndSize(viewport); 17590 return viewport.viewerElementToViewportRectangle(new $.Rect( 17591 positionAndSize.position.x, 17592 positionAndSize.position.y, 17593 positionAndSize.size.x, 17594 positionAndSize.size.y)); 17595 } 17596 17597 // NO_ROTATION case 17598 return bounds.rotate(-viewport.degrees, 17599 this._getPlacementPoint(bounds)); 17600 } 17601 }; 17602 17603}(OpenSeadragon)); 17604 17605/* 17606 * OpenSeadragon - Drawer 17607 * 17608 * Copyright (C) 2009 CodePlex Foundation 17609 * Copyright (C) 2010-2013 OpenSeadragon contributors 17610 * 17611 * Redistribution and use in source and binary forms, with or without 17612 * modification, are permitted provided that the following conditions are 17613 * met: 17614 * 17615 * - Redistributions of source code must retain the above copyright notice, 17616 * this list of conditions and the following disclaimer. 17617 * 17618 * - Redistributions in binary form must reproduce the above copyright 17619 * notice, this list of conditions and the following disclaimer in the 17620 * documentation and/or other materials provided with the distribution. 17621 * 17622 * - Neither the name of CodePlex Foundation nor the names of its 17623 * contributors may be used to endorse or promote products derived from 17624 * this software without specific prior written permission. 17625 * 17626 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17627 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17628 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17629 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17630 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
17631 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 17632 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 17633 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 17634 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 17635 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 17636 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17637 */ 17638 17639(function( $ ){ 17640 17641/** 17642 * @class Drawer 17643 * @memberof OpenSeadragon 17644 * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. 17645 * @param {Object} options - Options for this Drawer. 17646 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer. 17647 * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport. 17648 * @param {Element} options.element - Parent element. 17649 * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details. 17650 */ 17651$.Drawer = function( options ) { 17652 17653 $.console.assert( options.viewer, "[Drawer] options.viewer is required" ); 17654 17655 //backward compatibility for positional args while preferring more 17656 //idiomatic javascript options object as the only argument 17657 var args = arguments; 17658 17659 if( !$.isPlainObject( options ) ){ 17660 options = { 17661 source: args[ 0 ], // Reference to Viewer tile source. 17662 viewport: args[ 1 ], // Reference to Viewer viewport. 17663 element: args[ 2 ] // Parent element. 17664 }; 17665 } 17666 17667 $.console.assert( options.viewport, "[Drawer] options.viewport is required" ); 17668 $.console.assert( options.element, "[Drawer] options.element is required" ); 17669 17670 if ( options.source ) { 17671 $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" ); 17672 } 17673 17674 this.viewer = options.viewer; 17675 this.viewport = options.viewport; 17676 this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor; 17677 if (options.opacity) { 17678 $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" ); 17679 } 17680 17681 this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); 17682 /** 17683 * The parent element of this Drawer instance, passed in when the Drawer was created. 17684 * The parent of {@link OpenSeadragon.Drawer#canvas}. 17685 * @member {Element} container 17686 * @memberof OpenSeadragon.Drawer# 17687 */ 17688 this.container = $.getElement( options.element ); 17689 /** 17690 * A <canvas> element if the browser supports them, otherwise a <div> element. 17691 * Child element of {@link OpenSeadragon.Drawer#container}. 17692 * @member {Element} canvas 17693 * @memberof OpenSeadragon.Drawer# 17694 */ 17695 this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); 17696 /** 17697 * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null. 17698 * @member {Object} context 17699 * @memberof OpenSeadragon.Drawer# 17700 */ 17701 this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null; 17702 17703 /** 17704 * Sketch canvas used to temporarily draw tiles which cannot be drawn directly 17705 * to the main canvas due to opacity. Lazily initialized. 17706 */ 17707 this.sketchCanvas = null; 17708 this.sketchContext = null; 17709 17710 /** 17711 * @member {Element} element 17712 * @memberof OpenSeadragon.Drawer# 17713 * @deprecated Alias for {@link OpenSeadragon.Drawer#container}. 17714 */ 17715 this.element = this.container; 17716 17717 // We force our container to ltr because our drawing math doesn't work in rtl. 17718 // This issue only affects our canvas renderer, but we do it always for consistency. 17719 // Note that this means overlays you want to be rtl need to be explicitly set to rtl. 17720 this.container.dir = 'ltr'; 17721 17722 // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density 17723 if (this.useCanvas) { 17724 var viewportSize = this._calculateCanvasSize(); 17725 this.canvas.width = viewportSize.x; 17726 this.canvas.height = viewportSize.y; 17727 } 17728 17729 this.canvas.style.width = "100%"; 17730 this.canvas.style.height = "100%"; 17731 this.canvas.style.position = "absolute"; 17732 $.setElementOpacity( this.canvas, this.opacity, true ); 17733 17734 // explicit left-align 17735 this.container.style.textAlign = "left"; 17736 this.container.appendChild( this.canvas ); 17737 17738 // Image smoothing for canvas rendering (only if canvas is used). 17739 // Canvas default is "true", so this will only be changed if user specified "fal
17739se". 17740 this._imageSmoothingEnabled = true; 17741}; 17742 17743/** @lends OpenSeadragon.Drawer.prototype */ 17744$.Drawer.prototype = { 17745 // deprecated 17746 addOverlay: function( element, location, placement, onDraw ) { 17747 $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); 17748 this.viewer.addOverlay( element, location, placement, onDraw ); 17749 return this; 17750 }, 17751 17752 // deprecated 17753 updateOverlay: function( element, location, placement ) { 17754 $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); 17755 this.viewer.updateOverlay( element, location, placement ); 17756 return this; 17757 }, 17758 17759 // deprecated 17760 removeOverlay: function( element ) { 17761 $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); 17762 this.viewer.removeOverlay( element ); 17763 return this; 17764 }, 17765 17766 // deprecated 17767 clearOverlays: function() { 17768 $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); 17769 this.viewer.clearOverlays(); 17770 return this; 17771 }, 17772 17773 /** 17774 * This function converts the given point from to the drawer coordinate by 17775 * multiplying it with the pixel density. 17776 * This function does not take rotation into account, thus assuming provided 17777 * point is at 0 degree. 17778 * @param {OpenSeadragon.Point} point - the pixel point to convert 17779 */ 17780 viewportCoordToDrawerCoord: function(point) { 17781 var vpPoint = this.viewport.pixelFromPointNoRotate(point, true); 17782 return new $.Point( 17783 vpPoint.x * $.pixelDensityRatio, 17784 vpPoint.y * $.pixelDensityRatio 17785 ); 17786 }, 17787 17788 /** 17789 * This function will create multiple polygon paths on the drawing context by provided polygons, 17790 * then clip the context to the paths. 17791 * @param {(OpenSeadragon.Point[])[]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point 17792 * @param {Boolean} useSketch - Whether to use the sketch canvas or not. 17793 */ 17794 clipWithPolygons: function (polygons, useSketch) { 17795 if (!this.useCanvas) { 17796 return; 17797 } 17798 var context = this._getContext(useSketch); 17799 context.beginPath(); 17800 polygons.forEach(function (polygon) { 17801 polygon.forEach(function (coord, i) { 17802 context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y); 17803 }); 17804 }); 17805 context.clip(); 17806 }, 17807 17808 /** 17809 * Set the opacity of the drawer. 17810 * @param {Number} opacity 17811 * @return {OpenSeadragon.Drawer} Chainable. 17812 */ 17813 setOpacity: function( opacity ) { 17814 $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead."); 17815 var world = this.viewer.world; 17816 for (var i = 0; i < world.getItemCount(); i++) { 17817 world.getItemAt( i ).setOpacity( opacity ); 17818 } 17819 return this; 17820 }, 17821 17822 /** 17823 * Get the opacity of the drawer. 17824 * @returns {Number} 17825 */ 17826 getOpacity: function() { 17827 $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead."); 17828 var world = this.viewer.world; 17829 var maxOpacity = 0; 17830 for (var i = 0; i < world.getItemCount(); i++) { 17831 var opacity = world.getItemAt( i ).getOpacity(); 17832 if ( opacity > maxOpacity ) { 17833 maxOpacity = opacity; 17834 } 17835 } 17836 return maxOpacity; 17837 }, 17838 17839 // deprecated 17840 needsUpdate: function() { 17841 $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." ); 17842 return this.viewer.world.needsDraw(); 17843 }, 17844 17845 // deprecated 17846 numTilesLoaded: function() { 17847 $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." ); 17848 return this.viewer.tileCache.numTilesLoaded(); 17849 }, 17850 17851 // deprecated 17852 reset: function() { 17853 $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." ); 17854 this.viewer.world.resetItems(); 17855 return this; 17856 }, 17857 17858 // deprecated 17859 update: function() { 17860 $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." ); 17861 this.clear(); 17862 this.viewer.world.draw(); 17863 return this; 17864 }, 17865 17866 /** 17867 * @return {Boolean} True if rotation is supported. 17868 */ 17869 canRotate: function() { 17870 return this.useCanvas; 17871 }, 17872 17873 /** 17874 * Destroy the drawer (unload current loaded tiles) 17875 */ 17876 destroy: function() { 17877 //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) 17878 this.canvas.width = 1; 17879 this.canvas.height = 1; 17880 this.sketchCanvas = null; 17881 this.sketchContext = null; 17882 }, 17883 17884 /** 17885 * Clears the Drawer so it's ready to draw another frame. 17886 */ 17887 clear: function() { 17888 this.canvas.innerHTML = ""; 17889 if ( this.useCanvas ) { 17890 var viewportSize = this._calculateCanvasSize(); 17891 if( this.canvas.width != viewportSize.x || 17892 this.canvas.height != viewportSize.y ) { 17893 this.canvas.width = viewportSize.x; 17894 this.canvas.height = viewportSize.y; 17895 this._updateImageSmoothingEnabled(this.context); 17896 if ( this.sketchCanvas !== null ) { 17897 var sketchCanvasSize = this._calculateSketchCanvasSize(); 17898 this.sketchCanvas.width = sketchCanvasSize.x; 17899 this.sketchCanvas.height = sketchCanvasSize.y; 17900 this._updateImageSmoothingEnabled(this.sketchContext); 17901 } 17902 } 17903 this._clear(); 17904 } 17905 }, 17906 17907 _clear: function (useSketch, bounds) { 17908 if (!this.useCanvas) { 17909 return; 17910 } 17911 var context = this._getContext(useSketch); 17912 if (bounds) { 17913 context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height); 17914 } else { 17915 var canvas = context.canvas; 17916 context.clearRect(0, 0, canvas.width, canvas.height); 17917 } 17918 }, 17919 17920 /** 17921 * Scale from OpenSeadragon viewer rectangle to drawer rectangle 17922 * (ignoring rotation) 17923 * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system. 17924 * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system. 17925 */ 17926 viewportToDrawerRectangle: function(rectangle) { 17927 var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true); 17928 var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true); 17929 17930 return new $.Rect( 17931 topLeft.x * $.pixelDensityRatio, 17932 topLeft.y * $.pixelDensityRatio, 17933 size.x * $.pixelDensityRatio, 17934 size.y * $.pixelDensityRatio 17935 ); 17936 }, 17937 17938 /** 17939 * Draws the given tile. 17940 * @param {OpenSeadragon.Tile} tile - The tile to draw. 17941 * @param {Function} drawingHandler - Method for firing the drawing event if using canvas. 17942 * drawingHandler({context, tile, rendered}) 17943 * @param {Boolean} useSketch - Whether to use the sketch canvas or not. 17944 * where <code>rendered</code> is the context with the pre-drawn image. 17945 * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1. 17946 * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position 17947 */ 17948 drawTile: function(tile, drawingHandler, useSketch, scale, translate) { 17949 $.console.assert(tile, '[Drawer.drawTile] tile is required'); 17950 $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required'); 17951 17952 if (this.useCanvas) { 17953 var context = this._getContext(useSketch); 17954 scale = scale || 1; 17955 tile.drawCanvas(context, drawingHandler, scale, translate); 17956 } else { 17957 tile.drawHTML( this.canvas ); 17958 } 17959 }, 17960 17961 _getContext: function( useSketch ) { 17962 var context = this.context; 17963 if ( useSketch ) { 17964 if (this.sketchCanvas === null) { 17965 this.sketchCanvas = document.createElement( "canvas" ); 17966 var sketchCanvasSize = this._calculateSketchCanvasSize(); 17967 this.sketchCanvas.width = sketchCanvasSize.x; 17968 this.sketchCanvas.height = sketchCanvasSize.y; 17969 this.sketchContext = this.sketchCanvas.getContext( "2d" ); 17970 17971 // If the viewport is not currently rotated, the sketchCanvas 17972 // will have the same size as the main canvas. However, if
17973 // the viewport get rotated later on, we will need to resize it. 17974 if (this.viewport.getRotation() === 0) { 17975 var self = this; 17976 this.viewer.addHandler('rotate', function resizeSketchCanvas() { 17977 if (self.viewport.getRotation() === 0) { 17978 return; 17979 } 17980 self.viewer.removeHandler('rotate', resizeSketchCanvas); 17981 var sketchCanvasSize = self._calculateSketchCanvasSize(); 17982 self.sketchCanvas.width = sketchCanvasSize.x; 17983 self.sketchCanvas.height = sketchCanvasSize.y; 17984 }); 17985 } 17986 this._updateImageSmoothingEnabled(this.sketchContext); 17987 } 17988 context = this.sketchContext; 17989 } 17990 return context; 17991 }, 17992 17993 // private 17994 saveContext: function( useSketch ) { 17995 if (!this.useCanvas) { 17996 return; 17997 } 17998 17999 this._getContext( useSketch ).save(); 18000 }, 18001 18002 // private 18003 restoreContext: function( useSketch ) { 18004 if (!this.useCanvas) { 18005 return; 18006 } 18007
18008 this._getContext( useSketch ).restore(); 18009 }, 18010 18011 // private 18012 setClip: function(rect, useSketch) { 18013 if (!this.useCanvas) { 18014 return; 18015 } 18016 18017 var context = this._getContext( useSketch ); 18018 context.beginPath(); 18019 context.rect(rect.x, rect.y, rect.width, rect.height); 18020 context.clip(); 18021 }, 18022 18023 // private 18024 drawRectangle: function(rect, fillStyle, useSketch) { 18025 if (!this.useCanvas) { 18026 return; 18027 } 18028 18029 var context = this._getContext( useSketch ); 18030 context.save(); 18031 context.fillStyle = fillStyle; 18032 context.fillRect(rect.x, rect.y, rect.width, rect.height); 18033 context.restore(); 18034 }, 18035 18036 /** 18037 * Blends the sketch canvas in the main canvas. 18038 * @param {Object} options The options 18039 * @param {Float} options.opacity The opacity of the blending. 18040 * @param {Float} [options.scale=1] The scale at which tiles were drawn on 18041 * the sketch. Default is 1. 18042 * Use scale to draw at a lower scale and then enlarge onto the main canvas. 18043 * @param {OpenSeadragon.Point} [options.translate] A translation vector 18044 * that was used to draw the tiles 18045 * @param {String} [options.compositeOperation] - How the image is 18046 * composited onto other images; see compositeOperation in 18047 * {@link OpenSeadragon.Options} for possible values. 18048 * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch 18049 * canvas to blend in the main canvas. If specified, options.scale and 18050 * options.translate get ignored. 18051 */ 18052 blendSketch: function(opacity, scale, translate, compositeOperation) { 18053 var options = opacity; 18054 if (!$.isPlainObject(options)) { 18055 options = { 18056 opacity: opacity, 18057 scale: scale, 18058 translate: translate, 18059 compositeOperation: compositeOperation 18060 }; 18061 } 18062 if (!this.useCanvas || !this.sketchCanvas) { 18063 return; 18064 } 18065 opacity = options.opacity; 18066 compositeOperation = options.compositeOperation; 18067 var bounds = options.bounds; 18068 18069 this.context.save(); 18070 this.context.globalAlpha = opacity; 18071 if (compositeOperation) { 18072 this.context.globalCompositeOperation = compositeOperation; 18073 } 18074 if (bounds) { 18075 // Internet Explorer, Microsoft Edge, and Safari have problems 18076 // when you call context.drawImage with negative x or y 18077 // or x + width or y + height greater than the canvas width or height respectively. 18078 if (bounds.x < 0) { 18079 bounds.width += bounds.x; 18080 bounds.x = 0; 18081 } 18082 if (bounds.x + bounds.width > this.canvas.width) { 18083 bounds.width = this.canvas.width - bounds.x; 18084 } 18085 if (bounds.y < 0) { 18086 bounds.height += bounds.y; 18087 bounds.y = 0; 18088 } 18089 if (bounds.y + bounds.height > this.canvas.height) { 18090 bounds.height = this.canvas.height - bounds.y; 18091 } 18092 18093 this.context.drawImage( 18094 this.sketchCanvas, 18095 bounds.x, 18096 bounds.y, 18097 bounds.width, 18098 bounds.height, 18099 bounds.x, 18100 bounds.y, 18101 bounds.width, 18102 bounds.height 18103 ); 18104 } else { 18105 scale = options.scale || 1; 18106 translate = options.translate; 18107 var position = translate instanceof $.Point ? 18108 translate : new $.Point(0, 0); 18109 18110 var widthExt = 0; 18111 var heightExt = 0; 18112 if (translate) { 18113 var widthDiff = this.sketchCanvas.width - this.canvas.width; 18114 var heightDiff = this.sketchCanvas.height - this.canvas.height; 18115 widthExt = Math.round(widthDiff / 2); 18116 heightExt = Math.round(heightDiff / 2); 18117 } 18118 this.context.drawImage( 18119 this.sketchCanvas, 18120 position.x - widthExt * scale, 18121 position.y - heightExt * scale, 18122 (this.canvas.width + 2 * widthExt) * scale, 18123 (this.canvas.height + 2 * heightExt) * scale, 18124 -widthExt, 18125 -heightExt, 18126 this.canvas.width + 2 * widthExt, 18127 this.canvas.height + 2 * heightExt 18128 ); 18129 }
18130 this.context.restore(); 18131 }, 18132 18133 // private 18134 drawDebugInfo: function(tile, count, i, tiledImage) { 18135 if ( !this.useCanvas ) { 18136 return; 18137 } 18138 18139 var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length; 18140 var context = this.context; 18141 context.save(); 18142 context.lineWidth = 2 * $.pixelDensityRatio; 18143 context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial'; 18144 context.strokeStyle = this.debugGridColor[colorIndex]; 18145 context.fillStyle = this.debugGridColor[colorIndex]; 18146 18147 if ( this.viewport.degrees !== 0 ) { 18148 this._offsetForRotation({degrees: this.viewport.degrees}); 18149 } 18150 if (tiledImage.getRotation(true) % 360 !== 0) { 18151 this._offsetForRotation({ 18152 degrees: tiledImage.getRotation(true), 18153 point: tiledImage.viewport.pixelFromPointNoRotate( 18154 tiledImage._getRotationPoint(true), true) 18155 }); 18156 } 18157 if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ 18158 if(tiledImage._drawer.viewer.viewport.getFlip()) { 18159 tiledImage._drawer._flip(); 18160 } 18161 } 18162 18163 context.strokeRect( 18164 tile.position.x * $.pixelDensityRatio, 18165 tile.position.y * $.pixelDensityRatio, 18166 tile.size.x * $.pixelDensityRatio, 18167 tile.size.y * $.pixelDensityRatio 18168 ); 18169 18170 var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio; 18171 var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio; 18172 18173 // Rotate the text the right way around. 18174 context.translate( tileCenterX, tileCenterY ); 18175 context.rotate( Math.PI / 180 * -this.viewport.degrees ); 18176 context.translate( -tileCenterX, -tileCenterY ); 18177 18178 if( tile.x === 0 && tile.y === 0 ){ 18179 context.fillText( 18180 "Zoom: " + this.viewport.getZoom(), 18181 tile.position.x * $.pixelDensityRatio, 18182 (tile.position.y - 30) * $.pixelDensityRatio 18183 ); 18184 context.fillText( 18185 "Pan: " + this.viewport.getBounds().toString(), 18186 tile.position.x * $.pixelDensityRatio, 18187 (tile.position.y - 20) * $.pixelDensityRatio 18188 ); 18189 } 18190 context.fillText( 18191 "Level: " + tile.level, 18192 (tile.position.x + 10) * $.pixelDensityRatio, 18193 (tile.position.y + 20) * $.pixelDensityRatio 18194 ); 18195 context.fillText( 18196 "Column: " + tile.x, 18197 (tile.position.x + 10) * $.pixelDensityRatio, 18198 (tile.position.y + 30) * $.pixelDensityRatio 18199 ); 18200 context.fillText( 18201 "Row: " + tile.y, 18202 (tile.position.x + 10) * $.pixelDensityRatio, 18203 (tile.position.y + 40) * $.pixelDensityRatio 18204 ); 18205 context.fillText( 18206 "Order: " + i + " of " + count, 18207 (tile.position.x + 10) * $.pixelDensityRatio, 18208 (tile.position.y + 50) * $.pixelDensityRatio 18209 ); 18210 context.fillText( 18211 "Size: " + tile.size.toString(), 18212 (tile.position.x + 10) * $.pixelDensityRatio, 18213 (tile.position.y + 60) * $.pixelDensityRatio 18214 ); 18215 context.fillText( 18216 "Position: " + tile.position.toString(), 18217 (tile.position.x + 10) * $.pixelDensityRatio, 18218 (tile.position.y + 70) * $.pixelDensityRatio 18219 ); 18220 18221 if ( this.viewport.degrees !== 0 ) { 18222 this._restoreRotationChanges(); 18223 } 18224 if (tiledImage.getRotation(true) % 360 !== 0) { 18225 this._restoreRotationChanges(); 18226 } 18227 18228 if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ 18229 if(tiledImage._drawer.viewer.viewport.getFlip()) { 18230 tiledImage._drawer._flip(); 18231 } 18232 } 18233 18234 context.restore(); 18235 }, 18236 18237 // private 18238 debugRect: function(rect) { 18239 if ( this.useCanvas ) { 18240 var context = this.context; 18241 context.save(); 18242 context.lineWidth = 2 * $.pixelDensityRatio; 18243 context.strokeStyle = this.debugGridColor[0]; 18244 context.fillStyle = this.debugGridColor[0]; 18245 18246 context.strokeRect( 18247 rect.x * $.pixelDensityRatio, 18248 rect.y * $.pixelDensityRatio, 18249 rect.width * $.pixelDensityRatio, 18250 rect.height * $.pixelDensityRatio 18251 ); 18252
18253 context.restore(); 18254 } 18255 }, 18256 18257 /** 18258 * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property. 18259 * 18260 * @function 18261 * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is 18262 * drawn smoothly on the canvas; see imageSmoothingEnabled in 18263 * {@link OpenSeadragon.Options} for more explanation. 18264 */ 18265 setImageSmoothingEnabled: function(imageSmoothingEnabled){ 18266 if ( this.useCanvas ) { 18267 this._imageSmoothingEnabled = imageSmoothingEnabled; 18268 this._updateImageSmoothingEnabled(this.context); 18269 this.viewer.forceRedraw(); 18270 } 18271 }, 18272 18273 // private 18274 _updateImageSmoothingEnabled: function(context){ 18275 context.msImageSmoothingEnabled = this._imageSmoothingEnabled; 18276 context.imageSmoothingEnabled = this._imageSmoothingEnabled; 18277 }, 18278 18279 /** 18280 * Get the canvas size 18281 * @param {Boolean} sketch If set to true return the size of the sketch canvas 18282 * @returns {OpenSeadragon.Point} The size of the canvas 18283 */ 18284 getCanvasSize: function(sketch) { 18285 var canvas = this._getContext(sketch).canvas; 18286 return new $.Point(canvas.width, canvas.height); 18287 }, 18288 18289 getCanvasCenter: function() { 18290 return new $.Point(this.canvas.width / 2, this.canvas.height / 2); 18291 }, 18292 18293 // private 18294 _offsetForRotation: function(options) { 18295 var point = options.point ? 18296 options.point.times($.pixelDensityRatio) : 18297 this.getCanvasCenter(); 18298 18299 var context = this._getContext(options.useSketch); 18300 context.save(); 18301 18302 context.translate(point.x, point.y); 18303 if(this.viewer.viewport.flipped){ 18304 context.rotate(Math.PI / 180 * -options.degrees); 18305 context.scale(-1, 1); 18306 } else{ 18307 context.rotate(Math.PI / 180 * options.degrees); 18308 } 18309 context.translate(-point.x, -point.y); 18310 }, 18311 18312 // private 18313 _flip: function(options) { 18314 options = options || {}; 18315 var point = options.point ? 18316 options.point.times($.pixelDensityRatio) : 18317 this.getCanvasCenter(); 18318 var context = this._getContext(options.useSketch); 18319 18320 context.translate(point.x, 0); 18321 context.scale(-1, 1); 18322 context.translate(-point.x, 0); 18323 }, 18324 18325 // private 18326 _restoreRotationChanges: function(useSketch) { 18327 var context = this._getContext(useSketch); 18328 context.restore(); 18329 }, 18330 18331 // private 18332 _calculateCanvasSize: function() { 18333 var pixelDensityRatio = $.pixelDensityRatio; 18334 var viewportSize = this.viewport.getContainerSize(); 18335 return { 18336 // canvas width and height are integers 18337 x: Math.round(viewportSize.x * pixelDensityRatio), 18338 y: Math.round(viewportSize.y * pixelDensityRatio) 18339 }; 18340 }, 18341 18342 // private 18343 _calculateSketchCanvasSize: function() { 18344 var canvasSize = this._calculateCanvasSize(); 18345 if (this.viewport.getRotation() === 0) { 18346 return canvasSize; 18347 } 18348 // If the viewport is rotated, we need a larger sketch canvas in order 18349 // to support edge smoothing. 18350 var sketchCanvasSize = Math.ceil(Math.sqrt( 18351 canvasSize.x * canvasSize.x + 18352 canvasSize.y * canvasSize.y)); 18353 return { 18354 x: sketchCanvasSize, 18355 y: sketchCanvasSize 18356 }; 18357 } 18358}; 18359 18360}( OpenSeadragon )); 18361 18362/* 18363 * OpenSeadragon - Viewport 18364 * 18365 * Copyright (C) 2009 CodePlex Foundation 18366 * Copyright (C) 2010-2013 OpenSeadragon contributors 18367 * 18368 * Redistribution and use in source and binary forms, with or without 18369 * modification, are permitted provided that the following conditions are 18370 * met: 18371 * 18372 * - Redistributions of source code must retain the above copyright notice, 18373 * this list of conditions and the following disclaimer. 18374 * 18375 * - Redistributions in binary form must reproduce the above copyright 18376 * notice, this list of conditions and the following disclaimer in the 18377 * documentation and/or other materials provided with the distribution. 18378 * 18379 * - Neither the name of CodePlex Foundation nor the names of its 18380 * contributors may be used to endorse or promote products derived from 18381 * this software without specific prior written permission. 18382 * 18383 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18384 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18385 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18386 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18387 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18388 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 18389 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 18390 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 18391 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18392 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 18393 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 18394 */ 18395 18396(function( $ ){ 18397 18398 18399/** 18400 * @class Viewport 18401 * @memberof OpenSeadragon 18402 * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.) 18403 * for an {@link OpenSeadragon.Viewer}. 18404 * @param {Object} options - Options for this Viewport. 18405 * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}. 18406 * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}. 18407 * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}. 18408 * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}. 18409 * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}. 18410 * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}. 18411 * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}. 18412 * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}. 18413 * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}. 18414 * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}. 18415 * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}. 18416 * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}. 18417 * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}. 18418 */ 18419$.Viewport = function( options ) { 18420 18421 //backward compatibility for positional args while preferring more 18422 //idiomatic javascript options object as the only argument 18423 var args = arguments; 18424 if (args.length && args[0] instanceof $.Point) { 18425 options = { 18426 containerSize: args[0], 18427 contentSize: args[1], 18428 config: args[2] 18429 }; 18430 } 18431 18432 //options.config and the general config argument are deprecated 18433 //in favor of the more direct specification of optional settings 18434 //being passed directly on the options object 18435 if ( options.config ){ 18436 $.extend( true, options, options.config ); 18437 delete options.config; 18438 } 18439 18440 this._margins = $.extend({ 18441 left: 0, 18442 top: 0, 18443 right: 0, 18444 bottom: 0 18445 }, options.margins || {}); 18446 18447 delete options.margins; 18448 18449 $.extend( true, this, { 18450 18451 //required settings 18452 containerSize: null, 18453 contentSize: null, 18454 18455 //internal state properties 18456 zoomPoint: null, 18457 viewer: null, 18458 18459 //configurable options 18460 springStiffness: $.DEFAULT_SETTINGS.springStiffness, 18461 animationTime: $.DEFAULT_SETTINGS.animationTime, 18462 minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, 18463 maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio, 18464 visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio, 18465 wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, 18466 wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, 18467 defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel, 18468 minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, 18469 maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, 18470 degrees: $.DEFAULT_SETTINGS.degrees, 18471 flipped: $.DEFAULT_SETTINGS.flipped, 18472 homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer 18473 18474 }, options ); 18475 18476 this._updateContainerInnerSize(); 18477 18478 this.centerSpringX = new $.Spring({ 18479 initial: 0, 18480 springStiffness: this.springStiffness, 18481 animationTime: this.animationTime 18482 }); 18483 this.centerSpringY = new $.Spring({ 18484 initial: 0, 18485 springStiffness: this.springStiffness, 18486 animationTime: this.animationTime 18487 }); 18488 this.zoomSpring = new $.Spring({ 18489 exponential: true, 18490 initial: 1, 18491 springStiffness: this.springStiffness, 18492 animationTime: this.animationTime 18493 }); 18494 18495 this._oldCenterX = this.centerSpringX.current.value; 18496 this._oldCenterY = this.centerSpringY.current.value; 18497 this._oldZoom = this.zoomSpring.current.value; 18498 18499 this._setContentBounds(new $.Rect(0, 0, 1, 1), 1); 18500 18501 this.goHome(true); 18502 this.update(); 18503}; 18504 18505/** @lends OpenSeadragon.Viewport.prototype */ 18506$.Viewport.prototype = { 18507 /**
18508 * Updates the viewport's home bounds and constraints for the given content size. 18509 * @function 18510 * @param {OpenSeadragon.Point} contentSize - size of the content in content units 18511 * @return {OpenSeadragon.Viewport} Chainable. 18512 * @fires OpenSeadragon.Viewer.event:reset-size 18513 */ 18514 resetContentSize: function(contentSize) { 18515 $.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required"); 18516 $.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point"); 18517 $.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0"); 18518 $.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0"); 18519 18520 this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x); 18521 return this; 18522 }, 18523 18524 // deprecated 18525 setHomeBounds: function(bounds, contentFactor) { 18526 $.console.error("[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually."); 18527 this._setContentBounds(bounds, contentFactor); 18528 }, 18529 18530 // Set the viewport's content bounds 18531 // @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates 18532 // without rotation 18533 // @param {Number} contentFactor - how many content units per viewport unit 18534 // @fires OpenSeadragon.Viewer.event:reset-size 18535 // @private 18536 _setContentBounds: function(bounds, contentFactor) { 18537 $.console.assert(bounds, "[Viewport._setContentBounds] bounds is required"); 18538 $.console.assert(bounds instanceof $.Rect, "[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect"); 18539 $.console.assert(bounds.width > 0, "[Viewport._setContentBounds] bounds.width must be greater than 0"); 18540 $.console.assert(bounds.height > 0, "[Viewport._setContentBounds] bounds.height must be greater than 0"); 18541 18542 this._contentBoundsNoRotate = bounds.clone(); 18543 this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times( 18544 contentFactor); 18545 18546 this._contentBounds = bounds.rotate(this.degrees).getBoundingBox(); 18547 this._contentSize = this._contentBounds.getSize().times(contentFactor); 18548 this._contentAspectRatio = this._contentSize.x / this._contentSize.y; 18549 18550 if (this.viewer) { 18551 /** 18552 * Raised when the viewer's content size or home bounds are reset 18553 * (see {@link OpenSeadragon.Viewport#resetContentSize}). 18554 * 18555 * @event reset-size 18556 * @memberof OpenSeadragon.Viewer 18557 * @type {object} 18558 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 18559 * @property {OpenSeadragon.Point} contentSize 18560 * @property {OpenSeadragon.Rect} contentBounds - Content bounds. 18561 * @property {OpenSeadragon.Rect} homeBounds - Content bounds. 18562 * Deprecated use contentBounds instead. 18563 * @property {Number} contentFactor 18564 * @property {?Object} userData - Arbitrary subscriber-defined object. 18565 */ 18566 this.viewer.raiseEvent('reset-size', { 18567 contentSize: this._contentSizeNoRotate.clone(), 18568 contentFactor: contentFactor, 18569 homeBounds: this._contentBoundsNoRotate.clone(), 18570 contentBounds: this._contentBounds.clone() 18571 }); 18572 } 18573 }, 18574 18575 /** 18576 * Returns the home zoom in "viewport zoom" value. 18577 * @function 18578 * @returns {Number} The home zoom in "viewport zoom". 18579 */ 18580 getHomeZoom: function() { 18581 if (this.defaultZoomLevel) { 18582 return this.defaultZoomLevel; 18583 } 18584 18585 var aspectFactor = this._contentAspectRatio / this.getAspectRatio(); 18586 var output; 18587 if (this.homeFillsViewer) { // fill the viewer and clip the image 18588 output = aspectFactor >= 1 ? aspectFactor : 1; 18589 } else { 18590 output = aspectFactor >= 1 ? 1 : aspectFactor; 18591 } 18592 18593 return output / this._contentBounds.width; 18594 }, 18595 18596 /** 18597 * Returns the home bounds in viewport coordinates. 18598 * @function 18599 * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates. 18600 */ 18601 getHomeBounds: function() { 18602 return this.getHomeBoundsNoRotate().rotate(-this.getRotation()); 18603 }, 18604 18605 /** 18606 * Returns the home bounds in viewport coordinates. 18607 * This method ignores the viewport rotation. Use 18608 * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account. 18609 * @function 18610 * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates. 18611 */ 18612 getHomeBoundsNoRotate: function() { 18613 var center = this._contentBounds.getCenter(); 18614 var width = 1.0 / this.getHomeZoom(); 18615 var height = width / this.getAspectRatio(); 18616 18617 return new $.Rect( 18618 center.x - (width / 2.0), 18619 center.y - (height / 2.0), 18620 width, 18621 height 18622 ); 18623 }, 18624 18625 /** 18626 * @function 18627 * @param {Boolean} immediately 18628 * @fires OpenSeadragon.Viewer.event:home 18629 */ 18630 goHome: function(immediately) { 18631 if (this.viewer) { 18632 /** 18633 * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). 18634 * 18635 * @event home 18636 * @memberof OpenSeadragon.Viewer 18637 * @type {object} 18638 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 18639 * @property {Boolean} immediately
vendor: 5,300 bytes, lines 18640-18797
18640 * @property {?Object} userData - Arbitrary subscriber-defined object. 18641 */ 18642 this.viewer.raiseEvent('home', { 18643 immediately: immediately 18644 }); 18645 } 18646 return this.fitBounds(this.getHomeBounds(), immediately); 18647 }, 18648 18649 /** 18650 * @function 18651 */ 18652 getMinZoom: function() { 18653 var homeZoom = this.getHomeZoom(), 18654 zoom = this.minZoomLevel ? 18655 this.minZoomLevel : 18656 this.minZoomImageRatio * homeZoom; 18657 18658 return zoom; 18659 }, 18660 18661 /** 18662 * @function 18663 */ 18664 getMaxZoom: function() { 18665 var zoom = this.maxZoomLevel; 18666 if (!zoom) { 18667 zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x; 18668 zoom /= this._contentBounds.width; 18669 } 18670 18671 return Math.max( zoom, this.getHomeZoom() ); 18672 }, 18673 18674 /** 18675 * @function 18676 */ 18677 getAspectRatio: function() { 18678 return this._containerInnerSize.x / this._containerInnerSize.y; 18679 }, 18680 18681 /** 18682 * @function 18683 * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates. 18684 */ 18685 getContainerSize: function() { 18686 return new $.Point( 18687 this.containerSize.x, 18688 this.containerSize.y 18689 ); 18690 }, 18691 18692 /** 18693 * The margins push the "home" region in from the sides by the specified amounts. 18694 * @function 18695 * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom. 18696 */ 18697 getMargins: function() { 18698 return $.extend({}, this._margins); // Make a copy so we are not returning our original 18699 }, 18700 18701 /** 18702 * The margins push the "home" region in from the sides by the specified amounts. 18703 * @function 18704 * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom. 18705 */ 18706 setMargins: function(margins) { 18707 $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object'); 18708 18709 this._margins = $.extend({ 18710 left: 0, 18711 top: 0, 18712 right: 0, 18713 bottom: 0 18714 }, margins); 18715 18716 this._updateContainerInnerSize(); 18717 if (this.viewer) { 18718 this.viewer.forceRedraw(); 18719 } 18720 }, 18721 18722 /** 18723 * Returns the bounds of the visible area in viewport coordinates. 18724 * @function 18725 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18726 * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates. 18727 */ 18728 getBounds: function(current) { 18729 return this.getBoundsNoRotate(current).rotate(-this.getRotation()); 18730 }, 18731 18732 /** 18733 * Returns the bounds of the visible area in viewport coordinates. 18734 * This method ignores the viewport rotation. Use 18735 * {@link OpenSeadragon.Viewport#getBounds} to take it into account. 18736 * @function 18737 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18738 * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates. 18739 */ 18740 getBoundsNoRotate: function(current) { 18741 var center = this.getCenter(current); 18742 var width = 1.0 / this.getZoom(current); 18743 var height = width / this.getAspectRatio(); 18744 18745 return new $.Rect( 18746 center.x - (width / 2.0), 18747 center.y - (height / 2.0), 18748 width, 18749 height 18750 ); 18751 }, 18752 18753 /** 18754 * @function 18755 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18756 * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, 18757 * including the space taken by margins, in viewport coordinates. 18758 */ 18759 getBoundsWithMargins: function(current) { 18760 return this.getBoundsNoRotateWithMargins(current).rotate( 18761 -this.getRotation(), this.getCenter(current)); 18762 }, 18763 18764 /** 18765 * @function 18766 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18767 * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, 18768 * including the space taken by margins, in viewport coordinates. 18769 */ 18770 getBoundsNoRotateWithMargins: function(current) { 18771 var bounds = this.getBoundsNoRotate(current); 18772 var factor = this._containerInnerSize.x * this.getZoom(current); 18773 bounds.x -= this._margins.left / factor; 18774 bounds.y -= this._margins.top / factor; 18775 bounds.width += (this._margins.left + this._margins.right) / factor; 18776 bounds.height += (this._margins.top + this._margins.bottom) / factor; 18777 return bounds; 18778 }, 18779 18780 /** 18781 * @function 18782 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18783 */ 18784 getCenter: function( current ) { 18785 var centerCurrent = new $.Point( 18786 this.centerSpringX.current.value, 18787 this.centerSpringY.current.value 18788 ), 18789 centerTarget = new $.Point( 18790 this.centerSpringX.target.value, 18791 this.centerSpringY.target.value 18792 ), 18793 oldZoomPixel, 18794 zoom, 18795 width, 18796 height, 18797 bounds,
18798 newZoomPixel, 18799 deltaZoomPixels, 18800 deltaZoomPoints; 18801 18802 if ( current ) { 18803 return centerCurrent; 18804 } else if ( !this.zoomPoint ) { 18805 return centerTarget; 18806 } 18807 18808 oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true); 18809 18810 zoom = this.getZoom(); 18811 width = 1.0 / zoom; 18812 height = width / this.getAspectRatio(); 18813 bounds = new $.Rect( 18814 centerCurrent.x - width / 2.0, 18815 centerCurrent.y - height / 2.0, 18816 width, 18817 height 18818 ); 18819 18820 newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds); 18821 deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); 18822 deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom ); 18823 18824 return centerTarget.plus( deltaZoomPoints ); 18825 }, 18826 18827 /** 18828 * @function 18829 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 18830 */ 18831 getZoom: function( current ) { 18832 if ( current ) { 18833 return this.zoomSpring.current.value; 18834 } else { 18835 return this.zoomSpring.target.value; 18836 } 18837 }, 18838 18839 // private 18840 _applyZoomConstraints: function(zoom) { 18841 return Math.max( 18842 Math.min(zoom, this.getMaxZoom()), 18843 this.getMinZoom()); 18844 }, 18845 18846 /** 18847 * @function 18848 * @private 18849 * @param {OpenSeadragon.Rect} bounds 18850 * @return {OpenSeadragon.Rect} constrained bounds. 18851 */ 18852 _applyBoundaryConstraints: function(bounds) { 18853 var newBounds = new $.Rect( 18854 bounds.x, 18855 bounds.y, 18856 bounds.width, 18857 bounds.height); 18858 18859 if (this.wrapHorizontal) { 18860 //do nothing 18861 } else { 18862 var horizontalThreshold = this.visibilityRatio * newBounds.width; 18863 var boundsRight = newBounds.x + newBounds.width; 18864 var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width; 18865 var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold; 18866 var rightDx = contentRight - newBounds.x - horizontalThreshold; 18867 18868 if (horizontalThreshold > this._contentBoundsNoRotate.width) { 18869 newBounds.x += (leftDx + rightDx) / 2; 18870 } else if (rightDx < 0) { 18871 newBounds.x += rightDx; 18872 } else if (leftDx > 0) { 18873 newBounds.x += leftDx; 18874 } 18875 } 18876 18877 if (this.wrapVertical) { 18878 //do nothing 18879 } else { 18880 var verticalThreshold = this.visibilityRatio * newBounds.height; 18881 var boundsBottom = newBounds.y + newBounds.height; 18882 var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height; 18883 var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold; 18884 var bottomDy = contentBottom - newBounds.y - verticalThreshold; 18885 18886 if (verticalThreshold > this._contentBoundsNoRotate.height) { 18887 newBounds.y += (topDy + bottomDy) / 2; 18888 } else if (bottomDy < 0) { 18889 newBounds.y += bottomDy; 18890 } else if (topDy > 0) { 18891 newBounds.y += topDy; 18892 } 18893 } 18894 18895 return newBounds; 18896 }, 18897 18898 /** 18899 * @function 18900 * @private 18901 * @param {Boolean} [immediately=false] - whether the function that triggered this event was 18902 * called with the "immediately" flag 18903 */ 18904 _raiseConstraintsEvent: function(immediately) { 18905 if (this.viewer) { 18906 /** 18907 * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). 18908 * 18909 * @event constrain 18910 * @memberof OpenSeadragon.Viewer 18911 * @type {object} 18912 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 18913 * @property {Boolean} immediately - whether the function that triggered this event was 18914 * called with the "immediately" flag
18915 * @property {?Object} userData - Arbitrary subscriber-defined object. 18916 */ 18917 this.viewer.raiseEvent( 'constrain', { 18918 immediately: immediately 18919 }); 18920 } 18921 }, 18922 18923 /** 18924 * Enforces the minZoom, maxZoom and visibilityRatio constraints by 18925 * zooming and panning to the closest acceptable zoom and location. 18926 * @function 18927 * @param {Boolean} [immediately=false] 18928 * @return {OpenSeadragon.Viewport} Chainable. 18929 * @fires OpenSeadragon.Viewer.event:constrain 18930 */ 18931 applyConstraints: function(immediately) { 18932 var actualZoom = this.getZoom(); 18933 var constrainedZoom = this._applyZoomConstraints(actualZoom); 18934 18935 if (actualZoom !== constrainedZoom) { 18936 this.zoomTo(constrainedZoom, this.zoomPoint, immediately); 18937 } 18938 18939 var bounds = this.getBoundsNoRotate(); 18940 var constrainedBounds = this._applyBoundaryConstraints(bounds); 18941 this._raiseConstraintsEvent(immediately); 18942 18943 if (bounds.x !== constrainedBounds.x || 18944 bounds.y !== constrainedBounds.y || 18945 immediately) { 18946 this.fitBounds( 18947 constrainedBounds.rotate(-this.getRotation()), 18948 immediately); 18949 } 18950 return this; 18951 }, 18952 18953 /** 18954 * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints} 18955 * @function 18956 * @param {Boolean} [immediately=false] 18957 * @return {OpenSeadragon.Viewport} Chainable. 18958 * @fires OpenSeadragon.Viewer.event:constrain 18959 */ 18960 ensureVisible: function(immediately) { 18961 return this.applyConstraints(immediately); 18962 }, 18963 18964 /** 18965 * @function 18966 * @private 18967 * @param {OpenSeadragon.Rect} bounds 18968 * @param {Object} options (immediately=false, constraints=false) 18969 * @return {OpenSeadragon.Viewport} Chainable. 18970 */ 18971 _fitBounds: function(bounds, options) { 18972 options = options || {}; 18973 var immediately = options.immediately || false; 18974 var constraints = options.constraints || false; 18975 18976 var aspect = this.getAspectRatio(); 18977 var center = bounds.getCenter(); 18978 18979 // Compute width and height of bounding box. 18980 var newBounds = new $.Rect( 18981 bounds.x, 18982 bounds.y, 18983 bounds.width, 18984 bounds.height, 18985 bounds.degrees + this.getRotation()) 18986 .getBoundingBox(); 18987 18988 if (newBounds.getAspectRatio() >= aspect) { 18989 newBounds.height = newBounds.width / aspect; 18990 } else { 18991 newBounds.width = newBounds.height * aspect; 18992 } 18993 18994 // Compute x and y from width, height and center position 18995 newBounds.x = center.x - newBounds.width / 2; 18996 newBounds.y = center.y - newBounds.height / 2; 18997 var newZoom = 1.0 / newBounds.width; 18998 18999 if (constraints) { 19000 var newBoundsAspectRatio = newBounds.getAspectRatio(); 19001 var newConstrainedZoom = this._applyZoomConstraints(newZoom); 19002 19003 if (newZoom !== newConstrainedZoom) { 19004 newZoom = newConstrainedZoom; 19005 newBounds.width = 1.0 / newZoom; 19006 newBounds.x = center.x - newBounds.width / 2; 19007 newBounds.height = newBounds.width / newBoundsAspectRatio; 19008 newBounds.y = center.y - newBounds.height / 2; 19009 } 19010 19011 newBounds = this._applyBoundaryConstraints(newBounds); 19012 center = newBounds.getCenter(); 19013 this._raiseConstraintsEvent(immediately); 19014 } 19015 19016 if (immediately) { 19017 this.panTo(center, true); 19018 return this.zoomTo(newZoom, null, true); 19019 } 19020 19021 this.panTo(this.getCenter(true), true); 19022 this.zoomTo(this.getZoom(true), null, true); 19023 19024 var oldBounds = this.getBounds(); 19025 var oldZoom = this.getZoom(); 19026 19027 if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) { 19028 this.zoomTo(newZoom, true); 19029 return this.panTo(center, immediately); 19030 } 19031 19032 newBounds = newBounds.rotate(-this.getRotation()); 19033 var referencePoint = newBounds.getTopLeft().times(newZoom) 19034 .minus(oldBounds.getTopLeft().times(oldZoom)) 19035 .divide(newZoom - oldZoom); 19036 19037 return this.zoomTo(newZoom, referencePoint, immediately); 19038 }, 19039 19040 /** 19041 * Makes the viewport zoom and pan so that the specified boun
19041ds take 19042 * as much space as possible in the viewport. 19043 * Note: this method ignores the constraints (minZoom, maxZoom and 19044 * visibilityRatio). 19045 * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce 19046 * them. 19047 * @function 19048 * @param {OpenSeadragon.Rect} bounds 19049 * @param {Boolean} [immediately=false] 19050 * @return {OpenSeadragon.Viewport} Chainable. 19051 */ 19052 fitBounds: function(bounds, immediately) { 19053 return this._fitBounds(bounds, { 19054 immediately: immediately, 19055 constraints: false 19056 }); 19057 }, 19058 19059 /** 19060 * Makes the viewport zoom and pan so that the specified bounds take 19061 * as much space as possible in the viewport while enforcing the constraints 19062 * (minZoom, maxZoom and visibilityRatio). 19063 * Note: because this method enforces the constraints, part of the 19064 * provided bounds may end up outside of the viewport. 19065 * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them. 19066 * @function 19067 * @param {OpenSeadragon.Rect} bounds 19068 * @param {Boolean} [immediately=false] 19069 * @return {OpenSeadragon.Viewport} Chainable. 19070 */ 19071 fitBoundsWithConstraints: function(bounds, immediately) { 19072 return this._fitBounds(bounds, { 19073 immediately: immediately, 19074 constraints: true 19075 }); 19076 }, 19077 19078 /** 19079 * Zooms so the image just fills the viewer vertically. 19080 * @param {Boolean} immediately 19081 * @return {OpenSeadragon.Viewport} Chainable. 19082 */ 19083 fitVertically: function(immediately) { 19084 var box = new $.Rect( 19085 this._contentBounds.x + (this._contentBounds.width / 2), 19086 this._contentBounds.y, 19087 0, 19088 this._contentBounds.height); 19089 return this.fitBounds(box, immediately); 19090 }, 19091 19092 /** 19093 * Zooms so the image just fills the viewer horizontally. 19094 * @param {Boolean} immediately 19095 * @return {OpenSeadragon.Viewport} Chainable. 19096 */ 19097 fitHorizontally: function(immediately) { 19098 var box = new $.Rect( 19099 this._contentBounds.x, 19100 this._contentBounds.y + (this._contentBounds.height / 2), 19101 this._contentBounds.width, 19102 0); 19103 return this.fitBounds(box, immediately); 19104 }, 19105 19106 19107 /** 19108 * Returns bounds taking constraints into account 19109 * Added to improve constrained panning 19110 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 19111 * @return {OpenSeadragon.Viewport} Chainable. 19112 */ 19113 getConstrainedBounds: function(current) { 19114 var bounds, 19115 constrainedBounds; 19116 19117 bounds = this.getBounds(current); 19118 19119 constrainedBounds = this._applyBoundaryConstraints(bounds); 19120 19121 return constrainedBounds; 19122 }, 19123 19124 /** 19125 * @function 19126 * @param {OpenSeadragon.Point} delta 19127 * @param {Boolean} immediately 19128 * @return {OpenSeadragon.Viewport} Chainable. 19129 * @fires OpenSeadragon.Viewer.event:pan 19130 */ 19131 panBy: function( delta, immediately ) { 19132 var center = new $.Point( 19133 this.centerSpringX.target.value, 19134 this.centerSpringY.target.value 19135 ); 19136 return this.panTo( center.plus( delta ), immediately ); 19137 }, 19138 19139 /** 19140 * @function 19141 * @param {OpenSeadragon.Point} center 19142 * @param {Boolean} immediately 19143 * @return {OpenSeadragon.Viewport} Chainable. 19144 * @fires OpenSeadragon.Viewer.event:pan 19145 */ 19146 panTo: function( center, immediately ) { 19147 if ( immediately ) { 19148 this.centerSpringX.resetTo( center.x ); 19149 this.centerSpringY.resetTo( center.y ); 19150 } else { 19151 this.centerSpringX.springTo( center.x ); 19152 this.centerSpringY.springTo( center.y ); 19153 } 19154 19155 if( this.viewer ){ 19156 /** 19157 * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}). 19158 * 19159 * @event pan 19160 * @memberof OpenSeadragon.Viewer 19161 * @type {object} 19162 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 19163 * @property {OpenSeadragon.Point} center 19164 * @property {Boolean} immediately
19165 * @property {?Object} userData - Arbitrary subscriber-defined object. 19166 */ 19167 this.viewer.raiseEvent( 'pan', { 19168 center: center, 19169 immediately: immediately 19170 }); 19171 } 19172 19173 return this; 19174 }, 19175 19176 /** 19177 * @function 19178 * @return {OpenSeadragon.Viewport} Chainable. 19179 * @fires OpenSeadragon.Viewer.event:zoom 19180 */ 19181 zoomBy: function(factor, refPoint, immediately) { 19182 return this.zoomTo( 19183 this.zoomSpring.target.value * factor, refPoint, immediately); 19184 }, 19185 19186 /** 19187 * Zooms to the specified zoom level 19188 * @function 19189 * @param {Number} zoom The zoom level to zoom to. 19190 * @param {OpenSeadragon.Point} [refPoint] The point which will stay at 19191 * the same screen location. Defaults to the viewport center. 19192 * @param {Boolean} [immediately=false] 19193 * @return {OpenSeadragon.Viewport} Chainable. 19194 * @fires OpenSeadragon.Viewer.event:zoom 19195 */ 19196 zoomTo: function(zoom, refPoint, immediately) { 19197 var _this = this; 19198 19199 this.zoomPoint = refPoint instanceof $.Point && 19200 !isNaN(refPoint.x) && 19201 !isNaN(refPoint.y) ? 19202 refPoint : 19203 null; 19204 19205 if (immediately) { 19206 this._adjustCenterSpringsForZoomPoint(function() { 19207 _this.zoomSpring.resetTo(zoom); 19208 }); 19209 } else { 19210 this.zoomSpring.springTo(zoom); 19211 } 19212 19213 if (this.viewer) { 19214 /** 19215 * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}). 19216 * 19217 * @event zoom 19218 * @memberof OpenSeadragon.Viewer 19219 * @type {object} 19220 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 19221 * @property {Number} zoom 19222 * @property {OpenSeadragon.Point} refPoint 19223 * @property {Boolean} immediately 19224 * @property {?Object} userData - Arbitrary subscriber-defined object. 19225 */ 19226 this.viewer.raiseEvent('zoom', { 19227 zoom: zoom, 19228 refPoint: refPoint, 19229 immediately: immediately 19230 }); 19231 } 19232 19233 return this; 19234 }, 19235 19236 /** 19237 * Rotates this viewport to the angle specified. 19238 * @function 19239 * @param {Number} degrees The degrees to set the rotation to. 19240 * @return {OpenSeadragon.Viewport} Chainable. 19241 */ 19242 setRotation: function(degrees) { 19243 if (!this.viewer || !this.viewer.drawer.canRotate()) { 19244 return this; 19245 } 19246 this.degrees = $.positiveModulo(degrees, 360); 19247 this._setContentBounds( 19248 this.viewer.world.getHomeBounds(), 19249 this.viewer.world.getContentFactor()); 19250 this.viewer.forceRedraw(); 19251 19252 /** 19253 * Raised when rotation has been changed. 19254 * 19255 * @event rotate 19256 * @memberof OpenSeadragon.Viewer 19257 * @type {object} 19258 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 19259 * @property {Number} degrees - The number of degrees the rotation was set to. 19260 * @property {?Object} userData - Arbitrary subscriber-defined object. 19261 */ 19262 this.viewer.raiseEvent('rotate', {"degrees": degrees}); 19263 return this; 19264 }, 19265 19266 /** 19267 * Gets the current rotation in degrees. 19268 * @function 19269 * @return {Number} The current rotation in degrees. 19270 */ 19271 getRotation: function() { 19272 return this.degrees; 19273 }, 19274 19275 /** 19276 * @function 19277 * @return {OpenSeadragon.Viewport} Chainable. 19278 * @fires OpenSeadragon.Viewer.event:resize 19279 */ 19280 resize: function( newContainerSize, maintain ) { 19281 var oldBounds = this.getBoundsNoRotate(), 19282 newBounds = oldBounds, 19283 widthDeltaFactor; 19284 19285 this.containerSize.x = newContainerSize.x; 19286 this.containerSize.y = newContainerSize.y; 19287 19288 this._updateContainerInnerSize(); 19289 19290 if ( maintain ) { 19291 // TODO: widthDeltaFactor will always be 1; probably not what's intended 19292 widthDeltaFactor = newContainerSize.x / this.containerSize.x; 19293 newBounds.width = oldBounds.width * widthDeltaFactor; 19294 newBounds.height = newBounds.width / this.getAspectRatio(); 19295 } 19296 19297 if( this.viewer ){ 19298 /** 19299 * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}). 19300 * 19301 * @event resize 19302 * @memberof OpenSeadragon.Viewer 19303 * @type {object} 19304 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 19305 * @property {OpenSeadragon.Point} newContainerSize 19306 * @property {Boolean} maintain
vendor: 16,384 bytes, lines 19307-19706
19307 * @property {?Object} userData - Arbitrary subscriber-defined object. 19308 */ 19309 this.viewer.raiseEvent( 'resize', { 19310 newContainerSize: newContainerSize, 19311 maintain: maintain 19312 }); 19313 } 19314 19315 return this.fitBounds( newBounds, true ); 19316 }, 19317 19318 // private 19319 _updateContainerInnerSize: function() { 19320 this._containerInnerSize = new $.Point( 19321 Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)), 19322 Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom)) 19323 ); 19324 }, 19325 19326 /** 19327 * Update the zoom and center (X and Y) springs. 19328 * @function 19329 * @returns {Boolean} True if any change has been made, false otherwise. 19330 */ 19331 update: function() { 19332 var _this = this; 19333 this._adjustCenterSpringsForZoomPoint(function() { 19334 _this.zoomSpring.update(); 19335 }); 19336 19337 this.centerSpringX.update(); 19338 this.centerSpringY.update(); 19339 19340 var changed = this.centerSpringX.current.value !== this._oldCenterX || 19341 this.centerSpringY.current.value !== this._oldCenterY || 19342 this.zoomSpring.current.value !== this._oldZoom; 19343 19344 this._oldCenterX = this.centerSpringX.current.value; 19345 this._oldCenterY = this.centerSpringY.current.value; 19346 this._oldZoom = this.zoomSpring.current.value; 19347 19348 return changed; 19349 }, 19350 19351 _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) { 19352 if (this.zoomPoint) { 19353 var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true); 19354 zoomSpringHandler(); 19355 var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true); 19356 19357 var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel); 19358 var deltaZoomPoints = this.deltaPointsFromPixels( 19359 deltaZoomPixels, true); 19360 19361 this.centerSpringX.shiftBy(deltaZoomPoints.x); 19362 this.centerSpringY.shiftBy(deltaZoomPoints.y); 19363 19364 if (this.zoomSpring.isAtTargetValue()) { 19365 this.zoomPoint = null; 19366 } 19367 } else { 19368 zoomSpringHandler(); 19369 } 19370 }, 19371 19372 /** 19373 * Convert a delta (translation vector) from viewport coordinates to pixels 19374 * coordinates. This method does not take rotation into account. 19375 * Consider using deltaPixelsFromPoints if you need to account for rotation. 19376 * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert. 19377 * @param {Boolean} [current=false] - Pass true for the current location; 19378 * defaults to false (target location). 19379 * @returns {OpenSeadragon.Point} 19380 */ 19381 deltaPixelsFromPointsNoRotate: function(deltaPoints, current) { 19382 return deltaPoints.times( 19383 this._containerInnerSize.x * this.getZoom(current) 19384 ); 19385 }, 19386 19387 /** 19388 * Convert a delta (translation vector) from viewport coordinates to pixels 19389 * coordinates. 19390 * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert. 19391 * @param {Boolean} [current=false] - Pass true for the current location; 19392 * defaults to false (target location). 19393 * @returns {OpenSeadragon.Point} 19394 */ 19395 deltaPixelsFromPoints: function(deltaPoints, current) { 19396 return this.deltaPixelsFromPointsNoRotate( 19397 deltaPoints.rotate(this.getRotation()), 19398 current); 19399 }, 19400 19401 /** 19402 * Convert a delta (translation vector) from pixels coordinates to viewport 19403 * coordinates. This method does not take rotation into account. 19404 * Consider using deltaPointsFromPixels if you need to account for rotation. 19405 * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert. 19406 * @param {Boolean} [current=false] - Pass true for the current location; 19407 * defaults to false (target location). 19408 * @returns {OpenSeadragon.Point} 19409 */ 19410 deltaPointsFromPixelsNoRotate: function(deltaPixels, current) { 19411 return deltaPixels.divide( 19412 this._containerInnerSize.x * this.getZoom(current) 19413 ); 19414 }, 19415 19416 /** 19417 * Convert a delta (translation vector) from pixels coordinates to viewport 19418 * coordinates. 19419 * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert. 19420 * @param {Boolean} [current=false] - Pass true for the current location; 19421 * defaults to false (target location). 19422 * @returns {OpenSeadragon.Point} 19423 */ 19424 deltaPointsFromPixels: function(deltaPixels, current) { 19425 return this.deltaPointsFromPixelsNoRotate(deltaPixels, current) 19426 .rotate(-this.getRotation()); 19427 }, 19428 19429 /** 19430 * Convert viewport coordinates to pixels coordinates. 19431 * This method does not take rotation into account. 19432 * Consider using pixelFromPoint if you need to account for rotation. 19433 * @param {OpenSeadragon.Point} point the viewport coordinates 19434 * @param {Boolean} [current=false] - Pass true for the current location; 19435 * defaults to false (target location). 19436 * @returns {OpenSeadragon.Point} 19437 */ 19438 pixelFromPointNoRotate: function(point, current) { 19439 return this._pixelFromPointNoRotate( 19440 point, this.getBoundsNoRotate(current)); 19441 }, 19442 19443 /** 19444 * Convert viewport coordinates to pixel coordinates. 19445 * @param {OpenSeadragon.Point} point the viewport coordinates 19446 * @param {Boolean} [current=false] - Pass true for the current location; 19447 * defaults to false (target location). 19448 * @returns {OpenSeadragon.Point} 19449 */ 19450 pixelFromPoint: function(point, current) { 19451 return this._pixelFromPoint(point, this.getBoundsNoRotate(current)); 19452 }, 19453 19454 // private 19455 _pixelFromPointNoRotate: function(point, bounds) { 19456 return point.minus( 19457 bounds.getTopLeft() 19458 ).times( 19459 this._containerInnerSize.x / bounds.width 19460 ).plus( 19461 new $.Point(this._margins.left, this._margins.top) 19462 ); 19463 }, 19464 19465 // private 19466 _pixelFromPoint: function(point, bounds) { 19467 return this._pixelFromPointNoRotate( 19468 point.rotate(this.getRotation(), this.getCenter(true)), 19469 bounds); 19470 }, 19471 19472 /** 19473 * Convert pixel coordinates to viewport coordinates. 19474 * This method does not take rotation into account. 19475 * Consider using pointFromPixel if you need to account for rotation. 19476 * @param {OpenSeadragon.Point} pixel Pixel coordinates 19477 * @param {Boolean} [current=false] - Pass true for the current location; 19478 * defaults to false (target location). 19479 * @returns {OpenSeadragon.Point} 19480 */ 19481 pointFromPixelNoRotate: function(pixel, current) { 19482 var bounds = this.getBoundsNoRotate(current); 19483 return pixel.minus( 19484 new $.Point(this._margins.left, this._margins.top) 19485 ).divide( 19486 this._containerInnerSize.x / bounds.width 19487 ).plus( 19488 bounds.getTopLeft() 19489 ); 19490 }, 19491 19492 /** 19493 * Convert pixel coordinates to viewport coordinates. 19494 * @param {OpenSeadragon.Point} pixel Pixel coordinates 19495 * @param {Boolean} [current=false] - Pass true for the current location; 19496 * defaults to false (target location). 19497 * @returns {OpenSeadragon.Point} 19498 */ 19499 pointFromPixel: function(pixel, current) { 19500 return this.pointFromPixelNoRotate(pixel, current).rotate( 19501 -this.getRotation(), 19502 this.getCenter(true) 19503 ); 19504 }, 19505 19506 // private 19507 _viewportToImageDelta: function( viewerX, viewerY ) { 19508 var scale = this._contentBoundsNoRotate.width; 19509 return new $.Point( 19510 viewerX * this._contentSizeNoRotate.x / scale, 19511 viewerY * this._contentSizeNoRotate.x / scale); 19512 }, 19513 19514 /** 19515 * Translates from OpenSeadragon viewer coordinate system to image coordinate system. 19516 * This method can be called either by passing X,Y coordinates or an 19517 * OpenSeadragon.Point 19518 * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead. 19519 * @function 19520 * @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X 19521 * coordinate in viewport coordinate system. 19522 * @param {Number} [viewerY] Y coordinate in viewport coordinate system. 19523 * @return {OpenSeadragon.Point} a point representing the coordinates in the image. 19524 */ 19525 viewportToImageCoordinates: function(viewerX, viewerY) { 19526 if (viewerX instanceof $.Point) { 19527 //they passed a point instead of individual components 19528 return this.viewportToImageCoordinates(viewerX.x, viewerX.y); 19529 } 19530 19531 if (this.viewer) { 19532 var count = this.viewer.world.getItemCount(); 19533 if (count > 1) { 19534 $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' + 19535 'with multi-image; use TiledImage.viewportToImageCoordinates instead.'); 19536 } else if (count === 1) { 19537 // It is better to use TiledImage.viewportToImageCoordinates 19538 // because this._contentBoundsNoRotate can not be relied on 19539 // with clipping. 19540 var item = this.viewer.world.getItemAt(0); 19541 return item.viewportToImageCoordinates(viewerX, viewerY, true); 19542 } 19543 } 19544 19545 return this._viewportToImageDelta( 19546 viewerX - this._contentBoundsNoRotate.x, 19547 viewerY - this._contentBoundsNoRotate.y); 19548 }, 19549 19550 // private 19551 _imageToViewportDelta: function( imageX, imageY ) { 19552 var scale = this._contentBoundsNoRotate.width; 19553 return new $.Point( 19554 imageX / this._contentSizeNoRotate.x * scale, 19555 imageY / this._contentSizeNoRotate.x * scale); 19556 }, 19557 19558 /** 19559 * Translates from image coordinate system to OpenSeadragon viewer coordinate system 19560 * This method can be called either by passing X,Y coordinates or an 19561 * OpenSeadragon.Point 19562 * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead. 19563 * @function 19564 * @param {(OpenSeadragon.Point | Number)} imageX the point or the 19565 * X coordinate in image coordinate system. 19566 * @param {Number} [imageY] Y coordinate in image coordinate system. 19567 * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport. 19568 */ 19569 imageToViewportCoordinates: function(imageX, imageY) { 19570 if (imageX instanceof $.Point) { 19571 //they passed a point instead of individual components 19572 return this.imageToViewportCoordinates(imageX.x, imageX.y); 19573 } 19574 19575 if (this.viewer) { 19576 var count = this.viewer.world.getItemCount(); 19577 if (count > 1) { 19578 $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' + 19579 'with multi-image; use TiledImage.imageToViewportCoordinates instead.'); 19580 } else if (count === 1) { 19581 // It is better to use TiledImage.viewportToImageCoordinates 19582 // because this._contentBoundsNoRotate can not be relied on 19583 // with clipping. 19584 var item = this.viewer.world.getItemAt(0); 19585 return item.imageToViewportCoordinates(imageX, imageY, true); 19586 } 19587 } 19588 19589 var point = this._imageToViewportDelta(imageX, imageY); 19590 point.x += this._contentBoundsNoRotate.x; 19591 point.y += this._contentBoundsNoRotate.y; 19592 return point; 19593 }, 19594 19595 /** 19596 * Translates from a rectangle which describes a portion of the image in 19597 * pixel coordinates to OpenSeadragon viewport rectangle coordinates. 19598 * This method can be called either by passing X,Y,width,height or an 19599 * OpenSeadragon.Rect 19600 * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead. 19601 * @function 19602 * @param {(OpenSeadragon.Rect | Number)} imageX the rectangle or the X 19603 * coordinate of the top left corner of the rectangle in image coordinate system. 19604 * @param {Number} [imageY] the Y coordinate of the top left corner of the rectangle 19605 * in image coordinate system. 19606 * @param {Number} [pixelWidth] the width in pixel of the rectangle. 19607 * @param {Number} [pixelHeight] the height in pixel of the rectangle. 19608 * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates 19609 */ 19610 imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) { 19611 var rect = imageX; 19612 if (!(rect instanceof $.Rect)) { 19613 //they passed individual components instead of a rectangle 19614 rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight); 19615 } 19616 19617 if (this.viewer) { 19618 var count = this.viewer.world.getItemCount(); 19619 if (count > 1) { 19620 $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' + 19621 'with multi-image; use TiledImage.imageToViewportRectangle instead.'); 19622 } else if (count === 1) { 19623 // It is better to use TiledImage.imageToViewportRectangle 19624 // because this._contentBoundsNoRotate can not be relied on 19625 // with clipping. 19626 var item = this.viewer.world.getItemAt(0); 19627 return item.imageToViewportRectangle( 19628 imageX, imageY, pixelWidth, pixelHeight, true); 19629 } 19630 } 19631 19632 var coordA = this.imageToViewportCoordinates(rect.x, rect.y); 19633 var coordB = this._imageToViewportDelta(rect.width, rect.height); 19634 return new $.Rect( 19635 coordA.x, 19636 coordA.y, 19637 coordB.x, 19638 coordB.y, 19639 rect.degrees 19640 ); 19641 }, 19642 19643 /** 19644 * Translates from a rectangle which describes a portion of 19645 * the viewport in point coordinates to image rectangle coordinates. 19646 * This method can be called either by passing X,Y,width,height or an 19647 * OpenSeadragon.Rect 19648 * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead. 19649 * @function 19650 * @param {(OpenSeadragon.Rect | Number)} viewerX either a rectangle or 19651 * the X coordinate of the top left corner of the rectangle in viewport 19652 * coordinate system. 19653 * @param {Number} [viewerY] the Y coordinate of the top left corner of the rectangle 19654 * in viewport coordinate system. 19655 * @param {Number} [pointWidth] the width of the rectangle in viewport coordinate system. 19656 * @param {Number} [pointHeight] the height of the rectangle in viewport coordinate system. 19657 */ 19658 viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) { 19659 var rect = viewerX; 19660 if (!(rect instanceof $.Rect)) { 19661 //they passed individual components instead of a rectangle 19662 rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight); 19663 } 19664 19665 if (this.viewer) { 19666 var count = this.viewer.world.getItemCount(); 19667 if (count > 1) { 19668 $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' + 19669 'with multi-image; use TiledImage.viewportToImageRectangle instead.'); 19670 } else if (count === 1) { 19671 // It is better to use TiledImage.viewportToImageCoordinates 19672 // because this._contentBoundsNoRotate can not be relied on 19673 // with clipping. 19674 var item = this.viewer.world.getItemAt(0); 19675 return item.viewportToImageRectangle( 19676 viewerX, viewerY, pointWidth, pointHeight, true); 19677 } 19678 } 19679 19680 var coordA = this.viewportToImageCoordinates(rect.x, rect.y); 19681 var coordB = this._viewportToImageDelta(rect.width, rect.height); 19682 return new $.Rect( 19683 coordA.x, 19684 coordA.y, 19685 coordB.x, 19686 coordB.y, 19687 rect.degrees 19688 ); 19689 }, 19690 19691 /** 19692 * Convert pixel coordinates relative to the viewer element to image 19693 * coordinates. 19694 * Note: not accurate with multi-image. 19695 * @param {OpenSeadragon.Point} pixel 19696 * @returns {OpenSeadragon.Point} 19697 */ 19698 viewerElementToImageCoordinates: function( pixel ) { 19699 var point = this.pointFromPixel( pixel, true ); 19700 return this.viewportToImageCoordinates( point ); 19701 }, 19702 19703 /** 19704 * Convert pixel coordinates relative to the image to 19705 * viewer element coordinates. 19706 * Note: not accurat
19706e with multi-image. 19707 * @param {OpenSeadragon.Point} pixel 19708 * @returns {OpenSeadragon.Point} 19709 */ 19710 imageToViewerElementCoordinates: function( pixel ) { 19711 var point = this.imageToViewportCoordinates( pixel ); 19712 return this.pixelFromPoint( point, true ); 19713 }, 19714 19715 /** 19716 * Convert pixel coordinates relative to the window to image coordinates. 19717 * Note: not accurate with multi-image. 19718 * @param {OpenSeadragon.Point} pixel 19719 * @returns {OpenSeadragon.Point} 19720 */ 19721 windowToImageCoordinates: function(pixel) { 19722 $.console.assert(this.viewer, 19723 "[Viewport.windowToImageCoordinates] the viewport must have a viewer."); 19724 var viewerCoordinates = pixel.minus( 19725 $.getElementPosition(this.viewer.element)); 19726 return this.viewerElementToImageCoordinates(viewerCoordinates); 19727 }, 19728 19729 /** 19730 * Convert image coordinates to pixel coordinates relative to the window. 19731 * Note: not accurate with multi-image. 19732 * @param {OpenSeadragon.Point} pixel 19733 * @returns {OpenSeadragon.Point} 19734 */ 19735 imageToWindowCoordinates: function(pixel) { 19736 $.console.assert(this.viewer, 19737 "[Viewport.imageToWindowCoordinates] the viewport must have a viewer."); 19738 var viewerCoordinates = this.imageToViewerElementCoordinates(pixel); 19739 return viewerCoordinates.plus( 19740 $.getElementPosition(this.viewer.element)); 19741 }, 19742 19743 /** 19744 * Convert pixel coordinates relative to the viewer element to viewport 19745 * coordinates. 19746 * @param {OpenSeadragon.Point} pixel 19747 * @returns {OpenSeadragon.Point} 19748 */ 19749 viewerElementToViewportCoordinates: function( pixel ) { 19750 return this.pointFromPixel( pixel, true ); 19751 }, 19752 19753 /** 19754 * Convert viewport coordinates to pixel coordinates relative to the 19755 * viewer element. 19756 * @param {OpenSeadragon.Point} point 19757 * @returns {OpenSeadragon.Point} 19758 */ 19759 viewportToViewerElementCoordinates: function( point ) { 19760 return this.pixelFromPoint( point, true ); 19761 }, 19762 19763 /** 19764 * Convert a rectangle in pixel coordinates relative to the viewer element 19765 * to viewport coordinates. 19766 * @param {OpenSeadragon.Rect} rectangle the rectangle to convert 19767 * @returns {OpenSeadragon.Rect} the converted rectangle 19768 */ 19769 viewerElementToViewportRectangle: function(rectangle) { 19770 return $.Rect.fromSummits( 19771 this.pointFromPixel(rectangle.getTopLeft(), true), 19772 this.pointFromPixel(rectangle.getTopRight(), true), 19773 this.pointFromPixel(rectangle.getBottomLeft(), true) 19774 ); 19775 }, 19776 19777 /** 19778 * Convert a rectangle in viewport coordinates to pixel coordinates relative 19779 * to the viewer element. 19780 * @param {OpenSeadragon.Rect} rectangle the rectangle to convert 19781 * @returns {OpenSeadragon.Rect} the converted rectangle 19782 */ 19783 viewportToViewerElementRectangle: function(rectangle) { 19784 return $.Rect.fromSummits( 19785 this.pixelFromPoint(rectangle.getTopLeft(), true), 19786 this.pixelFromPoint(rectangle.getTopRight(), true), 19787 this.pixelFromPoint(rectangle.getBottomLeft(), true) 19788 ); 19789 }, 19790 19791 /** 19792 * Convert pixel coordinates relative to the window to viewport coordinates. 19793 * @param {OpenSeadragon.Point} pixel 19794 * @returns {OpenSeadragon.Point} 19795 */ 19796 windowToViewportCoordinates: function(pixel) { 19797 $.console.assert(this.viewer, 19798 "[Viewport.windowToViewportCoordinates] the viewport must have a viewer."); 19799 var viewerCoordinates = pixel.minus( 19800 $.getElementPosition(this.viewer.element)); 19801 return this.viewerElementToViewportCoordinates(viewerCoordinates); 19802 }, 19803 19804 /** 19805 * Convert viewport coordinates to pixel coordinates relative to the window. 19806 * @param {OpenSeadragon.Point} point 19807 * @returns {OpenSeadragon.Point} 19808 */ 19809 viewportToWindowCoordinates: function(point) { 19810 $.console.assert(this.viewer, 19811 "[Viewport.viewportToWindowCoordinates] the viewport must have a viewer."); 19812 var viewerCoordinates = this.viewportToViewerElementCoordinates(point); 19813 return viewerCoordinates.plus( 19814 $.getElementPosition(this.viewer.element)); 19815 }, 19816 19817 /** 19818 * Convert a viewport zoom to an image zoom. 19819 * Image zoom: ratio of the original image size to displayed image size. 19820 * 1 means original image size, 0.5 half size... 19821 * Viewport zoom: ratio of the displayed image's width to viewport'
19821s width. 19822 * 1 means identical width, 2 means image's width is twice the viewport's width... 19823 * Note: not accurate with multi-image. 19824 * @function 19825 * @param {Number} viewportZoom The viewport zoom 19826 * target zoom. 19827 * @returns {Number} imageZoom The image zoom 19828 */ 19829 viewportToImageZoom: function(viewportZoom) { 19830 if (this.viewer) { 19831 var count = this.viewer.world.getItemCount(); 19832 if (count > 1) { 19833 $.console.error('[Viewport.viewportToImageZoom] is not ' + 19834 'accurate with multi-image.'); 19835 } else if (count === 1) { 19836 // It is better to use TiledImage.viewportToImageZoom 19837 // because this._contentBoundsNoRotate can not be relied on 19838 // with clipping. 19839 var item = this.viewer.world.getItemAt(0); 19840 return item.viewportToImageZoom(viewportZoom); 19841 } 19842 } 19843 19844 var imageWidth = this._contentSizeNoRotate.x; 19845 var containerWidth = this._containerInnerSize.x; 19846 var scale = this._contentBoundsNoRotate.width; 19847 var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale; 19848 return viewportZoom * viewportToImageZoomRatio; 19849 }, 19850 19851 /** 19852 * Convert an image zoom to a viewport zoom. 19853 * Image zoom: ratio of the original image size to displayed image size. 19854 * 1 means original image size, 0.5 half size... 19855 * Viewport zoom: ratio of the displayed image's width to viewport's width. 19856 * 1 means identical width, 2 means image's width is twice the viewport's width... 19857 * Note: not accurate with multi-image. 19858 * @function 19859 * @param {Number} imageZoom The image zoom 19860 * target zoom. 19861 * @returns {Number} viewportZoom The viewport zoom 19862 */ 19863 imageToViewportZoom: function(imageZoom) { 19864 if (this.viewer) { 19865 var count = this.viewer.world.getItemCount(); 19866 if (count > 1) { 19867 $.console.error('[Viewport.imageToViewportZoom] is not accurate ' + 19868 'with multi-image.'); 19869 } else if (count === 1) { 19870 // It is better to use TiledImage.imageToViewportZoom 19871 // because this._contentBoundsNoRotate can not be relied on 19872 // with clipping. 19873 var item = this.viewer.world.getItemAt(0); 19874 return item.imageToViewportZoom(imageZoom); 19875 } 19876 } 19877 19878 var imageWidth = this._contentSizeNoRotate.x; 19879 var containerWidth = this._containerInnerSize.x; 19880 var scale = this._contentBoundsNoRotate.width; 19881 var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale; 19882 return imageZoom * viewportToImageZoomRatio; 19883 }, 19884 19885 /** 19886 * Toggles flip state and demands a new drawing on navigator and viewer objects. 19887 * @function 19888 * @return {OpenSeadragon.Viewport} Chainable. 19889 */ 19890 toggleFlip: function() { 19891 this.setFlip(!this.getFlip()); 19892 return this; 19893 }, 19894 19895 /** 19896 * Get flip state stored on viewport. 19897 * @function 19898 * @return {Boolean} Flip state. 19899 */ 19900 getFlip: function() { 19901 return this.flipped; 19902 }, 19903 19904 /** 19905 * Sets flip state according to the state input argument. 19906 * @function 19907 * @param {Boolean} state - Flip state to set. 19908 * @return {OpenSeadragon.Viewport} Chainable. 19909 */ 19910 setFlip: function( state ) { 19911 if ( this.flipped === state ) { 19912 return this; 19913 } 19914 19915 this.flipped = state; 19916 if(this.viewer.navigator){ 19917 this.viewer.navigator.setFlip(this.getFlip()); 19918 } 19919 this.viewer.forceRedraw(); 19920 19921 /** 19922 * Raised when flip state has been changed. 19923 * 19924 * @event flip 19925 * @memberof OpenSeadragon.Viewer 19926 * @type {object} 19927 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 19928 * @property {Number} flipped - The flip state after this change. 19929 * @property {?Object} userData - Arbitrary subscriber-defined object. 19930 */ 19931 this.viewer.raiseEvent('flip', {"flipped": state}); 19932 return this; 19933 } 19934 19935}; 19936 19937}( OpenSeadragon )); 19938 19939/* 19940 * OpenSeadragon - TiledImage 19941 * 19942 * Copyright (C) 2009 CodePlex Foundation 19943 * Copyright (C) 2010-2013 OpenSeadragon contributors 19944 * 19945 * Redistribution and use in source and binary forms, with or without 19946 * modification, are permitted provided that the following conditions are 19947 * met: 19948 * 19949 * - Redistributions of source code must retain the above copyright notice, 19950 * this list of conditions and the following disclaimer. 19951 * 19952 * - Redistributions in binary form must reproduce the above copyright 19953 * notice, this list of conditions and the following disclaimer in the 19954 * documentation and/or other materials provided with the distribution. 19955 * 19956 * - Neither the name of CodePlex Foundation nor the names of its 19957 * contributors may be used to endorse or promote products derived from 19958 * this software without specific prior written permission. 19959 * 19960 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19961 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19962 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19963 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19964 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19965 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 19966 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 19967 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 19968 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 19969 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 19970 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19971 */ 19972 19973(function( $ ){ 19974 19975/** 19976 * You shouldn't have to create a TiledImage instance directly; get it asynchronously by 19977 * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead. 19978 * @class TiledImage 19979 * @memberof OpenSeadragon 19980 * @extends OpenSeadragon.EventSource 19981 * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. 19982 * A new instance is created for each TileSource opened. 19983 * @param {Object} options - Configuration for this TiledImage. 19984 * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage. 19985 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage. 19986 * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use. 19987 * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto. 19988 * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use. 19989 * @param {Number} [options.x=0] - Left position, in viewport coordinates. 19990 * @param {Number} [options.y=0] - Top position, in viewport coordinates. 19991 * @param {Number} [options.width=1] - Width, in viewport coordinates. 19992 * @param {Number} [options.height] - Height, in viewport coordinates. 19993 * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates 19994 * to fit the image into. If specified, x, y, width and height get ignored. 19995 * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER] 19996 * How to anchor the image in the bounds if options.fitBounds is set. 19997 * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to 19998 * (portions of the image outside of this area will not be visible). Only works on 19999 * browsers that support the HTML5 canvas. 20000 * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}. 20001 * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}. 20002 * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}. 20003 * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}. 20004 * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}. 20005 * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}. 20006 * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}. 20007 * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}. 20008 * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}. 20009 * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}. 20010 * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}. 20011 * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw. 20012 * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity. 20013 * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values. 20014 * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}. 20015 * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. 20016 * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}. 20017 * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}. 20018 * @param {Boolean} [options.loadTilesWithAjax] 20019 * Whether to load tile data using AJAX requests. 20020 * Defaults to the setting in {@link OpenSeadragon.Options}. 20021 * @param {Object} [options.ajaxHeaders={}] 20022 * A set of headers to include when making tile AJAX requests. 20023 */ 20024$.TiledImage = function( options ) { 20025 var _this = this; 20026 /** 20027 * The {@link OpenSeadragon.TileSource} that defines this TiledImage. 20028 * @member {OpenSeadragon.TileSource} source 20029 * @memberof OpenSeadragon.TiledImage# 20030 */ 20031 $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" ); 20032 $.console.assert( options.drawer, "[TiledImage] options.drawer is required" ); 20033 $.console.assert( options.viewer, "[TiledImage] options.viewer is required" ); 20034 $.console.assert( options.imageLoader, "[TiledImage] options.imageLoader is required" ); 20035 $.console.assert( options.source, "[TiledImage] options.source is required" ); 20036 $.console.assert(!options.clip || options.clip instanceof $.Rect, 20037 "[TiledImage] options.clip must be an OpenSeadragon.Rect if present"); 20038 20039 $.EventSource.call( this ); 20040 20041 this._tileCache = options.tileCache; 20042 delete options.tileCache; 20043 20044 this._drawer = options.drawer; 20045 delete options.drawer; 20046 20047 this._imageLoader = options.imageLoader; 20048 delete options.imageLoader; 20049 20050 if (options.clip instanceof $.Rect) { 20051 this._clip = options.clip.clone(); 20052 } 20053 20054 delete options.clip; 20055 20056 var x = options.x || 0; 20057 delete options.x; 20058 var y = options.y || 0; 20059 delete options.y; 20060 20061 // Ratio of zoomable image height to width. 20062 this.normHeight = options.source.dimensions.y / options.source.dimensions.x; 20063 this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y; 20064 20065 var scale = 1; 20066 if ( options.width ) { 20067 scale = options.width; 20068 delete options.width; 20069 20070 if ( options.height ) { 20071 $.console.error( "specifying both width and height to a tiledImage is not supported" ); 20072 delete options.height; 20073 } 20074 } else if ( options.height ) { 20075 scale = options.height / this.normHeight; 20076 delete options.height; 20077 } 20078 20079 var fitBounds = options.fitBounds; 20080 delete options.fitBounds; 20081 var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER; 20082 delete options.fitBoundsPlacement; 20083 20084 var degrees = options.degrees || 0; 20085 delete options.degrees; 20086 20087 $.extend( true, this, { 20088 20089 //internal state properties 20090 viewer: null, 20091 tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. 20092 coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn. 20093 loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended. 20094 lastDrawn: [], // An unordered list of Tiles drawn last frame. 20095 lastResetTime: 0, // Last time for which the tiledImage was reset. 20096 _midDraw: false, // Is the tiledImage currently updating the viewport? 20097 _needsDraw: true, // Does the tiledImage need to update the viewport again? 20098 _hasOpaqueTile: false, // Do we have even one fully opaque tile? 20099 _tilesLoading: 0, // The number of pending tile requests. 20100 //configurable settings 20101 springStiffness: $.DEFAULT_SETTINGS.springStiffness, 20102 animationTime: $.DEFAULT_SETTINGS.animationTime,
vendor: 17,158 bytes, lines 20103-20532
20103 minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, 20104 wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, 20105 wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, 20106 immediateRender: $.DEFAULT_SETTINGS.immediateRender, 20107 blendTime: $.DEFAULT_SETTINGS.blendTime, 20108 alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, 20109 minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, 20110 smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom, 20111 iOSDevice: $.DEFAULT_SETTINGS.iOSDevice, 20112 debugMode: $.DEFAULT_SETTINGS.debugMode, 20113 crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, 20114 ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials, 20115 placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, 20116 opacity: $.DEFAULT_SETTINGS.opacity, 20117 preload: $.DEFAULT_SETTINGS.preload, 20118 compositeOperation: $.DEFAULT_SETTINGS.compositeOperation 20119 }, options ); 20120 20121 this._preload = this.preload; 20122 delete this.preload; 20123 20124 this._fullyLoaded = false; 20125 20126 this._xSpring = new $.Spring({ 20127 initial: x, 20128 springStiffness: this.springStiffness, 20129 animationTime: this.animationTime 20130 }); 20131 20132 this._ySpring = new $.Spring({ 20133 initial: y, 20134 springStiffness: this.springStiffness, 20135 animationTime: this.animationTime 20136 }); 20137 20138 this._scaleSpring = new $.Spring({ 20139 initial: scale, 20140 springStiffness: this.springStiffness, 20141 animationTime: this.animationTime 20142 }); 20143 20144 this._degreesSpring = new $.Spring({ 20145 initial: degrees, 20146 springStiffness: this.springStiffness, 20147 animationTime: this.animationTime 20148 }); 20149 20150 this._updateForScale(); 20151 20152 if (fitBounds) { 20153 this.fitBounds(fitBounds, fitBoundsPlacement, true); 20154 } 20155 20156 // We need a callback to give image manipulation a chance to happen 20157 this._drawingHandler = function(args) { 20158 /** 20159 * This event is fired just before the tile is drawn giving the application a chance to alter the image. 20160 * 20161 * NOTE: This event is only fired when the drawer is using a <canvas>. 20162 * 20163 * @event tile-drawing 20164 * @memberof OpenSeadragon.Viewer 20165 * @type {object} 20166 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 20167 * @property {OpenSeadragon.Tile} tile - The Tile being drawn. 20168 * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 20169 * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into. 20170 * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery. 20171 * @property {?Object} userData - Arbitrary subscriber-defined object. 20172 */ 20173 _this.viewer.raiseEvent('tile-drawing', $.extend({ 20174 tiledImage: _this 20175 }, args)); 20176 }; 20177}; 20178 20179$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{ 20180 /** 20181 * @returns {Boolean} Whether the TiledImage needs to be drawn. 20182 */ 20183 needsDraw: function() { 20184 return this._needsDraw; 20185 }, 20186 20187 /** 20188 * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded. 20189 */ 20190 getFullyLoaded: function() { 20191 return this._fullyLoaded; 20192 }, 20193 20194 // private 20195 _setFullyLoaded: function(flag) { 20196 if (flag === this._fullyLoaded) { 20197 return; 20198 } 20199 20200 this._fullyLoaded = flag; 20201 20202 /** 20203 * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage 20204 * to draw at the current view have been loaded) changes. 20205 * 20206 * @event fully-loaded-change 20207 * @memberof OpenSeadragon.TiledImage 20208 * @type {object} 20209 * @property {Boolean} fullyLoaded - The new "fully loaded" value. 20210 * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event. 20211 * @property {?Object} userData - Arbitrary subscriber-defined object. 20212 */ 20213 this.raiseEvent('fully-loaded-change', { 20214 fullyLoaded: this._fullyLoaded 20215 }); 20216 }, 20217 20218 /** 20219 * Clears all tiles and triggers an update on the next call to 20220 * {@link OpenSeadragon.TiledImage#update}. 20221 */ 20222 reset: function() { 20223 this._tileCache.clearTilesFor(this); 20224 this.lastResetTime = $.now(); 20225 this._needsDraw = true; 20226 }, 20227 20228 /** 20229 * Updates the TiledImage's bounds, animating if needed. 20230 * @returns {Boolean} Whether the TiledImage animated. 20231 */ 20232 update: function() { 20233 var xUpdated = this._xSpring.update(); 20234 var yUpdated = this._ySpring.update(); 20235 var scaleUpdated = this._scaleSpring.update(); 20236 var degreesUpdated = this._degreesSpring.update(); 20237 20238 if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) { 20239 this._updateForScale(); 20240 this._needsDraw = true; 20241 return true; 20242 } 20243 20244 return false; 20245 }, 20246 20247 /** 20248 * Draws the TiledImage to its Drawer. 20249 */ 20250 draw: function() { 20251 if (this.opacity !== 0 || this._preload) { 20252 this._midDraw = true; 20253 this._updateViewport(); 20254 this._midDraw = false; 20255 } 20256 // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images. 20257 else { 20258 this._needsDraw = false; 20259 } 20260 }, 20261 20262 /** 20263 * Destroy the TiledImage (unload current loaded tiles). 20264 */ 20265 destroy: function() { 20266 this.reset(); 20267 }, 20268 20269 /** 20270 * Get this TiledImage's bounds in viewport coordinates. 20271 * @param {Boolean} [current=false] - Pass true for the current location; 20272 * false for target location. 20273 * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates. 20274 */ 20275 getBounds: function(current) { 20276 return this.getBoundsNoRotate(current) 20277 .rotate(this.getRotation(current), this._getRotationPoint(current)); 20278 }, 20279 20280 /** 20281 * Get this TiledImage's bounds in viewport coordinates without taking 20282 * rotation into account. 20283 * @param {Boolean} [current=false] - Pass true for the current location; 20284 * false for target location. 20285 * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates. 20286 */ 20287 getBoundsNoRotate: function(current) { 20288 return current ? 20289 new $.Rect( 20290 this._xSpring.current.value, 20291 this._ySpring.current.value, 20292 this._worldWidthCurrent, 20293 this._worldHeightCurrent) : 20294 new $.Rect( 20295 this._xSpring.target.value, 20296 this._ySpring.target.value, 20297 this._worldWidthTarget, 20298 this._worldHeightTarget); 20299 }, 20300 20301 // deprecated 20302 getWorldBounds: function() { 20303 $.console.error('[TiledImage.getWorldBounds] is deprecated; use TiledImage.getBounds instead'); 20304 return this.getBounds(); 20305 }, 20306 20307 /** 20308 * Get the bounds of the displayed part of the tiled image. 20309 * @param {Boolean} [current=false] Pass true for the current location, 20310 * false for the target location. 20311 * @returns {$.Rect} The clipped bounds in viewport coordinates. 20312 */ 20313 getClippedBounds: function(current) { 20314 var bounds = this.getBoundsNoRotate(current); 20315 if (this._clip) { 20316 var worldWidth = current ? 20317 this._worldWidthCurrent : this._worldWidthTarget; 20318 var ratio = worldWidth / this.source.dimensions.x; 20319 var clip = this._clip.times(ratio); 20320 bounds = new $.Rect( 20321 bounds.x + clip.x, 20322 bounds.y + clip.y, 20323 clip.width, 20324 clip.height); 20325 } 20326 return bounds.rotate(this.getRotation(current), this._getRotationPoint(current)); 20327 }, 20328 20329 /** 20330 * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels. 20331 */ 20332 getContentSize: function() { 20333 return new $.Point(this.source.dimensions.x, this.source.dimensions.y); 20334 }, 20335 20336 // private 20337 _viewportToImageDelta: function( viewerX, viewerY, current ) { 20338 var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value); 20339 return new $.Point(viewerX * (this.source.dimensions.x / scale), 20340 viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale)); 20341 }, 20342 20343 /** 20344 * Translates from OpenSeadragon viewer coordinate system to image coordinate system. 20345 * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}. 20346 * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system. 20347 * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system. 20348 * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. 20349 * @return {OpenSeadragon.Point} A point representing the coordinates in the image. 20350 */ 20351 viewportToImageCoordinates: function(viewerX, viewerY, current) { 20352 var point; 20353 if (viewerX instanceof $.Point) { 20354 //they passed a point instead of individual components 20355 current = viewerY; 20356 point = viewerX; 20357 } else { 20358 point = new $.Point(viewerX, viewerY); 20359 } 20360 20361 point = point.rotate(-this.getRotation(current), this._getRotationPoint(current)); 20362 return current ? 20363 this._viewportToImageDelta( 20364 point.x - this._xSpring.current.value, 20365 point.y - this._ySpring.current.value) : 20366 this._viewportToImageDelta( 20367 point.x - this._xSpring.target.value, 20368 point.y - this._ySpring.target.value); 20369 }, 20370 20371 // private 20372 _imageToViewportDelta: function( imageX, imageY, current ) { 20373 var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value); 20374 return new $.Point((imageX / this.source.dimensions.x) * scale, 20375 (imageY / this.source.dimensions.y / this.contentAspectX) * scale); 20376 }, 20377 20378 /** 20379 * Translates from image coordinate system to OpenSeadragon viewer coordinate system 20380 * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}. 20381 * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system. 20382 * @param {Number} [imageY] - The Y coordinate in image coordinate system. 20383 * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. 20384 * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport. 20385 */ 20386 imageToViewportCoordinates: function(imageX, imageY, current) { 20387 if (imageX instanceof $.Point) { 20388 //they passed a point instead of individual components 20389 current = imageY; 20390 imageY = imageX.y; 20391 imageX = imageX.x; 20392 } 20393 20394 var point = this._imageToViewportDelta(imageX, imageY); 20395 if (current) { 20396 point.x += this._xSpring.current.value; 20397 point.y += this._ySpring.current.value; 20398 } else { 20399 point.x += this._xSpring.target.value; 20400 point.y += this._ySpring.target.value; 20401 } 20402 20403 return point.rotate(this.getRotation(current), this._getRotationPoint(current)); 20404 }, 20405 20406 /** 20407 * Translates from a rectangle which describes a portion of the image in 20408 * pixel coordinates to OpenSeadragon viewport rectangle coordinates. 20409 * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}. 20410 * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system. 20411 * @param {Number} [imageY] - The top coordinate in image coordinate system. 20412 * @param {Number} [pixelWidth] - The width in pixel of the rectangle. 20413 * @param {Number} [pixelHeight] - The height in pixel of the rectangle. 20414 * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. 20415 * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport. 20416 */ 20417 imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) { 20418 var rect = imageX; 20419 if (rect instanceof $.Rect) { 20420 //they passed a rect instead of individual components 20421 current = imageY; 20422 } else { 20423 rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight); 20424 } 20425 20426 var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current); 20427 var coordB = this._imageToViewportDelta(rect.width, rect.height, current); 20428 20429 return new $.Rect( 20430 coordA.x, 20431 coordA.y, 20432 coordB.x, 20433 coordB.y, 20434 rect.degrees + this.getRotation(current) 20435 ); 20436 }, 20437 20438 /** 20439 * Translates from a rectangle which describes a portion of 20440 * the viewport in point coordinates to image rectangle coordinates. 20441 * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}. 20442 * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system. 20443 * @param {Number} [viewerY] - The top coordinate in viewport coordinate system. 20444 * @param {Number} [pointWidth] - The width in viewport coordinate system. 20445 * @param {Number} [pointHeight] - The height in viewport coordinate system. 20446 * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. 20447 * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image. 20448 */ 20449 viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) { 20450 var rect = viewerX; 20451 if (viewerX instanceof $.Rect) { 20452 //they passed a rect instead of individual components 20453 current = viewerY; 20454 } else { 20455 rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight); 20456 } 20457 20458 var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current); 20459 var coordB = this._viewportToImageDelta(rect.width, rect.height, current); 20460 20461 return new $.Rect( 20462 coordA.x, 20463 coordA.y, 20464 coordB.x, 20465 coordB.y, 20466 rect.degrees - this.getRotation(current) 20467 ); 20468 }, 20469 20470 /** 20471 * Convert pixel coordinates relative to the viewer element to image 20472 * coordinates. 20473 * @param {OpenSeadragon.Point} pixel 20474 * @returns {OpenSeadragon.Point} 20475 */ 20476 viewerElementToImageCoordinates: function( pixel ) { 20477 var point = this.viewport.pointFromPixel( pixel, true ); 20478 return this.viewportToImageCoordinates( point ); 20479 }, 20480 20481 /** 20482 * Convert pixel coordinates relative to the image to 20483 * viewer element coordinates. 20484 * @param {OpenSeadragon.Point} pixel 20485 * @returns {OpenSeadragon.Point} 20486 */ 20487 imageToViewerElementCoordinates: function( pixel ) { 20488 var point = this.imageToViewportCoordinates( pixel ); 20489 return this.viewport.pixelFromPoint( point, true ); 20490 }, 20491 20492 /** 20493 * Convert pixel coordinates relative to the window to image coordinates. 20494 * @param {OpenSeadragon.Point} pixel 20495 * @returns {OpenSeadragon.Point} 20496 */ 20497 windowToImageCoordinates: function( pixel ) { 20498 var viewerCoordinates = pixel.minus( 20499 OpenSeadragon.getElementPosition( this.viewer.element )); 20500 return this.viewerElementToImageCoordinates( viewerCoordinates ); 20501 }, 20502 20503 /** 20504 * Convert image coordinates to pixel coordinates relative to the window. 20505 * @param {OpenSeadragon.Point} pixel 20506 * @returns {OpenSeadragon.Point} 20507 */ 20508 imageToWindowCoordinates: function( pixel ) { 20509 var viewerCoordinates = this.imageToViewerElementCoordinates( pixel ); 20510 return viewerCoordinates.plus( 20511 OpenSeadragon.getElementPosition( this.viewer.element )); 20512 }, 20513 20514 // private 20515 // Convert rectangle in viewport coordinates to this tiled image point 20516 // coordinates (x in [0, 1] and y in [0, aspectRatio]) 20517 _viewportToTiledImageRectangle: function(rect) { 20518 var scale = this._scaleSpring.current.value; 20519 rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true)); 20520 return new $.Rect( 20521 (rect.x - this._xSpring.current.value) / scale, 20522 (rect.y - this._ySpring.current.value) / scale, 20523 rect.width / scale, 20524 rect.height / scale, 20525 rect.degrees); 20526 }, 20527 20528 /** 20529 * Convert a viewport zoom to an image zoom. 20530 * Image zoom: ratio of the original image size to displayed image size. 20531 * 1 means original image size, 0.5 half size... 20532 * Viewport zoom: ratio of the displayed image's width to viewport'
20532s width. 20533 * 1 means identical width, 2 means image's width is twice the viewport's width... 20534 * @function 20535 * @param {Number} viewportZoom The viewport zoom 20536 * @returns {Number} imageZoom The image zoom 20537 */ 20538 viewportToImageZoom: function( viewportZoom ) { 20539 var ratio = this._scaleSpring.current.value * 20540 this.viewport._containerInnerSize.x / this.source.dimensions.x; 20541 return ratio * viewportZoom; 20542 }, 20543 20544 /** 20545 * Convert an image zoom to a viewport zoom. 20546 * Image zoom: ratio of the original image size to displayed image size. 20547 * 1 means original image size, 0.5 half size... 20548 * Viewport zoom: ratio of the displayed image's width to viewport's width. 20549 * 1 means identical width, 2 means image's width is twice the viewport's width... 20550 * Note: not accurate with multi-image. 20551 * @function 20552 * @param {Number} imageZoom The image zoom 20553 * @returns {Number} viewportZoom The viewport zoom 20554 */ 20555 imageToViewportZoom: function( imageZoom ) { 20556 var ratio = this._scaleSpring.current.value * 20557 this.viewport._containerInnerSize.x / this.source.dimensions.x; 20558 return imageZoom / ratio; 20559 }, 20560 20561 /** 20562 * Sets the TiledImage's position in the world. 20563 * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates. 20564 * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately. 20565 * @fires OpenSeadragon.TiledImage.event:bounds-change 20566 */ 20567 setPosition: function(position, immediately) { 20568 var sameTarget = (this._xSpring.target.value === position.x && 20569 this._ySpring.target.value === position.y); 20570 20571 if (immediately) { 20572 if (sameTarget && this._xSpring.current.value === position.x && 20573 this._ySpring.current.value === position.y) { 20574 return; 20575 } 20576 20577 this._xSpring.resetTo(position.x); 20578 this._ySpring.resetTo(position.y); 20579 this._needsDraw = true; 20580 } else { 20581 if (sameTarget) { 20582 return; 20583 } 20584 20585 this._xSpring.springTo(position.x); 20586 this._ySpring.springTo(position.y); 20587 this._needsDraw = true; 20588 } 20589 20590 if (!sameTarget) { 20591 this._raiseBoundsChange(); 20592 } 20593 }, 20594 20595 /** 20596 * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio. 20597 * @param {Number} width - The new width, in viewport coordinates. 20598 * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately. 20599 * @fires OpenSeadragon.TiledImage.event:bounds-change 20600 */ 20601 setWidth: function(width, immediately) { 20602 this._setScale(width, immediately); 20603 }, 20604 20605 /** 20606 * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio. 20607 * @param {Number} height - The new height, in viewport coordinates. 20608 * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately. 20609 * @fires OpenSeadragon.TiledImage.event:bounds-change 20610 */ 20611 setHeight: function(height, immediately) { 20612 this._setScale(height / this.normHeight, immediately); 20613 }, 20614 20615 /** 20616 * Sets an array of polygons to crop the TiledImage during draw tiles. 20617 * The render function will use the default non-zero winding rule. 20618 * @param Polygons represented in an array of point object in image coordinates. 20619 * Example format: [ 20620 * [{x: 197, y:172}, {x: 226, y:172}, {x: 226, y:198}, {x: 197, y:198}], // First polygon 20621 * [{x: 328, y:200}, {x: 330, y:199}, {x: 332, y:201}, {x: 329, y:202}] // Second polygon 20622 * [{x: 321, y:201}, {x: 356, y:205}, {x: 341, y:250}] // Third polygon 20623 * ] 20624 */ 20625 setCroppingPolygons: function( polygons ) { 20626 20627 var isXYObject = function(obj) { 20628 return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number'); 20629 }; 20630 20631 var objectToSimpleXYObject = function(objs) { 20632 return objs.map(function(obj) { 20633 try { 20634 if (isXYObject(obj)) { 20635 return { x: obj.x, y: obj.y }; 20636 } else { 20637 throw new Error(); 20638 } 20639 } catch(e) {
20640 throw new Error('A Provided cropping polygon point is not supported'); 20641 } 20642 }); 20643 }; 20644 20645 try { 20646 if (!$.isArray(polygons)) { 20647 throw new Error('Provided cropping polygon is not an array'); 20648 } 20649 this._croppingPolygons = polygons.map(function(polygon){ 20650 return objectToSimpleXYObject(polygon); 20651 }); 20652 } catch (e) { 20653 $.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported'); 20654 $.console.error(e); 20655 this._croppingPolygons = null; 20656 } 20657 }, 20658 20659 /** 20660 * Resets the cropping polygons, thus next render will remove all cropping 20661 * polygon effects. 20662 */ 20663 resetCroppingPolygons: function() { 20664 this._croppingPolygons = null; 20665 }, 20666 20667 /** 20668 * Positions and scales the TiledImage to fit in the specified bounds. 20669 * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change 20670 * twice 20671 * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into. 20672 * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER] 20673 * How to anchor the image in the bounds. 20674 * @param {Boolean} [immediately=false] Whether to animate to the new size 20675 * or snap immediately. 20676 * @fires OpenSeadragon.TiledImage.event:bounds-change 20677 */ 20678 fitBounds: function(bounds, anchor, immediately) { 20679 anchor = anchor || $.Placement.CENTER; 20680 var anchorProperties = $.Placement.properties[anchor]; 20681 var aspectRatio = this.contentAspectX; 20682 var xOffset = 0; 20683 var yOffset = 0; 20684 var displayedWidthRatio = 1; 20685 var displayedHeightRatio = 1; 20686 if (this._clip) { 20687 aspectRatio = this._clip.getAspectRatio(); 20688 displayedWidthRatio = this._clip.width / this.source.dimensions.x; 20689 displayedHeightRatio = this._clip.height / this.source.dimensions.y; 20690 if (bounds.getAspectRatio() > aspectRatio) { 20691 xOffset = this._clip.x / this._clip.height * bounds.height; 20692 yOffset = this._clip.y / this._clip.height * bounds.height; 20693 } else { 20694 xOffset = this._clip.x / this._clip.width * bounds.width; 20695 yOffset = this._clip.y / this._clip.width * bounds.width; 20696 } 20697 } 20698 20699 if (bounds.getAspectRatio() > aspectRatio) { 20700 // We will have margins on the X axis 20701 var height = bounds.height / displayedHeightRatio; 20702 var marginLeft = 0; 20703 if (anchorProperties.isHorizontallyCentered) { 20704 marginLeft = (bounds.width - bounds.height * aspectRatio) / 2; 20705 } else if (anchorProperties.isRight) { 20706 marginLeft = bounds.width - bounds.height * aspectRatio; 20707 } 20708 this.setPosition( 20709 new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset), 20710 immediately); 20711 this.setHeight(height, immediately); 20712 } else { 20713 // We will have margins on the Y axis 20714 var width = bounds.width / displayedWidthRatio; 20715 var marginTop = 0; 20716 if (anchorProperties.isVerticallyCentered) { 20717 marginTop = (bounds.height - bounds.width / aspectRatio) / 2; 20718 } else if (anchorProperties.isBottom) { 20719 marginTop = bounds.height - bounds.width / aspectRatio; 20720 } 20721 this.setPosition( 20722 new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop), 20723 immediately); 20724 this.setWidth(width, immediately); 20725 } 20726 }, 20727 20728 /** 20729 * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle, 20730 * in image pixels, or null if none. 20731 */ 20732 getClip: function() { 20733 if (this._clip) { 20734 return this._clip.clone(); 20735 } 20736 20737 return null; 20738 }, 20739 20740 /** 20741 * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to 20742 * (portions of the image outside of this area will not be visible). Only works on 20743 * browsers that support the HTML5 canvas.
vendor: 4,722 bytes, lines 20744-20884
20744 * @fires OpenSeadragon.TiledImage.event:clip-change 20745 */ 20746 setClip: function(newClip) { 20747 $.console.assert(!newClip || newClip instanceof $.Rect, 20748 "[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null"); 20749 20750 if (newClip instanceof $.Rect) { 20751 this._clip = newClip.clone(); 20752 } else { 20753 this._clip = null; 20754 } 20755 20756 this._needsDraw = true; 20757 /** 20758 * Raised when the TiledImage's clip is changed. 20759 * @event clip-change 20760 * @memberOf OpenSeadragon.TiledImage 20761 * @type {object} 20762 * @property {OpenSeadragon.TiledImage} eventSource - A reference to the 20763 * TiledImage which raised the event. 20764 * @property {?Object} userData - Arbitrary subscriber-defined object. 20765 */ 20766 this.raiseEvent('clip-change'); 20767 }, 20768 20769 /** 20770 * @returns {Number} The TiledImage's current opacity. 20771 */ 20772 getOpacity: function() { 20773 return this.opacity; 20774 }, 20775 20776 /** 20777 * @param {Number} opacity Opacity the tiled image should be drawn at. 20778 * @fires OpenSeadragon.TiledImage.event:opacity-change 20779 */ 20780 setOpacity: function(opacity) { 20781 if (opacity === this.opacity) { 20782 return; 20783 } 20784 20785 this.opacity = opacity; 20786 this._needsDraw = true; 20787 /** 20788 * Raised when the TiledImage's opacity is changed. 20789 * @event opacity-change 20790 * @memberOf OpenSeadragon.TiledImage 20791 * @type {object} 20792 * @property {Number} opacity - The new opacity value. 20793 * @property {OpenSeadragon.TiledImage} eventSource - A reference to the 20794 * TiledImage which raised the event. 20795 * @property {?Object} userData - Arbitrary subscriber-defined object. 20796 */ 20797 this.raiseEvent('opacity-change', { 20798 opacity: this.opacity 20799 }); 20800 }, 20801 20802 /** 20803 * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity. 20804 */ 20805 getPreload: function() { 20806 return this._preload; 20807 }, 20808 20809 /** 20810 * Set true to load even when hidden. Set false to block loading when hidden. 20811 */ 20812 setPreload: function(preload) { 20813 this._preload = !!preload; 20814 this._needsDraw = true; 20815 }, 20816 20817 /** 20818 * Get the rotation of this tiled image in degrees. 20819 * @param {Boolean} [current=false] True for current rotation, false for target. 20820 * @returns {Number} the rotation of this tiled image in degrees. 20821 */ 20822 getRotation: function(current) { 20823 return current ? 20824 this._degreesSpring.current.value : 20825 this._degreesSpring.target.value; 20826 }, 20827 20828 /** 20829 * Set the current rotation of this tiled image in degrees. 20830 * @param {Number} degrees the rotation in degrees. 20831 * @param {Boolean} [immediately=false] Whether to animate to the new angle 20832 * or rotate immediately. 20833 * @fires OpenSeadragon.TiledImage.event:bounds-change 20834 */ 20835 setRotation: function(degrees, immediately) { 20836 if (this._degreesSpring.target.value === degrees && 20837 this._degreesSpring.isAtTargetValue()) { 20838 return; 20839 } 20840 if (immediately) { 20841 this._degreesSpring.resetTo(degrees); 20842 } else { 20843 this._degreesSpring.springTo(degrees); 20844 } 20845 this._needsDraw = true; 20846 this._raiseBoundsChange(); 20847 }, 20848 20849 /** 20850 * Get the point around which this tiled image is rotated 20851 * @private 20852 * @param {Boolean} current True for current rotation point, false for target. 20853 * @returns {OpenSeadragon.Point} 20854 */ 20855 _getRotationPoint: function(current) { 20856 return this.getBoundsNoRotate(current).getCenter(); 20857 }, 20858 20859 /** 20860 * @returns {String} The TiledImage's current compositeOperation. 20861 */ 20862 getCompositeOperation: function() { 20863 return this.compositeOperation; 20864 }, 20865 20866 /** 20867 * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation. 20868 * @fires OpenSeadragon.TiledImage.event:composite-operation-change 20869 */ 20870 setCompositeOperation: function(compositeOperation) { 20871 if (compositeOperation === this.compositeOperation) { 20872 return; 20873 } 20874 20875 this.compositeOperation = compositeOperation; 20876 this._needsDraw = true; 20877 /** 20878 * Raised when the TiledImage's opacity is changed. 20879 * @event composite-operation-change 20880 * @memberOf OpenSeadragon.TiledImage 20881 * @type {object} 20882 * @property {String} compositeOperation - The new compositeOperation value. 20883 * @property {OpenSeadragon.TiledImage} eventSource - A reference to the 20884 * TiledImage which raised the event.
20885 * @property {?Object} userData - Arbitrary subscriber-defined object. 20886 */ 20887 this.raiseEvent('composite-operation-change', { 20888 compositeOperation: this.compositeOperation 20889 }); 20890 }, 20891 20892 // private 20893 _setScale: function(scale, immediately) { 20894 var sameTarget = (this._scaleSpring.target.value === scale); 20895 if (immediately) { 20896 if (sameTarget && this._scaleSpring.current.value === scale) { 20897 return; 20898 } 20899 20900 this._scaleSpring.resetTo(scale); 20901 this._updateForScale(); 20902 this._needsDraw = true; 20903 } else { 20904 if (sameTarget) { 20905 return; 20906 } 20907 20908 this._scaleSpring.springTo(scale); 20909 this._updateForScale(); 20910 this._needsDraw = true; 20911 } 20912 20913 if (!sameTarget) { 20914 this._raiseBoundsChange(); 20915 } 20916 }, 20917 20918 // private 20919 _updateForScale: function() { 20920 this._worldWidthTarget = this._scaleSpring.target.value; 20921 this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value; 20922 this._worldWidthCurrent = this._scaleSpring.current.value; 20923 this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value; 20924 }, 20925 20926 // private 20927 _raiseBoundsChange: function() { 20928 /** 20929 * Raised when the TiledImage's bounds are changed. 20930 * Note that this event is triggered only when the animation target is changed; 20931 * not for every frame of animation. 20932 * @event bounds-change 20933 * @memberOf OpenSeadragon.TiledImage 20934 * @type {object} 20935 * @property {OpenSeadragon.TiledImage} eventSource - A reference to the 20936 * TiledImage which raised the event. 20937 * @property {?Object} userData - Arbitrary subscriber-defined object. 20938 */ 20939 this.raiseEvent('bounds-change'); 20940 }, 20941 20942 // private 20943 _isBottomItem: function() { 20944 return this.viewer.world.getItemAt(0) === this; 20945 }, 20946 20947 // private 20948 _getLevelsInterval: function() { 20949 var lowestLevel = Math.max( 20950 this.source.minLevel, 20951 Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2)) 20952 ); 20953 var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate( 20954 this.source.getPixelRatio(0), true).x * 20955 this._scaleSpring.current.value; 20956 var highestLevel = Math.min( 20957 Math.abs(this.source.maxLevel), 20958 Math.abs(Math.floor( 20959 Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2) 20960 )) 20961 ); 20962 20963 // Calculations for the interval of levels to draw 20964 // can return invalid intervals; fix that here if necessary 20965 highestLevel = Math.max(highestLevel, this.source.minLevel || 0); 20966 lowestLevel = Math.min(lowestLevel, highestLevel); 20967 return { 20968 lowestLevel: lowestLevel, 20969 highestLevel: highestLevel 20970 }; 20971 }, 20972 20973 /** 20974 * @private 20975 * @inner 20976 * Pretty much every other line in this needs to be documented so it's clear 20977 * how each piece of this routine contributes to the drawing process. That's 20978 * why there are so many TODO's inside this function. 20979 */ 20980 _updateViewport: function() { 20981 this._needsDraw = false; 20982 this._tilesLoading = 0; 20983 this.loadingCoverage = {}; 20984 20985 // Reset tile's internal drawn state 20986 while (this.lastDrawn.length > 0) { 20987 var tile = this.lastDrawn.pop(); 20988 tile.beingDrawn = false; 20989 } 20990 20991 var viewport = this.viewport; 20992 var drawArea = this._viewportToTiledImageRectangle( 20993 viewport.getBoundsWithMargins(true)); 20994 20995 if (!this.wrapHorizontal && !this.wrapVertical) { 20996 var tiledImageBounds = this._viewportToTiledImageRectangle( 20997 this.getClippedBounds(true)); 20998 drawArea = drawArea.intersection(tiledImageBounds); 20999 if (drawArea === null) { 21000 return; 21001 } 21002 } 21003 21004 var levelsInterval = this._getLevelsInterval(); 21005 var lowestLevel = levelsInterval.lowestLevel; 21006 var highestLevel = levelsInterval.highestLevel; 21007 var bestTile = null; 21008 var haveDrawn = false;
21009 var currentTime = $.now(); 21010 21011 // Update any level that will be drawn 21012 for (var level = highestLevel; level >= lowestLevel; level--) { 21013 var drawLevel = false; 21014 21015 //Avoid calculations for draw if we have already drawn this 21016 var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate( 21017 this.source.getPixelRatio(level), 21018 true 21019 ).x * this._scaleSpring.current.value; 21020 21021 if (level === lowestLevel || 21022 (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) { 21023 drawLevel = true; 21024 haveDrawn = true; 21025 } else if (!haveDrawn) { 21026 continue; 21027 } 21028 21029 //Perform calculations for draw if we haven't drawn this 21030 var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate( 21031 this.source.getPixelRatio(level), 21032 false 21033 ).x * this._scaleSpring.current.value; 21034 21035 var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate( 21036 this.source.getPixelRatio( 21037 Math.max( 21038 this.source.getClosestLevel(), 21039 0 21040 ) 21041 ), 21042 false 21043 ).x * this._scaleSpring.current.value; 21044 21045 var optimalRatio = this.immediateRender ? 1 : targetZeroRatio; 21046 var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5); 21047 var levelVisibility = optimalRatio / Math.abs( 21048 optimalRatio - targetRenderPixelRatio 21049 ); 21050 21051 // Update the level and keep track of 'best' tile to load 21052 bestTile = updateLevel( 21053 this, 21054 haveDrawn, 21055 drawLevel, 21056 level, 21057 levelOpacity, 21058 levelVisibility, 21059 drawArea, 21060 currentTime, 21061 bestTile 21062 ); 21063 21064 // Stop the loop if lower-res tiles would all be covered by 21065 // already drawn tiles 21066 if (providesCoverage(this.coverage, level)) { 21067 break; 21068 } 21069 } 21070 21071 // Perform the actual drawing 21072 drawTiles(this, this.lastDrawn); 21073 21074 // Load the new 'best' tile 21075 if (bestTile && !bestTile.context2D) { 21076 loadTile(this, bestTile, currentTime); 21077 this._needsDraw = true; 21078 this._setFullyLoaded(false); 21079 } else { 21080 this._setFullyLoaded(this._tilesLoading === 0); 21081 } 21082 }, 21083 21084 // private 21085 _getCornerTiles: function(level, topLeftBound, bottomRightBound) { 21086 var leftX; 21087 var rightX; 21088 if (this.wrapHorizontal) { 21089 leftX = $.positiveModulo(topLeftBound.x, 1); 21090 rightX = $.positiveModulo(bottomRightBound.x, 1); 21091 } else { 21092 leftX = Math.max(0, topLeftBound.x); 21093 rightX = Math.min(1, bottomRightBound.x); 21094 } 21095 var topY; 21096 var bottomY; 21097 var aspectRatio = 1 / this.source.aspectRatio; 21098 if (this.wrapVertical) { 21099 topY = $.positiveModulo(topLeftBound.y, aspectRatio); 21100 bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio); 21101 } else { 21102 topY = Math.max(0, topLeftBound.y); 21103 bottomY = Math.min(aspectRatio, bottomRightBound.y); 21104 } 21105 21106 var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY)); 21107 var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY)); 21108 var numTiles = this.source.getNumTiles(level); 21109 21110 if (this.wrapHorizontal) { 21111 topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x); 21112 bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x); 21113 } 21114 if (this.wrapVertical) { 21115 topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio); 21116 bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio); 21117 } 21118 21119 return { 21120 topLeft: topLeftTile, 21121 bottomRight: bottomRightTile, 21122 }; 21123 } 21124}); 21125 21126/** 21127 * @private 21128 * @inner 21129 * Updates all tiles at a given resolution level. 21130 * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 21131 * @param {Boolean} haveDrawn 21132 * @param {Boolean} drawLevel 21133 * @param {Number} level 21134 * @param {Number} levelOpacity 21135 * @param {Number} levelVisibility 21136 * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile. 21137 * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile. 21138 * @param {Number} currentTime 21139 * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. 21140 */ 21141function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity, 21142 levelVisibility, drawArea, currentTime, best) { 21143 21144 var topLeftBound = drawArea.getBoundingBox().getTopLeft(); 21145 var bottomRightBound = drawArea.getBoundingBox().getBottomRight(); 21146 21147 if (tiledImage.viewer) { 21148 /** 21149 * <em>- Needs documentation -</em> 21150 * 21151 * @event update-level 21152 * @memberof OpenSeadragon.Viewer 21153 * @type {object} 21154 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 21155 * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 21156 * @property {Object} havedrawn 21157 * @property {Object} level 21158 * @property {Object} opacity 21159 * @property {Object} visibility 21160 * @property {OpenSeadragon.Rect} drawArea 21161 * @property {Object} topleft deprecated, use drawArea instead 21162 * @property {Object} bottomright deprecated, use drawArea instead 21163 * @property {Object} currenttime 21164 * @property {Object} best
21165 * @property {?Object} userData - Arbitrary subscriber-defined object. 21166 */ 21167 tiledImage.viewer.raiseEvent('update-level', { 21168 tiledImage: tiledImage, 21169 havedrawn: haveDrawn, 21170 level: level, 21171 opacity: levelOpacity, 21172 visibility: levelVisibility, 21173 drawArea: drawArea, 21174 topleft: topLeftBound, 21175 bottomright: bottomRightBound, 21176 currenttime: currentTime, 21177 best: best 21178 }); 21179 } 21180 21181 resetCoverage(tiledImage.coverage, level); 21182 resetCoverage(tiledImage.loadingCoverage, level); 21183 21184 //OK, a new drawing so do your calculations 21185 var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound); 21186 var topLeftTile = cornerTiles.topLeft; 21187 var bottomRightTile = cornerTiles.bottomRight; 21188 var numberOfTiles = tiledImage.source.getNumTiles(level); 21189 21190 var viewportCenter = tiledImage.viewport.pixelFromPoint( 21191 tiledImage.viewport.getCenter()); 21192 for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) { 21193 for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) { 21194 21195 // Optimisation disabled with wrapping because getTileBounds does not 21196 // work correctly with x and y outside of the number of tiles 21197 if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) { 21198 var tileBounds = tiledImage.source.getTileBounds(level, x, y); 21199 if (drawArea.intersection(tileBounds) === null) { 21200 // This tile is outside of the viewport, no need to draw it 21201 continue; 21202 } 21203 } 21204 21205 best = updateTile( 21206 tiledImage, 21207 drawLevel, 21208 haveDrawn, 21209 x, y, 21210 level, 21211 levelOpacity, 21212 levelVisibility, 21213 viewportCenter, 21214 numberOfTiles, 21215 currentTime, 21216 best 21217 ); 21218 21219 } 21220 } 21221 21222 return best; 21223} 21224 21225/** 21226 * @private 21227 * @inner 21228 * Update a single tile at a particular resolution level. 21229 * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 21230 * @param {Boolean} haveDrawn 21231 * @param {Boolean} drawLevel 21232 * @param {Number} x 21233 * @param {Number} y 21234 * @param {Number} level 21235 * @param {Number} levelOpacity 21236 * @param {Number} levelVisibility 21237 * @param {OpenSeadragon.Point} viewportCenter 21238 * @param {Number} numberOfTiles 21239 * @param {Number} currentTime 21240 * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. 21241 */ 21242function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ 21243 21244 var tile = getTile( 21245 x, y, 21246 level, 21247 tiledImage, 21248 tiledImage.source, 21249 tiledImage.tilesMatrix, 21250 currentTime, 21251 numberOfTiles, 21252 tiledImage._worldWidthCurrent, 21253 tiledImage._worldHeightCurrent 21254 ), 21255 drawTile = drawLevel; 21256 21257 if( tiledImage.viewer ){ 21258 /** 21259 * <em>- Needs documentation -</em> 21260 * 21261 * @event update-tile 21262 * @memberof OpenSeadragon.Viewer 21263 * @type {object} 21264 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 21265 * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 21266 * @property {OpenSeadragon.Tile} tile 21267 * @property {?Object} userData - Arbitrary subscriber-defined object. 21268 */ 21269 tiledImage.viewer.raiseEvent( 'update-tile', { 21270 tiledImage: tiledImage, 21271 tile: tile 21272 }); 21273 } 21274 21275 setCoverage( tiledImage.coverage, level, x, y, false ); 21276 21277 var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y); 21278 setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage); 21279 21280 if ( !tile.exists ) { 21281 return best; 21282 } 21283 21284 if ( haveDrawn && !drawTile ) { 21285 if ( isCovered( tiledImage.coverage, level, x, y ) ) { 21286 setCoverage( tiledImage.coverage, level, x, y, true ); 21287 } else { 21288 drawTile = true; 21289 } 21290 } 21291 21292 if ( !drawTile ) { 21293 return best; 21294 } 21295 21296 positionTile( 21297 tile, 21298 tiledImage.source.tileOverlap, 21299 tiledImage.viewport, 21300 viewportCenter, 21301 levelVisibility, 21302 tiledImage 21303 ); 21304 21305 if (!tile.loaded) { 21306 if (tile.context2D) { 21307 setTileLoaded(tiledImage, tile); 21308 } else { 21309 var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey); 21310 if (imageRecord) { 21311 var image = imageRecord.getImage(); 21312 setTileLoaded(tiledImage, tile, image); 21313 } 21314 } 21315 } 21316 21317 if ( tile.loaded ) { 21318 var needsDraw = blendTile( 21319 tiledImage, 21320 tile, 21321 x, y, 21322 level, 21323 levelOpacity, 21324 currentTime 21325 ); 21326 21327 if ( needsDraw ) { 21328 tiledImage._needsDraw = true; 21329 } 21330 } else if ( tile.loading ) { 21331 // the tile is already in the download queue 21332 tiledImage._tilesLoading++; 21333 } else if (!loadingCoverage) { 21334 best = compareTiles( best, tile ); 21335 } 21336 21337 return best; 21338} 21339
21340/** 21341 * @private 21342 * @inner 21343 * Obtains a tile at the given location. 21344 * @param {Number} x 21345 * @param {Number} y 21346 * @param {Number} level 21347 * @param {OpenSeadragon.TiledImage} tiledImage 21348 * @param {OpenSeadragon.TileSource} tileSource 21349 * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile. 21350 * @param {Number} time 21351 * @param {Number} numTiles 21352 * @param {Number} worldWidth 21353 * @param {Number} worldHeight 21354 * @returns {OpenSeadragon.Tile} 21355 */ 21356function getTile( 21357 x, y, 21358 level, 21359 tiledImage, 21360 tileSource, 21361 tilesMatrix, 21362 time, 21363 numTiles, 21364 worldWidth, 21365 worldHeight 21366) { 21367 var xMod, 21368 yMod, 21369 bounds, 21370 sourceBounds, 21371 exists, 21372 url, 21373 ajaxHeaders, 21374 context2D, 21375 tile; 21376 21377 if ( !tilesMatrix[ level ] ) { 21378 tilesMatrix[ level ] = {}; 21379 } 21380 if ( !tilesMatrix[ level ][ x ] ) { 21381 tilesMatrix[ level ][ x ] = {}; 21382 } 21383 21384 if ( !tilesMatrix[ level ][ x ][ y ] ) { 21385 xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; 21386 yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; 21387 bounds = tileSource.getTileBounds( level, xMod, yMod ); 21388 sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); 21389 exists = tileSource.tileExists( level, xMod, yMod ); 21390 url = tileSource.getTileUrl( level, xMod, yMod ); 21391 21392 // Headers are only applicable if loadTilesWithAjax is set 21393 if (tiledImage.loadTilesWithAjax) { 21394 ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod ); 21395 // Combine tile AJAX headers with tiled image AJAX headers (if applicable) 21396 if ($.isPlainObject(tiledImage.ajaxHeaders)) { 21397 ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders); 21398 } 21399 } else { 21400 ajaxHeaders = null; 21401 } 21402 21403 context2D = tileSource.getContext2D ? 21404 tileSource.getContext2D(level, xMod, yMod) : undefined; 21405 21406 bounds.x += ( x - xMod ) / numTiles.x; 21407 bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y); 21408 21409 tile = new $.Tile( 21410 level, 21411 x, 21412 y, 21413 bounds, 21414 exists, 21415 url, 21416 context2D, 21417 tiledImage.loadTilesWithAjax, 21418 ajaxHeaders, 21419 sourceBounds 21420 ); 21421 21422 if (xMod === numTiles.x - 1) { 21423 tile.isRightMost = true; 21424 } 21425 21426 if (yMod === numTiles.y - 1) { 21427 tile.isBottomMost = true; 21428 } 21429 21430 tilesMatrix[ level ][ x ][ y ] = tile; 21431 } 21432 21433 tile = tilesMatrix[ level ][ x ][ y ]; 21434 tile.lastTouchTime = time; 21435 21436 return tile; 21437} 21438 21439/** 21440 * @private 21441 * @inner 21442 * Dispatch a job to the ImageLoader to load the Image for a Tile. 21443 * @param {OpenSeadragon.TiledImage} tiledImage 21444 * @param {OpenSeadragon.Tile} tile 21445 * @param {Number} time 21446 */ 21447function loadTile( tiledImage, tile, time ) { 21448 tile.loading = true; 21449 tiledImage._imageLoader.addJob({ 21450 src: tile.url, 21451 loadWithAjax: tile.loadWithAjax, 21452 ajaxHeaders: tile.ajaxHeaders, 21453 crossOriginPolicy: tiledImage.crossOriginPolicy, 21454 ajaxWithCredentials: tiledImage.ajaxWithCredentials, 21455 callback: function( image, errorMsg, tileRequest ){ 21456 onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ); 21457 }, 21458 abort: function() { 21459 tile.loading = false; 21460 } 21461 }); 21462} 21463 21464/** 21465 * @private 21466 * @inner 21467 * Callback fired when a Tile's Image finished downloading. 21468 * @param {OpenSeadragon.TiledImage} tiledImage 21469 * @param {OpenSeadragon.Tile} tile 21470 * @param {Number} time 21471 * @param {Image} image 21472 * @param {String} errorMsg 21473 * @param {XMLHttpRequest} tileRequest 21474 */ 21475function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) { 21476 if ( !image ) { 21477 $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); 21478 /** 21479 * Triggered when a tile fails to load. 21480 * 21481 * @event tile-load-failed 21482 * @memberof OpenSeadragon.Viewer 21483 * @type {object} 21484 * @property {OpenSeadragon.Tile} tile - The tile that failed to load. 21485 * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to. 21486 * @property {number} time - The time in milliseconds when the tile load began. 21487 * @property {string} message - The error message. 21488 * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available. 21489 */ 21490 tiledImage.viewer.raiseEvent("tile-load-failed", { 21491 tile: tile, 21492 tiledImage: tiledImage, 21493 time: time, 21494 message: errorMsg, 21495 tileRequest: tileRequest 21496 }); 21497 tile.loading = false;
21498 tile.exists = false; 21499 return; 21500 } 21501 21502 if ( time < tiledImage.lastResetTime ) { 21503 $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); 21504 tile.loading = false; 21505 return; 21506 } 21507 21508 var finish = function() { 21509 var cutoff = tiledImage.source.getClosestLevel(); 21510 setTileLoaded(tiledImage, tile, image, cutoff, tileRequest); 21511 }; 21512 21513 // Check if we're mid-update; this can happen on IE8 because image load events for 21514 // cached images happen immediately there 21515 if ( !tiledImage._midDraw ) { 21516 finish(); 21517 } else { 21518 // Wait until after the update, in case caching unloads any tiles 21519 window.setTimeout( finish, 1); 21520 } 21521} 21522 21523/** 21524 * @private 21525 * @inner 21526 * @param {OpenSeadragon.TiledImage} tiledImage 21527 * @param {OpenSeadragon.Tile} tile 21528 * @param {Image} image 21529 * @param {Number} cutoff 21530 */ 21531function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) { 21532 var increment = 0; 21533 21534 function getCompletionCallback() { 21535 increment++; 21536 return completionCallback; 21537 } 21538 21539 function completionCallback() { 21540 increment--; 21541 if (increment === 0) { 21542 tile.loading = false; 21543 tile.loaded = true; 21544 if (!tile.context2D) { 21545 tiledImage._tileCache.cacheTile({ 21546 image: image, 21547 tile: tile, 21548 cutoff: cutoff, 21549 tiledImage: tiledImage 21550 }); 21551 } 21552 tiledImage._needsDraw = true; 21553 } 21554 } 21555 21556 /** 21557 * Triggered when a tile has just been loaded in memory. That means that the 21558 * image has been downloaded and can be modified before being drawn to the canvas. 21559 * 21560 * @event tile-loaded 21561 * @memberof OpenSeadragon.Viewer 21562 * @type {object} 21563 * @property {Image} image - The image of the tile. 21564 * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile. 21565 * @property {OpenSeadragon.Tile} tile - The tile which has been loaded. 21566 * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable). 21567 * @property {function} getCompletionCallback - A function giving a callback to call 21568 * when the asynchronous processing of the image is done. The image will be 21569 * marked as entirely loaded when the callback has been called once for each 21570 * call to getCompletionCallback. 21571 */ 21572 tiledImage.viewer.raiseEvent("tile-loaded", { 21573 tile: tile, 21574 tiledImage: tiledImage, 21575 tileRequest: tileRequest, 21576 image: image, 21577 getCompletionCallback: getCompletionCallback 21578 }); 21579 // In case the completion callback is never called, we at least force it once. 21580 getCompletionCallback()(); 21581} 21582 21583/** 21584 * @private 21585 * @inner 21586 * @param {OpenSeadragon.Tile} tile 21587 * @param {Boolean} overlap 21588 * @param {OpenSeadragon.Viewport} viewport 21589 * @param {OpenSeadragon.Point} viewportCenter 21590 * @param {Number} levelVisibility 21591 * @param {OpenSeadragon.TiledImage} tiledImage 21592 */ 21593function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){ 21594 var boundsTL = tile.bounds.getTopLeft(); 21595 21596 boundsTL.x *= tiledImage._scaleSpring.current.value; 21597 boundsTL.y *= tiledImage._scaleSpring.current.value; 21598 boundsTL.x += tiledImage._xSpring.current.value; 21599 boundsTL.y += tiledImage._ySpring.current.value; 21600 21601 var boundsSize = tile.bounds.getSize(); 21602 21603 boundsSize.x *= tiledImage._scaleSpring.current.value; 21604 boundsSize.y *= tiledImage._scaleSpring.current.value; 21605 21606 var positionC = viewport.pixelFromPointNoRotate(boundsTL, true), 21607 positionT = viewport.pixelFromPointNoRotate(boundsTL, false), 21608 sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true), 21609 sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false), 21610 tileCenter = positionT.plus( sizeT.divide( 2 ) ), 21611 tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter ); 21612 21613 if ( !overlap ) { 21614 sizeC = sizeC.plus( new $.Point( 1, 1 ) ); 21615 } 21616 21617 if (tile.isRightMost && tiledImage.wrapHorizontal) { 21618 sizeC.x += 0.75; // Otherwise Firefox and Safari show seams 21619 } 21620 21621 if (tile.isBottomMost && tiledImage.wrapVertical) { 21622 sizeC.y += 0.75; // Otherwise Firefox and Safari show seams 21623 } 21624 21625 tile.position = positionC; 21626 tile.size = sizeC; 21627 tile.squaredDistance = tileSquaredDistance; 21628 tile.visibility = levelVisibility; 21629} 21630 21631/** 21632 * @private 21633 * @inner 21634 * Updates the opacity of a tile according to the time it has been on screen 21635 * to perform a fade-in. 21636 * Updates coverage once a tile is fully opaque. 21637 * Returns whether the fade-in has completed. 21638 * 21639 * @param {OpenSeadragon.TiledImage} tiledImage 21640 * @param {OpenSeadragon.Tile} tile 21641 * @param {Number} x 21642 * @param {Number} y 21643 * @param {Number} level 21644 * @param {Number} levelOpacity 21645 * @param {Number} currentTime 21646 * @returns {Boolean} 21647 */ 21648function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){ 21649 var blendTimeMillis = 1000 * tiledImage.blendTime,
21650 deltaTime, 21651 opacity; 21652 21653 if ( !tile.blendStart ) { 21654 tile.blendStart = currentTime; 21655 } 21656 21657 deltaTime = currentTime - tile.blendStart; 21658 opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; 21659 21660 if ( tiledImage.alwaysBlend ) { 21661 opacity *= levelOpacity; 21662 } 21663 21664 tile.opacity = opacity; 21665 21666 tiledImage.lastDrawn.push( tile ); 21667 21668 if ( opacity === 1 ) { 21669 setCoverage( tiledImage.coverage, level, x, y, true ); 21670 tiledImage._hasOpaqueTile = true; 21671 } else if ( deltaTime < blendTimeMillis ) { 21672 return true; 21673 } 21674 21675 return false; 21676} 21677 21678/** 21679 * @private 21680 * @inner 21681 * Returns true if the given tile provides coverage to lower-level tiles of 21682 * lower resolution representing the same content. If neither x nor y is 21683 * given, returns true if the entire visible level provides coverage. 21684 * 21685 * Note that out-of-bounds tiles provide coverage in this sense, since 21686 * there's no content that they would need to cover. Tiles at non-existent 21687 * levels that are within the image bounds, however, do not. 21688 * 21689 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. 21690 * @param {Number} level - The resolution level of the tile. 21691 * @param {Number} x - The X position of the tile. 21692 * @param {Number} y - The Y position of the tile. 21693 * @returns {Boolean} 21694 */ 21695function providesCoverage( coverage, level, x, y ) { 21696 var rows, 21697 cols, 21698 i, j; 21699 21700 if ( !coverage[ level ] ) { 21701 return false; 21702 } 21703 21704 if ( x === undefined || y === undefined ) { 21705 rows = coverage[ level ]; 21706 for ( i in rows ) { 21707 if ( Object.prototype.hasOwnProperty.call( rows, i ) ) { 21708 cols = rows[ i ]; 21709 for ( j in cols ) { 21710 if ( Object.prototype.hasOwnProperty.call( cols, j ) && !cols[ j ] ) { 21711 return false; 21712 } 21713 } 21714 } 21715 } 21716 21717 return true; 21718 } 21719 21720 return ( 21721 coverage[ level ][ x] === undefined || 21722 coverage[ level ][ x ][ y ] === undefined || 21723 coverage[ level ][ x ][ y ] === true 21724 ); 21725} 21726 21727/** 21728 * @private 21729 * @inner 21730 * Returns true if the given tile is completely covered by higher-level 21731 * tiles of higher resolution representing the same content. If neither x 21732 * nor y is given, returns true if the entire visible level is covered. 21733 * 21734 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. 21735 * @param {Number} level - The resolution level of the tile. 21736 * @param {Number} x - The X position of the tile. 21737 * @param {Number} y - The Y position of the tile. 21738 * @returns {Boolean} 21739 */ 21740function isCovered( coverage, level, x, y ) { 21741 if ( x === undefined || y === undefined ) { 21742 return providesCoverage( coverage, level + 1 ); 21743 } else { 21744 return ( 21745 providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && 21746 providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && 21747 providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && 21748 providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) 21749 ); 21750 } 21751} 21752 21753/** 21754 * @private 21755 * @inner 21756 * Sets whether the given tile provides coverage or not. 21757 * 21758 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. 21759 * @param {Number} level - The resolution level of the tile. 21760 * @param {Number} x - The X position of the tile. 21761 * @param {Number} y - The Y position of the tile. 21762 * @param {Boolean} covers - Whether the tile provides coverage. 21763 */ 21764function setCoverage( coverage, level, x, y, covers ) { 21765 if ( !coverage[ level ] ) { 21766 $.console.warn( 21767 "Setting coverage for a tile before its level's coverage has been reset: %s", 21768 level 21769 ); 21770 return; 21771 } 21772 21773 if ( !coverage[ level ][ x ] ) { 21774 coverage[ level ][ x ] = {}; 21775 } 21776 21777 coverage[ level ][ x ][ y ] = covers; 21778} 21779 21780/** 21781 * @private 21782 * @inner 21783 * Resets coverage information for the given level. This should be called 21784 * after every draw routine. Note that at the beginning of the next draw 21785 * routine, coverage for every visible tile should be explicitly set. 21786 * 21787 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21788 * @param {Number} level - The resolution level of tiles to completely reset. 21789 */ 21790function resetCoverage( coverage, level ) { 21791 coverage[ level ] = {}; 21792} 21793 21794/** 21795 * @private 21796 * @inner 21797 * Determines whether the 'last best' tile for the area is better than the 21798 * tile in question. 21799 * 21800 * @param {OpenSeadragon.Tile} previousBest 21801 * @param {OpenSeadragon.Tile} tile 21802 * @returns {OpenSeadragon.Tile} The new best tile. 21803 */ 21804function compareTiles( previousBest, tile ) { 21805 if ( !previousBest ) { 21806 return tile; 21807 } 21808 21809 if ( tile.visibility > previousBest.visibility ) { 21810 return tile; 21811 } else if ( tile.visibility == previousBest.visibility ) { 21812 if ( tile.squaredDistance < previousBest.squaredDistance ) { 21813 return tile; 21814 } 21815 } 21816 21817 return previousBest; 21818} 21819 21820/** 21821 * @private 21822 * @inner 21823 * Draws a TiledImage. 21824 * @param {OpenSeadragon.TiledImage} tiledImage 21825 * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. 21826 */ 21827function drawTiles( tiledImage, lastDrawn ) { 21828 if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) { 21829 return; 21830 } 21831 21832 var tile = lastDrawn[0]; 21833 var useSketch; 21834 21835 if (tile) { 21836 useSketch = tiledImage.opacity < 1 || 21837 (tiledImage.compositeOperation && 21838 tiledImage.compositeOperation !== 'source-over') || 21839 (!tiledImage._isBottomItem() && tile._hasTransparencyChannel()); 21840 } 21841 21842 var sketchScale; 21843 var sketchTranslate; 21844 21845 var zoom = tiledImage.viewport.getZoom(true); 21846 var imageZoom = tiledImage.viewportToImageZoom(zoom); 21847 21848 if (lastDrawn.length > 1 && 21849 imageZoom > tiledImage.smoothTileEdgesMinZoom && 21850 !tiledImage.iOSDevice && 21851 tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation. 21852 $.supportsCanvas) { 21853 // When zoomed in a lot (>100%) the tile edges are visible. 21854 // So we have to composite them at ~100% and scale them up together. 21855 // Note: Disabled on iOS devices per default as it causes a native crash 21856 useSketch = true; 21857 sketchScale = tile.getScaleForEdgeSmoothing(); 21858 sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale, 21859 tiledImage._drawer.getCanvasSize(false), 21860 tiledImage._drawer.getCanvasSize(true)); 21861 } 21862 21863 var bounds; 21864 if (useSketch) { 21865 if (!sketchScale) { 21866 // Except when edge smoothing, we only clean the part of the 21867 // sketch canvas we are going to use for performance reasons. 21868 bounds = tiledImage.viewport.viewportToViewerElementRectangle( 21869 tiledImage.getClippedBounds(true)) 21870 .getIntegerBoundingBox(); 21871 21872 if(tiledImage._drawer.viewer.viewport.getFlip()) { 21873 if (tiledImage.viewport.degrees !== 0 || tiledImage.getRotation(true) % 360 !== 0){ 21874 bounds.x = tiledImage._drawer.viewer.container.clientWidth - (bounds.x + bounds.width); 21875 } 21876 } 21877 21878 bounds = bounds.times($.pixelDensityRatio); 21879 } 21880 tiledImage._drawer._clear(true, bounds); 21881 } 21882 21883 // When scaling, we must rotate only when blending the sketch canvas to 21884 // avoid interpolation 21885 if (!sketchScale) { 21886 if (tiledImage.viewport.degrees !== 0) { 21887 tiledImage._drawer._offsetForRotation({ 21888 degrees: tiledImage.viewport.degrees, 21889 useSketch: useSketch 21890 }); 21891 } 21892 if (tiledImage.getRotation(true) % 360 !== 0) { 21893 tiledImage._drawer._offsetForRotation({ 21894 degrees: tiledImage.getRotation(true), 21895 point: tiledImage.viewport.pixelFromPointNoRotate( 21896 tiledImage._getRotationPoint(true), true), 21897 useSketch: useSketch 21898 }); 21899 } 21900 21901 if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ 21902 if(tiledImage._drawer.viewer.viewport.getFlip()) { 21903 tiledImage._drawer._flip(); 21904 } 21905 } 21906 } 21907 21908 var usedClip = false;
21909 if ( tiledImage._clip ) { 21910 tiledImage._drawer.saveContext(useSketch); 21911 21912 var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); 21913 box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); 21914 var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box); 21915 if (sketchScale) { 21916 clipRect = clipRect.times(sketchScale); 21917 } 21918 if (sketchTranslate) { 21919 clipRect = clipRect.translate(sketchTranslate); 21920 } 21921 tiledImage._drawer.setClip(clipRect, useSketch); 21922 21923 usedClip = true; 21924 } 21925 21926 if (tiledImage._croppingPolygons) { 21927 tiledImage._drawer.saveContext(useSketch); 21928 try { 21929 var polygons = tiledImage._croppingPolygons.map(function (polygon) { 21930 return polygon.map(function (coord) { 21931 var point = tiledImage 21932 .imageToViewportCoordinates(coord.x, coord.y, true) 21933 .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); 21934 var clipPoint = tiledImage._drawer.viewportCoordToDrawerCoord(point); 21935 if (sketchScale) { 21936 clipPoint = clipPoint.times(sketchScale); 21937 } 21938 return clipPoint; 21939 }); 21940 }); 21941 tiledImage._drawer.clipWithPolygons(polygons, useSketch); 21942 } catch (e) { 21943 $.console.error(e); 21944 } 21945 usedClip = true; 21946 } 21947 21948 if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) { 21949 var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true)); 21950 if (sketchScale) { 21951 placeholderRect = placeholderRect.times(sketchScale); 21952 } 21953 if (sketchTranslate) { 21954 placeholderRect = placeholderRect.translate(sketchTranslate); 21955 } 21956 21957 var fillStyle = null; 21958 if ( typeof tiledImage.placeholderFillStyle === "function" ) { 21959 fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context); 21960 } 21961 else { 21962 fillStyle = tiledImage.placeholderFillStyle; 21963 } 21964 21965 tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch); 21966 } 21967 21968 for (var i = lastDrawn.length - 1; i >= 0; i--) { 21969 tile = lastDrawn[ i ]; 21970 tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate ); 21971 tile.beingDrawn = true; 21972 21973 if( tiledImage.viewer ){ 21974 /** 21975 * <em>- Needs documentation -</em> 21976 * 21977 * @event tile-drawn 21978 * @memberof OpenSeadragon.Viewer 21979 * @type {object} 21980 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 21981 * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. 21982 * @property {OpenSeadragon.Tile} tile 21983 * @property {?Object} userData - Arbitrary subscriber-defined object. 21984 */ 21985 tiledImage.viewer.raiseEvent( 'tile-drawn', { 21986 tiledImage: tiledImage, 21987 tile: tile 21988 }); 21989 } 21990 } 21991 21992 if ( usedClip ) { 21993 tiledImage._drawer.restoreContext( useSketch ); 21994 } 21995 21996 if (!sketchScale) { 21997 if (tiledImage.getRotation(true) % 360 !== 0) { 21998 tiledImage._drawer._restoreRotationChanges(useSketch); 21999 } 22000 if (tiledImage.viewport.degrees !== 0) { 22001 tiledImage._drawer._restoreRotationChanges(useSketch); 22002 } 22003 } 22004 22005 if (useSketch) { 22006 if (sketchScale) { 22007 if (tiledImage.viewport.degrees !== 0) { 22008 tiledImage._drawer._offsetForRotation({ 22009 degrees: tiledImage.viewport.degrees, 22010 useSketch: false 22011 }); 22012 } 22013 if (tiledImage.getRotation(true) % 360 !== 0) { 22014 tiledImage._drawer._offsetForRotation({ 22015 degrees: tiledImage.getRotation(true), 22016 point: tiledImage.viewport.pixelFromPointNoRotate( 22017 tiledImage._getRotationPoint(true), true), 22018 useSketch: false 22019 }); 22020 } 22021 } 22022 tiledImage._drawer.blendSketch({ 22023 opacity: tiledImage.opacity, 22024 scale: sketchScale, 22025 translate: sketchTranslate, 22026 compositeOperation: tiledImage.compositeOperation,
22027 bounds: bounds 22028 }); 22029 if (sketchScale) { 22030 if (tiledImage.getRotation(true) % 360 !== 0) { 22031 tiledImage._drawer._restoreRotationChanges(false); 22032 } 22033 if (tiledImage.viewport.degrees !== 0) { 22034 tiledImage._drawer._restoreRotationChanges(false); 22035 } 22036 } 22037 } 22038 22039 if (!sketchScale) { 22040 if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ 22041 if(tiledImage._drawer.viewer.viewport.getFlip()) { 22042 tiledImage._drawer._flip(); 22043 } 22044 } 22045 } 22046 22047 drawDebugInfo( tiledImage, lastDrawn ); 22048} 22049 22050/** 22051 * @private 22052 * @inner 22053 * Draws special debug information for a TiledImage if in debug mode. 22054 * @param {OpenSeadragon.TiledImage} tiledImage 22055 * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. 22056 */ 22057function drawDebugInfo( tiledImage, lastDrawn ) { 22058 if( tiledImage.debugMode ) { 22059 for ( var i = lastDrawn.length - 1; i >= 0; i-- ) { 22060 var tile = lastDrawn[ i ]; 22061 try { 22062 tiledImage._drawer.drawDebugInfo( 22063 tile, lastDrawn.length, i, tiledImage); 22064 } catch(e) { 22065 $.console.error(e); 22066 } 22067 } 22068 } 22069} 22070 22071}( OpenSeadragon )); 22072 22073/* 22074 * OpenSeadragon - TileCache 22075 * 22076 * Copyright (C) 2009 CodePlex Foundation 22077 * Copyright (C) 2010-2013 OpenSeadragon contributors 22078 * 22079 * Redistribution and use in source and binary forms, with or without 22080 * modification, are permitted provided that the following conditions are 22081 * met: 22082 * 22083 * - Redistributions of source code must retain the above copyright notice, 22084 * this list of conditions and the following disclaimer. 22085 * 22086 * - Redistributions in binary form must reproduce the above copyright 22087 * notice, this list of conditions and the following disclaimer in the 22088 * documentation and/or other materials provided with the distribution. 22089 * 22090 * - Neither the name of CodePlex Foundation nor the names of its 22091 * contributors may be used to endorse or promote products derived from 22092 * this software without specific prior written permission. 22093 * 22094 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22095 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22096 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22097 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22098 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22099 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22100 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22101 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22102 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22103 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22104 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22105 */ 22106 22107(function( $ ){ 22108 22109// private class 22110var TileRecord = function( options ) { 22111 $.console.assert( options, "[TileCache.cacheTile] options is required" ); 22112 $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" ); 22113 $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" ); 22114 this.tile = options.tile; 22115 this.tiledImage = options.tiledImage; 22116}; 22117 22118// private class 22119var ImageRecord = function(options) { 22120 $.console.assert( options, "[ImageRecord] options is required" ); 22121 $.console.assert( options.image, "[ImageRecord] options.image is required" ); 22122 this._image = options.image; 22123 this._tiles = []; 22124}; 22125 22126ImageRecord.prototype = { 22127 destroy: function() { 22128 this._image = null; 22129 this._renderedContext = null; 22130 this._tiles = null; 22131 }, 22132 22133 getImage: function() { 22134 return this._image; 22135 }, 22136 22137 getRenderedContext: function() { 22138 if (!this._renderedContext) { 22139 var canvas = document.createElement( 'canvas' ); 22140 canvas.width = this._image.width; 22141 canvas.height = this._image.height; 22142 this._renderedContext = canvas.getContext('2d'); 22143 this._renderedContext.drawImage( this._image, 0, 0 ); 22144 //since we are caching the prerendered image on a canvas 22145 //allow the image to not be held in memory 22146 this._image = null; 22147 } 22148 return this._renderedContext; 22149 }, 22150 22151 setRenderedContext: function(renderedContext) { 22152 $.console.error("ImageRecord.setRenderedContext is deprecated. " + 22153 "The rendered context should be created by the ImageRecord " + 22154 "itself when calling ImageRecord.getRenderedContext."); 22155 this._renderedContext = renderedContext; 22156 }, 22157 22158 addTile: function(tile) { 22159 $.console.assert(tile, '[ImageRecord.addTile] tile is required'); 22160 this._tiles.push(tile); 22161 }, 22162 22163 removeTile: function(tile) { 22164 for (var i = 0; i < this._tiles.length; i++) { 22165 if (this._tiles[i] === tile) { 22166 this._tiles.splice(i, 1); 22167 return; 22168 } 22169 } 22170 22171 $.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile); 22172 }, 22173 22174 getTileCount: function() { 22175 return this._tiles.length; 22176 } 22177}; 22178 22179/** 22180 * @class TileCache 22181 * @memberof OpenSeadragon 22182 * @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}. 22183 * You generally won't have to interact with the TileCache directly. 22184 * @param {Object} options - Configuration for this TileCache. 22185 * @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in 22186 * {@link OpenSeadragon.Options} for details. 22187 */ 22188$.TileCache = function( options ) { 22189 options = options || {}; 22190 22191 this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount; 22192 this._tilesLoaded = []; 22193 this._imagesLoaded = []; 22194 this._imagesLoadedCount = 0; 22195}; 22196 22197/** @lends OpenSeadragon.TileCache.prototype */ 22198$.TileCache.prototype = { 22199 /** 22200 * @returns {Number} The total number of tiles that have been loaded by 22201 * this TileCache. 22202 */ 22203 numTilesLoaded: function() { 22204 return this._tilesLoaded.length; 22205 }, 22206 22207 /**
22208 * Caches the specified tile, removing an old tile if necessary to stay under the 22209 * maxImageCacheCount specified on construction. Note that if multiple tiles reference 22210 * the same image, there may be more tiles than maxImageCacheCount; the goal is to keep 22211 * the number of images below that number. Note, as well, that even the number of images 22212 * may temporarily surpass that number, but should eventually come back down to the max specified. 22213 * @param {Object} options - Tile info. 22214 * @param {OpenSeadragon.Tile} options.tile - The tile to cache. 22215 * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache. 22216 * @param {Image} options.image - The image of the tile to cache. 22217 * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile. 22218 * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this 22219 * function will release an old tile. The cutoff option specifies a tile level at or below which 22220 * tiles will not be released. 22221 */ 22222 cacheTile: function( options ) { 22223 $.console.assert( options, "[TileCache.cacheTile] options is required" ); 22224 $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" ); 22225 $.console.assert( options.tile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" ); 22226 $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" ); 22227 22228 var cutoff = options.cutoff || 0; 22229 var insertionIndex = this._tilesLoaded.length; 22230 22231 var imageRecord = this._imagesLoaded[options.tile.cacheKey]; 22232 if (!imageRecord) { 22233 $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" ); 22234 imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({ 22235 image: options.image 22236 }); 22237 22238 this._imagesLoadedCount++; 22239 } 22240 22241 imageRecord.addTile(options.tile); 22242 options.tile.cacheImageRecord = imageRecord; 22243 22244 // Note that just because we're unloading a tile doesn't necessarily mean 22245 // we're unloading an image. With repeated calls it should sort itself out, though. 22246 if ( this._imagesLoadedCount > this._maxImageCacheCount ) { 22247 var worstTile = null; 22248 var worstTileIndex = -1; 22249 var worstTileRecord = null; 22250 var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord; 22251 22252 for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) { 22253 prevTileRecord = this._tilesLoaded[ i ]; 22254 prevTile = prevTileRecord.tile; 22255 22256 if ( prevTile.level <= cutoff || prevTile.beingDrawn ) { 22257 continue; 22258 } else if ( !worstTile ) { 22259 worstTile = prevTile; 22260 worstTileIndex = i; 22261 worstTileRecord = prevTileRecord; 22262 continue; 22263 } 22264 22265 prevTime = prevTile.lastTouchTime; 22266 worstTime = worstTile.lastTouchTime; 22267 prevLevel = prevTile.level; 22268 worstLevel = worstTile.level; 22269 22270 if ( prevTime < worstTime || 22271 ( prevTime == worstTime && prevLevel > worstLevel ) ) { 22272 worstTile = prevTile; 22273 worstTileIndex = i; 22274 worstTileRecord = prevTileRecord; 22275 } 22276 } 22277 22278 if ( worstTile && worstTileIndex >= 0 ) { 22279 this._unloadTile(worstTileRecord); 22280 insertionIndex = worstTileIndex; 22281 } 22282 } 22283 22284 this._tilesLoaded[ insertionIndex ] = new TileRecord({ 22285 tile: options.tile, 22286 tiledImage: options.tiledImage 22287 }); 22288 }, 22289 22290 /** 22291 * Clears all tiles associated with the specified tiledImage. 22292 * @param {OpenSeadragon.TiledImage} tiledImage 22293 */ 22294 clearTilesFor: function( tiledImage ) { 22295 $.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required'); 22296 var tileRecord; 22297 for ( var i = 0; i < this._tilesLoaded.length; ++i ) { 22298 tileRecord = this._tilesLoaded[ i ]; 22299 if ( tileRecord.tiledImage === tiledImage ) { 22300 this._unloadTile(tileRecord); 22301 this._tilesLoaded.splice( i, 1 ); 22302 i--; 22303 } 22304 } 22305 }, 22306 22307 // private 22308 getImageRecord: function(cacheKey) { 22309 $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required'); 22310 return this._imagesLoaded[cacheKey]; 22311 }, 22312 22313 // private 22314 _unloadTile: function(tileRecord) { 22315 $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required'); 22316 var tile = tileRecord.tile; 22317 var tiledImage = tileRecord.tiledImage; 22318 22319 tile.unload(); 22320 tile.cacheImageRecord = null; 22321 22322 var imageRecord = this._imagesLoaded[tile.cacheKey]; 22323 imageRecord.removeTile(tile); 22324 if (!imageRecord.getTileCount()) { 22325 imageRecord.destroy(); 22326 delete this._imagesLoaded[tile.cacheKey]; 22327 this._imagesLoadedCount--; 22328 } 22329 22330 /** 22331 * Triggered when a tile has just been unloaded from memory. 22332 * 22333 * @event tile-unloaded 22334 * @memberof OpenSeadragon.Viewer 22335 * @type {object} 22336 * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile. 22337 * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded. 22338 */ 22339 tiledImage.viewer.raiseEvent("tile-unloaded", { 22340 tile: tile, 22341 tiledImage: tiledImage 22342 }); 22343 } 22344}; 22345 22346}( OpenSeadragon )); 22347 22348/* 22349 * OpenSeadragon - World 22350 * 22351 * Copyright (C) 2009 CodePlex Foundation 22352 * Copyright (C) 2010-2013 OpenSeadragon contributors 22353 * 22354 * Redistribution and use in source and binary forms, with or without 22355 * modification, are permitted provided that the following conditions are 22356 * met: 22357 * 22358 * - Redistributions of source code must retain the above copyright notice, 22359 * this list of conditions and the following disclaimer. 22360 * 22361 * - Redistributions in binary form must reproduce the above copyright 22362 * notice, this list of conditions and the following disclaimer in the 22363 * documentation and/or other materials provided with the distribution. 22364 * 22365 * - Neither the name of CodePlex Foundation nor the names of its 22366 * contributors may be used to endorse or promote products derived from 22367 * this software without specific prior written permission. 22368 * 22369 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22370 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22371 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22372 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22373 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22374 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22375 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22376 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22377 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22378 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22379 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22380 */ 22381 22382(function( $ ){ 22383 22384/** 22385 * @class World 22386 * @memberof OpenSeadragon 22387 * @extends OpenSeadragon.EventSource 22388 * @classdesc Keeps track of all of the tiled images in the scene. 22389 * @param {Object} options - World options. 22390 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World. 22391 **/ 22392$.World = function( options ) { 22393 var _this = this; 22394 22395 $.console.assert( options.viewer, "[World] options.viewer is required" ); 22396 22397 $.EventSource.call( this ); 22398 22399 this.viewer = options.viewer; 22400 this._items = []; 22401 this._needsDraw = false; 22402 this._autoRefigureSizes = true; 22403 this._needsSizesFigured = false; 22404 this._delegatedFigureSizes = function(event) { 22405 if (_this._autoRefigureSizes) { 22406 _this._figureSizes(); 22407 } else { 22408 _this._needsSizesFigured = true; 22409 } 22410 }; 22411 22412 this._figureSizes(); 22413}; 22414 22415$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{ 22416 /** 22417 * Add the specified item. 22418 * @param {OpenSeadragon.TiledImage} item - The item to add. 22419 * @param {Number} [options.index] - Index for the item. If not specified, goes at the top. 22420 * @fires OpenSeadragon.World.event:add-item 22421 * @fires OpenSeadragon.World.event:metrics-change 22422 */ 22423 addItem: function( item, options ) { 22424 $.console.assert(item, "[World.addItem] item is required"); 22425 $.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time"); 22426 22427 options = options || {}; 22428 if (options.index !== undefined) { 22429 var index = Math.max(0, Math.min(this._items.length, options.index)); 22430 this._items.splice(index, 0, item); 22431 } else { 22432 this._items.push( item ); 22433 } 22434 22435 if (this._autoRefigureSizes) { 22436 this._figureSizes(); 22437 } else { 22438 this._needsSizesFigured = true; 22439 } 22440 22441 this._needsDraw = true; 22442 22443 item.addHandler('bounds-change', this._delegatedFigureSizes); 22444 item.addHandler('clip-change', this._delegatedFigureSizes); 22445 22446 /** 22447 * Raised when an item is added to the World. 22448 * @event add-item 22449 * @memberOf OpenSeadragon.World 22450 * @type {object} 22451 * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event. 22452 * @property {OpenSeadragon.TiledImage} item - The item that has been added. 22453 * @property {?Object} userData - Arbitrary subscriber-defined object. 22454 */ 22455 this.raiseEvent( 'add-item', { 22456 item: item 22457 } ); 22458 }, 22459 22460 /** 22461 * Get the item at the specified index. 22462 * @param {Number} index - The item's index. 22463 * @returns {OpenSeadragon.TiledImage} The item at the specified index. 22464 */ 22465 getItemAt: function( index ) { 22466 $.console.assert(index !== undefined, "[World.getItemAt] index is required"); 22467 return this._items[ index ]; 22468 }, 22469 22470 /** 22471 * Get the index of the given item or -1 if not present. 22472 * @param {OpenSeadragon.TiledImage} item - The item. 22473 * @returns {Number} The index of the item or -1 if not present. 22474 */ 22475 getIndexOfItem: function( item ) { 22476 $.console.assert(item, "[World.getIndexOfItem] item is required"); 22477 return $.indexOf( this._items, item ); 22478 }, 22479 22480 /** 22481 * @returns {Number} The number of items used. 22482 */ 22483 getItemCount: function() { 22484 return this._items.length; 22485 }, 22486 22487 /** 22488 * Change the index of a item so that it appears over or under others. 22489 * @param {OpenSeadragon.TiledImage} item - The item to move. 22490 * @param {Number} index - The new index. 22491 * @fires OpenSeadragon.World.event:item-index-change 22492 */ 22493 setItemIndex: function( item, index ) { 22494 $.console.assert(item, "[World.setItemIndex] item is required"); 22495 $.console.assert(index !== undefined, "[World.setItemIndex] index is required"); 22496 22497 var oldIndex = this.getIndexOfItem( item ); 22498 22499 if ( index >= this._items.length ) { 22500 throw new Error( "Index bigger than number of layers." ); 22501 } 22502 22503 if ( index === oldIndex || oldIndex === -1 ) { 22504 return; 22505 } 22506 22507 this._items.splice( oldIndex, 1 ); 22508 this._items.splice( index, 0, item ); 22509 this._needsDraw = true; 22510 22511 /** 22512 * Raised when the order of the indexes has been changed. 22513 * @event item-index-change 22514 * @memberOf OpenSeadragon.World 22515 * @type {object} 22516 * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event. 22517 * @property {OpenSeadragon.TiledImage} item - The item whose index has 22518 * been changed 22519 * @property {Number} previousIndex - The previous index of the item 22520 * @property {Number} newIndex - The new index of the item
22521 * @property {?Object} userData - Arbitrary subscriber-defined object. 22522 */ 22523 this.raiseEvent( 'item-index-change', { 22524 item: item, 22525 previousIndex: oldIndex, 22526 newIndex: index 22527 } ); 22528 }, 22529 22530 /** 22531 * Remove an item. 22532 * @param {OpenSeadragon.TiledImage} item - The item to remove. 22533 * @fires OpenSeadragon.World.event:remove-item 22534 * @fires OpenSeadragon.World.event:metrics-change 22535 */ 22536 removeItem: function( item ) { 22537 $.console.assert(item, "[World.removeItem] item is required"); 22538 22539 var index = $.indexOf(this._items, item ); 22540 if ( index === -1 ) { 22541 return; 22542 } 22543 22544 item.removeHandler('bounds-change', this._delegatedFigureSizes); 22545 item.removeHandler('clip-change', this._delegatedFigureSizes); 22546 item.destroy(); 22547 this._items.splice( index, 1 ); 22548 this._figureSizes(); 22549 this._needsDraw = true; 22550 this._raiseRemoveItem(item); 22551 }, 22552 22553 /** 22554 * Remove all items. 22555 * @fires OpenSeadragon.World.event:remove-item 22556 * @fires OpenSeadragon.World.event:metrics-change 22557 */ 22558 removeAll: function() { 22559 // We need to make sure any pending images are canceled so the world items don't get messed up 22560 this.viewer._cancelPendingImages(); 22561 var item; 22562 var i; 22563 for (i = 0; i < this._items.length; i++) { 22564 item = this._items[i]; 22565 item.removeHandler('bounds-change', this._delegatedFigureSizes); 22566 item.removeHandler('clip-change', this._delegatedFigureSizes); 22567 item.destroy(); 22568 } 22569 22570 var removedItems = this._items; 22571 this._items = []; 22572 this._figureSizes(); 22573 this._needsDraw = true; 22574 22575 for (i = 0; i < removedItems.length; i++) { 22576 item = removedItems[i]; 22577 this._raiseRemoveItem(item); 22578 } 22579 }, 22580 22581 /** 22582 * Clears all tiles and triggers updates for all items. 22583 */ 22584 resetItems: function() { 22585 for ( var i = 0; i < this._items.length; i++ ) { 22586 this._items[i].reset(); 22587 } 22588 }, 22589 22590 /** 22591 * Updates (i.e. animates bounds of) all items. 22592 */ 22593 update: function() { 22594 var animated = false; 22595 for ( var i = 0; i < this._items.length; i++ ) { 22596 animated = this._items[i].update() || animated; 22597 } 22598 22599 return animated; 22600 }, 22601 22602 /** 22603 * Draws all items. 22604 */ 22605 draw: function() { 22606 for ( var i = 0; i < this._items.length; i++ ) { 22607 this._items[i].draw(); 22608 } 22609 22610 this._needsDraw = false; 22611 }, 22612 22613 /** 22614 * @returns {Boolean} true if any items need updating. 22615 */ 22616 needsDraw: function() { 22617 for ( var i = 0; i < this._items.length; i++ ) { 22618 if ( this._items[i].needsDraw() ) { 22619 return true; 22620 } 22621 } 22622 return this._needsDraw; 22623 }, 22624 22625 /** 22626 * @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates. 22627 */ 22628 getHomeBounds: function() { 22629 return this._homeBounds.clone(); 22630 }, 22631 22632 /** 22633 * To facilitate zoom constraints, we keep track of the pixel density of the 22634 * densest item in the World (i.e. the item whose content size to viewport size 22635 * ratio is the highest) and save it as this "content factor". 22636 * @returns {Number} the number of content units per viewport unit. 22637 */ 22638 getContentFactor: function() { 22639 return this._contentFactor; 22640 }, 22641 22642 /** 22643 * As a performance optimization, setting this flag to false allows the bounds-change event handler 22644 * on tiledImages to skip calculations on the world bounds. If a lot of images are going to be positioned in 22645 * rapid succession, this is a good idea. When finished, setAutoRefigureSizes should be called with true 22646 * or the system may behave oddly. 22647 * @param {Boolean} [value] The value to which to set the flag. 22648 */ 22649 setAutoRefigureSizes: function(value) { 22650 this._autoRefigureSizes = value; 22651 if (value & this._needsSizesFigured) { 22652 this._figureSizes(); 22653 this._needsSizesFigured = false;
22654 } 22655 }, 22656 22657 /** 22658 * Arranges all of the TiledImages with the specified settings. 22659 * @param {Object} options - Specifies how to arrange. 22660 * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement. 22661 * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}. 22662 * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}. 22663 * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}. 22664 * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}. 22665 * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}. 22666 * @fires OpenSeadragon.World.event:metrics-change 22667 */ 22668 arrange: function(options) { 22669 options = options || {}; 22670 var immediately = options.immediately || false; 22671 var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout; 22672 var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows; 22673 var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns; 22674 var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize; 22675 var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin; 22676 var increment = tileSize + tileMargin; 22677 var wrap; 22678 if (!options.rows && columns) { 22679 wrap = columns; 22680 } else { 22681 wrap = Math.ceil(this._items.length / rows); 22682 } 22683 var x = 0; 22684 var y = 0; 22685 var item, box, width, height, position; 22686 22687 this.setAutoRefigureSizes(false); 22688 for (var i = 0; i < this._items.length; i++) { 22689 if (i && (i % wrap) === 0) { 22690 if (layout === 'horizontal') { 22691 y += increment; 22692 x = 0; 22693 } else { 22694 x += increment; 22695 y = 0; 22696 } 22697 } 22698 22699 item = this._items[i]; 22700 box = item.getBounds(); 22701 if (box.width > box.height) { 22702 width = tileSize; 22703 } else { 22704 width = tileSize * (box.width / box.height); 22705 } 22706 22707 height = width * (box.height / box.width); 22708 position = new $.Point(x + ((tileSize - width) / 2), 22709 y + ((tileSize - height) / 2)); 22710 22711 item.setPosition(position, immediately); 22712 item.setWidth(width, immediately); 22713 22714 if (layout === 'horizontal') { 22715 x += increment; 22716 } else { 22717 y += increment; 22718 } 22719 } 22720 this.setAutoRefigureSizes(true); 22721 }, 22722 22723 // private 22724 _figureSizes: function() { 22725 var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null; 22726 var oldContentSize = this._contentSize ? this._contentSize.clone() : null; 22727 var oldContentFactor = this._contentFactor || 0; 22728 22729 if (!this._items.length) { 22730 this._homeBounds = new $.Rect(0, 0, 1, 1); 22731 this._contentSize = new $.Point(1, 1); 22732 this._contentFactor = 1; 22733 } else { 22734 var item = this._items[0]; 22735 var bounds = item.getBounds(); 22736 this._contentFactor = item.getContentSize().x / bounds.width; 22737 var clippedBounds = item.getClippedBounds().getBoundingBox(); 22738 var left = clippedBounds.x; 22739 var top = clippedBounds.y; 22740 var right = clippedBounds.x + clippedBounds.width; 22741 var bottom = clippedBounds.y + clippedBounds.height; 22742 for (var i = 1; i < this._items.length; i++) { 22743 item = this._items[i]; 22744 bounds = item.getBounds(); 22745 this._contentFactor = Math.max(this._contentFactor, 22746 item.getContentSize().x / bounds.width); 22747 clippedBounds = item.getClippedBounds().getBoundingBox(); 22748 left = Math.min(left, clippedBounds.x); 22749 top = Math.min(top, clippedBounds.y); 22750 right = Math.max(right, clippedBounds.x + clippedBounds.width);
vendor: 1,623 bytes, lines 22751-22792
22751 bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height); 22752 } 22753 22754 this._homeBounds = new $.Rect(left, top, right - left, bottom - top); 22755 this._contentSize = new $.Point( 22756 this._homeBounds.width * this._contentFactor, 22757 this._homeBounds.height * this._contentFactor); 22758 } 22759 22760 if (this._contentFactor !== oldContentFactor || 22761 !this._homeBounds.equals(oldHomeBounds) || 22762 !this._contentSize.equals(oldContentSize)) { 22763 /** 22764 * Raised when the home bounds or content factor change. 22765 * @event metrics-change 22766 * @memberOf OpenSeadragon.World 22767 * @type {object} 22768 * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event. 22769 * @property {?Object} userData - Arbitrary subscriber-defined object. 22770 */ 22771 this.raiseEvent('metrics-change', {}); 22772 } 22773 }, 22774 22775 // private 22776 _raiseRemoveItem: function(item) { 22777 /** 22778 * Raised when an item is removed. 22779 * @event remove-item 22780 * @memberOf OpenSeadragon.World 22781 * @type {object} 22782 * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event. 22783 * @property {OpenSeadragon.TiledImage} item - The item's underlying item. 22784 * @property {?Object} userData - Arbitrary subscriber-defined object. 22785 */ 22786 this.raiseEvent( 'remove-item', { item: item } ); 22787 } 22788}); 22789 22790}( OpenSeadragon )); 22791 22792//# sourceMappingURL=openseadragon.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.