1//! OpenSeadragon 1.1.1 2//! Built on 2014-07-11 3//! Git commit: v1.1.1-18-ef8bd1a 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 * @version OpenSeadragon 1.1.1 93 * 94 * @file 95 * <h2><strong>OpenSeadragon - Javascript Deep Zooming</strong></h2> 96 * <p> 97 * OpenSeadragon provides an html interface for creating 98 * deep zoom user interfaces. The simplest examples include deep 99 * zoom for large resolution images, and complex examples include 100 * zoomable map interfaces driven by SVG files. 101 * </p> 102 * 103 */ 104 105/** 106 * @module OpenSeadragon 107 * 108 */ 109 110/** 111 * @namespace OpenSeadragon 112 * 113 * @classdesc The root namespace for OpenSeadragon. All utility methods 114 * and classes are defined on or below this namespace. 115 * 116 */ 117 118 119// Typedefs 120 121 /** 122 * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer. 123 * 124 * @typedef {Object} Options 125 * @memberof OpenSeadragon 126 * 127 * @property {String} id 128 * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided. 129 * If both the element and id properties are specified, the viewer is appended to the element provided in the element property. 130 * 131 * @property {Element} element 132 * The element to append the viewer's container element to. If not provided, the 'id' property must be provided. 133 * If both the element and id properties are specified, the viewer is appended to the element provided in the element property. 134 * 135 * @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null] 136 * As an Array, the tileSource can hold either Objects or mixed 137 * types of Arrays of Objects, Strings, or Functions. When a value is a String, 138 * the tileSource is used to create a {@link OpenSeadragon.DziTileSource}. 139 * When a value is a Function, the function is used to create a new 140 * {@link OpenSeadragon.TileSource} whose abstract method 141 * getUrl( level, x, y ) is implemented by the function. Finally, when it 142 * is an Array of objects, it is used to create a 143 * {@link OpenSeadragon.LegacyTileSource}. 144 * 145 * @property {Array} overlays Array of objects defining permanent overlays of 146 * the viewer. The overlays added via this option and later removed with 147 * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new 148 * image is opened. 149 * To add overlays which can be definitively removed, one must use
150 * {@link OpenSeadragon.Viewer#addOverlay} 151 * If displaying a sequence of images, the overlays can be associated 152 * with a specific page by passing the overlays array to the page's 153 * tile source configuration. 154 * Expected properties: 155 * * x, y, (or px, py for pixel coordinates) to define the location. 156 * * width, height in point if using x,y or in pixels if using px,py. If width 157 * and height are specified, the overlay size is adjusted when zooming, 158 * otherwise the size stays the size of the content (or the size defined by CSS). 159 * * className to associate a class to the overlay 160 * * id to set the overlay element. If an element with this id already exists, 161 * it is reused, otherwise it is created. If not specified, a new element is 162 * created. 163 * * placement a string to define the relative position to the viewport. 164 * Only used if no width and height are specified. Default: 'TOP_LEFT'. 165 * See {@link OpenSeadragon.OverlayPlacement} for possible values. 166 * 167 * @property {String} [xmlPath=null] 168 * <strong>DEPRECATED</strong>. A relative path to load a DZI file from the server. 169 * Prefer the newer Options.tileSources. 170 * 171 * @property {String} [prefixUrl='/images/'] 172 * Prepends the prefixUrl to navImages paths, which is very useful 173 * since the default paths are rarely useful for production 174 * environments. 175 * 176 * @property {OpenSeadragon.NavImages} [navImages] 177 * An object with a property for each button or other built-in navigation 178 * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'. 179 * Each of those in turn provides an image path for each state of the button 180 * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the 181 * image paths, by default assume there is a folder on the servers root path 182 * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust 183 * these paths, prefer setting the option.prefixUrl rather than overriding 184 * every image path directly through this setting. 185 * 186 * @property {Object} [tileHost=null] 187 * TODO: Implement this. Currently not used. 188 * 189 * @property {Boolean} [debugMode=false] 190 * TODO: provide an in-screen panel providing event detail feedback. 191 * 192 * @property {String} [debugGridColor='#437AB2'] 193 * 194 * @property {Number} [blendTime=0] 195 * Specifies the duration of animation as higher or lower level tiles are 196 * replacing the existing tile. 197 * 198 * @property {Boolean} [alwaysBlend=false] 199 * Forces the tile to always blend. By default the tiles skip blending 200 * when the blendTime is surpassed and the current animation frame would 201 * not complete the blend. 202 * 203 * @property {Boolean} [autoHideControls=true] 204 * If the user stops interacting with the viewport, fade the navigation 205 * controls. Useful for presentation since the controls are by default 206 * floated on top of the image the user is viewing. 207 * 208 * @property {Boolean} [immediateRender=false] 209 * Render the best closest level first, ignoring the lowering levels which 210 * provide the effect of very blurry to sharp. It is recommended to change 211 * setting to true for mobile devices. 212 * 213 * @property {Number} [defaultZoomLevel=0] 214 * Zoom level to use when image is first opened or the home button is clicked. 215 * If 0, adjusts to fit viewer. 216 * 217 * @property {Number} [opacity=1] 218 * Opacity of the drawer (1=opaque, 0=transparent) 219 * 220 * @property {Number} [layersAspectRatioEpsilon=0.0001] 221 * Maximum aspectRatio mismatch between 2 layers. 222 * 223 * @property {Number} [degrees=0] 224 * Initial rotation. 225 * 226 * @property {Number} [minZoomLevel=null] 227 * 228 * @property {Number} [maxZoomLevel=null] 229 * 230 * @property {Boolean} [panHorizontal=true] 231 * Allow horizontal pan. 232 * 233 * @property {Boolean} [panVertical=true] 234 * Allow vertical pan. 235 * 236 * @property {Boolean} [constrainDuringPan=false] 237 * 238 * @property {Boolean} [wrapHorizontal=false] 239 * Set to true to force the image to wrap horizontally within the viewport. 240 * Useful for maps or images representing the surface of a sphere or cylinder. 241 * 242 * @property {Boolean} [wrapVertical=false] 243 * Set to true to force the image to wrap vertically within the viewport. 244 * Useful for maps or images representing the surface of a sphere or cylinder. 245 * 246 * @property {Number} [minZoomImageRatio=0.9] 247 * The minimum percentage ( expressed as a number between 0 and 1 ) of 248 * the viewport height or width at which the zoom out will be constrained. 249 * Setting it to 0, for example will allow you to zoom out infinitly. 250 * 251 * @property {Number} [maxZoomPixelRatio=1.1] 252 * The maximum ratio to allow a zoom-in to affect the highest level pixel 253 * ratio. This can be set to Infinity to allow 'infinite' zooming into the 254 * image though it is less effective visually if the HTML5 Canvas is not 255 * availble on the viewing device. 256 * 257 * @property {Boolean} [autoResize=true] 258 * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior. 259 * 260 * @property {Number} [pixelsPerWheelLine=40] 261 * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. 262 * 263 * @property {Number} [visibilityRatio=0.5] 264 * The percentage ( as a number from 0 to 1 ) of the source image which 265 * must be kept within the viewport. If the image is dragged beyond that 266 * limit, it will 'bounce' back until the minimum visibility ration is 267 * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to 268 * true will provide the effect of an infinitely scrolling viewport. 269 * 270 * @property {Number} [imageLoaderLimit=0] 271 * The maximum number of image requests to make concurrently. By default 272 * it is set to 0 allowing the browser to make the maximum number of 273 * image requests in parallel as allowed by the browsers policy. 274 * 275 * @property {Number} [clickTimeThreshold=300] 276 * The number of milliseconds within which a pointer down-up event combination 277 * will be treated as a click gesture. 278 * 279 * @property {Number} [clickDistThreshold=5] 280 * The maximum distance allowed between a pointer down event and a pointer up event 281 * to be treated as a click gesture. 282 * 283 * @property {Number} [dblClickTimeThreshold=300] 284 * The number of milliseconds within which two pointer down-up event combinations 285 * will be treated as a double-click gesture. 286 * 287 * @property {Number} [dblClickDistThreshold=20] 288 * The maximum distance allowed between two pointer click events 289 * to be treated as a double-click gesture. 290 * 291 * @property {Number} [springStiffness=6.5] 292 * 293 * @property {Number} [animationTime=1.2] 294 * Specifies the animation duration per each {@link OpenSeadragon.Spring} 295 * which occur when the image is dragged or zoomed. 296 * 297 * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse] 298 * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings}) 299 * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture 300 * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture 301 * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true 302 * then clickToZoom should be set to false to prevent multiple zooms. 303 * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture 304 * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture 305 * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 306 * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 307 * 308 * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch] 309 * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings}) 310 * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture 311 * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture 312 * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true 313 * then clickToZoom should be set to false to prevent multiple zooms. 314 * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture 315 * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture 316 * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 317 * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 318 * 319 * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen] 320 * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings}) 321 * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture 322 * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture 323 * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true 324 * then clickToZoom should be set to false to prevent multiple zooms. 325 * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture 326 * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture 327 * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 328 * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 329 * 330 * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown] 331 * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings}) 332 * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture 333 * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture 334 * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true 335 * then clickToZoom should be set to false to prevent multiple zooms. 336 * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture 337 * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture 338 * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second) 339 * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture 340 * 341 * @property {Number} [zoomPerClick=2.0]
342 * 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> 343 * 344 * @property {Number} [zoomPerScroll=1.2] 345 * 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> 346 * 347 * @property {Number} [zoomPerSecond=1.0] 348 * The number of seconds to animate a single zoom event over. 349 * 350 * @property {Boolean} [showNavigator=false] 351 * Set to true to make the navigator minimap appear. 352 * 353 * @property {Boolean} [navigatorId=navigator-GENERATED DATE] 354 * The ID of a div to hold the navigator minimap. 355 * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored. 356 * If an ID is not specified, a div element will be generated and placed on top of the main image. 357 * 358 * @property {String} [navigatorPosition='TOP_RIGHT'] 359 * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.<br> 360 * If 'ABSOLUTE' is specified, then navigatorTop|Left|Height|Width determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br> 361 * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigatorHeight|Width values determine the size of the navigator minimap. 362 * 363 * @property {Number} [navigatorSizeRatio=0.2] 364 * Ratio of navigator size to viewer size. Ignored if navigatorHeight|Width are specified. 365 * 366 * @property {Boolean} [navigatorMaintainSizeRatio=false] 367 * If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes. 368 * 369 * @property {Number|String} [navigatorTop=null] 370 * Specifies the location of the navigator minimap (see navigatorPosition). 371 * 372 * @property {Number|String} [navigatorLeft=null] 373 * Specifies the location of the navigator minimap (see navigatorPosition). 374 * 375 * @property {Number|String} [navigatorHeight=null] 376 * Specifies the size of the navigator minimap (see navigatorPosition). 377 * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored. 378 * 379 * @property {Number|String} [navigatorWidth=null] 380 * Specifies the size of the navigator minimap (see navigatorPosition). 381 * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored. 382 * 383 * @property {Boolean} [navigatorAutoResize=true] 384 * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior. 385 * Setting to false can also improve performance when the navigator is configured to a fixed size. 386 * 387 * @property {Number} [controlsFadeDelay=2000] 388 * The number of milliseconds to wait once the user has stopped interacting 389 * with the interface before begining to fade the controls. Assumes 390 * showNavigationControl and autoHideControls are both true. 391 * 392 * @property {Number} [controlsFadeLength=1500] 393 * The number of milliseconds to animate the controls fading out. 394 * 395 * @property {Number} [maxImageCacheCount=200] 396 * The max number of images we should keep in memory (per drawer). 397 * 398 * @property {Number} [timeout=30000] 399 * 400 * @property {Boolean} [useCanvas=true] 401 * Set to false to not use an HTML canvas element for image rendering even if canvas is supported. 402 * 403 * @property {Number} [minPixelRatio=0.5] 404 * The higher the minPixelRatio, the lower the quality of the image that 405 * is considered sufficient to stop rendering a given zoom level. For 406 * example, if you are targeting mobile devices with less bandwith you may 407 * try setting this to 1.5 or higher. 408 * 409 * @property {Boolean} [mouseNavEnabled=true] 410 * Is the user able to interact with the image via mouse or touch. Default 411 * interactions include draging the image in a plane, and zooming in toward 412 * and away from the image. 413 * 414 * @property {Boolean}
414 [showNavigationControl=true] 415 * Set to false to prevent the appearance of the default navigation controls.<br> 416 * Note that if set to false, the customs buttons set by the options 417 * zoomInButton, zoomOutButton etc, are rendered inactive. 418 * 419 * @property {OpenSeadragon.ControlAnchor} [navigationControlAnchor=TOP_LEFT] 420 * Placement of the default navigation controls. 421 * To set the placement of the sequence controls, see the 422 * sequenceControlAnchor option. 423 * 424 * @property {Boolean} [showZoomControl=true] 425 * If true then + and - buttons to zoom in and out are displayed.<br> 426 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 427 * this setting when set to false. 428 * 429 * @property {Boolean} [showHomeControl=true] 430 * If true then the 'Go home' button is displayed to go back to the original 431 * zoom and pan.<br> 432 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 433 * this setting when set to false. 434 * 435 * @property {Boolean} [showFullPageControl=true] 436 * If true then the 'Toggle full page' button is displayed to switch 437 * between full page and normal mode.<br> 438 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 439 * this setting when set to false. 440 * 441 * @property {Boolean} [showRotationControl=false] 442 * If true then the rotate left/right controls will be displayed as part of the 443 * standard controls. This is also subject to the browser support for rotate 444 * (e.g. viewer.drawer.canRotate()).<br> 445 * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding 446 * this setting when set to false. 447 * 448 * @property {Boolean} [showSequenceControl=true] 449 * If the viewer has been configured with a sequence of tile sources, then 450 * provide buttons for navigating forward and backward through the images. 451 * 452 * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT] 453 * Placement of the default sequence controls. 454 * 455 * @property {Boolean} [navPrevNextWrap=false] 456 * If true then the 'previous' button will wrap to the last image when 457 * viewing the first image and the 'next' button will wrap to the first 458 * image when viewing the last image. 459 * 460 * @property {String} zoomInButton 461 * Set the id of the custom 'Zoom in' button to use. 462 * This is useful to have a custom button anywhere in the web page.<br> 463 * To only change the button images, consider using 464 * {@link OpenSeadragon.Options.navImages} 465 * 466 * @property {String} zoomOutButton 467 * Set the id of the custom 'Zoom out' button to use. 468 * This is useful to have a custom button anywhere in the web page.<br> 469 * To only change the button images, consider using 470 * {@link OpenSeadragon.Options.navImages} 471 * 472 * @property {String} homeButton 473 * Set the id of the custom 'Go home' button to use. 474 * This is useful to have a custom button anywhere in the web page.<br> 475 * To only change the button images, consider using 476 * {@link OpenSeadragon.Options.navImages} 477 * 478 * @property {String} fullPageButton 479 * Set the id of the custom 'Toggle full page' button to use. 480 * This is useful to have a custom button anywhere in the web page.<br> 481 * To only change the button images, consider using 482 * {@link OpenSeadragon.Options.navImages} 483 * 484 * @property {String} rotateLeftButton 485 * Set the id of the custom 'Rotate left' button to use. 486 * This is useful to have a custom button anywhere in the web page.<br> 487 * To only change the button images, consider using 488 * {@link OpenSeadragon.Options.navImages} 489 * 490 * @property {String} rotateRightButton 491 * Set the id of the custom 'Rotate right' button to use. 492 * This is useful to have a custom button anywhere in the web page.<br> 493 * To only change the button images, consider using 494 * {@link OpenSeadragon.Options.navImages} 495 * 496 * @property {String} previousButton 497 * Set the id of the custom 'Previous page' button to use. 498 * This is useful to have a custom button anywhere in the web page.<br> 499 * To only change the button images, consider using 500 * {@link OpenSeadragon.Options.navImages} 501 * 502 * @property {String} nextButton 503 * Set the id of the custom 'Next page' button to use. 504 * This is useful to have a custom button anywhere in the web page.<br> 505 * To only change the button images, consider using 506 * {@link OpenSeadragon.Options.navImages} 507 * 508 * @property {Number} [initialPage=0] 509 * If the viewer has been configured with a sequence of tile sources, display this page initially. 510 * 511 * @property {Boolean} [preserveViewport=false] 512 * If the viewer has been configured with a sequence of tile sources, then 513 * normally navigating to through each image resets the viewport to 'home' 514 * position. If preserveViewport is set to true, then the viewport position 515 * is preserved when navigating between images in the sequence. 516 * 517 * @property {Boolean} [showReferenceStrip=false] 518 * If the viewer has been configured with a sequence of tile sources, then 519 * display a scrolling strip of image thumbnails for navigating through the images. 520 * 521 * @property {String} [referenceStripScroll='horizontal'] 522 * 523 * @property {Element} [referenceStripElement=null] 524 * 525 * @property {Number} [referenceStripHeight=null] 526 * 527 * @property {Number} [referenceStripWidth=null] 528 * 529 * @property {String} [referenceStripPosition='BOTTOM_LEFT'] 530 * 531 * @property {Number} [referenceStripSizeRatio=0.2] 532 * 533 * @property {Boolean} [collectionMode=false] 534 * 535 * @property {Number} [collectionRows=3] 536 * 537 * @property {String} [collectionLayout='horizontal'] 538 * 539 * @property {Number}
539 [collectionTileSize=800] 540 * 541 * @property {String|Boolean} [crossOriginPolicy=false] 542 * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will 543 * not use CORS, and the canvas will be tainted. 544 * 545 */ 546 547 /** 548 * Settings for gestures generated by a pointer device. 549 * 550 * @typedef {Object} GestureSettings 551 * @memberof OpenSeadragon 552 * 553 * @property {Boolean} scrollToZoom 554 * Set to false to disable zooming on scroll gestures. 555 * 556 * @property {Boolean} clickToZoom 557 * Set to false to disable zooming on click gestures. 558 * 559 * @property {Boolean} dblClickToZoom 560 * Set to false to disable zooming on double-click gestures. Note: If set to true 561 * then clickToZoom should be set to false to prevent multiple zooms. 562 * 563 * @property {Boolean} pinchToZoom 564 * Set to false to disable zooming on pinch gestures. 565 * 566 * @property {Boolean} flickEnabled 567 * Set to false to disable the kinetic panning effect (flick) at the end of a drag gesture. 568 * 569 * @property {Number} flickMinSpeed 570 * 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. 571 * 572 * @property {Number} flickMomentum 573 * 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. 574 * A larger value will make the flick feel "lighter", while a smaller value will make the flick feel "heavier". 575 * Note: springStiffness and animationTime also affect the "spring" used to stop the flick animation. 576 * 577 */ 578 579/** 580 * The names for the image resources used for the image navigation buttons. 581 * 582 * @typedef {Object} NavImages 583 * @memberof OpenSeadragon 584 * 585 * @property {Object} zoomIn - Images for the zoom-in button. 586 * @property {String} zoomIn.REST 587 * @property {String} zoomIn.GROUP 588 * @property {String} zoomIn.HOVER 589 * @property {String} zoomIn.DOWN 590 * 591 * @property {Object} zoomOut - Images for the zoom-out button. 592 * @property {String} zoomOut.REST 593 * @property {String} zoomOut.GROUP 594 * @property {String} zoomOut.HOVER 595 * @property {String} zoomOut.DOWN 596 * 597 * @property {Object} home - Images for the home button. 598 * @property {String} home.REST 599 * @property {String} home.GROUP 600 * @property {String} home.HOVER 601 * @property {String} home.DOWN 602 * 603 * @property {Object} fullpage - Images for the full-page button. 604 * @property {String} fullpage.REST 605 * @property {String} fullpage.GROUP 606 * @property {String} fullpage.HOVER 607 * @property {String} fullpage.DOWN 608 * 609 * @property {Object} rotateleft - Images for the rotate left button. 610 * @property {String} rotateleft.REST 611 * @property {String} rotateleft.GROUP 612 * @property {String} rotateleft.HOVER 613 * @property {String} rotateleft.DOWN 614 * 615 * @property {Object} rotateright - Images for the rotate right button. 616 * @property {String} rotateright.REST 617 * @property {String} rotateright.GROUP 618 * @property {String} rotateright.HOVER 619 * @property {String} rotateright.DOWN 620 * 621 * @property {Object} previous - Images for the previous button. 622 * @property {String} previous.REST 623 * @property {String} previous.GROUP 624 * @property {String} previous.HOVER 625 * @property {String} previous.DOWN 626 * 627 * @property {Object} next - Images for the next button. 628 * @property {String} next.REST 629 * @property {String} next.GROUP 630 * @property {String} next.HOVER 631 * @property {String} next.DOWN 632 * 633 */ 634 635 636 /** 637 * This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all 638 * combinations of out-of-the-box configurable features. 639 * 640 * @function OpenSeadragon 641 * @memberof module:OpenSeadragon 642 * @param {OpenSeadragon.Options} options - Viewer options. 643 * @returns {OpenSeadragon.Viewer} 644 */ 645window.OpenSeadragon = window.OpenSeadragon || function( options ){ 646 647 return new OpenSeadragon.Viewer( options ); 648 649}; 650 651 652(function( $ ){ 653 654 655 /** 656 * The OpenSeadragon version. 657 * 658 * @member {Object} OpenSeadragon.version 659 * @property {String} versionStr - The version number as a string ('major.minor.revision'). 660 * @property {Number} major - The major version number. 661 * @property {Number} minor - The minor version number. 662 * @property {Number} revision - The revision number. 663 * @since 1.0.0 664 */ 665 $.version = { 666 versionStr: '1.1.1', 667 major: parseInt('1', 10), 668 minor: parseInt('1', 10), 669 revision: parseInt('1', 10) 670 }; 671 672 673 /** 674 * Taken from jquery 1.6.1 675 * [[Class]] -> type pairs 676 * @private 677 */ 678 var class2type = { 679 '[object Boolean]': 'boolean', 680 '[object Number]': 'number', 681 '[object String]': 'string', 682 '[object Function]': 'function', 683 '[object Array]': 'array', 684 '[object Date]': 'date', 685 '[object RegExp]': 'regexp', 686 '[object Object]': 'object' 687 }, 688 // Save a reference to some core methods 689 toString = Object.prototype.toString, 690 hasOwn = Object.prototype.hasOwnProperty; 691 692 /** 693 * Taken from jQuery 1.6.1 694 * @function isFunction 695 * @memberof OpenSeadragon 696 * @see {@link http://www.jquery.com/ jQuery} 697 */ 698 $.isFunction = function( obj ) { 699 return $.type(obj) === "function"; 700 }; 701 702 703 /** 704 * Taken from jQuery 1.6.1 705 * @function isArray 706 * @memberof OpenSeadragon 707 * @see {@link http://www.jquery.com/ jQuery} 708 */ 709 $.isArray = Array.isArray || function( obj ) { 710 return $.type(obj) === "array"; 711 }; 712 713 714 /** 715 * A crude way of determining if an object is a window. 716 * Taken from jQuery 1.6.1 717 * @function isWindow 718 * @memberof OpenSeadragon 719 * @see {@link http://www.jquery.com/ jQuery} 720 */ 721 $.isWindow = function( obj ) { 722 return obj && typeof obj === "object" && "setInterval" in obj; 723 }; 724 725 726 /** 727 * Taken from jQuery 1.6.1 728 * @function type 729 * @memberof OpenSeadragon 730 * @see {@link http://www.jquery.com/ jQuery} 731 */ 732 $.type = function( obj ) { 733 return ( obj === null ) || ( obj === undefined ) ? 734 String( obj ) : 735 class2type[ toString.call(obj) ] || "object"; 736 }; 737 738 739 /** 740 * Taken from jQuery 1.6.1 741 * @function isPlainObject 742 * @memberof OpenSeadragon 743 * @see {@link http://www.jquery.com/ jQuery} 744 */ 745 $.isPlainObject = function( obj ) { 746 // Must be an Object. 747 // Because of IE, we also have to check the presence of the constructor property. 748 // Make sure that DOM nodes and window objects don't pass through, as well 749 if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) { 750 return false; 751 } 752 753 // Not own constructor property must be Object 754 if ( obj.constructor && 755 !hasOwn.call(obj, "constructor") && 756 !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { 757 return false; 758 } 759 760 // Own properties are enumerated firstly, so to speed up, 761 // if last one is own, then all properties are own. 762 763 var key; 764 for ( key in obj ) {} 765 766 return key === undefined || hasOwn.call( obj, key ); 767 }; 768 769 770 /** 771 * Taken from jQuery 1.6.1 772 * @function isEmptyObject 773 * @memberof OpenSeadragon 774 * @see {@link http://www.jquery.com/ jQuery} 775 */ 776 $.isEmptyObject = function( obj ) { 777 for ( var name in obj ) { 778 return false; 779 } 780 return true; 781 }; 782 783 784 /** 785 * True if the browser supports the HTML5 canvas element 786 * @member {Boolean} supportsCanvas 787 * @memberof OpenSeadragon 788 */ 789 $.supportsCanvas = (function () { 790 var canvasElement = document.createElement( 'canvas' ); 791 return !!( $.isFunction( canvasElement.getContext ) && 792 canvasElement.getContext( '2d' ) ); 793 }()); 794 795 796}( OpenSeadragon )); 797 798/** 799 * This closure defines all static methods available to the OpenSeadragon 800 * namespace. Many, if not most, are taked directly from jQuery for use
801 * to simplify and reduce common programming patterns. More static methods 802 * from jQuery may eventually make their way into this though we are 803 * attempting to avoid an explicit dependency on jQuery only because 804 * OpenSeadragon is a broadly useful code base and would be made less broad 805 * by requiring jQuery fully. 806 * 807 * Some static methods have also been refactored from the original OpenSeadragon 808 * project. 809 */ 810(function( $ ){ 811 812 /** 813 * Taken from jQuery 1.6.1 814 * @function extend 815 * @memberof OpenSeadragon 816 * @see {@link http://www.jquery.com/ jQuery} 817 */ 818 $.extend = function() { 819 var options, 820 name, 821 src, 822 copy, 823 copyIsArray, 824 clone, 825 target = arguments[ 0 ] || {}, 826 length = arguments.length, 827 deep = false, 828 i = 1; 829 830 // Handle a deep copy situation 831 if ( typeof target === "boolean" ) { 832 deep = target; 833 target = arguments[ 1 ] || {}; 834 // skip the boolean and the target 835 i = 2; 836 } 837 838 // Handle case when target is a string or something (possible in deep copy) 839 if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) { 840 target = {}; 841 } 842 843 // extend jQuery itself if only one argument is passed 844 if ( length === i ) { 845 target = this; 846 --i; 847 } 848 849 for ( ; i < length; i++ ) { 850 // Only deal with non-null/undefined values 851 options = arguments[ i ]; 852 if ( options !== null || options !== undefined ) { 853 // Extend the base object 854 for ( name in options ) { 855 src = target[ name ]; 856 copy = options[ name ]; 857 858 // Prevent never-ending loop 859 if ( target === copy ) { 860 continue; 861 } 862 863 // Recurse if we're merging plain objects or arrays 864 if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) { 865 if ( copyIsArray ) { 866 copyIsArray = false; 867 clone = src && OpenSeadragon.isArray( src ) ? src : []; 868 869 } else { 870 clone = src && OpenSeadragon.isPlainObject( src ) ? src : {}; 871 } 872 873 // Never move original objects, clone them 874 target[ name ] = OpenSeadragon.extend( deep, clone, copy ); 875 876 // Don't bring in undefined values 877 } else if ( copy !== undefined ) { 878 target[ name ] = copy; 879 } 880 } 881 } 882 } 883 884 // Return the modified object 885 return target; 886 }; 887 888 889 $.extend( $, /** @lends OpenSeadragon */{ 890 /** 891 * The default values for the optional settings documented at {@link OpenSeadragon.Options}. 892 * @static 893 * @type {Object} 894 */ 895 DEFAULT_SETTINGS: { 896 //DATA SOURCE DETAILS 897 xmlPath: null, 898 tileSources: null, 899 tileHost: null, 900 initialPage: 0, 901 crossOriginPolicy: false, 902 903 //PAN AND ZOOM SETTINGS AND CONSTRAINTS 904 panHorizontal: true, 905 panVertical: true, 906 constrainDuringPan: false, 907 wrapHorizontal: false, 908 wrapVertical: false, 909 visibilityRatio: 0.5, //-> how much of the viewer can be negative space 910 minPixelRatio: 0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom 911 defaultZoomLevel: 0, 912 minZoomLevel: null, 913 maxZoomLevel: null, 914 915 //UI RESPONSIVENESS AND FEEL 916 clickTimeThreshold: 300, 917 clickDistThreshold: 5, 918 dblClickTimeThreshold: 300, 919 dblClickDistThreshold: 20, 920 springStiffness: 6.5, 921 animationTime: 1.2,
922 gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 }, 923 gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 }, 924 gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 }, 925 gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 }, 926 zoomPerClick: 2, 927 zoomPerScroll: 1.2, 928 zoomPerSecond: 1.0, 929 blendTime: 0, 930 alwaysBlend: false, 931 autoHideControls: true, 932 immediateRender: false, 933 minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity 934 maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels 935 pixelsPerWheelLine: 40, 936 autoResize: true, 937 938 //DEFAULT CONTROL SETTINGS 939 showSequenceControl: true, //SEQUENCE 940 sequenceControlAnchor: null, //SEQUENCE 941 preserveViewport: false, //SEQUENCE 942 navPrevNextWrap: false, //SEQUENCE 943 showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION 944 navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION 945 showZoomControl: true, //ZOOM 946 showHomeControl: true, //HOME 947 showFullPageControl: true, //FULL 948 showRotationControl: false, //ROTATION 949 controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE 950 controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE 951 mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY 952 953 //VIEWPORT NAVIGATOR SETTINGS 954 showNavigator: false, 955 navigatorId: null, 956 navigatorPosition: null, 957 navigatorSizeRatio: 0.2, 958 navigatorMaintainSizeRatio: false, 959 navigatorTop: null, 960 navigatorLeft: null, 961 navigatorHeight: null, 962 navigatorWidth: null, 963 navigatorAutoResize: true, 964 965 // INITIAL ROTATION 966 degrees: 0, 967 968 // APPEARANCE 969 opacity: 1, 970 971 // LAYERS SETTINGS 972 layersAspectRatioEpsilon: 0.0001, 973 974 //REFERENCE STRIP SETTINGS 975 showReferenceStrip: false, 976 referenceStripScroll: 'horizontal', 977 referenceStripElement: null, 978 referenceStripHeight: null, 979 referenceStripWidth: null, 980 referenceStripPosition: 'BOTTOM_LEFT', 981 referenceStripSizeRatio: 0.2, 982 983 //COLLECTION VISUALIZATION SETTINGS 984 collectionRows: 3, //or columns depending on layout 985 collectionLayout: 'horizontal', //vertical 986 collectionMode: false, 987 collectionTileSize: 800, 988 989 //PERFORMANCE SETTINGS 990 imageLoaderLimit: 0, 991 maxImageCacheCount: 200, 992 timeout: 30000, 993 useCanvas: true, // Use canvas element for drawing if available 994 995 //INTERFACE RESOURCE SETTINGS 996 prefixUrl: "/images/", 997 navImages: { 998 zoomIn: { 999 REST: 'zoomin_rest.png', 1000 GROUP: 'zoomin_grouphover.png', 1001 HOVER: 'zoomin_hover.png',
1002 DOWN: 'zoomin_pressed.png' 1003 }, 1004 zoomOut: { 1005 REST: 'zoomout_rest.png', 1006 GROUP: 'zoomout_grouphover.png', 1007 HOVER: 'zoomout_hover.png', 1008 DOWN: 'zoomout_pressed.png' 1009 }, 1010 home: { 1011 REST: 'home_rest.png', 1012 GROUP: 'home_grouphover.png', 1013 HOVER: 'home_hover.png', 1014 DOWN: 'home_pressed.png' 1015 }, 1016 fullpage: { 1017 REST: 'fullpage_rest.png', 1018 GROUP: 'fullpage_grouphover.png', 1019 HOVER: 'fullpage_hover.png', 1020 DOWN: 'fullpage_pressed.png' 1021 }, 1022 rotateleft: { 1023 REST: 'rotateleft_rest.png', 1024 GROUP: 'rotateleft_grouphover.png', 1025 HOVER: 'rotateleft_hover.png', 1026 DOWN: 'rotateleft_pressed.png' 1027 }, 1028 rotateright: { 1029 REST: 'rotateright_rest.png', 1030 GROUP: 'rotateright_grouphover.png', 1031 HOVER: 'rotateright_hover.png', 1032 DOWN: 'rotateright_pressed.png' 1033 }, 1034 previous: { 1035 REST: 'previous_rest.png', 1036 GROUP: 'previous_grouphover.png', 1037 HOVER: 'previous_hover.png', 1038 DOWN: 'previous_pressed.png' 1039 }, 1040 next: { 1041 REST: 'next_rest.png', 1042 GROUP: 'next_grouphover.png', 1043 HOVER: 'next_hover.png', 1044 DOWN: 'next_pressed.png' 1045 } 1046 }, 1047 1048 //DEVELOPER SETTINGS 1049 debugMode: false, 1050 debugGridColor: '#437AB2' 1051 }, 1052 1053 1054 /** 1055 * TODO: get rid of this. I can't see how it's required at all. Looks 1056 * like an early legacy code artifact. 1057 * @static 1058 * @ignore 1059 */ 1060 SIGNAL: "----seadragon----", 1061 1062 1063 /** 1064 * Returns a function which invokes the method as if it were a method belonging to the object. 1065 * @function 1066 * @param {Object} object 1067 * @param {Function} method 1068 * @returns {Function} 1069 */ 1070 delegate: function( object, method ) { 1071 return function(){ 1072 var args = arguments; 1073 if ( args === undefined ){ 1074 args = []; 1075 } 1076 return method.apply( object, args ); 1077 }; 1078 }, 1079 1080 1081 /** 1082 * An enumeration of Browser vendors. 1083 * @static 1084 * @type {Object} 1085 * @property {Number} UNKNOWN 1086 * @property {Number} IE 1087 * @property {Number} FIREFOX 1088 * @property {Number} SAFARI 1089 * @property {Number} CHROME 1090 * @property {Number} OPERA 1091 */ 1092 BROWSERS: { 1093 UNKNOWN: 0, 1094 IE: 1, 1095 FIREFOX: 2, 1096 SAFARI: 3, 1097 CHROME: 4, 1098 OPERA: 5 1099 }, 1100 1101 1102 /** 1103 * Returns a DOM Element for the given id or element. 1104 * @function 1105 * @param {String|Element} element Accepts an id or element. 1106 * @returns {Element} The element with the given id, null, or the element itself. 1107 */ 1108 getElement: function( element ) { 1109 if ( typeof ( element ) == "string" ) { 1110 element = document.getElementById( element ); 1111 } 1112 return element; 1113 }, 1114 1115 1116 /** 1117 * Determines the position of the upper-left corner of the element. 1118 * @function 1119 * @param {Element|String} element - the elemenet we want the position for. 1120 * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element. 1121 */ 1122 getElementPosition: function( element ) { 1123 var result = new $.Point(), 1124 isFixed, 1125 offsetParent; 1126 1127 element = $.getElement( element ); 1128 isFixed = $.getElementStyle( element ).position == "fixed"; 1129 offsetParent = getOffsetParent( element, isFixed ); 1130 1131 while ( offsetParent ) { 1132 1133 result.x += element.offsetLeft; 1134 result.y += element.offsetTop; 1135 1136 if ( isFixed ) { 1137 result = result.plus( $.getPageScroll() ); 1138 } 1139 1140 element = offsetParent; 1141 isFixed = $.getElementStyle( element ).position == "fixed"; 1142 offsetParent = getOffsetParent( element, isFixed ); 1143 } 1144 1145 return result; 1146 }, 1147 1148 1149 /** 1150 * Determines the position of the upper-left corner of the element adju
1150sted for current page and/or element scroll. 1151 * @function 1152 * @param {Element|String} element - the element we want the position for. 1153 * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll. 1154 */ 1155 getElementOffset: function( element ) { 1156 element = $.getElement( element ); 1157 1158 var doc = element && element.ownerDocument, 1159 docElement, 1160 win, 1161 boundingRect = { top: 0, left: 0 }; 1162 1163 if ( !doc ) { 1164 return new $.Point(); 1165 } 1166 1167 docElement = doc.documentElement; 1168 1169 if ( typeof element.getBoundingClientRect !== typeof undefined ) { 1170 boundingRect = element.getBoundingClientRect(); 1171 } 1172 1173 win = ( doc == doc.window ) ? 1174 doc : 1175 ( doc.nodeType === 9 ) ? 1176 doc.defaultView || doc.parentWindow : 1177 false; 1178 1179 return new $.Point( 1180 boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ), 1181 boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 ) 1182 ); 1183 }, 1184 1185 1186 /** 1187 * Determines the height and width of the given element. 1188 * @function 1189 * @param {Element|String} element 1190 * @returns {OpenSeadragon.Point} 1191 */ 1192 getElementSize: function( element ) { 1193 element = $.getElement( element ); 1194 1195 return new $.Point( 1196 element.clientWidth, 1197 element.clientHeight 1198 ); 1199 }, 1200 1201 1202 /** 1203 * Returns the CSSStyle object for the given element. 1204 * @function 1205 * @param {Element|String} element 1206 * @returns {CSSStyle} 1207 */ 1208 getElementStyle: 1209 document.documentElement.currentStyle ? 1210 function( element ) { 1211 element = $.getElement( element ); 1212 return element.currentStyle; 1213 } : 1214 function( element ) { 1215 element = $.getElement( element ); 1216 return window.getComputedStyle( element, "" ); 1217 }, 1218 1219 1220 /** 1221 * Determines if a point is within the bounding rectangle of the given element (hit-test). 1222 * @function 1223 * @param {Element|String} element 1224 * @param {OpenSeadragon.Point} point 1225 * @returns {Boolean} 1226 */ 1227 pointInElement: function( element, point ) { 1228 element = $.getElement( element ); 1229 var offset = $.getElementOffset( element ), 1230 size = $.getElementSize( element ); 1231 return point.x >= offset.x && point.x < offset.x + size.x && point.y < offset.y + size.y && point.y >= offset.y; 1232 }, 1233 1234 1235 /** 1236 * Gets the latest event, really only useful internally since its 1237 * specific to IE behavior. 1238 * @function 1239 * @param {Event} [event] 1240 * @returns {Event} 1241 * @deprecated For internal use only 1242 * @private 1243 */ 1244 getEvent: function( event ) { 1245 if( event ){ 1246 $.getEvent = function( event ) { 1247 return event; 1248 }; 1249 } else { 1250 $.getEvent = function() { 1251 return window.event; 1252 }; 1253 } 1254 return $.getEvent( event ); 1255 }, 1256 1257 1258 /** 1259 * Gets the position of the mouse on the screen for a given event. 1260 * @function 1261 * @param {Event} [event] 1262 * @returns {OpenSeadragon.Point} 1263 */ 1264 getMousePosition: function( event ) { 1265 1266 if ( typeof( event.pageX ) == "number" ) { 1267 $.getMousePosition = function( event ){ 1268 var result = new $.Point(); 1269 1270 event = $.getEvent( event ); 1271 result.x = event.pageX; 1272 result.y = event.pageY; 1273 1274 return result; 1275 }; 1276 } else if ( typeof( event.clientX ) == "number" ) { 1277 $.getMousePosition = function( event ){ 1278 var result = new $.Point(); 1279 1280 event = $.getEvent( event ); 1281 result.x = 1282 event.clientX + 1283 document.body.scrollLeft + 1284 document.documentElement.scrollLeft; 1285 result.y = 1286 event.clientY + 1287 document.body.scrollTop + 1288 document.documentElement.scrollTop; 1289 1290 return result; 1291 }; 1292 } else { 1293 throw new Error( 1294 "Unknown event mouse position, no known technique." 1295 ); 1296 } 1297 1298 return $.getMousePosition( event ); 1299 }, 1300 1301 1302 /** 1303 * Determines the page's current scroll position. 1304 * @function 1305 * @returns {OpenSeadragon.Point} 1306 */ 1307 getPageScroll: function() { 1308 var docElement = document.documentElement || {}, 1309 body = document.body || {}; 1310 1311 if ( typeof( window.pageXOffset ) == "number" ) { 1312 $.getPageScroll = function(){ 1313 return new $.Point( 1314 window.pageXOffset, 1315 window.pageYOffset 1316 ); 1317 }; 1318 } else if ( body.scrollLeft || body.scrollTop ) { 1319 $.getPageScroll = function(){ 1320 return new $.Point( 1321 document.body.scrollLeft, 1322 document.body.scrollTop 1323 ); 1324 }; 1325 } else if ( docElement.scrollLeft || docElement.scrollTop ) { 1326 $.getPageScroll = function(){ 1327 return new $.Point( 1328 document.documentElement.scrollLeft, 1329 document.documentElement.scrollTop 1330 ); 1331 }; 1332 } else { 1333 // We can't reassign the function yet, as there was no scroll. 1334 return new $.Point(0,0); 1335 } 1336 1337 return $.getPageScroll(); 1338 }, 1339 1340 /** 1341 * Set the page scroll position. 1342 * @function 1343 * @returns {OpenSeadragon.Point} 1344 */ 1345 setPageScroll: function( scroll ) { 1346 if ( typeof ( window.scrollTo ) !== "undefined" ) { 1347 $.setPageScroll = function( scroll ) { 1348 window.scrollTo( scroll.x, scroll.y ); 1349 }; 1350 } else { 1351 var originalScroll = $.getPageScroll(); 1352 if ( originalScroll.x === scroll.x && 1353 originalScroll.y === scroll.y ) { 1354 // We are already correctly positioned and there 1355 // is no way to detect the correct method. 1356 return; 1357 } 1358 1359 document.body.scrollLeft = scroll.x; 1360 document.body.scrollTop = scroll.y; 1361 var currentScroll = $.getPageScroll(); 1362 if ( currentScroll.x !== originalScroll.x && 1363 currentScroll.y !== originalScroll.y ) { 1364 $.setPageScroll = function( scroll ) { 1365 document.body.scrollLeft = scroll.x; 1366 document.body.scrollTop = scroll.y; 1367 }; 1368 return; 1369 } 1370 1371 document.documentElement.scrollLeft = scroll.x; 1372 document.documentElement.scrollTop = scroll.y; 1373 currentScroll = $.getPageScroll(); 1374 if ( currentScroll.x !== originalScroll.x && 1375 currentScroll.y !== originalScroll.y ) { 1376 $.setPageScroll = function( scroll ) { 1377 document.documentElement.scrollLeft = scroll.x; 1378 document.documentElement.scrollTop = scroll.y; 1379 }; 1380 return; 1381 } 1382 1383 // We can't find anything working, so we do nothing. 1384 $.setPageScroll = function( scroll ) { 1385 }; 1386 } 1387 1388 return $.setPageScroll( scroll ); 1389 }, 1390 1391 /** 1392 * Determines the size of the browsers window. 1393 * @function 1394 * @returns {OpenSeadragon.Point} 1395 */ 1396 getWindowSize: function() { 1397 var docElement = document.documentElement || {}, 1398 body = document.body || {}; 1399 1400 if ( typeof( window.innerWidth ) == 'number' ) { 1401 $.getWindowSize = function(){ 1402 return new $.Point( 1403 window.innerWidth, 1404 window.innerHeight 1405 ); 1406 }; 1407 } else if ( docElement.clientWidth || docElement.clientHeight ) { 1408 $.getWindowSize = function(){ 1409 return new $.Point( 1410 document.documentElement.clientWidth, 1411 document.documentElement.clientHeight 1412 ); 1413 }; 1414 } else if ( body.clientWidth || body.clientHeight ) { 1415 $.getWindowSize = function(){ 1416 return new $.Point( 1417 document.body.clientWidth, 1418 document.body.clientHeight 1419 ); 1420 }; 1421 } else { 1422 throw new Error("Unknown window size, no known technique."); 1423 } 1424 1425 return $.getWindowSize(); 1426 }, 1427 1428 1429 /** 1430 * Wraps the given element in a nest of divs so that the element can 1431 * be easily centered using CSS tables 1432 * @function 1433 * @param {Element|String} element 1434 * @returns {Element} outermost wrapper element 1435 */ 1436 makeCenteredNode: function( element ) { 1437 // Convert a possible ID to an actual HTMLElement 1438 element = $.getElement( element ); 1439 1440 /* 1441 CSS tables require you to have a display:table/row/cell hierarchy so we need to create 1442 three nested wrapper divs: 1443 */ 1444 1445 var wrappers = [ 1446 $.makeNeutralElement( 'div' ), 1447 $.makeNeutralElement( 'div' ), 1448 $.makeNeutralElement( 'div' ) 1449 ]; 1450 1451 // It feels like we should be able to pass style dicts to makeNeutralElement: 1452 $.extend(wrappers[0].style, { 1453 display: "table", 1454 height: "100%", 1455 width: "100%" 1456 }); 1457 1458 $.extend(wrappers[1].style, { 1459 display: "table-row" 1460 }); 1461 1462 $.extend(wrappers[2].style, { 1463 display: "table-cell", 1464 verticalAlign: "middle", 1465 textAlign: "center" 1466 }); 1467 1468 wrappers[0].appendChild(wrappers[1]); 1469 wrappers[1].appendChild(wrappers[2]); 1470 wrappers[2].appendChild(element); 1471 1472 return wrappers[0]; 1473 }, 1474 1475 1476 /** 1477 * Creates an easily positionable element of the given type that therefor 1478 * serves as an excellent container element. 1479 * @function 1480 * @param {String} tagName 1481 * @returns {Element} 1482 */ 1483 makeNeutralElement: function( tagName ) { 1484 var element = document.createElement( tagName ), 1485 style = element.style; 1486 1487 style.background = "transparent none"; 1488 style.border = "none"; 1489 style.margin = "0px"; 1490 style.padding = "0px"; 1491 style.position = "static"; 1492 1493 return element; 1494 }, 1495 1496 1497 /** 1498 * Returns the current milliseconds, using Date.now() if available 1499 * @function 1500 */ 1501 now: function( ) { 1502 if (Date.now) { 1503 $.now = Date.now; 1504 } else { 1505 $.now = function() { return new Date().getTime(); }; 1506 } 1507 1508 return $.now(); 1509 }, 1510 1511 1512 /** 1513 * Ensures an image is loaded correctly to support alpha transparency. 1514 * Generally only IE has issues doing this correctly for formats like 1515 * png. 1516 * @function 1517 * @param {String} src 1518 * @returns {Element} 1519 */ 1520 makeTransparentImage: function( src ) { 1521 1522 $.makeTransparentImage = function( src ){ 1523 var img = $.makeNeutralElement( "img" ); 1524 1525 img.src = src; 1526 1527 return img; 1528 }; 1529 1530 if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) { 1531 1532 $.makeTransparentImage = function( src ){ 1533 var img = $.makeNeutralElement( "img" ), 1534 element = null; 1535 1536 element = $.makeNeutralElement("span"); 1537 element.style.display = "inline-block"; 1538 1539 img.onload = function() { 1540 element.style.width = element.style.width || img.width + "px"; 1541 element.style.height = element.style.height || img.height + "px"; 1542 1543 img.onload = null; 1544 img = null; // to prevent memory leaks in IE 1545 }; 1546 1547 img.src = src; 1548 element.style.filter = 1549 "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + 1550 src + 1551 "', sizingMethod='scale')"; 1552 1553 return element; 1554 }; 1555 1556 } 1557 1558 return $.makeTransparentImage( src ); 1559 }, 1560 1561 1562 /** 1563 * Sets the opacity of the specified element. 1564 * @function 1565 * @param {Element|String} element 1566 * @param {Number} opacity 1567 * @param {Boolean} [usesAlpha] 1568 */ 1569 setElementOpacity: function( element, opacity, usesAlpha ) { 1570 1571 var ieOpacity, 1572 ieFilter; 1573 1574 element = $.getElement( element ); 1575 1576 if ( usesAlpha && !$.Browser.alpha ) { 1577 opacity = Math.round( opacity ); 1578 } 1579 1580 if ( $.Browser.opacity ) { 1581 element.style.opacity = opacity < 1 ? opacity : ""; 1582 } else { 1583 if ( opacity < 1 ) { 1584 ieOpacity = Math.round( 100 * opacity ); 1585 ieFilter = "alpha(opacity=" + ieOpacity + ")"; 1586 element.style.filter = ieFilter; 1587 } else { 1588 element.style.filter = ""; 1589 } 1590 } 1591 }, 1592 1593 1594 /** 1595 * Add the specified CSS class to the element if not present. 1596 * @function 1597 * @param {Element|String} element 1598 * @param {String} className 1599 */ 1600 addClass: function( element, className ) { 1601 element = $.getElement( element ); 1602 1603 if ( ! element.className ) { 1604 element.className = className; 1605 } else if ( ( ' ' + element.className + ' ' ). 1606 indexOf( ' ' + className + ' ' ) === -1 ) { 1607 element.className += ' ' + className; 1608 } 1609 }, 1610 1611 /** 1612 * Find the first index at which an element is found in an array or -1 1613 * if not present. 1614 * 1615 * Code taken and adapted from 1616 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility 1617 * 1618 * @function 1619 * @param {Array} array The array from which to find the element 1620 * @param {Object} searchElement The element to
1620find 1621 * @param {Number} [fromIndex=0] Index to start research. 1622 * @returns {Number} The index of the element in the array. 1623 */ 1624 indexOf: function( array, searchElement, fromIndex ) { 1625 if ( Array.prototype.indexOf ) { 1626 this.indexOf = function( array, searchElement, fromIndex ) { 1627 return array.indexOf( searchElement, fromIndex ); 1628 }; 1629 } else { 1630 this.indexOf = function( array, searchElement, fromIndex ) { 1631 var i, 1632 pivot = ( fromIndex ) ? fromIndex : 0, 1633 length; 1634 if ( !array ) { 1635 throw new TypeError( ); 1636 } 1637 1638 length = array.length; 1639 if ( length === 0 || pivot >= length ) { 1640 return -1; 1641 } 1642 1643 if ( pivot < 0 ) { 1644 pivot = length - Math.abs( pivot ); 1645 } 1646 1647 for ( i = pivot; i < length; i++ ) { 1648 if ( array[i] === searchElement ) { 1649 return i; 1650 } 1651 } 1652 return -1; 1653 }; 1654 } 1655 return this.indexOf( array, searchElement, fromIndex ); 1656 }, 1657 1658 /** 1659 * Remove the specified CSS class from the element. 1660 * @function 1661 * @param {Element|String} element 1662 * @param {String} className 1663 */ 1664 removeClass: function( element, className ) { 1665 var oldClasses, 1666 newClasses = [], 1667 i; 1668 1669 element = $.getElement( element ); 1670 oldClasses = element.className.split( /\s+/ ); 1671 for ( i = 0; i < oldClasses.length; i++ ) { 1672 if ( oldClasses[ i ] && oldClasses[ i ] !== className ) { 1673 newClasses.push( oldClasses[ i ] ); 1674 } 1675 } 1676 element.className = newClasses.join(' '); 1677 }, 1678 1679 1680 /** 1681 * Adds an event listener for the given element, eventName and handler. 1682 * @function 1683 * @param {Element|String} element 1684 * @param {String} eventName 1685 * @param {Function} handler 1686 * @param {Boolean} [useCapture] 1687 */ 1688 addEvent: (function () { 1689 if ( window.addEventListener ) { 1690 return function ( element, eventName, handler, useCapture ) { 1691 element = $.getElement( element ); 1692 element.addEventListener( eventName, handler, useCapture ); 1693 }; 1694 } else if ( window.attachEvent ) { 1695 return function ( element, eventName, handler, useCapture ) { 1696 element = $.getElement( element ); 1697 element.attachEvent( 'on' + eventName, handler ); 1698 }; 1699 } else { 1700 throw new Error( "No known event model." ); 1701 } 1702 }()), 1703 1704 1705 /** 1706 * Remove a given event listener for the given element, event type and 1707 * handler. 1708 * @function 1709 * @param {Element|String} element 1710 * @param {String} eventName 1711 * @param {Function} handler 1712 * @param {Boolean} [useCapture] 1713 */ 1714 removeEvent: (function () { 1715 if ( window.removeEventListener ) { 1716 return function ( element, eventName, handler, useCapture ) { 1717 element = $.getElement( element ); 1718 element.removeEventListener( eventName, handler, useCapture ); 1719 }; 1720 } else if ( window.detachEvent ) { 1721 return function( element, eventName, handler, useCapture ) { 1722 element = $.getElement( element ); 1723 element.detachEvent( 'on' + eventName, handler ); 1724 }; 1725 } else { 1726 throw new Error( "No known event model." ); 1727 } 1728 }()), 1729 1730 1731 /** 1732 * Cancels the default browser behavior had the event propagated all 1733 * the way up the DOM to the window object. 1734 * @function 1735 * @param {Event} [event] 1736 */ 1737 cancelEvent: function( event ) { 1738 event = $.getEvent( event ); 1739 1740 if ( event.preventDefault ) { 1741 $.cancelEvent = function( event ){ 1742 // W3C for preventing default 1743 event.preventDefault(); 1744 }; 1745 } else { 1746 $.cancelEvent = function( event ){ 1747 event = $.getEvent( event ); 1748 // legacy for preventing default 1749 event.cancel = true; 1750 // IE for preventing default 1751 event.returnValue = false; 1752 }; 1753 } 1754 $.cancelEvent( event ); 1755 }, 1756 1757 1758 /** 1759 * Stops the propagation of the event up the DOM. 1760 * @function 1761 * @param {Event} [event] 1762 */ 1763 stopEvent: function( event ) { 1764 event = $.getEvent( event ); 1765 1766 if ( event.stopPropagation ) { 1767 // W3C for stopping propagation 1768 $.stopEvent = function( event ){ 1769 event.stopPropagation(); 1770 }; 1771 } else { 1772 // IE for stopping propagation 1773 $.stopEvent = function( event ){ 1774 event = $.getEvent( event ); 1775 event.cancelBubble = true; 1776 }; 1777 1778 } 1779 1780 $.stopEvent( event ); 1781 }, 1782 1783 1784 /** 1785 * Similar to OpenSeadragon.delegate, but it does not immediately call 1786 * the method on the object, returning a function which can be called 1787 * repeatedly to delegate the method. It also allows additonal arguments 1788 * to be passed during construction which will be added during each 1789 * invocation, and each invocation can add additional arguments as well. 1790 * 1791 * @function 1792 * @param {Object} object 1793 * @param {Function} method 1794 * @param [args] any additional arguments are passed as arguments to the 1795 * created callback 1796 * @returns {Function} 1797 */ 1798 createCallback: function( object, method ) { 1799 //TODO: This pattern is painful to use and debug. It's much cleaner 1800 // to use pinning plus anonymous functions. Get rid of this 1801 // pattern! 1802 var initialArgs = [], 1803 i; 1804 for ( i = 2; i < arguments.length; i++ ) { 1805 initialArgs.push( arguments[ i ] ); 1806 } 1807 1808 return function() { 1809 var args = initialArgs.concat( [] ), 1810 i; 1811 for ( i = 0; i < arguments.length; i++ ) { 1812 args.push( arguments[ i ] ); 1813 } 1814 1815 return method.apply( object, args ); 1816 }; 1817 }, 1818 1819 1820 /** 1821 * Retreives the value of a url parameter from the window.location string. 1822 * @function 1823 * @param {String} key 1824 * @returns {String} The value of the url parameter or null if no param matches. 1825 */ 1826 getUrlParameter: function( key ) { 1827 var value = URLPARAMS[ key ]; 1828 return value ? value : null; 1829 }, 1830 1831 /** 1832 * Retrieves the protocol used by the url. The url can either be absolute 1833 * or relative. 1834 * @function 1835 * @private 1836 * @param {String} url The url to retrieve the protocol from. 1837 * @return {String} The protocol (http:, https:, file:, ftp: ...) 1838 */ 1839 getUrlProtocol: function( url ) { 1840 var match = url.match(/^([a-z]+:)\/\//i); 1841 if ( match === null ) { 1842 // Relative URL, retrive the protocol from window.location 1843 return window.location.protocol; 1844 } 1845 return match[1].toLowerCase(); 1846 }, 1847 1848 /** 1849 * Create an XHR object 1850 * @private 1851 * @param {type} [local] If set to true, the XHR will be file: protocol 1852 * compatible if possible (but may raise a warning in the browser). 1853 * @returns {XMLHttpRequest} 1854 */ 1855 createAjaxRequest: function( local ) { 1856 // IE11 does not support window.ActiveXObject so we just try to 1857 // create one to see if it is supported.
1858 // See: http://msdn.microsoft.com/en-us/library/ie/dn423948%28v=vs.85%29.aspx 1859 var supportActiveX; 1860 try { 1861 /* global ActiveXObject:true */ 1862 supportActiveX = !!new ActiveXObject( "Microsoft.XMLHTTP" ); 1863 } catch( e ) { 1864 supportActiveX = false; 1865 } 1866 1867 if ( supportActiveX ) { 1868 if ( window.XMLHttpRequest ) { 1869 $.createAjaxRequest = function( local ) { 1870 if ( local ) { 1871 return new ActiveXObject( "Microsoft.XMLHTTP" ); 1872 } 1873 return new XMLHttpRequest(); 1874 }; 1875 } else { 1876 $.createAjaxRequest = function() { 1877 return new ActiveXObject( "Microsoft.XMLHTTP" ); 1878 }; 1879 } 1880 } else if ( window.XMLHttpRequest ) { 1881 $.createAjaxRequest = function() { 1882 return new XMLHttpRequest(); 1883 }; 1884 } else { 1885 throw new Error( "Browser doesn't support XMLHttpRequest." ); 1886 } 1887 return $.createAjaxRequest( local ); 1888 }, 1889 1890 /** 1891 * Makes an AJAX request. 1892 * @function 1893 * @param {String} url - the url to request 1894 * @param {Function} onSuccess - a function to call on a successful response 1895 * @param {Function} onError - a function to call on when an error occurs 1896 * @throws {Error} 1897 */ 1898 makeAjaxRequest: function( url, onSuccess, onError ) { 1899 var protocol = $.getUrlProtocol( url ); 1900 var request = $.createAjaxRequest( protocol === "file:" ); 1901 1902 if ( !$.isFunction( onSuccess ) ) { 1903 throw new Error( "makeAjaxRequest requires a success callback" ); 1904 } 1905 1906 request.onreadystatechange = function() { 1907 // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties) 1908 if ( request.readyState == 4 ) { 1909 request.onreadystatechange = function(){}; 1910 1911 var successStatus = 1912 protocol === "http:" || protocol === "https:" ? 200 : 0; 1913 if ( request.status === successStatus ) { 1914 onSuccess( request ); 1915 } else { 1916 $.console.log( "AJAX request returned %d: %s", request.status, url ); 1917 1918 if ( $.isFunction( onError ) ) { 1919 onError( request ); 1920 } 1921 } 1922 } 1923 }; 1924 1925 try { 1926 request.open( "GET", url, true ); 1927 request.send( null ); 1928 } catch (e) { 1929 var msg = e.message; 1930 1931 /* 1932 IE < 10 does not support CORS and an XHR request to a different origin will fail as soon 1933 as send() is called. This is particularly easy to miss during development and appear in 1934 production if you use a CDN or domain sharding and the security policy is likely to break 1935 exception handlers since any attempt to access a property of the request object will 1936 raise an access denied TypeError inside the catch block. 1937 1938 To be friendlier, we'll check for this specific error and add a documentation pointer 1939 to point developers in the right direction. We test the exception number because IE's 1940 error messages are localized. 1941 */ 1942 var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10; 1943 if ( oldIE && typeof( e.number ) != "undefined" && e.number == -2147024891 ) { 1944 msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain"; 1945 } 1946 1947 $.console.log( "%s while making AJAX request: %s", e.name, msg ); 1948 1949 request.onreadystatechange = function(){}; 1950 1951 if ( $.isFunction( onError ) ) { 1952 onError( request, e ); 1953 } 1954 } 1955 }, 1956 1957 /** 1958 * Taken from jQuery 1.6.1 1959 * @function 1960 * @param {Object} options 1961 * @param {String} options.url 1962 * @param {Function} options.callback 1963 * @param {String} [options.param='callback'] The name of the url parameter 1964 * to request the jsonp provider with. 1965 * @param {String} [options.callbackName=] The name of the callback to 1966 * request the jsonp provider with. 1967 */ 1968 jsonp: function( options ){ 1969 var script, 1970 url = options.url, 1971 head = document.head || 1972 document.getElementsByTagName( "head" )[ 0 ] || 1973 document.documentElement, 1974 jsonpCallback = options.callbackName || 'openseadragon' + $.now(), 1975 previous = window[ jsonpCallback ], 1976 replace = "$1" + jsonpCallback + "$2", 1977 callbackParam = options.param || 'callback', 1978 callback = options.callback; 1979 1980 url = url.replace( /(\=)\?(&|$)|\?\?/i, replace ); 1981 // Add callback manually 1982 url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback; 1983 1984 // Install callback 1985 window[ jsonpCallback ] = function( response ) { 1986 if ( !previous ){ 1987 try{ 1988 delete window[ jsonpCallback ]; 1989 }catch(e){ 1990 //swallow 1991 } 1992 } else { 1993 window[ jsonpCallback ] = previous; 1994 } 1995 if( callback && $.isFunction( callback ) ){ 1996 callback( response ); 1997 } 1998 }; 1999 2000 script = document.createElement( "script" ); 2001 2002 //TODO: having an issue with async info requests 2003 if( undefined !== options.async || false !== options.async ){ 2004 script.async = "async"; 2005 } 2006 2007 if ( options.scriptCharset ) { 2008 script.charset = options.scriptCharset; 2009 } 2010 2011 script.src = url; 2012 2013 // Attach handlers for all browsers 2014 script.onload = script.onreadystatechange = function( _, isAbort ) { 2015 2016 if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { 2017 2018 // Handle memory leak in IE 2019 script.onload = script.onreadystatechange = null; 2020 2021 // Remove the script 2022 if ( head && script.parentNode ) { 2023 head.removeChild( script ); 2024 } 2025 2026 // Dereference the script 2027 script = undefined; 2028 } 2029 }; 2030 // Use insertBefore instead of appendChild to circumvent an IE6 bug. 2031 // This arises when a base node is used (#2709 and #4378). 2032 head.insertBefore( script, head.firstChild ); 2033 2034 }, 2035 2036 2037 /** 2038 * Fully deprecated. Will throw an error. 2039 * @function 2040 * @deprecated use {@link OpenSeadragon.Viewer#open} 2041 */ 2042 createFromDZI: function() { 2043 throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open."; 2044 }, 2045 2046 /** 2047 * Parses an XML string into a DOM Document. 2048 * @function 2049 * @param {String} string 2050 * @returns {Document} 2051 */ 2052 parseXml: function( string ) { 2053 if ( window.DOMParser ) { 2054 2055 $.parseXml = function( string ) { 2056 var xmlDoc = null, 2057 parser; 2058 2059 parser = new DOMParser(); 2060 xmlDoc = parser.parseFromString( string, "text/xml" ); 2061 return xmlDoc; 2062 }; 2063 2064 } else if ( window.ActiveXObject ) { 2065 2066 $.parseXml = function( string ) { 2067 var xmlDoc = null; 2068 2069 xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" ); 2070 xmlDoc.async = false; 2071 xmlDoc.loadXML( string ); 2072 return xmlDoc; 2073 }; 2074 2075 } else { 2076 throw new Error( "Browser doesn't support XML DOM." ); 2077 } 2078 2079 return $.parseXml( string ); 2080 }, 2081 2082 2083 /** 2084 * Reports whether the image format is supported for tiling in this 2085 * version. 2086 * @function 2087 * @param {String} [extension] 2088 * @returns {Boolean} 2089 */ 2090 imageFormatSupported: function( extension ) { 2091 extension = extension ? extension : ""; 2092 return !!FILEFORMATS[ extension.toLowerCase() ]; 2093 } 2094 2095 });
2096 2097 2098 /** 2099 * The current browser vendor, version, and related information regarding detected features. 2100 * @member {Object} Browser 2101 * @memberof OpenSeadragon 2102 * @static 2103 * @type {Object} 2104 * @property {OpenSeadragon.BROWSERS} vendor - One of the {@link OpenSeadragon.BROWSERS} enumeration values. 2105 * @property {Number} version 2106 * @property {Boolean} alpha - Does the browser support image alpha transparency. 2107 */ 2108 $.Browser = { 2109 vendor: $.BROWSERS.UNKNOWN, 2110 version: 0, 2111 alpha: true 2112 }; 2113 2114 2115 var FILEFORMATS = { 2116 "bmp": false, 2117 "jpeg": true, 2118 "jpg": true, 2119 "png": true, 2120 "tif": false, 2121 "wdp": false 2122 }, 2123 URLPARAMS = {}; 2124 2125 (function() { 2126 //A small auto-executing routine to determine the browser vendor, 2127 //version and supporting feature sets. 2128 var app = navigator.appName, 2129 ver = navigator.appVersion, 2130 ua = navigator.userAgent, 2131 regex; 2132 2133 //console.error( 'appName: ' + navigator.appName ); 2134 //console.error( 'appVersion: ' + navigator.appVersion ); 2135 //console.error( 'userAgent: ' + navigator.userAgent ); 2136 2137 switch( navigator.appName ){ 2138 case "Microsoft Internet Explorer": 2139 if( !!window.attachEvent && 2140 !!window.ActiveXObject ) { 2141 2142 $.Browser.vendor = $.BROWSERS.IE; 2143 $.Browser.version = parseFloat( 2144 ua.substring( 2145 ua.indexOf( "MSIE" ) + 5, 2146 ua.indexOf( ";", ua.indexOf( "MSIE" ) ) ) 2147 ); 2148 } 2149 break; 2150 case "Netscape": 2151 if( !!window.addEventListener ){ 2152 if ( ua.indexOf( "Firefox" ) >= 0 ) { 2153 $.Browser.vendor = $.BROWSERS.FIREFOX; 2154 $.Browser.version = parseFloat( 2155 ua.substring( ua.indexOf( "Firefox" ) + 8 ) 2156 ); 2157 } else if ( ua.indexOf( "Safari" ) >= 0 ) { 2158 $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ? 2159 $.BROWSERS.CHROME : 2160 $.BROWSERS.SAFARI; 2161 $.Browser.version = parseFloat( 2162 ua.substring( 2163 ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1, 2164 ua.indexOf( "Safari" ) 2165 ) 2166 ); 2167 } else { 2168 regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,}) "); 2169 if ( regex.exec( ua ) !== null ) { 2170 $.Browser.vendor = $.BROWSERS.IE; 2171 $.Browser.version = parseFloat( RegExp.$1 ); 2172 } 2173 } 2174 } 2175 break; 2176 case "Opera": 2177 $.Browser.vendor = $.BROWSERS.OPERA; 2178 $.Browser.version = parseFloat( ver ); 2179 break; 2180 } 2181 2182 // ignore '?' portion of query string 2183 var query = window.location.search.substring( 1 ), 2184 parts = query.split('&'), 2185 part, 2186 sep, 2187 i; 2188 2189 for ( i = 0; i < parts.length; i++ ) { 2190 part = parts[ i ]; 2191 sep = part.indexOf( '=' ); 2192 2193 if ( sep > 0 ) { 2194 URLPARAMS[ part.substring( 0, sep ) ] = 2195 decodeURIComponent( part.substring( sep + 1 ) ); 2196 } 2197 } 2198 2199 //determine if this browser supports image alpha transparency 2200 $.Browser.alpha = !( 2201 ( 2202 $.Browser.vendor == $.BROWSERS.IE && 2203 $.Browser.version < 9 2204 ) || ( 2205 $.Browser.vendor == $.BROWSERS.CHROME && 2206 $.Browser.version < 2 2207 ) 2208 ); 2209 2210 //determine if this browser supports element.style.opacity 2211 $.Browser.opacity = !( 2212 $.Browser.vendor == $.BROWSERS.IE && 2213 $.Browser.version < 9 2214 ); 2215 2216 })(); 2217 2218 2219 //TODO: $.console is often used inside a try/catch block which generally 2220 // prevents allowings errors to occur with detection until a debugger 2221 // is attached. Although I've been guilty of the same anti-p
2221attern 2222 // I eventually was convinced that errors should naturally propogate in 2223 // all but the most special cases. 2224 /** 2225 * A convenient alias for console when available, and a simple null 2226 * function when console is unavailable. 2227 * @static 2228 * @private 2229 */ 2230 var nullfunction = function( msg ){ 2231 //document.location.hash = msg; 2232 }; 2233 2234 $.console = window.console || { 2235 log: nullfunction, 2236 debug: nullfunction, 2237 info: nullfunction, 2238 warn: nullfunction, 2239 error: nullfunction 2240 }; 2241 2242 2243 // Adding support for HTML5's requestAnimationFrame as suggested by acdha. 2244 // Implementation taken from matt synder's post here: 2245 // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/ 2246 (function( w ) { 2247 2248 // most browsers have an implementation 2249 var requestAnimationFrame = w.requestAnimationFrame || 2250 w.mozRequestAnimationFrame || 2251 w.webkitRequestAnimationFrame || 2252 w.msRequestAnimationFrame; 2253 2254 var cancelAnimationFrame = w.cancelAnimationFrame || 2255 w.mozCancelAnimationFrame || 2256 w.webkitCancelAnimationFrame || 2257 w.msCancelAnimationFrame; 2258 2259 // polyfill, when necessary 2260 if ( requestAnimationFrame && cancelAnimationFrame ) { 2261 // We can't assign these window methods directly to $ because they 2262 // expect their "this" to be "window", so we call them in wrappers. 2263 $.requestAnimationFrame = function(){ 2264 return requestAnimationFrame.apply( w, arguments ); 2265 }; 2266 $.cancelAnimationFrame = function(){ 2267 return cancelAnimationFrame.apply( w, arguments ); 2268 }; 2269 } else {
2270 var aAnimQueue = [], 2271 processing = [], 2272 iRequestId = 0, 2273 iIntervalId; 2274 2275 // create a mock requestAnimationFrame function 2276 $.requestAnimationFrame = function( callback ) { 2277 aAnimQueue.push( [ ++iRequestId, callback ] ); 2278 2279 if ( !iIntervalId ) { 2280 iIntervalId = setInterval( function() { 2281 if ( aAnimQueue.length ) { 2282 var time = $.now(); 2283 // Process all of the currently outstanding frame 2284 // requests, but none that get added during the 2285 // processing. 2286 // Swap the arrays so we don't have to create a new 2287 // array every frame. 2288 var temp = processing; 2289 processing = aAnimQueue; 2290 aAnimQueue = temp; 2291 while ( processing.length ) { 2292 processing.shift()[ 1 ]( time ); 2293 } 2294 } else { 2295 // don't continue the interval, if unnecessary 2296 clearInterval( iIntervalId ); 2297 iIntervalId = undefined; 2298 } 2299 }, 1000 / 50); // estimating support for 50 frames per second 2300 } 2301 2302 return iRequestId; 2303 }; 2304 2305 // create a mock cancelAnimationFrame function 2306 $.cancelAnimationFrame = function( requestId ) { 2307 // find the request ID and remove it 2308 var i, j; 2309 for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) { 2310 if ( aAnimQueue[ i ][ 0 ] === requestId ) { 2311 aAnimQueue.splice( i, 1 ); 2312 return; 2313 } 2314 } 2315 2316 // If it's not in the queue, it may be in the set we're currently 2317 // processing (if cancelAnimationFrame is called from within a 2318 // requestAnimationFrame callback). 2319 for ( i = 0, j = processing.length; i < j; i += 1 ) { 2320 if ( processing[ i ][ 0 ] === requestId ) { 2321 processing.splice( i, 1 ); 2322 return; 2323 } 2324 } 2325 }; 2326 } 2327 })( window ); 2328 2329 /** 2330 * @private 2331 * @inner 2332 * @function 2333 * @param {Element} element 2334 * @param {Boolean} [isFixed] 2335 * @returns {Element} 2336 */ 2337 function getOffsetParent( element, isFixed ) { 2338 if ( isFixed && element != document.body ) { 2339 return document.body; 2340 } else { 2341 return element.offsetParent; 2342 } 2343 } 2344 2345 /** 2346 * @private 2347 * @inner 2348 * @function 2349 * @param {XMLHttpRequest} xhr 2350 * @param {String} tilesUrl 2351 * @deprecated 2352 */ 2353 function processDZIResponse( xhr, tilesUrl ) { 2354 var status, 2355 statusText, 2356 doc = null; 2357 2358 if ( !xhr ) { 2359 throw new Error( $.getString( "Errors.Security" ) ); 2360 } else if ( xhr.status !== 200 && xhr.status !== 0 ) { 2361 status = xhr.status; 2362 statusText = ( status == 404 ) ? 2363 "Not Found" : 2364 xhr.statusText; 2365 throw new Error( $.getString( "Errors.Status", status, statusText ) ); 2366 } 2367 2368 if ( xhr.responseXML && xhr.responseXML.documentElement ) { 2369 doc = xhr.responseXML; 2370 } else if ( xhr.responseText ) { 2371 doc = $.parseXml( xhr.responseText ); 2372 } 2373 2374 return processDZIXml( doc, tilesUrl ); 2375 } 2376 2377 /** 2378 * @private 2379 * @inner 2380 * @function 2381 * @param {Document} xmlDoc 2382 * @param {String} tilesUrl 2383 * @deprecated 2384 */ 2385 function processDZIXml( xmlDoc, tilesUrl ) { 2386 2387 if ( !xmlDoc || !xmlDoc.documentElement ) { 2388 throw new Error( $.getString( "Errors.Xml" ) ); 2389 } 2390 2391 var root = xmlDoc.documentElement, 2392 rootName = root.tagName; 2393 2394 if ( rootName == "Image" ) { 2395 try { 2396 return processDZI( root, tilesUrl ); 2397 } catch ( e ) { 2398 throw (e instanceof Error) ? 2399 e : 2400 new Error( $.getString("Errors.Dzi") ); 2401 } 2402 } else if ( rootName == "Collection" ) { 2403 throw new Error( $.getString( "Errors.Dzc" ) ); 2404 } else if ( rootName == "Error" ) { 2405 return $._processDZIError( root ); 2406 } 2407 2408 throw new Error( $.getString( "Errors.Dzi" ) ); 2409 } 2410 2411 /** 2412 * @private 2413 * @inner 2414 * @function 2415 * @param {Element} imageNode 2416 * @param {String} tilesUrl 2417 * @deprecated 2418 */ 2419 function processDZI( imageNode, tilesUrl ) { 2420 var fileFormat = imageNode.getAttribute( "Format" ), 2421 sizeNode = imageNode.getElementsByTagName( "Size" )[ 0 ], 2422 dispRectNodes = imageNode.getElementsByTagName( "Dis
2422playRect" ), 2423 width = parseInt( sizeNode.getAttribute( "Width" ), 10 ), 2424 height = parseInt( sizeNode.getAttribute( "Height" ), 10 ), 2425 tileSize = parseInt( imageNode.getAttribute( "TileSize" ), 10 ), 2426 tileOverlap = parseInt( imageNode.getAttribute( "Overlap" ), 10 ), 2427 dispRects = [], 2428 dispRectNode, 2429 rectNode, 2430 i; 2431 2432 if ( !$.imageFormatSupported( fileFormat ) ) { 2433 throw new Error( 2434 $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) 2435 ); 2436 } 2437 2438 for ( i = 0; i < dispRectNodes.length; i++ ) { 2439 dispRectNode = dispRectNodes[ i ]; 2440 rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ]; 2441 2442 dispRects.push( new $.DisplayRect( 2443 parseInt( rectNode.getAttribute( "X" ), 10 ), 2444 parseInt( rectNode.getAttribute( "Y" ), 10 ), 2445 parseInt( rectNode.getAttribute( "Width" ), 10 ), 2446 parseInt( rectNode.getAttribute( "Height" ), 10 ), 2447 0, // ignore MinLevel attribute, bug in Deep Zoom Composer 2448 parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 ) 2449 )); 2450 } 2451 return new $.DziTileSource( 2452 width, 2453 height, 2454 tileSize, 2455 tileOverlap, 2456 tilesUrl, 2457 fileFormat, 2458 dispRects 2459 ); 2460 } 2461 2462 /** 2463 * @private 2464 * @inner 2465 * @function 2466 * @param {Element} imageNode 2467 * @param {String} tilesUrl 2468 * @deprecated 2469 */ 2470 function processDZIJSON( imageData, tilesUrl ) { 2471 var fileFormat = imageData.Format, 2472 sizeData = imageData.Size, 2473 dispRectData = imageData.DisplayRect || [], 2474 width = parseInt( sizeData.Width, 10 ), 2475 height = parseInt( sizeData.Height, 10 ), 2476 tileSize = parseInt( imageData.TileSize, 10 ), 2477 tileOverlap = parseInt( imageData.Overlap, 10 ), 2478 dispRects = [], 2479 rectData, 2480 i; 2481 2482 if ( !$.imageFormatSupported( fileFormat ) ) { 2483 throw new Error( 2484 $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) 2485 ); 2486 } 2487 2488 for ( i = 0; i < dispRectData.length; i++ ) { 2489 rectData = dispRectData[ i ].Rect; 2490 2491 dispRects.push( new $.DisplayRect( 2492 parseInt( rectData.X, 10 ), 2493 parseInt( rectData.Y, 10 ), 2494 parseInt( rectData.Width, 10 ), 2495 parseInt( rectData.Height, 10 ), 2496 0, // ignore MinLevel attribute, bug in Deep Zoom Composer 2497 parseInt( rectData.MaxLevel, 10 ) 2498 )); 2499 } 2500 return new $.DziTileSource( 2501 width, 2502 height, 2503 tileSize, 2504 tileOverlap, 2505 tilesUrl, 2506 fileFormat, 2507 dispRects 2508 ); 2509 } 2510 2511 /** 2512 * @private 2513 * @inner 2514 * @function 2515 * @param {Document} errorNode 2516 * @throws {Error} 2517 * @deprecated 2518 */ 2519 $._processDZIError = function ( errorNode ) { 2520 var messageNode = errorNode.getElementsByTagName( "Message" )[ 0 ], 2521 message = messageNode.firstChild.nodeValue; 2522 2523 throw new Error(message); 2524 }; 2525 2526}( OpenSeadragon )); 2527 2528/* 2529 * OpenSeadragon - full-screen support functions 2530 * 2531 * Copyright (C) 2009 CodePlex Foundation 2532 * Copyright (C) 2010-2013 OpenSeadragon contributors 2533 * 2534 * Redistribution and use in source and binary forms, with or without 2535 * modification, are permitted provided that the following conditions are 2536 * met: 2537 * 2538 * - Redistributions of source code must retain the above copyright notice, 2539 * this list of conditions and the following disclaimer. 2540 * 2541 * - Redistributions in binary form must reproduce the above copyright 2542 * notice, this list of conditions and the following disclaimer in the 2543 * documentation and/or other materials provided with the distribution. 2544 * 2545 * - Neither the name of CodePlex Foundation nor the names of its 2546 * contributors may be used to endorse or promote products derived from 2547 * this software without specific prior written permission. 2548 * 2549 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2550 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2551 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2552 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2553 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2554 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2555 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2556 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2557 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2558 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2559 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2560 */ 2561 2562(function( $ ) { 2563 /** 2564 * Determine native full screen support we can get from the browser. 2565 * @member fullScreenApi 2566 * @memberof OpenSeadragon 2567 * @type {object} 2568 * @property {Boolean} supportsFullScreen Return true if full screen API is supported. 2569 * @property {Function} isFullScreen Return true if currently in full screen mode. 2570 * @property {Function} getFullScreenElement Return the element currently in full screen mode. 2571 * @property {Function} requestFullScreen Make a request to go in full screen mode. 2572 * @property {Function} exitFullScreen Make a request to exit full screen mode. 2573 * @property {Function} cancelFullScreen Deprecated, use exitFullScreen instead. 2574 * @property {String} fullScreenEventName Event fired when the full screen mode change. 2575 * @property {String} fullScreenErrorEventName Event fired when a request to go 2576 * in full screen mode failed. 2577 */ 2578 var fullScreenApi = { 2579 supportsFullScreen: false, 2580 isFullScreen: function() { return false; }, 2581 getFullScreenElement: function() { return null; }, 2582 requestFullScreen: function() {}, 2583 exitFullScreen: function() {}, 2584 cancelFullScreen: function() {}, 2585 fullScreenEventName: '', 2586 fullScreenErrorEventName: '' 2587 }; 2588 2589 // check for native support 2590 if ( document.exitFullscreen ) { 2591 // W3C standard 2592 fullScreenApi.supportsFullScreen = true; 2593 fullScreenApi.getFullScreenElement = function() { 2594 return document.fullscreenElement; 2595 }; 2596 fullScreenApi.requestFullScreen = function( element ) { 2597 return element.requestFullscreen(); 2598 }; 2599 fullScreenApi.exitFullScreen = function() { 2600 document.exitFullscreen(); 2601 }; 2602 fullScreenApi.fullScreenEventName = "fullscreenchange"; 2603 fullScreenApi.fullScreenErrorEventName = "fullscreenerror"; 2604 } else if ( document.msExitFullscreen ) { 2605 // IE 11 2606 fullScreenApi.supportsFullScreen = true; 2607 fullScreenApi.getFullScreenElement = function() { 2608 return document.msFullscreenElement; 2609 }; 2610 fullScreenApi.requestFullScreen = function( element ) { 2611 return element.msRequestFullscreen(); 2612 }; 2613 fullScreenApi.exitFullScreen = function() { 2614 document.msExitFullscreen(); 2615 }; 2616 fullScreenApi.fullScreenEventName = "MSFullscreenChange"; 2617 fullScreenApi.fullScreenErrorEventName = "MSFullscreenError"; 2618 } else if ( document.webkitExitFullscreen ) { 2619 // Recent webkit 2620 fullScreenApi.supportsFullScreen = true; 2621 fullScreenApi.getFullScreenElement = function() { 2622 return document.webkitFullscreenElement; 2623 }; 2624 fullScreenApi.requestFullScreen = function( element ) { 2625 return element.webkitRequestFullscreen(); 2626 }; 2627 fullScreenApi.exitFullScreen = function() { 2628 document.webkitExitFullscreen(); 2629 }; 2630 fullScreenApi.fullScreenEventName = "webkitfullscreenchange"; 2631 fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror"; 2632 } else if ( document.webkitCancelFullScreen ) { 2633 // Old webkit 2634 fullScreenApi.supportsFullScreen = true; 2635 fullScreenApi.getFullScreenElement = function() { 2636 return document.webkitCurrentFullScreenElement; 2637 }; 2638 fullScreenApi.requestFullScreen = function( element ) { 2639 return element.webkitRequestFullScreen(); 2640 }; 2641 fullScreenApi.exitFullScreen = function() { 2642 document.webkitCancelFullScreen(); 2643 }; 2644 fullScreenApi.fullScreenEventName = "webkitfullscreenchange"; 2645 fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror"; 2646 } else if ( document.mozCancelFullScreen ) { 2647 // Firefox 2648 fullScreenApi.supportsFullScreen = true; 2649 fullScreenApi.getFullScreenElement = function() { 2650 return document.mozFullScreenElement; 2651 }; 2652 fullScreenApi.requestFullScreen = function( element ) { 2653 return element.mozRequestFullScreen(); 2654 }; 2655 fullScreenApi.exitFullScreen = function() { 2656 document.mozCancelFullScreen(); 2657 }; 2658 fullScreenApi.fullScreenEventName = "mozfullscreenchange"; 2659 fullScreenApi.fullScreenErrorEventName = "mozfullscreenerror"; 2660 } 2661 fullScreenApi.isFullScreen = function() { 2662 return fullScreenApi.getFullScreenElement() !== null; 2663 }; 2664 fullScreenApi.cancelFullScreen = function() { 2665 $.console.error("cancelFullScreen is deprecated. Use exitFullScreen instead."); 2666 fullScreenApi.exitFullScreen(); 2667 }; 2668 2669 // export api 2670 $.extend( $, fullScreenApi ); 2671 2672})( OpenSeadragon ); 2673 2674/* 2675 * OpenSeadragon - EventSource 2676 * 2677 * Copyright (C) 2009 CodePlex Foundation 2678 * Copyright (C) 2010-2013 OpenSeadragon contributors 2679 * 2680 * Redistribution and use in source and binary forms, with or without 2681 * modification, are permitted provided that the following conditions are 2682 * met: 2683 * 2684 * - Redistributions of source code must retain the above copyright notice, 2685 * this list of conditions and the following disclaimer. 2686 * 2687 * - Redistributions in binary form must reproduce the above copyright 2688 * notice, this list of conditions and the following disclaimer in the 2689 * documentation and/or other materials provided with the distribution. 2690 * 2691 * - Neither the name of CodePlex Foundation nor the names of its 2692 * contributors may be used to endorse or promote products derived from 2693 * this software without specific prior written permission. 2694 * 2695 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2696 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2697 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2698 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2699 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2700 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2701 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2702 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2703 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2704 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2705 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2706 */ 2707 2708(function($){ 2709 2710/** 2711 * Event handler method signature used by all OpenSeadragon events. 2712 * 2713 * @callback EventHandler 2714 * @memberof OpenSeadragon 2715 * @param {Object} event - See individual events for event-specific properties. 2716 */ 2717 2718 2719/** 2720 * @class EventSource 2721 * @classdesc For use by classes which want to support custom, non-browser events. 2722 * 2723 * @memberof OpenSeadragon 2724 */ 2725$.EventSource = function() { 2726 this.events = {}; 2727}; 2728 2729$.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{ 2730 2731 // TODO: Add a method 'one' which automatically unbinds a listener after the first triggered event that matches. 2732 2733 /** 2734 * Add an event handler for a given event. 2735 * @function 2736 * @param {String} eventName - Name of event to register. 2737 * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered. 2738 * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler. 2739 */ 2740 addHandler: function ( eventName, handler, userData ) { 2741 var events = this.events[ eventName ]; 2742 if ( !events ) { 2743 this.events[ eventName ] = events = []; 2744 } 2745 if ( handler && $.isFunction( handler ) ) { 2746 events[ events.length ] = { handler: handler, userData: userData || null }; 2747 } 2748 }, 2749 2750 /** 2751 * Remove a specific event handler for a given event. 2752 * @function 2753 * @param {String} eventName - Name of event for which the handler is to be removed. 2754 * @param {OpenSeadragon.EventHandler} handler - Function to be removed. 2755 */ 2756 removeHandler: function ( eventName, handler ) { 2757 var events = this.events[ eventName ], 2758 handlers = [], 2759 i; 2760 if ( !events ) { 2761 return; 2762 } 2763 if ( $.isArray( events ) ) { 2764 for ( i = 0; i < events.length; i++ ) { 2765 if ( events[i].handler !== handler ) { 2766 handlers.push( events[ i ] ); 2767 } 2768 } 2769 this.events[ eventName ] = handlers; 2770 } 2771 }, 2772 2773 2774 /** 2775 * Remove all event handlers for a given event type. If no type is given all 2776 * event handlers for every event type are removed. 2777 * @function 2778 * @param {String} eventName - Name of event for which all handlers are to be removed. 2779 */ 2780 removeAllHandlers: function( eventName ) { 2781 if ( eventName ){ 2782 this.events[ eventName ] = []; 2783 } else{ 2784 for ( var eventType in this.events ) { 2785 this.events[ eventType ] = []; 2786 } 2787 } 2788 }, 2789 2790 /** 2791 * Get a function which iterates the list of all handlers registered for a given event, calling the handler for each. 2792 * @function 2793 * @param {String} eventName - Name of event to get handlers for. 2794 */ 2795 getHandler: function ( eventName ) { 2796 var events = this.events[ eventName ]; 2797 if ( !events || !events.length ) { 2798 return null; 2799 } 2800 events = events.length === 1 ? 2801 [ events[ 0 ] ] : 2802 Array.apply( null, events ); 2803 return function ( source, args ) { 2804 var i, 2805 length = events.length; 2806 for ( i = 0; i < length; i++ ) { 2807 if ( events[ i ] ) { 2808 args.eventSource = source; 2809 args.userData = events[ i ].userData; 2810 events[ i ].handler( args ); 2811 } 2812 } 2813 }; 2814 }, 2815 2816 /** 2817 * Trigger an event, optionally passing additional information. 2818 * @function 2819 * @param {String} eventName - Name of event to register. 2820 * @param {Object} eventArgs - Event-specific data. 2821 */ 2822 raiseEvent: function( eventName, eventArgs ) { 2823 //uncomment if you want to get a log of all events 2824 //$.console.log( eventName ); 2825 var handler = this.getHandler( eventName ); 2826 2827 if ( handler ) { 2828 if ( !eventArgs ) { 2829 eventArgs = {}; 2830 } 2831 2832 handler( this, eventArgs ); 2833 } 2834 } 2835}; 2836 2837}( OpenSeadragon )); 2838 2839/* 2840 * OpenSeadragon - MouseTracker 2841 * 2842 * Copyright (C) 2009 CodePlex Foundation 2843 * Copyright (C) 2010-2013 OpenSeadragon contributors 2844 * 2845 * Redistribution and use in source and binary forms, with or without 2846 * modification, are permitted provided that the following conditions are 2847 * met: 2848 * 2849 * - Redistributions of source code must retain the above copyright notice, 2850 * this list of conditions and the following disclaimer. 2851 * 2852 * - Redistributions in binary form must reproduce the above copyright 2853 * notice, this list of conditions and the following disclaimer in the 2854 * documentation and/or other materials provided with the distribution. 2855 * 2856 * - Neither the name of CodePlex Foundation nor the names of its 2857 * contributors may be used to endorse or promote products derived from 2858 * this software without specific prior written permission. 2859 * 2860 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2861 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2862 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2863 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2864 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2865 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2866 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 2867 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2868 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2869 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 2870 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2871 */ 2872 2873(function ( $ ) { 2874 2875 // dictionary from hash to private properties 2876 var THIS = {}; 2877 2878 2879 /** 2880 * @class MouseTracker 2881 * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) gestures 2882 * and keyboard events on a specified element. 2883 * @memberof OpenSeadragon 2884 * @param {Object} options 2885 * Allows configurable properties to be entirely specified by passing 2886 * an options object to the constructor. The constructor also supports 2887 * the original positional arguments 'element', 'clickTimeThreshold', 2888 * and 'clickDistThreshold' in that order. 2889 * @param {Element|String} options.element 2890 * A reference to an element or an element id for which the pointer/key 2891 * events will be monitored. 2892 * @param {Number} options.clickTimeThreshold 2893 * The number of milliseconds within which a pointer down-up event combination 2894 * will be treated as a click gesture. 2895 * @param {Number} options.clickDistThreshold 2896 * The maximum distance allowed between a pointer down event and a pointer up event 2897 * to be treated as a click gesture. 2898 * @param {Number} options.dblClickTimeThreshold 2899 * The number of milliseconds within which two pointer down-up event combinations 2900 * will be treated as a double-click gesture. 2901 * @param {Number} options.dblClickDistThreshold 2902 * The maximum distance allowed between two pointer click events 2903 * to be treated as a click gesture. 2904 * @param {Number} [options.stopDelay=50] 2905 * The number of milliseconds without pointer move before the stop 2906 * event is fired. 2907 * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] 2908 * An optional handler for pointer enter. 2909 * @param {OpenSeadragon.EventHandler} [options.exitHandler=null] 2910 * An optional handler for pointer exit. 2911 * @param {OpenSeadragon.EventHandler} [options.pressHandler=null] 2912 * An optional handler for pointer press. 2913 * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null] 2914 * An optional handler for pointer release. 2915 * @param {OpenSeadragon.EventHandler} [options.moveHandler=null] 2916 * An optional handler for pointer move. 2917 * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null] 2918 * An optional handler for mouse wheel scroll. 2919 * @param {OpenSeadragon.EventHandler} [options.clickHandler=null] 2920 * An optional handler for pointer click. 2921 * @param {OpenSeadragon.EventHandler} [options.dblClickHandler=null] 2922 * An optional handler for pointer double-click. 2923 * @param {OpenSeadragon.EventHandler} [options.dragHandler=null] 2924 * An optional handler for the drag gesture. 2925 * @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null] 2926 * An optional handler for after a drag gesture. 2927 * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null] 2928 * An optional handler for the pinch gesture. 2929 * @param {OpenSeadragon.EventHandler} [options.keyHandler=null] 2930 * An optional handler for keypress. 2931 * @param {OpenSeadragon.EventHandler} [options.focusHandler=null] 2932 * An optional handler for focus. 2933 * @param {OpenSeadragon.EventHandler} [options.blurHandler=null] 2934 * An optional handler for blur. 2935 * @param {Object} [options.userData=null] 2936 * Arbitrary object to be passed unchanged to any attached handler methods. 2937 */ 2938 $.MouseTracker = function ( options ) { 2939 2940 var args = arguments; 2941 2942 if ( !$.isPlainObject( options ) ) { 2943 options = { 2944 element: args[ 0 ], 2945 clickTimeThreshold: args[ 1 ], 2946 clickDistThreshold: args[ 2 ] 2947 }; 2948 } 2949 2950 this.hash = Math.random(); // An unique hash for this tracker. 2951 /** 2952 * The element for which pointer events are being monitored. 2953 * @member {Element} element 2954 * @memberof OpenSeadragon.MouseTracker# 2955 */ 2956 this.element = $.getElement( options.element ); 2957 /** 2958 * The number of milliseconds within which a pointer down-up event combination 2959 * will be treated as a click gesture. 2960 * @member {Number} clickTimeThreshold 2961 * @memberof OpenSeadragon.MouseTracker# 2962 */ 2963 this.clickTimeThreshold = options.clickTimeThreshold; 2964 /** 2965 * The maximum distance allowed between a pointer down event and a pointer up event 2966 * to be treated as a click gesture. 2967 * @member {Number} clickDistThreshold 2968 * @memberof OpenSeadragon.MouseTracker# 2969 */ 2970 this.clickDistThreshold = options.clickDistThreshold; 2971 /** 2972 * The number of milliseconds within which two pointer down-up event combinations 2973 * will be treated as a double-click gesture. 2974 * @member {Number} dblClickTimeThreshold 2975 * @memberof OpenSeadragon.MouseTracker# 2976 */ 2977 this.dblClickTimeThreshold = options.dblClickTimeThreshold; 2978 /** 2979 * The maximum distance allowed between two pointer click events 2980 * to be treated as a click gesture. 2981 * @member {Number} clickDistThreshold 2982 * @memberof OpenSeadragon.MouseTracker# 2983 */ 2984 this.dblClickDistThreshold = options.dblClickDistThreshold; 2985 this.userData = options.userData || null; 2986 this.stopDelay = options.stopDelay || 50; 2987 2988 this.enterHandler = options.enterHandler || null; 2989 this.exitHandler = options.exitHandler || null; 2990 this.pressHandler = options.pressHandler || null; 2991 this.releaseHandler = options.releaseHandler || null; 2992 this.moveHandler = options.moveHandler || null; 2993 this.scrollHandler = options.scrollHandler || null; 2994 this.clickHandler = options.clickHandler || null; 2995 this.dblClickHandler = options.dblClickHandler || null; 2996 this.dragHandler = options.dragHandler || null; 2997 this.dragEndHandler = options.dragEndHandler || null; 2998 this.pinchHandler = options.pinchHandler || null; 2999 this.stopHandler = options.stopHandler || null; 3000 this.keyHandler = options.keyHandler || null; 3001 this.focusHandler = options.focusHandler || null; 3002 this.blurHandler = options.blurHandler || null; 3003 3004 //Store private properties in a scope sealed hash map 3005 var _this = this; 3006 3007 /** 3008 * @private 3009 * @property {Boolean} tracking 3010 * Are we currently tracking pointer events for this element. 3011 * @property {Boolean} capturing 3012 * Are we curruently capturing mouse events (legacy mouse events only). 3013 */ 3014 THIS[ this.hash ] = { 3015 click: function ( event ) { onClick( _this, event ); }, 3016 dblclick: function ( event ) { onDblClick( _this, event ); }, 3017 keypress: function ( event ) { onKeyPress( _this, event ); }, 3018 focus: function ( event ) { onFocus( _this, event ); }, 3019 blur: function ( event ) { onBlur( _this, event ); }, 3020 3021 wheel: function ( event ) { onWheel( _this, event ); }, 3022 mousewheel: function ( event ) { onMouseWheel( _this, event ); }, 3023 DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, 3024 MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, 3025 3026 mouseover: function ( event ) { onMouseOver( _this, event ); }, 3027 mouseout: function ( event ) { onMouseOut( _this, event ); }, 3028 mouseenter: function ( event ) { onMouseEnter( _this, event ); }, 3029 mouseleave: function ( event ) { onMouseLeave( _this, event ); }, 3030 mousedown: function ( event ) { onMouseDown( _this, event ); }, 3031 mouseup: function ( event ) { onMouseUp( _this, event ); }, 3032 mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); }, 3033 mousemove: function ( event ) { onMouseMove( _this, event ); }, 3034 mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, 3035 3036 touchenter: function ( event ) { onTouchEnter( _this, event ); }, 3037 touchleave: function ( event ) { onTouchLeave( _this, event ); }, 3038 touchstart: function ( event ) { onTouchStart( _this, event ); }, 3039 touchend: function ( event ) { onTouchEnd( _this, event ); }, 3040 touchmove: function ( event ) { onTouchMove( _this, event ); }, 3041 touchcancel: function ( event ) { onTouchCancel( _this, event ); }, 3042 3043 gesturestart: function ( event ) { onGestureStart( _this, event ); }, 3044 gesturechange: function ( event ) { onGestureChange( _this, event ); }, 3045 3046 pointerenter: function ( event ) { onPointerEnter( _this, event ); }, 3047 MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); }, 3048 pointerleave: function ( event ) { onPointerLeave( _this, event ); }, 3049 MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); }, 3050 pointerdown: function ( event ) { onPointerDown( _this, event ); }, 3051 MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, 3052 pointerup: function ( event ) { onPointerUp( _this, event ); }, 3053 MSPointerUp: function ( event ) { onPointerUp( _this, event ); }, 3054 pointermove: function ( event ) { onPointerMove( _this, event ); }, 3055 MSPointerMove: function ( event ) { onPointerMove( _this, event ); }, 3056 pointercancel: function ( event ) { onPointerCancel( _this, event ); }, 3057 MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); }, 3058 3059 tracking: false, 3060 3061 // Active pointers lists. Array of GesturePointList objects, one for each pointer device type. 3062 // GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()). 3063 // Active pointers are any pointer being tracked for this element which are in the hit-test area 3064 // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. 3065 activePointersLists: [], 3066 3067 // Legacy mouse event tracking 3068 capturing: false, 3069 3070 // Tracking for double-click gesture 3071 lastClickPos: null, 3072 dblClickTimeOut: null, 3073 3074 // Tracking for pinch gesture 3075 pinchGPoints: [], 3076 lastPinchDist: 0, 3077 currentPinchDist: 0, 3078 lastPinchCenter: null, 3079 currentPinchCenter: null 3080 }; 3081 3082 }; 3083 3084 $.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{ 3085 3086 /** 3087 * Clean up any events or objects created by the tracker. 3088 * @function 3089 */ 3090 destroy: function () { 3091 stopTracking( this ); 3092 this.element = null; 3093 3094 THIS[ this.hash ] = null; 3095 delete THIS[ this.hash ]; 3096 }, 3097 3098 /** 3099 * Are we currently tracking events on this element. 3100 * @deprecated Just use this.tracking 3101 * @function 3102 * @returns {Boolean} Are we currently tracking events on this element. 3103 */ 3104 isTracking: function () { 3105 return THIS[ this.hash ].tracking; 3106 }, 3107 3108 /** 3109 * Enable or disable whether or not we are tracking events on this element. 3110 * @function 3111 * @param {Boolean} track True to start tracking, false to stop tracking. 3112 * @returns {OpenSeadragon.MouseTracker} Chainable. 3113 */ 3114 setTracking: function ( track ) { 3115 if ( track ) { 3116 startTracking( this ); 3117 } else { 3118 stopTracking( this ); 3119 } 3120 //chain 3121 return this; 3122 }, 3123 3124 /** 3125 * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type, 3126 * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type. 3127 * @function 3128 * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. 3129 * @returns {OpenSeadragon.MouseTracker.GesturePointList} 3130 */ 3131 getActivePointersListByType: function ( type ) { 3132 var delegate = THIS[ this.hash ], 3133 i, 3134 len = delegate.activePointersLists.length, 3135 list; 3136 3137 for ( i = 0; i < len; i++ ) { 3138 if ( delegate.activePointersLists[ i ].type === type ) { 3139 return delegate.activePointersLists[ i ]; 3140 } 3141 } 3142 3143 list = new $.MouseTracker.GesturePointList( type ); 3144 delegate.activePointersLists.push( list ); 3145 return list; 3146 }, 3147 3148 /** 3149 * Implement or assign implementation to these handlers during or after 3150 * calling the constructor. 3151 * @function 3152 * @param {Object} event 3153 * @param {OpenSeadragon.MouseTracker} event.eventSource 3154 * A reference to the tracker instance. 3155 * @param {String} event.pointerType 3156 * "mouse", "touch", "pen", etc. 3157 * @param {OpenSeadragon.Point} event.position 3158 * The position of the event relative to the tracked element. 3159 * @param {Number} event.buttons 3160 * Current buttons pressed. 3161 * 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. 3162 * @param {Boolean} event.insideElementPressed 3163 * True if the left mouse button is currently being pressed and was 3164 * initiated inside the tracked element, otherwise false. 3165 * @param {Boolean} event.buttonDownAny 3166 * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 3167 * @param {Boolean} event.isTouchEvent 3168 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3169 * @param {Object} event.originalEvent 3170 * The original event object. 3171 * @param {Boolean} event.preventDefaultAction 3172 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3173 * @param {Object} event.userData 3174 * Arbitrary user-defined object. 3175 */ 3176 enterHandler: function () { }, 3177 3178 /** 3179 * Implement or assign implementation to these
3179handlers during or after 3180 * calling the constructor. 3181 * @function 3182 * @param {Object} event 3183 * @param {OpenSeadragon.MouseTracker} event.eventSource 3184 * A reference to the tracker instance. 3185 * @param {String} event.pointerType 3186 * "mouse", "touch", "pen", etc. 3187 * @param {OpenSeadragon.Point} event.position 3188 * The position of the event relative to the tracked element. 3189 * @param {Number} event.buttons 3190 * Current buttons pressed. 3191 * 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. 3192 * @param {Boolean} event.insideElementPressed 3193 * True if the left mouse button is currently being pressed and was 3194 * initiated inside the tracked element, otherwise false. 3195 * @param {Boolean} event.buttonDownAny 3196 * Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 3197 * @param {Boolean} event.isTouchEvent 3198 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3199 * @param {Object} event.originalEvent 3200 * The original event object. 3201 * @param {Boolean} event.preventDefaultAction 3202 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3203 * @param {Object} event.userData 3204 * Arbitrary user-defined object. 3205 */ 3206 exitHandler: function () { }, 3207 3208 /** 3209 * Implement or assign implementation to these handlers during or after 3210 * calling the constructor. 3211 * @function 3212 * @param {Object} event 3213 * @param {OpenSeadragon.MouseTracker} event.eventSource 3214 * A reference to the tracker instance. 3215 * @param {String} event.pointerType 3216 * "mouse", "touch", "pen", etc. 3217 * @param {OpenSeadragon.Point} event.position 3218 * The position of the event relative to the tracked element. 3219 * @param {Number} event.buttons 3220 * Current buttons pressed. 3221 * 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. 3222 * @param {Boolean} event.isTouchEvent 3223 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3224 * @param {Object} event.originalEvent 3225 * The original event object. 3226 * @param {Boolean} event.preventDefaultAction 3227 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3228 * @param {Object} event.userData 3229 * Arbitrary user-defined object. 3230 */ 3231 pressHandler: function () { }, 3232 3233 /** 3234 * Implement or assign implementation to these handlers during or after 3235 * calling the constructor. 3236 * @function 3237 * @param {Object} event 3238 * @param {OpenSeadragon.MouseTracker} event.eventSource 3239 * A reference to the tracker instance. 3240 * @param {String} event.pointerType 3241 * "mouse", "touch", "pen", etc. 3242 * @param {OpenSeadragon.Point} event.position 3243 * The position of the event relative to the tracked element. 3244 * @param {Number} event.buttons 3245 * Current buttons pressed. 3246 * 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. 3247 * @param {Boolean} event.insideElementPressed 3248 * True if the left mouse button is currently being pressed and was 3249 * initiated inside the tracked element, otherwise false. 3250 * @param {Boolean} event.insideElementReleased 3251 * True if the cursor inside the tracked element when the button was released. 3252 * @param {Boolean} event.isTouchEvent 3253 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3254 * @param {Object} event.originalEvent 3255 * The original event object. 3256 * @param {Boolean} event.preventDefaultAction 3257 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3258 * @param {Object} event.userData 3259 * Arbitrary user-defined object. 3260 */ 3261 releaseHandler: function () { }, 3262 3263 /** 3264 * Implement or assign implementation to these handlers during or after 3265 * calling the constructor. 3266 * @function 3267 * @param {Object} event 3268 * @param {OpenSeadragon.MouseTracker} event.eventSource 3269 * A reference to the tracker instance. 3270 * @param {String} event.pointerType 3271 * "mouse", "touch", "pen", etc. 3272 * @param {OpenSeadragon.Point} event.position 3273 * The position of the event relative to the tracked element. 3274 * @param {Number} event.buttons 3275 * Current buttons pressed. 3276 * 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. 3277 * @param {Boolean} event.isTouchEvent 3278 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3279 * @param {Object} event.originalEvent 3280 * The original event object. 3281 * @param {Boolean} event.preventDefaultAction 3282 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3283 * @param {Object} event.userData 3284 * Arbitrary user-defined object. 3285 */ 3286 moveHandler: function () { }, 3287 3288 /** 3289 * Implement or assign implementation to these handlers during or after 3290 * calling the constructor. 3291 * @function 3292 * @param {Object} event 3293 * @param {OpenSeadragon.MouseTracker} event.eventSource 3294 * A reference to the tracker instance. 3295 * @param {String} event.pointerType 3296 * "mouse", "touch", "pen", etc. 3297 * @param {OpenSeadragon.Point} event.position 3298 * The position of the event relative to the tracked element. 3299 * @param {Number} event.scroll 3300 * The scroll delta for the event. 3301 * @param {Boolean} event.shift 3302 * True if the shift key was pressed during this event. 3303 * @param {Boolean} event.isTouchEvent 3304 * 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> 3305 * @param {Object} event.originalEvent 3306 * The original event object. 3307 * @param {Boolean} event.preventDefaultAction 3308 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3309 * @param {Object} event.userData 3310 * Arbitrary user-defined object. 3311 */ 3312 scrollHandler: function () { }, 3313 3314 /** 3315 * Implement or assign implementation to these handlers during or after 3316 * calling the constructor. 3317 * @function 3318 * @param {Object} event 3319 * @param {OpenSeadragon.MouseTracker} event.eventSource 3320 * A reference to the tracker instance. 3321 * @param {String} event.pointerType 3322 * "mouse", "touch", "pen", etc. 3323 * @param {OpenSeadragon.Point} event.position 3324 * The position of the event relative to the tracked element. 3325 * @param {Boolean} event.quick 3326 * True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for ignoring drag events. 3327 * @param {Boolean} event.shift 3328 * True if the shift key was pressed during this event. 3329 * @param {Boolean} event.isTouchEvent 3330 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3331 * @param {Object} event.originalEvent 3332 * The original event object. 3333 * @param {Boolean} event.preventDefaultAction 3334 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3335 * @param {Object} event.userData 3336 * Arbitrary user-defined object. 3337 */ 3338 clickHandler: function () { }, 3339 3340 /** 3341 * Implement or assign implementation to these handlers during or after 3342 * calling the constructor. 3343 * @function 3344 * @param {Object} event 3345 * @param {OpenSeadragon.MouseTracker} event.eventSource 3346 * A reference to the tracker instance. 3347 * @param {String} event.pointerType 3348 * "mouse", "touch", "pen", etc. 3349 * @param {OpenSeadragon.Point} event.position 3350 * The position of the event relative to the tracked element. 3351 * @param {Boolean} event.shift 3352 * True if the shift key was pressed during this event. 3353 * @param {Boolean} event.isTouchEvent 3354 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3355 * @param {Object} event.originalEvent 3356 * The original event object. 3357 * @param {Boolean} event.preventDefaultAction 3358 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3359 * @param {Object} event.userData 3360 * Arbitrary user-defined object. 3361 */ 3362 dblClickHandler: function () { }, 3363 3364 /** 3365 * Implement or assign implementation to these handlers during or after 3366 * calling the constructor. 3367 * @function 3368 * @param {Object} event 3369 * @param {OpenSeadragon.MouseTracker} event.eventSource 3370 * A reference to the tracker instance. 3371 * @param {String} event.pointerType 3372 * "mouse", "touch", "pen", etc. 3373 * @param {OpenSeadragon.Point} event.position 3374 * The position of the event relative to the tracked element. 3375 * @param {Number} event.buttons 3376 * Current buttons pressed. 3377 * 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. 3378 * @param {OpenSeadragon.Point} event.delta 3379 * The x,y components of the difference between the current position and the last drag event position. Useful for ignoring or weighting the events. 3380 * @param {Number} event.speed 3381 * Current computed speed, in pixels per second. 3382 * @param {Number} event.direction 3383 * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 3384 * @param {Boolean} event.shift 3385 * True if the shift key was pressed during this event. 3386 * @param {Boolean} event.isTouchEvent 3387 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3388 * @param {Object} event.originalEvent 3389 * The original event object. 3390 * @param {Boolean} event.preventDefaultAction 3391 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3392 * @param {Object} event.userData 3393 * Arbitrary user-defined object. 3394 */ 3395 dragHandler: function () { }, 3396 3397 /** 3398 * Implement or assign implementation to these handlers during or after 3399 * calling the constructor. 3400 * @function 3401 * @param {Object} event 3402 * @param {OpenSeadragon.MouseTracker} event.eventSource 3403 * A reference to the tracker instance. 3404 * @param {String} event.pointerType 3405 * "mouse", "touch", "pen", etc. 3406 * @param {OpenSeadragon.Point} event.position 3407 * The position of the event relative to the tracked element. 3408 * @param {Number} event.speed 3409 * Speed at the end of a drag gesture, in pixels per second. 3410 * @param {Number} event.direction 3411 * 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. 3412 * @param {Boolean} event.shift 3413 * True if the shift key was pressed during this event. 3414 * @param {Boolean} event.isTouchEvent 3415 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3416 * @param {Object} event.originalEvent 3417 * The original event object. 3418 * @param {Boolean} event.preventDefaultAction 3419 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3420 * @param {Object} event.userData 3421 * Arbitrary user-defined object. 3422 */ 3423 dragEndHandler: function () { }, 3424 3425 /** 3426 * Implement or assign implementation to these handlers during or after 3427 * calling the constructor. 3428 * @function 3429 * @param {Object} event 3430 * @param {OpenSeadragon.MouseTracker} event.eventSource 3431 * A reference to the tracker instance. 3432 * @param {String} event.pointerType 3433 * "mouse", "touch", "pen", etc. 3434 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} event.gesturePoints 3435 * Gesture points associated with the gesture. Velocity data can be found here. 3436 * @param {OpenSeadragon.Point} event.lastCenter 3437 * The previous center point of the two pinch contact points relative to the tracked element. 3438 * @param {OpenSeadragon.Point} event.center 3439 * The center point of the two pinch contact points relative to the tracked element. 3440 * @param {Number} event.lastDistance 3441 * The previous distance between the two pinch contact points in CSS pixels. 3442 * @param {Number} event.distance 3443 * The distance between the two pinch contact points in CSS pixels. 3444 * @param {Boolean} event.shift 3445 * True if the shift key was pressed during this event. 3446 * @param {Object} event.originalEvent 3447 * The original event object. 3448 * @param {Boolean} event.preventDefaultAction 3449 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3450 * @param {Object} event.userData 3451 * Arbitrary user-defined object. 3452 */ 3453 pinchHandler: function () { }, 3454 3455 /** 3456 * Implement or assign implementation to these handlers during or after 3457 * calling the constructor. 3458 * @function 3459 * @param {
3459Object} event 3460 * @param {OpenSeadragon.MouseTracker} event.eventSource 3461 * A reference to the tracker instance. 3462 * @param {String} event.pointerType 3463 * "mouse", "touch", "pen", etc. 3464 * @param {OpenSeadragon.Point} event.position 3465 * The position of the event relative to the tracked element. 3466 * @param {Number} event.buttons 3467 * Current buttons pressed. 3468 * 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. 3469 * @param {Boolean} event.isTouchEvent 3470 * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span> 3471 * @param {Object} event.originalEvent 3472 * The original event object. 3473 * @param {Boolean} event.preventDefaultAction 3474 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3475 * @param {Object} event.userData 3476 * Arbitrary user-defined object. 3477 */ 3478 stopHandler: function () { }, 3479 3480 /** 3481 * Implement or assign implementation to these handlers during or after 3482 * calling the constructor. 3483 * @function 3484 * @param {Object} event 3485 * @param {OpenSeadragon.MouseTracker} event.eventSource 3486 * A reference to the tracker instance. 3487 * @param {Number} event.keyCode 3488 * The key code that was pressed. 3489 * @param {Boolean} event.shift 3490 * True if the shift key was pressed during this event. 3491 * @param {Object} event.originalEvent 3492 * The original event object. 3493 * @param {Boolean} event.preventDefaultAction 3494 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3495 * @param {Object} event.userData 3496 * Arbitrary user-defined object. 3497 */ 3498 keyHandler: function () { }, 3499 3500 /** 3501 * Implement or assign implementation to these handlers during or after 3502 * calling the constructor. 3503 * @function 3504 * @param {Object} event 3505 * @param {OpenSeadragon.MouseTracker} event.eventSource 3506 * A reference to the tracker instance. 3507 * @param {Object} event.originalEvent 3508 * The original event object. 3509 * @param {Boolean} event.preventDefaultAction 3510 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3511 * @param {Object} event.userData 3512 * Arbitrary user-defined object. 3513 */ 3514 focusHandler: function () { }, 3515 3516 /** 3517 * Implement or assign implementation to these handlers during or after 3518 * calling the constructor. 3519 * @function 3520 * @param {Object} event 3521 * @param {OpenSeadragon.MouseTracker} event.eventSource 3522 * A reference to the tracker instance. 3523 * @param {Object} event.originalEvent 3524 * The original event object. 3525 * @param {Boolean} event.preventDefaultAction 3526 * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. 3527 * @param {Object} event.userData 3528 * Arbitrary user-defined object. 3529 */ 3530 blurHandler: function () { } 3531 }; 3532 3533 3534 /** 3535 * Provides continuous computation of velocity (speed and direction) of active pointers. 3536 * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than 3537 * two active gesture pointers at a time. 3538 * 3539 * @private 3540 * @member gesturePointVelocityTracker 3541 * @memberof OpenSeadragon.MouseTracker 3542 */ 3543 $.MouseTracker.gesturePointVelocityTracker = (function () { 3544 var trackerPoints = [], 3545 intervalId = 0, 3546 lastTime = 0; 3547 3548 // Generates a unique identifier for a tracked gesture point 3549 var _generateGuid = function ( tracker, gPoint ) { 3550 return tracker.hash.toString() + gPoint.type + gPoint.id.toString(); 3551 }; 3552 3553 // Interval timer callback. Computes velocity for all tracked gesture points. 3554 var _doTracking = function () { 3555 var i, 3556 len = trackerPoints.length, 3557 trackPoint, 3558 gPoint, 3559 now = $.now(), 3560 elapsedTime, 3561 distance, 3562 speed; 3563 3564 elapsedTime = now - lastTime;
3565 lastTime = now; 3566 3567 for ( i = 0; i < len; i++ ) { 3568 trackPoint = trackerPoints[ i ]; 3569 gPoint = trackPoint.gPoint; 3570 // Math.atan2 gives us just what we need for a velocity vector, as we can simply 3571 // use cos()/sin() to extract the x/y velocity components. 3572 gPoint.direction = Math.atan2( gPoint.currentPos.y - trackPoint.lastPos.y, gPoint.currentPos.x - trackPoint.lastPos.x ); 3573 // speed = distance / elapsed time 3574 distance = trackPoint.lastPos.distanceTo( gPoint.currentPos ); 3575 trackPoint.lastPos = gPoint.currentPos; 3576 speed = 1000 * distance / ( elapsedTime + 1 ); 3577 // Simple biased average, favors the most recent speed computation. Smooths out erratic gestures a bit. 3578 gPoint.speed = 0.75 * speed + 0.25 * gPoint.speed; 3579 } 3580 }; 3581 3582 // Public. Add a gesture point to be tracked 3583 var addPoint = function ( tracker, gPoint ) { 3584 var guid = _generateGuid( tracker, gPoint ); 3585 3586 trackerPoints.push( 3587 { 3588 guid: guid, 3589 gPoint: gPoint, 3590 lastPos: gPoint.currentPos 3591 } ); 3592 3593 // Only fire up the interval timer when there's gesture pointers to track 3594 if ( trackerPoints.length === 1 ) { 3595 lastTime = $.now(); 3596 intervalId = window.setInterval( _doTracking, 50 ); 3597 } 3598 }; 3599 3600 // Public. Stop tracking a gesture point 3601 var removePoint = function ( tracker, gPoint ) { 3602 var guid = _generateGuid( tracker, gPoint ), 3603 i, 3604 len = trackerPoints.length; 3605 for ( i = 0; i < len; i++ ) { 3606 if ( trackerPoints[ i ].guid === guid ) { 3607 trackerPoints.splice( i, 1 ); 3608 // Only run the interval timer if theres gesture pointers to track 3609 len--; 3610 if ( len === 0 ) { 3611 window.clearInterval( intervalId ); 3612 } 3613 break; 3614 } 3615 } 3616 }; 3617 3618 return { 3619 addPoint: addPoint, 3620 removePoint: removePoint 3621 }; 3622 } )(); 3623 3624 3625/////////////////////////////////////////////////////////////////////////////// 3626// Pointer event model and feature detection 3627/////////////////////////////////////////////////////////////////////////////// 3628 3629 /** 3630 * Detect available mouse wheel event name. 3631 */ 3632 $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) || 3633 ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel' 3634 document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel' 3635 'DOMMouseScroll'; // Assume old Firefox 3636 3637 /** 3638 * Detect legacy mouse capture support. 3639 */ 3640 $.MouseTracker.supportsMouseCapture = (function () { 3641 var divElement = document.createElement( 'div' ); 3642 return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); 3643 }()); 3644 3645 /** 3646 * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. 3647 */ 3648 $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; 3649 3650 if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { 3651 // Older Firefox 3652 $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); 3653 } 3654 3655 if ( window.PointerEvent ) { 3656 // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents) 3657 $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointermove", "pointercancel" ); 3658 $.MouseTracker.unprefixedPointerEvents = true; 3659 if( navigator.maxTouchPoints ) { 3660 $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints; 3661 } else { 3662 $.MouseTracker.maxTouchPoints = 0; 3663 } 3664 $.MouseTracker.haveTouchEnter = true; 3665 $.MouseTracker.haveMouseEnter = true; 3666 } else if ( window.MSPointerEvent ) { 3667 // IE10 3668 $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); 3669 $.MouseTracker.unprefixedPointerEvents = false; 3670 if( navigator.msMaxTouchPoints ) { 3671 $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints; 3672 } else { 3673 $.MouseTracker.maxTouchPoints = 0; 3674 } 3675 $.MouseTracker.haveTouchEnter = true; 3676 $.MouseTracker.haveMouseEnter = true; 3677 } else { 3678 // Legacy W3C mouse events 3679 // TODO: Favor mouseenter/mouseleave over mouseover/mouseout when Webkit browser support is better 3680 $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" ); 3681 $.MouseTracker.haveMouseEnter = false; 3682 if ( 'ontouchstart' in window ) { 3683 // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505
3683) 3684 $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); 3685 if ( 'ontouchenter' in window ) { 3686 $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" ); 3687 $.MouseTracker.haveTouchEnter = true; 3688 } else { 3689 $.MouseTracker.haveTouchEnter = false; 3690 } 3691 } else { 3692 $.MouseTracker.haveTouchEnter = false; 3693 } 3694 if ( 'ongesturestart' in window ) { 3695 // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html) 3696 // Subscribe to these to prevent default gesture handling 3697 $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); 3698 } 3699 $.MouseTracker.mousePointerId = "legacy-mouse"; 3700 $.MouseTracker.maxTouchPoints = 10; 3701 } 3702 3703 3704/////////////////////////////////////////////////////////////////////////////// 3705// Classes and typedefs 3706/////////////////////////////////////////////////////////////////////////////// 3707 3708 /** 3709 * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device. 3710 * 3711 * @typedef {Object} GesturePoint 3712 * @memberof OpenSeadragon.MouseTracker 3713 * 3714 * @property {Number} id 3715 * Identifier unique from all other active GesturePoints for a given pointer device. 3716 * @property {String} type 3717 * The pointer device type: "mouse", "touch", "pen", etc. 3718 * @property {Boolean} captured 3719 * True if events for the gesture point are captured to the tracked element. 3720 * @property {Boolean} isPrimary 3721 * 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. 3722 * @property {Boolean} insideElementPressed 3723 * True if button pressed or contact point initiated inside the screen area of the tracked element. 3724 * @property {Boolean} insideElement 3725 * True if pointer or contact point is currently inside the bounds of the tracked element. 3726 * @property {Number} speed 3727 * Current computed speed, in pixels per second. 3728 * @property {Number} direction 3729 * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. 3730 * @property {OpenSeadragon.Point} contactPos 3731 * The initial pointer contact position, relative to the page including any scrolling. Only valid if the pointer has contact (pressed, touch contact, pen contact). 3732 * @property {Number} contactTime 3733 * The initial pointer contact time, in milliseconds. Only valid if the pointer has contact (pressed, touch contact, pen contact). 3734 * @property {OpenSeadragon.Point} lastPos 3735 * The last pointer position, relative to the page including any scrolling. 3736 * @property {Number} lastTime 3737 * The last pointer contact time, in milliseconds. 3738 * @property {OpenSeadragon.Point} currentPos 3739 * The current pointer position, relative to the page including any scrolling. 3740 * @property {Number} currentTime 3741 * The current pointer contact time, in milliseconds. 3742 */ 3743 3744 3745 /** 3746 * @class GesturePointList 3747 * @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type. 3748 * Active pointers are any pointer being tracked for this element which are in the hit-test area 3749 * of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. 3750 * @memberof OpenSeadragon.MouseTracker 3751 * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc. 3752 */ 3753 $.MouseTracker.GesturePointList = function ( type ) { 3754 this._gPoints = []; 3755 /** 3756 * The pointer device type: "mouse", "touch", "pen", etc. 3757 * @member {String} type 3758 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 3759 */ 3760 this.type = type; 3761 /** 3762 * Current buttons pressed for the device. 3763 * 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. 3764 * @member {Number} buttons 3765 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 3766 */ 3767 this.buttons = 0; 3768 /** 3769 * Current number of contact points (touch points, mouse down, etc.) for the device. 3770 * @member {Number} contacts 3771 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 3772 */ 3773 this.contacts = 0; 3774 /** 3775 * Current number of clicks for the device. Used for multiple click gesture tracking. 3776 * @member {Number} clicks 3777 * @memberof OpenSeadragon.MouseTracker.GesturePointList# 3778 */ 3779 this.clicks = 0; 3780 }; 3781 $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{ 3782 /** 3783 * @function 3784 * @returns {Number} Number of gesture points in the list. 3785 */ 3786 getLength: function () { 3787 return this._gPoints.length; 3788 }, 3789 /** 3790 * @function 3791 * @returns {Array.<OpenSeadragon.MouseTracker.GesturePoint>} The list of gesture points in the list as an array (read-only). 3792 */ 3793 asArray: function () { 3794 return this._gPoints; 3795 }, 3796 /** 3797 * @function 3798 * @param {OpenSeadragon.MouseTracker.GesturePoint} gesturePoint - A gesture point to add to the list. 3799 * @returns {Number} Number of gesture points in the list. 3800 */ 3801 add: function ( gp ) { 3802 return this._gPoints.push( gp ); 3803 }, 3804 /** 3805 * @function 3806 * @param {Number} id - The id of the gesture point to remove from the list. 3807 * @returns {Number} Number of gesture points in the list. 3808 */ 3809 removeById: function ( id ) { 3810 var i, 3811 len = this._gPoints.length; 3812 for ( i = 0; i < len; i++ ) { 3813 if ( this._gPoints[ i ].id === id ) { 3814 this._gPoints.splice( i, 1 ); 3815 break; 3816 } 3817 } 3818 return this._gPoints.length; 3819 }, 3820 /** 3821 * @function 3822 * @param {Number} index - The index of the gesture point to retrieve from the list. 3823 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point at the given index, or null if not found. 3824 */ 3825 getByIndex: function ( index ) { 3826 if ( index < this._gPoints.length) { 3827 return this._gPoints[ index ]; 3828 } 3829 3830 return null; 3831 }, 3832 /** 3833 * @function 3834 * @param {Number} id - The id of the gesture point to retrieve from the list. 3835 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point with the given id, or null if not found. 3836 */ 3837 getById: function ( id ) { 3838 var i, 3839 len = this._gPoints.length; 3840 for ( i = 0; i < len; i++ ) { 3841 if ( this._gPoints[ i ].id === id ) { 3842 return this._gPoints[ i ]; 3843 } 3844 } 3845 return null; 3846 }, 3847 /** 3848 * @function 3849 * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The primary gesture point in the list, or null if not found. 3850 */ 3851 getPrimary: function ( id ) { 3852 var i, 3853 len = this._gPoints.length; 3854 for ( i = 0; i < len; i++ ) { 3855 if ( this._gPoints[ i ].isPrimary ) { 3856 return this._gPoints[ i ]; 3857 } 3858 } 3859 return null; 3860 } 3861 }; 3862 3863
3864/////////////////////////////////////////////////////////////////////////////// 3865// Utility functions 3866/////////////////////////////////////////////////////////////////////////////// 3867 3868 /** 3869 * Starts tracking pointer events on the tracked element. 3870 * @private 3871 * @inner 3872 */ 3873 function startTracking( tracker ) { 3874 var delegate = THIS[ tracker.hash ], 3875 event, 3876 i; 3877 3878 if ( !delegate.tracking ) { 3879 for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { 3880 event = $.MouseTracker.subscribeEvents[ i ]; 3881 $.addEvent( 3882 tracker.element, 3883 event, 3884 delegate[ event ], 3885 false 3886 ); 3887 } 3888 delegate.tracking = true; 3889 } 3890 } 3891 3892 /** 3893 * Stops tracking pointer events on the tracked element. 3894 * @private 3895 * @inner 3896 */ 3897 function stopTracking( tracker ) { 3898 var delegate = THIS[ tracker.hash ], 3899 event, 3900 i; 3901 3902 if ( delegate.tracking ) { 3903 for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { 3904 event = $.MouseTracker.subscribeEvents[ i ]; 3905 $.removeEvent( 3906 tracker.element, 3907 event, 3908 delegate[ event ], 3909 false 3910 ); 3911 } 3912 3913 releaseMouse( tracker ); 3914 delegate.tracking = false; 3915 } 3916 } 3917 3918 /** 3919 * Begin capturing mouse events to the tracked element (legacy mouse events only). 3920 * @private 3921 * @inner 3922 */ 3923 function captureMouse( tracker ) { 3924 var delegate = THIS[ tracker.hash ]; 3925 3926 if ( !delegate.capturing ) { 3927 if ( $.MouseTracker.supportsMouseCapture ) { 3928 // IE<10, Firefox, other browsers with setCapture()/releaseCapture() 3929 tracker.element.setCapture( true ); 3930 } else { 3931 // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object. 3932 // (Note we listen on the capture phase so the captured handlers will get called first) 3933 $.addEvent( 3934 window, 3935 "mouseup", 3936 delegate.mouseupcaptured, 3937 true 3938 ); 3939 $.addEvent( 3940 window, 3941 "mousemove", 3942 delegate.mousemovecaptured, 3943 true 3944 ); 3945 } 3946 delegate.capturing = true; 3947 } 3948 } 3949 3950 3951 /** 3952 * Stop capturing mouse events to the tracked element (legacy mouse events only). 3953 * @private 3954 * @inner 3955 */ 3956 function releaseMouse( tracker ) { 3957 var delegate = THIS[ tracker.hash ]; 3958 3959 if ( delegate.capturing ) { 3960 if ( $.MouseTracker.supportsMouseCapture ) { 3961 // IE<10, Firefox, other browsers with setCapture()/releaseCapture() 3962 tracker.element.releaseCapture(); 3963 } else { 3964 // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object. 3965 // (Note we listen on the capture phase so the captured handlers will get called first) 3966 $.removeEvent( 3967 window, 3968 "mousemove", 3969 delegate.mousemovecaptured, 3970 true 3971 ); 3972 $.removeEvent( 3973 window, 3974 "mouseup", 3975 delegate.mouseupcaptured, 3976 true 3977 ); 3978 } 3979 delegate.capturing = false; 3980 } 3981 } 3982 3983 3984 /** 3985 * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event. 3986 * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc. 3987 * @private 3988 * @inner 3989 */ 3990 function getPointerType( event ) { 3991 var pointerTypeStr; 3992 if ( $.MouseTracker.unprefixedPointerEvents ) { 3993 pointerTypeStr = event.pointerType; 3994 } else { 3995 // IE10 3996 // MSPOINTER_TYPE_TOUCH: 0x00000002 3997 // MSPOINTER_TYPE_PEN: 0x00000003 3998 // MSPOINTER_TYPE_MOUSE: 0x00000004 3999 switch( event.pointerType ) 4000 { 4001 case 0x00000002: 4002 pointerTypeStr = 'touch'; 4003 break; 4004 case 0x00000003: 4005 pointerTypeStr = 'pen'; 4006 break; 4007 case 0x00000004: 4008 pointerTypeStr = 'mouse'; 4009 break; 4010 default: 4011 pointerTypeStr = ''; 4012 } 4013 } 4014 return pointerTypeStr; 4015 } 4016 4017 4018 /** 4019 * @private 4020 * @inner 4021 */ 4022 function getMouseAbsolute( event ) { 4023 return $.getMousePosition( event ); 4024 } 4025 4026 /** 4027 * @private 4028 * @inner 4029 */ 4030 function getMouseRelative( event, element ) { 4031 return getPointRelativeToAbsolute( getMouseAbsolute( event ), element ); 4032 } 4033 4034 /** 4035 * @private 4036 * @inner 4037 */ 4038 function getPointRelativeToAbsolute( point, element ) { 4039 var offset = $.getElementOffset( element ); 4040 return point.minus( offset ); 4041 } 4042 4043 /** 4044 * @private 4045 * @inner 4046 */ 4047 function getCenterPoint( point1, point2 ) { 4048 return new $.Point( ( point1.x + point2.x ) / 2, ( point1.y + point2.y ) / 2 ); 4049 } 4050 4051 4052/////////////////////////////////////////////////////////////////////////////// 4053// Device-specific DOM event handlers
4054/////////////////////////////////////////////////////////////////////////////// 4055 4056 /** 4057 * @private 4058 * @inner 4059 */ 4060 function onClick( tracker, event ) { 4061 if ( tracker.clickHandler ) { 4062 $.cancelEvent( event ); 4063 } 4064 } 4065 4066 4067 /** 4068 * @private 4069 * @inner 4070 */ 4071 function onDblClick( tracker, event ) { 4072 if ( tracker.dblClickHandler ) { 4073 $.cancelEvent( event ); 4074 } 4075 } 4076 4077 4078 /** 4079 * @private 4080 * @inner 4081 */ 4082 function onKeyPress( tracker, event ) { 4083 //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); 4084 var propagate; 4085 if ( tracker.keyHandler ) { 4086 event = $.getEvent( event ); 4087 propagate = tracker.keyHandler( 4088 { 4089 eventSource: tracker, 4090 position: getMouseRelative( event, tracker.element ), 4091 keyCode: event.keyCode ? event.keyCode : event.charCode, 4092 shift: event.shiftKey, 4093 originalEvent: event, 4094 preventDefaultAction: false, 4095 userData: tracker.userData 4096 } 4097 ); 4098 if ( !propagate ) { 4099 $.cancelEvent( event ); 4100 } 4101 } 4102 } 4103 4104 4105 /** 4106 * @private 4107 * @inner 4108 */ 4109 function onFocus( tracker, event ) { 4110 //console.log( "focus %s", event ); 4111 var propagate; 4112 if ( tracker.focusHandler ) { 4113 event = $.getEvent( event ); 4114 propagate = tracker.focusHandler( 4115 { 4116 eventSource: tracker, 4117 originalEvent: event, 4118 preventDefaultAction: false, 4119 userData: tracker.userData 4120 } 4121 ); 4122 if ( propagate === false ) { 4123 $.cancelEvent( event ); 4124 } 4125 } 4126 } 4127 4128 4129 /** 4130 * @private 4131 * @inner 4132 */ 4133 function onBlur( tracker, event ) { 4134 //console.log( "blur %s", event ); 4135 var propagate; 4136 if ( tracker.blurHandler ) { 4137 event = $.getEvent( event ); 4138 propagate = tracker.blurHandler( 4139 { 4140 eventSource: tracker, 4141 originalEvent: event, 4142 preventDefaultAction: false, 4143 userData: tracker.userData 4144 } 4145 ); 4146 if ( propagate === false ) { 4147 $.cancelEvent( event ); 4148 } 4149 } 4150 } 4151 4152 4153 /** 4154 * Handler for 'wheel' events 4155 * 4156 * @private 4157 * @inner 4158 */ 4159 function onWheel( tracker, event ) { 4160 handleWheelEvent( tracker, event, event ); 4161 } 4162 4163 4164 /** 4165 * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events 4166 * 4167 * @private 4168 * @inner 4169 */ 4170 function onMouseWheel( tracker, event ) { 4171 event = $.getEvent( event ); 4172 4173 // Simulate a 'wheel' event 4174 var simulatedEvent = { 4175 target: event.target || event.srcElement, 4176 type: "wheel", 4177 shiftKey: event.shiftKey || false, 4178 clientX: event.clientX, 4179 clientY: event.clientY, 4180 pageX: event.pageX ? event.pageX : event.clientX, 4181 pageY: event.pageY ? event.pageY : event.clientY, 4182 deltaMode: event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page 4183 deltaX: 0, 4184 deltaZ: 0 4185 }; 4186 4187 // Calculate deltaY 4188 if ( $.MouseTracker.wheelEventName == "mousewheel" ) { 4189 simulatedEvent.deltaY = - 1 / $.DEFAULT_SETTINGS.pixelsPerWheelLine * event.wheelDelta; 4190 } else { 4191 simulatedEvent.deltaY = event.detail; 4192 } 4193 4194 handleWheelEvent( tracker, simulatedEvent, event ); 4195 } 4196 4197 4198 /** 4199 * Handles 'wheel' events. 4200 * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()). 4201 * 4202 * @private 4203 * @inner 4204 */ 4205 function handleWheelEvent( tracker, event, originalEvent ) { 4206 var nDelta = 0, 4207 propagate; 4208 4209 // The nDelta variable is gated to provide smooth z-index scrolling 4210 // since the mouse wheel allows for substantial deltas meant for rapid 4211 // y-index scrolling. 4212 // event.deltaMode: 0=pixel, 1=line, 2=page 4213 // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached 4214 nDelta = event.deltaY < 0 ? 1 : -1; 4215 4216 if ( tracker.scrollHandler ) { 4217 propagate = tracker.scrollHandler( 4218 { 4219 eventSource: tracker, 4220 pointerType: 'mouse', 4221 position: getMouseRelative( event, tracker.element ), 4222 scroll: nDelta, 4223 shift: event.shiftKey, 4224 isTouchEvent: false, 4225 originalEvent: originalEvent, 4226 preventDefaultAction: false, 4227 userData: tracker.userData 4228 } 4229 ); 4230 if ( propagate === false ) { 4231 $.cancelEvent( originalEvent ); 4232 } 4233 } 4234 } 4235 4236 4237 /** 4238 * @private 4239 * @inner 4240 */ 4241 function isParentChild( parent, child ) 4242 { 4243 if ( parent === child ) { 4244 return false; 4245 } 4246 while ( child && child !== parent ) { 4247 child = child.parentNode; 4248 } 4249 return child === parent; 4250 } 4251 4252 4253 /** 4254 * @private 4255 * @inner 4256 */ 4257 function onMouseOver( tracker, event ) { 4258 var gPoint; 4259 4260 event = $.getEvent( event ); 4261 4262 if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) { 4263 return; 4264 } 4265 4266 gPoint = { 4267 id: $.MouseTracker.mousePointerId, 4268 type: 'mouse', 4269 isPrimary: true, 4270 currentPos: getMouseAbsolute( event ), 4271 currentTime: $.now() 4272 }; 4273 4274 updatePointersEnter( tracker, event, [ gPoint ] ); 4275 } 4276 4277 4278 /** 4279 * @private 4280 * @inner 4281 */ 4282 function onMouseOut( tracker, event ) { 4283 var gPoint; 4284 4285 event = $.getEvent( event ); 4286 4287 if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) { 4288 return; 4289 } 4290 4291 gPoint = { 4292 id: $.MouseTracker.mousePointerId, 4293 type: 'mouse', 4294 isPrimary: true, 4295 currentPos: getMouseAbsolute( event ), 4296 currentTime: $.now() 4297 }; 4298 4299 updatePointersExit( tracker, event, [ gPoint ] ); 4300 } 4301 4302 4303 /** 4304 * @private 4305 * @inner 4306 */ 4307 function onMouseEnter( tracker, event ) { 4308 var gPoint; 4309 4310 event = $.getEvent( event ); 4311 4312 gPoint = { 4313 id: $.MouseTracker.mousePointerId, 4314 type: 'mouse', 4315 isPrimary: true, 4316 currentPos: getMouseAbsolute( event ), 4317 currentTime: $.now() 4318 }; 4319 4320 updatePointersEnter( tracker, event, [ gPoint ] ); 4321 } 4322 4323 4324 /** 4325 * @private 4326 * @inner 4327 */ 4328 function onMouseLeave( tracker, event ) { 4329 var gPoint; 4330 4331 event = $.getEvent( event ); 4332 4333 gPoint = { 4334 id: $.MouseTracker.mousePointerId, 4335 type: 'mouse', 4336 isPrimary: true, 4337 currentPos: getMouseAbsolute( event ), 4338 currentTime: $.now() 4339 }; 4340 4341 updatePointersExit( tracker, event, [ gPoint ] ); 4342 } 4343 4344 4345 /** 4346 * @private 4347 * @inner 4348 */ 4349 function onMouseDown( tracker, event ) { 4350 var gPoint; 4351 4352 event = $.getEvent( event ); 4353 4354 gPoint = { 4355 id: $.MouseTracker.mousePointerId, 4356 type: 'mouse', 4357 isPrimary: true, 4358 currentPos: getMouseAbsolute( event ), 4359 currentTime: $.now() 4360 }; 4361 4362 if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { 4363 $.stopEvent( event ); 4364 captureMouse( tracker ); 4365 } 4366 4367 if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { 4368 $.cancelEvent( event ); 4369 } 4370 } 4371 4372 4373 /** 4374 * @private 4375 * @inner 4376 */ 4377 function onMouseUp( tracker, event ) { 4378 handleMouseUp( tracker, event ); 4379 } 4380 4381 /** 4382 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 4383 * Only triggered in W3C browsers that don't have setCapture/releaseCapture 4384 * methods or don't support the new pointer events model. 4385 * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice. 4386 * 4387 * @private 4388 * @inner 4389 */ 4390 function onMouseUpCaptured( tracker, event ) { 4391 handleMouseUp( tracker, event ); 4392 $.stopEvent( event ); 4393 } 4394 4395 4396 /** 4397 * @private 4398 * @inner 4399 */ 4400 function handleMouseUp( tracker, event ) { 4401 var gPoint; 4402 4403 event = $.getEvent( event ); 4404 4405 gPoint = { 4406 id: $.MouseTracker.mousePointerId, 4407 type: 'mouse', 4408 isPrimary: true, 4409 currentPos: getMouseAbsolute( event ), 4410 currentTime: $.now() 4411 }; 4412 4413 if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { 4414 releaseMouse( tracker ); 4415 } 4416 } 4417 4418 4419 /** 4420 * @private 4421 * @inner 4422 */ 4423 function onMouseMove( tracker, event ) { 4424 handleMouseMove( tracker, event ); 4425 } 4426 4427 4428 /** 4429 * This handler is attached to the window object (on the capture phase) to emulate mouse capture. 4430 * Only triggered in W3C browsers that don't have setCapture/releaseCapture 4431 * methods or don't support the new pointer events model. 4432 * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice. 4433 * 4434 * @private 4435 * @inner 4436 */ 4437 function onMouseMoveCaptured( tracker, event ) { 4438 handleMouseMove( tracker, event ); 4439 $.stopEvent( event ); 4440 } 4441 4442 4443 /** 4444 * @private 4445 * @inner 4446 */ 4447 function handleMouseMove( tracker, event ) { 4448 var gPoint; 4449 4450 event = $.getEvent( event ); 4451 4452 gPoint = { 4453 id: $.MouseTracker.mousePointerId, 4454 type: 'mouse', 4455 isPrimary: true, 4456 currentPos: getMouseAbsolute( event ), 4457 currentTime: $.now() 4458 }; 4459 4460 updatePointersMove( tracker, event, [ gPoint ] ); 4461 } 4462 4463 4464 /** 4465 * @private 4466 * @inner 4467 */ 4468 function onTouchEnter( tracker, event ) { 4469 var i, 4470 touchCount = event.changedTouches.length, 4471 gPoints = []; 4472 4473 for ( i = 0; i < touchCount; i++ ) { 4474 gPoints.push( { 4475 id: event.changedTouches[ i ].identifier, 4476 type: 'touch', 4477 // isPrimary not set - let the updatePointers functions determine it 4478 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 4479 currentTime: $.now() 4480 } ); 4481 } 4482 4483 updatePointersEnter( tracker, event, gPoints ); 4484 } 4485 4486 4487 /** 4488 * @private 4489 * @inner 4490 */ 4491 function onTouchLeave( tracker, event ) { 4492 var i, 4493 touchCount = event.changedTouches.length, 4494 gPoints = []; 4495 4496 for ( i = 0; i < touchCount; i++ ) { 4497 gPoints.push( { 4498 id: event.changedTouches[ i ].identifier, 4499 type: 'touch', 4500 // isPrimary not set - let the updatePointers functions determine it 4501 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 4502 currentTime: $.now() 4503 } ); 4504 } 4505 4506 updatePointersExit( tracker, event, gPoints ); 4507 } 4508 4509 4510 /** 4511 * @private 4512 * @inner 4513 */ 4514 function onTouchStart( tracker, event ) { 4515 var time, 4516 i, 4517 touchCount = event.changedTouches.length, 4518 gPoints = []; 4519 4520 time = $.now(); 4521 4522 for ( i = 0; i < touchCount; i++ ) { 4523 gPoints.push( { 4524 id: event.changedTouches[ i ].identifier, 4525 type: 'touch', 4526 // isPrimary not set - let the updatePointers functions determine it 4527 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 4528 currentTime: time 4529 } ); 4530 } 4531 4532 // simulate touchenter if not natively available 4533 if ( !$.MouseTracker.haveTouchEnter ) { 4534 updatePointersEnter( tracker, event, gPoints ); 4535 } 4536 4537 if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact 4538 // Touch event model start, end, and move events are always captured so we don't need to capture explicitly 4539 } 4540 4541 $.cancelEvent( event ); 4542 } 4543 4544 4545 /** 4546 * @private 4547 * @inner 4548 */ 4549 function onTouchEnd( tracker, event ) { 4550 var time, 4551 i, 4552 touchCount = event.changedTouches.length, 4553 gPoints = []; 4554 4555 time = $.now(); 4556 4557 for ( i = 0; i < touchCount; i++ ) { 4558 gPoints.push( { 4559 id: event.changedTouches[ i ].identifier, 4560 type: 'touch', 4561 // isPrimary not set - let the updatePointers functions determine it 4562 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 4563 currentTime: time 4564 } ); 4565 } 4566 4567 // Touch event model start, end, and move events are always captured so we don't need to release capture. 4568 // We'll ignore the should-release-capture return value here 4569 updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact 4570 4571 // simulate touchleave if not natively available 4572 if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) { 4573 updatePointersExit( tracker, event, gPoints ); 4574 } 4575 4576 $.cancelEvent( event ); 4577 } 4578 4579 4580 /** 4581 * @private 4582 * @inner 4583 */ 4584 function onTouchMove( tracker, event ) { 4585 var i, 4586 touchCount = event.changedTouches.length, 4587 gPoints = []; 4588 4589 for ( i = 0; i < touchCount; i++ ) { 4590 gPoints.push( { 4591 id: event.changedTouches[ i ].identifier, 4592 type: 'touch', 4593 // isPrimary not set - let the updatePointers functions determine it 4594 currentPos: getMouseAbsolute( event.changedTouches[ i ] ), 4595 currentTime: $.now() 4596 } ); 4597 } 4598 4599 updatePointersMove( tracker, event, gPoints ); 4600 4601 $.cancelEvent( event ); 4602 } 4603 4604 4605 /** 4606 * @private 4607 * @inner 4608 */ 4609 function onTouchCancel( tracker, event ) { 4610 var i, 4611 touchCount = event.changedTouches.length, 4612 gPoints = []; 4613 4614 for ( i = 0; i < touchCount; i++ ) { 4615 gPoints.push( { 4616 id: event.changedTouches[ i ].identifier, 4617 type: 'touch' 4618 } ); 4619 } 4620 4621 updatePointersCancel( tracker, event, gPoints ); 4622 } 4623 4624 4625 /** 4626 * @private 4627 * @inner 4628 */ 4629 function onGestureStart( tracker, event ) { 4630 event.stopPropagation(); 4631 event.preventDefault(); 4632 return false; 4633 } 4634 4635 4636 /** 4637 * @private 4638 * @inner 4639 */ 4640 function onGestureChange( tracker, event ) { 4641 event.stopPropagation(); 4642 event.preventDefault();
4643 return false; 4644 } 4645 4646 4647 /** 4648 * @private 4649 * @inner 4650 */ 4651 function onPointerEnter( tracker, event ) { 4652 var gPoint; 4653 4654 gPoint = { 4655 id: event.pointerId, 4656 type: getPointerType( event ), 4657 isPrimary: event.isPrimary, 4658 currentPos: getMouseAbsolute( event ), 4659 currentTime: $.now() 4660 }; 4661 4662 updatePointersEnter( tracker, event, [ gPoint ] ); 4663 } 4664 4665 4666 /** 4667 * @private 4668 * @inner 4669 */ 4670 function onPointerLeave( tracker, event ) { 4671 var gPoint; 4672 4673 gPoint = { 4674 id: event.pointerId, 4675 type: getPointerType( event ), 4676 isPrimary: event.isPrimary, 4677 currentPos: getMouseAbsolute( event ), 4678 currentTime: $.now() 4679 }; 4680 4681 updatePointersExit( tracker, event, [ gPoint ] ); 4682 } 4683 4684 4685 /** 4686 * @private 4687 * @inner 4688 */ 4689 function onPointerDown( tracker, event ) { 4690 var gPoint; 4691 4692 gPoint = { 4693 id: event.pointerId, 4694 type: getPointerType( event ), 4695 isPrimary: event.isPrimary, 4696 currentPos: getMouseAbsolute( event ), 4697 currentTime: $.now() 4698 }; 4699 4700 if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { 4701 if ( $.MouseTracker.unprefixedPointerEvents ) { 4702 event.currentTarget.setPointerCapture( event.pointerId ); 4703 } else { 4704 event.currentTarget.msSetPointerCapture( event.pointerId ); 4705 } 4706 $.stopEvent( event ); 4707 } 4708 4709 if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 4710 $.cancelEvent( event ); 4711 } 4712 } 4713 4714 4715 /** 4716 * @private 4717 * @inner 4718 */ 4719 function onPointerUp( tracker, event ) { 4720 var gPoint; 4721 4722 gPoint = { 4723 id: event.pointerId, 4724 type: getPointerType( event ), 4725 isPrimary: event.isPrimary, 4726 currentPos: getMouseAbsolute( event ), 4727 currentTime: $.now() 4728 }; 4729 4730 if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { 4731 if ( $.MouseTracker.unprefixedPointerEvents ) { 4732 event.currentTarget.releasePointerCapture( event.pointerId ); 4733 } else { 4734 event.currentTarget.msReleasePointerCapture( event.pointerId ); 4735 } 4736 } 4737 } 4738 4739 4740 /** 4741 * @private 4742 * @inner 4743 */ 4744 function onPointerMove( tracker, event ) { 4745 // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) 4746 var gPoint; 4747 4748 gPoint = { 4749 id: event.pointerId, 4750 type: getPointerType( event ), 4751 isPrimary: event.isPrimary, 4752 currentPos: getMouseAbsolute( event ), 4753 currentTime: $.now() 4754 }; 4755 4756 updatePointersMove( tracker, event, [ gPoint ] ); 4757 } 4758 4759 4760 /** 4761 * @private 4762 * @inner 4763 */ 4764 function onPointerCancel( tracker, event ) { 4765 var gPoint; 4766 4767 gPoint = { 4768 id: event.pointerId, 4769 type: getPointerType( event ) 4770 }; 4771 4772 updatePointersCancel( tracker, event, [ gPoint ] ); 4773 } 4774 4775 4776/////////////////////////////////////////////////////////////////////////////// 4777// Device-agnostic DOM event handlers 4778/////////////////////////////////////////////////////////////////////////////// 4779 4780 /** 4781 * @function 4782 * @private 4783 * @inner 4784 * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList 4785 * The GesturePointList to track the pointer in. 4786 * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint 4787 * Gesture point to track. 4788 * @returns {Number} Number of gesture points in pointsList. 4789 */ 4790 function startTrackingPointer( pointsList, gPoint ) { 4791 4792 // If isPrimary is not known for the pointer then set it according to our rules: 4793 // true if the first pointer in the gesture, otherwise false 4794 if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) { 4795 if ( pointsList.getLength() === 0 ) { 4796 gPoint.isPrimary = true; 4797 } else { 4798 gPoint.isPrimary = false; 4799 } 4800 } 4801 gPoint.speed = 0; 4802 gPoint.direction = 0; 4803 gPoint.contactPos = gPoint.currentPos; 4804 gPoint.contactTime = gPoint.currentTime;
4805 gPoint.lastPos = gPoint.currentPos; 4806 gPoint.lastTime = gPoint.currentTime; 4807 4808 return pointsList.add( gPoint ); 4809 } 4810 4811 4812 /** 4813 * @function 4814 * @private 4815 * @inner 4816 * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList 4817 * The GesturePointList to stop tracking the pointer on. 4818 * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint 4819 * Gesture point to stop tracking. 4820 * @returns {Number} Number of gesture points in pointsList. 4821 */ 4822 function stopTrackingPointer( pointsList, gPoint ) { 4823 var listLength, 4824 primaryPoint; 4825 4826 if ( pointsList.getById( gPoint.id ) ) { 4827 listLength = pointsList.removeById( gPoint.id ); 4828 4829 // 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 4830 if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) { 4831 primaryPoint = pointsList.getPrimary(); 4832 if ( !primaryPoint ) { 4833 primaryPoint = pointsList.getByIndex( 0 ); 4834 if ( primaryPoint ) { 4835 primaryPoint.isPrimary = true; 4836 } 4837 } 4838 } 4839 } else { 4840 listLength = pointsList.getLength(); 4841 } 4842 4843 return listLength; 4844 } 4845 4846 4847 /** 4848 * @function 4849 * @private 4850 * @inner 4851 * @param {OpenSeadragon.MouseTracker} tracker 4852 * A reference to the MouseTracker instance. 4853 * @param {Object} event 4854 * A reference to the originating DOM event. 4855 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 4856 * Gesture points associated with the event. 4857 */ 4858 function updatePointersEnter( tracker, event, gPoints ) { 4859 var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 4860 i, 4861 gPointCount = gPoints.length, 4862 curGPoint, 4863 updateGPoint, 4864 propagate; 4865 4866 for ( i = 0; i < gPointCount; i++ ) { 4867 curGPoint = gPoints[ i ]; 4868 updateGPoint = pointsList.getById( curGPoint.id ); 4869 4870 if ( updateGPoint ) { 4871 // Already tracking the pointer...update it 4872 updateGPoint.insideElement = true; 4873 updateGPoint.lastPos = updateGPoint.currentPos; 4874 updateGPoint.lastTime = updateGPoint.currentTime; 4875 updateGPoint.currentPos = curGPoint.currentPos; 4876 updateGPoint.currentTime = curGPoint.currentTime; 4877 4878 curGPoint = updateGPoint; 4879 } else { 4880 // Initialize for tracking and add to the tracking list 4881 curGPoint.captured = false; 4882 curGPoint.insideElementPressed = false; 4883 curGPoint.insideElement = true; 4884 startTrackingPointer( pointsList, curGPoint ); 4885 } 4886 4887 // Enter 4888 if ( tracker.enterHandler ) { 4889 propagate = tracker.enterHandler( 4890 { 4891 eventSource: tracker, 4892 pointerType: curGPoint.type, 4893 position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), 4894 buttons: pointsList.buttons, 4895 insideElementPressed: curGPoint.insideElementPressed, 4896 buttonDownAny: pointsList.buttons !== 0, 4897 isTouchEvent: curGPoint.type === 'touch', 4898 originalEvent: event, 4899 preventDefaultAction: false, 4900 userData: tracker.userData 4901 } 4902 ); 4903 if ( propagate === false ) { 4904 $.cancelEvent( event ); 4905 } 4906 } 4907 } 4908 } 4909 4910 4911 /** 4912 * @function 4913 * @private 4914 * @inner 4915 * @param {OpenSeadragon.MouseTracker} tracker 4916 * A reference to the MouseTracker instance. 4917 * @param {Object} event 4918 * A reference to the originating DOM event. 4919 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 4920 * Gesture points associated with the event. 4921 */ 4922 function updatePointersExit( tracker, event, gPoints ) { 4923 var delegate = THIS[ tracker.hash ], 4924 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 4925 i, 4926 gPointCount = gPoints.length, 4927 curGPoint, 4928 updateGPoint, 4929 propagate; 4930 4931 for ( i = 0; i < gPointCount; i++ ) { 4932 curGPoint = gPoints[ i ]; 4933 updateGPoint = pointsList.getById( curGPoint.id ); 4934 4935 if ( updateGPoint ) { 4936 // Already tracking the pointer. If captured then update it, else stop tracking it 4937 if ( updateGPoint.captured ) { 4938 updateGPoint.insideElement = false;
4939 updateGPoint.lastPos = updateGPoint.currentPos; 4940 updateGPoint.lastTime = updateGPoint.currentTime; 4941 updateGPoint.currentPos = curGPoint.currentPos; 4942 updateGPoint.currentTime = curGPoint.currentTime; 4943 } else { 4944 stopTrackingPointer( pointsList, updateGPoint ); 4945 } 4946 4947 curGPoint = updateGPoint; 4948 } 4949 4950 // Exit 4951 if ( tracker.exitHandler ) { 4952 propagate = tracker.exitHandler( 4953 { 4954 eventSource: tracker, 4955 pointerType: curGPoint.type, 4956 position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), 4957 buttons: pointsList.buttons, 4958 insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, 4959 buttonDownAny: pointsList.buttons !== 0, 4960 isTouchEvent: curGPoint.type === 'touch', 4961 originalEvent: event, 4962 preventDefaultAction: false, 4963 userData: tracker.userData 4964 } 4965 ); 4966 4967 if ( propagate === false ) { 4968 $.cancelEvent( event ); 4969 } 4970 } 4971 } 4972 } 4973 4974 4975 /** 4976 * @function 4977 * @private 4978 * @inner 4979 * @param {OpenSeadragon.MouseTracker} tracker 4980 * A reference to the MouseTracker instance. 4981 * @param {Object} event 4982 * A reference to the originating DOM event. 4983 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 4984 * Gesture points associated with the event. 4985 * @param {Number} buttonChanged 4986 * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser. 4987 * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, 4988 * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. 4989 * 4990 * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false. 4991 */ 4992 function updatePointersDown( tracker, event, gPoints, buttonChanged ) { 4993 var delegate = THIS[ tracker.hash ], 4994 propagate, 4995 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 4996 i, 4997 gPointCount = gPoints.length, 4998 curGPoint, 4999 updateGPoint; 5000 5001 if ( typeof event.buttons !== 'undefined' ) { 5002 pointsList.buttons = event.buttons; 5003 } else { 5004 if ( buttonChanged === 0 ) { 5005 // Primary 5006 pointsList.buttons |= 1; 5007 } else if ( buttonChanged === 1 ) { 5008 // Aux 5009 pointsList.buttons |= 4; 5010 } else if ( buttonChanged === 2 ) { 5011 // Secondary 5012 pointsList.buttons |= 2; 5013 } else if ( buttonChanged === 3 ) { 5014 // X1 (Back) 5015 pointsList.buttons |= 8; 5016 } else if ( buttonChanged === 4 ) { 5017 // X2 (Forward) 5018 pointsList.buttons |= 16; 5019 } else if ( buttonChanged === 5 ) { 5020 // Pen Eraser 5021 pointsList.buttons |= 32; 5022 } 5023 } 5024 5025 // Only capture and track primary button, pen, and touch contacts 5026 //if ( buttonChanged !== 0 ) { 5027 if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above 5028 return false; 5029 } 5030 5031 for ( i = 0; i < gPointCount; i++ ) { 5032 curGPoint = gPoints[ i ]; 5033 updateGPoint = pointsList.getById( curGPoint.id ); 5034 5035 if ( updateGPoint ) { 5036 // Already tracking the pointer...update it 5037 updateGPoint.captured = true; 5038 updateGPoint.insideElementPressed = true; 5039 updateGPoint.insideElement = true; 5040 updateGPoint.contactPos = curGPoint.currentPos; 5041 updateGPoint.contactTime = curGPoint.currentTime;
5042 updateGPoint.lastPos = updateGPoint.currentPos; 5043 updateGPoint.lastTime = updateGPoint.currentTime; 5044 updateGPoint.currentPos = curGPoint.currentPos; 5045 updateGPoint.currentTime = curGPoint.currentTime; 5046 5047 curGPoint = updateGPoint; 5048 } else { 5049 // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this) 5050 curGPoint.captured = true; 5051 curGPoint.insideElementPressed = true; 5052 curGPoint.insideElement = true; 5053 startTrackingPointer( pointsList, curGPoint ); 5054 } 5055 5056 pointsList.contacts++; 5057 5058 if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 5059 $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint ); 5060 } 5061 5062 if ( pointsList.contacts === 1 ) { 5063 // Press 5064 if ( tracker.pressHandler ) { 5065 propagate = tracker.pressHandler( 5066 { 5067 eventSource: tracker, 5068 pointerType: curGPoint.type, 5069 position: getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ), 5070 buttons: pointsList.buttons, 5071 isTouchEvent: curGPoint.type === 'touch', 5072 originalEvent: event, 5073 preventDefaultAction: false, 5074 userData: tracker.userData 5075 } 5076 ); 5077 if ( propagate === false ) { 5078 $.cancelEvent( event ); 5079 } 5080 } 5081 } else if ( pointsList.contacts === 2 ) { 5082 if ( tracker.pinchHandler && curGPoint.type === 'touch' ) { 5083 // Initialize for pinch 5084 delegate.pinchGPoints = pointsList.asArray(); 5085 delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 5086 delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 5087 } 5088 } 5089 } 5090 5091 return true; 5092 } 5093 5094 5095 /** 5096 * @function 5097 * @private 5098 * @inner 5099 * @param {OpenSeadragon.MouseTracker} tracker 5100 * A reference to the MouseTracker instance. 5101 * @param {Object} event 5102 * A reference to the originating DOM event. 5103 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5104 * Gesture points associated with the event. 5105 * @param {Number} buttonChanged 5106 * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser. 5107 * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model, 5108 * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events. 5109 * 5110 * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false. 5111 */ 5112 function updatePointersUp( tracker, event, gPoints, buttonChanged ) { 5113 var delegate = THIS[ tracker.hash ], 5114 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 5115 propagate, 5116 insideElementReleased, 5117 releasePoint, 5118 releaseTime, 5119 i, 5120 gPointCount = gPoints.length, 5121 curGPoint, 5122 updateGPoint, 5123 releaseCapture = false, 5124 wasCaptured = false, 5125 quick; 5126 5127 if ( typeof event.buttons !== 'undefined' ) { 5128 pointsList.buttons = event.buttons; 5129 } else { 5130 if ( buttonChanged === 0 ) { 5131 // Primary 5132 pointsList.buttons ^= ~1; 5133 } else if ( buttonChanged === 1 ) { 5134 // Aux 5135 pointsList.buttons ^= ~4; 5136 } else if ( buttonChanged === 2 ) { 5137 // Secondary 5138 pointsList.buttons ^= ~2; 5139 } else if ( buttonChanged === 3 ) { 5140 // X1 (Back) 5141 pointsList.buttons ^= ~8; 5142 } else if ( buttonChanged === 4 ) { 5143 // X2 (Forward) 5144 pointsList.buttons ^= ~16; 5145 } else if ( buttonChanged === 5 ) { 5146 // Pen Eraser 5147 pointsList.buttons ^= ~32; 5148 } 5149 } 5150 5151 // Only capture and track primary button, pen, and touch contacts 5152 //if ( buttonChanged !== 0 ) { 5153 if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above 5154 return false; 5155 } 5156 5157 for ( i = 0; i < gPointCount; i++ ) { 5158 curGPoint = gPoints[ i ]; 5159 updateGPoint = pointsList.getById( curGPoint.id ); 5160 5161 if ( updateGPoint ) { 5162 // Update the pointer, stop tracking it if not still in this element 5163 if ( updateGPoint.captured ) { 5164 updateGPoint.captured = false; 5165 releaseCapture = true; 5166 wasCaptured = true; 5167 }
5168 updateGPoint.lastPos = updateGPoint.currentPos; 5169 updateGPoint.lastTime = updateGPoint.currentTime; 5170 updateGPoint.currentPos = curGPoint.currentPos; 5171 updateGPoint.currentTime = curGPoint.currentTime; 5172 if ( !updateGPoint.insideElement ) { 5173 stopTrackingPointer( pointsList, updateGPoint ); 5174 } 5175 5176 releasePoint = updateGPoint.currentPos; 5177 releaseTime = updateGPoint.currentTime; 5178 5179 if ( wasCaptured ) { 5180 // Pointer was activated in our element but could have been removed in any element since events are captured to our element 5181 5182 pointsList.contacts--; 5183 5184 if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { 5185 $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); 5186 } 5187 5188 if ( pointsList.contacts === 0 ) { 5189 5190 // Release (pressed in our element) 5191 if ( tracker.releaseHandler ) { 5192 propagate = tracker.releaseHandler( 5193 { 5194 eventSource: tracker, 5195 pointerType: updateGPoint.type, 5196 position: getPointRelativeToAbsolute( releasePoint, tracker.element ), 5197 buttons: pointsList.buttons, 5198 insideElementPressed: updateGPoint.insideElementPressed, 5199 insideElementReleased: updateGPoint.insideElement, 5200 isTouchEvent: updateGPoint.type === 'touch', 5201 originalEvent: event, 5202 preventDefaultAction: false, 5203 userData: tracker.userData 5204 } 5205 ); 5206 if ( propagate === false ) { 5207 $.cancelEvent( event ); 5208 } 5209 } 5210 5211 // Drag End 5212 if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) { 5213 propagate = tracker.dragEndHandler( 5214 { 5215 eventSource: tracker, 5216 pointerType: updateGPoint.type, 5217 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 5218 speed: updateGPoint.speed, 5219 direction: updateGPoint.direction, 5220 shift: event.shiftKey, 5221 isTouchEvent: updateGPoint.type === 'touch', 5222 originalEvent: event, 5223 preventDefaultAction: false, 5224 userData: tracker.userData 5225 } 5226 ); 5227 if ( propagate === false ) { 5228 $.cancelEvent( event ); 5229 } 5230 } 5231 5232 // Click / Double-Click 5233 if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) { 5234 quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold && 5235 updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold; 5236 5237 // Click 5238 if ( tracker.clickHandler ) { 5239 propagate = tracker.clickHandler( 5240 { 5241 eventSource: tracker, 5242 pointerType: updateGPoint.type, 5243 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 5244 quick: quick, 5245 shift: event.shiftKey, 5246 isTouchEvent: updateGPoint.type === 'touch', 5247 originalEvent: event, 5248 preventDefaultAction: false, 5249 userData: tracker.userData 5250 } 5251 ); 5252 if ( propagate === false ) { 5253 $.cancelEvent( event ); 5254 } 5255 } 5256 5257 // Double-Click 5258 if ( tracker.dblClickHandler && quick ) { 5259 pointsList.clicks++; 5260 if ( pointsList.clicks === 1 ) { 5261 delegate.lastClickPos = releasePoint; 5262 /*jshint loopfunc:true*/ 5263 delegate.dblClickTimeOut = setTimeout( function() { 5264 pointsList.clicks = 0; 5265 }, tracker.dblClickTimeThreshold ); 5266 /*jshint loopfunc:false*/ 5267 } else if ( pointsList.clicks === 2 ) { 5268 clearTimeout( delegate.dblClickTimeOut ); 5269 pointsList.clicks = 0; 5270 if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) { 5271 propagate = tracker.dblClickHandler( 5272 { 5273 eventSource: tracker, 5274 pointerType: updateGPoint.type, 5275 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 5276 shift: event.shiftKey, 5277 isTouchEvent: updateGPoint.type === 'touch', 5278 originalEvent: event, 5279 preventDefaultAction: false, 5280 userData: tracker.userData 5281 } 5282 ); 5283 if ( propagate === false ) { 5284 $.cancelEvent( event ); 5285 } 5286 } 5287 delegate.lastClickPos = null; 5288 } 5289 } 5290 } 5291 } else if ( pointsList.contacts === 2 ) { 5292 if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) { 5293 // Reset for pinch 5294 delegate.pinchGPoints = pointsList.asArray(); 5295 delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 5296 delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 5297 } 5298 } 5299 } else { 5300 // Pointer was activated in another element but removed in our element 5301 5302 // Release (pressed in another element) 5303 if ( tracker.releaseHandler ) { 5304 propagate = tracker.releaseHandler( 5305 { 5306 eventSource: tracker, 5307 pointerType: updateGPoint.type, 5308 position: getPointRelativeToAbsolute( releasePoint, tracker.element ), 5309 buttons: pointsList.buttons, 5310 insideElementPressed: updateGPoint.insideElementPressed, 5311 insideElementReleased: updateGPoint.insideElement, 5312 isTouchEvent: updateGPoint.type === 'touch', 5313 originalEvent: event, 5314 preventDefaultAction: false, 5315 userData: tracker.userData 5316 } 5317 ); 5318 if ( propagate === false ) { 5319 $.cancelEvent( event ); 5320 } 5321 } 5322 } 5323 } 5324 } 5325 5326 return releaseCapture; 5327 } 5328 5329 5330 /** 5331 * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) 5332 * 5333 * @function 5334 * @private 5335 * @inner 5336 * @param {OpenSeadragon.MouseTracker} tracker 5337 * A reference to the MouseTracker instance. 5338 * @param {Object} event 5339 * A reference to the originating DOM event. 5340 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5341 * Gesture points associated with the event. 5342 */ 5343 function updatePointersMove( tracker, event, gPoints ) { 5344 var delegate = THIS[ tracker.hash ], 5345 pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), 5346 i, 5347 gPointCount = gPoints.length, 5348 curGPoint, 5349 updateGPoint, 5350 gPointArray, 5351 delta, 5352 propagate; 5353 5354 if ( typeof event.buttons !== 'undefined' ) { 5355 pointsList.buttons = event.buttons; 5356 } 5357 5358 for ( i = 0; i < gPointCount; i++ ) { 5359 curGPoint = gPoints[ i ]; 5360 updateGPoint = pointsList.getById( curGPoint.id ); 5361 5362 if ( updateGPoint ) { 5363 // Already tracking the pointer...update it 5364 if ( curGPoint.hasOwnProperty( 'isPrimary' ) ) { 5365 updateGPoint.isPrimary = curGPoint.isPrimary; 5366 }
5367 updateGPoint.lastPos = updateGPoint.currentPos; 5368 updateGPoint.lastTime = updateGPoint.currentTime; 5369 updateGPoint.currentPos = curGPoint.currentPos; 5370 updateGPoint.currentTime = curGPoint.currentTime; 5371 } else { 5372 // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this) 5373 curGPoint.captured = false; 5374 curGPoint.insideElementPressed = false; 5375 curGPoint.insideElement = true; 5376 startTrackingPointer( pointsList, curGPoint ); 5377 } 5378 } 5379 5380 // Stop (mouse only) 5381 if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) { 5382 clearTimeout( tracker.stopTimeOut ); 5383 tracker.stopTimeOut = setTimeout( function() { 5384 handlePointerStop( tracker, event, gPoints[ 0 ].type ); 5385 }, tracker.stopDelay ); 5386 } 5387 5388 if ( pointsList.contacts === 0 ) { 5389 // Move (no contacts: hovering mouse or other hover-capable device) 5390 if ( tracker.moveHandler ) { 5391 propagate = tracker.moveHandler( 5392 { 5393 eventSource: tracker, 5394 pointerType: gPoints[ 0 ].type, 5395 position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ), 5396 buttons: pointsList.buttons, 5397 isTouchEvent: gPoints[ 0 ].type === 'touch', 5398 originalEvent: event, 5399 preventDefaultAction: false, 5400 userData: tracker.userData 5401 } 5402 ); 5403 if ( propagate === false ) { 5404 $.cancelEvent( event ); 5405 } 5406 } 5407 } else if ( pointsList.contacts === 1 ) { 5408 // Move (1 contact) 5409 if ( tracker.moveHandler ) { 5410 updateGPoint = pointsList.asArray()[ 0 ]; 5411 propagate = tracker.moveHandler( 5412 { 5413 eventSource: tracker, 5414 pointerType: updateGPoint.type, 5415 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 5416 buttons: pointsList.buttons, 5417 isTouchEvent: updateGPoint.type === 'touch', 5418 originalEvent: event, 5419 preventDefaultAction: false, 5420 userData: tracker.userData 5421 } 5422 ); 5423 if ( propagate === false ) { 5424 $.cancelEvent( event ); 5425 } 5426 } 5427 5428 // Drag 5429 if ( tracker.dragHandler ) { 5430 updateGPoint = pointsList.asArray()[ 0 ]; 5431 delta = updateGPoint.currentPos.minus( updateGPoint.lastPos ); 5432 propagate = tracker.dragHandler( 5433 { 5434 eventSource: tracker, 5435 pointerType: updateGPoint.type, 5436 position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), 5437 buttons: pointsList.buttons, 5438 delta: delta, 5439 speed: updateGPoint.speed, 5440 direction: updateGPoint.direction, 5441 shift: event.shiftKey, 5442 isTouchEvent: updateGPoint.type === 'touch', 5443 originalEvent: event, 5444 preventDefaultAction: false, 5445 userData: tracker.userData 5446 } 5447 ); 5448 if ( propagate === false ) { 5449 $.cancelEvent( event ); 5450 } 5451 } 5452 } else if ( pointsList.contacts === 2 ) { 5453 // Move (2 contacts, use center) 5454 if ( tracker.moveHandler ) { 5455 gPointArray = pointsList.asArray(); 5456 propagate = tracker.moveHandler( 5457 { 5458 eventSource: tracker, 5459 pointerType: gPointArray[ 0 ].type, 5460 position: getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ), 5461 buttons: pointsList.buttons, 5462 isTouchEvent: gPointArray[ 0 ].type === 'touch', 5463 originalEvent: event, 5464 preventDefaultAction: false, 5465 userData: tracker.userData 5466 } 5467 ); 5468 if ( propagate === false ) { 5469 $.cancelEvent( event ); 5470 } 5471 } 5472 5473 // Pinch 5474 if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) { 5475 delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos ); 5476 if ( delta != delegate.currentPinchDist ) { 5477 delegate.lastPinchDist = delegate.currentPinchDist; 5478 delegate.currentPinchDist = delta; 5479 delegate.lastPinchCenter = delegate.currentPinchCenter; 5480 delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos ); 5481 propagate = tracker.pinchHandler( 5482 { 5483 eventSource: tracker, 5484 pointerType: 'touch', 5485 gesturePoints: delegate.pinchGPoints, 5486 lastCenter: getPointRelativeToAbsolute( delegate.lastPinchCenter, tracker.element ), 5487 center: getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ), 5488 lastDistance: delegate.lastPinchDist, 5489 distance: delegate.currentPinchDist, 5490 shift: event.shiftKey, 5491 originalEvent: event, 5492 preventDefaultAction: false, 5493 userData: tracker.userData 5494 } 5495 ); 5496 if ( propagate === false ) { 5497 $.cancelEvent( event ); 5498 } 5499 } 5500 } 5501 } 5502 } 5503 5504 5505 /** 5506 * @function 5507 * @private 5508 * @inner 5509 * @param {OpenSeadragon.MouseTracker} tracker 5510 * A reference to the MouseTracker instance. 5511 * @param {Object} event 5512 * A reference to the originating DOM event. 5513 * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints 5514 * Gesture points associated with the event. 5515 */ 5516 function updatePointersCancel( tracker, event, gPoints ) { 5517 updatePointersUp( tracker, event, gPoints, 0 ); 5518 updatePointersExit( tracker, event, gPoints ); 5519 } 5520 5521 5522 /** 5523 * @private 5524 * @inner 5525 */ 5526 function handlePointerStop( tracker, originalMoveEvent, pointerType ) { 5527 if ( tracker.stopHandler ) { 5528 tracker.stopHandler( { 5529 eventSource: tracker, 5530 pointerType: pointerType, 5531 position: getMouseRelative( originalMoveEvent, tracker.element ), 5532 buttons: tracker.getActivePointersListByType( pointerType ).buttons, 5533 isTouchEvent: pointerType === 'touch', 5534 originalEvent: originalMoveEvent, 5535 preventDefaultAction: false, 5536 userData: tracker.userData 5537 } ); 5538 } 5539 } 5540 5541} ( OpenSeadragon ) ); 5542 5543/* 5544 * OpenSeadragon - Control 5545 * 5546 * Copyright (C) 2009 CodePlex Foundation 5547 * Copyright (C) 2010-2013 OpenSeadragon contributors 5548 * 5549 * Redistribution and use in source and binary forms, with or without 5550 * modification, are permitted provided that the following conditions are 5551 * met: 5552 * 5553 * - Redistributions of source code must retain the above copyright notice, 5554 * this list of conditions and the following disclaimer. 5555 * 5556 * - Redistributions in binary form must reproduce the above copyright 5557 * notice, this list of conditions and the following disclaimer in the 5558 * documentation and/or other materials provided with the distribution. 5559 * 5560 * - Neither the name of CodePlex Foundation nor the names of its 5561 * contributors may be used to endorse or promote products derived from 5562 * this software without specific prior written permission. 5563 * 5564 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 5565 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5566 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 5567 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 5568 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5569 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 5570 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 5571 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 5572 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 5573 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 5574 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 5575 */ 5576 5577(function( $ ){ 5578 5579/** 5580 * An enumeration of supported locations where controls can be anchored. 5581 * The anchoring is always relative to the container. 5582 * @member ControlAnchor 5583 * @memberof OpenSeadragon 5584 * @static 5585 * @type {Object} 5586 * @property {Number} NONE 5587 * @property {Number} TOP_LEFT 5588 * @property {Number} TOP_RIGHT 5589 * @property {Number} BOTTOM_LEFT 5590 * @property {Number} BOTTOM_RIGHT 5591 * @property {Number} ABSOLUTE 5592 */ 5593$.ControlAnchor = { 5594 NONE: 0, 5595 TOP_LEFT: 1, 5596 TOP_RIGHT: 2, 5597 BOTTOM_RIGHT: 3, 5598 BOTTOM_LEFT: 4, 5599 ABSOLUTE: 5 5600}; 5601 5602/** 5603 * @class Control 5604 * @classdesc A Control represents any interface element which is meant to allow the user 5605 * to interact with the zoomable interface. Any control can be anchored to any 5606 * element. 5607 * 5608 * @memberof OpenSeadragon 5609 * @param {Element} element - the control element to be anchored in the container. 5610 * @param {Object } options - All required and optional settings for configuring a control element. 5611 * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - the position of the control 5612 * relative to the container. 5613 * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or 5614 * directly to the container 5615 * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior 5616 * @param {Element} container - the element to control will be anchored too. 5617 */ 5618$.Control = function ( element, options, container ) { 5619 var parent = element.parentNode; 5620 if (typeof options === 'number') 5621 { 5622 $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " + 5623 "please use an options object instead. " + 5624 "Support for this deprecated variant is scheduled for removal in December 2013"); 5625 options = {anchor: options}; 5626 } 5627 options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer; 5628 /** 5629 * True if the control should have autofade behavior. 5630 * @member {Boolean} autoFade 5631 * @memberof OpenSeadragon.Control# 5632 */ 5633 this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade; 5634 /** 5635 * The element providing the user interface with some type of control (e.g. a zoom-in button). 5636 * @member {Element} element 5637 * @memberof OpenSeadragon.Control# 5638 */ 5639 this.element = element; 5640 /** 5641 * The position of the Control relative to its container. 5642 * @member {OpenSeadragon.ControlAnchor} anchor 5643 * @memberof OpenSeadragon.Control# 5644 */ 5645 this.anchor = options.anchor; 5646 /** 5647 * The Control's containing element. 5648 * @member {Element} container 5649 * @memberof OpenSeadragon.Control# 5650 */ 5651 this.container = container; 5652 /** 5653 * A neutral element surrounding the control element. 5654 * @member {Element} wrapper 5655 * @memberof OpenSeadragon.Control# 5656 */ 5657 if ( this.anchor == $.ControlAnchor.ABSOLUTE ) { 5658 this.wrapper = $.makeNeutralElement( "div" ); 5659 this.wrapper.style.position = "absolute"; 5660 this.wrapper.style.top = typeof ( options.top ) == "number" ? ( options.top + 'px' ) : options.top; 5661 this.wrapper.style.left = typeof ( options.left ) == "number" ? (options.left + 'px' ) : options.left; 5662 this.wrapper.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height; 5663 this.wrapper.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width; 5664 this.wrapper.style.margin = "0px"; 5665 this.wrapper.style.padding = "0px"; 5666 5667 this.element.style.position = "relative"; 5668 this.element.style.top = "0px";
5669 this.element.style.left = "0px"; 5670 this.element.style.height = "100%"; 5671 this.element.style.width = "100%"; 5672 } else { 5673 this.wrapper = $.makeNeutralElement( "div" ); 5674 this.wrapper.style.display = "inline-block"; 5675 if ( this.anchor == $.ControlAnchor.NONE ) { 5676 // IE6 fix 5677 this.wrapper.style.width = this.wrapper.style.height = "100%"; 5678 } 5679 } 5680 this.wrapper.appendChild( this.element ); 5681 5682 if (options.attachToViewer ) { 5683 if ( this.anchor == $.ControlAnchor.TOP_RIGHT || 5684 this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) { 5685 this.container.insertBefore( 5686 this.wrapper, 5687 this.container.firstChild 5688 ); 5689 } else { 5690 this.container.appendChild( this.wrapper ); 5691 } 5692 } else { 5693 parent.appendChild( this.wrapper ); 5694 } 5695}; 5696 5697$.Control.prototype = /** @lends OpenSeadragon.Control.prototype */{ 5698 5699 /** 5700 * Removes the control from the container. 5701 * @function 5702 */ 5703 destroy: function() { 5704 this.wrapper.removeChild( this.element ); 5705 this.container.removeChild( this.wrapper ); 5706 }, 5707 5708 /** 5709 * Determines if the control is currently visible. 5710 * @function 5711 * @return {Boolean} true if currenly visible, false otherwise. 5712 */ 5713 isVisible: function() { 5714 return this.wrapper.style.display != "none"; 5715 }, 5716 5717 /** 5718 * Toggles the visibility of the control. 5719 * @function 5720 * @param {Boolean} visible - true to make visible, false to hide. 5721 */ 5722 setVisible: function( visible ) { 5723 this.wrapper.style.display = visible ? 5724 ( this.anchor == $.ControlAnchor.ABSOLUTE ? 'block' : 'inline-block' ) : 5725 "none"; 5726 }, 5727 5728 /** 5729 * Sets the opacity level for the control. 5730 * @function 5731 * @param {Number} opactiy - a value between 1 and 0 inclusively. 5732 */ 5733 setOpacity: function( opacity ) { 5734 if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) { 5735 $.setElementOpacity( this.element, opacity, true ); 5736 } else { 5737 $.setElementOpacity( this.wrapper, opacity, true ); 5738 } 5739 } 5740}; 5741 5742}( OpenSeadragon )); 5743 5744/* 5745 * OpenSeadragon - ControlDock 5746 * 5747 * Copyright (C) 2009 CodePlex Foundation 5748 * Copyright (C) 2010-2013 OpenSeadragon contributors 5749 * 5750 * Redistribution and use in source and binary forms, with or without 5751 * modification, are permitted provided that the following conditions are 5752 * met: 5753 * 5754 * - Redistributions of source code must retain the above copyright notice, 5755 * this list of conditions and the following disclaimer. 5756 * 5757 * - Redistributions in binary form must reproduce the above copyright 5758 * notice, this list of conditions and the following disclaimer in the 5759 * documentation and/or other materials provided with the distribution. 5760 * 5761 * - Neither the name of CodePlex Foundation nor the names of its 5762 * contributors may be used to endorse or promote products derived from 5763 * this software without specific prior written permission. 5764 * 5765 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 5766 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5767 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 5768 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 5769 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 5770 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 5771 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 5772 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 5773 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 5774 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 5775 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 5776 */ 5777 5778(function( $ ){ 5779 /** 5780 * @class ControlDock 5781 * @classdesc Provides a container element (a <form> element) with support for the layout of control elements. 5782 * 5783 * @memberof OpenSeadragon 5784 */ 5785 $.ControlDock = function( options ){ 5786 var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'], 5787 layout, 5788 i; 5789 5790 $.extend( true, this, { 5791 id: 'controldock-'+$.now()+'-'+Math.floor(Math.random()*1000000), 5792 container: $.makeNeutralElement( 'div' ), 5793 controls: [] 5794 }, options ); 5795 5796 // Disable the form's submit; otherwise button clicks and return keys 5797 // can trigger it. 5798 this.container.onsubmit = function() { 5799 return false; 5800 }; 5801 5802 if( this.element ){ 5803 this.element = $.getElement( this.element ); 5804 this.element.appendChild( this.container ); 5805 this.element.style.position = 'relative'; 5806 this.container.style.width = '100%'; 5807 this.container.style.height = '100%'; 5808 } 5809 5810 for( i = 0; i < layouts.length; i++ ){ 5811 layout = layouts[ i ]; 5812 this.controls[ layout ] = $.makeNeutralElement( "div" ); 5813 this.controls[ layout ].style.position = 'absolute'; 5814 if ( layout.match( 'left' ) ){
5815 this.controls[ layout ].style.left = '0px'; 5816 } 5817 if ( layout.match( 'right' ) ){ 5818 this.controls[ layout ].style.right = '0px'; 5819 } 5820 if ( layout.match( 'top' ) ){ 5821 this.controls[ layout ].style.top = '0px'; 5822 } 5823 if ( layout.match( 'bottom' ) ){ 5824 this.controls[ layout ].style.bottom = '0px'; 5825 } 5826 } 5827 5828 this.container.appendChild( this.controls.topleft ); 5829 this.container.appendChild( this.controls.topright ); 5830 this.container.appendChild( this.controls.bottomright ); 5831 this.container.appendChild( this.controls.bottomleft ); 5832 }; 5833 5834 $.ControlDock.prototype = /** @lends OpenSeadragon.ControlDock.prototype */{ 5835 5836 /** 5837 * @function 5838 */ 5839 addControl: function ( element, controlOptions ) { 5840 element = $.getElement( element ); 5841 var div = null; 5842 5843 if ( getControlIndex( this, element ) >= 0 ) { 5844 return; // they're trying to add a duplicate control 5845 } 5846 5847 switch ( controlOptions.anchor ) { 5848 case $.ControlAnchor.TOP_RIGHT: 5849 div = this.controls.topright; 5850 element.style.position = "relative"; 5851 element.style.paddingRight = "0px"; 5852 element.style.paddingTop = "0px"; 5853 break; 5854 case $.ControlAnchor.BOTTOM_RIGHT: 5855 div = this.controls.bottomright; 5856 element.style.position = "relative"; 5857 element.style.paddingRight = "0px"; 5858 element.style.paddingBottom = "0px"; 5859 break; 5860 case $.ControlAnchor.BOTTOM_LEFT: 5861 div = this.controls.bottomleft; 5862 element.style.position = "relative"; 5863 element.style.paddingLeft = "0px"; 5864 element.style.paddingBottom = "0px"; 5865 break; 5866 case $.ControlAnchor.TOP_LEFT: 5867 div = this.controls.topleft; 5868 element.style.position = "relative"; 5869 element.style.paddingLeft = "0px"; 5870 element.style.paddingTop = "0px"; 5871 break; 5872 case $.ControlAnchor.ABSOLUTE: 5873 div = this.container; 5874 element.style.margin = "0px"; 5875 element.style.padding = "0px"; 5876 break; 5877 default: 5878 case $.ControlAnchor.NONE: 5879 div = this.container; 5880 element.style.margin = "0px"; 5881 element.style.padding = "0px"; 5882 break; 5883 } 5884 5885 this.controls.push( 5886 new $.Control( element, controlOptions, div ) 5887 ); 5888 element.style.display = "inline-block"; 5889 }, 5890 5891 5892 /** 5893 * @function 5894 * @return {OpenSeadragon.ControlDock} Chainable. 5895 */ 5896 removeControl: function ( element ) { 5897 element = $.getElement( element ); 5898 var i = getControlIndex( this, element ); 5899 5900 if ( i >= 0 ) { 5901 this.controls[ i ].destroy(); 5902 this.controls.splice( i, 1 ); 5903 } 5904 5905 return this; 5906 }, 5907 5908 /** 5909 * @function 5910 * @return {OpenSeadragon.ControlDock} Chainable. 5911 */ 5912 clearControls: function () { 5913 while ( this.controls.length > 0 ) { 5914 this.controls.pop().destroy(); 5915 } 5916 5917 return this; 5918 }, 5919 5920 5921 /** 5922 * @function 5923 * @return {Boolean} 5924 */ 5925 areControlsEnabled: function () { 5926 var i; 5927 5928 for ( i = this.controls.length - 1; i >= 0; i-- ) { 5929 if ( this.controls[ i ].isVisible() ) { 5930 return true; 5931 } 5932 } 5933 5934 return false; 5935 }, 5936 5937 5938 /** 5939 * @function 5940 * @return {OpenSeadragon.ControlDock} Chainable. 5941 */ 5942 setControlsEnabled: function( enabled ) { 5943 var i; 5944 5945 for ( i = this.controls.length - 1; i >= 0; i-- ) { 5946 this.controls[ i ].setVisible( enabled ); 5947 } 5948 5949 return this; 5950 } 5951 5952 }; 5953 5954
5955 /////////////////////////////////////////////////////////////////////////////// 5956 // Utility methods 5957 /////////////////////////////////////////////////////////////////////////////// 5958 function getControlIndex( dock, element ) { 5959 var controls = dock.controls, 5960 i; 5961 5962 for ( i = controls.length - 1; i >= 0; i-- ) { 5963 if ( controls[ i ].element == element ) { 5964 return i; 5965 } 5966 } 5967 5968 return -1; 5969 } 5970 5971}( OpenSeadragon )); 5972 5973/* 5974 * OpenSeadragon - Viewer 5975 * 5976 * Copyright (C) 2009 CodePlex Foundation 5977 * Copyright (C) 2010-2013 OpenSeadragon contributors 5978 * 5979 * Redistribution and use in source and binary forms, with or without 5980 * modification, are permitted provided that the following conditions are 5981 * met: 5982 * 5983 * - Redistributions of source code must retain the above copyright notice, 5984 * this list of conditions and the following disclaimer. 5985 * 5986 * - Redistributions in binary form must reproduce the above copyright 5987 * notice, this list of conditions and the following disclaimer in the 5988 * documentation and/or other materials provided with the distribution. 5989 * 5990 * - Neither the name of CodePlex Foundation nor the names of its 5991 * contributors may be used to endorse or promote products derived from 5992 * this software without specific prior written permission. 5993 * 5994 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 5995 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5996 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 5997 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 5998 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 5999 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 6000 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 6001 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 6002 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 6003 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 6004 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6005 */ 6006 6007(function( $ ){ 6008 6009// dictionary from hash to private properties 6010var THIS = {}, 6011// We keep a list of viewers so we can 'wake-up' each viewer on 6012// a page after toggling between fullpage modes 6013 VIEWERS = {}; 6014 6015/** 6016 * 6017 * The main point of entry into creating a zoomable image on the page. 6018 * 6019 * We have provided an idiomatic javascript constructor which takes 6020 * a single object, but still support the legacy positional arguments. 6021 * 6022 * The options below are given in order that they appeared in the constructor 6023 * as arguments and we translate a positional call into an idiomatic call. 6024 * 6025 * @class Viewer 6026 * @classdesc The main OpenSeadragon viewer class. 6027 * 6028 * @memberof OpenSeadragon 6029 * @extends OpenSeadragon.EventSource 6030 * @extends OpenSeadragon.ControlDock 6031 * @param {OpenSeadragon.Options} options - Viewer options. 6032 * 6033 **/
6034$.Viewer = function( options ) { 6035 6036 var args = arguments, 6037 _this = this, 6038 i; 6039 6040 6041 //backward compatibility for positional args while prefering more 6042 //idiomatic javascript options object as the only argument 6043 if( !$.isPlainObject( options ) ){ 6044 options = { 6045 id: args[ 0 ], 6046 xmlPath: args.length > 1 ? args[ 1 ] : undefined, 6047 prefixUrl: args.length > 2 ? args[ 2 ] : undefined, 6048 controls: args.length > 3 ? args[ 3 ] : undefined, 6049 overlays: args.length > 4 ? args[ 4 ] : undefined 6050 }; 6051 } 6052 6053 //options.config and the general config argument are deprecated 6054 //in favor of the more direct specification of optional settings 6055 //being pass directly on the options object 6056 if ( options.config ){ 6057 $.extend( true, options, options.config ); 6058 delete options.config; 6059 } 6060 6061 //Public properties 6062 //Allow the options object to override global defaults 6063 $.extend( true, this, { 6064 6065 //internal state and dom identifiers 6066 id: options.id, 6067 hash: options.hash || options.id, 6068 6069 //dom nodes 6070 /** 6071 * The parent element of this Viewer instance, passed in when the Viewer was created. 6072 * @member {Element} element 6073 * @memberof OpenSeadragon.Viewer# 6074 */ 6075 element: null, 6076 /** 6077 * A <div> element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.<br><br> 6078 * Child element of {@link OpenSeadragon.Viewer#element}. 6079 * @member {Element} container 6080 * @memberof OpenSeadragon.Viewer# 6081 */ 6082 container: null, 6083 /** 6084 * A <textarea> element, the element where keyboard events are handled.<br><br> 6085 * Child element of {@link OpenSeadragon.Viewer#container}, 6086 * positioned below {@link OpenSeadragon.Viewer#canvas}. 6087 * @member {Element} keyboardCommandArea 6088 * @memberof OpenSeadragon.Viewer# 6089 */ 6090 keyboardCommandArea: null, 6091 /** 6092 * A <div> element, the element where user-input events are handled for panning and zooming.<br><br> 6093 * Child element of {@link OpenSeadragon.Viewer#container}, 6094 * positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.<br><br> 6095 * The parent of {@link OpenSeadragon.Drawer#canvas} instances. 6096 * @member {Element} canvas 6097 * @memberof OpenSeadragon.Viewer# 6098 */ 6099 canvas: null, 6100 6101 // Overlays list. An overlay allows to add html on top of the viewer. 6102 overlays: [], 6103 // Container inside the canvas where overlays are drawn. 6104 overlaysContainer: null, 6105 6106 //private state properties 6107 previousBody: [], 6108 6109 //This was originally initialized in the constructor and so could never 6110 //have anything in it. now it can because we allow it to be specified 6111 //in the options and is only empty by default if not specified. Also 6112 //this array was returned from get_controls which I find confusing 6113 //since this object has a controls property which is treated in other 6114 //functions like clearControls. I'm removing the accessors. 6115 customControls: [], 6116 6117 //These are originally not part options but declared as members 6118 //in initialize. It's still considered idiomatic to put them here 6119 source: null, 6120 /** 6121 * Handles rendering of tiles in the viewer. Created for each TileSource opened. 6122 * @member {OpenSeadragon.Drawer} drawer 6123 * @memberof OpenSeadragon.Viewer# 6124 */ 6125 drawer: null, 6126 drawers: [], 6127 // Container inside the canvas where drawers (layers) are drawn. 6128 drawersContainer: null, 6129 /** 6130 * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened. 6131 * @member {OpenSeadragon.Viewport} viewport 6132 * @memberof OpenSeadragon.Viewer# 6133 */ 6134 viewport: null, 6135 /** 6136 * @member {OpenSeadragon.Navigator} navigator 6137 * @memberof OpenSeadragon.Viewer# 6138 */ 6139 navigator: null, 6140 6141 //A collection viewport is a separate viewport used to provide 6142 //simultaneous rendering of sets of tiles 6143 collectionViewport: null, 6144 collectionDrawer: null, 6145 6146 //UI image resources 6147 //TODO: rename navImages to uiImages 6148 navImages: null, 6149 6150 //interface button controls 6151 buttons: null, 6152 6153 //TODO: this is defunct so safely remove it 6154 profiler: null 6155 6156 }, $.DEFAULT_SETTINGS, options ); 6157 6158 if ( typeof( this.hash) === "undefined" ) { 6159 throw new Error("A hash must be defined, either by specifying options.id or options.hash."); 6160 } 6161 if ( typeof( THIS[ this.hash ] ) !== "undefined" ) { 6162 // We don't want to throw an error here, as the user might have discarded 6163 // the previous viewer with the same hash and now want to re
6163create it. 6164 $.console.warn("Hash " + this.hash + " has already been used."); 6165 } 6166 6167 //Private state properties 6168 THIS[ this.hash ] = { 6169 "fsBoundsDelta": new $.Point( 1, 1 ), 6170 "prevContainerSize": null, 6171 "animating": false, 6172 "forceRedraw": false, 6173 "mouseInside": false, 6174 "group": null, 6175 // whether we should be continuously zooming 6176 "zooming": false, 6177 // how much we should be continuously zooming by 6178 "zoomFactor": null, 6179 "lastZoomTime": null, 6180 // did we decide this viewer has a sequence of tile sources 6181 "sequenced": false, 6182 "sequence": 0, 6183 "fullPage": false, 6184 "onfullscreenchange": null 6185 }; 6186 6187 this._updateRequestId = null; 6188 this.currentOverlays = []; 6189 6190 //Inherit some behaviors and properties 6191 $.EventSource.call( this ); 6192 6193 this.addHandler( 'open-failed', function ( event ) { 6194 var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message); 6195 _this._showMessage( msg ); 6196 }); 6197 6198 $.ControlDock.call( this, options ); 6199 6200 //Deal with tile sources 6201 var initialTileSource; 6202 6203 if ( this.xmlPath ){ 6204 //Deprecated option. Now it is preferred to use the tileSources option 6205 this.tileSources = [ this.xmlPath ]; 6206 } 6207 6208 if ( this.tileSources ){ 6209 // tileSources is a complex option... 6210 // 6211 // It can be a string, object, or an array of any of strings and objects. 6212 // At this point we only care about if it is an Array or not. 6213 // 6214 if( $.isArray( this.tileSources ) ){ 6215 6216 //must be a sequence of tileSource since the first item 6217 //is a legacy tile source 6218 if( this.tileSources.length > 1 ){ 6219 THIS[ this.hash ].sequenced = true; 6220 } 6221 6222 //Keeps the initial page within bounds 6223 if ( this.initialPage > this.tileSources.length - 1 ){ 6224 this.initialPage = this.tileSources.length - 1; 6225 } 6226 6227 initialTileSource = this.tileSources[ this.initialPage ]; 6228 6229 //Update the sequence (aka currrent page) property 6230 THIS[ this.hash ].sequence = this.initialPage; 6231 } else { 6232 initialTileSource = this.tileSources; 6233 } 6234 } 6235 6236 this.element = this.element || document.getElementById( this.id ); 6237 this.canvas = $.makeNeutralElement( "div" ); 6238 this.keyboardCommandArea = $.makeNeutralElement( "textarea" ); 6239 this.drawersContainer = $.makeNeutralElement( "div" ); 6240 this.overlaysContainer = $.makeNeutralElement( "div" ); 6241 6242 this.canvas.className = "openseadragon-canvas"; 6243 (function( style ){ 6244 style.width = "100%"; 6245 style.height = "100%"; 6246 style.overflow = "hidden"; 6247 style.position = "absolute"; 6248 style.top = "0px"; 6249 style.left = "0px"; 6250 // Disable browser default touch handling 6251 if (style["touch-action"] !== undefined) { 6252 style["touch-action"] = "none"; 6253 } else if (style["-ms-touch-action"] !== undefined) { 6254 style["-ms-touch-action"] = "none"; 6255 } 6256 }(this.canvas.style)); 6257 6258 //the container is created through applying the ControlDock constructor above 6259 this.container.className = "openseadragon-container"; 6260 (function( style ){ 6261 style.width = "100%"; 6262 style.height = "100%"; 6263 style.position = "relative"; 6264 style.overflow = "hidden"; 6265 style.left = "0px"; 6266 style.top = "0px"; 6267 style.textAlign = "left"; // needed to protect against 6268 }( this.container.style )); 6269 6270 this.keyboardCommandArea.className = "keyboard-command-area"; 6271 (function( style ){ 6272 style.width = "100%"; 6273 style.height = "100%"; 6274 style.overflow = "hidden"; 6275 style.position = "absolute"; 6276 style.top = "0px"; 6277 style.left = "0px"; 6278 style.resize = "none"; 6279 }( this.keyboardCommandArea.style )); 6280 6281 this.container.insertBefore( this.canvas, this.container.firstChild ); 6282 this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild ); 6283 this.element.appendChild( this.container ); 6284 this.canvas.appendChild( this.drawersContainer ); 6285 this.canvas.appendChild( this.overlaysContainer ); 6286 6287 //Used for toggling between fullscreen and default container size
6288 //TODO: these can be closure private and shared across Viewer 6289 // instances. 6290 this.bodyWidth = document.body.style.width; 6291 this.bodyHeight = document.body.style.height; 6292 this.bodyOverflow = document.body.style.overflow; 6293 this.docOverflow = document.documentElement.style.overflow; 6294 6295 this.keyboardCommandArea.innerTracker = new $.MouseTracker({ 6296 _this : this, 6297 element: this.keyboardCommandArea, 6298 focusHandler: function( event ){ 6299 if ( !event.preventDefaultAction ) { 6300 var point = $.getElementPosition( this.element ); 6301 window.scrollTo( 0, point.y ); 6302 } 6303 }, 6304 6305 keyHandler: function( event ){ 6306 if ( !event.preventDefaultAction ) { 6307 switch( event.keyCode ){ 6308 case 61://=|+ 6309 _this.viewport.zoomBy(1.1); 6310 _this.viewport.applyConstraints(); 6311 return false; 6312 case 45://-|_ 6313 _this.viewport.zoomBy(0.9); 6314 _this.viewport.applyConstraints(); 6315 return false; 6316 case 48://0|) 6317 _this.viewport.goHome(); 6318 _this.viewport.applyConstraints(); 6319 return false; 6320 case 119://w 6321 case 87://W 6322 case 38://up arrow 6323 if ( event.shift ) { 6324 _this.viewport.zoomBy(1.1); 6325 } else { 6326 _this.viewport.panBy(new $.Point(0, -0.05)); 6327 } 6328 _this.viewport.applyConstraints(); 6329 return false; 6330 case 115://s 6331 case 83://S 6332 case 40://down arrow 6333 if ( event.shift ) { 6334 _this.viewport.zoomBy(0.9); 6335 } else { 6336 _this.viewport.panBy(new $.Point(0, 0.05)); 6337 } 6338 _this.viewport.applyConstraints(); 6339 return false; 6340 case 97://a 6341 case 37://left arrow 6342 _this.viewport.panBy(new $.Point(-0.05, 0)); 6343 _this.viewport.applyConstraints(); 6344 return false; 6345 case 100://d 6346 case 39://right arrow 6347 _this.viewport.panBy(new $.Point(0.05, 0)); 6348 _this.viewport.applyConstraints(); 6349 return false; 6350 default: 6351 //console.log( 'navigator keycode %s', event.keyCode ); 6352 return true; 6353 } 6354 } 6355 } 6356 }).setTracking( true ); // default state 6357 6358 6359 this.innerTracker = new $.MouseTracker({ 6360 element: this.canvas, 6361 clickTimeThreshold: this.clickTimeThreshold, 6362 clickDistThreshold: this.clickDistThreshold, 6363 dblClickTimeThreshold: this.dblClickTimeThreshold, 6364 dblClickDistThreshold: this.dblClickDistThreshold, 6365 clickHandler: $.delegate( this, onCanvasClick ), 6366 dblClickHandler: $.delegate( this, onCanvasDblClick ), 6367 dragHandler: $.delegate( this, onCanvasDrag ), 6368 dragEndHandler: $.delegate( this, onCanvasDragEnd ), 6369 releaseHandler: $.delegate( this, onCanvasRelease ), 6370 scrollHandler: $.delegate( this, onCanvasScroll ), 6371 pinchHandler: $.delegate( this, onCanvasPinch ) 6372 }).setTracking( this.mouseNavEnabled ? true : false ); // default state 6373 6374 this.outerTracker = new $.MouseTracker({ 6375 element: this.container, 6376 clickTimeThreshold: this.clickTimeThreshold, 6377 clickDistThreshold: this.clickDistThreshold, 6378 dblClickTimeThreshold: this.dblClickTimeThreshold, 6379 dblClickDistThreshold: this.dblClickDistThreshold, 6380 enterHandler: $.delegate( this, onContainerEnter ), 6381 exitHandler: $.delegate( this, onContainerExit ), 6382 pressHandler: $.delegate( this, onContainerPress ), 6383 releaseHandler: $.delegate( this, onContainerRelease ) 6384 }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking 6385 6386 if( this.toolbar ){ 6387 this.toolbar = new $.ControlDock({ element: this.toolbar }); 6388 } 6389 6390 this.bindStandardControls(); 6391 this.bindSequenceControls(); 6392 6393 if ( initialTileSource ) { 6394 this.open( initialTileSource ); 6395 6396 if ( this.tileSources.length > 1 ) { 6397 this._updateSequenceButtons( this.initialPage ); 6398 } 6399 } 6400 6401 for ( i = 0; i < this.customControls.length; i++ ) { 6402 this.addControl( 6403 this.customControls[ i ].id,
6404 {anchor: this.customControls[ i ].anchor} 6405 ); 6406 } 6407 6408 $.requestAnimationFrame( function(){ 6409 beginControlsAutoHide( _this ); 6410 } ); // initial fade out 6411 6412}; 6413 6414$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{ 6415 6416 6417 /** 6418 * @function 6419 * @return {Boolean} 6420 */ 6421 isOpen: function () { 6422 return !!this.source; 6423 }, 6424 6425 /** 6426 * A deprecated function, renamed to 'open' to match event name and 6427 * match current 'close' method. 6428 * @function 6429 * @param {String} dzi xml string or the url to a DZI xml document. 6430 * @return {OpenSeadragon.Viewer} Chainable. 6431 * 6432 * @deprecated - use {@link OpenSeadragon.Viewer#open} instead. 6433 */ 6434 openDzi: function ( dzi ) { 6435 return this.open( dzi ); 6436 }, 6437 6438 /** 6439 * A deprecated function, renamed to 'open' to match event name and 6440 * match current 'close' method. 6441 * @function 6442 * @param {String|Object|Function} See OpenSeadragon.Viewer.prototype.open 6443 * @return {OpenSeadragon.Viewer} Chainable. 6444 * 6445 * @deprecated - use {@link OpenSeadragon.Viewer#open} instead. 6446 */ 6447 openTileSource: function ( tileSource ) { 6448 return this.open( tileSource ); 6449 }, 6450 6451 /** 6452 * Open a TileSource object into the viewer. 6453 * 6454 * tileSources is a complex option... 6455 * 6456 * It can be a string, object, function, or an array of any of these: 6457 * 6458 * - A String implies a url used to determine the tileSource implementation 6459 * based on the file extension of url. JSONP is implied by *.js, 6460 * otherwise the url is retrieved as text and the resulting text is 6461 * introspected to determine if its json, xml, or text and parsed. 6462 * - An Object implies an inline configuration which has a single 6463 * property sufficient for being able to determine tileSource 6464 * implementation. If the object has a property which is a function 6465 * named 'getTileUrl', it is treated as a custom TileSource. 6466 * @function 6467 * @param {String|Object|Function} 6468 * @return {OpenSeadragon.Viewer} Chainable. 6469 * @fires OpenSeadragon.Viewer.event:open 6470 * @fires OpenSeadragon.Viewer.event:open-failed 6471 */ 6472 open: function ( tileSource ) { 6473 var _this = this; 6474 6475 _this._hideMessage(); 6476 6477 getTileSourceImplementation( _this, tileSource, function( tileSource ) { 6478 openTileSource( _this, tileSource ); 6479 }, function( event ) { 6480 /** 6481 * Raised when an error occurs loading a TileSource. 6482 * 6483 * @event open-failed 6484 * @memberof OpenSeadragon.Viewer 6485 * @type {object} 6486 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6487 * @property {String} message 6488 * @property {String} source 6489 * @property {?Object} userData - Arbitrary subscriber-defined object. 6490 */ 6491 _this.raiseEvent( 'open-failed', event ); 6492 }); 6493 6494 return this; 6495 }, 6496 6497 6498 /** 6499 * @function 6500 * @return {OpenSeadragon.Viewer} Chainable. 6501 * @fires OpenSeadragon.Viewer.event:close 6502 */ 6503 close: function ( ) { 6504 6505 if ( !THIS[ this.hash ] ) { 6506 //this viewer has already been destroyed: returning immediately 6507 return this; 6508 } 6509 6510 if ( this._updateRequestId !== null ) { 6511 $.cancelAnimationFrame( this._updateRequestId ); 6512 this._updateRequestId = null; 6513 } 6514 6515 if ( this.navigator ) { 6516 this.navigator.close(); 6517 } 6518 6519 this.clearOverlays(); 6520 this.drawersContainer.innerHTML = ""; 6521 this.overlaysContainer.innerHTML = ""; 6522 6523 if ( this.drawer ) { 6524 this.drawer.destroy(); 6525 } 6526 6527 this.source = null; 6528 this.drawer = null; 6529 this.drawers = []; 6530 6531 this.viewport = this.preserveViewport ? this.viewport : null; 6532 6533 6534 VIEWERS[ this.hash ] = null; 6535 delete VIEWERS[ this.hash ]; 6536 6537 /** 6538 * Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}). 6539 * 6540 * @event close 6541 * @memberof OpenSeadragon.Viewer 6542 * @type {object} 6543 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
6544 * @property {?Object} userData - Arbitrary subscriber-defined object. 6545 */ 6546 this.raiseEvent( 'close' ); 6547 6548 return this; 6549 }, 6550 6551 6552 /** 6553 * Function to destroy the viewer and clean up everything created by OpenSeadragon. 6554 * 6555 * Example: 6556 * var viewer = OpenSeadragon({ 6557 * [...] 6558 * }); 6559 * 6560 * //when you are done with the viewer: 6561 * viewer.destroy(); 6562 * viewer = null; //important 6563 * 6564 * @function 6565 */ 6566 destroy: function( ) { 6567 this.close(); 6568 6569 //TODO: implement this... 6570 //this.unbindSequenceControls() 6571 //this.unbindStandardControls() 6572 6573 this.removeAllHandlers(); 6574 6575 // Go through top element (passed to us) and remove all children 6576 // Use removeChild to make sure it handles SVG or any non-html 6577 // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15 6578 if (this.element){ 6579 while (this.element.firstChild) { 6580 this.element.removeChild(this.element.firstChild); 6581 } 6582 } 6583 6584 // destroy the mouse trackers 6585 if (this.keyboardCommandArea){ 6586 this.keyboardCommandArea.innerTracker.destroy(); 6587 } 6588 if (this.innerTracker){ 6589 this.innerTracker.destroy(); 6590 } 6591 if (this.outerTracker){ 6592 this.outerTracker.destroy(); 6593 } 6594 6595 THIS[ this.hash ] = null; 6596 delete THIS[ this.hash ]; 6597 6598 // clear all our references to dom objects 6599 this.canvas = null; 6600 this.keyboardCommandArea = null; 6601 this.container = null; 6602 6603 // clear our reference to the main element - they will need to pass it in again, creating a new viewer 6604 this.element = null; 6605 }, 6606 6607 6608 /** 6609 * @function 6610 * @return {Boolean} 6611 */ 6612 isMouseNavEnabled: function () { 6613 return this.innerTracker.isTracking(); 6614 }, 6615 6616 /** 6617 * @function 6618 * @param {Boolean} enabled - true to enable, false to disable 6619 * @return {OpenSeadragon.Viewer} Chainable. 6620 * @fires OpenSeadragon.Viewer.event:mouse-enabled 6621 */ 6622 setMouseNavEnabled: function( enabled ){ 6623 this.innerTracker.setTracking( enabled ); 6624 /** 6625 * Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}). 6626 * 6627 * @event mouse-enabled 6628 * @memberof OpenSeadragon.Viewer 6629 * @type {object} 6630 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6631 * @property {Boolean} enabled 6632 * @property {?Object} userData - Arbitrary subscriber-defined object. 6633 */ 6634 this.raiseEvent( 'mouse-enabled', { enabled: enabled } ); 6635 return this; 6636 }, 6637 6638 6639 /** 6640 * @function 6641 * @return {Boolean} 6642 */ 6643 areControlsEnabled: function () { 6644 var enabled = this.controls.length, 6645 i; 6646 for( i = 0; i < this.controls.length; i++ ){ 6647 enabled = enabled && this.controls[ i ].isVisibile(); 6648 } 6649 return enabled; 6650 }, 6651 6652 6653 /** 6654 * Shows or hides the controls (e.g. the default navigation buttons). 6655 * 6656 * @function 6657 * @param {Boolean} true to show, false to hide. 6658 * @return {OpenSeadragon.Viewer} Chainable. 6659 * @fires OpenSeadragon.Viewer.event:controls-enabled 6660 */ 6661 setControlsEnabled: function( enabled ) { 6662 if( enabled ){ 6663 abortControlsAutoHide( this ); 6664 } else { 6665 beginControlsAutoHide( this ); 6666 } 6667 /** 6668 * Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}). 6669 * 6670 * @event controls-enabled 6671 * @memberof OpenSeadragon.Viewer 6672 * @type {object} 6673 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6674 * @property {Boolean} enabled
6675 * @property {?Object} userData - Arbitrary subscriber-defined object. 6676 */ 6677 this.raiseEvent( 'controls-enabled', { enabled: enabled } ); 6678 return this; 6679 }, 6680 6681 6682 /** 6683 * @function 6684 * @return {Boolean} 6685 */ 6686 isFullPage: function () { 6687 return THIS[ this.hash ].fullPage; 6688 }, 6689 6690 6691 /** 6692 * Toggle full page mode. 6693 * @function 6694 * @param {Boolean} fullPage 6695 * If true, enter full page mode. If false, exit full page mode. 6696 * @return {OpenSeadragon.Viewer} Chainable. 6697 * @fires OpenSeadragon.Viewer.event:pre-full-page 6698 * @fires OpenSeadragon.Viewer.event:full-page 6699 */ 6700 setFullPage: function( fullPage ) { 6701 6702 var body = document.body, 6703 bodyStyle = body.style, 6704 docStyle = document.documentElement.style, 6705 _this = this, 6706 hash, 6707 nodes, 6708 i; 6709 6710 //dont bother modifying the DOM if we are already in full page mode. 6711 if ( fullPage == this.isFullPage() ) { 6712 return this; 6713 } 6714 6715 var fullPageEventArgs = { 6716 fullPage: fullPage, 6717 preventDefaultAction: false 6718 }; 6719 /** 6720 * Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}). 6721 * 6722 * @event pre-full-page 6723 * @memberof OpenSeadragon.Viewer 6724 * @type {object} 6725 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6726 * @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode. 6727 * @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false. 6728 * @property {?Object} userData - Arbitrary subscriber-defined object. 6729 */ 6730 this.raiseEvent( 'pre-full-page', fullPageEventArgs ); 6731 if ( fullPageEventArgs.preventDefaultAction ) { 6732 return this; 6733 } 6734 6735 if ( fullPage ) { 6736 6737 this.elementSize = $.getElementSize( this.element ); 6738 this.pageScroll = $.getPageScroll(); 6739 6740 this.elementMargin = this.element.style.margin; 6741 this.element.style.margin = "0"; 6742 this.elementPadding = this.element.style.padding; 6743 this.element.style.padding = "0"; 6744 6745 this.bodyMargin = bodyStyle.margin; 6746 this.docMargin = docStyle.margin; 6747 bodyStyle.margin = "0"; 6748 docStyle.margin = "0"; 6749 6750 this.bodyPadding = bodyStyle.padding; 6751 this.docPadding = docStyle.padding; 6752 bodyStyle.padding = "0"; 6753 docStyle.padding = "0"; 6754 6755 this.bodyWidth = bodyStyle.width; 6756 this.bodyHeight = bodyStyle.height; 6757 bodyStyle.width = "100%"; 6758 bodyStyle.height = "100%"; 6759 6760 //when entering full screen on the ipad it wasnt sufficient to leave 6761 //the body intact as only only the top half of the screen would 6762 //respond to touch events on the canvas, while the bottom half treated 6763 //them as touch events on the document body. Thus we remove and store 6764 //the bodies elements and replace them when we leave full screen. 6765 this.previousBody = []; 6766 THIS[ this.hash ].prevElementParent = this.element.parentNode; 6767 THIS[ this.hash ].prevNextSibling = this.element.nextSibling; 6768 THIS[ this.hash ].prevElementWidth = this.element.style.width; 6769 THIS[ this.hash ].prevElementHeight = this.element.style.height; 6770 nodes = body.childNodes.length; 6771 for ( i = 0; i < nodes; i++ ) { 6772 this.previousBody.push( body.childNodes[ 0 ] ); 6773 body.removeChild( body.childNodes[ 0 ] ); 6774 } 6775 6776 //If we've got a toolbar, we need to enable the user to use css to 6777 //preserve it in fullpage mode 6778 if ( this.toolbar && this.toolbar.element ) { 6779 //save a reference to the parent so we can put it back 6780 //in the long run we need a better strategy 6781 this.toolbar.parentNode = this.toolbar.element.parentNode; 6782 this.toolbar.nextSibling = this.toolbar.element.nextSibling; 6783 body.appendChild( this.toolbar.element ); 6784 6785 //Make sure the user has some ability to style the toolbar based 6786 //on the mode 6787 $.addClass( this.toolbar.element, 'fullpage' ); 6788 } 6789 6790 $.addClass( this.element, 'fullpage' ); 6791 body.appendChild( this.element ); 6792 6793 this.element.style.height = $.getWindowSize().y + 'px'; 6794 this.element.style.width = $.getWindowSize().x + 'px'; 6795 6796 if ( this.toolbar && this.toolbar.element ) { 6797 this.element.style.height = ( 6798 $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y 6799 ) + 'px'; 6800 } 6801 6802 THIS[ this.hash ].fullPage = true; 6803 6804 // mouse will be inside container now 6805 $.delegate( this, onContainerEnter )( {} ); 6806 6807 } else { 6808 6809 this.element.style.margin = this.elementMargin; 6810 this.element.style.padding = this.elementPadding; 6811 6812 bodyStyle.margin = this.bodyMargin; 6813 docStyle.margin = this.docMargin; 6814 6815 bodyStyle.padding = this.bodyPadding; 6816 docStyle.padding = this.docPadding; 6817 6818 bodyStyle.width = this.bodyWidth; 6819 bodyStyle.height = this.bodyHeight; 6820 6821 body.removeChild( this.element ); 6822 nodes = this.previousBody.length; 6823 for ( i = 0; i < nodes; i++ ) { 6824 body.appendChild( this.previousBody.shift() ); 6825 } 6826 6827 $.removeClass( this.element, 'fullpage' ); 6828 THIS[ this.hash ].prevElementParent.insertBefore( 6829 this.element, 6830 THIS[ this.hash ].prevNextSibling 6831 ); 6832 6833 //If we've got a toolbar, we need to enable the user to use css to 6834 //reset it to its original state 6835 if ( this.toolbar && this.toolbar.element ) { 6836 body.removeChild( this.toolbar.element ); 6837 6838 //Make sure the user has some ability to style the toolbar based 6839 //on the mode 6840 $.removeClass( this.toolbar.element, 'fullpage' ); 6841 6842 this.toolbar.parentNode.insertBefore( 6843 this.toolbar.element, 6844 this.toolbar.nextSibling 6845 ); 6846 delete this.toolbar.parentNode; 6847 delete this.toolbar.nextSibling; 6848 } 6849 6850 this.element.style.width = THIS[ this.hash ].prevElementWidth; 6851 this.element.style.height = THIS[ this.hash ].prevElementHeight; 6852 6853 // After exiting fullPage or fullScreen, it can take some time 6854 // before the browser can actually set the scroll. 6855 var restoreScrollCounter = 0; 6856 var restoreScroll = function() { 6857 $.setPageScroll( _this.pageScroll ); 6858 var pageScroll = $.getPageScroll(); 6859 restoreScrollCounter++; 6860 if ( restoreScrollCounter < 10 && 6861 pageScroll.x !== _this.pageScroll.x || 6862 pageScroll.y !== _this.pageScroll.y ) { 6863 $.requestAnimationFrame( restoreScroll ); 6864 } 6865 }; 6866 $.requestAnimationFrame( restoreScroll ); 6867 6868 THIS[ this.hash ].fullPage = false; 6869 6870 // mouse will likely be outside now 6871 $.delegate( this, onContainerExit )( { } ); 6872 6873 } 6874 6875 if ( this.navigator && this.viewport ) { 6876 this.navigator.update( this.viewport ); 6877 } 6878 6879 /** 6880 * Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}). 6881 * 6882 * @event full-page 6883 * @memberof OpenSeadragon.Viewer 6884 * @type {object} 6885 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6886 * @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode.
6887 * @property {?Object} userData - Arbitrary subscriber-defined object. 6888 */ 6889 this.raiseEvent( 'full-page', { fullPage: fullPage } ); 6890 6891 return this; 6892 }, 6893 6894 /** 6895 * Toggle full screen mode if supported. Toggle full page mode otherwise. 6896 * @function 6897 * @param {Boolean} fullScreen 6898 * If true, enter full screen mode. If false, exit full screen mode. 6899 * @return {OpenSeadragon.Viewer} Chainable. 6900 * @fires OpenSeadragon.Viewer.event:pre-full-screen 6901 * @fires OpenSeadragon.Viewer.event:full-screen 6902 */ 6903 setFullScreen: function( fullScreen ) { 6904 var _this = this; 6905 6906 if ( !$.supportsFullScreen ) { 6907 return this.setFullPage( fullScreen ); 6908 } 6909 6910 if ( $.isFullScreen() === fullScreen ) { 6911 return this; 6912 } 6913 6914 var fullScreeEventArgs = { 6915 fullScreen: fullScreen, 6916 preventDefaultAction: false 6917 }; 6918 /** 6919 * Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}). 6920 * 6921 * @event pre-full-screen 6922 * @memberof OpenSeadragon.Viewer 6923 * @type {object} 6924 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6925 * @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode. 6926 * @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false. 6927 * @property {?Object} userData - Arbitrary subscriber-defined object. 6928 */ 6929 this.raiseEvent( 'pre-full-screen', fullScreeEventArgs ); 6930 if ( fullScreeEventArgs.preventDefaultAction ) { 6931 return this; 6932 } 6933 6934 if ( fullScreen ) { 6935 6936 this.setFullPage( true ); 6937 // If the full page mode is not actually entered, we need to prevent 6938 // the full screen mode. 6939 if ( !this.isFullPage() ) { 6940 return this; 6941 } 6942 6943 this.fullPageStyleWidth = this.element.style.width; 6944 this.fullPageStyleHeight = this.element.style.height; 6945 this.element.style.width = '100%'; 6946 this.element.style.height = '100%'; 6947 6948 var onFullScreenChange = function() { 6949 var isFullScreen = $.isFullScreen(); 6950 if ( !isFullScreen ) { 6951 $.removeEvent( document, $.fullScreenEventName, onFullScreenChange ); 6952 $.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange ); 6953 6954 _this.setFullPage( false ); 6955 if ( _this.isFullPage() ) { 6956 _this.element.style.width = _this.fullPageStyleWidth; 6957 _this.element.style.height = _this.fullPageStyleHeight; 6958 } 6959 } 6960 if ( _this.navigator && _this.viewport ) { 6961 _this.navigator.update( _this.viewport ); 6962 } 6963 /** 6964 * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}). 6965 * 6966 * @event full-screen 6967 * @memberof OpenSeadragon.Viewer 6968 * @type {object} 6969 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 6970 * @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode. 6971 * @property {?Object} userData - Arbitrary subscriber-defined object. 6972 */ 6973 _this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } ); 6974 }; 6975 $.addEvent( document, $.fullScreenEventName, onFullScreenChange ); 6976 $.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange ); 6977 6978 $.requestFullScreen( document.body ); 6979 6980 } else { 6981 $.exitFullScreen(); 6982 } 6983 return this; 6984 }, 6985 6986 /** 6987 * @function 6988 * @return {Boolean} 6989 */ 6990 isVisible: function () { 6991 return this.container.style.visibility != "hidden"; 6992 }, 6993 6994 6995 /** 6996 * @function 6997 * @param {Boolean} visible 6998 * @return {OpenSeadragon.Viewer} Chainable. 6999 * @fires OpenSeadragon.Viewer.event:visible 7000 */ 7001 setVisible: function( visible ){ 7002 this.container.style.visibility = visible ? "" : "hidden"; 7003 /** 7004 * Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}). 7005 * 7006 * @event visible 7007 * @memberof OpenSeadragon.Viewer 7008 * @type {object} 7009 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7010 * @property {Boolean} visible
7011 * @property {?Object} userData - Arbitrary subscriber-defined object. 7012 */ 7013 this.raiseEvent( 'visible', { visible: visible } ); 7014 return this; 7015 }, 7016 7017 /** 7018 * Add a layer. 7019 * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open} 7020 * supports except arrays of images as layers cannot be sequences. 7021 * @function 7022 * @param {Object} options 7023 * @param {String|Object|Function} options.tileSource The TileSource of the layer. 7024 * @param {Number} [options.opacity=1] The opacity of the layer. 7025 * @param {Number} [options.level] The level of the layer. Added on top of 7026 * all other layers if not specified. 7027 * @returns {OpenSeadragon.Viewer} Chainable. 7028 * @fires OpenSeadragon.Viewer.event:add-layer 7029 * @fires OpenSeadragon.Viewer.event:add-layer-failed 7030 */ 7031 addLayer: function( options ) { 7032 var _this = this, 7033 tileSource = options.tileSource; 7034 7035 if ( !this.isOpen() ) { 7036 throw new Error( "An image must be loaded before adding layers." ); 7037 } 7038 if ( !tileSource ) { 7039 throw new Error( "No tile source provided as new layer." ); 7040 } 7041 if ( this.collectionMode ) { 7042 throw new Error( "Layers not supported in collection mode." ); 7043 } 7044 7045 function raiseAddLayerFailed( event ) { 7046 /** 7047 * Raised when an error occurs while adding a layer. 7048 * @event add-layer-failed 7049 * @memberOf OpenSeadragon.Viewer 7050 * @type {object} 7051 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7052 * @property {String} message 7053 * @property {String} source 7054 * @property {Object} options The options passed to the addLayer method. 7055 * @property {?Object} userData - Arbitrary subscriber-defined object. 7056 */ 7057 _this.raiseEvent( 'add-layer-failed', event ); 7058 } 7059 7060 getTileSourceImplementation( this, tileSource, function( tileSource ) { 7061 7062 if ( tileSource instanceof Array ) { 7063 raiseAddLayerFailed({ 7064 message: "Sequences can not be added as layers.", 7065 source: tileSource, 7066 options: options 7067 }); 7068 return; 7069 } 7070 7071 for ( var i = 0; i < _this.drawers.length; i++ ) { 7072 var otherAspectRatio = _this.drawers[ i ].source.aspectRatio; 7073 var diff = otherAspectRatio - tileSource.aspectRatio; 7074 if ( Math.abs( diff ) > _this.layersAspectRatioEpsilon ) { 7075 raiseAddLayerFailed({ 7076 message: "Aspect ratio mismatch with layer " + i + ".", 7077 source: tileSource, 7078 options: options 7079 }); 7080 return; 7081 } 7082 } 7083 7084 var drawer = new $.Drawer({ 7085 viewer: _this, 7086 source: tileSource, 7087 viewport: _this.viewport, 7088 element: _this.drawersContainer, 7089 opacity: options.opacity !== undefined ? 7090 options.opacity : _this.opacity, 7091 maxImageCacheCount: _this.maxImageCacheCount, 7092 imageLoaderLimit: _this.imageLoaderLimit, 7093 minZoomImageRatio: _this.minZoomImageRatio, 7094 wrapHorizontal: _this.wrapHorizontal, 7095 wrapVertical: _this.wrapVertical, 7096 immediateRender: _this.immediateRender, 7097 blendTime: _this.blendTime, 7098 alwaysBlend: _this.alwaysBlend, 7099 minPixelRatio: _this.minPixelRatio, 7100 timeout: _this.timeout, 7101 debugMode: _this.debugMode, 7102 debugGridColor: _this.debugGridColor 7103 }); 7104 _this.drawers.push( drawer ); 7105 if ( options.level !== undefined ) { 7106 _this.setLayerLevel( drawer, options.level ); 7107 } 7108 THIS[ _this.hash ].forceRedraw = true; 7109 /** 7110 * Raised when a layer is successfully added. 7111 * @event add-layer 7112 * @memberOf OpenSeadragon.Viewer 7113 * @type {object} 7114 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7115 * @property {Object} options The options passed to the addLayer method. 7116 * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
7117 * @property {?Object} userData - Arbitrary subscriber-defined object. 7118 */ 7119 _this.raiseEvent( 'add-layer', { 7120 options: options, 7121 drawer: drawer 7122 }); 7123 }, function( event ) { 7124 event.options = options; 7125 raiseAddLayerFailed(event); 7126 } ); 7127 7128 return this; 7129 }, 7130 7131 /** 7132 * Get the layer at the specified level. 7133 * @param {Number} level The layer to retrieve level. 7134 * @returns {OpenSeadragon.Drawer} The layer at the specified level. 7135 */ 7136 getLayerAtLevel: function( level ) { 7137 if ( level >= this.drawers.length ) { 7138 throw new Error( "Level bigger than number of layers." ); 7139 } 7140 return this.drawers[ level ]; 7141 }, 7142 7143 /** 7144 * Get the level of the layer associated with the given drawer or -1 if not 7145 * present. 7146 * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer. 7147 * @returns {Number} The level of the layer or -1 if not present. 7148 */ 7149 getLevelOfLayer: function( drawer ) { 7150 return $.indexOf( this.drawers, drawer ); 7151 }, 7152 7153 /** 7154 * Get the number of layers used. 7155 * @returns {Number} The number of layers used. 7156 */ 7157 getLayersCount: function() { 7158 return this.drawers.length; 7159 }, 7160 7161 /** 7162 * Change the level of a layer so that it appears over or under others. 7163 * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing 7164 * level layer. 7165 * @param {Number} level The new level 7166 * @returns {OpenSeadragon.Viewer} Chainable. 7167 * @fires OpenSeadragon.Viewer.event:layer-level-changed 7168 */ 7169 setLayerLevel: function( drawer, level ) { 7170 var oldLevel = this.getLevelOfLayer( drawer ); 7171 7172 if ( level >= this.drawers.length ) { 7173 throw new Error( "Level bigger than number of layers." ); 7174 } 7175 if ( level === oldLevel || oldLevel === -1 ) { 7176 return this; 7177 } 7178 if ( level === 0 || oldLevel === 0 ) { 7179 if ( THIS[ this.hash ].sequenced ) { 7180 throw new Error( "Cannot reassign base level when in sequence mode." ); 7181 } 7182 // We need to re-assign the base drawer and the source 7183 this.drawer = level === 0 ? drawer : this.getLayerAtLevel( level ); 7184 this.source = this.drawer.source; 7185 } 7186 this.drawers.splice( oldLevel, 1 ); 7187 this.drawers.splice( level, 0, drawer ); 7188 this.drawersContainer.removeChild( drawer.canvas ); 7189 if ( level === 0 ) { 7190 var nextLevelCanvas = this.drawers[ 1 ].canvas; 7191 nextLevelCanvas.parentNode.insertBefore( drawer.canvas, 7192 nextLevelCanvas ); 7193 } else { 7194 // Insert right after layer at level - 1 7195 var prevLevelCanvas = this.drawers[level - 1].canvas; 7196 prevLevelCanvas.parentNode.insertBefore( drawer.canvas, 7197 prevLevelCanvas.nextSibling ); 7198 } 7199 7200 /** 7201 * Raised when the order of the layers has been changed. 7202 * @event layer-level-changed 7203 * @memberOf OpenSeadragon.Viewer 7204 * @type {object} 7205 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7206 * @property {OpenSeadragon.Drawer} drawer - The drawer which level has 7207 * been changed 7208 * @property {Number} previousLevel - The previous level of the drawer 7209 * @property {Number} newLevel - The new level of the drawer 7210 * @property {?Object} userData - Arbitrary subscriber-defined object. 7211 */ 7212 this.raiseEvent( 'layer-level-changed', { 7213 drawer: drawer, 7214 previousLevel: oldLevel, 7215 newLevel: level 7216 } ); 7217 7218 return this; 7219 }, 7220 7221 /** 7222 * Remove a layer. If there is only one layer, close the viewer. 7223 * @function 7224 * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer 7225 * to remove 7226 * @returns {OpenSeadragon.Viewer} Chainable. 7227 * @fires OpenSeadragon.Viewer.event:remove-layer 7228 */ 7229 removeLayer: function( drawer ) { 7230 var index = this.drawers.indexOf( drawer ); 7231 if ( index === -1 ) { 7232 return this; 7233 } 7234 if ( index === 0 ) { 7235 if ( THIS[ this.hash ].sequenced ) { 7236 throw new Error( "Cannot remove base layer when in sequence mode." ); 7237 } 7238 if ( this.drawers.length === 1 ) { 7239 this.close(); 7240 return this; 7241 } 7242 this.drawer = this.drawers[ 1 ]; 7243 } 7244 7245 this.drawers.splice( index, 1 ); 7246 this.drawersContainer.removeChild( drawer.canvas ); 7247 /** 7248 * Raised when a layer is removed. 7249 * @event remove-layer 7250 * @memberOf OpenSeadragon.Viewer 7251 * @type {object} 7252 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7253 * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
7254 * @property {?Object} userData - Arbitrary subscriber-defined object. 7255 */ 7256 this.raiseEvent( 'remove-layer', { drawer: drawer } ); 7257 return this; 7258 }, 7259 7260 /** 7261 * Force the viewer to redraw its drawers. 7262 * @returns {OpenSeadragon.Viewer} Chainable. 7263 */ 7264 forceRedraw: function() { 7265 THIS[ this.hash ].forceRedraw = true; 7266 return this; 7267 }, 7268 7269 /** 7270 * @function 7271 * @return {OpenSeadragon.Viewer} Chainable. 7272 */ 7273 bindSequenceControls: function(){ 7274 7275 ////////////////////////////////////////////////////////////////////////// 7276 // Image Sequence Controls 7277 ////////////////////////////////////////////////////////////////////////// 7278 var onFocusHandler = $.delegate( this, onFocus ), 7279 onBlurHandler = $.delegate( this, onBlur ), 7280 onNextHandler = $.delegate( this, onNext ), 7281 onPreviousHandler = $.delegate( this, onPrevious ), 7282 navImages = this.navImages, 7283 useGroup = true ; 7284 7285 if( this.showSequenceControl && THIS[ this.hash ].sequenced ){ 7286 7287 if( this.previousButton || this.nextButton ){ 7288 //if we are binding to custom buttons then layout and 7289 //grouping is the responsibility of the page author 7290 useGroup = false; 7291 } 7292 7293 this.previousButton = new $.Button({ 7294 element: this.previousButton ? $.getElement( this.previousButton ) : null, 7295 clickTimeThreshold: this.clickTimeThreshold, 7296 clickDistThreshold: this.clickDistThreshold, 7297 tooltip: $.getString( "Tooltips.PreviousPage" ), 7298 srcRest: resolveUrl( this.prefixUrl, navImages.previous.REST ), 7299 srcGroup: resolveUrl( this.prefixUrl, navImages.previous.GROUP ), 7300 srcHover: resolveUrl( this.prefixUrl, navImages.previous.HOVER ), 7301 srcDown: resolveUrl( this.prefixUrl, navImages.previous.DOWN ), 7302 onRelease: onPreviousHandler, 7303 onFocus: onFocusHandler, 7304 onBlur: onBlurHandler 7305 }); 7306 7307 this.nextButton = new $.Button({ 7308 element: this.nextButton ? $.getElement( this.nextButton ) : null, 7309 clickTimeThreshold: this.clickTimeThreshold, 7310 clickDistThreshold: this.clickDistThreshold, 7311 tooltip: $.getString( "Tooltips.NextPage" ), 7312 srcRest: resolveUrl( this.prefixUrl, navImages.next.REST ), 7313 srcGroup: resolveUrl( this.prefixUrl, navImages.next.GROUP ), 7314 srcHover: resolveUrl( this.prefixUrl, navImages.next.HOVER ), 7315 srcDown: resolveUrl( this.prefixUrl, navImages.next.DOWN ), 7316 onRelease: onNextHandler, 7317 onFocus: onFocusHandler, 7318 onBlur: onBlurHandler 7319 }); 7320 7321 if( !this.navPrevNextWrap ){ 7322 this.previousButton.disable(); 7323 } 7324 7325 if( useGroup ){ 7326 this.paging = new $.ButtonGroup({ 7327 buttons: [ 7328 this.previousButton, 7329 this.nextButton 7330 ], 7331 clickTimeThreshold: this.clickTimeThreshold, 7332 clickDistThreshold: this.clickDistThreshold 7333 }); 7334 7335 this.pagingControl = this.paging.element; 7336 7337 if( this.toolbar ){ 7338 this.toolbar.addControl( 7339 this.pagingControl, 7340 {anchor: $.ControlAnchor.BOTTOM_RIGHT} 7341 ); 7342 }else{ 7343 this.addControl( 7344 this.pagingControl, 7345 {anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT} 7346 ); 7347 } 7348 } 7349 } 7350 return this; 7351 }, 7352 7353 7354 /** 7355 * @function 7356 * @return {OpenSeadragon.Viewer} Chainable. 7357 */ 7358 bindStandardControls: function(){
7359 ////////////////////////////////////////////////////////////////////////// 7360 // Navigation Controls 7361 ////////////////////////////////////////////////////////////////////////// 7362 var beginZoomingInHandler = $.delegate( this, beginZoomingIn ), 7363 endZoomingHandler = $.delegate( this, endZooming ), 7364 doSingleZoomInHandler = $.delegate( this, doSingleZoomIn ), 7365 beginZoomingOutHandler = $.delegate( this, beginZoomingOut ), 7366 doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ), 7367 onHomeHandler = $.delegate( this, onHome ), 7368 onFullScreenHandler = $.delegate( this, onFullScreen ), 7369 onRotateLeftHandler = $.delegate( this, onRotateLeft ), 7370 onRotateRightHandler = $.delegate( this, onRotateRight ), 7371 onFocusHandler = $.delegate( this, onFocus ), 7372 onBlurHandler = $.delegate( this, onBlur ), 7373 navImages = this.navImages, 7374 buttons = [], 7375 useGroup = true ; 7376 7377 7378 if ( this.showNavigationControl ) { 7379 7380 if( this.zoomInButton || this.zoomOutButton || 7381 this.homeButton || this.fullPageButton || 7382 this.rotateLeftButton || this.rotateRightButton ) { 7383 //if we are binding to custom buttons then layout and 7384 //grouping is the responsibility of the page author 7385 useGroup = false; 7386 } 7387 7388 if ( this.showZoomControl ) { 7389 buttons.push( this.zoomInButton = new $.Button({ 7390 element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null, 7391 clickTimeThreshold: this.clickTimeThreshold, 7392 clickDistThreshold: this.clickDistThreshold, 7393 tooltip: $.getString( "Tooltips.ZoomIn" ), 7394 srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ), 7395 srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ), 7396 srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ), 7397 srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ), 7398 onPress: beginZoomingInHandler, 7399 onRelease: endZoomingHandler, 7400 onClick: doSingleZoomInHandler, 7401 onEnter: beginZoomingInHandler, 7402 onExit: endZoomingHandler, 7403 onFocus: onFocusHandler, 7404 onBlur: onBlurHandler 7405 })); 7406 7407 buttons.push( this.zoomOutButton = new $.Button({ 7408 element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null, 7409 clickTimeThreshold: this.clickTimeThreshold, 7410 clickDistThreshold: this.clickDistThreshold, 7411 tooltip: $.getString( "Tooltips.ZoomOut" ), 7412 srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ), 7413 srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ), 7414 srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ), 7415 srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ), 7416 onPress: beginZoomingOutHandler, 7417 onRelease: endZoomingHandler, 7418 onClick: doSingleZoomOutHandler, 7419 onEnter: beginZoomingOutHandler, 7420 onExit: endZoomingHandler, 7421 onFocus: onFocusHandler, 7422 onBlur: onBlurHandler 7423 })); 7424 } 7425 7426 if ( this.showHomeControl ) { 7427 buttons.push( this.homeButton = new $.Button({ 7428 element: this.homeButton ? $.getElement( this.homeButton ) : null, 7429 clickTimeThreshold: this.clickTimeThreshold, 7430 clickDistThreshold: this.clickDistThreshold, 7431 tooltip: $.getString( "Tooltips.Home" ), 7432 srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ), 7433 srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ), 7434 srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ), 7435 srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ), 7436 onRelease: onHomeHandler, 7437 onFocus: onFocusHandler, 7438 onBlur: onBlurHandler 7439 })); 7440 } 7441
7442 if ( this.showFullPageControl ) { 7443 buttons.push( this.fullPageButton = new $.Button({ 7444 element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null, 7445 clickTimeThreshold: this.clickTimeThreshold, 7446 clickDistThreshold: this.clickDistThreshold, 7447 tooltip: $.getString( "Tooltips.FullPage" ), 7448 srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ), 7449 srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ), 7450 srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ), 7451 srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ), 7452 onRelease: onFullScreenHandler, 7453 onFocus: onFocusHandler, 7454 onBlur: onBlurHandler 7455 })); 7456 } 7457 7458 if ( this.showRotationControl ) { 7459 buttons.push( this.rotateLeftButton = new $.Button({ 7460 element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null, 7461 clickTimeThreshold: this.clickTimeThreshold, 7462 clickDistThreshold: this.clickDistThreshold, 7463 tooltip: $.getString( "Tooltips.RotateLeft" ), 7464 srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ), 7465 srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ), 7466 srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ), 7467 srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ), 7468 onRelease: onRotateLeftHandler, 7469 onFocus: onFocusHandler, 7470 onBlur: onBlurHandler 7471 })); 7472 7473 buttons.push( this.rotateRightButton = new $.Button({ 7474 element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null, 7475 clickTimeThreshold: this.clickTimeThreshold, 7476 clickDistThreshold: this.clickDistThreshold, 7477 tooltip: $.getString( "Tooltips.RotateRight" ), 7478 srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ), 7479 srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ), 7480 srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ), 7481 srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ), 7482 onRelease: onRotateRightHandler, 7483 onFocus: onFocusHandler, 7484 onBlur: onBlurHandler 7485 })); 7486 7487 } 7488 7489 if ( useGroup ) { 7490 this.buttons = new $.ButtonGroup({ 7491 buttons: buttons, 7492 clickTimeThreshold: this.clickTimeThreshold, 7493 clickDistThreshold: this.clickDistThreshold 7494 }); 7495 7496 this.navControl = this.buttons.element; 7497 this.addHandler( 'open', $.delegate( this, lightUp ) ); 7498 7499 if( this.toolbar ){ 7500 this.toolbar.addControl( 7501 this.navControl, 7502 {anchor: $.ControlAnchor.TOP_LEFT} 7503 ); 7504 } else { 7505 this.addControl( 7506 this.navControl, 7507 {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT} 7508 ); 7509 } 7510 } 7511 7512 } 7513 return this; 7514 }, 7515 7516 /** 7517 * Gets the active page of a sequence 7518 * @function 7519 * @return {Number} 7520 */ 7521 currentPage: function() { 7522 return THIS[ this.hash ].sequence; 7523 }, 7524 7525 /** 7526 * @function 7527 * @return {OpenSeadragon.Viewer} Chainable. 7528 * @fires OpenSeadragon.Viewer.event:page 7529 */ 7530 goToPage: function( page ){ 7531 if( page >= 0 && page < this.tileSources.length ){ 7532 /** 7533 * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}). 7534 * 7535 * @event page 7536 * @memberof OpenSeadragon.Viewer 7537 * @type {Object} 7538 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7539 * @property {Number} page - The page index.
7540 * @property {?Object} userData - Arbitrary subscriber-defined object. 7541 */ 7542 this.raiseEvent( 'page', { page: page } ); 7543 7544 THIS[ this.hash ].sequence = page; 7545 7546 this._updateSequenceButtons( page ); 7547 7548 this.open( this.tileSources[ page ] ); 7549 7550 if( this.referenceStrip ){ 7551 this.referenceStrip.setFocus( page ); 7552 } 7553 } 7554 7555 return this; 7556 }, 7557 7558 /** 7559 * Adds an html element as an overlay to the current viewport. Useful for 7560 * highlighting words or areas of interest on an image or other zoomable 7561 * interface. The overlays added via this method are removed when the viewport 7562 * is closed which include when changing page. 7563 * @method 7564 * @param {Element|String|Object} element - A reference to an element or an id for 7565 * the element which will overlayed. Or an Object specifying the configuration for the overlay 7566 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 7567 * rectangle which will be overlayed. 7568 * @param {OpenSeadragon.OverlayPlacement} placement - The position of the 7569 * viewport which the location coordinates will be treated as relative 7570 * to. 7571 * @param {function} onDraw - If supplied the callback is called when the overlay 7572 * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. 7573 * It is passed position, size and element. 7574 * @return {OpenSeadragon.Viewer} Chainable. 7575 * @fires OpenSeadragon.Viewer.event:add-overlay 7576 */ 7577 addOverlay: function( element, location, placement, onDraw ) { 7578 var options; 7579 if( $.isPlainObject( element ) ){ 7580 options = element; 7581 } else { 7582 options = { 7583 element: element, 7584 location: location, 7585 placement: placement, 7586 onDraw: onDraw 7587 }; 7588 } 7589 7590 element = $.getElement( options.element ); 7591 7592 if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) { 7593 // they're trying to add a duplicate overlay 7594 return this; 7595 } 7596 this.currentOverlays.push( getOverlayObject( this, options ) ); 7597 THIS[ this.hash ].forceRedraw = true; 7598 /** 7599 * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}). 7600 * 7601 * @event add-overlay 7602 * @memberof OpenSeadragon.Viewer 7603 * @type {object} 7604 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7605 * @property {Element} element - The overlay element. 7606 * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location 7607 * @property {OpenSeadragon.OverlayPlacement} placement 7608 * @property {?Object} userData - Arbitrary subscriber-defined object. 7609 */ 7610 this.raiseEvent( 'add-overlay', { 7611 element: element, 7612 location: options.location, 7613 placement: options.placement 7614 }); 7615 return this; 7616 }, 7617 7618 /** 7619 * Updates the overlay represented by the reference to the element or 7620 * element id moving it to the new location, relative to the new placement. 7621 * @method 7622 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 7623 * rectangle which will be overlayed. 7624 * @param {OpenSeadragon.OverlayPlacement} placement - The position of the 7625 * viewport which the location coordinates will be treated as relative 7626 * to. 7627 * @return {OpenSeadragon.Viewer} Chainable. 7628 * @fires OpenSeadragon.Viewer.event:update-overlay 7629 */ 7630 updateOverlay: function( element, location, placement ) { 7631 var i; 7632 7633 element = $.getElement( element ); 7634 i = getOverlayIndex( this.currentOverlays, element ); 7635 7636 if ( i >= 0 ) { 7637 this.currentOverlays[ i ].update( location, placement ); 7638 THIS[ this.hash ].forceRedraw = true; 7639 /** 7640 * Raised when an overlay's location or placement changes 7641 * (see {@link OpenSeadragon.Viewer#updateOverlay}). 7642 * 7643 * @event update-overlay 7644 * @memberof OpenSeadragon.Viewer 7645 * @type {object} 7646 * @property {OpenSeadragon.Viewer} eventSource - A reference to the 7647 * Viewer which raised the event. 7648 * @property {Element} element 7649 * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location 7650 * @property {OpenSeadragon.OverlayPlacement} placement
7651 * @property {?Object} userData - Arbitrary subscriber-defined object. 7652 */ 7653 this.raiseEvent( 'update-overlay', { 7654 element: element, 7655 location: location, 7656 placement: placement 7657 }); 7658 } 7659 return this; 7660 }, 7661 7662 /** 7663 * Removes an overlay identified by the reference element or element id 7664 * and schedules an update. 7665 * @method 7666 * @param {Element|String} element - A reference to the element or an 7667 * element id which represent the ovelay content to be removed. 7668 * @return {OpenSeadragon.Viewer} Chainable. 7669 * @fires OpenSeadragon.Viewer.event:remove-overlay 7670 */ 7671 removeOverlay: function( element ) { 7672 var i; 7673 7674 element = $.getElement( element ); 7675 i = getOverlayIndex( this.currentOverlays, element ); 7676 7677 if ( i >= 0 ) { 7678 this.currentOverlays[ i ].destroy(); 7679 this.currentOverlays.splice( i, 1 ); 7680 THIS[ this.hash ].forceRedraw = true; 7681 /** 7682 * Raised when an overlay is removed from the viewer 7683 * (see {@link OpenSeadragon.Viewer#removeOverlay}). 7684 * 7685 * @event remove-overlay 7686 * @memberof OpenSeadragon.Viewer 7687 * @type {object} 7688 * @property {OpenSeadragon.Viewer} eventSource - A reference to the 7689 * Viewer which raised the event. 7690 * @property {Element} element - The overlay element. 7691 * @property {?Object} userData - Arbitrary subscriber-defined object. 7692 */ 7693 this.raiseEvent( 'remove-overlay', { 7694 element: element 7695 }); 7696 } 7697 return this; 7698 }, 7699 7700 /** 7701 * Removes all currently configured Overlays from this Viewer and schedules 7702 * an update. 7703 * @method 7704 * @return {OpenSeadragon.Viewer} Chainable. 7705 * @fires OpenSeadragon.Viewer.event:clear-overlay 7706 */ 7707 clearOverlays: function() { 7708 while ( this.currentOverlays.length > 0 ) { 7709 this.currentOverlays.pop().destroy(); 7710 } 7711 THIS[ this.hash ].forceRedraw = true; 7712 /** 7713 * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}). 7714 * 7715 * @event clear-overlay 7716 * @memberof OpenSeadragon.Viewer 7717 * @type {object} 7718 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 7719 * @property {?Object} userData - Arbitrary subscriber-defined object. 7720 */ 7721 this.raiseEvent( 'clear-overlay', {} ); 7722 return this; 7723 }, 7724 7725 /** 7726 * Updates the sequence buttons. 7727 * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons 7728 * @private 7729 * @param {Number} Sequence Value 7730 */ 7731 _updateSequenceButtons: function( page ) { 7732 7733 if ( this.nextButton ) { 7734 if( ( this.tileSources.length - 1 ) === page ) { 7735 //Disable next button 7736 if ( !this.navPrevNextWrap ) { 7737 this.nextButton.disable(); 7738 } 7739 } else { 7740 this.nextButton.enable(); 7741 } 7742 } 7743 if ( this.previousButton ) { 7744 if ( page > 0 ) { 7745 //Enable previous button 7746 this.previousButton.enable(); 7747 } else { 7748 if ( !this.navPrevNextWrap ) { 7749 this.previousButton.disable(); 7750 } 7751 } 7752 } 7753 }, 7754 7755 /** 7756 * Display a message in the viewport 7757 * @function OpenSeadragon.Viewer.prototype._showMessage 7758 * @private 7759 * @param {String} text message 7760 */ 7761 _showMessage: function ( message ) { 7762 this._hideMessage(); 7763 7764 var div = $.makeNeutralElement( "div" ); 7765 div.appendChild( document.createTextNode( message ) ); 7766 7767 this.messageDiv = $.makeCenteredNode( div ); 7768 7769 $.addClass(this.messageDiv, "openseadragon-message"); 7770 7771 this.container.appendChild( this.messageDiv ); 7772 }, 7773 7774 /** 7775 * Hide any currently displayed viewport message 7776 * @function OpenSeadragon.Viewer.prototype._hideMessage 7777 * @private 7778 */ 7779 _hideMessage: function () { 7780 var div = this.messageDiv; 7781 if (div) { 7782 div.parentNode.removeChild(div); 7783 delete this.messageDiv; 7784 } 7785 }, 7786 7787 /** 7788 * Gets this viewer's gesture settings for the given pointer device type. 7789 * @method 7790 * @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.). 7791 * @return {OpenSeadragon.GestureSettings} 7792 */ 7793 gestureSettingsByDeviceType: function ( type ) { 7794 switch ( type ) { 7795 case 'mouse': 7796 return this.gestureSettingsMouse; 7797 case 'touch': 7798 return this.gestureSettingsTouch; 7799 case 'pen': 7800 return this.gestureSettingsPen; 7801 default: 7802 return this.gestureSettingsUnknown; 7803 } 7804 } 7805 7806}); 7807 7808 7809/** 7810 * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y, 7811 * which was causing some calling operations in updateOnce and openTileSource to 7812 * return NaN. 7813 * @returns {Point} 7814 * @private 7815 */ 7816function _getSafeElemSize (oElement) { 7817 oElement = $.getElement( oElement ); 7818 7819 return new $.Point( 7820 (oElement.clientWidth === 0 ? 1 : oElement.clientWidth), 7821 (oElement.clientHeight === 0 ? 1 : oElement.clientHeight) 7822 ); 7823} 7824 7825/** 7826 * @function 7827 * @private 7828 */ 7829function getTileSourceImplementation( viewer, tileSource, successCallback, 7830 failCallback ) { 7831 var _this = viewer; 7832 7833 //allow plain xml strings or json strings to be parsed here 7834 if ( $.type( tileSource ) == 'string' ) { 7835 if ( tileSource.match( /\s*<.*/ ) ) { 7836 tileSource = $.parseXml( tileSource ); 7837 } else if ( tileSource.match( /\s*[\{\[].*/ ) ) { 7838 /*jshint evil:true*/ 7839 tileSource = eval( '(' + tileSource + ')' ); 7840 } 7841 } 7842 7843 setTimeout( function() { 7844 if ( $.type( tileSource ) == 'string' ) { 7845 //If its still a string it means it must be a url at this point 7846 tileSource = new $.TileSource( tileSource, function( event ) { 7847 successCallback( event.tileSource ); 7848 }); 7849 tileSource.addHandler( 'open-failed', function( event ) { 7850 failCallback( event ); 7851 } ); 7852 7853 } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) { 7854 if ( $.isFunction( tileSource.getTileUrl ) ) { 7855 //Custom tile source 7856 var customTileSource = new $.TileSource( tileSource ); 7857 customTileSource.getTileUrl = tileSource.getTileUrl; 7858 successCallback( customTileSource ); 7859 } else { 7860 //inline configuration 7861 var $TileSource = $.TileSource.determineType( _this, tileSource ); 7862 if ( !$TileSource ) { 7863 failCallback( { 7864 message: "Unable to load TileSource", 7865 source: tileSource 7866 }); 7867 return; 7868 } 7869 var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] ); 7870 var readySource = new $TileSource( options ); 7871 successCallback( readySource ); 7872 } 7873 } else { 7874 //can assume it's already a tile source implementation 7875 successCallback( tileSource ); 7876 } 7877 }, 1 ); 7878} 7879 7880/** 7881 * @function 7882 * @private 7883 */ 7884function openTileSource( viewer, source ) { 7885 var i, 7886 _this = viewer; 7887 7888 if ( _this.source ) { 7889 _this.close( ); 7890 } 7891
7892 THIS[ _this.hash ].prevContainerSize = _getSafeElemSize( _this.container ); 7893 7894 7895 if( _this.collectionMode ){ 7896 _this.source = new $.TileSourceCollection({ 7897 rows: _this.collectionRows, 7898 layout: _this.collectionLayout, 7899 tileSize: _this.collectionTileSize, 7900 tileSources: _this.tileSources, 7901 tileMargin: _this.collectionTileMargin 7902 }); 7903 _this.viewport = _this.viewport ? _this.viewport : new $.Viewport({ 7904 collectionMode: true, 7905 collectionTileSource: _this.source, 7906 containerSize: THIS[ _this.hash ].prevContainerSize, 7907 contentSize: _this.source.dimensions, 7908 springStiffness: _this.springStiffness, 7909 animationTime: _this.animationTime, 7910 showNavigator: false, 7911 minZoomImageRatio: 1, 7912 maxZoomPixelRatio: 1, 7913 viewer: _this, 7914 degrees: _this.degrees //, 7915 //TODO: figure out how to support these in a way that makes sense 7916 //minZoomLevel: this.minZoomLevel, 7917 //maxZoomLevel: this.maxZoomLevel 7918 }); 7919 } else { 7920 if( source ){ 7921 _this.source = source; 7922 } 7923 _this.viewport = _this.viewport ? _this.viewport : new $.Viewport({ 7924 containerSize: THIS[ _this.hash ].prevContainerSize, 7925 contentSize: _this.source.dimensions, 7926 springStiffness: _this.springStiffness, 7927 animationTime: _this.animationTime, 7928 minZoomImageRatio: _this.minZoomImageRatio, 7929 maxZoomPixelRatio: _this.maxZoomPixelRatio, 7930 visibilityRatio: _this.visibilityRatio, 7931 wrapHorizontal: _this.wrapHorizontal, 7932 wrapVertical: _this.wrapVertical, 7933 defaultZoomLevel: _this.defaultZoomLevel, 7934 minZoomLevel: _this.minZoomLevel, 7935 maxZoomLevel: _this.maxZoomLevel, 7936 viewer: _this, 7937 degrees: _this.degrees 7938 }); 7939 } 7940 7941 if( _this.preserveViewport ){ 7942 _this.viewport.resetContentSize( _this.source.dimensions ); 7943 } 7944 7945 _this.source.overlays = _this.source.overlays || []; 7946 7947 _this.drawer = new $.Drawer({ 7948 viewer: _this, 7949 source: _this.source, 7950 viewport: _this.viewport, 7951 element: _this.drawersContainer, 7952 opacity: _this.opacity, 7953 maxImageCacheCount: _this.maxImageCacheCount, 7954 imageLoaderLimit: _this.imageLoaderLimit, 7955 minZoomImageRatio: _this.minZoomImageRatio, 7956 wrapHorizontal: _this.wrapHorizontal, 7957 wrapVertical: _this.wrapVertical, 7958 immediateRender: _this.immediateRender, 7959 blendTime: _this.blendTime, 7960 alwaysBlend: _this.alwaysBlend, 7961 minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio, 7962 timeout: _this.timeout, 7963 debugMode: _this.debugMode, 7964 debugGridColor: _this.debugGridColor, 7965 crossOriginPolicy: _this.crossOriginPolicy 7966 }); 7967 _this.drawers = [_this.drawer]; 7968 7969 // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons 7970 if (!_this.drawer.canRotate()) { 7971 // Disable/remove the rotate left/right buttons since they aren't supported 7972 if (_this.rotateLeft) { 7973 i = _this.buttons.buttons.indexOf(_this.rotateLeft); 7974 _this.buttons.buttons.splice(i, 1); 7975 _this.buttons.element.removeChild(_this.rotateLeft.element); 7976 } 7977 if (_this.rotateRight) { 7978 i = _this.buttons.buttons.indexOf(_this.rotateRight); 7979 _this.buttons.buttons.splice(i, 1); 7980 _this.buttons.element.removeChild(_this.rotateRight.element); 7981 } 7982 } 7983 7984 //Instantiate a navigator if configured 7985 if ( _this.showNavigator && !_this.collectionMode ){ 7986 // Note: By passing the fully parsed source, the navigator doesn't 7987 // have to load it again. 7988 if ( _this.navigator ) { 7989 _this.navigator.open( source ); 7990 } else { 7991 _this.navigator = new $.Navigator({ 7992 id: _this.navigatorId, 7993 position: _this.navigatorPosition, 7994 sizeRatio: _this.navigatorSizeRatio, 7995 maintainSizeRatio: _this.navigatorMaintainSizeRatio, 7996 top: _this.navigatorTop, 7997 left: _this.navigatorLeft, 7998 width: _this.navigatorWidth, 7999 height: _this.navigatorHeight, 8000 autoResize: _this.navigatorAutoResize, 8001 tileSources: source, 8002 tileHost: _this.tileHost, 8003 prefixUrl: _this.prefixUrl, 8004 viewer: _this 8005 }); 8006 } 8007 } 8008 8009 //Instantiate a referencestrip if configured 8010 if ( _this.showReferenceStrip && !_this.referenceStrip ){ 8011 _this.referenceStrip = new $.ReferenceStrip({ 8012 id: _this.referenceStripElement, 8013 position: _this.referenceStripPosition, 8014 sizeRatio: _this.referenceStripSizeRatio, 8015 scroll: _this.referenceStripScroll, 8016 height: _this.referenceStripHeight, 8017 width: _this.referenceStripWidth, 8018 tileSources: _this.tileSources, 8019 tileHost: _this.tileHost, 8020 prefixUrl: _this.prefixUrl, 8021 viewer: _this 8022 }); 8023 } 8024 8025 //this.profiler = new $.Profiler(); 8026 8027 THIS[ _this.hash ].animating = false; 8028 THIS[ _this.hash ].forceRedraw = true; 8029 _this._updateRequestId = scheduleUpdate( _this, updateMulti ); 8030 8031 VIEWERS[ _this.hash ] = _this; 8032 8033 loadOverlays( _this ); 8034 8035 /** 8036 * Raised when the viewer has opened and loaded one or more TileSources. 8037 * 8038 * @event open 8039 * @memberof OpenSeadragon.Viewer 8040 * @type {object} 8041 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 8042 * @property {OpenSeadragon.TileSource} source
8043 * @property {?Object} userData - Arbitrary subscriber-defined object. 8044 */ 8045 _this.raiseEvent( 'open', { source: source } ); 8046 8047 return _this; 8048} 8049 8050function loadOverlays( _this ) { 8051 _this.currentOverlays = []; 8052 for ( var i = 0; i < _this.overlays.length; i++ ) { 8053 _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] ); 8054 } 8055 for ( var j = 0; j < _this.source.overlays.length; j++ ) { 8056 _this.currentOverlays[ i + j ] = 8057 getOverlayObject( _this, _this.source.overlays[ j ] ); 8058 } 8059} 8060 8061function getOverlayObject( viewer, overlay ) { 8062 if ( overlay instanceof $.Overlay ) { 8063 return overlay; 8064 } 8065 8066 var element = null; 8067 if ( overlay.element ) { 8068 element = $.getElement( overlay.element ); 8069 } else { 8070 var id = overlay.id ? 8071 overlay.id : 8072 "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 ); 8073 8074 element = $.getElement( overlay.id ); 8075 if ( !element ) { 8076 element = document.createElement( "a" ); 8077 element.href = "#/overlay/" + id; 8078 } 8079 element.id = id; 8080 $.addClass( element, overlay.className ? 8081 overlay.className : 8082 "openseadragon-overlay" 8083 ); 8084 } 8085 8086 var location = overlay.location; 8087 if ( !location ) { 8088 if ( overlay.width && overlay.height ) { 8089 location = overlay.px !== undefined ? 8090 viewer.viewport.imageToViewportRectangle( new $.Rect( 8091 overlay.px, 8092 overlay.py, 8093 overlay.width, 8094 overlay.height 8095 ) ) : 8096 new $.Rect( 8097 overlay.x, 8098 overlay.y, 8099 overlay.width, 8100 overlay.height 8101 ); 8102 } else { 8103 location = overlay.px !== undefined ? 8104 viewer.viewport.imageToViewportCoordinates( new $.Point( 8105 overlay.px, 8106 overlay.py 8107 ) ) : 8108 new $.Point( 8109 overlay.x, 8110 overlay.y 8111 ); 8112 } 8113 } 8114 8115 var placement = overlay.placement; 8116 if ( placement && ( $.type( placement ) === "string" ) ) { 8117 placement = $.OverlayPlacement[ overlay.placement.toUpperCase() ]; 8118 } 8119 8120 return new $.Overlay({ 8121 element: element, 8122 location: location, 8123 placement: placement, 8124 onDraw: overlay.onDraw, 8125 checkResize: overlay.checkResize 8126 }); 8127} 8128 8129/** 8130 * @private 8131 * @inner 8132 * Determines the index of the given overlay in the given overlays array. 8133 */ 8134function getOverlayIndex( overlays, element ) { 8135 var i; 8136 for ( i = overlays.length - 1; i >= 0; i-- ) { 8137 if ( overlays[ i ].element === element ) { 8138 return i; 8139 } 8140 } 8141 8142 return -1; 8143} 8144 8145function drawOverlays( viewport, overlays, container ) { 8146 var i, 8147 length = overlays.length; 8148 for ( i = 0; i < length; i++ ) { 8149 overlays[ i ].drawHTML( container, viewport ); 8150 } 8151} 8152 8153/////////////////////////////////////////////////////////////////////////////// 8154// Schedulers provide the general engine for animation 8155/////////////////////////////////////////////////////////////////////////////// 8156function scheduleUpdate( viewer, updateFunc ){ 8157 return $.requestAnimationFrame( function(){ 8158 updateFunc( viewer ); 8159 } ); 8160} 8161 8162 8163//provides a sequence in the fade animation 8164function scheduleControlsFade( viewer ) { 8165 $.requestAnimationFrame( function(){ 8166 updateControlsFade( viewer ); 8167 }); 8168} 8169 8170 8171//initiates an animation to hide the controls 8172function beginControlsAutoHide( viewer ) { 8173 if ( !viewer.autoHideControls ) { 8174 return; 8175 } 8176 viewer.controlsShouldFade = true; 8177 viewer.controlsFadeBeginTime = 8178 $.now() + 8179 viewer.controlsFadeDelay; 8180 8181 window.setTimeout( function(){ 8182 scheduleControlsFade( viewer ); 8183 }, viewer.controlsFadeDelay ); 8184} 8185 8186 8187//determines if fade animation is done or continues the animation 8188function updateControlsFade( viewer ) { 8189 var currentTime,
8190 deltaTime, 8191 opacity, 8192 i; 8193 if ( viewer.controlsShouldFade ) { 8194 currentTime = $.now(); 8195 deltaTime = currentTime - viewer.controlsFadeBeginTime; 8196 opacity = 1.0 - deltaTime / viewer.controlsFadeLength; 8197 8198 opacity = Math.min( 1.0, opacity ); 8199 opacity = Math.max( 0.0, opacity ); 8200 8201 for ( i = viewer.controls.length - 1; i >= 0; i--) { 8202 if (viewer.controls[ i ].autoFade) { 8203 viewer.controls[ i ].setOpacity( opacity ); 8204 } 8205 } 8206 8207 if ( opacity > 0 ) { 8208 // fade again 8209 scheduleControlsFade( viewer ); 8210 } 8211 } 8212} 8213 8214 8215//stop the fade animation on the controls and show them 8216function abortControlsAutoHide( viewer ) { 8217 var i; 8218 viewer.controlsShouldFade = false; 8219 for ( i = viewer.controls.length - 1; i >= 0; i-- ) { 8220 viewer.controls[ i ].setOpacity( 1.0 ); 8221 } 8222} 8223 8224 8225 8226/////////////////////////////////////////////////////////////////////////////// 8227// Default view event handlers. 8228/////////////////////////////////////////////////////////////////////////////// 8229function onFocus(){ 8230 abortControlsAutoHide( this ); 8231} 8232 8233function onBlur(){ 8234 beginControlsAutoHide( this ); 8235 8236} 8237 8238function onCanvasClick( event ) { 8239 var gestureSettings; 8240 8241 if ( !event.preventDefaultAction && this.viewport && event.quick ) { 8242 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8243 if ( gestureSettings.clickToZoom ) { 8244 this.viewport.zoomBy( 8245 event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick, 8246 this.viewport.pointFromPixel( event.position, true ) 8247 ); 8248 this.viewport.applyConstraints(); 8249 } 8250 } 8251 /** 8252 * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element. 8253 * 8254 * @event canvas-click 8255 * @memberof OpenSeadragon.Viewer 8256 * @type {object} 8257 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8258 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8259 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8260 * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags. 8261 * @property {Boolean} shift - True if the shift key was pressed during this event. 8262 * @property {Object} originalEvent - The original DOM event. 8263 * @property {?Object} userData - Arbitrary subscriber-defined object. 8264 */ 8265 this.raiseEvent( 'canvas-click', { 8266 tracker: event.eventSource, 8267 position: event.position, 8268 quick: event.quick, 8269 shift: event.shift, 8270 originalEvent: event.originalEvent 8271 }); 8272} 8273 8274function onCanvasDblClick( event ) { 8275 var gestureSettings; 8276 8277 if ( !event.preventDefaultAction && this.viewport ) { 8278 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8279 if ( gestureSettings.dblClickToZoom ) { 8280 this.viewport.zoomBy( 8281 event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick, 8282 this.viewport.pointFromPixel( event.position, true ) 8283 ); 8284 this.viewport.applyConstraints(); 8285 } 8286 } 8287 /** 8288 * Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element. 8289 * 8290 * @event canvas-double-click 8291 * @memberof OpenSeadragon.Viewer 8292 * @type {object} 8293 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8294 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8295 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8296 * @property {Boolean} shift - True if the shift key was pressed during this event. 8297 * @property {Object} originalEvent - The original DOM event.
8298 * @property {?Object} userData - Arbitrary subscriber-defined object. 8299 */ 8300 this.raiseEvent( 'canvas-double-click', { 8301 tracker: event.eventSource, 8302 position: event.position, 8303 shift: event.shift, 8304 originalEvent: event.originalEvent 8305 }); 8306} 8307 8308function onCanvasDrag( event ) { 8309 var gestureSettings; 8310 8311 if ( !event.preventDefaultAction && this.viewport ) { 8312 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8313 if( !this.panHorizontal ){ 8314 event.delta.x = 0; 8315 } 8316 if( !this.panVertical ){ 8317 event.delta.y = 0; 8318 } 8319 this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled ); 8320 if( this.constrainDuringPan ){ 8321 this.viewport.applyConstraints(); 8322 } 8323 } 8324 /** 8325 * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element. 8326 * 8327 * @event canvas-drag 8328 * @memberof OpenSeadragon.Viewer 8329 * @type {object} 8330 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8331 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8332 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8333 * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag. 8334 * @property {Number} speed - Current computed speed, in pixels per second. 8335 * @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. 8336 * @property {Boolean} shift - True if the shift key was pressed during this event. 8337 * @property {Object} originalEvent - The original DOM event. 8338 * @property {?Object} userData - Arbitrary subscriber-defined object. 8339 */ 8340 this.raiseEvent( 'canvas-drag', { 8341 tracker: event.eventSource, 8342 position: event.position, 8343 delta: event.delta, 8344 speed: event.speed, 8345 direction: event.direction, 8346 shift: event.shift, 8347 originalEvent: event.originalEvent 8348 }); 8349} 8350 8351function onCanvasDragEnd( event ) { 8352 var gestureSettings; 8353 8354 if ( !event.preventDefaultAction && this.viewport ) { 8355 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8356 if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) { 8357 var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction ) ), 8358 amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction ) ), 8359 center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ), 8360 target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) ); 8361 if( !this.panHorizontal ) { 8362 target.x = center.x; 8363 } 8364 if( !this.panVertical ) { 8365 target.y = center.y; 8366 } 8367 this.viewport.panTo( target, false ); 8368 this.viewport.applyConstraints(); 8369 } 8370 } 8371 /** 8372 * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element. 8373 * 8374 * @event canvas-drag-end 8375 * @memberof OpenSeadragon.Viewer 8376 * @type {object} 8377 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8378 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8379 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8380 * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second. 8381 * @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. 8382 * @property {Boolean} shift - True if the shift key was pressed during this event. 8383 * @property {Object} originalEvent - The original DOM event.
8384 * @property {?Object} userData - Arbitrary subscriber-defined object. 8385 */ 8386 this.raiseEvent( 'canvas-drag-end', { 8387 tracker: event.eventSource, 8388 position: event.position, 8389 speed: event.speed, 8390 direction: event.direction, 8391 shift: event.shift, 8392 originalEvent: event.originalEvent 8393 }); 8394} 8395 8396function onCanvasRelease( event ) { 8397 var gestureSettings; 8398 8399 if ( event.insideElementPressed && this.viewport ) { 8400 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8401 8402 if ( !gestureSettings.flickEnabled ) { 8403 this.viewport.applyConstraints(); 8404 } 8405 } 8406 /** 8407 * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element. 8408 * 8409 * @event canvas-release 8410 * @memberof OpenSeadragon.Viewer 8411 * @type {object} 8412 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8413 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8414 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8415 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 8416 * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. 8417 * @property {Object} originalEvent - The original DOM event. 8418 * @property {?Object} userData - Arbitrary subscriber-defined object. 8419 */ 8420 this.raiseEvent( 'canvas-release', { 8421 tracker: event.eventSource, 8422 position: event.position, 8423 insideElementPressed: event.insideElementPressed, 8424 insideElementReleased: event.insideElementReleased, 8425 originalEvent: event.originalEvent 8426 }); 8427} 8428 8429function onCanvasPinch( event ) { 8430 var gestureSettings, 8431 centerPt, 8432 lastCenterPt, 8433 panByPt; 8434 8435 if ( !event.preventDefaultAction && this.viewport ) { 8436 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8437 if ( gestureSettings.pinchToZoom ) { 8438 centerPt = this.viewport.pointFromPixel( event.center, true ); 8439 lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true ); 8440 panByPt = lastCenterPt.minus( centerPt ); 8441 if( !this.panHorizontal ) { 8442 panByPt.x = 0; 8443 } 8444 if( !this.panVertical ) { 8445 panByPt.y = 0; 8446 } 8447 this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true ); 8448 this.viewport.panBy( panByPt, true ); 8449 this.viewport.applyConstraints(); 8450 } 8451 } 8452 /** 8453 * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element. 8454 * 8455 * @event canvas-pinch 8456 * @memberof OpenSeadragon.Viewer 8457 * @type {object} 8458 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8459 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8460 * @property {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here. 8461 * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element. 8462 * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element. 8463 * @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels. 8464 * @property {Number} distance - The distance between the two pinch contact points in CSS pixels. 8465 * @property {Boolean} shift - True if the shift key was pressed during this event. 8466 * @property {Object} originalEvent - The original DOM event.
8467 * @property {?Object} userData - Arbitrary subscriber-defined object. 8468 */ 8469 this.raiseEvent('canvas-pinch', { 8470 tracker: event.eventSource, 8471 gesturePoints: event.gesturePoints, 8472 lastCenter: event.lastCenter, 8473 center: event.center, 8474 lastDistance: event.lastDistance, 8475 distance: event.distance, 8476 shift: event.shift, 8477 originalEvent: event.originalEvent 8478 }); 8479 //cancels event 8480 return false; 8481} 8482 8483function onCanvasScroll( event ) { 8484 var gestureSettings, 8485 factor; 8486 8487 if ( !event.preventDefaultAction && this.viewport ) { 8488 gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); 8489 if ( gestureSettings.scrollToZoom ) { 8490 factor = Math.pow( this.zoomPerScroll, event.scroll ); 8491 this.viewport.zoomBy( 8492 factor, 8493 this.viewport.pointFromPixel( event.position, true ) 8494 ); 8495 this.viewport.applyConstraints(); 8496 } 8497 } 8498 /** 8499 * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel). 8500 * 8501 * @event canvas-scroll 8502 * @memberof OpenSeadragon.Viewer 8503 * @type {object} 8504 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8505 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8506 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8507 * @property {Number} scroll - The scroll delta for the event. 8508 * @property {Boolean} shift - True if the shift key was pressed during this event. 8509 * @property {Object} originalEvent - The original DOM event. 8510 * @property {?Object} userData - Arbitrary subscriber-defined object. 8511 */ 8512 this.raiseEvent( 'canvas-scroll', { 8513 tracker: event.eventSource, 8514 position: event.position, 8515 scroll: event.scroll, 8516 shift: event.shift, 8517 originalEvent: event.originalEvent 8518 }); 8519 //cancels event 8520 return false; 8521} 8522 8523function onContainerExit( event ) { 8524 if ( !event.insideElementPressed ) { 8525 THIS[ this.hash ].mouseInside = false; 8526 if ( !THIS[ this.hash ].animating ) { 8527 beginControlsAutoHide( this ); 8528 } 8529 } 8530 /** 8531 * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element. 8532 * 8533 * @event container-exit 8534 * @memberof OpenSeadragon.Viewer 8535 * @type {object} 8536 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8537 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8538 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8539 * @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. 8540 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 8541 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 8542 * @property {Object} originalEvent - The original DOM event. 8543 * @property {?Object} userData - Arbitrary subscriber-defined object. 8544 */ 8545 this.raiseEvent( 'container-exit', { 8546 tracker: event.eventSource, 8547 position: event.position, 8548 buttons: event.buttons, 8549 insideElementPressed: event.insideElementPressed, 8550 buttonDownAny: event.buttonDownAny, 8551 originalEvent: event.originalEvent 8552 }); 8553} 8554 8555function onContainerPress( event ) { 8556 if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { 8557 THIS[ this.hash ].mouseInside = true; 8558 abortControlsAutoHide( this ); 8559 } 8560} 8561 8562function onContainerRelease( event ) { 8563 if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { 8564 THIS[ this.hash ].mouseInside = false; 8565 if ( !THIS[ this.hash ].animating ) { 8566 beginControlsAutoHide( this ); 8567 } 8568 } 8569 /** 8570 * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element. 8571 * 8572 * @event container-release 8573 * @memberof OpenSeadragon.Viewer 8574 * @type {object} 8575 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8576 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8577 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8578 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 8579 * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. 8580 * @property {Object} originalEvent - The original DOM event.
8581 * @property {?Object} userData - Arbitrary subscriber-defined object. 8582 */ 8583 this.raiseEvent( 'container-release', { 8584 tracker: event.eventSource, 8585 position: event.position, 8586 insideElementPressed: event.insideElementPressed, 8587 insideElementReleased: event.insideElementReleased, 8588 originalEvent: event.originalEvent 8589 }); 8590} 8591 8592function onContainerEnter( event ) { 8593 THIS[ this.hash ].mouseInside = true; 8594 abortControlsAutoHide( this ); 8595 /** 8596 * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element. 8597 * 8598 * @event container-enter 8599 * @memberof OpenSeadragon.Viewer 8600 * @type {object} 8601 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8602 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 8603 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 8604 * @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. 8605 * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. 8606 * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span> 8607 * @property {Object} originalEvent - The original DOM event. 8608 * @property {?Object} userData - Arbitrary subscriber-defined object. 8609 */ 8610 this.raiseEvent( 'container-enter', { 8611 tracker: event.eventSource, 8612 position: event.position, 8613 buttons: event.buttons, 8614 insideElementPressed: event.insideElementPressed, 8615 buttonDownAny: event.buttonDownAny, 8616 originalEvent: event.originalEvent 8617 }); 8618} 8619 8620 8621/////////////////////////////////////////////////////////////////////////////// 8622// Page update routines ( aka Views - for future reference ) 8623/////////////////////////////////////////////////////////////////////////////// 8624 8625function updateMulti( viewer ) { 8626 if ( !viewer.source ) { 8627 viewer._updateRequestId = null; 8628 return; 8629 } 8630 8631 updateOnce( viewer ); 8632 8633 // Request the next frame, unless we've been closed during the updateOnce() 8634 if ( viewer.source ) { 8635 viewer._updateRequestId = scheduleUpdate( viewer, updateMulti ); 8636 } 8637} 8638 8639function updateOnce( viewer ) { 8640 8641 var containerSize, 8642 animated; 8643 8644 if ( !viewer.source ) { 8645 return; 8646 } 8647 8648 //viewer.profiler.beginUpdate(); 8649 8650 if ( viewer.autoResize ) { 8651 containerSize = _getSafeElemSize( viewer.container ); 8652 if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) { 8653 // maintain image position 8654 var oldBounds = viewer.viewport.getBounds(); 8655 var oldCenter = viewer.viewport.getCenter(); 8656 resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter); 8657 THIS[ viewer.hash ].prevContainerSize = containerSize; 8658 THIS[ viewer.hash ].forceRedraw = true; 8659 } 8660 } 8661 8662 animated = viewer.viewport.update(); 8663 8664 if( viewer.referenceStrip ){ 8665 animated = viewer.referenceStrip.update( viewer.viewport ) || animated; 8666 } 8667 8668 if ( !THIS[ viewer.hash ].animating && animated ) { 8669 /** 8670 * Raised when any spring animation starts (zoom, pan, etc.). 8671 * 8672 * @event animation-start 8673 * @memberof OpenSeadragon.Viewer 8674 * @type {object} 8675 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8676 * @property {?Object} userData - Arbitrary subscriber-defined object. 8677 */ 8678 viewer.raiseEvent( "animation-start" ); 8679 abortControlsAutoHide( viewer ); 8680 } 8681 8682 if ( animated ) { 8683 updateDrawers( viewer ); 8684 drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer ); 8685 if( viewer.navigator ){ 8686 viewer.navigator.update( viewer.viewport ); 8687 } 8688 /** 8689 * Raised when any spring animation update occurs (zoom, pan, etc.). 8690 * 8691 * @event animation 8692 * @memberof OpenSeadragon.Viewer 8693 * @type {object} 8694 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8695 * @property {?Object} userData - Arbitrary subscriber-defined object. 8696 */ 8697 viewer.raiseEvent( "animation" ); 8698 } else if ( THIS[ viewer.hash ].forceRedraw || drawersNeedUpdate( viewer ) ) { 8699 updateDrawers( viewer ); 8700 drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer ); 8701 if( viewer.navigator ){ 8702 viewer.navigator.update( viewer.viewport ); 8703 } 8704 THIS[ viewer.hash ].forceRedraw = false; 8705 } 8706 8707 if ( THIS[ viewer.hash ].animating && !animated ) { 8708 /** 8709 * Raised when any spring animation ends (zoom, pan, etc.). 8710 * 8711 * @event animation-finish 8712 * @memberof OpenSeadragon.Viewer 8713 * @type {object} 8714 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 8715 * @property {?Object} userData - Arbitrary subscriber-defined object. 8716 */ 8717 viewer.raiseEvent( "animation-finish" ); 8718 8719 if ( !THIS[ viewer.hash ].mouseInside ) { 8720 beginControlsAutoHide( viewer ); 8721 } 8722 } 8723 8724 THIS[ viewer.hash ].animating = animated; 8725 8726 //viewer.profiler.endUpdate(); 8727} 8728 8729// This function resizes the viewport and recenters the image 8730// as it was before resizing. 8731// TODO: better adjust width and height. The new width and height 8732// should depend on the image dimensions and on the dimensions 8733// of the viewport before and after switching mode. 8734function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter ) { 8735 var viewport = viewer.viewport; 8736 8737 viewport.resize( containerSize, true ); 8738 8739 // We try to remove blanks as much as possible 8740 var imageHeight = 1 / viewer.source.aspectRatio; 8741 var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1; 8742 var newHeight = oldBounds.height <= imageHeight ? 8743 oldBounds.height : imageHeight; 8744 8745 var newBounds = new $.Rect( 8746 oldCenter.x - ( newWidth / 2.0 ), 8747 oldCenter.y - ( newHeight / 2.0 ), 8748 newWidth, 8749 newHeight 8750 ); 8751 viewport.fitBounds( newBounds, true ); 8752} 8753 8754function updateDrawers( viewer ) { 8755 for (var i = 0; i < viewer.drawers.length; i++ ) { 8756 viewer.drawers[i].update(); 8757 } 8758} 8759 8760function drawersNeedUpdate( viewer ) { 8761 for (var i = 0; i < viewer.drawers.length; i++ ) { 8762 if (viewer.drawers[i].needsUpdate()) { 8763 return true; 8764 } 8765 } 8766 return false; 8767} 8768 8769/////////////////////////////////////////////////////////////////////////////// 8770// Navigation Controls 8771/////////////////////////////////////////////////////////////////////////////// 8772function resolveUrl( prefix, url ) { 8773 return prefix ? prefix + url : url; 8774} 8775 8776 8777 8778function beginZoomingIn() { 8779 THIS[ this.hash ].lastZoomTime = $.now(); 8780 THIS[ this.hash ].zoomFactor = this.zoomPerSecond; 8781 THIS[ this.hash ].zooming = true; 8782 scheduleZoom( this ); 8783} 8784 8785 8786function beginZoomingOut() { 8787 THIS[ this.hash ].lastZoomTime = $.now(); 8788 THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond; 8789 THIS[ this.hash ].zooming = true; 8790 scheduleZoom( this ); 8791} 8792 8793 8794function endZooming() { 8795 THIS[ this.hash ].zooming = false; 8796} 8797 8798 8799function scheduleZoom( viewer ) { 8800 $.requestAnimationFrame( $.delegate( viewer, doZoom ) ); 8801} 8802 8803 8804function doZoom() { 8805 var currentTime, 8806 deltaTime, 8807 adjustedFactor; 8808 8809 if ( THIS[ this.hash ].zooming && this.viewport) { 8810 currentTime = $.now(); 8811 deltaTime = currentTime - THIS[ this.hash ].lastZoomTime; 8812 adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 ); 8813 8814 this.viewport.zoomBy( adjustedFactor ); 8815 this.viewport.applyConstraints(); 8816 THIS[ this.hash ].lastZoomTime = currentTime; 8817 scheduleZoom( this ); 8818 } 8819} 8820 8821 8822function doSingleZoomIn() { 8823 if ( this.viewport ) { 8824 THIS[ this.hash ].zooming = false; 8825 this.viewport.zoomBy( 8826 this.zoomPerClick / 1.0 8827 ); 8828 this.viewport.applyConstraints();
8829 } 8830} 8831 8832 8833function doSingleZoomOut() { 8834 if ( this.viewport ) { 8835 THIS[ this.hash ].zooming = false; 8836 this.viewport.zoomBy( 8837 1.0 / this.zoomPerClick 8838 ); 8839 this.viewport.applyConstraints(); 8840 } 8841} 8842 8843 8844function lightUp() { 8845 this.buttons.emulateEnter(); 8846 this.buttons.emulateExit(); 8847} 8848 8849 8850function onHome() { 8851 if ( this.viewport ) { 8852 this.viewport.goHome(); 8853 } 8854} 8855 8856 8857function onFullScreen() { 8858 if ( this.isFullPage() && !$.isFullScreen() ) { 8859 // Is fullPage but not fullScreen 8860 this.setFullPage( false ); 8861 } else { 8862 this.setFullScreen( !this.isFullPage() ); 8863 } 8864 // correct for no mouseout event on change 8865 if ( this.buttons ) { 8866 this.buttons.emulateExit(); 8867 } 8868 this.fullPageButton.element.focus(); 8869 if ( this.viewport ) { 8870 this.viewport.applyConstraints(); 8871 } 8872} 8873 8874/** 8875 * Note: The current rotation feature is limited to 90 degree turns. 8876 */ 8877function onRotateLeft() { 8878 if ( this.viewport ) { 8879 var currRotation = this.viewport.getRotation(); 8880 if (currRotation === 0) { 8881 currRotation = 270; 8882 } 8883 else { 8884 currRotation -= 90; 8885 } 8886 this.viewport.setRotation(currRotation); 8887 } 8888} 8889 8890/** 8891 * Note: The current rotation feature is limited to 90 degree turns. 8892 */ 8893function onRotateRight() { 8894 if ( this.viewport ) { 8895 var currRotation = this.viewport.getRotation(); 8896 if (currRotation === 270) { 8897 currRotation = 0; 8898 } 8899 else { 8900 currRotation += 90; 8901 } 8902 this.viewport.setRotation(currRotation); 8903 } 8904} 8905 8906 8907function onPrevious(){ 8908 var previous = THIS[ this.hash ].sequence - 1; 8909 if(this.navPrevNextWrap && previous < 0){ 8910 previous += this.tileSources.length; 8911 } 8912 this.goToPage( previous ); 8913} 8914 8915 8916function onNext(){ 8917 var next = THIS[ this.hash ].sequence + 1; 8918 if(this.navPrevNextWrap && next >= this.tileSources.length){ 8919 next = 0; 8920 } 8921 this.goToPage( next ); 8922} 8923 8924 8925}( OpenSeadragon )); 8926 8927/* 8928 * OpenSeadragon - Navigator 8929 * 8930 * Copyright (C) 2009 CodePlex Foundation 8931 * Copyright (C) 2010-2013 OpenSeadragon contributors 8932 * 8933 * Redistribution and use in source and binary forms, with or without 8934 * modification, are permitted provided that the following conditions are 8935 * met: 8936 * 8937 * - Redistributions of source code must retain the above copyright notice, 8938 * this list of conditions and the following disclaimer. 8939 * 8940 * - Redistributions in binary form must reproduce the above copyright 8941 * notice, this list of conditions and the following disclaimer in the 8942 * documentation and/or other materials provided with the distribution. 8943 * 8944 * - Neither the name of CodePlex Foundation nor the names of its 8945 * contributors may be used to endorse or promote products derived from 8946 * this software without specific prior written permission. 8947 * 8948 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 8949 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 8950 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 8951 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 8952 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 8953 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 8954 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 8955 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 8956 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 8957 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 8958 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8959 */ 8960 8961(function( $ ){ 8962 8963/** 8964 * @class Navigator 8965 * @classdesc The Navigator provides a small view of the current image as fixed 8966 * while representing the viewport as a moving box serving as a frame 8967 * of reference in the larger viewport as to which portion of the image 8968 * is currently being examined. The navigator's viewport can be interacted 8969 * with using the keyboard or the mouse. 8970 * 8971 * @memberof OpenSeadragon 8972 * @extends OpenSeadragon.Viewer 8973 * @extends OpenSeadragon.EventSource 8974 * @param {Object} options 8975 */ 8976$.Navigator = function( options ){ 8977 8978 var viewer = options.viewer,
8979 viewerSize, 8980 navigatorSize, 8981 unneededElement; 8982 8983 //We may need to create a new element and id if they did not 8984 //provide the id for the existing element 8985 if( !options.id ){ 8986 options.id = 'navigator-' + $.now(); 8987 this.element = $.makeNeutralElement( "div" ); 8988 options.controlOptions = { 8989 anchor: $.ControlAnchor.TOP_RIGHT, 8990 attachToViewer: true, 8991 autoFade: true 8992 }; 8993 8994 if( options.position ){ 8995 if( 'BOTTOM_RIGHT' == options.position ){ 8996 options.controlOptions.anchor = $.ControlAnchor.BOTTOM_RIGHT; 8997 } else if( 'BOTTOM_LEFT' == options.position ){ 8998 options.controlOptions.anchor = $.ControlAnchor.BOTTOM_LEFT; 8999 } else if( 'TOP_RIGHT' == options.position ){ 9000 options.controlOptions.anchor = $.ControlAnchor.TOP_RIGHT; 9001 } else if( 'TOP_LEFT' == options.position ){ 9002 options.controlOptions.anchor = $.ControlAnchor.TOP_LEFT; 9003 } else if( 'ABSOLUTE' == options.position ){ 9004 options.controlOptions.anchor = $.ControlAnchor.ABSOLUTE; 9005 options.controlOptions.top = options.top; 9006 options.controlOptions.left = options.left; 9007 options.controlOptions.height = options.height; 9008 options.controlOptions.width = options.width; 9009 } 9010 } 9011 9012 } else { 9013 this.element = document.getElementById( options.id ); 9014 options.controlOptions = { 9015 anchor: $.ControlAnchor.NONE, 9016 attachToViewer: false, 9017 autoFade: false 9018 }; 9019 } 9020 this.element.id = options.id; 9021 this.element.className += ' navigator'; 9022 9023 options = $.extend( true, { 9024 sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio 9025 }, options, { 9026 element: this.element, 9027 //These need to be overridden to prevent recursion since 9028 //the navigator is a viewer and a viewer has a navigator 9029 showNavigator: false, 9030 mouseNavEnabled: false, 9031 showNavigationControl: false, 9032 showSequenceControl: false, 9033 immediateRender: true, 9034 blendTime: 0, 9035 animationTime: 0, 9036 autoResize: options.autoResize 9037 }); 9038 9039 options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio; 9040 9041 this.borderWidth = 2; 9042 //At some browser magnification levels the display regions lines up correctly, but at some there appears to 9043 //be a one pixel gap. 9044 this.fudge = new $.Point(1, 1); 9045 this.totalBorderWidths = new $.Point(this.borderWidth*2, this.borderWidth*2).minus(this.fudge); 9046 9047 9048 if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) { 9049 (function( style, borderWidth ){ 9050 style.margin = '0px'; 9051 style.border = borderWidth + 'px solid #555'; 9052 style.padding = '0px'; 9053 style.background = '#000'; 9054 style.opacity = 0.8; 9055 style.overflow = 'hidden'; 9056 }( this.element.style, this.borderWidth)); 9057 } 9058 9059 this.displayRegion = $.makeNeutralElement( "div" ); 9060 this.displayRegion.id = this.element.id + '-displayregion'; 9061 this.displayRegion.className = 'displayregion'; 9062 9063 (function( style, borderWidth ){ 9064 style.position = 'relative'; 9065 style.top = '0px'; 9066 style.left = '0px'; 9067 style.fontSize = '0px'; 9068 style.overflow = 'hidden'; 9069 style.border = borderWidth + 'px solid #900'; 9070 style.margin = '0px'; 9071 style.padding = '0px'; 9072 //TODO: IE doesnt like this property being set 9073 //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/} 9074 9075 style.background = 'transparent'; 9076 9077 // We use square bracket notation on the statement below, because float is a keyword. 9078 // This is important for the Google Closure compiler, if nothing else. 9079 /*jshint sub:true */ 9080 style['float'] = 'left'; //Webkit 9081 9082 style.cssFloat = 'left'; //Firefox 9083 style.styleFloat = 'left'; //IE 9084 style.zIndex = 999999999; 9085 style.cursor = 'default'; 9086 }( this.displayRegion.style, this.borderWidth )); 9087 9088 9089 this.element.innerTracker = new $.MouseTracker({ 9090 element: this.element, 9091 dragHandler: $.delegate( this, onCanvasDrag ), 9092 clickHandler: $.delegate( this, onCanvasClick ), 9093 releaseHandler: $.delegate( this, onCanvasRelease ), 9094 scrollHandler: $.delegate( this, onCanvasScroll ) 9095 }).setTracking( true ); 9096 9097 /*this.displayRegion.outerTracker = new $.MouseTracker({ 9098 element: this.container, 9099 clickTimeThreshold: this.clickTimeThreshold, 9100 clickDistThreshold: this.clickDistThreshold, 9101 enterHandler: $.delegate( this, onContainerEnter ), 9102 exitHandler: $.delegate( this, onContainerExit ), 9103 releaseHandler: $.delegate( this, onContainerRelease ) 9104 }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/ 9105 9106 9107 viewer.addControl( 9108 this.element, 9109 options.controlOptions 9110 ); 9111 9112 if ( options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE && options.controlOptions.anchor != $.ControlAnchor.NONE ) { 9113 if ( options.width && options.height ) { 9114 this.element.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height; 9115 this.element.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width; 9116 } else {
9117 viewerSize = $.getElementSize( viewer.element ); 9118 this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px'; 9119 this.element.style.width = Math.round( viewerSize.x * options.sizeRatio ) + 'px'; 9120 this.oldViewerSize = viewerSize; 9121 } 9122 navigatorSize = $.getElementSize( this.element ); 9123 this.elementArea = navigatorSize.x * navigatorSize.y; 9124 } 9125 9126 this.oldContainerSize = new $.Point( 0, 0 ); 9127 9128 $.Viewer.apply( this, [ options ] ); 9129 9130 this.element.getElementsByTagName( 'div' )[0].appendChild( this.displayRegion ); 9131 unneededElement = this.element.getElementsByTagName('textarea')[0]; 9132 if (unneededElement) { 9133 unneededElement.parentNode.removeChild(unneededElement); 9134 } 9135 9136}; 9137 9138$.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{ 9139 9140 /** 9141 * Used to notify the navigator when its size has changed. 9142 * Especially useful when {@link OpenSeadragon.Options}.navigatorAutoResize is set to false and the navigator is resizable. 9143 * @function 9144 */ 9145 updateSize: function () { 9146 if ( this.viewport ) { 9147 var containerSize = new $.Point( 9148 (this.container.clientWidth === 0 ? 1 : this.container.clientWidth), 9149 (this.container.clientHeight === 0 ? 1 : this.container.clientHeight) 9150 ); 9151 if ( !containerSize.equals( this.oldContainerSize ) ) { 9152 var oldBounds = this.viewport.getBounds(); 9153 var oldCenter = this.viewport.getCenter(); 9154 this.viewport.resize( containerSize, true ); 9155 var imageHeight = 1 / this.source.aspectRatio; 9156 var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1; 9157 var newHeight = oldBounds.height <= imageHeight ? 9158 oldBounds.height : imageHeight; 9159 var newBounds = new $.Rect( 9160 oldCenter.x - ( newWidth / 2.0 ), 9161 oldCenter.y - ( newHeight / 2.0 ), 9162 newWidth, 9163 newHeight 9164 ); 9165 this.viewport.fitBounds( newBounds, true ); 9166 this.oldContainerSize = containerSize; 9167 this.drawer.update(); 9168 } 9169 } 9170 }, 9171 9172 /** 9173 * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs. 9174 * @function 9175 * @param {OpenSeadragon.Viewport} The viewport this navigator is tracking. 9176 */ 9177 update: function( viewport ) { 9178 9179 var viewerSize, 9180 newWidth, 9181 newHeight, 9182 bounds, 9183 topleft, 9184 bottomright; 9185
9186 viewerSize = $.getElementSize( this.viewer.element ); 9187 if ( !viewerSize.equals( this.oldViewerSize ) ) { 9188 this.oldViewerSize = viewerSize; 9189 if ( this.maintainSizeRatio ) { 9190 newWidth = viewerSize.x * this.sizeRatio; 9191 newHeight = viewerSize.y * this.sizeRatio; 9192 } 9193 else { 9194 newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y)); 9195 newHeight = this.elementArea / newWidth; 9196 } 9197 this.element.style.width = Math.round( newWidth ) + 'px'; 9198 this.element.style.height = Math.round( newHeight ) + 'px'; 9199 this.updateSize(); 9200 } 9201 9202 if( viewport && this.viewport ) { 9203 bounds = viewport.getBounds( true ); 9204 topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false ); 9205 bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false ).minus( this.totalBorderWidths ); 9206 9207 //update style for navigator-box 9208 (function(style) { 9209 9210 style.top = Math.round( topleft.y ) + 'px'; 9211 style.left = Math.round( topleft.x ) + 'px'; 9212 9213 var width = Math.abs( topleft.x - bottomright.x ); 9214 var height = Math.abs( topleft.y - bottomright.y ); 9215 // make sure width and height are non-negative so IE doesn't throw 9216 style.width = Math.round( Math.max( width, 0 ) ) + 'px'; 9217 style.height = Math.round( Math.max( height, 0 ) ) + 'px'; 9218 9219 }( this.displayRegion.style )); 9220 } 9221 9222 }, 9223 9224 open: function( source ) { 9225 this.updateSize(); 9226 var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio ); 9227 if( source.tileSize > containerSize.x || 9228 source.tileSize > containerSize.y ){ 9229 this.minPixelRatio = Math.min( 9230 containerSize.x, 9231 containerSize.y 9232 ) / source.tileSize; 9233 } else { 9234 this.minPixelRatio = this.viewer.minPixelRatio; 9235 } 9236 return $.Viewer.prototype.open.apply( this, [ source ] ); 9237 } 9238 9239}); 9240 9241/** 9242 * @private 9243 * @inner 9244 * @function 9245 */ 9246function onCanvasClick( event ) { 9247 var newBounds, 9248 viewerPosition, 9249 dimensions; 9250 if (! this.drag) { 9251 if ( this.viewer.viewport ) { 9252 this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ) ); 9253 this.viewer.viewport.applyConstraints(); 9254 } 9255 } 9256 else { 9257 this.drag = false; 9258 } 9259} 9260 9261/** 9262 * @private 9263 * @inner 9264 * @function 9265 */ 9266function onCanvasDrag( event ) { 9267 if ( this.viewer.viewport ) { 9268 this.drag = true; 9269 if( !this.panHorizontal ){ 9270 event.delta.x = 0; 9271 } 9272 if( !this.panVertical ){ 9273 event.delta.y = 0; 9274 } 9275 this.viewer.viewport.panBy( 9276 this.viewport.deltaPointsFromPixels( 9277 event.delta 9278 ) 9279 ); 9280 } 9281} 9282 9283 9284/** 9285 * @private 9286 * @inner 9287 * @function 9288 */ 9289function onCanvasRelease( event ) { 9290 if ( event.insideElementPressed && this.viewer.viewport ) { 9291 this.viewer.viewport.applyConstraints(); 9292 } 9293} 9294 9295 9296/** 9297 * @private 9298 * @inner 9299 * @function 9300 */ 9301function onCanvasScroll( event ) { 9302 /** 9303 * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#navigator} element (mouse wheel, touch pinch, etc.). 9304 * 9305 * @event navigator-scroll 9306 * @memberof OpenSeadragon.Viewer 9307 * @type {object} 9308 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 9309 * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. 9310 * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. 9311 * @property {Number} scroll - The scroll delta for the event. 9312 * @property {Boolean} shift - True if the shift key was pressed during this event. 9313 * @property {Object} originalEvent - The original DOM event.
9314 * @property {?Object} userData - Arbitrary subscriber-defined object. 9315 */ 9316 this.viewer.raiseEvent( 'navigator-scroll', { 9317 tracker: event.eventSource, 9318 position: event.position, 9319 scroll: event.scroll, 9320 shift: event.shift, 9321 originalEvent: event.originalEvent 9322 }); 9323 9324 //dont scroll the page up and down if the user is scrolling 9325 //in the navigator 9326 return false; 9327} 9328 9329 9330}( OpenSeadragon )); 9331 9332/* 9333 * OpenSeadragon - getString/setString 9334 * 9335 * Copyright (C) 2009 CodePlex Foundation 9336 * Copyright (C) 2010-2013 OpenSeadragon contributors 9337 * 9338 * Redistribution and use in source and binary forms, with or without 9339 * modification, are permitted provided that the following conditions are 9340 * met: 9341 * 9342 * - Redistributions of source code must retain the above copyright notice, 9343 * this list of conditions and the following disclaimer. 9344 * 9345 * - Redistributions in binary form must reproduce the above copyright 9346 * notice, this list of conditions and the following disclaimer in the 9347 * documentation and/or other materials provided with the distribution. 9348 * 9349 * - Neither the name of CodePlex Foundation nor the names of its 9350 * contributors may be used to endorse or promote products derived from 9351 * this software without specific prior written permission. 9352 * 9353 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 9354 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 9355 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9356 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9357 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 9358 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 9359 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 9360 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 9361 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 9362 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 9363 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9364 */ 9365 9366(function( $ ){ 9367 9368//TODO: I guess this is where the i18n needs to be reimplemented. I'll look 9369// into existing patterns for i18n in javascript but i think that mimicking 9370// pythons gettext might be a reasonable approach. 9371var I18N = { 9372 Errors: { 9373 Dzc: "Sorry, we don't support Deep Zoom Collections!", 9374 Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.", 9375 Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.", 9376 ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.", 9377 Security: "It looks like a security restriction stopped us from " + 9378 "loading this Deep Zoom Image.", 9379 Status: "This space unintentionally left blank ({0} {1}).", 9380 OpenFailed: "Unable to open {0}: {1}" 9381 }, 9382 9383 Tooltips: { 9384 FullPage: "Toggle full page", 9385 Home: "Go home", 9386 ZoomIn: "Zoom in", 9387 ZoomOut: "Zoom out", 9388 NextPage: "Next page", 9389 PreviousPage: "Previous page", 9390 RotateLeft: "Rotate left", 9391 RotateRight: "Rotate right" 9392 } 9393}; 9394 9395$.extend( $, /** @lends OpenSeadragon */{ 9396 9397 /** 9398 * @function 9399 * @param {String} property 9400 */ 9401 getString: function( prop ) { 9402 9403 var props = prop.split('.'), 9404 string = null, 9405 args = arguments, 9406 container = I18N, 9407 i; 9408 9409 for ( i = 0; i < props.length-1; i++ ) { 9410 // in case not a subproperty 9411 container = container[ props[ i ] ] || {}; 9412 } 9413 string = container[ props[ i ] ]; 9414 9415 if ( typeof( string ) != "string" ) { 9416 $.console.debug( "Untranslated source string:", prop ); 9417 string = ""; // FIXME: this breaks gettext()-style convention, which would return source 9418 } 9419 9420 return string.replace(/\{\d+\}/g, function(capture) { 9421 var i = parseInt( capture.match( /\d+/ ), 10 ) + 1; 9422 return i < args.length ? 9423 args[ i ] : 9424 ""; 9425 }); 9426 }, 9427 9428 /** 9429 * @function 9430 * @param {String} property 9431 * @param {*} value 9432 */ 9433 setString: function( prop, value ) { 9434 9435 var props = prop.split('.'), 9436 container = I18N, 9437 i; 9438 9439 for ( i = 0; i < props.length - 1; i++ ) { 9440 if ( !container[ props[ i ] ] ) { 9441 container[ props[ i ] ] = {}; 9442 } 9443 container = container[ props[ i ] ]; 9444 } 9445 9446 container[ props[ i ] ] = value; 9447 } 9448 9449}); 9450 9451}( OpenSeadragon )); 9452 9453/* 9454 * OpenSeadragon - Point 9455 * 9456 * Copyright (C) 2009 CodePlex Foundation 9457 * Copyright (C) 2010-2013 OpenSeadragon contributors 9458 * 9459 * Redistribution and use in source and binary forms, with or without 9460 * modification, are permitted provided that the following conditions are 9461 * met: 9462 * 9463 * - Redistributions of source code must retain the above copyright notice, 9464 * this list of conditions and the following disclaimer. 9465 * 9466 * - Redistributions in binary form must reproduce the above copyright 9467 * notice, this list of conditions and the following disclaimer in the 9468 * documentation and/or other materials provided with the distribution. 9469 * 9470 * - Neither the name of CodePlex Foundation nor the names of its 9471 * contributors may be used to endorse or promote products derived from 9472 * this software without specific prior written permission. 9473 * 9474 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 9475 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 9476 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9477 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9478 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9479 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 9480 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 9481 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 9482 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 9483 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 9484 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9485 */ 9486 9487(function( $ ){ 9488 9489/** 9490 * @class Point 9491 * @classdesc A Point is really used as a 2-dimensional vector, equally useful for 9492 * representing a point on a plane, or the height and width of a plane 9493 * not requiring any other frame of reference. 9494 * 9495 * @memberof OpenSeadragon 9496 * @param {Number} [x] The vector component 'x'. Defaults to the origin at 0. 9497 * @param {Number} [y] The vector component 'y'. Defaults to the origin at 0. 9498 */ 9499$.Point = function( x, y ) { 9500 /** 9501 * The vector component 'x'. 9502 * @member {Number} x 9503 * @memberof OpenSeadragon.Point# 9504 */ 9505 this.x = typeof ( x ) == "number" ? x : 0; 9506 /** 9507 * The vector component 'y'. 9508 * @member {Number} y 9509 * @memberof OpenSeadragon.Point# 9510 */ 9511 this.y = typeof ( y ) == "number" ? y : 0; 9512}; 9513 9514$.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ 9515 9516 /** 9517 * Add another Point to this point and return a new Point. 9518 * @function 9519 * @param {OpenSeadragon.Point} point The point to add vector components. 9520 * @returns {OpenSeadragon.Point} A new point representing the sum of the 9521 * vector components 9522 */ 9523 plus: function( point ) { 9524 return new $.Point( 9525 this.x + point.x, 9526 this.y + point.y 9527 ); 9528 }, 9529 9530 /** 9531 * Substract another Point to this point and return a new Point. 9532 * @function 9533 * @param {OpenSeadragon.Point} point The point to substract vector components. 9534 * @returns {OpenSeadragon.Point} A new point representing the substraction of the 9535 * vector components 9536 */ 9537 minus: function( point ) { 9538 return new $.Point( 9539 this.x - point.x, 9540 this.y - point.y 9541 ); 9542 }, 9543 9544 /**
9545 * Multiply this point by a factor and return a new Point. 9546 * @function 9547 * @param {Number} factor The factor to multiply vector components. 9548 * @returns {OpenSeadragon.Point} A new point representing the multiplication 9549 * of the vector components by the factor 9550 */ 9551 times: function( factor ) { 9552 return new $.Point( 9553 this.x * factor, 9554 this.y * factor 9555 ); 9556 }, 9557 9558 /** 9559 * Divide this point by a factor and return a new Point. 9560 * @function 9561 * @param {Number} factor The factor to divide vector components. 9562 * @returns {OpenSeadragon.Point} A new point representing the division of the 9563 * vector components by the factor 9564 */ 9565 divide: function( factor ) { 9566 return new $.Point( 9567 this.x / factor, 9568 this.y / factor 9569 ); 9570 }, 9571 9572 /** 9573 * Compute the opposite of this point and return a new Point. 9574 * @function 9575 * @returns {OpenSeadragon.Point} A new point representing the opposite of the 9576 * vector components 9577 */ 9578 negate: function() { 9579 return new $.Point( -this.x, -this.y ); 9580 }, 9581 9582 /** 9583 * Compute the distance between this point and another point. 9584 * @function 9585 * @param {OpenSeadragon.Point} point The point to compute the distance with. 9586 * @returns {Number} The distance between the 2 points 9587 */ 9588 distanceTo: function( point ) { 9589 return Math.sqrt( 9590 Math.pow( this.x - point.x, 2 ) + 9591 Math.pow( this.y - point.y, 2 ) 9592 ); 9593 }, 9594 9595 /** 9596 * Apply a function to each coordinate of this point and return a new point. 9597 * @function 9598 * @param {function} func The function to apply to each coordinate. 9599 * @returns {OpenSeadragon.Point} A new point with the coordinates computed 9600 * by the specified function 9601 */ 9602 apply: function( func ) { 9603 return new $.Point( func( this.x ), func( this.y ) ); 9604 }, 9605 9606 /** 9607 * Check if this point is equal to another one. 9608 * @function 9609 * @param {OpenSeadragon.Point} point The point to compare this point with. 9610 * @returns {Boolean} true if they are equal, false otherwise. 9611 */ 9612 equals: function( point ) { 9613 return ( 9614 point instanceof $.Point 9615 ) && ( 9616 this.x === point.x 9617 ) && ( 9618 this.y === point.y 9619 ); 9620 }, 9621 9622 /** 9623 * Rotates the point around the specified pivot 9624 * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point 9625 * @function 9626 * @param {Number} degress to rotate around the pivot. 9627 * @param {OpenSeadragon.Point} pivot Point about which to rotate. 9628 * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot 9629 */ 9630 rotate: function ( degrees, pivot ) { 9631 var angle = degrees * Math.PI / 180.0, 9632 x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x, 9633 y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.
9633cos( angle ) * ( this.y - pivot.y ) + pivot.y; 9634 return new $.Point( x, y ); 9635 }, 9636 9637 /** 9638 * Convert this point to a string in the format (x,y) where x and y are 9639 * rounded to the nearest integer. 9640 * @function 9641 * @returns {String} A string representation of this point. 9642 */ 9643 toString: function() { 9644 return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")"; 9645 } 9646}; 9647 9648}( OpenSeadragon )); 9649 9650/* 9651 * OpenSeadragon - TileSource 9652 * 9653 * Copyright (C) 2009 CodePlex Foundation 9654 * Copyright (C) 2010-2013 OpenSeadragon contributors 9655 * 9656 * Redistribution and use in source and binary forms, with or without 9657 * modification, are permitted provided that the following conditions are 9658 * met: 9659 * 9660 * - Redistributions of source code must retain the above copyright notice, 9661 * this list of conditions and the following disclaimer. 9662 * 9663 * - Redistributions in binary form must reproduce the above copyright 9664 * notice, this list of conditions and the following disclaimer in the 9665 * documentation and/or other materials provided with the distribution. 9666 * 9667 * - Neither the name of CodePlex Foundation nor the names of its 9668 * contributors may be used to endorse or promote products derived from 9669 * this software without specific prior written permission. 9670 * 9671 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 9672 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 9673 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9674 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9675 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 9676 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 9677 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 9678 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 9679 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 9680 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 9681 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9682 */ 9683 9684(function( $ ){ 9685 9686 9687/** 9688 * @class TileSource 9689 * @classdesc The TileSource contains the most basic implementation required to create a 9690 * smooth transition between layer in an image pyramid. It has only a single key 9691 * interface that must be implemented to complete it key functionality: 9692 * 'getTileUrl'. It also has several optional interfaces that can be 9693 * implemented if a new TileSource wishes to support configuration via a simple 9694 * object or array ('configure') and if the tile source supports or requires 9695 * configuration via retreival of a document on the network ala AJAX or JSONP, 9696 * ('getImageInfo'). 9697 * <br/> 9698 * By default the image pyramid is split into N layers where the images longest 9699 * side in M (in pixels), where N is the smallest integer which satisfies 9700 * <strong>2^(N+1) >= M</strong>. 9701 * 9702 * @memberof OpenSeadragon 9703 * @extends OpenSeadragon.EventSource 9704 * @param {Number|Object|Array|String} width 9705 * If more than a single argument is supplied, the traditional use of 9706 * positional parameters is supplied and width is expected to be the width 9707 * source image at its max resolution in pixels. If a single argument is supplied and 9708 * it is an Object or Array, the construction is assumed to occur through 9709 * the extending classes implementation of 'configure'. Finally if only a 9710 * single argument is supplied and it is a String, the extending class is 9711 * expected to implement 'getImageInfo' and 'configure'. 9712 * @param {Number} height 9713 * Width of the source image at max resolution in pixels. 9714 * @param {Number} tileSize 9715 * The size of the tiles to assumed to make up each pyramid layer in pixels. 9716 * Tile size determines the point at which the image pyramid must be 9717 * divided into a matrix of smaller images. 9718 * @param {Number} tileOverlap 9719 * The number of pixels each tile is expected to overlap touching tiles. 9720 * @param {Number} minLevel 9721 * The minimum level to attempt to load. 9722 * @param {Number} maxLevel 9723 * The maximum level to attempt to load. 9724 */ 9725$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) { 9726 var callback = null, 9727 args = arguments, 9728 options, 9729 i; 9730 9731 if( $.isPlainObject( width ) ){ 9732 options = width; 9733 }else{ 9734 options = { 9735 width: args[0], 9736 height: args[1], 9737 tileSize: args[2], 9738 tileOverlap: args[3], 9739 minLevel: args[4], 9740 maxLevel: args[5] 9741 }; 9742 } 9743 9744 //Tile sources supply some events, namely 'ready' when they must be configured 9745 //by asynchronously fetching their configuration data. 9746 $.EventSource.call( this ); 9747 9748 //we allow options to override anything we dont treat as 9749 //required via idiomatic options or which is functionally 9750 //set depending on the state of the readiness of this tile 9751 //source 9752 $.extend( true, this, options ); 9753 9754 //Any functions that are passed as arguments are bound to the ready callback 9755 /*jshint loopfunc:true*/ 9756 for ( i = 0; i < arguments.length; i++ ) { 9757 if ( $.isFunction( arguments[ i ] ) ) { 9758 callback = arguments[ i ]; 9759 this.addHandler( 'ready', function ( event ) { 9760 callback( event ); 9761 } ); 9762 //only one callback per constructor 9763 break; 9764 } 9765 } 9766 9767 /** 9768 * Ratio of width to height 9769 * @member {Number} aspectRatio 9770 * @memberof OpenSeadragon.TileSource# 9771 */ 9772 /** 9773 * Vector storing x and y dimensions ( width and height respectively ). 9774 * @member {OpenSeadragon.Point} dimensions 9775 * @memberof OpenSeadragon.TileSource# 9776 */ 9777 /** 9778 * The size of the image tiles used to compose the image. 9779 * @member {Number} tileSize 9780 * @memberof OpenSeadragon.TileSource# 9781 */ 9782 /** 9783 * The overlap in pixels each tile shares with its adjacent neighbors. 9784 * @member {Number} tileOverlap 9785 * @memberof OpenSeadragon.TileSource# 9786 */ 9787 /** 9788 * The minimum pyramid level this tile source supports or should attempt to load. 9789 * @member {Number} minLevel 9790 * @memberof OpenSeadragon.TileSource# 9791 */ 9792 /** 9793 * The maximum pyramid level this tile source supports or should attempt to load. 9794 * @member {Number} maxLevel 9795 * @memberof OpenSeadragon.TileSource# 9796 */ 9797 /** 9798 * 9799 * @member {Boolean} ready 9800 * @memberof OpenSeadragon.TileSource# 9801 */ 9802 9803 if( 'string' == $.type( arguments[ 0 ] ) ){ 9804 //in case the getImageInfo method is overriden and/or implies an 9805 //async mechanism set some safe defaults first 9806 this.aspectRatio = 1; 9807 this.dimensions = new $.Point( 10, 10 ); 9808 this.tileSize = 0; 9809 this.tileOverlap = 0; 9810 this.minLevel = 0; 9811 this.maxLevel = 0; 9812 this.ready = false; 9813 //configuration via url implies the extending class 9814 //implements and 'configure' 9815 this.getImageInfo( arguments[ 0 ] ); 9816 9817 } else { 9818 9819 //explicit configuration via positional args in constructor 9820 //or the more idiomatic 'options' object 9821 this.ready = true; 9822 this.aspectRatio = ( options.width && options.height ) ? 9823 ( options.width / options.height ) : 1; 9824 this.dimensions = new $.Point( options.width, options.height ); 9825 this.tileSize = options.tileSize ? options.tileSize : 0; 9826 this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0; 9827 this.minLevel = options.minLevel ? options.minLevel : 0; 9828 this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ? 9829 options.maxLevel : ( 9830 ( options.width && options.height ) ? Math.ceil( 9831 Math.log( Math.max( options.width, options.height ) ) / 9832 Math.log( 2 ) 9833 ) : 0 9834 ); 9835 if( callback && $.isFunction( callback ) ){ 9836 callback( this ); 9837 } 9838 } 9839 9840 9841}; 9842 9843 9844$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{ 9845 9846 /** 9847 * @function 9848 * @param {Number} level 9849 */ 9850 getLevelScale: function( level ) { 9851 9852 // see https://github.com/openseadragon/openseadragon/issues/22 9853 // we use the tilesources implementation of getLevelScale to generate 9854 // a memoized re-implementation 9855 var levelScaleCache = {}, 9856 i; 9857 for( i = 0; i <= this.maxLevel; i++ ){ 9858 levelScaleCache[ i ] = 1 / Math.pow(2, this.maxLevel - i); 9859 } 9860 this.getLevelScale = function( _level ){ 9861 return levelScaleCache[ _level ]; 9862 }; 9863 return this.getLevelScale( level ); 9864 }, 9865 9866 /** 9867 * @function 9868 * @param {Number} level 9869 */ 9870 getNumTiles: function( level ) { 9871 var scale = this.getLevelScale( level ), 9872 x = Math.ceil( scale * this.dimensions.x / this.tileSize ), 9873 y = Math.ceil( scale * this.dimensions.y / this.tileSize ); 9874 9875 return new $.Point( x, y ); 9876 }, 9877 9878 /** 9879 * @function 9880 * @param {Number} level 9881 */ 9882 getPixelRatio: function( level ) { 9883 var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ), 9884 rx = 1.0 / imageSizeScaled.x, 9885 ry = 1.0 / imageSizeScaled.y; 9886 9887 return new $.Point(rx, ry); 9888 }, 9889 9890 9891 /** 9892 * @function 9893 * @param {Number} level 9894 */ 9895 getClosestLevel: function( rect ) { 9896 var i, 9897 tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.tileSize ), 9898 tiles; 9899 for( i = this.minLevel; i < this.maxLevel; i++ ){ 9900 tiles = this.getNumTiles( i ); 9901 if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){ 9902 break; 9903 } 9904 } 9905 return Math.max( 0, i - 1 ); 9906 }, 9907 9908 /** 9909 * @function 9910 * @param {Number} level 9911 * @param {OpenSeadragon.Point} point 9912 */ 9913 getTileAtPoint: function( level, point ) { 9914 var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ), 9915 tx = Math.floor( pixel.x / this.tileSize ), 9916 ty = Math.floor( pixel.y / this.tileSize ); 9917 9918 return new $.Point( tx, ty ); 9919 }, 9920 9921 /** 9922 * @function 9923 * @param {Number} level 9924 * @param {Number} x 9925 * @param {Number} y 9926 */ 9927 getTileBounds: function( level, x, y ) { 9928 var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ), 9929 px = ( x === 0 ) ? 0 : this.tileSize * x - this.tileOverlap, 9930 py = ( y === 0 ) ? 0 : this.tileSize * y - this.tileOverlap, 9931 sx = this.tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap, 9932 sy = this.tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap, 9933 scale = 1.0 / dimensionsScaled.x; 9934 9935 sx = Math.min( sx, dimensionsScaled.x - px ); 9936 sy = Math.min( sy, dimensionsScaled.y - py ); 9937 9938 return new $.Rect( px * scale, py * scale, sx * scale, sy * scale ); 9939 }, 9940 9941 9942 /** 9943 * Responsible for retrieving, and caching the 9944 * image metadata pertinent to this TileSources implementation. 9945 * @function 9946 * @param {String} url 9947 * @throws {Error} 9948 */ 9949 getImageInfo: function( url ) { 9950 var _this = this, 9951 callbackName, 9952 callback, 9953 readySource, 9954 options, 9955 urlParts, 9956 filename, 9957 lastDot; 9958 9959 9960 if( url ) { 9961 urlParts = url.split( '/' ); 9962 filename = urlParts[ urlParts.length - 1 ]; 9963 lastDot = filename.lastIndexOf( '.' ); 9964 if ( lastDot > -1 ) { 9965 urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot ); 9966 } 9967 } 9968 9969 callback = function( data ){
9970 if( typeof(data) === "string" ) { 9971 data = $.parseXml( data ); 9972 } 9973 var $TileSource = $.TileSource.determineType( _this, data, url ); 9974 if ( !$TileSource ) { 9975 /** 9976 * Raised when an error occurs loading a TileSource. 9977 * 9978 * @event open-failed 9979 * @memberof OpenSeadragon.TileSource 9980 * @type {object} 9981 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 9982 * @property {String} message 9983 * @property {String} source 9984 * @property {?Object} userData - Arbitrary subscriber-defined object. 9985 */ 9986 _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } ); 9987 return; 9988 } 9989 9990 options = $TileSource.prototype.configure.apply( _this, [ data, url ]); 9991 readySource = new $TileSource( options ); 9992 _this.ready = true; 9993 /** 9994 * Raised when a TileSource is opened and initialized. 9995 * 9996 * @event ready 9997 * @memberof OpenSeadragon.TileSource 9998 * @type {object} 9999 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 10000 * @property {Object} tileSource 10001 * @property {?Object} userData - Arbitrary subscriber-defined object. 10002 */ 10003 _this.raiseEvent( 'ready', { tileSource: readySource } ); 10004 }; 10005 10006 if( url.match(/\.js$/) ){ 10007 //TODO: Its not very flexible to require tile sources to end jsonp 10008 // request for info with a url that ends with '.js' but for 10009 // now it's the only way I see to distinguish uniformly. 10010 callbackName = url.split( '/' ).pop().replace('.js',''); 10011 $.jsonp({ 10012 url: url, 10013 async: false, 10014 callbackName: callbackName, 10015 callback: callback 10016 }); 10017 } else { 10018 // request info via xhr asynchronously. 10019 $.makeAjaxRequest( url, function( xhr ) { 10020 var data = processResponse( xhr ); 10021 callback( data ); 10022 }, function ( xhr, exc ) { 10023 var msg; 10024 10025 /* 10026 IE < 10 will block XHR requests to different origins. Any property access on the request 10027 object will raise an exception which we'll attempt to handle by formatting the original 10028 exception rather than the second one raised when we try to access xhr.status 10029 */ 10030 try { 10031 msg = "HTTP " + xhr.status + " attempting to load TileSource"; 10032 } catch ( e ) { 10033 var formattedExc; 10034 if ( typeof( exc ) == "undefined" || !exc.toString ) { 10035 formattedExc = "Unknown error"; 10036 } else { 10037 formattedExc = exc.toString(); 10038 } 10039 10040 msg = formattedExc + " attempting to load TileSource"; 10041 } 10042 10043 /*** 10044 * Raised when an error occurs loading a TileSource. 10045 * 10046 * @event open-failed 10047 * @memberof OpenSeadragon.TileSource 10048 * @type {object} 10049 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. 10050 * @property {String} message 10051 * @property {String} source 10052 * @property {?Object} userData - Arbitrary subscriber-defined object. 10053 */ 10054 _this.raiseEvent( 'open-failed', { 10055 message: msg, 10056 source: url 10057 }); 10058 }); 10059 } 10060 10061 }, 10062 10063 /** 10064 * Responsible determining if a the particular TileSource supports the 10065 * data format ( and allowed to apply logic against the url the data was 10066 * loaded from, if any ). Overriding implementations are expected to do 10067 * something smart with data and / or url to determine support. Also 10068 * understand that iteration order of TileSources is not guarunteed so 10069 * please make sure your data or url is expressive enough to ensure a simple 10070 * and sufficient mechanisim for clear determination. 10071 * @function 10072 * @param {String|Object|Array|Document} data 10073 * @param {String} url - the url the data was loaded 10074 * from if any. 10075 * @return {Boolean} 10076 */ 10077 supports: function( data, url ) { 10078 return false; 10079 }, 10080 10081 /** 10082 * Responsible for parsing and configuring the 10083 * image metadata pertinent to this TileSources implementation. 10084 * This method is not implemented by this class other than to throw an Error 10085 * announcing you have to implement it. Because of the variety of tile 10086 * server technologies, and various specifications for building image 10087 * pyramids, this method is here to allow easy integration. 10088 * @function 10089 * @param {String|Object|Array|Document} data 10090 * @param {String} url - the url the data was loaded 10091 * from if any. 10092 * @return {Object} options - A dictionary of keyword arguments sufficient 10093 * to configure this tile sources constructor. 10094 * @throws {Error} 10095 */ 10096 configure: function( data, url ) { 10097 throw new Error( "Method not implemented." ); 10098 }, 10099 10100 /** 10101 * Responsible for retriving the url which will return an image for the 10102 * region speified by the given x, y, and level components. 10103 * This method is not implemented by this class other than to throw an Error 10104 * announcing you have to implement it. Because of the variety of tile 10105 * server technologies, and various specifications for building image 10106 * pyramids, this method is here to allow easy integration. 10107 * @function 10108 * @param {Number} level 10109 * @param {Number} x 10110 * @param {Number} y 10111 * @throws {Error} 10112 */ 10113 getTileUrl: function( level, x, y ) { 10114 throw new Error( "Method not implemented." ); 10115 }, 10116 10117 /** 10118 * @function 10119 * @param {Number} level 10120 * @param {Number} x 10121 * @param {Number} y 10122 */ 10123 tileExists: function( level, x, y ) { 10124 var numTiles = this.getNumTiles( level ); 10125 return level >= this.minLevel && 10126 level <= this.maxLevel && 10127 x >= 0 && 10128 y >= 0 && 10129 x < numTiles.x && 10130 y < numTiles.y; 10131 } 10132}; 10133 10134 10135$.extend( true, $.TileSource.prototype, $.EventSource.prototype ); 10136 10137 10138/** 10139 * Decides whether to try to process the response as xml, json, or hand back 10140 * the text 10141 * @private 10142 * @inner 10143 * @function 10144 * @param {XMLHttpRequest} xhr - the completed network request 10145 */ 10146function processResponse( xhr ){ 10147 var responseText = xhr.responseText, 10148 status = xhr.status, 10149 statusText, 10150 data; 10151 10152 if ( !xhr ) { 10153 throw new Error( $.getString( "Errors.Security" ) ); 10154 } else if ( xhr.status !== 200 && xhr.status !== 0 ) { 10155 status = xhr.status; 10156 statusText = ( status == 404 ) ? 10157 "Not Found" : 10158 xhr.statusText; 10159 throw new Error( $.getString( "Errors.Status", status, statusText ) ); 10160 } 10161 10162 if( responseText.match(/\s*<.*/) ){ 10163 try{ 10164 data = ( xhr.responseXML && xhr.responseXML.documentElement ) ? 10165 xhr.responseXML : 10166 $.parseXml( responseText ); 10167 } catch (e){ 10168 data = xhr.responseText; 10169 } 10170 }else if( responseText.match(/\s*[\{\[].*/) ){ 10171 /*jshint evil:true*/ 10172 data = eval( '('+responseText+')' ); 10173 }else{ 10174 data = responseText; 10175 } 10176 return data; 10177} 10178 10179 10180/** 10181 * Determines the TileSource Implementation by introspection of OpenSeadragon 10182 * namespace, calling each TileSource implementation of 'isType' 10183 * @private 10184 * @inner 10185 * @function 10186 * @param {Object|Array|Document} data - the tile source configuration object 10187 * @param {String} url - the url where the tile source configuration object was 10188 * loaded from, if any. 10189 */ 10190$.TileSource.determineType = function( tileSource, data, url ){ 10191 var property; 10192 for( property in OpenSeadragon ){ 10193 if( property.match(/.+TileSource$/) && 10194 $.isFunction( OpenSeadragon[ property ] ) && 10195 $.isFunction( OpenSeadragon[ property ].prototype.supports ) && 10196 OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url ) 10197 ){ 10198 return OpenSeadragon[ property ]; 10199 } 10200 } 10201 10202 $.console.error( "No TileSource was able to open %s %s", url, data ); 10203};
10204 10205 10206}( OpenSeadragon )); 10207 10208/* 10209 * OpenSeadragon - DziTileSource 10210 * 10211 * Copyright (C) 2009 CodePlex Foundation 10212 * Copyright (C) 2010-2013 OpenSeadragon contributors 10213 * 10214 * Redistribution and use in source and binary forms, with or without 10215 * modification, are permitted provided that the following conditions are 10216 * met: 10217 * 10218 * - Redistributions of source code must retain the above copyright notice, 10219 * this list of conditions and the following disclaimer. 10220 * 10221 * - Redistributions in binary form must reproduce the above copyright 10222 * notice, this list of conditions and the following disclaimer in the 10223 * documentation and/or other materials provided with the distribution. 10224 * 10225 * - Neither the name of CodePlex Foundation nor the names of its 10226 * contributors may be used to endorse or promote products derived from 10227 * this software without specific prior written permission. 10228 * 10229 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 10230 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 10231 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 10232 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10233 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10234 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 10235 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 10236 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 10237 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 10238 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 10239 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10240 */ 10241 10242(function( $ ){ 10243 10244/** 10245 * @class DziTileSource 10246 * @memberof OpenSeadragon 10247 * @extends OpenSeadragon.TileSource 10248 * @param {Number|Object} width - the pixel width of the image or the idiomatic 10249 * options object which is used instead of positional arguments. 10250 * @param {Number} height 10251 * @param {Number} tileSize 10252 * @param {Number} tileOverlap 10253 * @param {String} tilesUrl 10254 * @param {String} fileFormat 10255 * @param {OpenSeadragon.DisplayRect[]} displayRects 10256 * @property {String} tilesUrl 10257 * @property {String} fileFormat 10258 * @property {OpenSeadragon.DisplayRect[]} displayRects 10259 */ 10260$.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects, minLevel, maxLevel ) { 10261 var i, 10262 rect, 10263 level, 10264 options; 10265 10266 if( $.isPlainObject( width ) ){ 10267 options = width; 10268 }else{ 10269 options = { 10270 width: arguments[ 0 ], 10271 height: arguments[ 1 ], 10272 tileSize: arguments[ 2 ], 10273 tileOverlap: arguments[ 3 ], 10274 tilesUrl: arguments[ 4 ], 10275 fileFormat: arguments[ 5 ], 10276 displayRects: arguments[ 6 ], 10277 minLevel: arguments[ 7 ], 10278 maxLevel: arguments[ 8 ] 10279 }; 10280 } 10281 10282 this._levelRects = {}; 10283 this.tilesUrl = options.tilesUrl; 10284 this.fileFormat = options.fileFormat; 10285 this.displayRects = options.displayRects; 10286 10287 if ( this.displayRects ) { 10288 for ( i = this.displayRects.length - 1; i >= 0; i-- ) { 10289 rect = this.displayRects[ i ]; 10290 for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) { 10291 if ( !this._levelRects[ level ] ) { 10292 this._levelRects[ level ] = []; 10293 } 10294 this._levelRects[ level ].push( rect ); 10295 } 10296 } 10297 } 10298 10299 $.TileSource.apply( this, [ options ] ); 10300 10301}; 10302 10303$.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.DziTileSource.prototype */{ 10304 10305 10306 /** 10307 * Determine if the data and/or url imply the image service is supported by 10308 * this tile source. 10309 * @function 10310 * @param {Object|Array} data 10311 * @param {String} optional - url 10312 */ 10313 supports: function( data, url ){ 10314 var ns; 10315 if ( data.Image ) { 10316 ns = data.Image.xmlns; 10317 } else if ( data.documentElement && "Image" == data.documentElement.tagName ) { 10318 ns = data.documentElement.namespaceURI; 10319 } 10320 10321 return ( "http://schemas.microsoft.com/deepzoom/2008" == ns || 10322 "http://schemas.microsoft.com/deepzoom/2009" == ns ); 10323 }, 10324 10325 /** 10326 * 10327 * @function 10328 * @param {Object|XMLDocument} data - the raw configuration 10329 * @param {String} url - the url the data was retreived from if any. 10330 * @return {Object} options - A dictionary of keyword arguments sufficient 10331 * to configure this tile sources constructor. 10332 */ 10333 configure: function( data, url ){ 10334 10335 var options; 10336 10337 if( !$.isPlainObject(data) ){ 10338 10339 options = configureFromXML( this, data ); 10340 10341 }else{ 10342 10343 options = configureFromObject( this, data ); 10344 } 10345 10346 if (url && !options.tilesUrl) { 10347 options.tilesUrl = url.replace(/([^\/]+)\.(dzi|xml|js)(\?.*|$)/, '$1_files/'); 10348 10349 if (url.search(/\.(dzi|xml|js)\?/) != -1) { 10350 options.queryParams = url.match(/\?.*/); 10351 }else{ 10352 options.queryParams = ''; 10353 } 10354 } 10355 10356 return options; 10357 }, 10358 10359 10360 /** 10361 * @function 10362 * @param {Number} level 10363 * @param {Number} x 10364 * @param {Number} y 10365 */ 10366 getTileUrl: function( level, x, y ) { 10367 return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat, this.queryParams ].join( '' ); 10368 }, 10369 10370 10371 /** 10372 * @function 10373 * @param {Number} level 10374 * @param {Number} x 10375 * @param {Number} y 10376 */ 10377 tileExists: function( level, x, y ) { 10378 var rects = this._levelRects[ level ], 10379 rect, 10380 scale, 10381 xMin, 10382 yMin, 10383 xMax, 10384 yMax, 10385 i; 10386 10387 if ( !rects || !rects.length ) { 10388 return true; 10389 } 10390 10391 for ( i = rects.length - 1; i >= 0; i-- ) { 10392 rect = rects[ i ]; 10393 10394 if ( level < rect.minLevel || level > rect.maxLevel ) { 10395 continue; 10396 } 10397 10398 scale = this.getLevelScale( level ); 10399 xMin = rect.x * scale; 10400 yMin = rect.y * scale; 10401 xMax = xMin + rect.width * scale; 10402 yMax = yMin + rect.height * scale; 10403 10404 xMin = Math.floor( xMin / this.tileSize ); 10405 yMin = Math.floor( yMin / this.tileSize ); 10406 xMax = Math.ceil( xMax / this.tileSize );
10407 yMax = Math.ceil( yMax / this.tileSize ); 10408 10409 if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) { 10410 return true; 10411 } 10412 } 10413 10414 return false; 10415 } 10416}); 10417 10418 10419/** 10420 * @private 10421 * @inner 10422 * @function 10423 */ 10424function configureFromXML( tileSource, xmlDoc ){ 10425 10426 if ( !xmlDoc || !xmlDoc.documentElement ) { 10427 throw new Error( $.getString( "Errors.Xml" ) ); 10428 } 10429 10430 var root = xmlDoc.documentElement, 10431 rootName = root.tagName, 10432 configuration = null, 10433 displayRects = [], 10434 dispRectNodes, 10435 dispRectNode, 10436 rectNode, 10437 sizeNode, 10438 i; 10439 10440 if ( rootName == "Image" ) { 10441 10442 try { 10443 sizeNode = root.getElementsByTagName( "Size" )[ 0 ]; 10444 configuration = { 10445 Image: { 10446 xmlns: "http://schemas.microsoft.com/deepzoom/2008", 10447 Url: root.getAttribute( "Url" ), 10448 Format: root.getAttribute( "Format" ), 10449 DisplayRect: null, 10450 Overlap: parseInt( root.getAttribute( "Overlap" ), 10 ), 10451 TileSize: parseInt( root.getAttribute( "TileSize" ), 10 ), 10452 Size: { 10453 Height: parseInt( sizeNode.getAttribute( "Height" ), 10 ), 10454 Width: parseInt( sizeNode.getAttribute( "Width" ), 10 ) 10455 } 10456 } 10457 }; 10458 10459 if ( !$.imageFormatSupported( configuration.Image.Format ) ) { 10460 throw new Error( 10461 $.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() ) 10462 ); 10463 } 10464 10465 dispRectNodes = root.getElementsByTagName( "DisplayRect" ); 10466 for ( i = 0; i < dispRectNodes.length; i++ ) { 10467 dispRectNode = dispRectNodes[ i ]; 10468 rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ]; 10469 10470 displayRects.push({ 10471 Rect: { 10472 X: parseInt( rectNode.getAttribute( "X" ), 10 ), 10473 Y: parseInt( rectNode.getAttribute( "Y" ), 10 ), 10474 Width: parseInt( rectNode.getAttribute( "Width" ), 10 ), 10475 Height: parseInt( rectNode.getAttribute( "Height" ), 10 ), 10476 MinLevel: parseInt( dispRectNode.getAttribute( "MinLevel" ), 10 ), 10477 MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 ) 10478 } 10479 }); 10480 } 10481 10482 if( displayRects.length ){ 10483 configuration.Image.DisplayRect = displayRects; 10484 } 10485 10486 return configureFromObject( tileSource, configuration ); 10487 10488 } catch ( e ) { 10489 throw (e instanceof Error) ? 10490 e : 10491 new Error( $.getString("Errors.Dzi") ); 10492 } 10493 } else if ( rootName == "Collection" ) { 10494 throw new Error( $.getString( "Errors.Dzc" ) ); 10495 } else if ( rootName == "Error" ) { 10496 return $._processDZIError( root ); 10497 } 10498 10499 throw new Error( $.getString( "Errors.Dzi" ) ); 10500} 10501 10502/** 10503 * @private 10504 * @inner 10505 * @function 10506 */ 10507function configureFromObject( tileSource, configuration ){ 10508 var imageData = configuration.Image, 10509 tilesUrl = imageData.Url, 10510 fileFormat = imageData.Format, 10511 sizeData = imageData.Size, 10512 dispRectData = imageData.DisplayRect || [], 10513 width = parseInt( sizeData.Width, 10 ), 10514 height = parseInt( sizeData.Height, 10 ), 10515 tileSize = parseInt( imageData.TileSize, 10 ), 10516 tileOverlap = parseInt( imageData.Overlap, 10 ), 10517 displayRects = [], 10518 rectData, 10519 i; 10520 10521 //TODO: need to figure out out to better handle image format compatibility 10522 // which actually includes additional file formats like xml and pdf 10523 // and plain text for various tilesource implementations to avoid low 10524 // level errors. 10525 // 10526 // For now, just don't perform the check. 10527 // 10528 /*if ( !imageFormatSupported( fileFormat ) ) { 10529 throw new Error( 10530 $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() ) 10531 ); 10532 }*/ 10533 10534 for ( i = 0; i < dispRectData.length; i++ ) {
10535 rectData = dispRectData[ i ].Rect; 10536 10537 displayRects.push( new $.DisplayRect( 10538 parseInt( rectData.X, 10 ), 10539 parseInt( rectData.Y, 10 ), 10540 parseInt( rectData.Width, 10 ), 10541 parseInt( rectData.Height, 10 ), 10542 parseInt( rectData.MinLevel, 10 ), 10543 parseInt( rectData.MaxLevel, 10 ) 10544 )); 10545 } 10546 10547 return $.extend(true, { 10548 width: width, /* width *required */ 10549 height: height, /* height *required */ 10550 tileSize: tileSize, /* tileSize *required */ 10551 tileOverlap: tileOverlap, /* tileOverlap *required */ 10552 minLevel: null, /* minLevel */ 10553 maxLevel: null, /* maxLevel */ 10554 tilesUrl: tilesUrl, /* tilesUrl */ 10555 fileFormat: fileFormat, /* fileFormat */ 10556 displayRects: displayRects /* displayRects */ 10557 }, configuration ); 10558 10559} 10560 10561}( OpenSeadragon )); 10562 10563/* 10564 * OpenSeadragon - IIIFTileSource 10565 * 10566 * Copyright (C) 2009 CodePlex Foundation 10567 * Copyright (C) 2010-2013 OpenSeadragon contributors 10568 * 10569 * Redistribution and use in source and binary forms, with or without 10570 * modification, are permitted provided that the following conditions are 10571 * met: 10572 * 10573 * - Redistributions of source code must retain the above copyright notice, 10574 * this list of conditions and the following disclaimer. 10575 * 10576 * - Redistributions in binary form must reproduce the above copyright 10577 * notice, this list of conditions and the following disclaimer in the 10578 * documentation and/or other materials provided with the distribution. 10579 * 10580 * - Neither the name of CodePlex Foundation nor the names of its 10581 * contributors may be used to endorse or promote products derived from 10582 * this software without specific prior written permission. 10583 * 10584 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 10585 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 10586 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 10587 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10588 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10589 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 10590 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 10591 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 10592 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 10593 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 10594 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10595 */ 10596 10597/* 10598 * The getTileUrl implementation is based on Jon Stroop's Python version, 10599 * which is released under the New BSD license: 10600 * https://gist.github.com/jpstroop/4624253 10601 */ 10602 10603 10604(function( $ ){ 10605 10606/** 10607 * @class IIIFTileSource 10608 * @classdesc A client implementation of the International Image Interoperability 10609 * Format: Image API Draft 0.2 10610 * 10611 * @memberof OpenSeadragon 10612 * @extends OpenSeadragon.TileSource 10613 * @see http://library.stanford.edu/iiif/image-api/ 10614 */ 10615$.IIIFTileSource = function( options ){ 10616 10617 $.extend( true, this, options ); 10618 10619 if( !(this.height && this.width && this.identifier && this.tilesUrl ) ){ 10620 throw new Error('IIIF required parameters not provided.'); 10621 } 10622 10623 //TODO: at this point the base tile source implementation assumes 10624 // a tile is a square and so only has one property tileSize 10625 // to store it. It may be possible to make tileSize a vector 10626 // OpenSeadraon.Point but would require careful implementation 10627 // to preserve backward compatibility. 10628 options.tileSize = this.tile_width; 10629 10630 if (! options.maxLevel ) { 10631 var mf = -1; 10632 var scfs = this.scale_factors || this.scale_factor; 10633 if ( scfs instanceof Array ) { 10634 for ( var i = 0; i < scfs.length; i++ ) { 10635 var cf = Number( scfs[i] ); 10636 if ( !isNaN( cf ) && cf > mf ) { mf = cf; } 10637 } 10638 } 10639 if ( mf < 0 ) { options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); } 10640 else { options.maxLevel = mf; } 10641 } 10642 10643 $.TileSource.apply( this, [ options ] ); 10644};
10645 10646$.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{ 10647 /** 10648 * Determine if the data and/or url imply the image service is supported by 10649 * this tile source. 10650 * @method 10651 * @param {Object|Array} data 10652 * @param {String} optional - url 10653 */ 10654 supports: function( data, url ){ 10655 return ( 10656 data.ns && 10657 "http://library.stanford.edu/iiif/image-api/ns/" == data.ns 10658 ) || ( 10659 data.profile && ( 10660 "http://library.stanford.edu/iiif/image-api/compliance.html#level1" == data.profile || 10661 "http://library.stanford.edu/iiif/image-api/compliance.html#level2" == data.profile || 10662 "http://library.stanford.edu/iiif/image-api/compliance.html#level3" == data.profile || 10663 "http://library.stanford.edu/iiif/image-api/compliance.html" == data.profile 10664 ) 10665 ) || ( 10666 data.documentElement && 10667 "info" == data.documentElement.tagName && 10668 "http://library.stanford.edu/iiif/image-api/ns/" == 10669 data.documentElement.namespaceURI 10670 ); 10671 }, 10672 10673 /** 10674 * 10675 * @method 10676 * @param {Object|XMLDocument} data - the raw configuration 10677 * @param {String} url - the url the data was retreived from if any. 10678 * @return {Object} options - A dictionary of keyword arguments sufficient 10679 * to configure this tile source via its constructor. 10680 */ 10681 configure: function( data, url ){ 10682 var service, 10683 options, 10684 host; 10685 10686 if( !$.isPlainObject(data) ){ 10687 10688 options = configureFromXml( this, data ); 10689 10690 }else{ 10691 10692 options = configureFromObject( this, data ); 10693 } 10694 10695 if( url && !options.tilesUrl ){ 10696 service = url.split('/'); 10697 service.pop(); //info.json or info.xml 10698 service = service.join('/'); 10699 if( 'http' !== url.substring( 0, 4 ) ){ 10700 host = location.protocol + '//' + location.host; 10701 service = host + service; 10702 } 10703 options.tilesUrl = service.replace( 10704 data.identifier, 10705 '' 10706 ); 10707 } 10708 10709 return options; 10710 }, 10711 10712 /** 10713 * Responsible for retreiving the url which will return an image for the 10714 * region speified by the given x, y, and level components. 10715 * @method 10716 * @param {Number} level - z index 10717 * @param {Number} x 10718 * @param {Number} y 10719 * @throws {Error} 10720 */ 10721 getTileUrl: function( level, x, y ){ 10722 10723 //# constants 10724 var IIIF_ROTATION = '0', 10725 IIIF_QUALITY = 'native.jpg', 10726 10727 //## get the scale (level as a decimal) 10728 scale = Math.pow( 0.5, this.maxLevel - level ), 10729 10730 //## get iiif size 10731 // iiif_size = 'pct:' + ( scale * 100 ), 10732 10733 //# image dimensions at this level 10734 level_width = Math.ceil( this.width * scale ), 10735 level_height = Math.ceil( this.height * scale ), 10736 10737 //## iiif region 10738 iiif_tile_size_width = Math.ceil( this.tileSize / scale ), 10739 iiif_tile_size_height = Math.ceil( this.tileSize / scale ), 10740 iiif_region, 10741 iiif_tile_x, 10742 iiif_tile_y, 10743 iiif_tile_w, 10744 iiif_tile_h, 10745 iiif_size; 10746 10747 10748 if ( level_width < this.tile_width && level_height < this.tile_height ){ 10749 iiif_size = level_width + ","; // + level_height; only one dim. for IIIF level 1 compliance 10750 iiif_region = 'full'; 10751 } else { 10752 iiif_tile_x = x * iiif_tile_size_width; 10753 iiif_tile_y = y * iiif_tile_size_height; 10754 iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x ); 10755 iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y ); 10756 iiif_size = Math.ceil(iiif_tile_w * scale) + ","; 10757 iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(','); 10758 } 10759 10760 return [ 10761 this.tilesUrl, 10762 this.identifier, 10763 iiif_region, 10764 iiif_size, 10765 IIIF_ROTATION, 10766 IIIF_QUALITY 10767 ].join('/'); 10768 } 10769 10770 10771}); 10772 10773/** 10774 * @private 10775 * @inner 10776 * @function 10777 * @example 10778 * <?xml version="1.0" encoding="UTF-8"?> 10779 * <info xmlns="http://library.stanford.edu/iiif/image-api/ns/"> 10780 * <identifier>1E34750D-38DB-4825-A38A-B60A345E591C</identifier> 10781 * <width>6000</width> 10782 * <height>4000</height> 10783 * <scale_factors> 10784 * <scale_factor>1</scale_factor> 10785 * <scale_factor>2</scale_factor> 10786 * <scale_factor>4</scale_factor> 10787 * </scale_factors> 10788 * <tile_width>1024</tile_width> 10789 * <tile_height>1024</tile_height> 10790 * <formats> 10791 * <format>jpg</format> 10792 * <format>png</format> 10793 * </formats> 10794 * <qualities> 10795 * <quality>native</quality> 10796 * <quality>grey</quality> 10797 * </qualities> 10798 * </info> 10799 */ 10800function configureFromXml( tileSource, xmlDoc ){ 10801 10802 //parse the xml 10803 if ( !xmlDoc || !xmlDoc.documentElement ) { 10804 throw new Error( $.getString( "Errors.Xml" ) ); 10805 } 10806 10807 var root = xmlDoc.documentElement, 10808 rootName = root.tagName, 10809 configuration = null; 10810 10811 if ( rootName == "info" ) { 10812 10813 try { 10814 10815 configuration = { 10816 "ns": root.namespaceURI 10817 }; 10818 10819 parseXML( root, configuration ); 10820 10821 return configureFromObject( tileSource, configuration ); 10822 10823 } catch ( e ) { 10824 throw (e instanceof Error) ? 10825 e : 10826 new Error( $.getString("Errors.IIIF") ); 10827 } 10828 } 10829 10830 throw new Error( $.getString( "Errors.IIIF" ) ); 10831 10832} 10833 10834 10835/** 10836 * @private 10837 * @inner 10838 * @function 10839 */ 10840function parseXML( node, configuration, property ){ 10841 var i, 10842 value; 10843 if( node.nodeType == 3 && property ){//text node 10844 value = node.nodeValue.trim(); 10845 if( value.match(/^\d*$/)){ 10846 value = Number( value ); 10847 } 10848 if( !configuration[ property ] ){ 10849 configuration[ property ] = value; 10850 }else{ 10851 if( !$.isArray( configuration[ property ] ) ){ 10852 configuration[ property ] = [ configuration[ property ] ]; 10853 } 10854 configuration[ property ].push( value ); 10855 } 10856 } else if( node.nodeType == 1 ){ 10857 for( i = 0; i < node.childNodes.length; i++ ){ 10858 parseXML( node.childNodes[ i ], configuration, node.nodeName ); 10859 } 10860 } 10861} 10862 10863 10864/** 10865 * @private 10866 * @inner 10867 * @function 10868 * @example 10869 * { 10870 * "profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level1", 10871 * "identifier" : "1E34750D-38DB-4825-A38A-B60A345E591C", 10872 * "width" : 6000, 10873 * "height" : 4000, 10874 * "scale_factors" : [ 1, 2, 4 ], 10875 * "tile_width" : 1024, 10876 * "tile_height" : 1024, 10877 * "formats" : [ "jpg", "png" ], 10878 * "quality" : [ "native", "grey" ] 10879 * } 10880 */ 10881function configureFromObject( tileSource, configuration ){
10882 //the image_host property is not part of the iiif standard but is included here to 10883 //allow the info.json and info.xml specify a different server to load the 10884 //images from so we can test the implementation. 10885 if( configuration.image_host ){ 10886 configuration.tilesUrl = configuration.image_host; 10887 } 10888 return configuration; 10889} 10890 10891}( OpenSeadragon )); 10892 10893/* 10894 * OpenSeadragon - IIIF1_1TileSource 10895 * 10896 * Copyright (C) 2009 CodePlex Foundation 10897 * Copyright (C) 2010-2013 OpenSeadragon contributors 10898 * 10899 * Redistribution and use in source and binary forms, with or without 10900 * modification, are permitted provided that the following conditions are 10901 * met: 10902 * 10903 * - Redistributions of source code must retain the above copyright notice, 10904 * this list of conditions and the following disclaimer. 10905 * 10906 * - Redistributions in binary form must reproduce the above copyright 10907 * notice, this list of conditions and the following disclaimer in the 10908 * documentation and/or other materials provided with the distribution. 10909 * 10910 * - Neither the name of CodePlex Foundation nor the names of its 10911 * contributors may be used to endorse or promote products derived from 10912 * this software without specific prior written permission. 10913 * 10914 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 10915 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 10916 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 10917 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10918 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10919 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 10920 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 10921 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 10922 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 10923 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 10924 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10925 */ 10926 10927(function( $ ){ 10928 10929/** 10930 * @class IIIF1_1TileSource 10931 * @classdesc A client implementation of the International Image Interoperability 10932 * Format: Image API 1.1 10933 * 10934 * @memberof OpenSeadragon 10935 * @extends OpenSeadragon.TileSource 10936 * @see http://library.stanford.edu/iiif/image-api/ 10937 */ 10938$.IIIF1_1TileSource = function( options ){ 10939 10940 10941 $.extend( true, this, options ); 10942 10943 10944 if ( !( this.height && this.width && this['@id'] ) ){ 10945 throw new Error( 'IIIF required parameters not provided.' ); 10946 } 10947 10948 if ( ( this.profile && 10949 this.profile == "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" ) ){ 10950 // what if not reporting a profile? 10951 throw new Error( 'IIIF Image API 1.1 compliance level 1 or greater is required.' ); 10952 } 10953 10954 if ( this.tile_width ) { 10955 options.tileSize = this.tile_width; 10956 } else if ( this.tile_height ) { 10957 options.tileSize = this.tile_height; 10958 } else { 10959 // use the largest of tileOptions that is smaller than the short 10960 // dimension 10961 10962 var shortDim = Math.min( this.height, this.width ), 10963 tileOptions = [256,512,1024], 10964 smallerTiles = []; 10965 10966 for ( var c = 0; c < tileOptions.length; c++ ) { 10967 if ( tileOptions[c] <= shortDim ) { 10968 smallerTiles.push( tileOptions[c] ); 10969 } 10970 } 10971 10972 if ( smallerTiles.length > 0 ) { 10973 options.tileSize = Math.max.apply( null, smallerTiles ); 10974 } else { 10975 // If we're smaller than 256, just use the short side. 10976 options.tileSize = shortDim; 10977 } 10978 this.tile_width = options.tileSize; // So that 'full' gets used for 10979 this.tile_height = options.tileSize; // the region below 10980 } 10981 10982 if ( !options.maxLevel ) { 10983 var mf = -1; 10984 var scfs = this.scale_factors || this.scale_factor; 10985 if ( scfs instanceof Array ) { 10986 for ( var i = 0; i < scfs.length; i++ ) { 10987 var cf = Number( scfs[i] ); 10988 if ( !isNaN( cf ) && cf > mf ) { mf = cf; } 10989 } 10990 }
10991 if ( mf < 0 ) { options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) ); } 10992 else { options.maxLevel = mf; } 10993 } 10994 10995 $.TileSource.apply( this, [ options ] ); 10996}; 10997 10998$.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIF1_1TileSource.prototype */{ 10999 /** 11000 * Determine if the data and/or url imply the image service is supported by 11001 * this tile source. 11002 * @function 11003 * @param {Object|Array} data 11004 * @param {String} optional - url 11005 */ 11006 supports: function( data, url ) { 11007 return ( data['@context'] && 11008 data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" ); 11009 }, 11010 11011 /** 11012 * 11013 * @function 11014 * @param {Object} data - the raw configuration 11015 * @example <caption>IIIF 1.1 Info Looks like this (XML syntax is no more)</caption> 11016 * { 11017 * "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json", 11018 * "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C", 11019 * "width" : 6000, 11020 * "height" : 4000, 11021 * "scale_factors" : [ 1, 2, 4 ], 11022 * "tile_width" : 1024, 11023 * "tile_height" : 1024, 11024 * "formats" : [ "jpg", "png" ], 11025 * "qualities" : [ "native", "grey" ], 11026 * "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" 11027 * } 11028 */ 11029 configure: function( data ){ 11030 return data; 11031 }, 11032 /** 11033 * Responsible for retreiving the url which will return an image for the 11034 * region specified by the given x, y, and level components. 11035 * @function 11036 * @param {Number} level - z index 11037 * @param {Number} x 11038 * @param {Number} y 11039 * @throws {Error} 11040 */ 11041 getTileUrl: function( level, x, y ){ 11042 11043 //# constants 11044 11045 var IIIF_ROTATION = '0', 11046 IIIF_QUALITY = 'native.jpg', 11047 11048 //## get the scale (level as a decimal) 11049 scale = Math.pow( 0.5, this.maxLevel - level ), 11050 11051 //# image dimensions at this level 11052 levelWidth = Math.ceil( this.width * scale ), 11053 levelHeight = Math.ceil( this.height * scale ), 11054 11055 //## iiif region 11056 iiifTileSizeWidth = Math.ceil( this.tileSize / scale ), 11057 iiifTileSizeHeight = Math.ceil( this.tileSize / scale ), 11058 iiifRegion, 11059 iiifTileX, 11060 iiifTileY, 11061 iiifTileW, 11062 iiifTileH, 11063 iiifSize, 11064 uri; 11065 11066 if ( levelWidth < this.tile_width && levelHeight < this.tile_height ){ 11067 iiifSize = levelWidth + ","; 11068 iiifRegion = 'full'; 11069 } else { 11070 iiifTileX = x * iiifTileSizeWidth; 11071 iiifTileY = y * iiifTileSizeHeight; 11072 iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX ); 11073 iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY ); 11074 11075 iiifSize = Math.ceil( iiifTileW * scale ) + ","; 11076 11077 iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' ); 11078 } 11079 uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, IIIF_QUALITY ].join( '/' ); 11080 return uri; 11081 } 11082 }); 11083 11084}( OpenSeadragon )); 11085 11086/* 11087 * OpenSeadragon - OsmTileSource 11088 * 11089 * Copyright (C) 2009 CodePlex Foundation 11090 * Copyright (C) 2010-2013 OpenSeadragon contributors 11091 * 11092 * Redistribution and use in source and binary forms, with or without 11093 * modification, are permitted provided that the following conditions are 11094 * met: 11095 * 11096 * - Redistributions of source code must retain the above copyright notice, 11097 * this list of conditions and the following disclaimer. 11098 * 11099 * - Redistributions in binary form must reproduce the above copyright 11100 * notice, this list of conditions and the following disclaimer in the 11101 * documentation and/or other materials provided with the distribution. 11102 * 11103 * - Neither the name of CodePlex Foundation nor the names of its 11104 * contributors may be used to endorse or promote products derived from 11105 * this software without specific prior written permission. 11106 * 11107 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11108 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11109 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11110 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11111 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11112 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11113 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11114 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11115 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11116 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11117 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11118 */ 11119 11120/* 11121 * Derived from the OSM tile source in Rainer Simon's seajax-utils project 11122 * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed 11123 * the included code to the OpenSeadragon project under the New BSD license; 11124 * see <https://github.com/openseadragon/openseadragon/issues/58>. 11125 */ 11126 11127 11128(function( $ ){ 11129 11130/** 11131 * @class OsmTileSource 11132 * @classdesc A tilesource implementation for OpenStreetMap.<br><br> 11133 * 11134 * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep 11135 * Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of 11136 * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a 11137 * difference of log2(256)=8 levels.<br><br> 11138 * 11139 * Note 2. Image dimension. According to the OSM Wiki 11140 * (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels) 11141 * the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256 11142 * pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864 11143 * pixels. 11144 * 11145 * @memberof OpenSeadragon 11146 * @extends OpenSeadragon.TileSource 11147 * @param {Number|Object} width - the pixel width of the image or the idiomatic 11148 * options object which is used instead of positional arguments. 11149 * @param {Number} height 11150 * @param {Number} tileSize 11151 * @param {Number} tileOverlap 11152 * @param {String} tilesUrl 11153 */ 11154$.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) { 11155 var options; 11156 11157 if( $.isPlainObject( width ) ){ 11158 options = width; 11159 }else{ 11160 options = { 11161 width: arguments[0], 11162 height: arguments[1], 11163 tileSize: arguments[2], 11164 tileOverlap: arguments[3], 11165 tilesUrl: arguments[4] 11166 }; 11167 } 11168 //apply default setting for standard public OpenStreatMaps service 11169 //but allow them to be specified so fliks can host there own instance 11170 //or apply against other services supportting the same standard 11171 if( !options.width || !options.height ){ 11172 options.width = 65572864; 11173 options.height = 65572864; 11174 } 11175 if( !options.tileSize ){ 11176 options.tileSize = 256; 11177 options.tileOverlap = 0; 11178 } 11179 if( !options.tilesUrl ){ 11180 options.tilesUrl = "http://tile.openstreetmap.org/"; 11181 } 11182 options.minLevel = 8; 11183 11184 $.TileSource.apply( this, [ options ] ); 11185 11186}; 11187 11188$.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.OsmTileSource.prototype */{ 11189 11190 11191 /** 11192 * Determine if the data and/or url imply the image service is supported by 11193 * this tile source. 11194 * @function 11195 * @param {Object|Array} data 11196 * @param {String} optional - url 11197 */ 11198 supports: function( data, url ){ 11199 return ( 11200 data.type && 11201 "openstreetmaps" == data.type 11202 ); 11203 }, 11204 11205 /** 11206 * 11207 * @function 11208 * @param {Object} data - the raw configuration 11209 * @param {String} url - the url the data was retreived from if any. 11210 * @return {Object} options - A dictionary of keyword arguments sufficient 11211 * to configure this tile sources constructor. 11212 */ 11213 configure: function( data, url ){ 11214 return data; 11215 }, 11216 11217 11218 /** 11219 * @function 11220 * @param {Number} level 11221 * @param {Number} x 11222 * @param {Number} y 11223 */ 11224 getTileUrl: function( level, x, y ) { 11225 return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png"; 11226 } 11227}); 11228 11229 11230}( OpenSeadragon )); 11231 11232/* 11233 * OpenSeadragon - TmsTileSource 11234 * 11235 * Copyright (C) 2009 CodePlex Foundation 11236 * Copyright (C) 2010-2013 OpenSeadragon contributors 11237 * 11238 * Redistribution and use in source and binary forms, with or without 11239 * modification, are permitted provided that the following conditions are 11240 * met: 11241 * 11242 * - Redistributions of source code must retain the above copyright notice, 11243 * this list of conditions and the following disclaimer. 11244 * 11245 * - Redistributions in binary form must reproduce the above copyright 11246 * notice, this list of conditions and the following disclaimer in the 11247 * documentation and/or other materials provided with the distribution. 11248 * 11249 * - Neither the name of CodePlex Foundation nor the names of its 11250 * contributors may be used to endorse or promote products derived from 11251 * this software without specific prior written permission. 11252 * 11253 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11254 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11255 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11256 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11257 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11258 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11259 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11260 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11261 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11262 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11263 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11264 */ 11265 11266/* 11267 * Derived from the TMS tile source in Rainer Simon's seajax-utils project 11268 * <http://github.com/rsimon/seajax-utils>. Rainer Simon has contributed 11269 * the included code to the OpenSeadragon project under the New BSD license; 11270 * see <https://github.com/openseadragon/openseadragon/issues/58>. 11271 */ 11272 11273 11274(function( $ ){ 11275 11276/** 11277 * @class TmsTileSource 11278 * @classdesc A tilesource implementation for Tiled Map Services (TMS). 11279 * TMS tile scheme ( [ as supported by OpenLayers ] is described here 11280 * ( http://openlayers.org/dev/examples/tms.html ). 11281 * 11282 * @memberof OpenSeadragon 11283 * @extends OpenSeadragon.TileSource 11284 * @param {Number|Object} width - the pixel width of the image or the idiomatic 11285 * options object which is used instead of positional arguments. 11286 * @param {Number} height 11287 * @param {Number} tileSize 11288 * @param {Number} tileOverlap 11289 * @param {String} tilesUrl 11290 */ 11291$.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) { 11292 var options; 11293 11294 if( $.isPlainObject( width ) ){ 11295 options = width; 11296 }else{ 11297 options = { 11298 width: arguments[0], 11299 height: arguments[1], 11300 tileSize: arguments[2], 11301 tileOverlap: arguments[3], 11302 tilesUrl: arguments[4] 11303 }; 11304 } 11305 // TMS has integer multiples of 256 for width/height and adds buffer 11306 // if necessary -> account for this! 11307 var bufferedWidth = Math.ceil(options.width / 256) * 256, 11308 bufferedHeight = Math.ceil(options.height / 256) * 256, 11309 max; 11310 11311 // Compute number of zoomlevels in this tileset 11312 if (bufferedWidth > bufferedHeight) { 11313 max = bufferedWidth / 256; 11314 } else { 11315 max = bufferedHeight / 256; 11316 } 11317 options.maxLevel = Math.ceil(Math.log(max)/Math.log(2)) - 1; 11318 options.tileSize = 256; 11319 options.width = bufferedWidth; 11320 options.height = bufferedHeight; 11321 11322 $.TileSource.apply( this, [ options ] ); 11323 11324}; 11325 11326$.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TmsTileSource.prototype */{ 11327 11328 11329 /** 11330 * Determine if the data and/or url imply the image service is supported by 11331 * this tile source. 11332 * @function 11333 * @param {Object|Array} data 11334 * @param {String} optional - url 11335 */ 11336 supports: function( data, url ){ 11337 return ( data.type && "tiledmapservice" == data.type ); 11338 }, 11339 11340 /** 11341 * 11342 * @function 11343 * @param {Object} data - the raw configuration 11344 * @param {String} url - the url the data was retreived from if any. 11345 * @return {Object} options - A dictionary of keyword arguments sufficient 11346 * to configure this tile sources constructor. 11347 */ 11348 configure: function( data, url ){ 11349 return data; 11350 }, 11351 11352 11353 /** 11354 * @function 11355 * @param {Number} level 11356 * @param {Number} x 11357 * @param {Number} y 11358 */ 11359 getTileUrl: function( level, x, y ) { 11360 // Convert from Deep Zoom definition to TMS zoom definition 11361 var yTiles = this.getNumTiles( level ).y - 1; 11362 11363 return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png"; 11364 } 11365}); 11366 11367 11368}( OpenSeadragon )); 11369 11370/* 11371 * OpenSeadragon - LegacyTileSource 11372 * 11373 * Copyright (C) 2009 CodePlex Foundation 11374 * Copyright (C) 2010-2013 OpenSeadragon contributors 11375 * 11376 * Redistribution and use in source and binary forms, with or without 11377 * modification, are permitted provided that the following conditions are 11378 * met: 11379 * 11380 * - Redistributions of source code must retain the above copyright notice, 11381 * this list of conditions and the following disclaimer. 11382 * 11383 * - Redistributions in binary form must reproduce the above copyright 11384 * notice, this list of conditions and the following disclaimer in the 11385 * documentation and/or other materials provided with the distribution. 11386 * 11387 * - Neither the name of CodePlex Foundation nor the names of its 11388 * contributors may be used to endorse or promote products derived from 11389 * this software without specific prior written permission. 11390 * 11391 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11392 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11393 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11394 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11395 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11396 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11397 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11398 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11399 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11400 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11401 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11402 */ 11403 11404(function( $ ){ 11405 11406/** 11407 * @class LegacyTileSource 11408 * @classdesc The LegacyTileSource allows simple, traditional image pyramids to be loaded 11409 * into an OpenSeadragon Viewer. Basically, this translates to the historically 11410 * common practice of starting with a 'master' image, maybe a tiff for example, 11411 * and generating a set of 'service' images like one or more thumbnails, a medium 11412 * resolution image and a high resolution image in standard web formats like 11413 * png or jpg. 11414 * 11415 * @memberof OpenSeadragon 11416 * @extends OpenSeadragon.TileSource 11417 * @param {Array} levels An array of file descriptions, each is an object with 11418 * a 'url', a 'width', and a 'height'. Overriding classes can expect more 11419 * properties but these properties are sufficient for this implementation. 11420 * Additionally, the levels are required to be listed in order from 11421 * smallest to largest. 11422 * @property {Number} aspectRatio 11423 * @property {Number} dimensions 11424 * @property {Number} tileSize 11425 * @property {Number} tileOverlap 11426 * @property {Number} minLevel 11427 * @property {Number} maxLevel 11428 * @property {Array} levels 11429 */ 11430$.LegacyTileSource = function( levels ) { 11431 11432 var options, 11433 width, 11434 height; 11435 11436 if( $.isArray( levels ) ){ 11437 options = { 11438 type: 'legacy-image-pyramid', 11439 levels: levels 11440 }; 11441 } 11442 11443 //clean up the levels to make sure we support all formats 11444 options.levels = filterFiles( options.levels ); 11445 11446 if ( options.levels.length > 0 ) { 11447 width = options.levels[ options.levels.length - 1 ].width; 11448 height = options.levels[ options.levels.length - 1 ].height; 11449 } 11450 else { 11451 width = 0; 11452 height = 0; 11453 $.console.error( "No supported image formats found" ); 11454 } 11455 11456 $.extend( true, options, { 11457 width: width, 11458 height: height, 11459 tileSize: Math.max( height, width ), 11460 tileOverlap: 0, 11461 minLevel: 0, 11462 maxLevel: options.levels.length > 0 ? options.levels.length - 1 : 0 11463 } ); 11464 11465 $.TileSource.apply( this, [ options ] ); 11466 11467 this.levels = options.levels; 11468}; 11469 11470$.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.LegacyTileSource.prototype */{ 11471 /** 11472 * Determine if the data and/or url imply the image service is supported by 11473 * this tile source. 11474 * @function 11475 * @param {Object|Array} data 11476 * @param {String} optional - url 11477 */ 11478 supports: function( data, url ){ 11479 return ( 11480 data.type && 11481 "legacy-image-pyramid" == data.type 11482 ) || ( 11483 data.documentElement && 11484 "legacy-image-pyramid" == data.documentElement.getAttribute('type') 11485 ); 11486 }, 11487 11488 11489 /** 11490 * 11491 * @function 11492 * @param {Object|XMLDocument} configuration - the raw configuration 11493 * @param {String} dataUrl - the url the data was retreived from if any. 11494 * @return {Object} options - A dictionary of keyword arguments sufficient 11495 * to configure this tile sources constructor. 11496 */ 11497 configure: function( configuration, dataUrl ){ 11498 11499 var options; 11500 11501 if( !$.isPlainObject(configuration) ){ 11502 11503 options = configureFromXML( this, configuration ); 11504 11505 }else{ 11506 11507 options = configureFromObject( this, configuration ); 11508 } 11509 11510 return options; 11511 11512 }, 11513 11514 /** 11515 * @function 11516 * @param {Number} level 11517 */ 11518 getLevelScale: function ( level ) { 11519 var levelScale = NaN; 11520 if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { 11521 levelScale = 11522 this.levels[ level ].width / 11523 this.levels[ this.maxLevel ].width; 11524 } 11525 return levelScale; 11526 }, 11527 11528 /** 11529 * @function 11530 * @param {Number} level 11531 */ 11532 getNumTiles: function( level ) { 11533 var scale = this.getLevelScale( level ); 11534 if ( scale ){ 11535 return new $.Point( 1, 1 ); 11536 } else { 11537 return new $.Point( 0, 0 ); 11538 } 11539 }, 11540 11541 /** 11542 * @function 11543 * @param {Number} level 11544 * @param {OpenSeadragon.Point} point 11545 */ 11546 getTileAtPoint: function( level, point ) { 11547 return new $.Point( 0, 0 ); 11548 }, 11549 11550 11551 /** 11552 * This method is not implemented by this class other than to throw an Error 11553 * announcing you have to implement it. Because of the variety of tile 11554 * server technologies, and various specifications for building image 11555 * pyramids, this method is here to allow easy integration. 11556 * @function 11557 * @param {Number} level 11558 * @param {Number} x 11559 * @param {Number} y 11560 * @throws {Error} 11561 */ 11562 getTileUrl: function ( level, x, y ) { 11563 var url = null; 11564 if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { 11565 url = this.levels[ level ].url; 11566 } 11567 return url; 11568 } 11569} ); 11570 11571/** 11572 * This method removes any files from the Array which dont conform to our 11573 * basic requirements for a 'level' in the LegacyTileSource. 11574 * @private 11575 * @inner 11576 * @function 11577 */ 11578function filterFiles( files ){ 11579 var filtered = [], 11580 file, 11581 i; 11582 for( i = 0; i < files.length; i++ ){ 11583 file = files[ i ]; 11584 if( file.height && 11585 file.width && 11586 file.url && ( 11587 file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)$/) || ( 11588 file.mimetype && 11589 file.mimetype.toLowerCase().match(/^.*\/(png|jpg|jpeg|gif)$/) 11590 ) 11591 ) ){ 11592 //This is sufficient to serve as a level 11593 filtered.push({ 11594 url: file.url, 11595 width: Number( file.width ), 11596 height: Number( file.height ) 11597 }); 11598 } 11599 else { 11600 $.console.error( 'Unsupported image format: %s', file.url ? file.url : '<no URL>' ); 11601 } 11602 } 11603 11604 return filtered.sort(function(a,b){ 11605 return a.height - b.height; 11606 });
11607 11608} 11609 11610/** 11611 * @private 11612 * @inner 11613 * @function 11614 */ 11615function configureFromXML( tileSource, xmlDoc ){ 11616 11617 if ( !xmlDoc || !xmlDoc.documentElement ) { 11618 throw new Error( $.getString( "Errors.Xml" ) ); 11619 } 11620 11621 var root = xmlDoc.documentElement, 11622 rootName = root.tagName, 11623 conf = null, 11624 levels = [], 11625 level, 11626 i; 11627 11628 if ( rootName == "image" ) { 11629 11630 try { 11631 conf = { 11632 type: root.getAttribute( "type" ), 11633 levels: [] 11634 }; 11635 11636 levels = root.getElementsByTagName( "level" ); 11637 for ( i = 0; i < levels.length; i++ ) { 11638 level = levels[ i ]; 11639 11640 conf.levels .push({ 11641 url: level.getAttribute( "url" ), 11642 width: parseInt( level.getAttribute( "width" ), 10 ), 11643 height: parseInt( level.getAttribute( "height" ), 10 ) 11644 }); 11645 } 11646 11647 return configureFromObject( tileSource, conf ); 11648 11649 } catch ( e ) { 11650 throw (e instanceof Error) ? 11651 e : 11652 new Error( 'Unknown error parsing Legacy Image Pyramid XML.' ); 11653 } 11654 } else if ( rootName == "collection" ) { 11655 throw new Error( 'Legacy Image Pyramid Collections not yet supported.' ); 11656 } else if ( rootName == "error" ) { 11657 throw new Error( 'Error: ' + xmlDoc ); 11658 } 11659 11660 throw new Error( 'Unknown element ' + rootName ); 11661} 11662 11663/** 11664 * @private 11665 * @inner 11666 * @function 11667 */ 11668function configureFromObject( tileSource, configuration ){ 11669 11670 return configuration.levels; 11671 11672} 11673 11674}( OpenSeadragon )); 11675 11676/* 11677 * OpenSeadragon - TileSourceCollection 11678 * 11679 * Copyright (C) 2009 CodePlex Foundation 11680 * Copyright (C) 2010-2013 OpenSeadragon contributors 11681 * 11682 * Redistribution and use in source and binary forms, with or without 11683 * modification, are permitted provided that the following conditions are 11684 * met: 11685 * 11686 * - Redistributions of source code must retain the above copyright notice, 11687 * this list of conditions and the following disclaimer. 11688 * 11689 * - Redistributions in binary form must reproduce the above copyright 11690 * notice, this list of conditions and the following disclaimer in the 11691 * documentation and/or other materials provided with the distribution. 11692 * 11693 * - Neither the name of CodePlex Foundation nor the names of its 11694 * contributors may be used to endorse or promote products derived from 11695 * this software without specific prior written permission. 11696 * 11697 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11698 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11699 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11700 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11701 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11702 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11703 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11704 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11705 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11706 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11707 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11708 */ 11709 11710(function( $ ){ 11711 11712/** 11713 * @class TileSourceCollection 11714 * @memberof OpenSeadragon 11715 * @extends OpenSeadragon.TileSource 11716 */ 11717$.TileSourceCollection = function( tileSize, tileSources, rows, layout ) { 11718 var options; 11719 11720 if( $.isPlainObject( tileSize ) ){ 11721 options = tileSize; 11722 }else{ 11723 options = { 11724 tileSize: arguments[ 0 ], 11725 tileSources: arguments[ 1 ], 11726 rows: arguments[ 2 ], 11727 layout: arguments[ 3 ] 11728 }; 11729 } 11730 11731 if( !options.layout ){ 11732 options.layout = 'horizontal'; 11733 } 11734 11735 var minLevel = 0, 11736 levelSize = 1.0, 11737 tilesPerRow = Math.ceil( options.tileSources.length / options.rows ), 11738 longSide = tilesPerRow >= options.rows ? 11739 tilesPerRow : 11740 options.rows; 11741 11742 if( 'horizontal' == options.layout ){ 11743 options.width = ( options.tileSize ) * tilesPerRow; 11744 options.height = ( options.tileSize ) * options.rows; 11745 } else { 11746 options.height = ( options.tileSize ) * tilesPerRow; 11747 options.width = ( options.tileSize ) * options.rows; 11748 } 11749 11750 options.tileOverlap = -options.tileMargin; 11751 options.tilesPerRow = tilesPerRow; 11752 11753 //Set min level to avoid loading sublevels since collection is a 11754 //different kind of abstraction 11755 11756 while( levelSize < ( options.tileSize ) * longSide ){ 11757 //$.console.log( '%s levelSize %s minLevel %s', options.tileSize * longSide, levelSize, minLevel ); 11758 levelSize = levelSize * 2.0; 11759 minLevel++; 11760 } 11761 options.minLevel = minLevel; 11762 11763 //for( var name in options ){ 11764 // $.console.log( 'Collection %s %s', name, options[ name ] ); 11765 //} 11766 11767 $.TileSource.apply( this, [ options ] ); 11768 11769}; 11770 11771$.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TileSourceCollection.prototype */{ 11772 11773 /** 11774 * @function 11775 * @param {Number} level 11776 * @param {Number} x 11777 * @param {Number} y 11778 */ 11779 getTileBounds: function( level, x, y ) { 11780 var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ), 11781 px = this.tileSize * x - this.tileOverlap, 11782 py = this.tileSize * y - this.tileOverlap, 11783 sx = this.tileSize + 1 * this.tileOverlap, 11784 sy = this.tileSize + 1 * this.tileOverlap, 11785 scale = 1.0 / dimensionsScaled.x; 11786 11787 sx = Math.min( sx, dimensionsScaled.x - px ); 11788 sy = Math.min( sy, dimensionsScaled.y - py ); 11789 11790 return new $.Rect( px * scale, py * scale, sx * scale, sy * scale ); 11791 }, 11792 11793 /** 11794 * 11795 * @function 11796 */ 11797 configure: function( data, url ){ 11798 return; 11799 }, 11800 11801 11802 /** 11803 * @function 11804 * @param {Number} level 11805 * @param {Number} x 11806 * @param {Number} y 11807 */ 11808 getTileUrl: function( level, x, y ) { 11809 //$.console.log([ level, '/', x, '_', y ].join( '' )); 11810 return null; 11811 } 11812 11813 11814 11815}); 11816 11817 11818}( OpenSeadragon )); 11819 11820/* 11821 * OpenSeadragon - Button 11822 * 11823 * Copyright (C) 2009 CodePlex Foundation 11824 * Copyright (C) 2010-2013 OpenSeadragon contributors 11825 * 11826 * Redistribution and use in source and binary forms, with or without 11827 * modification, are permitted provided that the following conditions are 11828 * met: 11829 * 11830 * - Redistributions of source code must retain the above copyright notice, 11831 * this list of conditions and the following disclaimer. 11832 * 11833 * - Redistributions in binary form must reproduce the above copyright 11834 * notice, this list of conditions and the following disclaimer in the 11835 * documentation and/or other materials provided with the distribution. 11836 * 11837 * - Neither the name of CodePlex Foundation nor the names of its 11838 * contributors may be used to endorse or promote products derived from 11839 * this software without specific prior written permission. 11840 * 11841 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11842 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 11843 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 11844 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 11845 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11846 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 11847 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 11848 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 11849 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 11850 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 11851 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11852 */ 11853 11854(function( $ ){ 11855 11856/** 11857 * An enumeration of button states 11858 * @member ButtonState 11859 * @memberof OpenSeadragon 11860 * @static 11861 * @type {Object} 11862 * @property {Number} REST 11863 * @property {Number} GROUP 11864 * @property {Number} HOVER 11865 * @property {Number} DOWN 11866 */ 11867$.ButtonState = { 11868 REST: 0, 11869 GROUP: 1, 11870 HOVER: 2, 11871 DOWN: 3 11872}; 11873 11874/** 11875 * @class Button 11876 * @classdesc Manages events, hover states for individual buttons, tool-tips, as well 11877 * as fading the buttons out when the user has not interacted with them 11878 * for a specified period. 11879 * 11880 * @memberof OpenSeadragon 11881 * @extends OpenSeadragon.EventSource 11882 * @param {Object} options 11883 * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML <button> element is created. 11884 * @param {String} [options.tooltip=null] Provides context help for the button when the 11885 * user hovers over it. 11886 * @param {String} [options.srcRest=null] URL of image to use in 'rest' state. 11887 * @param {String} [options.srcGroup=null] URL of image to use in 'up' state. 11888 * @param {String} [options.srcHover=null] URL of image to use in 'hover' state. 11889 * @param {String} [options.srcDown=null] URL of image to use in 'down' state. 11890 * @param {Number} [options.fadeDelay=0] How long to wait before fading. 11891 * @param {Number} [options.fadeLength=2000] How long should it take to fade the button. 11892 * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}. 11893 * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}. 11894 * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}. 11895 * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}. 11896 * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}. 11897 * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}. 11898 * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}. 11899 */ 11900$.Button = function( options ) { 11901 11902 var _this = this; 11903 11904 $.EventSource.call( this ); 11905 11906 $.extend( true, this, { 11907 11908 tooltip: null, 11909 srcRest: null, 11910 srcGroup: null, 11911 srcHover: null, 11912 srcDown: null, 11913 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold, 11914 clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold, 11915 /** 11916 * How long to wait before fading. 11917 * @member {Number} fadeDelay 11918 * @memberof OpenSeadragon.Button# 11919 */ 11920 fadeDelay: 0, 11921 /** 11922 * How long should it take to fade the button. 11923 * @member {Number} fadeLength 11924 * @memberof OpenSeadragon.Button# 11925 */ 11926 fadeLength: 2000, 11927 onPress: null, 11928 onRelease: null, 11929 onClick: null, 11930 onEnter: null, 11931 onExit: null, 11932 onFocus: null, 11933 onBlur: null 11934 11935 }, options ); 11936 11937 /** 11938 * The button element. 11939 * @member {Element} element 11940 * @memberof OpenSeadragon.Button# 11941 */ 11942 this.element = options.element || $.makeNeutralElement( "div" ); 11943 11944 //if the user has specified the element to bind the control to explicitly 11945 //then do not add the default control images 11946 if ( !options.element ) { 11947 this.imgRest = $.makeTransparentImage( this.srcRest ); 11948 this.imgGroup = $.makeTransparentImage( this.srcGroup ); 11949 this.imgHover = $.makeTransparentImage( this.srcHover ); 11950 this.imgDown = $.makeTransparentImage( this.srcDown ); 11951 11952 this.imgRest.alt = 11953 this.imgGroup.alt = 11954 this.imgHover.alt = 11955 this.imgDown.alt = 11956 this.tooltip; 11957 11958 this.element.style.position = "relative"; 11959 11960 this.imgGroup.style.position = 11961 this.imgHover.style.position = 11962 this.imgDown.style.position = 11963 "absolute"; 11964 11965 this.imgGroup.style.top = 11966 this.imgHover.style.top = 11967 this.imgDown.style.top = 11968 "0px"; 11969 11970 this.imgGroup.style.left = 11971 this.imgHover.style.left = 11972 this.imgDown.style.left = 11973 "0px"; 11974 11975 this.imgHover.style.visibility = 11976 this.imgDown.style.visibility = 11977 "hidden"; 11978 11979 if ( $.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3 ){ 11980 this.imgGroup.style.top = 11981 this.imgHover.style.top = 11982 this.imgDown.style.top = 11983 ""; 11984 } 11985 11986 this.element.appendChild( this.imgRest ); 11987 this.element.appendChild( this.imgGroup ); 11988 this.element.appendChild( this.imgHover ); 11989 this.element.appendChild( this.imgDown ); 11990 } 11991 11992 11993 this.addHandler( "press", this.onPress ); 11994 this.addHandler( "release", this.onRelease ); 11995 this.addHandler( "click", this.onClick ); 11996 this.addHandler( "enter", this.onEnter ); 11997 this.addHandler( "exit", this.onExit ); 11998 this.addHandler( "focus", this.onFocus ); 11999 this.addHandler( "blur", this.onBlur ); 12000 12001 /** 12002 * The button's current state. 12003 * @member {OpenSeadragon.ButtonState} currentState 12004 * @memberof OpenSeadragon.Button# 12005 */ 12006 this.currentState = $.ButtonState.GROUP; 12007 12008 // When the button last began to fade. 12009 this.fadeBeginTime = null; 12010 // Whether this button should fade after user stops interacting with the viewport. 12011 this.shouldFade = false; 12012 12013 this.element.style.display = "inline-block"; 12014 this.element.style.position = "relative"; 12015 this.element.title = this.tooltip; 12016 12017 /** 12018 * Tracks mouse/touch/key events on the button. 12019 * @member {OpenSeadragon.MouseTracker} tracker 12020 * @memberof OpenSeadragon.Button# 12021 */ 12022 this.tracker = new $.MouseTracker({ 12023 12024 element: this.element, 12025 clickTimeThreshold: this.clickTimeThreshold, 12026 clickDistThreshold: this.clickDistThreshold, 12027 12028 enterHandler: function( event ) { 12029 if ( event.insideElementPressed ) { 12030 inTo( _this, $.ButtonState.DOWN ); 12031 /** 12032 * Raised when the cursor enters the Button element. 12033 * 12034 * @event enter 12035 * @memberof OpenSeadragon.Button 12036 * @type {object} 12037 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12038 * @property {Object} originalEvent - The original DOM event.
12039 * @property {?Object} userData - Arbitrary subscriber-defined object. 12040 */ 12041 _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); 12042 } else if ( !event.buttonDownAny ) { 12043 inTo( _this, $.ButtonState.HOVER ); 12044 } 12045 }, 12046 12047 focusHandler: function ( event ) { 12048 this.enterHandler( event ); 12049 /** 12050 * Raised when the Button element receives focus. 12051 * 12052 * @event focus 12053 * @memberof OpenSeadragon.Button 12054 * @type {object} 12055 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12056 * @property {Object} originalEvent - The original DOM event. 12057 * @property {?Object} userData - Arbitrary subscriber-defined object. 12058 */ 12059 _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); 12060 }, 12061 12062 exitHandler: function( event ) { 12063 outTo( _this, $.ButtonState.GROUP ); 12064 if ( event.insideElementPressed ) { 12065 /** 12066 * Raised when the cursor leaves the Button element. 12067 * 12068 * @event exit 12069 * @memberof OpenSeadragon.Button 12070 * @type {object} 12071 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12072 * @property {Object} originalEvent - The original DOM event. 12073 * @property {?Object} userData - Arbitrary subscriber-defined object. 12074 */ 12075 _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); 12076 } 12077 }, 12078 12079 blurHandler: function ( event ) { 12080 this.exitHandler( event ); 12081 /** 12082 * Raised when the Button element loses focus. 12083 * 12084 * @event blur 12085 * @memberof OpenSeadragon.Button 12086 * @type {object} 12087 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12088 * @property {Object} originalEvent - The original DOM event. 12089 * @property {?Object} userData - Arbitrary subscriber-defined object. 12090 */ 12091 _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); 12092 }, 12093 12094 pressHandler: function ( event ) { 12095 inTo( _this, $.ButtonState.DOWN ); 12096 /** 12097 * Raised when a mouse button is pressed or touch occurs in the Button element. 12098 * 12099 * @event press 12100 * @memberof OpenSeadragon.Button 12101 * @type {object} 12102 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12103 * @property {Object} originalEvent - The original DOM event. 12104 * @property {?Object} userData - Arbitrary subscriber-defined object. 12105 */ 12106 _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); 12107 }, 12108 12109 releaseHandler: function( event ) { 12110 if ( event.insideElementPressed && event.insideElementReleased ) { 12111 outTo( _this, $.ButtonState.HOVER ); 12112 /** 12113 * Raised when the mouse button is released or touch ends in the Button element. 12114 * 12115 * @event release 12116 * @memberof OpenSeadragon.Button 12117 * @type {object} 12118 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12119 * @property {Object} originalEvent - The original DOM event. 12120 * @property {?Object} userData - Arbitrary subscriber-defined object. 12121 */ 12122 _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); 12123 } else if ( event.insideElementPressed ) { 12124 outTo( _this, $.ButtonState.GROUP ); 12125 } else { 12126 inTo( _this, $.ButtonState.HOVER ); 12127 } 12128 }, 12129 12130 clickHandler: function( event ) { 12131 if ( event.quick ) { 12132 /** 12133 * 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. 12134 * 12135 * @event click 12136 * @memberof OpenSeadragon.Button 12137 * @type {object} 12138 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12139 * @property {Object} originalEvent - The original DOM event.
12140 * @property {?Object} userData - Arbitrary subscriber-defined object. 12141 */ 12142 _this.raiseEvent("click", { originalEvent: event.originalEvent }); 12143 } 12144 }, 12145 12146 keyHandler: function( event ){ 12147 //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode); 12148 if( 13 === event.keyCode ){ 12149 /*** 12150 * 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. 12151 * 12152 * @event click 12153 * @memberof OpenSeadragon.Button 12154 * @type {object} 12155 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12156 * @property {Object} originalEvent - The original DOM event. 12157 * @property {?Object} userData - Arbitrary subscriber-defined object. 12158 */ 12159 _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); 12160 /*** 12161 * Raised when the mouse button is released or touch ends in the Button element. 12162 * 12163 * @event release 12164 * @memberof OpenSeadragon.Button 12165 * @type {object} 12166 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. 12167 * @property {Object} originalEvent - The original DOM event. 12168 * @property {?Object} userData - Arbitrary subscriber-defined object. 12169 */ 12170 _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); 12171 return false; 12172 } 12173 return true; 12174 } 12175 12176 }).setTracking( true ); 12177 12178 outTo( this, $.ButtonState.REST ); 12179}; 12180 12181$.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{ 12182 12183 /** 12184 * TODO: Determine what this function is intended to do and if it's actually 12185 * useful as an API point. 12186 * @function 12187 */ 12188 notifyGroupEnter: function() { 12189 inTo( this, $.ButtonState.GROUP ); 12190 }, 12191 12192 /** 12193 * TODO: Determine what this function is intended to do and if it's actually 12194 * useful as an API point. 12195 * @function 12196 */ 12197 notifyGroupExit: function() { 12198 outTo( this, $.ButtonState.REST ); 12199 }, 12200 12201 /** 12202 * @function 12203 */ 12204 disable: function(){ 12205 this.notifyGroupExit(); 12206 this.element.disabled = true; 12207 $.setElementOpacity( this.element, 0.2, true ); 12208 }, 12209 12210 /** 12211 * @function 12212 */ 12213 enable: function(){ 12214 this.element.disabled = false; 12215 $.setElementOpacity( this.element, 1.0, true ); 12216 this.notifyGroupEnter(); 12217 } 12218 12219}); 12220 12221 12222function scheduleFade( button ) { 12223 $.requestAnimationFrame(function(){ 12224 updateFade( button ); 12225 }); 12226} 12227 12228function updateFade( button ) { 12229 var currentTime, 12230 deltaTime, 12231 opacity; 12232 12233 if ( button.shouldFade ) { 12234 currentTime = $.now(); 12235 deltaTime = currentTime - button.fadeBeginTime; 12236 opacity = 1.0 - deltaTime / button.fadeLength; 12237 opacity = Math.min( 1.0, opacity ); 12238 opacity = Math.max( 0.0, opacity ); 12239 12240 if( button.imgGroup ){ 12241 $.setElementOpacity( button.imgGroup, opacity, true ); 12242 } 12243 if ( opacity > 0 ) { 12244 // fade again 12245 scheduleFade( button ); 12246 } 12247 } 12248} 12249 12250function beginFading( button ) { 12251 button.shouldFade = true; 12252 button.fadeBeginTime = $.now() + button.fadeDelay; 12253 window.setTimeout( function(){ 12254 scheduleFade( button ); 12255 }, button.fadeDelay ); 12256} 12257 12258function stopFading( button ) { 12259 button.shouldFade = false; 12260 if( button.imgGroup ){ 12261 $.setElementOpacity( button.imgGroup, 1.0, true ); 12262 } 12263} 12264 12265function inTo( button, newState ) { 12266 12267 if( button.element.disabled ){ 12268 return; 12269 } 12270 12271 if ( newState >= $.ButtonState.GROUP && 12272 button.currentState == $.ButtonState.REST ) { 12273 stopFading( button ); 12274 button.currentState = $.ButtonState.GROUP; 12275 } 12276 12277 if ( newState >= $.ButtonState.HOVER && 12278 button.currentState == $.ButtonState.GROUP ) { 12279 if( button.imgHover ){ 12280 button.imgHover.style.visibility = ""; 12281 } 12282 button.currentState = $.ButtonState.HOVER; 12283 } 12284 12285 if ( newState >= $.ButtonState.DOWN && 12286 button.currentState == $.ButtonState.HOVER ) { 12287 if( button.imgDown ){ 12288 button.imgDown.style.visibility = ""; 12289 } 12290 button.currentState = $.ButtonState.DOWN; 12291 } 12292} 12293 12294 12295function outTo( button, newState ) { 12296 12297 if( button.element.disabled ){ 12298 return; 12299 } 12300 12301 if ( newState <= $.ButtonState.HOVER && 12302 button.currentState == $.ButtonState.DOWN ) { 12303 if( button.imgDown ){ 12304 button.imgDown.style.visibility = "hidden"; 12305 } 12306 button.currentState = $.ButtonState.HOVER; 12307 } 12308 12309 if ( newState <= $.ButtonState.GROUP && 12310 button.currentState == $.ButtonState.HOVER ) { 12311 if( button.imgHover ){ 12312 button.imgHover.style.visibility = "hidden"; 12313 } 12314 button.currentState = $.ButtonState.GROUP; 12315 } 12316 12317 if ( newState <= $.ButtonState.REST && 12318 button.currentState == $.ButtonState.GROUP ) { 12319 beginFading( button ); 12320 button.currentState = $.ButtonState.REST; 12321 } 12322} 12323 12324 12325 12326}( OpenSeadragon )); 12327 12328/* 12329 * OpenSeadragon - ButtonGroup 12330 * 12331 * Copyright (C) 2009 CodePlex Foundation 12332 * Copyright (C) 2010-2013 OpenSeadragon contributors 12333 * 12334 * Redistribution and use in source and binary forms, with or without 12335 * modification, are permitted provided that the following conditions are 12336 * met: 12337 * 12338 * - Redistributions of source code must retain the above copyright notice, 12339 * this list of conditions and the following disclaimer. 12340 * 12341 * - Redistributions in binary form must reproduce the above copyright 12342 * notice, this list of conditions and the following disclaimer in the 12343 * documentation and/or other materials provided with the distribution. 12344 * 12345 * - Neither the name of CodePlex Foundation nor the names of its 12346 * contributors may be used to endorse or promote products derived from 12347 * this software without specific prior written permission. 12348 * 12349 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12350 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12351 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 12352 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 12353 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
12354 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 12355 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12356 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 12357 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 12358 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 12359 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12360 */ 12361 12362(function( $ ){ 12363/** 12364 * @class ButtonGroup 12365 * @classdesc Manages events on groups of buttons. 12366 * 12367 * @memberof OpenSeadragon 12368 * @param {Object} options - A dictionary of settings applied against the entire group of buttons. 12369 * @param {Array} options.buttons Array of buttons 12370 * @param {Element} [options.element] Element to use as the container 12371 **/ 12372$.ButtonGroup = function( options ) { 12373 12374 $.extend( true, this, { 12375 /** 12376 * An array containing the buttons themselves. 12377 * @member {Array} buttons 12378 * @memberof OpenSeadragon.ButtonGroup# 12379 */ 12380 buttons: [], 12381 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold, 12382 clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold, 12383 labelText: "" 12384 }, options ); 12385 12386 // copy the button elements TODO: Why? 12387 var buttons = this.buttons.concat([]), 12388 _this = this, 12389 i; 12390 12391 /** 12392 * The shared container for the buttons. 12393 * @member {Element} element 12394 * @memberof OpenSeadragon.ButtonGroup# 12395 */ 12396 this.element = options.element || $.makeNeutralElement( "div" ); 12397 12398 // TODO What if there IS an options.group specified? 12399 if( !options.group ){ 12400 this.label = $.makeNeutralElement( "label" ); 12401 //TODO: support labels for ButtonGroups 12402 //this.label.innerHTML = this.labelText; 12403 this.element.style.display = "inline-block"; 12404 this.element.appendChild( this.label ); 12405 for ( i = 0; i < buttons.length; i++ ) { 12406 this.element.appendChild( buttons[ i ].element ); 12407 } 12408 } 12409 12410 /** 12411 * Tracks mouse/touch/key events accross the group of buttons. 12412 * @member {OpenSeadragon.MouseTracker} tracker 12413 * @memberof OpenSeadragon.ButtonGroup# 12414 */ 12415 this.tracker = new $.MouseTracker({ 12416 element: this.element, 12417 clickTimeThreshold: this.clickTimeThreshold, 12418 clickDistThreshold: this.clickDistThreshold, 12419 enterHandler: function ( event ) { 12420 var i; 12421 for ( i = 0; i < _this.buttons.length; i++ ) { 12422 _this.buttons[ i ].notifyGroupEnter(); 12423 } 12424 }, 12425 exitHandler: function ( event ) { 12426 var i; 12427 if ( !event.insideElementPressed ) { 12428 for ( i = 0; i < _this.buttons.length; i++ ) { 12429 _this.buttons[ i ].notifyGroupExit(); 12430 } 12431 } 12432 }, 12433 pressHandler: function ( event ) { 12434 if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { 12435 var i; 12436 for ( i = 0; i < _this.buttons.length; i++ ) { 12437 _this.buttons[ i ].notifyGroupEnter(); 12438 } 12439 } 12440 }, 12441 releaseHandler: function ( event ) { 12442 var i; 12443 if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { 12444 for ( i = 0; i < _this.buttons.length; i++ ) { 12445 _this.buttons[ i ].notifyGroupExit(); 12446 } 12447 } 12448 } 12449 }).setTracking( true ); 12450}; 12451 12452$.ButtonGroup.prototype = /** @lends OpenSeadragon.ButtonGroup.prototype */{ 12453 12454 /** 12455 * TODO: Figure out why this is used on the public API and if a more useful 12456 * api can be created. 12457 * @function 12458 * @private 12459 */ 12460 emulateEnter: function() { 12461 this.tracker.enterHandler( { eventSource: this.tracker } ); 12462 }, 12463 12464 /** 12465 * TODO: Figure out why this is used on the public API and if a more useful 12466 * api can be created. 12467 * @function 12468 * @private 12469 */ 12470 emulateExit: function() { 12471 this.tracker.exitHandler( { eventSource: this.tracker } ); 12472 } 12473}; 12474 12475 12476}( OpenSeadragon )); 12477 12478/* 12479 * OpenSeadragon - Rect 12480 * 12481 * Copyright (C) 2009 CodePlex Foundation 12482 * Copyright (C) 2010-2013 OpenSeadragon contributors 12483 * 12484 * Redistribution and use in source and binary forms, with or without 12485 * modification, are permitted provided that the following conditions are 12486 * met: 12487 * 12488 * - Redistributions of source code must retain the above copyright notice, 12489 * this list of conditions and the following disclaimer. 12490 * 12491 * - Redistributions in binary form must reproduce the above copyright 12492 * notice, this list of conditions and the following disclaimer in the 12493 * documentation and/or other materials provided with the distribution. 12494 * 12495 * - Neither the name of CodePlex Foundation nor the names of its 12496 * contributors may be used to endorse or promote products derived from 12497 * this software without specific prior written permission. 12498 * 12499 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12500 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12501 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 12502 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 12503 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
12504 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 12505 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12506 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 12507 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 12508 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 12509 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12510 */ 12511 12512(function( $ ){ 12513 12514/** 12515 * @class Rect 12516 * @classdesc A Rectangle really represents a 2x2 matrix where each row represents a 12517 * 2 dimensional vector component, the first is (x,y) and the second is 12518 * (width, height). The latter component implies the equation of a simple 12519 * plane. 12520 * 12521 * @memberof OpenSeadragon 12522 * @param {Number} x The vector component 'x'. 12523 * @param {Number} y The vector component 'y'. 12524 * @param {Number} width The vector component 'height'. 12525 * @param {Number} height The vector component 'width'. 12526 */ 12527$.Rect = function( x, y, width, height ) { 12528 /** 12529 * The vector component 'x'. 12530 * @member {Number} x 12531 * @memberof OpenSeadragon.Rect# 12532 */ 12533 this.x = typeof ( x ) == "number" ? x : 0; 12534 /** 12535 * The vector component 'y'. 12536 * @member {Number} y 12537 * @memberof OpenSeadragon.Rect# 12538 */ 12539 this.y = typeof ( y ) == "number" ? y : 0; 12540 /** 12541 * The vector component 'width'. 12542 * @member {Number} width 12543 * @memberof OpenSeadragon.Rect# 12544 */ 12545 this.width = typeof ( width ) == "number" ? width : 0; 12546 /** 12547 * The vector component 'height'. 12548 * @member {Number} height 12549 * @memberof OpenSeadragon.Rect# 12550 */ 12551 this.height = typeof ( height ) == "number" ? height : 0; 12552}; 12553 12554$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ 12555 12556 /** 12557 * The aspect ratio is simply the ratio of width to height. 12558 * @function 12559 * @returns {Number} The ratio of width to height. 12560 */ 12561 getAspectRatio: function() { 12562 return this.width / this.height; 12563 }, 12564 12565 /** 12566 * Provides the coordinates of the upper-left corner of the rectangle as a 12567 * point. 12568 * @function 12569 * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of 12570 * the rectangle. 12571 */ 12572 getTopLeft: function() { 12573 return new $.Point( 12574 this.x, 12575 this.y 12576 ); 12577 }, 12578 12579 /** 12580 * Provides the coordinates of the bottom-right corner of the rectangle as a 12581 * point. 12582 * @function 12583 * @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of 12584 * the rectangle. 12585 */ 12586 getBottomRight: function() { 12587 return new $.Point( 12588 this.x + this.width, 12589 this.y + this.height 12590 ); 12591 }, 12592 12593 /** 12594 * Provides the coordinates of the top-right corner of the rectangle as a 12595 * point. 12596 * @function 12597 * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of 12598 * the rectangle. 12599 */ 12600 getTopRight: function() { 12601 return new $.Point( 12602 this.x + this.width, 12603 this.y 12604 ); 12605 }, 12606 12607 /** 12608 * Provides the coordinates of the bottom-left corner of the rectangle as a 12609 * point. 12610 * @function 12611 * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of 12612 * the rectangle. 12613 */ 12614 getBottomLeft: function() { 12615 return new $.Point( 12616 this.x, 12617 this.y + this.height 12618 ); 12619 }, 12620 12621 /** 12622 * Computes the center of the rectangle. 12623 * @function 12624 * @returns {OpenSeadragon.Point} The center of the rectangle as represented 12625 * as represented by a 2-dimensional vector (x,y) 12626 */ 12627 getCenter: function() { 12628 return new $.Point( 12629 this.x + this.width / 2.0, 12630 this.y + this.height / 2.0 12631 ); 12632 }, 12633 12634 /** 12635 * Returns the width and height component as a vector OpenSeadragon.Point 12636 * @function 12637 * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the 12638 * the width and height of the rectangle. 12639 */ 12640 getSize: function() { 12641 return new $.Point( this.width, this.height ); 12642 }, 12643 12644 /** 12645 * Determines if two Rectangles have equivalent components. 12646 * @function 12647 * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to. 12648 * @return {Boolean}
12648 'true' if all components are equal, otherwise 'false'. 12649 */ 12650 equals: function( other ) { 12651 return ( other instanceof $.Rect ) && 12652 ( this.x === other.x ) && 12653 ( this.y === other.y ) && 12654 ( this.width === other.width ) && 12655 ( this.height === other.height ); 12656 }, 12657 12658 /** 12659 * Rotates a rectangle around a point. Currently only 90, 180, and 270 12660 * degrees are supported. 12661 * @function 12662 * @param {Number} degrees The angle in degrees to rotate. 12663 * @param {OpenSeadragon.Point} pivot The point about which to rotate. 12664 * Defaults to the center of the rectangle. 12665 * @return {OpenSeadragon.Rect} 12666 */ 12667 rotate: function( degrees, pivot ) { 12668 // TODO support arbitrary rotation 12669 var width = this.width, 12670 height = this.height, 12671 newTopLeft; 12672 12673 degrees = ( degrees + 360 ) % 360; 12674 if( degrees % 90 !== 0 ) { 12675 throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); 12676 } 12677 12678 if( degrees === 0 ){ 12679 return new $.Rect( 12680 this.x, 12681 this.y, 12682 this.width, 12683 this.height 12684 ); 12685 } 12686 12687 pivot = pivot || this.getCenter(); 12688 12689 switch ( degrees ) { 12690 case 90: 12691 newTopLeft = this.getBottomLeft(); 12692 width = this.height; 12693 height = this.width; 12694 break; 12695 case 180: 12696 newTopLeft = this.getBottomRight(); 12697 break; 12698 case 270: 12699 newTopLeft = this.getTopRight(); 12700 width = this.height; 12701 height = this.width; 12702 break; 12703 default: 12704 newTopLeft = this.getTopLeft(); 12705 break; 12706 } 12707 12708 newTopLeft = newTopLeft.rotate(degrees, pivot); 12709 12710 return new $.Rect(newTopLeft.x, newTopLeft.y, width, height); 12711 }, 12712 12713 /** 12714 * Provides a string representation of the rectangle which is useful for 12715 * debugging. 12716 * @function 12717 * @returns {String} A string representation of the rectangle. 12718 */ 12719 toString: function() { 12720 return "[" + 12721 Math.round(this.x*100) + "," + 12722 Math.round(this.y*100) + "," + 12723 Math.round(this.width*100) + "x" + 12724 Math.round(this.height*100) + 12725 "]"; 12726 } 12727}; 12728 12729 12730}( OpenSeadragon )); 12731 12732/* 12733 * OpenSeadragon - ReferenceStrip 12734 * 12735 * Copyright (C) 2009 CodePlex Foundation 12736 * Copyright (C) 2010-2013 OpenSeadragon contributors 12737 * 12738 * Redistribution and use in source and binary forms, with or without 12739 * modification, are permitted provided that the following conditions are 12740 * met: 12741 * 12742 * - Redistributions of source code must retain the above copyright notice, 12743 * this list of conditions and the following disclaimer. 12744 * 12745 * - Redistributions in binary form must reproduce the above copyright 12746 * notice, this list of conditions and the following disclaimer in the 12747 * documentation and/or other materials provided with the distribution. 12748 * 12749 * - Neither the name of CodePlex Foundation nor the names of its 12750 * contributors may be used to endorse or promote products derived from 12751 * this software without specific prior written permission. 12752 * 12753 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12754 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12755 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 12756 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 12757 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 12758 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 12759 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 12760 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 12761 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 12762 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 12763 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12764 */ 12765 12766(function ( $ ) { 12767 12768// dictionary from id to private properties 12769var THIS = {}; 12770 12771/** 12772 * The CollectionDrawer is a reimplementation if the Drawer API that 12773 * focuses on allowing a viewport to be redefined as a collection 12774 * of smaller viewports, defined by a clear number of rows and / or 12775 * columns of which each item in the matrix of viewports has its own 12776 * source. 12777 * 12778 * This idea is a reexpression of the idea of dzi collections 12779 * which allows a clearer algorithm to reuse the tile sources already 12780 * supported by OpenSeadragon, in heterogenious or homogenious 12781 * sequences just like mixed groups already supported by the viewer
12782 * for the purpose of image sequnces. 12783 * 12784 * TODO: The difficult part of this feature is figuring out how to express 12785 * this functionality as a combination of the functionality already 12786 * provided by Drawer, Viewport, TileSource, and Navigator. It may 12787 * require better abstraction at those points in order to effeciently 12788 * reuse those paradigms. 12789 */ 12790/** 12791 * @class ReferenceStrip 12792 * @memberof OpenSeadragon 12793 * @param {Object} options 12794 */ 12795$.ReferenceStrip = function ( options ) { 12796 12797 var _this = this, 12798 viewer = options.viewer, 12799 viewerSize = $.getElementSize( viewer.element ), 12800 element, 12801 style, 12802 i; 12803 12804 //We may need to create a new element and id if they did not 12805 //provide the id for the existing element 12806 if ( !options.id ) { 12807 options.id = 'referencestrip-' + $.now(); 12808 this.element = $.makeNeutralElement( "div" ); 12809 this.element.id = options.id; 12810 this.element.className = 'referencestrip'; 12811 } 12812 12813 options = $.extend( true, { 12814 sizeRatio: $.DEFAULT_SETTINGS.referenceStripSizeRatio, 12815 position: $.DEFAULT_SETTINGS.referenceStripPosition, 12816 scroll: $.DEFAULT_SETTINGS.referenceStripScroll, 12817 clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold 12818 }, options, { 12819 //required overrides 12820 element: this.element, 12821 //These need to be overridden to prevent recursion since 12822 //the navigator is a viewer and a viewer has a navigator 12823 showNavigator: false, 12824 mouseNavEnabled: false, 12825 showNavigationControl: false, 12826 showSequenceControl: false 12827 } ); 12828 12829 $.extend( this, options ); 12830 //Private state properties 12831 THIS[this.id] = { 12832 "animating": false 12833 }; 12834 12835 this.minPixelRatio = this.viewer.minPixelRatio; 12836 12837 style = this.element.style; 12838 style.marginTop = '0px'; 12839 style.marginRight = '0px'; 12840 style.marginBottom = '0px'; 12841 style.marginLeft = '0px'; 12842 style.left = '0px'; 12843 style.bottom = '0px'; 12844 style.border = '0px'; 12845 style.background = '#000'; 12846 style.position = 'relative'; 12847 12848 $.setElementOpacity( this.element, 0.8 ); 12849 12850 this.viewer = viewer; 12851 this.innerTracker = new $.MouseTracker( { 12852 element: this.element, 12853 dragHandler: $.delegate( this, onStripDrag ), 12854 scrollHandler: $.delegate( this, onStripScroll ), 12855 enterHandler: $.delegate( this, onStripEnter ), 12856 exitHandler: $.delegate( this, onStripExit ), 12857 keyHandler: $.delegate( this, onKeyPress ) 12858 } ).setTracking( true ); 12859 12860 //Controls the position and orientation of the reference strip and sets the 12861 //appropriate width and height 12862 if ( options.width && options.height ) { 12863 this.element.style.width = options.width + 'px'; 12864 this.element.style.height = options.height + 'px'; 12865 viewer.addControl( 12866 this.element, 12867 { anchor: $.ControlAnchor.BOTTOM_LEFT } 12868 ); 12869 } else { 12870 if ( "horizontal" == options.scroll ) { 12871 this.element.style.width = (
12872 viewerSize.x * 12873 options.sizeRatio * 12874 viewer.tileSources.length 12875 ) + ( 12 * viewer.tileSources.length ) + 'px'; 12876 12877 this.element.style.height = ( 12878 viewerSize.y * 12879 options.sizeRatio 12880 ) + 'px'; 12881 12882 viewer.addControl( 12883 this.element, 12884 { anchor: $.ControlAnchor.BOTTOM_LEFT } 12885 ); 12886 } else { 12887 this.element.style.height = ( 12888 viewerSize.y * 12889 options.sizeRatio * 12890 viewer.tileSources.length 12891 ) + ( 12 * viewer.tileSources.length ) + 'px'; 12892 12893 this.element.style.width = ( 12894 viewerSize.x * 12895 options.sizeRatio 12896 ) + 'px'; 12897 12898 viewer.addControl( 12899 this.element, 12900 { anchor: $.ControlAnchor.TOP_LEFT } 12901 ); 12902 12903 } 12904 } 12905 12906 this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8; 12907 this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8; 12908 this.panels = []; 12909 12910 /*jshint loopfunc:true*/ 12911 for ( i = 0; i < viewer.tileSources.length; i++ ) { 12912 12913 element = $.makeNeutralElement( 'div' ); 12914 element.id = this.element.id + "-" + i; 12915 12916 element.style.width = _this.panelWidth + 'px'; 12917 element.style.height = _this.panelHeight + 'px'; 12918 element.style.display = 'inline'; 12919 element.style.float = 'left'; //Webkit 12920 element.style.cssFloat = 'left'; //Firefox 12921 element.style.styleFloat = 'left'; //IE 12922 element.style.padding = '2px'; 12923 12924 element.innerTracker = new $.MouseTracker( { 12925 element: element, 12926 clickTimeThreshold: this.clickTimeThreshold, 12927 clickDistThreshold: this.clickDistThreshold, 12928 pressHandler: function ( event ) { 12929 event.eventSource.dragging = $.now(); 12930 }, 12931 releaseHandler: function ( event ) { 12932 var tracker = event.eventSource, 12933 id = tracker.element.id, 12934 page = Number( id.split( '-' )[2] ), 12935 now = $.now(); 12936 12937 if ( event.insideElementPressed && 12938 event.insideElementReleased && 12939 tracker.dragging && 12940 ( now - tracker.dragging ) < tracker.clickTimeThreshold ) { 12941 tracker.dragging = null; 12942 viewer.goToPage( page ); 12943 } 12944 } 12945 } ).setTracking( true ); 12946 12947 this.element.appendChild( element ); 12948 12949 element.activePanel = false; 12950 12951 this.panels.push( element ); 12952 12953 } 12954 loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.y, 0 ); 12955 this.setFocus( 0 ); 12956 12957}; 12958 12959$.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.prototype */{ 12960 12961 /** 12962 * @function 12963 */ 12964 setFocus: function ( page ) { 12965 var element = $.getElement( this.element.id + '-' + page ),
12966 viewerSize = $.getElementSize( this.viewer.canvas ), 12967 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 12968 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ), 12969 offsetLeft = -Number( this.element.style.marginLeft.replace( 'px', '' ) ), 12970 offsetTop = -Number( this.element.style.marginTop.replace( 'px', '' ) ), 12971 offset; 12972 12973 if ( this.currentSelected !== element ) { 12974 if ( this.currentSelected ) { 12975 this.currentSelected.style.background = '#000'; 12976 } 12977 this.currentSelected = element; 12978 this.currentSelected.style.background = '#999'; 12979 12980 if ( 'horizontal' == this.scroll ) { 12981 //right left 12982 offset = ( Number( page ) ) * ( this.panelWidth + 3 ); 12983 if ( offset > offsetLeft + viewerSize.x - this.panelWidth ) { 12984 offset = Math.min( offset, ( scrollWidth - viewerSize.x ) ); 12985 this.element.style.marginLeft = -offset + 'px'; 12986 loadPanels( this, viewerSize.x, -offset ); 12987 } else if ( offset < offsetLeft ) { 12988 offset = Math.max( 0, offset - viewerSize.x / 2 ); 12989 this.element.style.marginLeft = -offset + 'px'; 12990 loadPanels( this, viewerSize.x, -offset ); 12991 } 12992 } else { 12993 offset = ( Number( page ) ) * ( this.panelHeight + 3 ); 12994 if ( offset > offsetTop + viewerSize.y - this.panelHeight ) { 12995 offset = Math.min( offset, ( scrollHeight - viewerSize.y ) ); 12996 this.element.style.marginTop = -offset + 'px'; 12997 loadPanels( this, viewerSize.y, -offset ); 12998 } else if ( offset < offsetTop ) { 12999 offset = Math.max( 0, offset - viewerSize.y / 2 ); 13000 this.element.style.marginTop = -offset + 'px'; 13001 loadPanels( this, viewerSize.y, -offset ); 13002 } 13003 } 13004 13005 this.currentPage = page; 13006 $.getElement( element.id + '-displayregion' ).focus(); 13007 onStripEnter.call( this, { eventSource: this.innerTracker } ); 13008 } 13009 }, 13010 13011 /** 13012 * @function 13013 */ 13014 update: function () { 13015 if ( THIS[this.id].animating ) { 13016 $.console.log( 'image reference strip update' ); 13017 return true; 13018 } 13019 return false; 13020 } 13021 13022} ); 13023 13024 13025 13026 13027/** 13028 * @private 13029 * @inner 13030 * @function 13031 */ 13032function onStripDrag( event ) { 13033 13034 var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), 13035 offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), 13036 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 13037 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
13038 viewerSize = $.getElementSize( this.viewer.canvas ); 13039 this.dragging = true; 13040 if ( this.element ) { 13041 if ( 'horizontal' == this.scroll ) { 13042 if ( -event.delta.x > 0 ) { 13043 //forward 13044 if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) { 13045 this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px'; 13046 loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) ); 13047 } 13048 } else if ( -event.delta.x < 0 ) { 13049 //reverse 13050 if ( offsetLeft < 0 ) { 13051 this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px'; 13052 loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) ); 13053 } 13054 } 13055 } else { 13056 if ( -event.delta.y > 0 ) { 13057 //forward 13058 if ( offsetTop > -( scrollHeight - viewerSize.y ) ) { 13059 this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px'; 13060 loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) ); 13061 } 13062 } else if ( -event.delta.y < 0 ) { 13063 //reverse 13064 if ( offsetTop < 0 ) { 13065 this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px'; 13066 loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) ); 13067 } 13068 } 13069 } 13070 } 13071 return false; 13072 13073} 13074 13075 13076 13077/** 13078 * @private 13079 * @inner 13080 * @function 13081 */ 13082function onStripScroll( event ) { 13083 var offsetLeft = Number( this.element.style.marginLeft.replace( 'px', '' ) ), 13084 offsetTop = Number( this.element.style.marginTop.replace( 'px', '' ) ), 13085 scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ), 13086 scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
13087 viewerSize = $.getElementSize( this.viewer.canvas ); 13088 if ( this.element ) { 13089 if ( 'horizontal' == this.scroll ) { 13090 if ( event.scroll > 0 ) { 13091 //forward 13092 if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) { 13093 this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px'; 13094 loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) ); 13095 } 13096 } else if ( event.scroll < 0 ) { 13097 //reverse 13098 if ( offsetLeft < 0 ) { 13099 this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px'; 13100 loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) ); 13101 } 13102 } 13103 } else { 13104 if ( event.scroll < 0 ) { 13105 //scroll up 13106 if ( offsetTop > viewerSize.y - scrollHeight ) { 13107 this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px'; 13108 loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) ); 13109 } 13110 } else if ( event.scroll > 0 ) { 13111 //scroll dowm 13112 if ( offsetTop < 0 ) { 13113 this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px'; 13114 loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) ); 13115 } 13116 } 13117 } 13118 } 13119 //cancels event 13120 return false; 13121} 13122 13123 13124function loadPanels( strip, viewerSize, scroll ) { 13125 var panelSize, 13126 activePanelsStart, 13127 activePanelsEnd, 13128 miniViewer, 13129 style, 13130 i, 13131 element; 13132 if ( 'horizontal' == strip.scroll ) { 13133 panelSize = strip.panelWidth; 13134 } else { 13135 panelSize = strip.panelHeight; 13136 } 13137 activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5; 13138 activePanelsEnd = Math.ceil( ( Math.abs( scroll ) + viewerSize ) / panelSize ) + 1; 13139 activePanelsStart = activePanelsEnd - activePanelsStart; 13140 activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart; 13141 13142 for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) { 13143 element = strip.panels[i]; 13144 if ( !element.activePanel ) { 13145 miniViewer = new $.Viewer( { 13146 id: element.id, 13147 tileSources: [strip.viewer.tileSources[i]], 13148 element: element, 13149 navigatorSizeRatio: strip.sizeRatio, 13150 showNavigator: false, 13151 mouseNavEnabled: false, 13152 showNavigationControl: false, 13153 showSequenceControl: false, 13154 immediateRender: true, 13155 blendTime: 0, 13156 animationTime: 0 13157 } ); 13158 13159 miniViewer.displayRegion = $.makeNeutralElement( "textarea" ); 13160 miniViewer.displayRegion.id = element.id + '-displayregion'; 13161 miniViewer.displayRegion.className = 'displayregion'; 13162 13163 style = miniViewer.displayRegion.style; 13164 style.position = 'relative'; 13165 style.top = '0px'; 13166 style.left = '0px'; 13167 style.fontSize = '0px'; 13168 style.overflow = 'hidden'; 13169 style.float = 'left'; //Webkit 13170 style.cssFloat = 'left'; //Firefox 13171 style.styleFloat = 'left'; //IE 13172 style.zIndex = 999999999; 13173 style.cursor = 'default'; 13174 style.width = ( strip.panelWidth - 4 ) + 'px'; 13175 style.height = ( strip.panelHeight - 4 ) + 'px'; 13176 13177 miniViewer.displayRegion.innerTracker = new $.MouseTracker( { 13178 element: miniViewer.displayRegion 13179 } ); 13180 13181 element.getElementsByTagName( 'div' )[0].appendChild( 13182 miniViewer.displayRegion 13183 ); 13184 13185 element.activePanel = true; 13186 } 13187 } 13188} 13189 13190 13191/** 13192 * @private 13193 * @inner 13194 * @function 13195 */ 13196function onStripEnter( event ) { 13197 var element = event.eventSource.element; 13198 13199 //$.setElementOpacity(element, 0.8); 13200 13201 //element.style.border = '1px solid #555'; 13202 //element.style.background = '#000'; 13203 13204 if ( 'horizontal' == this.scroll ) { 13205 13206 //element.style.paddingTop = "0px";
13207 element.style.marginBottom = "0px"; 13208 13209 } else { 13210 13211 //element.style.paddingRight = "0px"; 13212 element.style.marginLeft = "0px"; 13213 13214 } 13215 return false; 13216} 13217 13218 13219/** 13220 * @private 13221 * @inner 13222 * @function 13223 */ 13224function onStripExit( event ) { 13225 var element = event.eventSource.element; 13226 13227 if ( 'horizontal' == this.scroll ) { 13228 13229 //element.style.paddingTop = "10px"; 13230 element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px"; 13231 13232 } else { 13233 13234 //element.style.paddingRight = "10px"; 13235 element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px"; 13236 13237 } 13238 return false; 13239} 13240 13241 13242 13243/** 13244 * @private 13245 * @inner 13246 * @function 13247 */ 13248function onKeyPress( event ) { 13249 //console.log( event.keyCode ); 13250 13251 switch ( event.keyCode ) { 13252 case 61: //=|+ 13253 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 13254 return false; 13255 case 45: //-|_ 13256 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 13257 return false; 13258 case 48: //0|) 13259 case 119: //w 13260 case 87: //W 13261 case 38: //up arrow 13262 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 13263 return false; 13264 case 115: //s 13265 case 83: //S 13266 case 40: //down arrow 13267 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 13268 return false; 13269 case 97: //a 13270 case 37: //left arrow 13271 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); 13272 return false; 13273 case 100: //d 13274 case 39: //right arrow 13275 onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); 13276 return false; 13277 default: 13278 //console.log( 'navigator keycode %s', event.keyCode ); 13279 return true; 13280 } 13281} 13282 13283 13284 13285} ( OpenSeadragon ) ); 13286 13287/* 13288 * OpenSeadragon - DisplayRect 13289 * 13290 * Copyright (C) 2009 CodePlex Foundation 13291 * Copyright (C) 2010-2013 OpenSeadragon contributors 13292 * 13293 * Redistribution and use in source and binary forms, with or without 13294 * modification, are permitted provided that the following conditions are 13295 * met: 13296 * 13297 * - Redistributions of source code must retain the above copyright notice, 13298 * this list of conditions and the following disclaimer. 13299 * 13300 * - Redistributions in binary form must reproduce the above copyright 13301 * notice, this list of conditions and the following disclaimer in the 13302 * documentation and/or other materials provided with the distribution. 13303 * 13304 * - Neither the name of CodePlex Foundation nor the names of its 13305 * contributors may be used to endorse or promote products derived from 13306 * this software without specific prior written permission. 13307 * 13308 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13309 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13310 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13311 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13312 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 13313 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13314 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13315 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13316 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13317 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13318 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13319 */ 13320 13321(function( $ ){ 13322 13323/** 13324 * @class DisplayRect 13325 * @classdesc A display rectangle is very similar to {@link OpenSeadragon.Rect} but adds two 13326 * fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels 13327 * for this rectangle. 13328 * 13329 * @memberof OpenSeadragon 13330 * @extends OpenSeadragon.Rect 13331 * @param {Number} x The vector component 'x'. 13332 * @param {Number} y The vector component 'y'. 13333 * @param {Number} width The vector component 'height'. 13334 * @param {Number} height The vector component 'width'. 13335 * @param {Number} minLevel The lowest zoom level supported. 13336 * @param {Number} maxLevel The highest zoom level supported. 13337 */ 13338$.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) { 13339 $.Rect.apply( this, [ x, y, width, height ] ); 13340 13341 /** 13342 * The lowest zoom level supported. 13343 * @member {Number} minLevel 13344 * @memberof OpenSeadragon.DisplayRect# 13345 */ 13346 this.minLevel = minLevel; 13347 /** 13348 * The highest zoom level supported. 13349 * @member {Number} maxLevel 13350 * @memberof OpenSeadragon.DisplayRect# 13351 */ 13352 this.maxLevel = maxLevel; 13353}; 13354 13355$.extend( $.DisplayRect.prototype, $.Rect.prototype ); 13356 13357}( OpenSeadragon )); 13358 13359/* 13360 * OpenSeadragon - Spring 13361 * 13362 * Copyright (C) 2009 CodePlex Foundation 13363 * Copyright (C) 2010-2013 OpenSeadragon contributors 13364 * 13365 * Redistribution and use in source and binary forms, with or without 13366 * modification, are permitted provided that the following conditions are 13367 * met: 13368 * 13369 * - Redistributions of source code must retain the above copyright notice, 13370 * this list of conditions and the following disclaimer. 13371 * 13372 * - Redistributions in binary form must reproduce the above copyright 13373 * notice, this list of conditions and the following disclaimer in the 13374 * documentation and/or other materials provided with the distribution. 13375 * 13376 * - Neither the name of CodePlex Foundation nor the names of its 13377 * contributors may be used to endorse or promote products derived from 13378 * this software without specific prior written permission. 13379 * 13380 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13381 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13382 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13383 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13384 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13385 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13386 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13387 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13388 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13389 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13390 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13391 */ 13392 13393(function( $ ){ 13394 13395/** 13396 * @class Spring 13397 * @memberof OpenSeadragon 13398 * @param {Object} options - Spring configuration settings. 13399 * @param {Number} options.initial - Initial value of spring, default to 0 so 13400 * spring is not in motion initally by default. 13401 * @param {Number} options.springStiffness - Spring stiffness. 13402 * @param {Number} options.animationTime - Animation duration per spring. 13403 */ 13404$.Spring = function( options ) { 13405 var args = arguments; 13406 13407 if( typeof( options ) != 'object' ){ 13408 //allows backward compatible use of ( initialValue, config ) as 13409 //constructor parameters 13410 options = { 13411 initial: args.length && typeof ( args[ 0 ] ) == "number" ? 13412 args[ 0 ] : 13413 0, 13414 /** 13415 * Spring stiffness. 13416 * @member {Number} springStiffness 13417 * @memberof OpenSeadragon.Spring# 13418 */ 13419 springStiffness: args.length > 1 ? 13420 args[ 1 ].springStiffness : 13421 5.0, 13422 /** 13423 * Animation duration per spring. 13424 * @member {Number} animationTime 13425 * @memberof OpenSeadragon.Spring# 13426 */ 13427 animationTime: args.length > 1 ? 13428 args[ 1 ].animationTime : 13429 1.5 13430 }; 13431 } 13432 13433 $.extend( true, this, options); 13434 13435 /** 13436 * @member {Object} current 13437 * @memberof OpenSeadragon.Spring# 13438 * @property {Number} value 13439 * @property {Number} time 13440 */ 13441 this.current = { 13442 value: typeof ( this.initial ) == "number" ? 13443 this.initial : 13444 0, 13445 time: $.now() // always work in milliseconds 13446 }; 13447 13448 /** 13449 * @member {Object} start 13450 * @memberof OpenSeadragon.Spring# 13451 * @property {Number} value 13452 * @property {Number} time 13453 */ 13454 this.start = { 13455 value: this.current.value, 13456 time: this.current.time 13457 }; 13458 13459 /** 13460 * @member {Object} target 13461 * @memberof OpenSeadragon.Spring# 13462 * @property {Number} value 13463 * @property {Number} time 13464 */ 13465 this.target = { 13466 value: this.current.value, 13467 time: this.current.time 13468 }; 13469};
13470 13471$.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{ 13472 13473 /** 13474 * @function 13475 * @param {Number} target 13476 */ 13477 resetTo: function( target ) { 13478 this.target.value = target; 13479 this.target.time = this.current.time; 13480 this.start.value = this.target.value; 13481 this.start.time = this.target.time; 13482 }, 13483 13484 /** 13485 * @function 13486 * @param {Number} target 13487 */ 13488 springTo: function( target ) { 13489 this.start.value = this.current.value; 13490 this.start.time = this.current.time; 13491 this.target.value = target; 13492 this.target.time = this.start.time + 1000 * this.animationTime; 13493 }, 13494 13495 /** 13496 * @function 13497 * @param {Number} delta 13498 */ 13499 shiftBy: function( delta ) { 13500 this.start.value += delta; 13501 this.target.value += delta; 13502 }, 13503 13504 /** 13505 * @function 13506 */ 13507 update: function() { 13508 this.current.time = $.now(); 13509 this.current.value = (this.current.time >= this.target.time) ? 13510 this.target.value : 13511 this.start.value + 13512 ( this.target.value - this.start.value ) * 13513 transform( 13514 this.springStiffness, 13515 ( this.current.time - this.start.time ) / 13516 ( this.target.time - this.start.time ) 13517 ); 13518 } 13519}; 13520 13521/** 13522 * @private 13523 */ 13524function transform( stiffness, x ) { 13525 return ( 1.0 - Math.exp( stiffness * -x ) ) / 13526 ( 1.0 - Math.exp( -stiffness ) ); 13527} 13528 13529}( OpenSeadragon )); 13530 13531/* 13532 * OpenSeadragon - ImageLoader 13533 * 13534 * Copyright (C) 2009 CodePlex Foundation 13535 * Copyright (C) 2010-2013 OpenSeadragon contributors 13536 13537 * Redistribution and use in source and binary forms, with or without 13538 * modification, are permitted provided that the following conditions are 13539 * met: 13540 * 13541 * - Redistributions of source code must retain the above copyright notice, 13542 * this list of conditions and the following disclaimer. 13543 * 13544 * - Redistributions in binary form must reproduce the above copyright 13545 * notice, this list of conditions and the following disclaimer in the 13546 * documentation and/or other materials provided with the distribution. 13547 * 13548 * - Neither the name of CodePlex Foundation nor the names of its 13549 * contributors may be used to endorse or promote products derived from 13550 * this software without specific prior written permission. 13551 * 13552 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13553 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13554 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13555 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13556 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 13557 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13558 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13559 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13560 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13561 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13562 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13563 */ 13564 13565(function( $ ){ 13566 13567/** 13568 * @private 13569 * @class ImageJob 13570 * @classdesc Handles loading a single image for use in a single {@link OpenSeadragon.Tile}. 13571 * 13572 * @memberof OpenSeadragon 13573 * @param {String} source - URL of image to download. 13574 * @param {String} crossOriginPolicy - CORS policy to use for downloads 13575 * @param {Function} callback - Called once image has finished downloading. 13576 */ 13577function ImageJob ( options ) { 13578 13579 $.extend( true, this, { 13580 timeout: $.DEFAULT_SETTINGS.timeout, 13581 jobId: null 13582 }, options ); 13583 13584 /** 13585 * Image object which will contain downloaded image. 13586 * @member {Image} image 13587 * @memberof OpenSeadragon.ImageJob# 13588 */ 13589 this.image = null; 13590} 13591 13592ImageJob.prototype = { 13593 13594 /** 13595 * Initiates downloading of associated image. 13596 * @method 13597 */ 13598 start: function(){ 13599 var _this = this; 13600 13601 this.image = new Image(); 13602 13603 if ( this.crossOriginPolicy !== false ) { 13604 this.image.crossOrigin = this.crossOriginPolicy; 13605 } 13606 13607 this.image.onload = function(){ 13608 _this.finish( true ); 13609 }; 13610 this.image.onabort = this.image.onerror = function(){
13611 _this.finish( false ); 13612 }; 13613 13614 this.jobId = window.setTimeout( function(){ 13615 _this.finish( false ); 13616 }, this.timeout); 13617 13618 this.image.src = this.src; 13619 }, 13620 13621 finish: function( successful ) { 13622 this.image.onload = this.image.onerror = this.image.onabort = null; 13623 if (!successful) { 13624 this.image = null; 13625 } 13626 13627 if ( this.jobId ) { 13628 window.clearTimeout( this.jobId ); 13629 } 13630 13631 this.callback( this ); 13632 } 13633 13634}; 13635 13636/** 13637 * @class 13638 * @classdesc Handles downloading of a set of images using asynchronous queue pattern. 13639 */ 13640$.ImageLoader = function() { 13641 13642 $.extend( true, this, { 13643 jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit, 13644 jobQueue: [], 13645 jobsInProgress: 0 13646 }); 13647 13648}; 13649 13650$.ImageLoader.prototype = { 13651 13652 /** 13653 * Add an unloaded image to the loader queue. 13654 * @method 13655 * @param {String} src - URL of image to download. 13656 * @param {String} crossOriginPolicy - CORS policy to use for downloads 13657 * @param {Function} callback - Called once image has been downloaded. 13658 */ 13659 addJob: function( options ) { 13660 var _this = this, 13661 complete = function( job ) { 13662 completeJob( _this, job, options.callback ); 13663 }, 13664 jobOptions = { 13665 src: options.src, 13666 crossOriginPolicy: options.crossOriginPolicy, 13667 callback: complete 13668 }, 13669 newJob = new ImageJob( jobOptions ); 13670 13671 if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) { 13672 newJob.start(); 13673 this.jobsInProgress++; 13674 } 13675 else { 13676 this.jobQueue.push( newJob ); 13677 } 13678 13679 } 13680}; 13681 13682/** 13683 * Cleans up ImageJob once completed. 13684 * @method 13685 * @private 13686 * @param loader - ImageLoader used to start job. 13687 * @param job - The ImageJob that has completed. 13688 * @param callback - Called once cleanup is finished. 13689 */ 13690function completeJob( loader, job, callback ) { 13691 var nextJob; 13692 13693 loader.jobsInProgress--; 13694 13695 if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) { 13696 nextJob = loader.jobQueue.shift(); 13697 nextJob.start(); 13698 } 13699 13700 callback( job.image ); 13701} 13702 13703}( OpenSeadragon )); 13704 13705 13706/* 13707 * OpenSeadragon - Tile 13708 * 13709 * Copyright (C) 2009 CodePlex Foundation 13710 * Copyright (C) 2010-2013 OpenSeadragon contributors 13711 * 13712 * Redistribution and use in source and binary forms, with or without 13713 * modification, are permitted provided that the following conditions are 13714 * met: 13715 * 13716 * - Redistributions of source code must retain the above copyright notice, 13717 * this list of conditions and the following disclaimer. 13718 * 13719 * - Redistributions in binary form must reproduce the above copyright 13720 * notice, this list of conditions and the following disclaimer in the 13721 * documentation and/or other materials provided with the distribution. 13722 * 13723 * - Neither the name of CodePlex Foundation nor the names of its 13724 * contributors may be used to endorse or promote products derived from 13725 * this software without specific prior written permission. 13726 * 13727 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13728 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13729 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 13730 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 13731 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 13732 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 13733 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 13734 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 13735 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 13736 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13737 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13738 */ 13739 13740(function( $ ){ 13741 var TILE_CACHE = {}; 13742/** 13743 * @class Tile 13744 * @memberof OpenSeadragon 13745 * @param {Number} level The zoom level this tile belongs to. 13746 * @param {Number} x The vector component 'x'. 13747 * @param {Number} y The vector component 'y'. 13748 * @param {OpenSeadragon.Point}
13748 bounds Where this tile fits, in normalized 13749 * coordinates. 13750 * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has 13751 * this tile failed to load? ) 13752 * @param {String} url The URL of this tile's image. 13753 */ 13754$.Tile = function(level, x, y, bounds, exists, url) { 13755 /** 13756 * The zoom level this tile belongs to. 13757 * @member {Number} level 13758 * @memberof OpenSeadragon.Tile# 13759 */ 13760 this.level = level; 13761 /** 13762 * The vector component 'x'. 13763 * @member {Number} x 13764 * @memberof OpenSeadragon.Tile# 13765 */ 13766 this.x = x; 13767 /** 13768 * The vector component 'y'. 13769 * @member {Number} y 13770 * @memberof OpenSeadragon.Tile# 13771 */ 13772 this.y = y; 13773 /** 13774 * Where this tile fits, in normalized coordinates 13775 * @member {OpenSeadragon.Point} bounds 13776 * @memberof OpenSeadragon.Tile# 13777 */ 13778 this.bounds = bounds; 13779 /** 13780 * Is this tile a part of a sparse image? Also has this tile failed to load? 13781 * @member {Boolean} exists 13782 * @memberof OpenSeadragon.Tile# 13783 */ 13784 this.exists = exists; 13785 /** 13786 * The URL of this tile's image. 13787 * @member {String} url 13788 * @memberof OpenSeadragon.Tile# 13789 */ 13790 this.url = url; 13791 /** 13792 * Is this tile loaded? 13793 * @member {Boolean} loaded 13794 * @memberof OpenSeadragon.Tile# 13795 */ 13796 this.loaded = false; 13797 /** 13798 * Is this tile loading? 13799 * @member {Boolean} loading 13800 * @memberof OpenSeadragon.Tile# 13801 */ 13802 this.loading = false; 13803 13804 /** 13805 * The HTML div element for this tile 13806 * @member {Element} element 13807 * @memberof OpenSeadragon.Tile# 13808 */ 13809 this.element = null; 13810 /** 13811 * The HTML img element for this tile. 13812 * @member {Element} imgElement 13813 * @memberof OpenSeadragon.Tile# 13814 */ 13815 this.imgElement = null; 13816 /** 13817 * The Image object for this tile. 13818 * @member {Object} image 13819 * @memberof OpenSeadragon.Tile# 13820 */ 13821 this.image = null; 13822 13823 /** 13824 * The alias of this.element.style. 13825 * @member {String} style 13826 * @memberof OpenSeadragon.Tile# 13827 */ 13828 this.style = null; 13829 /** 13830 * This tile's position on screen, in pixels. 13831 * @member {OpenSeadragon.Point} position 13832 * @memberof OpenSeadragon.Tile# 13833 */ 13834 this.position = null; 13835 /** 13836 * This tile's size on screen, in pixels. 13837 * @member {OpenSeadragon.Point} size 13838 * @memberof OpenSeadragon.Tile# 13839 */ 13840 this.size = null; 13841 /** 13842 * The start time of this tile's blending. 13843 * @member {Number} blendStart 13844 * @memberof OpenSeadragon.Tile# 13845 */ 13846 this.blendStart = null; 13847 /** 13848 * The current opacity this tile should be. 13849 * @member {Number} opacity 13850 * @memberof OpenSeadragon.Tile# 13851 */ 13852 this.opacity = null; 13853 /** 13854 * The distance of this tile to the viewport center. 13855 * @member {Number} distance 13856 * @memberof OpenSeadragon.Tile# 13857 */ 13858 this.distance = null; 13859 /** 13860 * The visibility score of this tile. 13861 * @member {Number} visibility 13862 * @memberof OpenSeadragon.Tile# 13863 */ 13864 this.visibility = null; 13865 13866 /** 13867 * Whether this tile is currently being drawn. 13868 * @member {Boolean} beingDrawn 13869 * @memberof OpenSeadragon.Tile# 13870 */ 13871 this.beingDrawn = false; 13872 /** 13873 * Timestamp the tile was last touched. 13874 * @member {Number} lastTouchTime 13875 * @memberof OpenSeadragon.Tile# 13876 */ 13877 this.lastTouchTime = 0; 13878}; 13879 13880$.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{ 13881 13882 /** 13883 * Provides a string representation of this tiles level and (x,y) 13884 * components. 13885 * @function 13886 * @returns {String} 13887 */ 13888 toString: function() { 13889 return this.level + "/" + this.x + "_" + this.y; 13890 }, 13891 13892 /** 13893 * Renders the tile in an html container. 13894 * @function 13895 * @param {Element} container 13896 */ 13897 drawHTML: function( container ) { 13898 if ( !this.loaded || !this.image ) { 13899 $.console.warn( 13900 "Attempting to draw tile %s when it's not yet loaded.", 13901 this.toString() 13902 ); 13903 return; 13904 } 13905 13906 //EXPERIMENTAL - trying to figure out how to scale the container 13907 // content during animation of the container size. 13908 13909 if ( !this.element ) { 13910 this.element = $.makeNeutralElement( "div" );
13911 this.imgElement = $.makeNeutralElement( "img" ); 13912 this.imgElement.src = this.url; 13913 this.imgElement.style.msInterpolationMode = "nearest-neighbor"; 13914 this.imgElement.style.width = "100%"; 13915 this.imgElement.style.height = "100%"; 13916 13917 this.style = this.element.style; 13918 this.style.position = "absolute"; 13919 } 13920 if ( this.element.parentNode != container ) { 13921 container.appendChild( this.element ); 13922 } 13923 if ( this.imgElement.parentNode != this.element ) { 13924 this.element.appendChild( this.imgElement ); 13925 } 13926 13927 this.style.top = this.position.y + "px"; 13928 this.style.left = this.position.x + "px"; 13929 this.style.height = this.size.y + "px"; 13930 this.style.width = this.size.x + "px"; 13931 13932 $.setElementOpacity( this.element, this.opacity ); 13933 }, 13934 13935 /** 13936 * Renders the tile in a canvas-based context. 13937 * @function 13938 * @param {Canvas} context 13939 * @param {Function} method for firing the drawing event. drawingHandler({context, tile, rendered}) 13940 * where <code>rendered</code> is the context with the pre-drawn image. 13941 */ 13942 drawCanvas: function( context, drawingHandler ) { 13943 13944 var position = this.position, 13945 size = this.size, 13946 rendered, 13947 canvas; 13948 13949 if ( !this.loaded || !( this.image || TILE_CACHE[ this.url ] ) ){ 13950 $.console.warn( 13951 "Attempting to draw tile %s when it's not yet loaded.", 13952 this.toString() 13953 ); 13954 return; 13955 } 13956 context.globalAlpha = this.opacity; 13957 13958 //context.save(); 13959 13960 //if we are supposed to be rendering fully opaque rectangle, 13961 //ie its done fading or fading is turned off, and if we are drawing 13962 //an image with an alpha channel, then the only way 13963 //to avoid seeing the tile underneath is to clear the rectangle 13964 if( context.globalAlpha == 1 && this.url.match('.png') ){ 13965 //clearing only the inside of the rectangle occupied 13966 //by the png prevents edge flikering 13967 context.clearRect( 13968 position.x+1, 13969 position.y+1, 13970 size.x-2, 13971 size.y-2 13972 ); 13973 13974 } 13975 13976 if( !TILE_CACHE[ this.url ] ){ 13977 canvas = document.createElement( 'canvas' ); 13978 canvas.width = this.image.width; 13979 canvas.height = this.image.height; 13980 rendered = canvas.getContext('2d'); 13981 rendered.drawImage( this.image, 0, 0 ); 13982 TILE_CACHE[ this.url ] = rendered; 13983 //since we are caching the prerendered image on a canvas 13984 //allow the image to not be held in memory 13985 this.image = null; 13986 } 13987 13988 rendered = TILE_CACHE[ this.url ]; 13989 13990 // This gives the application a chance to make image manipulation changes as we are rendering the image 13991 drawingHandler({context: context, tile: this, rendered: rendered}); 13992 13993 //rendered.save(); 13994 context.drawImage( 13995 rendered.canvas, 13996 0, 13997 0, 13998 rendered.canvas.width, 13999 rendered.canvas.height, 14000 position.x, 14001 position.y, 14002 size.x, 14003 size.y 14004 ); 14005 //rendered.restore(); 14006 14007 //context.restore(); 14008 }, 14009 14010 /** 14011 * Removes tile from its container. 14012 * @function 14013 */ 14014 unload: function() {
14015 if ( this.imgElement && this.imgElement.parentNode ) { 14016 this.imgElement.parentNode.removeChild( this.imgElement ); 14017 } 14018 if ( this.element && this.element.parentNode ) { 14019 this.element.parentNode.removeChild( this.element ); 14020 } 14021 if ( TILE_CACHE[ this.url ]){ 14022 delete TILE_CACHE[ this.url ]; 14023 } 14024 14025 this.element = null; 14026 this.imgElement = null; 14027 this.image = null; 14028 this.loaded = false; 14029 this.loading = false; 14030 } 14031}; 14032 14033}( OpenSeadragon )); 14034 14035/* 14036 * OpenSeadragon - Overlay 14037 * 14038 * Copyright (C) 2009 CodePlex Foundation 14039 * Copyright (C) 2010-2013 OpenSeadragon contributors 14040 * 14041 * Redistribution and use in source and binary forms, with or without 14042 * modification, are permitted provided that the following conditions are 14043 * met: 14044 * 14045 * - Redistributions of source code must retain the above copyright notice, 14046 * this list of conditions and the following disclaimer. 14047 * 14048 * - Redistributions in binary form must reproduce the above copyright 14049 * notice, this list of conditions and the following disclaimer in the 14050 * documentation and/or other materials provided with the distribution. 14051 * 14052 * - Neither the name of CodePlex Foundation nor the names of its 14053 * contributors may be used to endorse or promote products derived from 14054 * this software without specific prior written permission. 14055 * 14056 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14057 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14058 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14059 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14060 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14061 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14062 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14063 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14064 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14065 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14066 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14067 */ 14068 14069(function( $ ){ 14070 14071 /** 14072 * An enumeration of positions that an overlay may be assigned relative to 14073 * the viewport. 14074 * @member OverlayPlacement 14075 * @memberof OpenSeadragon 14076 * @static 14077 * @type {Object} 14078 * @property {Number} CENTER 14079 * @property {Number} TOP_LEFT 14080 * @property {Number} TOP 14081 * @property {Number} TOP_RIGHT 14082 * @property {Number} RIGHT 14083 * @property {Number} BOTTOM_RIGHT 14084 * @property {Number} BOTTOM 14085 * @property {Number} BOTTOM_LEFT 14086 * @property {Number} LEFT 14087 */ 14088 $.OverlayPlacement = { 14089 CENTER: 0, 14090 TOP_LEFT: 1, 14091 TOP: 2, 14092 TOP_RIGHT: 3, 14093 RIGHT: 4, 14094 BOTTOM_RIGHT: 5, 14095 BOTTOM: 6, 14096 BOTTOM_LEFT: 7, 14097 LEFT: 8 14098 }; 14099 14100 /** 14101 * @class Overlay 14102 * @classdesc Provides a way to float an HTML element on top of the viewer element. 14103 * 14104 * @memberof OpenSeadragon 14105 * @param {Object} options 14106 * @param {Element} options.element 14107 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The 14108 * location of the overlay on the image. If a {@link OpenSeadragon.Point} 14109 * is specified, the overlay will keep a constant size independently of the 14110 * zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will 14111 * be adjusted when the zoom changes. 14112 * @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT] 14113 * Relative position to the viewport. 14114 * Only used if location is a {@link OpenSeadragon.Point}. 14115 * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw] 14116 * @param {Boolean} [options.checkResize=true] Set to false to avoid to 14117 * check the size of the overlay everytime it is drawn when using a 14118 * {@link OpenSeadragon.Point} as options.location. It will improve 14119 * performances but will cause a misalignment if the overlay size changes. 14120 */ 14121 $.Overlay = function( element, location, placement ) { 14122 14123 /** 14124 * onDraw callback signature used by {@link OpenSeadragon.Overlay}. 14125 * 14126 * @callback OnDrawCallback 14127 * @memberof OpenSeadragon.Overlay 14128 * @param {OpenSeadragon.Point} position 14129 * @param {OpenSeadragon.Point} size 14130 * @param {Element} element 14131 */ 14132 14133 var options; 14134 if ( $.isPlainObject( element ) ) { 14135 options = element; 14136 } else { 14137 options = { 14138 element: element, 14139 location: location, 14140 placement: placement 14141 }; 14142 } 14143 14144 this.element = options.element; 14145 this.scales = options.location instanceof $.Rect; 14146 this.bounds = new $.Rect( 14147 options.location.x, 14148 options.location.y, 14149 options.location.width, 14150 options.location.height 14151 ); 14152 this.position = new $.Point( 14153 options.location.x, 14154 options.location.y 14155 ); 14156 this.size = new $.Point( 14157 options.location.width, 14158 options.location.height 14159 ); 14160 this.style = options.element.style; 14161 // rects are always top-left 14162 this.placement = options.location instanceof $.Point ? 14163 options.placement : 14164 $.OverlayPlacement.TOP_LEFT; 14165 this.onDraw = options.onDraw; 14166 this.checkResize = options.checkResize === undefined ? 14167 true : options.checkResize; 14168 }; 14169 14170 $.Overlay.prototype = /** @lends OpenSeadragon.Overlay.prototype */{ 14171 14172 /** 14173 * @function 14174 * @param {OpenSeadragon.OverlayPlacement} position 14175 * @param {OpenSeadragon.Point} size 14176 */ 14177 adjust: function( position, size ) { 14178 switch ( this.placement ) { 14179 case $.OverlayPlacement.TOP_LEFT: 14180 break; 14181 case $.OverlayPlacement.TOP: 14182 position.x -= size.x / 2; 14183 break; 14184 case $.OverlayPlacement.TOP_RIGHT: 14185 position.x -= size.x; 14186 break; 14187 case $.OverlayPlacement.RIGHT: 14188 position.x -= size.x; 14189 position.y -= size.y / 2; 14190 break; 14191 case $.OverlayPlacement.BOTTOM_RIGHT: 14192 position.x -= size.x; 14193 position.y -= size.y; 14194 break; 14195 case $.OverlayPlacement.BOTTOM: 14196 position.x -= size.x / 2; 14197 position.y -= size.y; 14198 break; 14199 case $.OverlayPlacement.BOTTOM_LEFT: 14200 position.y -= size.y; 14201 break; 14202 case $.OverlayPlacement.LEFT: 14203 position.y -= size.y / 2; 14204 break; 14205 default: 14206 case $.OverlayPlacement.CENTER: 14207 position.x -= size.x / 2; 14208 position.y -= size.y / 2; 14209 break; 14210 } 14211 }, 14212 14213 /** 14214 * @function 14215 */ 14216 destroy: function() { 14217 var element = this.element, 14218 style = this.style; 14219 14220 if ( element.parentNode ) { 14221 element.parentNode.removeChild( element ); 14222 //this should allow us to preserve overlays when required between 14223 //pages 14224 if ( element.prevElementParent ) { 14225 style.display = 'none'; 14226 //element.prevElementParent.insertBefore( 14227 // element, 14228 // element.prevNextSibling 14229 //); 14230 document.body.appendChild( element ); 14231 } 14232 } 14233 14234 // clear the onDraw callback 14235 this.onDraw = null; 14236 14237 style.top = ""; 14238 style.left = ""; 14239 style.position = ""; 14240 14241 if ( this.scales ) { 14242 style.width = ""; 14243 style.height = ""; 14244 } 14245 }, 14246 14247 /** 14248 * @function 14249 * @param {Element} container 14250 */ 14251 drawHTML: function( container, viewport ) { 14252 var element = this.element, 14253 style = this.style, 14254 scales = this.scales, 14255 degrees = viewport.degrees, 14256 position = viewport.pixelFromPoint( 14257 this.bounds.getTopLeft(), 14258 true 14259 ), 14260 size, 14261 overlayCenter; 14262 14263 if ( element.parentNode != container ) { 14264 //save the source parent for later if we need it 14265 element.prevElementParent = element.parentNode; 14266 element.prevNextSibling = element.nextSibling; 14267 container.appendChild( element ); 14268 this.size = $.getElementSize( element ); 14269 } 14270 14271 if ( scales ) { 14272 size = viewport.deltaPixelsFromPoints( 14273 this.bounds.getSize(), 14274 true 14275 ); 14276 } else if ( this.checkResize ) { 14277 size = $.getElementSize( element ); 14278 } else { 14279 size = this.size; 14280 } 14281 14282 this.position = position; 14283 this.size = size; 14284 14285 this.adjust( position, size ); 14286 14287 position = position.apply( Math.floor ); 14288 size = size.apply( Math.ceil ); 14289 14290 // rotate the position of the overlay 14291 // TODO only rotate overlays if in canvas mode 14292 // TODO replace the size rotation with CSS3 transforms
14293 // TODO add an option to overlays to not rotate with the image 14294 // Currently only rotates position and size 14295 if( degrees !== 0 && this.scales ) { 14296 overlayCenter = new $.Point( size.x / 2, size.y / 2 ); 14297 14298 var drawerCenter = new $.Point( 14299 viewport.viewer.drawer.canvas.width / 2, 14300 viewport.viewer.drawer.canvas.height / 2 14301 ); 14302 position = position.plus( overlayCenter ).rotate( 14303 degrees, 14304 drawerCenter 14305 ).minus( overlayCenter ); 14306 14307 size = size.rotate( degrees, new $.Point( 0, 0 ) ); 14308 size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) ); 14309 } 14310 14311 // call the onDraw callback if it exists to allow one to overwrite 14312 // the drawing/positioning/sizing of the overlay 14313 if ( this.onDraw ) { 14314 this.onDraw( position, size, element ); 14315 } else { 14316 style.left = position.x + "px"; 14317 style.top = position.y + "px"; 14318 style.position = "absolute"; 14319 style.display = 'block'; 14320 14321 if ( scales ) { 14322 style.width = size.x + "px"; 14323 style.height = size.y + "px"; 14324 } 14325 } 14326 }, 14327 14328 /** 14329 * @function 14330 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location 14331 * @param {OpenSeadragon.OverlayPlacement} position 14332 */ 14333 update: function( location, placement ) { 14334 this.scales = location instanceof $.Rect; 14335 this.bounds = new $.Rect( 14336 location.x, 14337 location.y, 14338 location.width, 14339 location.height 14340 ); 14341 // rects are always top-left 14342 this.placement = location instanceof $.Point ? 14343 placement : 14344 $.OverlayPlacement.TOP_LEFT; 14345 } 14346 14347 }; 14348 14349}( OpenSeadragon )); 14350 14351/* 14352 * OpenSeadragon - Drawer 14353 * 14354 * Copyright (C) 2009 CodePlex Foundation 14355 * Copyright (C) 2010-2013 OpenSeadragon contributors 14356 * 14357 * Redistribution and use in source and binary forms, with or without 14358 * modification, are permitted provided that the following conditions are 14359 * met: 14360 * 14361 * - Redistributions of source code must retain the above copyright notice, 14362 * this list of conditions and the following disclaimer. 14363 * 14364 * - Redistributions in binary form must reproduce the above copyright 14365 * notice, this list of conditions and the following disclaimer in the 14366 * documentation and/or other materials provided with the distribution. 14367 * 14368 * - Neither the name of CodePlex Foundation nor the names of its 14369 * contributors may be used to endorse or promote products derived from 14370 * this software without specific prior written permission. 14371 * 14372 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14373 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14374 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14375 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 14376 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 14377 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 14378 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 14379 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 14380 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 14381 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 14382 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14383 */ 14384 14385(function( $ ){ 14386 14387var DEVICE_SCREEN = $.getWindowSize(), 14388 BROWSER = $.Browser.vendor, 14389 BROWSER_VERSION = $.Browser.version, 14390 14391 SUBPIXEL_RENDERING = ( 14392 ( BROWSER == $.BROWSERS.FIREFOX ) || 14393 ( BROWSER == $.BROWSERS.OPERA ) || 14394 ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) || 14395 ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) || 14396 ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 ) 14397 ); 14398 14399 14400/** 14401 * @class Drawer 14402 * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. 14403 * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}). 14404 * 14405 * @memberof OpenSeadragon 14406 * @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source. 14407 * @param {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport. 14408 * @param {Element} element - Parent element. 14409 */ 14410$.Drawer = function( options ) { 14411 14412 //backward compatibility for positional args while prefering more 14413 //idiomatic javascript options object as the only argument 14414 var args = arguments, 14415 i; 14416 14417 if( !$.isPlainObject( options ) ){ 14418 options = { 14419 source: args[ 0 ], // Reference to Viewer tile source. 14420 viewport: args[ 1 ], // Reference to Viewer viewport. 14421 element: args[ 2 ] // Parent element. 14422 }; 14423 } 14424 14425 $.extend( true, this, { 14426 14427 //internal state properties 14428 viewer: null, 14429 imageLoader: new $.ImageLoader(), 14430 tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. 14431 tilesLoaded: [], // An unordered list of Tiles with loaded images. 14432 coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. 14433 lastDrawn: [], // An unordered list of Tiles drawn last frame. 14434 lastResetTime: 0, // Last time for which the drawer was reset. 14435 midUpdate: false, // Is the drawer currently updating the viewport? 14436 updateAgain: true, // Does the drawer need to update the viewort again? 14437 14438 14439 //internal state / configurable settings 14440 collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer. 14441 14442 //configurable settings 14443 opacity: $.DEFAULT_SETTINGS.opacity, 14444 maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
14445 minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, 14446 wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, 14447 wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, 14448 immediateRender: $.DEFAULT_SETTINGS.immediateRender, 14449 blendTime: $.DEFAULT_SETTINGS.blendTime, 14450 alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, 14451 minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, 14452 debugMode: $.DEFAULT_SETTINGS.debugMode, 14453 timeout: $.DEFAULT_SETTINGS.timeout, 14454 crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy 14455 14456 }, options ); 14457 14458 this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); 14459 /** 14460 * The parent element of this Drawer instance, passed in when the Drawer was created. 14461 * The parent of {@link OpenSeadragon.Drawer#canvas}. 14462 * @member {Element} container 14463 * @memberof OpenSeadragon.Drawer# 14464 */ 14465 this.container = $.getElement( this.element ); 14466 /** 14467 * A <canvas> element if the browser supports them, otherwise a <div> element. 14468 * Child element of {@link OpenSeadragon.Drawer#container}. 14469 * @member {Element} canvas 14470 * @memberof OpenSeadragon.Drawer# 14471 */ 14472 this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); 14473 /** 14474 * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null. 14475 * @member {Object} context 14476 * @memberof OpenSeadragon.Drawer# 14477 */ 14478 this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null; 14479 // Ratio of zoomable image height to width. 14480 this.normHeight = this.source.dimensions.y / this.source.dimensions.x; 14481 /** 14482 * @member {Element} element 14483 * @memberof OpenSeadragon.Drawer# 14484 * @deprecated Alias for {@link OpenSeadragon.Drawer#container}. 14485 */ 14486 this.element = this.container; 14487 14488 // We force our container to ltr because our drawing math doesn't work in rtl. 14489 // This issue only affects our canvas renderer, but we do it always for consistency. 14490 // Note that this means overlays you want to be rtl need to be explicitly set to rtl. 14491 this.container.dir = 'ltr'; 14492 14493 this.canvas.style.width = "100%"; 14494 this.canvas.style.height = "100%"; 14495 this.canvas.style.position = "absolute"; 14496 $.setElementOpacity( this.canvas, this.opacity, true ); 14497 14498 // explicit left-align 14499 this.container.style.textAlign = "left"; 14500 this.container.appendChild( this.canvas ); 14501 14502 //this.profiler = new $.Profiler(); 14503}; 14504 14505$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ 14506 14507 /** 14508 * Adds an html element as an overlay to the current viewport. Useful for 14509 * highlighting words or areas of interest on an image or other zoomable 14510 * interface. 14511 * @method 14512 * @param {Element|String|Object} element - A reference to an element or an id for 14513 * the element which will overlayed. Or an Object specifying the configuration for the overlay 14514 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 14515 * rectangle which will be overlayed. 14516 * @param {OpenSeadragon.OverlayPlacement} placement - The position of the 14517 * viewport which the location coordinates will be treated as relative 14518 * to. 14519 * @param {function} onDraw - If supplied the callback is called when the overlay 14520 * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. 14521 * It is passed position, size and element. 14522 * @fires OpenSeadragon.Viewer.event:add-overlay 14523 * @deprecated - use {@link OpenSeadragon.Viewer#addOverlay} instead. 14524 */ 14525 addOverlay: function( element, location, placement, onDraw ) { 14526 $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); 14527 this.viewer.addOverlay( element, location, placement, onDraw ); 14528 return this; 14529 }, 14530 14531 /** 14532 * Updates the overlay represented by the reference to the element or 14533 * element id moving it to the new location, relative to the new placement. 14534 * @method 14535 * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or 14536 * rectangle which will be overlayed. 14537 * @param {OpenSeadragon.OverlayPlacement} placement - The position of the 14538 * viewport which the location coordinates will be treated as relative 14539 * to. 14540 * @return {OpenSeadragon.Drawer} Chainable. 14541 * @fires OpenSeadragon.Viewer.event:update-overlay 14542 * @deprecated - use {@link OpenSeadragon.Viewer#updateOverlay} instead. 14543 */ 14544 updateOverlay: function( element, location, placement ) { 14545 $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); 14546 this.viewer.updateOverlay( element, location, placement ); 14547 return this; 14548 }, 14549 14550 /** 14551 * Removes and overlay identified by the reference element or element id 14552 * and schedules and update. 14553 * @method 14554 * @param {Element|String} element - A reference to the element or an 14555 * element id which represent the ovelay content to be removed. 14556 * @return {OpenSeadragon.Drawer} Chainable. 14557 * @fires OpenSeadragon.Viewer.event:remove-overlay 14558 * @deprecated - use {@link OpenSeadragon.Viewer#removeOverlay} instead. 14559 */ 14560 removeOverlay: function( element ) { 14561 $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); 14562 this.viewer.removeOverlay( element ); 14563 return this; 14564 }, 14565 14566 /** 14567 * Removes all currently configured Overlays from this Drawer and schedules 14568 * and update. 14569 * @method 14570 * @return {OpenSeadragon.Drawer} Chainable. 14571 * @fires OpenSeadragon.Viewer.event:clear-overlay 14572 * @deprecated - use {@link OpenSeadragon.Viewer#clearOverlays} instead. 14573 */ 14574 clearOverlays: function() { 14575 $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); 14576 this.viewer.clearOverlays(); 14577 return this; 14578 }, 14579 14580 /** 14581 * Set the opacity of the drawer. 14582 * @method 14583 * @param {Number} opacity 14584 * @return {OpenSeadragon.Drawer} Chainable. 14585 */ 14586 setOpacity: function( opacity ) { 14587 this.opacity = opacity; 14588 $.setElementOpacity( this.canvas, this.opacity, true ); 14589 return this; 14590 }, 14591 14592 /** 14593 * Get the opacity of the drawer. 14594 * @method 14595 * @returns {Number} 14596 */ 14597 getOpacity: function() { 14598 return this.opacity; 14599 }, 14600 /** 14601 * Returns whether the Drawer is scheduled for an update at the 14602 * soonest possible opportunity. 14603 * @method 14604 * @returns {Boolean} - Whether the Drawer is scheduled for an update at the 14605 * soonest possible opportunity. 14606 */ 14607 needsUpdate: function() { 14608 return this.updateAgain; 14609 }, 14610 14611 /** 14612 * Returns the total number of tiles that have been loaded by this Drawer. 14613 * @method 14614 * @returns {Number} - The total number of tiles that have been loaded by 14615 * this Drawer. 14616 */ 14617 numTilesLoaded: function() { 14618 return this.tilesLoaded.length; 14619 }, 14620 14621 /** 14622 * Clears all tiles and triggers an update on the next call to
14623 * Drawer.prototype.update(). 14624 * @method 14625 * @return {OpenSeadragon.Drawer} Chainable. 14626 */ 14627 reset: function() { 14628 clearTiles( this ); 14629 this.lastResetTime = $.now(); 14630 this.updateAgain = true; 14631 return this; 14632 }, 14633 14634 /** 14635 * Forces the Drawer to update. 14636 * @method 14637 * @return {OpenSeadragon.Drawer} Chainable. 14638 */ 14639 update: function() { 14640 //this.profiler.beginUpdate(); 14641 this.midUpdate = true; 14642 updateViewport( this ); 14643 this.midUpdate = false; 14644 //this.profiler.endUpdate(); 14645 return this; 14646 }, 14647 14648 /** 14649 * Returns whether rotation is supported or not. 14650 * @method 14651 * @return {Boolean} True if rotation is supported. 14652 */ 14653 canRotate: function() { 14654 return this.useCanvas; 14655 }, 14656 14657 /** 14658 * Destroy the drawer (unload current loaded tiles) 14659 * @method 14660 * @return null 14661 */ 14662 destroy: function() { 14663 //unload current loaded tiles (=empty TILE_CACHE) 14664 for ( var i = 0; i < this.tilesLoaded.length; ++i ) { 14665 this.tilesLoaded[i].unload(); 14666 } 14667 14668 //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) 14669 this.canvas.width = 1; 14670 this.canvas.height = 1; 14671 } 14672}; 14673 14674/** 14675 * @private 14676 * @inner 14677 * Pretty much every other line in this needs to be documented so it's clear 14678 * how each piece of this routine contributes to the drawing process. That's 14679 * why there are so many TODO's inside this function. 14680 */ 14681function updateViewport( drawer ) { 14682 14683 drawer.updateAgain = false; 14684 14685 if( drawer.viewer ){ 14686 /** 14687 * <em>- Needs documentation -</em> 14688 * 14689 * @event update-viewport 14690 * @memberof OpenSeadragon.Viewer 14691 * @type {object} 14692 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 14693 * @property {?Object} userData - Arbitrary subscriber-defined object. 14694 */ 14695 drawer.viewer.raiseEvent( 'update-viewport', {} ); 14696 } 14697 14698 var tile, 14699 level, 14700 best = null, 14701 haveDrawn = false, 14702 currentTime = $.now(), 14703 viewportSize = drawer.viewport.getContainerSize(), 14704 viewportBounds = drawer.viewport.getBounds( true ), 14705 viewportTL = viewportBounds.getTopLeft(), 14706 viewportBR = viewportBounds.getBottomRight(), 14707 zeroRatioC = drawer.viewport.deltaPixelsFromPoints( 14708 drawer.source.getPixelRatio( 0 ), 14709 true 14710 ).x, 14711 lowestLevel = Math.max( 14712 drawer.source.minLevel, 14713 Math.floor( 14714 Math.log( drawer.minZoomImageRatio ) / 14715 Math.log( 2 ) 14716 ) 14717 ), 14718 highestLevel = Math.min( 14719 Math.abs(drawer.source.maxLevel), 14720 Math.abs(Math.floor( 14721 Math.log( zeroRatioC / drawer.minPixelRatio ) / 14722 Math.log( 2 ) 14723 )) 14724 ), 14725 degrees = drawer.viewport.degrees, 14726 renderPixelRatioC, 14727 renderPixelRatioT, 14728 zeroRatioT, 14729 optimalRatio, 14730 levelOpacity, 14731 levelVisibility; 14732 14733 // Reset tile's internal drawn state 14734 while ( drawer.lastDrawn.length > 0 ) { 14735 tile = drawer.lastDrawn.pop(); 14736 tile.beingDrawn = false; 14737 } 14738 14739 // Clear canvas 14740 drawer.canvas.innerHTML = ""; 14741 if ( drawer.useCanvas ) { 14742 if( drawer.canvas.width != viewportSize.x || 14743 drawer.canvas.height != viewportSize.y ){ 14744 drawer.canvas.width = viewportSize.x; 14745 drawer.canvas.height = viewportSize.y; 14746 } 14747 drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); 14748 } 14749 14750 //Change bounds for rotation 14751 if (degrees === 90 || degrees === 270) { 14752 var rotatedBounds = viewportBounds.rotate( degrees ); 14753 viewportTL = rotatedBounds.getTopLeft(); 14754 viewportBR = rotatedBounds.getBottomRight(); 14755 } 14756 14757 //Don't draw if completely outside of the viewport 14758 if ( !drawer.wrapHorizontal && 14759 ( viewportBR.x < 0 || viewportTL.x > 1 ) ) { 14760 return; 14761 } else if 14762 ( !drawer.wrapVertical && 14763 ( viewportBR.y < 0 || viewportTL.y > drawer.normHeight ) ) { 14764 return; 14765 } 14766 14767 // Calculate viewport rect / bounds 14768 if ( !drawer.wrapHorizontal ) { 14769 viewportTL.x = Math.max( viewportTL.x, 0 ); 14770 viewportBR.x = Math.min( viewportBR.x, 1 ); 14771 } 14772 if ( !drawer.wrapVertical ) { 14773 viewportTL.y = Math.max( viewportTL.y, 0 ); 14774 viewportBR.y = Math.min( viewportBR.y, drawer.normHeight ); 14775 } 14776 14777 // Calculations for the interval of levels to draw 14778 // (above in initial var statement) 14779 // can return invalid intervals; fix that here if necessary 14780 lowestLevel = Math.min( lowestLevel, highestLevel ); 14781 14782 // Update any level that will be drawn 14783 var drawLevel; // FIXME: drawLevel should have a more explanatory name 14784 for ( level = highestLevel; level >= lowestLevel; level-- ) { 14785 drawLevel = false; 14786 14787 //Avoid calculations for draw if we have already drawn this 14788 renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints( 14789 drawer.source.getPixelRatio( level ), 14790 true 14791 ).x; 14792 14793 if ( ( !haveDrawn && renderPixelRatioC >= drawer.minPixelRatio ) || 14794 ( level == lowestLevel ) ) { 14795 drawLevel = true; 14796 haveDrawn = true; 14797 } else if ( !haveDrawn ) { 14798 continue; 14799 } 14800 14801 //Perform calculations for draw if we haven't drawn this 14802 renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( 14803 drawer.source.getPixelRatio( level ), 14804 false 14805 ).x; 14806 14807 zeroRatioT = drawer.viewport.deltaPixelsFromPoints( 14808 drawer.source.getPixelRatio( 14809 Math.max( 14810 drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1, 14811 0 14812 ) 14813 ), 14814 false 14815 ).x; 14816 14817 optimalRatio = drawer.immediateRender ? 14818 1 : 14819 zeroRatioT; 14820 14821 levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 ); 14822 14823 levelVisibility = optimalRatio / Math.abs( 14824 optimalRatio - renderPixelRatioT 14825 ); 14826 14827 // Update the level and keep track of 'best' tile to load 14828 best = updateLevel( 14829 drawer, 14830 haveDrawn, 14831 drawLevel, 14832 level, 14833 levelOpacity, 14834 levelVisibility, 14835 viewportTL, 14836 viewportBR, 14837 currentTime, 14838 best 14839 ); 14840 14841 // Stop the loop if lower-res tiles would all be covered by 14842 // already drawn tiles 14843 if ( providesCoverage( drawer.coverage, level ) ) { 14844 break; 14845 } 14846 } 14847 14848 // Perform the actual drawing 14849 drawTiles( drawer, drawer.lastDrawn ); 14850 14851 // Load the new 'best' tile 14852 if ( best ) { 14853 loadTile( drawer, best, currentTime ); 14854 // because we haven't finished drawing, so 14855 drawer.updateAgain = true; 14856 } 14857 14858} 14859 14860 14861function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ 14862 14863 var x, y, 14864 tileTL, 14865 tileBR, 14866 numberOfTiles, 14867 viewportCenter = drawer.viewport.pixelFromPoint( drawer.viewport.getCenter() ); 14868 14869 14870 if( drawer.viewer ){ 14871 /** 14872 * <em>- Needs documentation -</em> 14873 * 14874 * @event update-level 14875 * @memberof OpenSeadragon.Viewer 14876 * @type {object} 14877 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 14878 * @property {Object} havedrawn 14879 * @property {Object} level 14880 * @property {Object} opacity 14881 * @property {Object} visibility 14882 * @property {Object} topleft 14883 * @property {Object} bottomright 14884 * @property {Object} currenttime 14885 * @property {Object} best
14886 * @property {?Object} userData - Arbitrary subscriber-defined object. 14887 */ 14888 drawer.viewer.raiseEvent( 'update-level', { 14889 havedrawn: haveDrawn, 14890 level: level, 14891 opacity: levelOpacity, 14892 visibility: levelVisibility, 14893 topleft: viewportTL, 14894 bottomright: viewportBR, 14895 currenttime: currentTime, 14896 best: best 14897 }); 14898 } 14899 14900 //OK, a new drawing so do your calculations 14901 tileTL = drawer.source.getTileAtPoint( level, viewportTL ); 14902 tileBR = drawer.source.getTileAtPoint( level, viewportBR ); 14903 numberOfTiles = drawer.source.getNumTiles( level ); 14904 14905 resetCoverage( drawer.coverage, level ); 14906 14907 if ( !drawer.wrapHorizontal ) { 14908 tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 ); 14909 } 14910 if ( !drawer.wrapVertical ) { 14911 tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 ); 14912 } 14913 14914 for ( x = tileTL.x; x <= tileBR.x; x++ ) { 14915 for ( y = tileTL.y; y <= tileBR.y; y++ ) { 14916 14917 best = updateTile( 14918 drawer, 14919 drawLevel, 14920 haveDrawn, 14921 x, y, 14922 level, 14923 levelOpacity, 14924 levelVisibility, 14925 viewportCenter, 14926 numberOfTiles, 14927 currentTime, 14928 best 14929 ); 14930 14931 } 14932 } 14933 14934 return best; 14935} 14936 14937function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ 14938 14939 var tile = getTile( 14940 x, y, 14941 level, 14942 drawer.source, 14943 drawer.tilesMatrix, 14944 currentTime, 14945 numberOfTiles, 14946 drawer.normHeight 14947 ), 14948 drawTile = drawLevel; 14949 14950 if( drawer.viewer ){ 14951 /** 14952 * <em>- Needs documentation -</em> 14953 * 14954 * @event update-tile 14955 * @memberof OpenSeadragon.Viewer 14956 * @type {object} 14957 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 14958 * @property {OpenSeadragon.Tile} tile 14959 * @property {?Object} userData - Arbitrary subscriber-defined object. 14960 */ 14961 drawer.viewer.raiseEvent( 'update-tile', { 14962 tile: tile 14963 }); 14964 } 14965 14966 setCoverage( drawer.coverage, level, x, y, false ); 14967 14968 if ( !tile.exists ) { 14969 return best; 14970 } 14971 14972 if ( haveDrawn && !drawTile ) { 14973 if ( isCovered( drawer.coverage, level, x, y ) ) { 14974 setCoverage( drawer.coverage, level, x, y, true ); 14975 } else { 14976 drawTile = true; 14977 } 14978 } 14979 14980 if ( !drawTile ) { 14981 return best; 14982 } 14983 14984 positionTile( 14985 tile, 14986 drawer.source.tileOverlap, 14987 drawer.viewport, 14988 viewportCenter, 14989 levelVisibility 14990 ); 14991 14992 if ( tile.loaded ) { 14993 var needsUpdate = blendTile( 14994 drawer, 14995 tile, 14996 x, y, 14997 level, 14998 levelOpacity, 14999 currentTime 15000 ); 15001 15002 if ( needsUpdate ) { 15003 drawer.updateAgain = true; 15004 } 15005 } else if ( tile.loading ) { 15006 // the tile is already in the download queue 15007 // thanks josh1093 for finally translating this typo 15008 } else { 15009 best = compareTiles( best, tile ); 15010 } 15011 15012 return best; 15013} 15014 15015function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeight ) { 15016 var xMod, 15017 yMod, 15018 bounds, 15019 exists, 15020 url, 15021 tile; 15022 15023 if ( !tilesMatrix[ level ] ) { 15024 tilesMatrix[ level ] = {}; 15025 } 15026 if ( !tilesMatrix[ level ][ x ] ) { 15027 tilesMatrix[ level ][ x ] = {}; 15028 } 15029 15030 if ( !tilesMatrix[ level ][ x ][ y ] ) { 15031 xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; 15032 yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; 15033 bounds = tileSource.getTileBounds( level, xMod, yMod ); 15034 exists = tileSource.tileExists( level, xMod, yMod ); 15035 url = tileSource.getTileUrl( level, xMod, yMod ); 15036 15037 bounds.x += 1.0 * ( x - xMod ) / numTiles.x; 15038 bounds.y += normHeight * ( y - yMod ) / numTiles.y; 15039 15040 tilesMatrix[ level ][ x ][ y ] = new $.Tile( 15041 level, 15042 x, 15043 y, 15044 bounds, 15045 exists, 15046 url 15047 ); 15048 } 15049 15050 tile = tilesMatrix[ level ][ x ][ y ]; 15051 tile.lastTouchTime = time; 15052 15053 return tile; 15054} 15055 15056function loadTile( drawer, tile, time ) { 15057 if( drawer.viewport.collectionMode ){ 15058 drawer.midUpdate = false; 15059 onTileLoad( drawer, tile, time ); 15060 } else { 15061 tile.loading = true; 15062 drawer.imageLoader.addJob({ 15063 src: tile.url, 15064 crossOriginPolicy: drawer.crossOriginPolicy, 15065 callback: function( image ){ 15066 onTileLoad( drawer, tile, time, image ); 15067 } 15068 }); 15069 } 15070} 15071 15072function onTileLoad( drawer, tile, time, image ) { 15073 var insertionIndex, 15074 cutoff, 15075 worstTile, 15076 worstTime, 15077 worstLevel, 15078 worstTileIndex, 15079 prevTile, 15080 prevTime, 15081 prevLevel, 15082 i; 15083 15084 tile.loading = false; 15085 15086 if ( drawer.midUpdate ) { 15087 $.console.warn( "Tile load callback in middle of drawing routine." ); 15088 return; 15089 } else if ( !image && !drawer.viewport.collectionMode ) { 15090 $.console.log( "Tile %s failed to load: %s", tile, tile.url ); 15091 if( !drawer.debugMode ){ 15092 tile.exists = false; 15093 return; 15094 } 15095 } else if ( time < drawer.lastResetTime ) { 15096 $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); 15097 return; 15098 } 15099 15100 tile.loaded = true; 15101 tile.image = image; 15102 15103 15104 insertionIndex = drawer.tilesLoaded.length; 15105 15106 if ( drawer.tilesLoaded.length >
15106= drawer.maxImageCacheCount ) { 15107 cutoff = Math.ceil( Math.log( drawer.source.tileSize ) / Math.log( 2 ) ); 15108 15109 worstTile = null; 15110 worstTileIndex = -1; 15111 15112 for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { 15113 prevTile = drawer.tilesLoaded[ i ]; 15114 15115 if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { 15116 continue; 15117 } else if ( !worstTile ) { 15118 worstTile = prevTile; 15119 worstTileIndex = i; 15120 continue; 15121 } 15122 15123 prevTime = prevTile.lastTouchTime; 15124 worstTime = worstTile.lastTouchTime; 15125 prevLevel = prevTile.level; 15126 worstLevel = worstTile.level; 15127 15128 if ( prevTime < worstTime || 15129 ( prevTime == worstTime && prevLevel > worstLevel ) ) { 15130 worstTile = prevTile; 15131 worstTileIndex = i; 15132 } 15133 } 15134 15135 if ( worstTile && worstTileIndex >= 0 ) { 15136 worstTile.unload(); 15137 insertionIndex = worstTileIndex; 15138 } 15139 } 15140 15141 drawer.tilesLoaded[ insertionIndex ] = tile; 15142 drawer.updateAgain = true; 15143} 15144 15145 15146function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility ){ 15147 var boundsTL = tile.bounds.getTopLeft(), 15148 boundsSize = tile.bounds.getSize(), 15149 positionC = viewport.pixelFromPoint( boundsTL, true ), 15150 positionT = viewport.pixelFromPoint( boundsTL, false ), 15151 sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ), 15152 sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ), 15153 tileCenter = positionT.plus( sizeT.divide( 2 ) ), 15154 tileDistance = viewportCenter.distanceTo( tileCenter ); 15155 15156 if ( !overlap ) { 15157 sizeC = sizeC.plus( new $.Point( 1, 1 ) ); 15158 } 15159 15160 tile.position = positionC; 15161 tile.size = sizeC; 15162 tile.distance = tileDistance; 15163 tile.visibility = levelVisibility; 15164} 15165 15166 15167function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ 15168 var blendTimeMillis = 1000 * drawer.blendTime, 15169 deltaTime, 15170 opacity; 15171 15172 if ( !tile.blendStart ) { 15173 tile.blendStart = currentTime; 15174 } 15175 15176 deltaTime = currentTime - tile.blendStart; 15177 opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; 15178 15179 if ( drawer.alwaysBlend ) { 15180 opacity *= levelOpacity; 15181 } 15182 15183 tile.opacity = opacity; 15184 15185 drawer.lastDrawn.push( tile ); 15186 15187 if ( opacity == 1 ) { 15188 setCoverage( drawer.coverage, level, x, y, true ); 15189 } else if ( deltaTime < blendTimeMillis ) { 15190 return true; 15191 } 15192 15193 return false; 15194} 15195 15196 15197function clearTiles( drawer ) { 15198 drawer.tilesMatrix = {}; 15199 drawer.tilesLoaded = []; 15200} 15201 15202/** 15203 * @private 15204 * @inner 15205 * Returns true if the given tile provides coverage to lower-level tiles of 15206 * lower resolution representing the same content. If neither x nor y is 15207 * given, returns true if the entire visible level provides coverage. 15208 * 15209 * Note that out-of-bounds tiles provide coverage in this sense, since 15210 * there's no content that they would need to cover. Tiles at non-existent 15211 * levels that are within the image bounds, however, do not. 15212 */ 15213function providesCoverage( coverage, level, x, y ) { 15214 var rows, 15215 cols, 15216 i, j; 15217 15218 if ( !coverage[ level ] ) { 15219 return false; 15220 } 15221 15222 if ( x === undefined || y === undefined ) { 15223 rows = coverage[ level ]; 15224 for ( i in rows ) { 15225 if ( rows.hasOwnProperty( i ) ) { 15226 cols = rows[ i ]; 15227 for ( j in cols ) { 15228 if ( cols.hasOwnProperty( j ) && !cols[ j ] ) { 15229 return false; 15230 } 15231 } 15232 } 15233 } 15234 15235 return true; 15236 } 15237 15238 return ( 15239 coverage[ level ][ x] === undefined || 15240 coverage[ level ][ x ][ y ] === undefined || 15241 coverage[ level ][ x ][ y ] === true 15242 ); 15243} 15244 15245/** 15246 * @private 15247 * @inner 15248 * Returns true if the given tile is completely covered by higher-level 15249 * tiles of higher resolution representing the same content. If neither x 15250 * nor y is given, returns true if the entire visible level is covered. 15251 */ 15252function isCovered( coverage, level, x, y ) { 15253 if ( x === undefined || y === undefined ) { 15254 return providesCoverage( coverage, level + 1 ); 15255 } else { 15256 return ( 15257 providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && 15258 providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && 15259 providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && 15260 providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) 15261 ); 15262 } 15263} 15264 15265/** 15266 * @private 15267 * @inner 15268 * Sets whether the given tile provides coverage or not. 15269 */ 15270function setCoverage( coverage, level, x, y, covers ) { 15271 if ( !coverage[ level ] ) { 15272 $.console.warn( 15273 "Setting coverage for a tile before its level's coverage has been reset: %s", 15274 level 15275 ); 15276 return; 15277 } 15278 15279 if ( !coverage[ level ][ x ] ) { 15280 coverage[ level ][ x ] = {}; 15281 } 15282 15283 coverage[ level ][ x ][ y ] = covers; 15284} 15285 15286/** 15287 * @private 15288 * @inner 15289 * Resets coverage information for the given level. This should be called 15290 * after every draw routine. Note that at the beginning of the next draw 15291 * routine, coverage for every visible tile should be explicitly set. 15292 */ 15293function resetCoverage( coverage, level ) { 15294 coverage[ level ] = {}; 15295} 15296 15297/** 15298 * @private 15299 * @inner 15300 * Determines whether the 'last best' tile for the area is better than the 15301 * tile in question. 15302 */ 15303function compareTiles( previousBest, tile ) { 15304 if ( !previousBest ) { 15305 return tile; 15306 } 15307 15308 if ( tile.visibility > previousBest.visibility ) { 15309 return tile; 15310 } else if ( tile.visibility == previousBest.visibility ) { 15311 if ( tile.distance < previousBest.distance ) { 15312 return tile; 15313 } 15314 } 15315 15316 return previousBest; 15317} 15318 15319function drawTiles( drawer, lastDrawn ){ 15320 var i, 15321 tile, 15322 tileKey, 15323 viewer, 15324 viewport, 15325 position, 15326 tileSource, 15327 collectionTileSource; 15328 15329 // We need a callback to give image manipulation a chance to happen 15330 var drawingHandler = function(args) { 15331 if (drawer.viewer) { 15332 /** 15333 * This event is fired just before the tile is drawn giving the application a chance to alter the image. 15334 * 15335 * NOTE: This event is only fired when the drawer is using a <canvas>. 15336 * 15337 * @event tile-drawing 15338 * @memberof OpenSeadragon.Viewer 15339 * @type {object} 15340 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 15341 * @property {OpenSeadragon.Tile} tile
15342 * @property {?Object} userData - 'context', 'tile' and 'rendered'. 15343 */ 15344 drawer.viewer.raiseEvent('tile-drawing', args); 15345 } 15346 }; 15347 15348 for ( i = lastDrawn.length - 1; i >= 0; i-- ) { 15349 tile = lastDrawn[ i ]; 15350 15351 //We dont actually 'draw' a collection tile, rather its used to house 15352 //an overlay which does the drawing in its own viewport 15353 if( drawer.viewport.collectionMode ){ 15354 15355 tileKey = tile.x + '/' + tile.y; 15356 viewport = drawer.viewport; 15357 collectionTileSource = viewport.collectionTileSource; 15358 15359 if( !drawer.collectionOverlays[ tileKey ] ){ 15360 15361 position = collectionTileSource.layout == 'horizontal' ? 15362 tile.y + ( tile.x * collectionTileSource.rows ) : 15363 tile.x + ( tile.y * collectionTileSource.rows ); 15364 15365 if (position < collectionTileSource.tileSources.length) { 15366 tileSource = collectionTileSource.tileSources[ position ]; 15367 } else { 15368 tileSource = null; 15369 } 15370 15371 //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); 15372 if( tileSource ){ 15373 drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ 15374 hash: viewport.viewer.hash + "-" + tileKey, 15375 element: $.makeNeutralElement( "div" ), 15376 mouseNavEnabled: false, 15377 showNavigator: false, 15378 showSequenceControl: false, 15379 showNavigationControl: false, 15380 tileSources: [ 15381 tileSource 15382 ] 15383 }); 15384 15385 //TODO: IE seems to barf on this, not sure if its just the border 15386 // but we probably need to clear this up with a better 15387 // test of support for various css features 15388 if( SUBPIXEL_RENDERING ){ 15389 viewer.element.style.border = '1px solid rgba(255,255,255,0.38)'; 15390 viewer.element.style['-webkit-box-reflect'] = 15391 'below 0px -webkit-gradient('+ 15392 'linear,left '+ 15393 'top,left '+ 15394 'bottom,from(transparent),color-stop(62%,transparent),to(rgba(255,255,255,0.62))'+ 15395 ')'; 15396 } 15397 15398 drawer.viewer.addOverlay( 15399 viewer.element, 15400 tile.bounds 15401 ); 15402 } 15403 15404 }else{ 15405 viewer = drawer.collectionOverlays[ tileKey ]; 15406 if( viewer.viewport ){ 15407 viewer.viewport.resize( tile.size, true ); 15408 viewer.viewport.goHome( true ); 15409 } 15410 } 15411 15412 } else { 15413 15414 if ( drawer.useCanvas ) { 15415 // TODO do this in a more performant way 15416 // specifically, don't save,rotate,restore every time we draw a tile 15417 if( drawer.viewport.degrees !== 0 ) { 15418 offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); 15419 tile.drawCanvas( drawer.context, drawingHandler ); 15420 restoreRotationChanges( tile, drawer.canvas, drawer.context ); 15421 } else { 15422 tile.drawCanvas( drawer.context, drawingHandler ); 15423 } 15424 } else { 15425 tile.drawHTML( drawer.canvas ); 15426 } 15427 15428 15429 tile.beingDrawn = true; 15430 } 15431 15432 if( drawer.debugMode ){ 15433 try{ 15434 drawDebugInfo( drawer, tile, lastDrawn.length, i ); 15435 }catch(e){ 15436 $.console.error(e); 15437 } 15438 } 15439 15440 if( drawer.viewer ){ 15441 /** 15442 * <em>- Needs documentation -</em> 15443 * 15444 * @event tile-drawn 15445 * @memberof OpenSeadragon.Viewer 15446 * @type {object} 15447 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. 15448 * @property {OpenSeadragon.Tile} tile
15449 * @property {?Object} userData - Arbitrary subscriber-defined object. 15450 */ 15451 drawer.viewer.raiseEvent( 'tile-drawn', { 15452 tile: tile 15453 }); 15454 } 15455 } 15456} 15457 15458function offsetForRotation( tile, canvas, context, degrees ){ 15459 var cx = canvas.width / 2, 15460 cy = canvas.height / 2, 15461 px = tile.position.x - cx, 15462 py = tile.position.y - cy; 15463 15464 context.save(); 15465 15466 context.translate(cx, cy); 15467 context.rotate( Math.PI / 180 * degrees); 15468 tile.position.x = px; 15469 tile.position.y = py; 15470} 15471 15472function restoreRotationChanges( tile, canvas, context ){ 15473 var cx = canvas.width / 2, 15474 cy = canvas.height / 2, 15475 px = tile.position.x + cx, 15476 py = tile.position.y + cy; 15477 15478 tile.position.x = px; 15479 tile.position.y = py; 15480 15481 context.restore(); 15482} 15483 15484 15485function drawDebugInfo( drawer, tile, count, i ){ 15486 15487 if ( drawer.useCanvas ) { 15488 drawer.context.save(); 15489 drawer.context.lineWidth = 2; 15490 drawer.context.font = 'small-caps bold 13px ariel'; 15491 drawer.context.strokeStyle = drawer.debugGridColor; 15492 drawer.context.fillStyle = drawer.debugGridColor; 15493 drawer.context.strokeRect( 15494 tile.position.x, 15495 tile.position.y, 15496 tile.size.x, 15497 tile.size.y 15498 ); 15499 if( tile.x === 0 && tile.y === 0 ){ 15500 drawer.context.fillText( 15501 "Zoom: " + drawer.viewport.getZoom(), 15502 tile.position.x, 15503 tile.position.y - 30 15504 ); 15505 drawer.context.fillText( 15506 "Pan: " + drawer.viewport.getBounds().toString(), 15507 tile.position.x, 15508 tile.position.y - 20 15509 ); 15510 } 15511 drawer.context.fillText( 15512 "Level: " + tile.level, 15513 tile.position.x + 10, 15514 tile.position.y + 20 15515 ); 15516 drawer.context.fillText( 15517 "Column: " + tile.x, 15518 tile.position.x + 10, 15519 tile.position.y + 30 15520 ); 15521 drawer.context.fillText( 15522 "Row: " + tile.y, 15523 tile.position.x + 10, 15524 tile.position.y + 40 15525 ); 15526 drawer.context.fillText( 15527 "Order: " + i + " of " + count, 15528 tile.position.x + 10, 15529 tile.position.y + 50 15530 ); 15531 drawer.context.fillText( 15532 "Size: " + tile.size.toString(), 15533 tile.position.x + 10, 15534 tile.position.y + 60 15535 ); 15536 drawer.context.fillText( 15537 "Position: " + tile.position.toString(), 15538 tile.position.x + 10, 15539 tile.position.y + 70 15540 ); 15541 drawer.context.restore(); 15542 } 15543} 15544 15545 15546}( OpenSeadragon )); 15547 15548/* 15549 * OpenSeadragon - Viewport 15550 * 15551 * Copyright (C) 2009 CodePlex Foundation 15552 * Copyright (C) 2010-2013 OpenSeadragon contributors 15553 * 15554 * Redistribution and use in source and binary forms, with or without 15555 * modification, are permitted provided that the following conditions are 15556 * met: 15557 * 15558 * - Redistributions of source code must retain the above copyright notice, 15559 * this list of conditions and the following disclaimer. 15560 * 15561 * - Redistributions in binary form must reproduce the above copyright 15562 * notice, this list of conditions and the following disclaimer in the 15563 * documentation and/or other materials provided with the distribution. 15564 * 15565 * - Neither the name of CodePlex Foundation nor the names of its 15566 * contributors may be used to endorse or promote products derived from 15567 * this software without specific prior written permission. 15568 * 15569 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15570 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15571 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15572 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 15573 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 15574 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 15575 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 15576 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 15577 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 15578 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 15579 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15580 */ 15581 15582(function( $ ){ 15583 15584 15585/** 15586 * @class Viewport 15587 * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.) for an {@link OpenSeadragon.Viewer}. 15588 * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#viewport}). 15589 * 15590 * @memberof OpenSeadragon 15591 */
15592$.Viewport = function( options ) { 15593 15594 //backward compatibility for positional args while prefering more 15595 //idiomatic javascript options object as the only argument 15596 var args = arguments; 15597 if( args.length && args[ 0 ] instanceof $.Point ){ 15598 options = { 15599 containerSize: args[ 0 ], 15600 contentSize: args[ 1 ], 15601 config: args[ 2 ] 15602 }; 15603 } 15604 15605 //options.config and the general config argument are deprecated 15606 //in favor of the more direct specification of optional settings 15607 //being passed directly on the options object 15608 if ( options.config ){ 15609 $.extend( true, options, options.config ); 15610 delete options.config; 15611 } 15612 15613 $.extend( true, this, { 15614 15615 //required settings 15616 containerSize: null, 15617 contentSize: null, 15618 15619 //internal state properties 15620 zoomPoint: null, 15621 viewer: null, 15622 15623 //configurable options 15624 springStiffness: $.DEFAULT_SETTINGS.springStiffness, 15625 animationTime: $.DEFAULT_SETTINGS.animationTime, 15626 minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, 15627 maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio, 15628 visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio, 15629 wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, 15630 wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, 15631 defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel, 15632 minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, 15633 maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, 15634 degrees: $.DEFAULT_SETTINGS.degrees 15635 15636 }, options ); 15637 15638 this.centerSpringX = new $.Spring({ 15639 initial: 0, 15640 springStiffness: this.springStiffness, 15641 animationTime: this.animationTime 15642 }); 15643 this.centerSpringY = new $.Spring({ 15644 initial: 0, 15645 springStiffness: this.springStiffness, 15646 animationTime: this.animationTime 15647 }); 15648 this.zoomSpring = new $.Spring({ 15649 initial: 1, 15650 springStiffness: this.springStiffness, 15651 animationTime: this.animationTime 15652 }); 15653 15654 this.resetContentSize( this.contentSize ); 15655 this.goHome( true ); 15656 this.update(); 15657}; 15658 15659$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ 15660 15661 /** 15662 * @function 15663 * @return {OpenSeadragon.Viewport} Chainable. 15664 * @fires OpenSeadragon.Viewer.event:reset-size 15665 */ 15666 resetContentSize: function( contentSize ){ 15667 this.contentSize = contentSize; 15668 this.contentAspectX = this.contentSize.x / this.contentSize.y; 15669 this.contentAspectY = this.contentSize.y / this.contentSize.x; 15670 this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY ); 15671 this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY); 15672 15673 this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY ); 15674 15675 if( this.viewer ){ 15676 /** 15677 * Raised when the viewer's content size is reset (see {@link OpenSeadragon.Viewport#resetContentSize}). 15678 * 15679 * @event reset-size 15680 * @memberof OpenSeadragon.Viewer 15681 * @type {object} 15682 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 15683 * @property {OpenSeadragon.Point} contentSize 15684 * @property {?Object} userData - Arbitrary subscriber-defined object. 15685 */ 15686 this.viewer.raiseEvent( 'reset-size', { 15687 contentSize: contentSize 15688 }); 15689 } 15690 15691 return this; 15692 }, 15693 15694 /** 15695 * @function 15696 */ 15697 getHomeZoom: function() { 15698 var aspectFactor = 15699 this.contentAspectX / this.getAspectRatio(); 15700 15701 if( this.defaultZoomLevel ){ 15702 return this.defaultZoomLevel; 15703 } else { 15704 return ( aspectFactor >= 1 ) ? 15705 1 : 15706 aspectFactor; 15707 } 15708 }, 15709 15710 /** 15711 * @function 15712 */ 15713 getHomeBounds: function() { 15714 var center = this.homeBounds.getCenter( ), 15715 width = 1.0 / this.getHomeZoom( ), 15716 height = width / this.getAspectRatio(); 15717 15718 return new $.Rect( 15719 center.x - ( width / 2.0 ), 15720 center.y - ( height / 2.0 ), 15721 width, 15722 height 15723 ); 15724 }, 15725 15726 /** 15727 * @function 15728 * @param {Boolean} immediately 15729 * @fires OpenSeadragon.Viewer.event:home 15730 */ 15731 goHome: function( immediately ) { 15732 if( this.viewer ){ 15733 /** 15734 * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). 15735 * 15736 * @event home 15737 * @memberof OpenSeadragon.Viewer 15738 * @type {object} 15739 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 15740 * @property {Boolean} immediately
15741 * @property {?Object} userData - Arbitrary subscriber-defined object. 15742 */ 15743 this.viewer.raiseEvent( 'home', { 15744 immediately: immediately 15745 }); 15746 } 15747 return this.fitBounds( this.getHomeBounds(), immediately ); 15748 }, 15749 15750 /** 15751 * @function 15752 */ 15753 getMinZoom: function() { 15754 var homeZoom = this.getHomeZoom(), 15755 zoom = this.minZoomLevel ? 15756 this.minZoomLevel : 15757 this.minZoomImageRatio * homeZoom; 15758 15759 return Math.min( zoom, homeZoom ); 15760 }, 15761 15762 /** 15763 * @function 15764 */ 15765 getMaxZoom: function() { 15766 var zoom = this.maxZoomLevel ? 15767 this.maxZoomLevel : 15768 ( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x ); 15769 15770 return Math.max( zoom, this.getHomeZoom() ); 15771 }, 15772 15773 /** 15774 * @function 15775 */ 15776 getAspectRatio: function() { 15777 return this.containerSize.x / this.containerSize.y; 15778 }, 15779 15780 /** 15781 * @function 15782 */ 15783 getContainerSize: function() { 15784 return new $.Point( 15785 this.containerSize.x, 15786 this.containerSize.y 15787 ); 15788 }, 15789 15790 /** 15791 * @function 15792 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 15793 */ 15794 getBounds: function( current ) { 15795 var center = this.getCenter( current ), 15796 width = 1.0 / this.getZoom( current ), 15797 height = width / this.getAspectRatio(); 15798 15799 return new $.Rect( 15800 center.x - ( width / 2.0 ), 15801 center.y - ( height / 2.0 ), 15802 width, 15803 height 15804 ); 15805 }, 15806 15807 /** 15808 * @function 15809 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 15810 */ 15811 getCenter: function( current ) { 15812 var centerCurrent = new $.Point( 15813 this.centerSpringX.current.value, 15814 this.centerSpringY.current.value 15815 ), 15816 centerTarget = new $.Point( 15817 this.centerSpringX.target.value, 15818 this.centerSpringY.target.value 15819 ), 15820 oldZoomPixel, 15821 zoom, 15822 width, 15823 height, 15824 bounds, 15825 newZoomPixel, 15826 deltaZoomPixels, 15827 deltaZoomPoints; 15828 15829 if ( current ) { 15830 return centerCurrent; 15831 } else if ( !this.zoomPoint ) { 15832 return centerTarget; 15833 } 15834 15835 oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true); 15836 15837 zoom = this.getZoom(); 15838 width = 1.0 / zoom; 15839 height = width / this.getAspectRatio(); 15840 bounds = new $.Rect( 15841 centerCurrent.x - width / 2.0, 15842 centerCurrent.y - height / 2.0, 15843 width, 15844 height 15845 ); 15846 15847 newZoomPixel = this.zoomPoint.minus( 15848 bounds.getTopLeft() 15849 ).times( 15850 this.containerSize.x / bounds.width 15851 ); 15852 deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); 15853 deltaZoomPoints = deltaZoomPixels.divide( this.containerSize.x * zoom ); 15854 15855 return centerTarget.plus( deltaZoomPoints ); 15856 }, 15857 15858 /** 15859 * @function 15860 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 15861 */ 15862 getZoom: function( current ) { 15863 if ( current ) { 15864 return this.zoomSpring.current.value; 15865 } else { 15866 return this.zoomSpring.target.value; 15867 } 15868 }, 15869 15870 /** 15871 * @function 15872 * @return {OpenSeadragon.Viewport} Chainable. 15873 * @fires OpenSeadragon.Viewer.event:constrain 15874 */ 15875 applyConstraints: function( immediately ) { 15876 var actualZoom = this.getZoom(), 15877 constrainedZoom = Math.max( 15878 Math.min( actualZoom, this.getMaxZoom() ), 15879 this.getMinZoom() 15880 ), 15881 bounds, 15882 horizontalThreshold, 15883 verticalThreshold, 15884 left, 15885 right, 15886 top, 15887 bottom, 15888 dx = 0, 15889 dy = 0; 15890 15891 if ( actualZoom != constrainedZoom ) { 15892 this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); 15893 } 15894 15895 bounds = this.getBounds(); 15896 15897 horizontalThreshold = this.visibilityRatio * bounds.width; 15898 verticalThreshold = this.visibilityRatio * bounds.height; 15899 15900 left = bounds.x + bounds.width; 15901 right = 1 - bounds.x; 15902 top = bounds.y + bounds.height; 15903 bottom = this.contentAspectY - bounds.y; 15904 15905 if ( this.wrapHorizontal ) { 15906 //do nothing 15907 } else { 15908 if ( left < horizontalThreshold ) { 15909 dx = horizontalThreshold - left; 15910 } 15911 if ( right < horizontalThreshold ) { 15912 dx = dx ? 15913 ( dx + right - horizontalThreshold ) / 2 : 15914 ( right - horizontalThreshold ); 15915 } 15916 } 15917 15918 if ( this.wrapVertical ) { 15919 //do nothing 15920 } else { 15921 if ( top < verticalThreshold ) { 15922 dy = ( verticalThreshold - top ); 15923 } 15924 if ( bottom < verticalThreshold ) { 15925 dy = dy ? 15926 ( dy + bottom - verticalThreshold ) / 2 : 15927 ( bottom - verticalThreshold ); 15928 } 15929 } 15930 15931 if ( dx || dy || immediately ) { 15932 bounds.x += dx; 15933 bounds.y += dy; 15934 if( bounds.width > 1 ){ 15935 bounds.x = 0.5 - bounds.width/2; 15936 } 15937 if( bounds.height > this.contentAspectY ){ 15938 bounds.y = this.contentAspectY/2 - bounds.height/2; 15939 } 15940 this.fitBounds( bounds, immediately ); 15941 } 15942 15943 if( this.viewer ){ 15944 /** 15945 * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). 15946 * 15947 * @event constrain 15948 * @memberof OpenSeadragon.Viewer 15949 * @type {object} 15950 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 15951 * @property {Boolean} immediately
15952 * @property {?Object} userData - Arbitrary subscriber-defined object. 15953 */ 15954 this.viewer.raiseEvent( 'constrain', { 15955 immediately: immediately 15956 }); 15957 } 15958 15959 return this; 15960 }, 15961 15962 /** 15963 * @function 15964 * @param {Boolean} immediately 15965 */ 15966 ensureVisible: function( immediately ) { 15967 return this.applyConstraints( immediately ); 15968 }, 15969 15970 /** 15971 * @function 15972 * @param {OpenSeadragon.Rect} bounds 15973 * @param {Boolean} immediately 15974 * @return {OpenSeadragon.Viewport} Chainable. 15975 */ 15976 fitBounds: function( bounds, immediately ) { 15977 var aspect = this.getAspectRatio(), 15978 center = bounds.getCenter(), 15979 newBounds = new $.Rect( 15980 bounds.x, 15981 bounds.y, 15982 bounds.width, 15983 bounds.height 15984 ), 15985 oldBounds, 15986 oldZoom, 15987 newZoom, 15988 referencePoint; 15989 15990 if ( newBounds.getAspectRatio() >= aspect ) { 15991 newBounds.height = bounds.width / aspect; 15992 newBounds.y = center.y - newBounds.height / 2; 15993 } else { 15994 newBounds.width = bounds.height * aspect; 15995 newBounds.x = center.x - newBounds.width / 2; 15996 } 15997 15998 this.panTo( this.getCenter( true ), true ); 15999 this.zoomTo( this.getZoom( true ), null, true ); 16000 16001 oldBounds = this.getBounds(); 16002 oldZoom = this.getZoom(); 16003 newZoom = 1.0 / newBounds.width; 16004 if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) { 16005 return this.panTo( center, immediately ); 16006 } 16007 16008 referencePoint = oldBounds.getTopLeft().times( 16009 this.containerSize.x / oldBounds.width 16010 ).minus( 16011 newBounds.getTopLeft().times( 16012 this.containerSize.x / newBounds.width 16013 ) 16014 ).divide( 16015 this.containerSize.x / oldBounds.width - 16016 this.containerSize.x / newBounds.width 16017 ); 16018 16019 return this.zoomTo( newZoom, referencePoint, immediately ); 16020 }, 16021 16022 16023 /** 16024 * @function 16025 * @param {Boolean} immediately 16026 * @return {OpenSeadragon.Viewport} Chainable. 16027 */ 16028 fitVertically: function( immediately ) { 16029 var center = this.getCenter(); 16030 16031 if ( this.wrapHorizontal ) { 16032 center.x = ( 1 + ( center.x % 1 ) ) % 1; 16033 this.centerSpringX.resetTo( center.x ); 16034 this.centerSpringX.update(); 16035 } 16036 16037 if ( this.wrapVertical ) { 16038 center.y = ( 16039 this.contentAspectY + ( center.y % this.contentAspectY ) 16040 ) % this.contentAspectY; 16041 this.centerSpringY.resetTo( center.y ); 16042 this.centerSpringY.update(); 16043 } 16044 16045 return this.fitBounds( this.fitHeightBounds, immediately ); 16046 }, 16047 16048 /** 16049 * @function 16050 * @param {Boolean} immediately 16051 * @return {OpenSeadragon.Viewport} Chainable. 16052 */ 16053 fitHorizontally: function( immediately ) { 16054 var center = this.getCenter(); 16055 16056 if ( this.wrapHorizontal ) { 16057 center.x = ( 16058 this.contentAspectX + ( center.x % this.contentAspectX ) 16059 ) % this.contentAspectX; 16060 this.centerSpringX.resetTo( center.x ); 16061 this.centerSpringX.update(); 16062 } 16063 16064 if ( this.wrapVertical ) { 16065 center.y = ( 1 + ( center.y % 1 ) ) % 1; 16066 this.centerSpringY.resetTo( center.y ); 16067 this.centerSpringY.update(); 16068 } 16069 16070 return this.fitBounds( this.fitWidthBounds, immediately ); 16071 }, 16072 16073 16074 /** 16075 * @function 16076 * @param {OpenSeadragon.Point} delta 16077 * @param {Boolean} immediately 16078 * @return {OpenSeadragon.Viewport} Chainable. 16079 * @fires OpenSeadragon.Viewer.event:pan 16080 */ 16081 panBy: function( delta, immediately ) { 16082 var center = new $.Point( 16083 this.centerSpringX.target.value, 16084 this.centerSpringY.target.value 16085 ); 16086 delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) ); 16087 return this.panTo( center.plus( delta ), immediately ); 16088 }, 16089 16090 /** 16091 * @function 16092 * @param {OpenSeadragon.Point} center 16093 * @param {Boolean} immediately 16094 * @return {OpenSeadragon.Viewport} Chainable. 16095 * @fires OpenSeadragon.Viewer.event:pan 16096 */ 16097 panTo: function( center, immediately ) { 16098 if ( immediately ) { 16099 this.centerSpringX.resetTo( center.x ); 16100 this.centerSpringY.resetTo( center.y ); 16101 } else { 16102 this.centerSpringX.springTo( center.x ); 16103 this.centerSpringY.springTo( center.y ); 16104 } 16105 16106 if( this.viewer ){ 16107 /** 16108 * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}). 16109 * 16110 * @event pan 16111 * @memberof OpenSeadragon.Viewer 16112 * @type {object} 16113 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 16114 * @property {OpenSeadragon.Point} center 16115 * @property {Boolean} immediately
16116 * @property {?Object} userData - Arbitrary subscriber-defined object. 16117 */ 16118 this.viewer.raiseEvent( 'pan', { 16119 center: center, 16120 immediately: immediately 16121 }); 16122 } 16123 16124 return this; 16125 }, 16126 16127 /** 16128 * @function 16129 * @return {OpenSeadragon.Viewport} Chainable. 16130 * @fires OpenSeadragon.Viewer.event:zoom 16131 */ 16132 zoomBy: function( factor, refPoint, immediately ) { 16133 if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) { 16134 refPoint = refPoint.rotate( 16135 -this.degrees, 16136 new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value ) 16137 ); 16138 } 16139 return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately ); 16140 }, 16141 16142 /** 16143 * @function 16144 * @return {OpenSeadragon.Viewport} Chainable. 16145 * @fires OpenSeadragon.Viewer.event:zoom 16146 */ 16147 zoomTo: function( zoom, refPoint, immediately ) { 16148 16149 this.zoomPoint = refPoint instanceof $.Point && 16150 !isNaN(refPoint.x) && 16151 !isNaN(refPoint.y) ? 16152 refPoint : 16153 null; 16154 16155 if ( immediately ) { 16156 this.zoomSpring.resetTo( zoom ); 16157 } else { 16158 this.zoomSpring.springTo( zoom ); 16159 } 16160 16161 if( this.viewer ){ 16162 /** 16163 * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}). 16164 * 16165 * @event zoom 16166 * @memberof OpenSeadragon.Viewer 16167 * @type {object} 16168 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 16169 * @property {Number} zoom 16170 * @property {OpenSeadragon.Point} refPoint 16171 * @property {Boolean} immediately 16172 * @property {?Object} userData - Arbitrary subscriber-defined object. 16173 */ 16174 this.viewer.raiseEvent( 'zoom', { 16175 zoom: zoom, 16176 refPoint: refPoint, 16177 immediately: immediately 16178 }); 16179 } 16180 16181 return this; 16182 }, 16183 16184 /** 16185 * Currently only 90 degree rotation is supported and it only works 16186 * with the canvas. Additionally, the navigator does not rotate yet, 16187 * debug mode doesn't rotate yet, and overlay rotation is only 16188 * partially supported. 16189 * @function 16190 * @return {OpenSeadragon.Viewport} Chainable. 16191 */ 16192 setRotation: function( degrees ) { 16193 if( !( this.viewer && this.viewer.drawer.canRotate() ) ) { 16194 return this; 16195 } 16196 16197 degrees = ( degrees + 360 ) % 360; 16198 if( degrees % 90 !== 0 ) { 16199 throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); 16200 } 16201 this.degrees = degrees; 16202 this.viewer.forceRedraw(); 16203 16204 return this; 16205 }, 16206 16207 /** 16208 * Gets the current rotation in degrees. 16209 * @function 16210 * @return {Number} The current rotation in degrees. 16211 */ 16212 getRotation: function() { 16213 return this.degrees; 16214 }, 16215 16216 /** 16217 * @function 16218 * @return {OpenSeadragon.Viewport} Chainable. 16219 * @fires OpenSeadragon.Viewer.event:resize 16220 */ 16221 resize: function( newContainerSize, maintain ) { 16222 var oldBounds = this.getBounds(), 16223 newBounds = oldBounds, 16224 widthDeltaFactor; 16225 16226 this.containerSize = new $.Point( 16227 newContainerSize.x, 16228 newContainerSize.y 16229 ); 16230 16231 if ( maintain ) { 16232 widthDeltaFactor = newContainerSize.x / this.containerSize.x; 16233 newBounds.width = oldBounds.width * widthDeltaFactor; 16234 newBounds.height = newBounds.width / this.getAspectRatio(); 16235 } 16236 16237 if( this.viewer ){ 16238 /** 16239 * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}). 16240 * 16241 * @event resize 16242 * @memberof OpenSeadragon.Viewer 16243 * @type {object} 16244 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. 16245 * @property {OpenSeadragon.Point} newContainerSize 16246 * @property {Boolean} maintain
16247 * @property {?Object} userData - Arbitrary subscriber-defined object. 16248 */ 16249 this.viewer.raiseEvent( 'resize', { 16250 newContainerSize: newContainerSize, 16251 maintain: maintain 16252 }); 16253 } 16254 16255 return this.fitBounds( newBounds, true ); 16256 }, 16257 16258 /** 16259 * @function 16260 */ 16261 update: function() { 16262 var oldCenterX = this.centerSpringX.current.value, 16263 oldCenterY = this.centerSpringY.current.value, 16264 oldZoom = this.zoomSpring.current.value, 16265 oldZoomPixel, 16266 newZoomPixel, 16267 deltaZoomPixels, 16268 deltaZoomPoints; 16269 16270 if (this.zoomPoint) { 16271 oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true ); 16272 } 16273 16274 this.zoomSpring.update(); 16275 16276 if (this.zoomPoint && this.zoomSpring.current.value != oldZoom) { 16277 newZoomPixel = this.pixelFromPoint( this.zoomPoint, true ); 16278 deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); 16279 deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true ); 16280 16281 this.centerSpringX.shiftBy( deltaZoomPoints.x ); 16282 this.centerSpringY.shiftBy( deltaZoomPoints.y ); 16283 } else { 16284 this.zoomPoint = null; 16285 } 16286 16287 this.centerSpringX.update(); 16288 this.centerSpringY.update(); 16289 16290 return this.centerSpringX.current.value != oldCenterX || 16291 this.centerSpringY.current.value != oldCenterY || 16292 this.zoomSpring.current.value != oldZoom; 16293 }, 16294 16295 16296 /** 16297 * Convert a delta (translation vector) from pixels coordinates to viewport coordinates 16298 * @function 16299 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 16300 */ 16301 deltaPixelsFromPoints: function( deltaPoints, current ) { 16302 return deltaPoints.times( 16303 this.containerSize.x * this.getZoom( current ) 16304 ); 16305 }, 16306 16307 /** 16308 * Convert a delta (translation vector) from viewport coordinates to pixels coordinates. 16309 * @function 16310 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 16311 */ 16312 deltaPointsFromPixels: function( deltaPixels, current ) { 16313 return deltaPixels.divide( 16314 this.containerSize.x * this.getZoom( current ) 16315 ); 16316 }, 16317 16318 /** 16319 * Convert image pixel coordinates to viewport coordinates. 16320 * @function 16321 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 16322 */ 16323 pixelFromPoint: function( point, current ) { 16324 var bounds = this.getBounds( current ); 16325 return point.minus( 16326 bounds.getTopLeft() 16327 ).times( 16328 this.containerSize.x / bounds.width 16329 ); 16330 }, 16331 16332 /** 16333 * Convert viewport coordinates to image pixel coordinates. 16334 * @function 16335 * @param {Boolean} current - Pass true for the current location; defaults to false (target location). 16336 */ 16337 pointFromPixel: function( pixel, current ) { 16338 var bounds = this.getBounds( current ); 16339 return pixel.divide( 16340 this.containerSize.x / bounds.width 16341 ).plus( 16342 bounds.getTopLeft() 16343 ); 16344 }, 16345 16346 /** 16347 * Translates from OpenSeadragon viewer coordinate system to image coordinate system. 16348 * This method can be called either by passing X,Y coordinates or an 16349 * OpenSeadragon.Point 16350 * @function 16351 * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system. 16352 * @param {Number} viewerX X coordinate in viewport coordinate system. 16353 * @param {Number} viewerY Y coordinate in viewport coordinate system. 16354 * @return {OpenSeadragon.Point} a point representing the coordinates in the image. 16355 */ 16356 viewportToImageCoordinates: function( viewerX, viewerY ) { 16357 if ( arguments.length == 1 ) { 16358 //they passed a point instead of individual components 16359 return this.viewportToImageCoordinates( viewerX.x, viewerX.y ); 16360 } 16361 return new $.Point( viewerX * this.contentSize.x, viewerY * this.contentSize.y * this.contentAspectX ); 16362 }, 16363 16364 /** 16365 * Translates from image coordinate system to OpenSeadragon viewer coordinate system 16366 * This method can be called either by passing X,Y coordinates or an 16367 * OpenSeadragon.Point 16368 * @function 16369 * @param {OpenSeadragon.Point} imageX the point in image coordinate system. 16370 * @param {Number} imageX X coordinate in image coordinate system. 16371 * @param {Number} imageY Y coordinate in image coordinate system. 16372 * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport. 16373 */ 16374 imageToViewportCoordinates: function( imageX, imageY ) { 16375 if ( arguments.length == 1 ) { 16376 //they passed a point instead of individual components 16377 return this.imageToViewportCoordinates( imageX.x, imageX.y ); 16378 } 16379 return new $.Point( imageX / this.contentSize.x, imageY / this.contentSize.y / this.contentAspectX ); 16380 }, 16381 16382 /** 16383 * Translates from a rectangle which describes a portion of the image in 16384 * pixel coordinates to OpenSeadragon viewport rectangle coordinates. 16385 * This method can be called either by passing X,Y,width,height or an 16386 * OpenSeadragon.Rect 16387 * @function 16388 * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system. 16389 * @param {Number} imageX the X coordinate of the top left corner of the rectangle 16390 * in image coordinate system. 16391 * @param {Number} imageY the Y coordinate of the top left corner of the rectangle 16392 * in image coordinate system. 16393 * @param {Number} pixelWidth the width in pixel of the rectangle. 16394 * @param {Number} pixelHeight the height in pixel of the rectangle. 16395 */ 16396 imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) { 16397 var coordA, 16398 coordB, 16399 rect; 16400 if( arguments.length == 1 ) { 16401 //they passed a rectangle instead of individual components 16402 rect = imageX; 16403 return this.imageToViewportRectangle( 16404 rect.x, rect.y, rect.width, rect.height 16405 ); 16406 } 16407 coordA = this.imageToViewportCoordinates( 16408 imageX, imageY 16409 ); 16410 coordB = this.imageToViewportCoordinates( 16411 pixelWidth, pixelHeight 16412 ); 16413 return new $.Rect( 16414 coordA.x, 16415 coordA.y, 16416 coordB.x, 16417 coordB.y 16418 ); 16419 }, 16420 16421 /** 16422 * Translates from a rectangle which describes a portion of 16423 * the viewport in point coordinates to image rectangle coordinates. 16424 * This method can be called either by passing X,Y,width,height or an 16425 * OpenSeadragon.Rect 16426 * @function 16427 * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system. 16428 * @param {Number} viewerX the X coordinate of the top left corner of the rectangle 16429 * in viewport coordinate system. 16430 * @param {Number} imageY the Y coordinate of the top left corner of the rectangle 16431 * in viewport coordinate system. 16432 * @param {Number} pointWidth the width of the rectangle in viewport coordinate system. 16433 * @param {Number} pointHeight the height of the rectangle in viewport coordinate system. 16434 */ 16435 viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) { 16436 var coordA, 16437 coordB, 16438 rect; 16439 if ( arguments.length == 1 ) { 16440 //they passed a rectangle instead of individual components 16441 rect = viewerX; 16442 return this.viewportToImageRectangle( 16443 rect.x, rect.y, rect.width, rect.height 16444 ); 16445 } 16446 coordA = this.viewportToImageCoordinates( viewerX, viewerY ); 16447 coordB = this.viewportToImageCoordinates( pointWidth, pointHeight ); 16448 return new $.Rect( 16449 coordA.x, 16450 coordA.y, 16451 coordB.x, 16452 coordB.y 16453 ); 16454 }, 16455 16456 /** 16457 * Convert pixel coordinates relative to the viewer element to image 16458 * coordinates. 16459 * @param {OpenSeadragon.Point} pixel 16460 * @returns {OpenSeadragon.Point} 16461 */ 16462 viewerElementToImageCoordinates: function( pixel ) { 16463 var point = this.pointFromPixel( pixel, true ); 16464 return this.viewportToImageCoordinates( point ); 16465 }, 16466 16467 /** 16468 * Convert pixel coordinates relative to the image to 16469 * viewer element coordinates. 16470 * @param {OpenSeadragon.Point} pixel 16471 * @returns {OpenSeadragon.Point} 16472 */ 16473 imageToViewerElementCoordinates: function( pixel ) { 16474 var point = this.imageToViewportCoordinates( pixel ); 16475 return this.pixelFromPoint( point, true ); 16476 }, 16477 16478 /** 16479 * Convert pixel coordinates relative to the window to image coordinates. 16480 * @param {OpenSeadragon.Point} pixel 16481 * @returns {OpenSeadragon.Point} 16482 */ 16483 windowToImageCoordinates: function( pixel ) { 16484 var viewerCoordinates = pixel.minus( 16485 OpenSeadragon.getElementPosition( this.viewer.element )); 16486 return this.viewerElementToImageCoordinates( viewerCoordinates ); 16487 }, 16488 16489 /** 16490 * Convert image coordinates to pixel coordinates relative to the window. 16491 * @param {OpenSeadragon.Point} pixel 16492 * @returns {OpenSeadragon.Point} 16493 */ 16494 imageToWindowCoordinates: function( pixel ) { 16495 var viewerCoordinates = this.imageToViewerElementCoordinates( pixel ); 16496 return viewerCoordinates.plus( 16497 OpenSeadragon.getElementPosition( this.viewer.element )); 16498 }, 16499 16500 /** 16501 * Convert pixel coordinates relative to the viewer element to viewport 16502 * coordinates. 16503 * @param {OpenSeadragon.Point} pixel 16504 * @returns {OpenSeadragon.Point} 16505 */ 16506 viewerElementToViewportCoordinates: function( pixel ) { 16507 return this.pointFromPixel( pixel, true ); 16508 }, 16509 16510 /** 16511 * Convert viewport coordinates to pixel coordinates relative to the 16512 * viewer element. 16513 * @param {OpenSeadragon.Point} point 16514 * @returns {OpenSeadragon.Point} 16515 */ 16516 viewportToViewerElementCoordinates: function( point ) { 16517 return this.pixelFromPoint( point, true ); 16518 }, 16519 16520 /** 16521 * Convert pixel coordinates relative to the window to viewport coordinates. 16522 * @param {OpenSeadragon.Point} pixel 16523 * @returns {OpenSeadragon.Point} 16524 */ 16525 windowToViewportCoordinates: function( pixel ) { 16526 var viewerCoordinates = pixel.minus( 16527 OpenSeadragon.getElementPosition( this.viewer.element )); 16528 return this.viewerElementToViewportCoordinates( viewerCoordinates ); 16529 }, 16530 16531 /** 16532 * Convert viewport coordinates to pixel coordinates relative to the window. 16533 * @param {OpenSeadragon.Point} point 16534 * @returns {OpenSeadragon.Point} 16535 */ 16536 viewportToWindowCoordinates: function( point ) { 16537 var viewerCoordinates = this.viewportToViewerElementCoordinates( point ); 16538 return viewerCoordinates.plus( 16539 OpenSeadragon.getElementPosition( this.viewer.element )); 16540 }, 16541 16542 /** 16543 * Convert a viewport zoom to an image zoom. 16544 * Image zoom: ratio of the original image size to displayed image size. 16545 * 1 means original image size, 0.5 half size... 16546 * Viewport zoom: ratio of the displayed image's width to viewport'
16546s width. 16547 * 1 means identical width, 2 means image's width is twice the viewport's width... 16548 * @function 16549 * @param {Number} viewportZoom The viewport zoom 16550 * target zoom. 16551 * @returns {Number} imageZoom The image zoom 16552 */ 16553 viewportToImageZoom: function( viewportZoom ) { 16554 var imageWidth = this.viewer.source.dimensions.x; 16555 var containerWidth = this.getContainerSize().x; 16556 var viewportToImageZoomRatio = containerWidth / imageWidth; 16557 return viewportZoom * viewportToImageZoomRatio; 16558 }, 16559 16560 /** 16561 * Convert an image zoom to a viewport zoom. 16562 * Image zoom: ratio of the original image size to displayed image size. 16563 * 1 means original image size, 0.5 half size... 16564 * Viewport zoom: ratio of the displayed image's width to viewport's width. 16565 * 1 means identical width, 2 means image's width is twice the viewport's width... 16566 * @function 16567 * @param {Number} imageZoom The image zoom 16568 * target zoom. 16569 * @returns {Number} viewportZoom The viewport zoom 16570 */ 16571 imageToViewportZoom: function( imageZoom ) { 16572 var imageWidth = this.viewer.source.dimensions.x; 16573 var containerWidth = this.getContainerSize().x; 16574 var viewportToImageZoomRatio = imageWidth / containerWidth; 16575 return imageZoom * viewportToImageZoomRatio; 16576 } 16577}; 16578 16579}( OpenSeadragon ));
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.