PageSourceSearch

https://searchlibrary.sheridancollege.ca/discovery/lib/bower_components/openseadragon/openseadragon.js

js sheridancollege.ca collected 2026-09-24 09:04:06 UTC 873,069 bytes, 22,792 lines download raw bytes

1//! openseadragon 2.4.2
2//! Built on 2020-03-05
3//! Git commit: v2.4.2-0-c450749
4//! http://openseadragon.github.io
5//! License: http://openseadragon.github.io/license/
6
7/*
8 * OpenSeadragon
9 *
10 * Copyright (C) 2009 CodePlex Foundation
11 * Copyright (C) 2010-2013 OpenSeadragon contributors
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are
15 * met:
16 *
17 * - Redistributions of source code must retain the above copyright notice,
18 *   this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above copyright
21 *   notice, this list of conditions and the following disclaimer in the
22 *   documentation and/or other materials provided with the distribution.
23 *
24 * - Neither the name of CodePlex Foundation nor the names of its
25 *   contributors may be used to endorse or promote products derived from
26 *   this software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
32 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
34 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 */
40
41/*
42 * Portions of this source file taken from jQuery:
43 *
44 * Copyright 2011 John Resig
45 *
46 * Permission is hereby granted, free of charge, to any person obtaining
47 * a copy of this software and associated documentation files (the
48 * "Software"), to deal in the Software without restriction, including
49 * without limitation the rights to use, copy, modify, merge, publish,
50 * distribute, sublicense, and/or sell copies of the Software, and to
51 * permit persons to whom the Software is furnished to do so, subject to
52 * the following conditions:
53 *
54 * The above copyright notice and this permission notice shall be
55 * included in all copies or substantial portions of the Software.
56 *
57 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
58 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
60 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
61 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
62 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
63 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
64 */
65
66/*
67 * Portions of this source file taken from mattsnider.com:
68 *
69 * Copyright (c) 2006-2013 Matt Snider
70 *
71 * Permission is hereby granted, free of charge, to any person obtaining a
72 * copy of this software and associated documentation files (the "Software"),
73 * to deal in the Software without restriction, including without limitation
74 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
75 * and/or sell copies of the Software, and to permit persons to whom the
76 * Software is furnished to do so, subject to the following conditions:
77 *
78 * The above copyright notice and this permission notice shall be included
79 * in all copies or substantial portions of the Software.
80 *
81 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
82 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
83 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
84 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
85 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
86 * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
87 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
88 */
89
90
91/**
92 * @namespace OpenSeadragon
93 * @version openseadragon 2.4.2
94 * @classdesc The root namespace for OpenSeadragon.  All utility methods
95 * and classes are defined on or below this namespace.
96 *
97 */
98
99
100// Typedefs
101
102 /**
103  * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer.
104  *
105  * @typedef {Object} Options
106  * @memberof OpenSeadragon
107  *
108  * @property {String} id
109  *     Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided.
110  *     If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
111  *
112  * @property {Element} element
113  *     The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
114  *     If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
115  *
116  * @property {Array|String|Function|Object} [tileSources=null]
117  *     Tile source(s) to open initially. This is a complex parameter; see
118  *     {@link OpenSeadragon.Viewer#open} for details.
119  *
120  * @property {Number} [tabIndex=0]
121  *     Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0
122  *     source order is used. A negative value omits the viewer from the tabbing order.
123  *
124  * @property {Array} overlays Array of objects defining permanent overlays of
125  *     the viewer. The overlays added via this option and later removed with
126  *     {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new
127  *     image is opened.
128  *     To add overlays which can be definitively removed, one must use
129  *     {@link OpenSeadragon.Viewer#addOverlay}
130  *     If displaying a sequence of images, the overlays can be associated
131  *     with a specific page by passing the overlays array to the page's
132  *     tile source configuration.
133  *     Expected properties:
134  *     * x, y, (or px, py for pixel coordinates) to define the location.
135  *     * width, height in point if using x,y or in pixels if using px,py. If width
136  *       and height are specified, the overlay size is adjusted when zooming,
137  *       otherwise the size stays the size of the content (or the size defined by CSS).
138  *     * className to associate a class to the overlay
139  *     * id to set the overlay element. If an element with this id already exists,
140  *       it is reused, otherwise it is created. If not specified, a new element is
141  *       created.
142  *     * placement a string to define the relative position to the viewport.
143  *       Only used if no width and height are specified. Default: 'TOP_LEFT'.
144  *       See {@link OpenSeadragon.Placement} for possible values.
145  *
146  * @property {String} [xmlPath=null]
147  *     <strong>DEPRECATED</strong>. A relative path to load a DZI file from the server.
148  *     Prefer the newer Options.tileSources.
149  *
150  * @property {String} [prefixUrl='/images/']
151  *     Prepends the prefixUrl to navImages paths, which is very useful
152  *     since the default paths are rarely useful for production
153  *     environments.
154  *
155  * @property {OpenSeadragon.NavImages} [navImages]
156  *     An object with a property for each button or other built-in navigation
157  *     control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.
158  *     Each of those in turn provides an image path for each state of the button
159  *     or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the
160  *     image paths, by default assume there is a folder on the servers root path
161  *     called '/images', eg '/images/zoomin_rest.png'.  If you need to adjust
162  *     these paths, prefer setting the option.prefixUrl rather than overriding
163  *     every image path directly through this setting.
164  *
165  * @property {Boolean} [debugMode=false]
166  *     TODO: provide an in-screen panel providing event detail feedback.
167  *
168  * @property {String} [debugGridColor=['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']]
169  *     The colors of grids in debug mode. Each tiled image's grid uses a consecutive color.
170  *     If there are more tiled images than provided colors, the color vector is recycled.
171  *
172  * @property {Number}
172 [blendTime=0]
173  *     Specifies the duration of animation as higher or lower level tiles are
174  *     replacing the existing tile.
175  *
176  * @property {Boolean} [alwaysBlend=false]
177  *     Forces the tile to always blend.  By default the tiles skip blending
178  *     when the blendTime is surpassed and the current animation frame would
179  *     not complete the blend.
180  *
181  * @property {Boolean} [autoHideControls=true]
182  *     If the user stops interacting with the viewport, fade the navigation
183  *     controls.  Useful for presentation since the controls are by default
184  *     floated on top of the image the user is viewing.
185  *
186  * @property {Boolean} [immediateRender=false]
187  *     Render the best closest level first, ignoring the lowering levels which
188  *     provide the effect of very blurry to sharp. It is recommended to change
189  *     setting to true for mobile devices.
190  *
191  * @property {Number} [defaultZoomLevel=0]
192  *     Zoom level to use when image is first opened or the home button is clicked.
193  *     If 0, adjusts to fit viewer.
194  *
195  * @property {Number} [opacity=1]
196  *     Default proportional opacity of the tiled images (1=opaque, 0=hidden)
197  *     Hidden images do not draw and only load when preloading is allowed.
198  *
199  * @property {Boolean} [preload=false]
200  *     Default switch for loading hidden images (true loads, false blocks)
201  *
202  * @property {String} [compositeOperation=null]
203  *     Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',
204  *     'destination-over', 'destination-atop', 'destination-in',
205  *     'destination-out', 'lighter', 'copy' or 'xor'
206  *
207  * @property {Boolean} [imageSmoothingEnabled=true]
208  *     Image smoothing for canvas rendering (only if canvas is used). Note: Ignored
209  *     by some (especially older) browsers which do not support this canvas property.
210  *     This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}.
211  *
212  * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
213  *     Draws a colored rectangle behind the tile if it is not loaded yet.
214  *     You can pass a CSS color value like "#FF8800".
215  *     When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.
216  *
217  * @property {Number} [degrees=0]
218  *     Initial rotation.
219  *
220  * @property {Boolean} [flipped=false]
221  *     Initial flip state.
222  *
223  * @property {Number} [minZoomLevel=null]
224  *
225  * @property {Number} [maxZoomLevel=null]
226  *
227  * @property {Boolean} [homeFillsViewer=false]
228  *     Make the 'home' button fill the viewer and clip the image, instead
229  *     of fitting the image to the viewer and letterboxing.
230  *
231  * @property {Boolean} [panHorizontal=true]
232  *     Allow horizontal pan.
233  *
234  * @property {Boolean} [panVertical=true]
235  *     Allow vertical pan.
236  *
237  * @property {Boolean} [constrainDuringPan=false]
238  *
239  * @property {Boolean} [wrapHorizontal=false]
240  *     Set to true to force the image to wrap horizontally within the viewport.
241  *     Useful for maps or images representing the surface of a sphere or cylinder.
242  *
243  * @property {Boolean} [wrapVertical=false]
244  *     Set to true to force the image to wrap vertically within the viewport.
245  *     Useful for maps or images representing the surface of a sphere or cylinder.
246  *
247  * @property {Number} [minZoomImageRatio=0.9]
248  *     The minimum percentage ( expressed as a number between 0 and 1 ) of
249  *     the viewport height or width at which the zoom out will be constrained.
250  *     Setting it to 0, for example will allow you to zoom out infinity.
251  *
252  * @property {Number} [maxZoomPixelRatio=1.1]
253  *     The maximum ratio to allow a zoom-in to affect the highest level pixel
254  *     ratio. This can be set to Infinity to allow 'infinite' zooming into the
255  *     image though it is less effective visually if the HTML5 Canvas is not
256  *     available on the viewing device.
257  *
258  * @property {Number} [smoothTileEdgesMinZoom=1.1]
259  *     A zoom percentage ( where 1 is 100% ) of the highest resolution level.
260  *     When zoomed in beyond this value alternative compositing will be used to
261  *     smooth out the edges between tiles. This will have a performance impact.
262  *     Can be set to Infinity to turn it off.
263  *     Note: This setting is ignored on iOS devices due to a known bug (See {@link https://github.com/openseadragon/openseadragon/issues/952})
264  *
265  * @property {Boolean} [iOSDevice=?]
266  *     True if running on an iOS device, false otherwise.
267  *     Used to disable certain features that behave differently on iOS devices.
268  *
269  * @property {Boolean} [autoResize=true]
270  *     Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
271  *
272  * @property {Boolean} [preserveImageSizeOnResize=false]
273  *     Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).
274  *
275  * @property {Number}
275 [minScrollDeltaTime=50]
276  *     Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll
277  *     events between different devices, causing the faster devices to slow down enough to make the zoom control
278  *     more manageable.
279  *
280  * @property {Number} [rotationIncrement=90]
281  *     The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
282  *
283  * @property {Number} [pixelsPerWheelLine=40]
284  *     For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
285  *
286  * @property {Number} [pixelsPerArrowPress=40]
287  *     The number of pixels viewport moves when an arrow key is pressed.
288  *
289  * @property {Number} [visibilityRatio=0.5]
290  *     The percentage ( as a number from 0 to 1 ) of the source image which
291  *     must be kept within the viewport.  If the image is dragged beyond that
292  *     limit, it will 'bounce' back until the minimum visibility ratio is
293  *     achieved.  Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
294  *     true will provide the effect of an infinitely scrolling viewport.
295  *
296  * @property {Object} [viewportMargins={}]
297  *     Pushes the "home" region in from the sides by the specified amounts.
298  *     Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
299  *
300  * @property {Number} [imageLoaderLimit=0]
301  *     The maximum number of image requests to make concurrently. By default
302  *     it is set to 0 allowing the browser to make the maximum number of
303  *     image requests in parallel as allowed by the browsers policy.
304  *
305  * @property {Number} [clickTimeThreshold=300]
306  *      The number of milliseconds within which a pointer down-up event combination
307  *      will be treated as a click gesture.
308  *
309  * @property {Number} [clickDistThreshold=5]
310  *      The maximum distance allowed between a pointer down event and a pointer up event
311  *      to be treated as a click gesture.
312  *
313  * @property {Number} [dblClickTimeThreshold=300]
314  *      The number of milliseconds within which two pointer down-up event combinations
315  *      will be treated as a double-click gesture.
316  *
317  * @property {Number} [dblClickDistThreshold=20]
318  *      The maximum distance allowed between two pointer click events
319  *      to be treated as a double-click gesture.
320  *
321  * @property {Number} [springStiffness=6.5]
322  *
323  * @property {Number} [animationTime=1.2]
324  *     Specifies the animation duration per each {@link OpenSeadragon.Spring}
325  *     which occur when the image is dragged or zoomed.
326  *
327  * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]
328  *     Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})
329  * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture
330  * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture
331  * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
332  *     then clickToZoom should be set to false to prevent multiple zooms.
333  * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture
334  * @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
335  *     the zoom is centered at the canvas center.
336  * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
337  * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
338  * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
339  * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
340  *
341  * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]
342  *     Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})
343  * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture
344  * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture
345  * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
346  *     then clickToZoom should be set to false to prevent multiple zooms.
347  * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture
348  * @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
349  *     the zoom is centered at the canvas center.
350  * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
351  * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
352  * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
353  * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
354  *
355  * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]
356  *     Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})
357  * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture
358  * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture
359  * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
360  *     then clickToZoom should be set to false to prevent multiple zooms.
361  * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture
362  * @property {Boolean} [gestureSettingsPen.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
363  *     the zoom is centered at the canvas center.
364  * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
365  * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
366  * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
367  * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
368  *
369  * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]
370  *     Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})
371  * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture
372  * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture
373  * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
374  *     then clickToZoom should be set to false to prevent multiple zooms.
375  * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture
376  * @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
377  *     the zoom is centered at the canvas center.
378  * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
379  * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
380  * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
381  * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
382  *
383  * @property {Number} [zoomPerClick=2.0]
384  *     The "zoom distance" per mouse click or touch tap. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).</em>
385  *
386  * @property {Number} [zoomPerScroll=1.2]
387  *     The "zoom distance" per mouse scroll or touch pinch. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).</em>
388  *
389  * @property {Number} [zoomPerSecond=1.0]
390  *     The number of seconds to animate a single zoom event over.
391  *
392  * @property {Boolean} [showNavigator=false]
393  *     Set to true to make the navigator minimap appear.
394  *
395  * @property {String} [navigatorId=navigator-GENERATED DATE]
396  *     The ID of a div to hold the navigator minimap.
397  *     If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored.
398  *     If an ID is not specified, a div element will be generated and placed on top of the main image.
399  *
400  * @property {String} [navigatorPosition='TOP_RIGHT']
401  *     Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.<br>
402  *     If 'ABSOLUTE' is specified, then navigator[Top|Left|Height|Width] determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br>
403  *     For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigator[Height|Width] values determine the size of the navigator minimap.
404  *
405  * @property {Number} [navigatorSizeRatio=0.2]
406  *     Ratio of navigator size to viewer size. Ignored if navigator[Height|Width] are specified.
407  *
408  * @property {Boolean} [navigatorMaintainSizeRatio=false]
409  *     If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes.
410  *
411  * @property {Number|String} [navigatorTop=null]
412  *     Specifies the location of the navigator minimap (see navigatorPosition).
413  *
414  * @property {Number|String} [navigatorLeft=null]
415  *     Specifies the location of the navigator minimap (see navigatorPosition).
416  *
417  * @property {Number|String} [navigatorHeight=null]
418  *     Specifies the size of the navigator minimap (see navigatorPosition).
419  *     If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
420  *
421  * @property {Number|String} [navigatorWidth=null]
422  *     Specifies the size of the navigator minimap (see navigatorPosition).
423  *     If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
424  *
425  * @property {Boolean} [navigatorAutoResize=true]
426  *     Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
427  *     Setting to false can also improve performance when the navigator is configured to a fixed size.
428  *
429  * @property {Boolean} [navigatorAutoFade=true]
430  *     If the user stops interacting with the viewport, fade the navigator minimap.
431  *     Setting to false will make the navigator minimap always visible.
432  *
433  * @property {Boolean} [navigatorRotate=true]
434  *     If true, the navigator will be rotated together with the viewer.
435  *
436  * @property {String} [navigatorBackground='#000']
437  *     Specifies the background color of the navigator minimap
438  *
439  * @property {Number} [navigatorOpacity=0.8]
440  *     Specifies the opacity of the navigator minimap.
441  *
442  * @property {String} [navigatorBorderColor='#555']
443  *     Specifies the border color of the navigator minimap
444  *
445  * @property {String} [navigatorDisplayRegionColor='#900']
446  *     Specifies the border color of the display region rectangle of the navigator minimap
447  *
448  * @property {Number} [controlsFadeDelay=2000]
449  *     The number of milliseconds to wait once the user has stopped interacting
450  *     with the interface before beginning to fade the controls. Assumes
451  *     showNavigationControl and autoHideControls are both true.
452  *
453  * @property {Number} [controlsFadeLength=1500]
454  *     The number of milliseconds to animate the controls fading out.
455  *
456  * @property {Number} [maxImageCacheCount=200]
457  *     The max number of images we should keep in memory (per drawer).
458  *
459  * @property {Number} [timeout=30000]
460  *     The max number of milliseconds that an image job may take to complete.
461  *
462  * @property {Boolean} [useCanvas=true]
463  *     Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
464  *
465  * @property {Number} [minPixelRatio=0.5]
466  *     The higher the minPixelRatio, the lower the quality of the image that
467  *     is considered sufficient to stop rendering a given zoom level.  For
468  *     example, if you are targeting mobile devices with less bandwidth you may
469  *     try setting this to 1.5 or higher.
470  *
471  * @property {Boolean} [mouseNavEnabled=true]
472  *     Is the user able to interact with the image via mouse or touch. Default
473  *     interactions include draging the image in a plane, and zooming in toward
474  *     and away from the image.
475  *
476  * @property {Boolean}
476 [showNavigationControl=true]
477  *     Set to false to prevent the appearance of the default navigation controls.<br>
478  *     Note that if set to false, the customs buttons set by the options
479  *     zoomInButton, zoomOutButton etc, are rendered inactive.
480  *
481  * @property {OpenSeadragon.ControlAnchor} [navigationControlAnchor=TOP_LEFT]
482  *     Placement of the default navigation controls.
483  *     To set the placement of the sequence controls, see the
484  *     sequenceControlAnchor option.
485  *
486  * @property {Boolean} [showZoomControl=true]
487  *     If true then + and - buttons to zoom in and out are displayed.<br>
488  *     Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
489  *     this setting when set to false.
490  *
491  * @property {Boolean} [showHomeControl=true]
492  *     If true then the 'Go home' button is displayed to go back to the original
493  *     zoom and pan.<br>
494  *     Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
495  *     this setting when set to false.
496  *
497  * @property {Boolean} [showFullPageControl=true]
498  *     If true then the 'Toggle full page' button is displayed to switch
499  *     between full page and normal mode.<br>
500  *     Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
501  *     this setting when set to false.
502  *
503  * @property {Boolean} [showRotationControl=false]
504  *     If true then the rotate left/right controls will be displayed as part of the
505  *     standard controls. This is also subject to the browser support for rotate
506  *     (e.g. viewer.drawer.canRotate()).<br>
507  *     Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
508  *     this setting when set to false.
509  *
510  * @property {Boolean} [showFlipControl=false]
511  *     If true then the flip controls will be displayed as part of the
512  *     standard controls.
513  *
514  * @property {Boolean} [showSequenceControl=true]
515  *     If sequenceMode is true, then provide buttons for navigating forward and
516  *     backward through the images.
517  *
518  * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT]
519  *     Placement of the default sequence controls.
520  *
521  * @property {Boolean} [navPrevNextWrap=false]
522  *     If true then the 'previous' button will wrap to the last image when
523  *     viewing the first image and the 'next' button will wrap to the first
524  *     image when viewing the last image.
525  *
526  * @property {String} zoomInButton
527  *     Set the id of the custom 'Zoom in' button to use.
528  *     This is useful to have a custom button anywhere in the web page.<br>
529  *     To only change the button images, consider using
530  *     {@link OpenSeadragon.Options.navImages}
531  *
532  * @property {String} zoomOutButton
533  *     Set the id of the custom 'Zoom out' button to use.
534  *     This is useful to have a custom button anywhere in the web page.<br>
535  *     To only change the button images, consider using
536  *     {@link OpenSeadragon.Options.navImages}
537  *
538  * @property {String} homeButton
539  *     Set the id of the custom 'Go home' button to use.
540  *     This is useful to have a custom button anywhere in the web page.<br>
541  *     To only change the button images, consider using
542  *     {@link OpenSeadragon.Options.navImages}
543  *
544  * @property {String} fullPageButton
545  *     Set the id of the custom 'Toggle full page' button to use.
546  *     This is useful to have a custom button anywhere in the web page.<br>
547  *     To only change the button images, consider using
548  *     {@link OpenSeadragon.Options.navImages}
549  *
550  * @property {String} rotateLeftButton
551  *     Set the id of the custom 'Rotate left' button to use.
552  *     This is useful to have a custom button anywhere in the web page.<br>
553  *     To only change the button images, consider using
554  *     {@link OpenSeadragon.Options.navImages}
555  *
556  * @property {String} rotateRightButton
557  *     Set the id of the custom 'Rotate right' button to use.
558  *     This is useful to have a custom button anywhere in the web page.<br>
559  *     To only change the button images, consider using
560  *     {@link OpenSeadragon.Options.navImages}
561  *
562  * @property {String} previousButton
563  *     Set the id of the custom 'Previous page' button to use.
564  *     This is useful to have a custom button anywhere in the web page.<br>
565  *     To only change the button images, consider using
566  *     {@link OpenSeadragon.Options.navImages}
567  *
568  * @property {String} nextButton
569  *     Set the id
vendor: 3,425 bytes, lines 569-636
569 of the custom 'Next page' button to use.
570  *     This is useful to have a custom button anywhere in the web page.<br>
571  *     To only change the button images, consider using
572  *     {@link OpenSeadragon.Options.navImages}
573  *
574  * @property {Boolean} [sequenceMode=false]
575  *     Set to true to have the viewer treat your tilesources as a sequence of images to
576  *     be opened one at a time rather than all at once.
577  *
578  * @property {Number} [initialPage=0]
579  *     If sequenceMode is true, display this page initially.
580  *
581  * @property {Boolean} [preserveViewport=false]
582  *     If sequenceMode is true, then normally navigating through each image resets the
583  *     viewport to 'home' position.  If preserveViewport is set to true, then the viewport
584  *     position is preserved when navigating between images in the sequence.
585  *
586  * @property {Boolean} [preserveOverlays=false]
587  *     If sequenceMode is true, then normally navigating through each image
588  *     resets the overlays.
589  *     If preserveOverlays is set to true, then the overlays added with {@link OpenSeadragon.Viewer#addOverlay}
590  *     are preserved when navigating between images in the sequence.
591  *     Note: setting preserveOverlays overrides any overlays specified in the global
592  *     "overlays" option for the Viewer. It's also not compatible with specifying
593  *     per-tileSource overlays via the options, as those overlays will persist
594  *     even after the tileSource is closed.
595  *
596  * @property {Boolean} [showReferenceStrip=false]
597  *     If sequenceMode is true, then display a scrolling strip of image thumbnails for
598  *     navigating through the images.
599  *
600  * @property {String} [referenceStripScroll='horizontal']
601  *
602  * @property {Element} [referenceStripElement=null]
603  *
604  * @property {Number} [referenceStripHeight=null]
605  *
606  * @property {Number} [referenceStripWidth=null]
607  *
608  * @property {String} [referenceStripPosition='BOTTOM_LEFT']
609  *
610  * @property {Number} [referenceStripSizeRatio=0.2]
611  *
612  * @property {Boolean} [collectionMode=false]
613  *     Set to true to have the viewer arrange your TiledImages in a grid or line.
614  *
615  * @property {Number} [collectionRows=3]
616  *     If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line.
617  *     If collectionLayout is 'vertical', specifies how many columns instead.
618  *
619  * @property {Number} [collectionColumns=0]
620  *     If collectionMode is true, specifies how many columns the grid should have. Use 1 to make a line.
621  *     If collectionLayout is 'vertical', specifies how many rows instead. Ignored if collectionRows is not set to a falsy value.
622  *
623  * @property {String} [collectionLayout='horizontal']
624  *     If collectionMode is true, specifies whether to arrange vertically or horizontally.
625  *
626  * @property {Number} [collectionTileSize=800]
627  *     If collectionMode is true, specifies the size, in viewport coordinates, for each TiledImage to fit into.
628  *     The TiledImage will be centered within a square of the specified size.
629  *
630  * @property {Number} [collectionTileMargin=80]
631  *     If collectionMode is true, specifies the margin, in viewport coordinates, between each TiledImage.
632  *
633  * @property {String|Boolean} [crossOriginPolicy=false]
634  *     Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
635  *     not use CORS, and the canvas will be tainted.
636  *
636
637  * @property {Boolean} [ajaxWithCredentials=false]
638  *     Whether to set the withCredentials XHR flag for AJAX requests.
639  *     Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
640  *
641  * @property {Boolean} [loadTilesWithAjax=false]
642  *     Whether to load tile data using AJAX requests.
643  *     Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
644  *
645  * @property {Object} [ajaxHeaders={}]
646  *     A set of headers to include when making AJAX requests for tile sources or tiles.
647  *
648  */
649
650 /**
651  * Settings for gestures generated by a pointer device.
652  *
653  * @typedef {Object} GestureSettings
654  * @memberof OpenSeadragon
655  *
656  * @property {Boolean} scrollToZoom
657  *     Set to false to disable zooming on scroll gestures.
658  *
659  * @property {Boolean} clickToZoom
660  *     Set to false to disable zooming on click gestures.
661  *
662  * @property {Boolean} dblClickToZoom
663  *     Set to false to disable zooming on double-click gestures. Note: If set to true
664  *     then clickToZoom should be set to false to prevent multiple zooms.
665  *
666  * @property {Boolean} pinchToZoom
667  *     Set to false to disable zooming on pinch gestures.
668  *
669  * @property {Boolean} flickEnabled
670  *     Set to false to disable the kinetic panning effect (flick) at the end of a drag gesture.
671  *
672  * @property {Number} flickMinSpeed
673  *     If flickEnabled is true, the minimum speed (in pixels-per-second) required to cause the kinetic panning effect (flick) at the end of a drag gesture.
674  *
675  * @property {Number} flickMomentum
676  *     If flickEnabled is true, a constant multiplied by the velocity to determine the distance of the kinetic panning effect (flick) at the end of a drag gesture.
677  *     A larger value will make the flick feel "lighter", while a smaller value will make the flick feel "heavier".
678  *     Note: springStiffness and animationTime also affect the "spring" used to stop the flick animation.
679  *
680  */
681
682/**
683  * The names for the image resources used for the image navigation buttons.
684  *
685  * @typedef {Object} NavImages
686  * @memberof OpenSeadragon
687  *
688  * @property {Object} zoomIn - Images for the zoom-in button.
689  * @property {String} zoomIn.REST
690  * @property {String} zoomIn.GROUP
691  * @property {String} zoomIn.HOVER
692  * @property {String} zoomIn.DOWN
693  *
694  * @property {Object} zoomOut - Images for the zoom-out button.
695  * @property {String} zoomOut.REST
696  * @property {String} zoomOut.GROUP
697  * @property {String} zoomOut.HOVER
698  * @property {String} zoomOut.DOWN
699  *
700  * @property {Object} home - Images for the home button.
701  * @property {String} home.REST
702  * @property {String} home.GROUP
703  * @property {String} home.HOVER
704  * @property {String} home.DOWN
705  *
706  * @property {Object} fullpage - Images for the full-page button.
707  * @property {String} fullpage.REST
708  * @property {String} fullpage.GROUP
709  * @property {String} fullpage.HOVER
710  * @property {String} fullpage.DOWN
711  *
712  * @property {Object} rotateleft - Images for the rotate left button.
713  * @property {String} rotateleft.REST
714  * @property {String} rotateleft.GROUP
715  * @property {String} rotateleft.HOVER
716  * @property {String} rotateleft.DOWN
717  *
718  * @property {Object} rotateright - Images for the rotate right button.
719  * @property {String} rotateright.REST
720  * @property {String} rotateright.GROUP
721  * @property {String} rotateright.HOVER
722  * @property {String} rotateright.DOWN
723  *
724  * @property {Object} flip - Images for the flip button.
725  * @property {String} flip.REST
726  * @property {String} flip.GROUP
727  * @property {String} flip.HOVER
728  * @property {String} flip.DOWN
729  *
730  * @property {Object} previous - Images for the previous button.
731  * @property {String} previous.REST
732  * @property {String} previous.GROUP
733  * @property {String} previous.HOVER
734  * @property {String} previous.DOWN
735  *
736  * @property {Object} next - Images for the next button.
737  * @property {String} next.REST
738  * @property {String} next.GROUP
739  * @property {String} next.HOVER
740  * @property {String} next.DOWN
741  *
742  */
743
744/* eslint-disable no-redeclare */
745function OpenSeadragon( options ){
746    return new OpenSeadragon.Viewer( options );
747}
748
749(function( $ ){
750
751
752    /**
753     * The OpenSeadragon version.
754     *
755     * @member {Object} OpenSeadragon.version
756     * @property {String} versionStr - The version number as a string ('major.minor.revision').
757     * @property {Number} major - The major version number.
758     * @property {Number} minor - The minor version number.
759     * @property {Number} revision - The revision number.
760     * @since 1.0.0
761     */
762    $.version = {
763        versionStr: '2.4.2',
764        major: parseInt('2', 10),
765        minor: parseInt('4', 10),
766        revision: parseInt('2', 10)
767    };
768
769
770    /**
771     * Taken from jquery 1.6.1
772     * [[Class]] -> type pairs
773     * @private
774     */
775    var class2type = {
776            '[object Boolean]':     'boolean',
777            '[object Number]':      'number',
778            '[object String]':      'string',
779            '[object Function]':    'function',
780            '[object Array]':       'array',
781            '[object Date]':        'date',
782            '[object RegExp]':      'regexp',
783            '[object Object]':      'object'
784        },
785        // Save a reference to some core methods
786        toString    = Object.prototype.toString,
787        hasOwn      = Object.prototype.hasOwnProperty;
788
789    /**
790     * Taken from jQuery 1.6.1
791     * @function isFunction
792     * @memberof OpenSeadragon
793     * @see {@link http://www.jquery.com/ jQuery}
794     */
795    $.isFunction = function( obj ) {
796        return $.type(obj) === "function";
797    };
798
799
800    /**
801     * Taken from jQuery 1.6.1
802     * @function isArray
803     * @memberof OpenSeadragon
804     * @see {@link http://www.jquery.com/ jQuery}
805     */
806    $.isArray = Array.isArray || function( obj ) {
807        return $.type(obj) === "array";
808    };
809
810
811    /**
812     * A crude way of determining if an object is a window.
813     * Taken from jQuery 1.6.1
814     * @function isWindow
815     * @memberof OpenSeadragon
816     * @see {@link http://www.jquery.com/ jQuery}
817     */
818    $.isWindow = function( obj ) {
819        return obj && typeof obj === "object" && "setInterval" in obj;
820    };
821
822
823    /**
824     * Taken from jQuery 1.6.1
825     * @function type
826     * @memberof OpenSeadragon
827     * @see {@link http://www.jquery.com/ jQuery}
828     */
829    $.type = function( obj ) {
830        return ( obj === null ) || ( obj === undefined ) ?
831            String( obj ) :
832            class2type[ toString.call(obj) ] || "object";
833    };
834
835
836    /**
837     * Taken from jQuery 1.6.1
838     * @function isPlainObject
839     * @memberof OpenSeadragon
840     * @see {@link http://www.jquery.com/ jQuery}
841     */
842    $.isPlainObject = function( obj ) {
843        // Must be an Object.
844        // Because of IE, we also have to check the presence of the constructor property.
845        // Make sure that DOM nodes and window objects don't pass through, as well
846        if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) {
847            return false;
848        }
849
850        // Not own constructor property must be Object
851        if ( obj.constructor &&
852            !hasOwn.call(obj, "constructor") &&
853            !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
854            return false;
855        }
856
857        // Own properties are enumerated firstly, so to speed up,
858        // if last one is own, then all properties are own.
859
860        var lastKey;
861        for (var key in obj ) {
862            lastKey = key;
863        }
864
865        return lastKey === undefined || hasOwn.call( obj, lastKey );
866    };
867
868
869    /**
870     * Taken from jQuery 1.6.1
871     * @function isEmptyObject
872     * @memberof OpenSeadragon
873     * @see {@link http://www.jquery.com/ jQuery}
874     */
875    $.isEmptyObject = function( obj ) {
876        for ( var name in obj ) {
877            return false;
878        }
879        return true;
880    };
881
882    /**
883     * Shim around Object.freeze. Does nothing if Object.freeze is not supported.
884     * @param {Object} obj The object to freeze.
885     * @return {Object} obj The frozen object.
886     */
887    $.freezeObject = function(obj) {
888        if (Object.freeze) {
889            $.freezeObject = Object.freeze;
890        } else {
891            $.freezeObject = function(obj) {
892                return obj;
893            };
894        }
895        return $.freezeObject(obj);
896    };
897
898    /**
899     * True if the browser supports the HTML5 canvas element
900     * @member {Boolean} supportsCanvas
901     * @memberof OpenSeadragon
902     */
903    $.supportsCanvas = (function () {
904        var canvasElement = document.createElement( 'canvas' );
905        return !!( $.isFunction( canvasElement.getContext ) &&
906                    canvasElement.getContext( '2d' ) );
907    }());
908
909    /**
910     * Test whether the submitted canvas is tainted or not.
911     * @argument {Canvas} canvas The canvas to test.
912     * @returns {Boolean} True if the canvas is tainted.
913     */
914    $.isCanvasTainted = function(canvas) {
915        var isTainted = false;
916        try {
917            // We test if the canvas is tainted by retrieving data from it.
918            // An exception will be raised if the canvas is tainted.
919            canvas.getContext('2d').getImageData(0, 0, 1, 1);
920        } catch (e) {
921            isTainted = true;
922        }
923        return isTainted;
924    };
925
926    /**
927     * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
928     * clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
929     * @member {Number} pixelDensityRatio
930     * @memberof OpenSeadragon
931     */
932    $.pixelDensityRatio = (function () {
933        if ( $.supportsCanvas ) {
934            var context = document.createElement('canvas').getContext('2d');
935            var devicePixelRatio = window.devicePixelRatio || 1;
936            var backingStoreRatio = context.webkitBackingStorePixelRatio ||
937                                    context.mozBackingStorePixelRatio ||
938                                    context.msBackingStorePixelRatio ||
939                                    context.oBackingStorePixelRatio ||
940                                    context.backingStorePixelRatio || 1;
941            return Math.max(devicePixelRatio, 1) / backingStoreRatio;
942        } else {
943            return 1;
944        }
945    }());
946
947}( OpenSeadragon ));
948
949/**
950 *  This closure defines all static methods available to the OpenSeadragon
951 *  namespace.  Many, if not most, are taked directly from jQuery for use
952 *  to simplify and reduce common programming patterns.  More static methods
953 *  from jQuery may eventually make their way into this though we are
954 *  attempting to avoid an explicit dependency on jQuery only because
955 *  OpenSeadragon is a broadly useful code base and would be made less broad
956 *  by requiring jQuery fully.
957 *
958 *  Some static methods have also been refactored from the original OpenSeadragon
959 *  project.
960 */
961(function( $ ){
962
963    /**
964     * Taken from jQuery 1.6.1
965     * @function extend
966     * @memberof OpenSeadragon
967     * @see {@link http://www.jquery.com/ jQuery}
968     */
969    $.extend = function() {
970        var options,
971            name,
972            src,
973            copy,
974            copyIsArray,
975            clone,
976            target  = arguments[ 0 ] || {},
977            length  = arguments.length,
978            deep    = false,
979            i       = 1;
980
981        // Handle a deep copy situation
982        if ( typeof target === "boolean" ) {
983            deep    = target;
984            target  = arguments[ 1 ] || {};
985            // skip the boolean and the target
986            i = 2;
987        }
988
989        // Handle case when target is a string or something (possible in deep copy)
990        if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) {
991            target = {};
992        }
993
994        // extend jQuery itself if only one argument is passed
995        if ( length === i ) {
996            target = this;
997            --i;
998        }
999
1000        for ( ; i < length; i++ ) {
1001            // Only deal with non-null/undefined values
1002            options = arguments[ i ];
1003            if ( options !== null || options !== undefined ) {
1004                // Extend the base object
1005                for ( name in options ) {
1006                    src = target[ name ];
1007                    copy = options[ name ];
1008
1009                    // Prevent never-ending loop
1010                    if ( target === copy ) {
1011                        continue;
1012                    }
1013
1014                    // Recurse if we're merging plain objects or arrays
1015                    if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) {
1016                        if ( copyIsArray ) {
1017                            copyIsArray = false;
vendor: 4,928 bytes, lines 1018-1147
1018                            clone = src && OpenSeadragon.isArray( src ) ? src : [];
1019
1020                        } else {
1021                            clone = src && OpenSeadragon.isPlainObject( src ) ? src : {};
1022                        }
1023
1024                        // Never move original objects, clone them
1025                        target[ name ] = OpenSeadragon.extend( deep, clone, copy );
1026
1027                    // Don't bring in undefined values
1028                    } else if ( copy !== undefined ) {
1029                        target[ name ] = copy;
1030                    }
1031                }
1032            }
1033        }
1034
1035        // Return the modified object
1036        return target;
1037    };
1038
1039    var isIOSDevice = function () {
1040        if (typeof navigator !== 'object') {
1041            return false;
1042        }
1043        var userAgent = navigator.userAgent;
1044        if (typeof userAgent !== 'string') {
1045            return false;
1046        }
1047        return userAgent.indexOf('iPhone') !== -1 ||
1048               userAgent.indexOf('iPad') !== -1 ||
1049               userAgent.indexOf('iPod') !== -1;
1050    };
1051
1052    $.extend( $, /** @lends OpenSeadragon */{
1053        /**
1054         * The default values for the optional settings documented at {@link OpenSeadragon.Options}.
1055         * @static
1056         * @type {Object}
1057         */
1058        DEFAULT_SETTINGS: {
1059            //DATA SOURCE DETAILS
1060            xmlPath:                null,
1061            tileSources:            null,
1062            tileHost:               null,
1063            initialPage:            0,
1064            crossOriginPolicy:      false,
1065            ajaxWithCredentials:    false,
1066            loadTilesWithAjax:      false,
1067            ajaxHeaders:            {},
1068
1069            //PAN AND ZOOM SETTINGS AND CONSTRAINTS
1070            panHorizontal:          true,
1071            panVertical:            true,
1072            constrainDuringPan:     false,
1073            wrapHorizontal:         false,
1074            wrapVertical:           false,
1075            visibilityRatio:        0.5, //-> how much of the viewer can be negative space
1076            minPixelRatio:          0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom
1077            defaultZoomLevel:       0,
1078            minZoomLevel:           null,
1079            maxZoomLevel:           null,
1080            homeFillsViewer:        false,
1081
1082            //UI RESPONSIVENESS AND FEEL
1083            clickTimeThreshold:     300,
1084            clickDistThreshold:     5,
1085            dblClickTimeThreshold:  300,
1086            dblClickDistThreshold:  20,
1087            springStiffness:        6.5,
1088            animationTime:          1.2,
1089            gestureSettingsMouse:   {
1090                scrollToZoom: true,
1091                clickToZoom: true,
1092                dblClickToZoom: false,
1093                pinchToZoom: false,
1094                zoomToRefPoint: true,
1095                flickEnabled: false,
1096                flickMinSpeed: 120,
1097                flickMomentum: 0.25,
1098                pinchRotate: false
1099            },
1100            gestureSettingsTouch:   {
1101                scrollToZoom: false,
1102                clickToZoom: false,
1103                dblClickToZoom: true,
1104                pinchToZoom: true,
1105                zoomToRefPoint: true,
1106                flickEnabled: true,
1107                flickMinSpeed: 120,
1108                flickMomentum: 0.25,
1109                pinchRotate: false
1110            },
1111            gestureSettingsPen:     {
1112                scrollToZoom: false,
1113                clickToZoom: true,
1114                dblClickToZoom: false,
1115                pinchToZoom: false,
1116                zoomToRefPoint: true,
1117                flickEnabled: false,
1118                flickMinSpeed: 120,
1119                flickMomentum: 0.25,
1120                pinchRotate: false
1121            },
1122            gestureSettingsUnknown: {
1123                scrollToZoom: false,
1124                clickToZoom: false,
1125                dblClickToZoom: true,
1126                pinchToZoom: true,
1127                zoomToRefPoint: true,
1128                flickEnabled: true,
1129                flickMinSpeed: 120,
1130                flickMomentum: 0.25,
1131                pinchRotate: false
1132            },
1133            zoomPerClick:           2,
1134            zoomPerScroll:          1.2,
1135            zoomPerSecond:          1.0,
1136            blendTime:              0,
1137            alwaysBlend:            false,
1138            autoHideControls:       true,
1139            immediateRender:        false,
1140            minZoomImageRatio:      0.9, //-> closer to 0 allows zoom out to infinity
1141            maxZoomPixelRatio:      1.1, //-> higher allows 'over zoom' into pixels
1142            smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
1143            iOSDevice:              isIOSDevice(),
1144            pixelsPerWheelLine:     40,
1145            pixelsPerArrowPress:    40,
1146            autoResize:             true,
1147            preserveImageSizeOnResize: false, // requires autoResize=true
1148            minScrollDeltaTime:     50,
1149            rotationIncrement:      90,
1150
1151            //DEFAULT CONTROL SETTINGS
1152            showSequenceControl:     true,  //SEQUENCE
1153            sequenceControlAnchor:   null,  //SEQUENCE
1154            preserveViewport:        false, //SEQUENCE
1155            preserveOverlays:        false, //SEQUENCE
1156            navPrevNextWrap:         false, //SEQUENCE
1157            showNavigationControl:   true,  //ZOOM/HOME/FULL/ROTATION
1158            navigationControlAnchor: null,  //ZOOM/HOME/FULL/ROTATION
1159            showZoomControl:         true,  //ZOOM
1160            showHomeControl:         true,  //HOME
1161            showFullPageControl:     true,  //FULL
1162            showRotationControl:     false, //ROTATION
1163            showFlipControl:         false,  //FLIP
1164            controlsFadeDelay:       2000,  //ZOOM/HOME/FULL/SEQUENCE
1165            controlsFadeLength:      1500,  //ZOOM/HOME/FULL/SEQUENCE
1166            mouseNavEnabled:         true,  //GENERAL MOUSE INTERACTIVITY
1167
1168            //VIEWPORT NAVIGATOR SETTINGS
1169            showNavigator:              false,
1170            navigatorId:                null,
1171            navigatorPosition:          null,
1172            navigatorSizeRatio:         0.2,
1173            navigatorMaintainSizeRatio: false,
1174            navigatorTop:               null,
1175            navigatorLeft:              null,
1176            navigatorHeight:            null,
1177            navigatorWidth:             null,
1178            navigatorAutoResize:        true,
1179            navigatorAutoFade:          true,
1180            navigatorRotate:            true,
1181            navigatorBackground:        '#000',
1182            navigatorOpacity:           0.8,
1183            navigatorBorderColor:       '#555',
1184            navigatorDisplayRegionColor: '#900',
1185
1186            // INITIAL ROTATION
1187            degrees:                    0,
1188
1189            // INITIAL FLIP STATE
1190            flipped:                    false,
1191
1192            // APPEARANCE
1193            opacity:                    1,
1194            preload:                    false,
1195            compositeOperation:         null,
1196            imageSmoothingEnabled:      true,
1197            placeholderFillStyle:       null,
1198
1199            //REFERENCE STRIP SETTINGS
1200            showReferenceStrip:          false,
1201            referenceStripScroll:       'horizontal',
1202            referenceStripElement:       null,
1203            referenceStripHeight:        null,
1204            referenceStripWidth:         null,
1205            referenceStripPosition:      'BOTTOM_LEFT',
1206            referenceStripSizeRatio:     0.2,
1207
1208            //COLLECTION VISUALIZATION SETTINGS
1209            collectionRows:         3, //or columns depending on layout
1210            collectionColumns:      0, //columns in horizontal layout, rows in vertical layout
1211            collectionLayout:       'horizontal', //vertical
1212            collectionMode:         false,
1213            collectionTileSize:     800,
1214            collectionTileMargin:   80,
1215
1216            //PERFORMANCE SETTINGS
1217            imageLoaderLimit:       0,
1218            maxImageCacheCount:     200,
1219            timeout:                30000,
1220            useCanvas:              true,  // Use canvas element for drawing if available
1221
1222            //INTERFACE RESOURCE SETTINGS
1223            prefixUrl:              "/images/",
1224            navImages: {
1225                zoomIn: {
1226                    REST:   'zoomin_rest.png',
1227                    GROUP:  'zoomin_grouphover.png',
1228                    HOVER:  'zoomin_hover.png',
1229                    DOWN:   'zoomin_pressed.png'
1230                },
1231                zoomOut: {
1232                    REST:   'zoomout_rest.png',
1233                    GROUP:  'zoomout_grouphover.png',
1234                    HOVER:  'zoomout_hover.png',
1235                    DOWN:   'zoomout_pressed.png'
1236                },
1237                home: {
1238                    REST:   'home_rest.png',
1239                    GROUP:  'home_grouphover.png',
1240                    HOVER:  'home_hover.png',
1241                    DOWN:   'home_pressed.png'
1242                },
1243                fullpage: {
1244                    REST:   'fullpage_rest.png',
1245                    GROUP:  'fullpage_grouphover.png',
1246                    HOVER:  'fullpage_hover.png',
1247                    DOWN:   'fullpage_pressed.png'
1248                },
1249                rotateleft: {
1250                    REST:   'rotateleft_rest.png',
1251                    GROUP:  'rotateleft_grouphover.png',
1252                    HOVER:  'rotateleft_hover.png',
1253                    DOWN:   'rotateleft_pressed.png'
1254                },
1255                rotateright: {
1256                    REST:   'rotateright_rest.png',
1257                    GROUP:  'rotateright_grouphover.png',
1258                    HOVER:  'rotateright_hover.png',
1259                    DOWN:   'rotateright_pressed.png'
1260                },
1261                flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos [email protected], https://thenounproject.com/term/flip/136289/
1262                    REST:   'flip_rest.png',
1263                    GROUP:  'flip_grouphover.png',
1264                    HOVER:  'flip_hover.png',
1265                    DOWN:   'flip_pressed.png'
1266                },
1267                previous: {
1268                    REST:   'previous_rest.png',
1269                    GROUP:  'previous_grouphover.png',
1270                    HOVER:  'previous_hover.png',
1271                    DOWN:   'previous_pressed.png'
1272                },
1273                next: {
1274                    REST:   'next_rest.png',
1275                    GROUP:  'next_grouphover.png',
1276                    HOVER:  'next_hover.png',
1277                    DOWN:   'next_pressed.png'
1278                }
1279            },
1280
1281            //DEVELOPER SETTINGS
1282            debugMode:              false,
1283            debugGridColor:         ['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']
1284        },
1285
1286
1287        /**
1288         * TODO: get rid of this.  I can't see how it's required at all.  Looks
1289         *       like an early legacy code artifact.
1290         * @static
1291         * @ignore
1292         */
1293        SIGNAL: "----seadragon----",
1294
1295
1296        /**
1297         * Returns a function which invokes the method as if it were a method belonging to the object.
1298         * @function
1299         * @param {Object} object
1300         * @param {Function} method
1301         * @returns {Function}
1302         */
1303        delegate: function( object, method ) {
1304            return function(){
1305                var args = arguments;
1306                if ( args === undefined ){
1307                    args = [];
1308                }
1309                return method.apply( object, args );
1310            };
1311        },
1312
1313
1314        /**
1315         * An enumeration of Browser vendors.
1316         * @static
1317         * @type {Object}
1318         * @property {Number} UNKNOWN
1319         * @property {Number} IE
1320         * @property {Number}
vendor: 15,928 bytes, lines 1320-1775
1320 FIREFOX
1321         * @property {Number} SAFARI
1322         * @property {Number} CHROME
1323         * @property {Number} OPERA
1324         */
1325        BROWSERS: {
1326            UNKNOWN:    0,
1327            IE:         1,
1328            FIREFOX:    2,
1329            SAFARI:     3,
1330            CHROME:     4,
1331            OPERA:      5
1332        },
1333
1334
1335        /**
1336         * Returns a DOM Element for the given id or element.
1337         * @function
1338         * @param {String|Element} element Accepts an id or element.
1339         * @returns {Element} The element with the given id, null, or the element itself.
1340         */
1341        getElement: function( element ) {
1342            if ( typeof ( element ) == "string" ) {
1343                element = document.getElementById( element );
1344            }
1345            return element;
1346        },
1347
1348
1349        /**
1350         * Determines the position of the upper-left corner of the element.
1351         * @function
1352         * @param {Element|String} element - the element we want the position for.
1353         * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element.
1354         */
1355        getElementPosition: function( element ) {
1356            var result = new $.Point(),
1357                isFixed,
1358                offsetParent;
1359
1360            element      = $.getElement( element );
1361            isFixed      = $.getElementStyle( element ).position == "fixed";
1362            offsetParent = getOffsetParent( element, isFixed );
1363
1364            while ( offsetParent ) {
1365
1366                result.x += element.offsetLeft;
1367                result.y += element.offsetTop;
1368
1369                if ( isFixed ) {
1370                    result = result.plus( $.getPageScroll() );
1371                }
1372
1373                element = offsetParent;
1374                isFixed = $.getElementStyle( element ).position == "fixed";
1375                offsetParent = getOffsetParent( element, isFixed );
1376            }
1377
1378            return result;
1379        },
1380
1381
1382        /**
1383         * Determines the position of the upper-left corner of the element adjusted for current page and/or element scroll.
1384         * @function
1385         * @param {Element|String} element - the element we want the position for.
1386         * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll.
1387         */
1388        getElementOffset: function( element ) {
1389            element = $.getElement( element );
1390
1391            var doc = element && element.ownerDocument,
1392                docElement,
1393                win,
1394                boundingRect = { top: 0, left: 0 };
1395
1396            if ( !doc ) {
1397                return new $.Point();
1398            }
1399
1400            docElement = doc.documentElement;
1401
1402            if ( typeof element.getBoundingClientRect !== typeof undefined ) {
1403                boundingRect = element.getBoundingClientRect();
1404            }
1405
1406            win = ( doc == doc.window ) ?
1407                doc :
1408                ( doc.nodeType === 9 ) ?
1409                    doc.defaultView || doc.parentWindow :
1410                    false;
1411
1412            return new $.Point(
1413                boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ),
1414                boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 )
1415            );
1416        },
1417
1418
1419        /**
1420         * Determines the height and width of the given element.
1421         * @function
1422         * @param {Element|String} element
1423         * @returns {OpenSeadragon.Point}
1424         */
1425        getElementSize: function( element ) {
1426            element = $.getElement( element );
1427
1428            return new $.Point(
1429                element.clientWidth,
1430                element.clientHeight
1431            );
1432        },
1433
1434
1435        /**
1436         * Returns the CSSStyle object for the given element.
1437         * @function
1438         * @param {Element|String} element
1439         * @returns {CSSStyle}
1440         */
1441        getElementStyle:
1442            document.documentElement.currentStyle ?
1443            function( element ) {
1444                element = $.getElement( element );
1445                return element.currentStyle;
1446            } :
1447            function( element ) {
1448                element = $.getElement( element );
1449                return window.getComputedStyle( element, "" );
1450            },
1451
1452        /**
1453         * Returns the property with the correct vendor prefix appended.
1454         * @param {String} property the property name
1455         * @returns {String} the property with the correct prefix or null if not
1456         * supported.
1457         */
1458        getCssPropertyWithVendorPrefix: function(property) {
1459            var memo = {};
1460
1461            $.getCssPropertyWithVendorPrefix = function(property) {
1462                if (memo[property] !== undefined) {
1463                    return memo[property];
1464                }
1465                var style = document.createElement('div').style;
1466                var result = null;
1467                if (style[property] !== undefined) {
1468                    result = property;
1469                } else {
1470                    var prefixes = ['Webkit', 'Moz', 'MS', 'O',
1471                        'webkit', 'moz', 'ms', 'o'];
1472                    var suffix = $.capitalizeFirstLetter(property);
1473                    for (var i = 0; i < prefixes.length; i++) {
1474                        var prop = prefixes[i] + suffix;
1475                        if (style[prop] !== undefined) {
1476                            result = prop;
1477                            break;
1478                        }
1479                    }
1480                }
1481                memo[property] = result;
1482                return result;
1483            };
1484            return $.getCssPropertyWithVendorPrefix(property);
1485        },
1486
1487        /**
1488         * Capitalizes the first letter of a string
1489         * @param {String} string
1490         * @returns {String} The string with the first letter capitalized
1491         */
1492        capitalizeFirstLetter: function(string) {
1493            return string.charAt(0).toUpperCase() + string.slice(1);
1494        },
1495
1496        /**
1497         * Compute the modulo of a number but makes sure to always return
1498         * a positive value.
1499         * @param {Number} number the number to computes the modulo of
1500         * @param {Number} modulo the modulo
1501         * @returns {Number} the result of the modulo of number
1502         */
1503        positiveModulo: function(number, modulo) {
1504            var result = number % modulo;
1505            if (result < 0) {
1506                result += modulo;
1507            }
1508            return result;
1509        },
1510
1511        /**
1512         * Determines if a point is within the bounding rectangle of the given element (hit-test).
1513         * @function
1514         * @param {Element|String} element
1515         * @param {OpenSeadragon.Point} point
1516         * @returns {Boolean}
1517         */
1518        pointInElement: function( element, point ) {
1519            element = $.getElement( element );
1520            var offset = $.getElementOffset( element ),
1521                size = $.getElementSize( element );
1522            return point.x >= offset.x && point.x < offset.x + size.x && point.y < offset.y + size.y && point.y >= offset.y;
1523        },
1524
1525
1526        /**
1527         * Gets the latest event, really only useful internally since its
1528         * specific to IE behavior.
1529         * @function
1530         * @param {Event} [event]
1531         * @returns {Event}
1532         * @deprecated For internal use only
1533         * @private
1534         */
1535        getEvent: function( event ) {
1536            if( event ){
1537                $.getEvent = function( event ) {
1538                    return event;
1539                };
1540            } else {
1541                $.getEvent = function() {
1542                    return window.event;
1543                };
1544            }
1545            return $.getEvent( event );
1546        },
1547
1548
1549        /**
1550         * Gets the position of the mouse on the screen for a given event.
1551         * @function
1552         * @param {Event} [event]
1553         * @returns {OpenSeadragon.Point}
1554         */
1555        getMousePosition: function( event ) {
1556
1557            if ( typeof ( event.pageX ) == "number" ) {
1558                $.getMousePosition = function( event ){
1559                    var result = new $.Point();
1560
1561                    event = $.getEvent( event );
1562                    result.x = event.pageX;
1563                    result.y = event.pageY;
1564
1565                    return result;
1566                };
1567            } else if ( typeof ( event.clientX ) == "number" ) {
1568                $.getMousePosition = function( event ){
1569                    var result = new $.Point();
1570
1571                    event = $.getEvent( event );
1572                    result.x =
1573                        event.clientX +
1574                        document.body.scrollLeft +
1575                        document.documentElement.scrollLeft;
1576                    result.y =
1577                        event.clientY +
1578                        document.body.scrollTop +
1579                        document.documentElement.scrollTop;
1580
1581                    return result;
1582                };
1583            } else {
1584                throw new Error(
1585                    "Unknown event mouse position, no known technique."
1586                );
1587            }
1588
1589            return $.getMousePosition( event );
1590        },
1591
1592
1593        /**
1594         * Determines the page's current scroll position.
1595         * @function
1596         * @returns {OpenSeadragon.Point}
1597         */
1598        getPageScroll: function() {
1599            var docElement  = document.documentElement || {},
1600                body        = document.body || {};
1601
1602            if ( typeof ( window.pageXOffset ) == "number" ) {
1603                $.getPageScroll = function(){
1604                    return new $.Point(
1605                        window.pageXOffset,
1606                        window.pageYOffset
1607                    );
1608                };
1609            } else if ( body.scrollLeft || body.scrollTop ) {
1610                $.getPageScroll = function(){
1611                    return new $.Point(
1612                        document.body.scrollLeft,
1613                        document.body.scrollTop
1614                    );
1615                };
1616            } else if ( docElement.scrollLeft || docElement.scrollTop ) {
1617                $.getPageScroll = function(){
1618                    return new $.Point(
1619                        document.documentElement.scrollLeft,
1620                        document.documentElement.scrollTop
1621                    );
1622                };
1623            } else {
1624                // We can't reassign the function yet, as there was no scroll.
1625                return new $.Point(0, 0);
1626            }
1627
1628            return $.getPageScroll();
1629        },
1630
1631        /**
1632         * Set the page scroll position.
1633         * @function
1634         * @returns {OpenSeadragon.Point}
1635         */
1636        setPageScroll: function( scroll ) {
1637            if ( typeof ( window.scrollTo ) !== "undefined" ) {
1638                $.setPageScroll = function( scroll ) {
1639                    window.scrollTo( scroll.x, scroll.y );
1640                };
1641            } else {
1642                var originalScroll = $.getPageScroll();
1643                if ( originalScroll.x === scroll.x &&
1644                    originalScroll.y === scroll.y ) {
1645                    // We are already correctly positioned and there
1646                    // is no way to detect the correct method.
1647                    return;
1648                }
1649
1650                document.body.scrollLeft = scroll.x;
1651                document.body.scrollTop = scroll.y;
1652                var currentScroll = $.getPageScroll();
1653                if ( currentScroll.x !== originalScroll.x &&
1654                    currentScroll.y !== originalScroll.y ) {
1655                    $.setPageScroll = function( scroll ) {
1656                        document.body.scrollLeft = scroll.x;
1657                        document.body.scrollTop = scroll.y;
1658                    };
1659                    return;
1660                }
1661
1662                document.documentElement.scrollLeft = scroll.x;
1663                document.documentElement.scrollTop = scroll.y;
1664                currentScroll = $.getPageScroll();
1665                if ( currentScroll.x !== originalScroll.x &&
1666                    currentScroll.y !== originalScroll.y ) {
1667                    $.setPageScroll = function( scroll ) {
1668                        document.documentElement.scrollLeft = scroll.x;
1669                        document.documentElement.scrollTop = scroll.y;
1670                    };
1671                    return;
1672                }
1673
1674                // We can't find anything working, so we do nothing.
1675                $.setPageScroll = function( scroll ) {
1676                };
1677            }
1678
1679            return $.setPageScroll( scroll );
1680        },
1681
1682        /**
1683         * Determines the size of the browsers window.
1684         * @function
1685         * @returns {OpenSeadragon.Point}
1686         */
1687        getWindowSize: function() {
1688            var docElement = document.documentElement || {},
1689                body    = document.body || {};
1690
1691            if ( typeof ( window.innerWidth ) == 'number' ) {
1692                $.getWindowSize = function(){
1693                    return new $.Point(
1694                        window.innerWidth,
1695                        window.innerHeight
1696                    );
1697                };
1698            } else if ( docElement.clientWidth || docElement.clientHeight ) {
1699                $.getWindowSize = function(){
1700                    return new $.Point(
1701                        document.documentElement.clientWidth,
1702                        document.documentElement.clientHeight
1703                    );
1704                };
1705            } else if ( body.clientWidth || body.clientHeight ) {
1706                $.getWindowSize = function(){
1707                    return new $.Point(
1708                        document.body.clientWidth,
1709                        document.body.clientHeight
1710                    );
1711                };
1712            } else {
1713                throw new Error("Unknown window size, no known technique.");
1714            }
1715
1716            return $.getWindowSize();
1717        },
1718
1719
1720        /**
1721         * Wraps the given element in a nest of divs so that the element can
1722         * be easily centered using CSS tables
1723         * @function
1724         * @param {Element|String} element
1725         * @returns {Element} outermost wrapper element
1726         */
1727        makeCenteredNode: function( element ) {
1728            // Convert a possible ID to an actual HTMLElement
1729            element = $.getElement( element );
1730
1731            /*
1732                CSS tables require you to have a display:table/row/cell hierarchy so we need to create
1733                three nested wrapper divs:
1734             */
1735
1736            var wrappers = [
1737                $.makeNeutralElement( 'div' ),
1738                $.makeNeutralElement( 'div' ),
1739                $.makeNeutralElement( 'div' )
1740            ];
1741
1742            // It feels like we should be able to pass style dicts to makeNeutralElement:
1743            $.extend(wrappers[0].style, {
1744                display: "table",
1745                height: "100%",
1746                width: "100%"
1747            });
1748
1749            $.extend(wrappers[1].style, {
1750                display: "table-row"
1751            });
1752
1753            $.extend(wrappers[2].style, {
1754                display: "table-cell",
1755                verticalAlign: "middle",
1756                textAlign: "center"
1757            });
1758
1759            wrappers[0].appendChild(wrappers[1]);
1760            wrappers[1].appendChild(wrappers[2]);
1761            wrappers[2].appendChild(element);
1762
1763            return wrappers[0];
1764        },
1765
1766
1767        /**
1768         * Creates an easily positionable element of the given type that therefor
1769         * serves as an excellent container element.
1770         * @function
1771         * @param {String} tagName
1772         * @returns {Element}
1773         */
1774        makeNeutralElement: function( tagName ) {
1775            var element = document.createElement( tagName ),
1776                style   = element.style;
1777
1778            style.background = "transparent none";
1779            style.border     = "none";
1780            style.margin     = "0px";
1781            style.padding    = "0px";
1782            style.position   = "static";
1783
1784            return element;
1785        },
1786
1787
1788        /**
1789         * Returns the current milliseconds, using Date.now() if available
1790         * @function
1791         */
1792        now: function( ) {
1793            if (Date.now) {
1794                $.now = Date.now;
1795            } else {
1796                $.now = function() {
1797                    return new Date().getTime();
1798                };
1799            }
1800
1801            return $.now();
1802        },
1803
1804
1805        /**
1806         * Ensures an image is loaded correctly to support alpha transparency.
1807         * Generally only IE has issues doing this correctly for formats like
1808         * png.
1809         * @function
1810         * @param {String} src
1811         * @returns {Element}
1812         */
1813        makeTransparentImage: function( src ) {
1814
1815            $.makeTransparentImage = function( src ){
1816                var img = $.makeNeutralElement( "img" );
1817
1818                img.src = src;
1819
1820                return img;
1821            };
1822
1823            if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) {
1824
1825                $.makeTransparentImage = function( src ){
1826                    var img     = $.makeNeutralElement( "img" ),
1827                        element = null;
1828
1829                    element = $.makeNeutralElement("span");
1830                    element.style.display = "inline-block";
1831
1832                    img.onload = function() {
1833                        element.style.width  = element.style.width || img.width + "px";
1834                        element.style.height = element.style.height || img.height + "px";
1835
1836                        img.onload = null;
1837                        img = null;     // to prevent memory leaks in IE
1838                    };
1839
1840                    img.src = src;
1841                    element.style.filter =
1842                        "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
1843                        src +
1844                        "', sizingMethod='scale')";
1845
1846                    return element;
1847                };
1848
1849            }
1850
1851            return $.makeTransparentImage( src );
1852        },
1853
1854
1855        /**
1856         * Sets the opacity of the specified element.
1857         * @function
1858         * @param {Element|String} element
1859         * @param {Number} opacity
1860         * @param {Boolean} [usesAlpha]
1861         */
1862        setElementOpacity: function( element, opacity, usesAlpha ) {
1863
1864            var ieOpacity,
1865                ieFilter;
1866
1867            element = $.getElement( element );
1868
1869            if ( usesAlpha && !$.Browser.alpha ) {
1870                opacity = Math.round( opacity );
1871            }
1872
1873            if ( $.Browser.opacity ) {
1874                element.style.opacity = opacity < 1 ? opacity : "";
1875            } else {
1876                if ( opacity < 1 ) {
1877                    ieOpacity = Math.round( 100 * opacity );
1878                    ieFilter  = "alpha(opacity=" + ieOpacity + ")";
1879                    element.style.filter = ieFilter;
1880                } else {
1881                    element.style.filter = "";
1882                }
1883            }
1884        },
1885
1886
1887        /**
1888         * Sets the specified element's touch-action style attribute to 'none'.
1889         * @function
1890         * @param {Element|String} element
1891         */
1892        setElementTouchActionNone: function( element ) {
1893            element = $.getElement( element );
1894            if ( typeof element.style.touchAction !== 'undefined' ) {
1895                element.style.touchAction = 'none';
1896            } else if ( typeof element.style.msTouchAction !== 'undefined' ) {
1897                element.style.msTouchAction = 'none';
1898            }
1899        },
1900
1901
1902        /**
1903         * Add the specified CSS class to the element if not present.
1904         * @function
1905         * @param {Element|String} element
1906         * @param {String} className
1907         */
1908        addClass: function( element, className ) {
1909            element = $.getElement( element );
1910
1911            if (!element.className) {
1912                element.className = className;
1913            } else if ( ( ' ' + element.className + ' ' ).
1914                indexOf( ' ' + className + ' ' ) === -1 ) {
1915                element.className += ' ' + className;
1916            }
1917        },
1918
1919        /**
1920         * Find the first index at which an element is found in an array or -1
1921         * if not present.
1922         *
1923         * Code taken and adapted from
1924         * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility
1925         *
1926         * @function
1927         * @param {Array} array The array from which to find the element
1928         * @param {Object} searchElement The element to find
1929         * @param {Number} [fromIndex=0] Index to start research.
1930         * @returns {Number} The index of the element in the array.
1931         */
1932        indexOf: function( array, searchElement, fromIndex ) {
1933            if ( Array.prototype.indexOf ) {
1934                this.indexOf = function( array, searchElement, fromIndex ) {
1935                    return array.indexOf( searchElement, fromIndex );
1936                };
1937            } else {
1938                this.indexOf = function( array, searchElement, fromIndex ) {
1939                    var i,
1940                        pivot = ( fromIndex ) ? fromIndex : 0,
1941                        length;
1942                    if ( !array ) {
1943                        throw new TypeError( );
1944                    }
1945
1946                    length = array.length;
1947                    if ( length === 0 || pivot >= length ) {
1948                        return -1;
1949                    }
1950
1951                    if ( pivot < 0 ) {
1952                        pivot = length - Math.abs( pivot );
1953                    }
1954
1955                    for ( i = pivot; i < length; i++ ) {
1956                        if ( array[i] === searchElement ) {
1957                            return i;
1958                        }
1959                    }
1960                    return -1;
1961                };
1962            }
1963            return this.indexOf( array, searchElement, fromIndex );
1964        },
1965
1966        /**
1967         * Remove the specified CSS class from the element.
1968         * @function
1969         * @param {Element|String} element
1970         * @param {String} className
1971         */
1972        removeClass: function( element, className ) {
1973            var oldClasses,
1974                newClasses = [],
1975                i;
1976
1977            element = $.getElement( element );
1978            oldClasses = element.className.split( /\s+/ );
1979            for ( i = 0; i < oldClasses.length; i++ ) {
1980                if ( oldClasses[ i ] && oldClasses[ i ] !== className ) {
1981                    newClasses.push( oldClasses[ i ] );
1982                }
1983            }
1984            element.className = newClasses.join(' ');
1985        },
1986
1987
1988        /**
1989         * Adds an event listener for the given element, eventName and handler.
1990         * @function
1991         * @param {Element|String} element
1992         * @param {String} eventName
1993         * @param {Function} handler
1994         * @param {Boolean} [useCapture]
1995         */
1996        addEvent: (function () {
1997            if ( window.addEventListener ) {
1998                return function ( element, eventName, handler, useCapture ) {
1999                    element = $.getElement( element );
2000                    element.addEventListener( eventName, handler, useCapture );
2001                };
2002            } else if ( window.attachEvent ) {
2003                return function ( element, eventName, handler, useCapture ) {
2004                    element = $.getElement( element );
2005                    element.attachEvent( 'on' + eventName, handler );
2006                };
2007            } else {
2008                throw new Error( "No known event model." );
2009            }
2010        }()),
2011
2012
2013        /**
2014         * Remove a given event listener for the given element, event type and
2015         * handler.
2016         * @function
2017         * @param {Element|String} element
2018         * @param {String} eventName
2019         * @param {Function} handler
2020         * @param {Boolean} [useCapture]
2021         */
2022        removeEvent: (function () {
2023            if ( window.removeEventListener ) {
2024                return function ( element, eventName, handler, useCapture ) {
2025                    element = $.getElement( element );
2026                    element.removeEventListener( eventName, handler, useCapture );
2027                };
2028            } else if ( window.detachEvent ) {
2029                return function( element, eventName, handler, useCapture ) {
2030                    element = $.getElement( element );
2031                    element.detachEvent( 'on' + eventName, handler );
2032                };
2033            } else {
2034                throw new Error( "No known event model." );
2035            }
2036        }()),
2037
2038
2039        /**
2040         * Cancels the default browser behavior had the event propagated all
2041         * the way up the DOM to the window object.
2042         * @function
2043         * @param {Event} [event]
2044         */
2045        cancelEvent: function( event ) {
2046            event = $.getEvent( event );
2047
2048            if ( event.preventDefault ) {
2049                $.cancelEvent = function( event ){
2050                    // W3C for preventing default
2051                    event.preventDefault();
2052                };
2053            } else {
2054                $.cancelEvent = function( event ){
2055                    event = $.getEvent( event );
2056                    // legacy for preventing default
2057                    event.cancel = true;
2058                    // IE for preventing default
2059                    event.returnValue = false;
2060                };
2061            }
2062            $.cancelEvent( event );
2063        },
2064
2065
2066        /**
2067         * Stops the propagation of the event up the DOM.
2068         * @function
2069         * @param {Event} [event]
2070         */
2071        stopEvent: function( event ) {
2072            event = $.getEvent( event );
2073
2074            if ( event.stopPropagation ) {
2075                // W3C for stopping propagation
2076                $.stopEvent = function( event ){
2077                    event.stopPropagation();
2078                };
2079            } else {
2080                // IE for stopping propagation
2081                $.stopEvent = function( event ){
2082                    event = $.getEvent( event );
2083                    event.cancelBubble = true;
2084                };
2085
2086            }
2087
2088            $.stopEvent( event );
2089        },
2090
2091
2092        /**
2093         * Similar to OpenSeadragon.delegate, but it does not immediately call
2094         * the method on the object, returning a function which can be called
2095         * repeatedly to delegate the method. It also allows additional arguments
2096         * to be passed during construction which will be added during each
2097         * invocation, and each invocation can add additional arguments as well.
2098         *
2099         * @function
2100         * @param {Object} object
2101         * @param {Function} method
2102         * @param [args] any additional arguments are passed as arguments to the
2103         *  created callback
2104         * @returns {Function}
2105         */
2106        createCallback: function( object, method ) {
2107            //TODO: This pattern is painful to use and debug.  It's much cleaner
2108            //      to use pinning plus anonymous functions.  Get rid of this
2109            //      pattern!
2110            var initialArgs = [],
2111                i;
2112            for ( i = 2; i < arguments.length; i++ ) {
2113                initialArgs.push( arguments[ i ] );
2114            }
2115
2116            return function() {
2117                var args = initialArgs.concat( [] ),
2118                    i;
2119                for ( i = 0; i < arguments.length; i++ ) {
2120                    args.push( arguments[ i ] );
2121                }
2122
2123                return method.apply( object, args );
2124            };
2125        },
2126
2127
2128        /**
2129         * Retrieves the value of a url parameter from the window.location string.
2130         * @function
2131         * @param {String} key
2132         * @returns {String} The value of the url parameter or null if no param matches.
2133         */
2134        getUrlParameter: function( key ) {
2135            // eslint-disable-next-line no-use-before-define
2136            var value = URLPARAMS[ key ];
2137            return value ? value : null;
2138        },
2139
2140        /**
2141         * Retrieves the protocol used by the url. The url can either be absolute
2142         * or relative.
2143         * @function
2144         * @private
2145         * @param {String} url The url to retrieve the protocol from.
2146         * @return {String} The protocol (http:, https:, file:, ftp: ...)
2147         */
2148        getUrlProtocol: function( url ) {
2149            var match = url.match(/^([a-z]+:)\/\//i);
2150            if ( match === null ) {
2151                // Relative URL, retrive the protocol from window.location
2152                return window.location.protocol;
2153            }
2154            return match[1].toLowerCase();
2155        },
2156
2157        /**
2158         * Create an XHR object
2159         * @private
2160         * @param {type} [local] If set to true, the XHR will be file: protocol
2161         * compatible if possible (but may raise a warning in the browser).
2162         * @returns {XMLHttpRequest}
2163         */
2164        createAjaxRequest: function( local ) {
2165            // IE11 does not support window.ActiveXObject so we just try to
2166            // create one to see if it is supported.
2167            // See: http://msdn.microsoft.com/en-us/library/ie/dn423948%28v=vs.85%29.aspx
2168            var supportActiveX;
2169            try {
2170                /* global ActiveXObject:true */
2171                supportActiveX = !!new ActiveXObject( "Microsoft.XMLHTTP" );
2172            } catch( e ) {
2173                supportActiveX = false;
2174            }
2175
2176            if ( supportActiveX ) {
2177                if ( window.XMLHttpRequest ) {
2178                    $.createAjaxRequest = function( local ) {
2179                        if ( local ) {
2180                            return new ActiveXObject( "Microsoft.XMLHTTP" );
2181                        }
2182                        return new XMLHttpRequest();
2183                    };
2184                } else {
2185                    $.createAjaxRequest = function() {
2186                        return new ActiveXObject( "Microsoft.XMLHTTP" );
2187                    };
2188                }
2189            } else if ( window.XMLHttpRequest ) {
2190                $.createAjaxRequest = function() {
2191                    return new XMLHttpRequest();
2192                };
2193            } else {
2194                throw new Error( "Browser doesn't support XMLHttpRequest." );
2195            }
2196            return $.createAjaxRequest( local );
2197        },
2198
2199        /**
2200         * Makes an AJAX request.
2201         * @param {Object} options
2202         * @param {String} options.url - the url to request
2203         * @param {Function} options.success - a function to call on a successful response
2204         * @param {Function} options.error - a function to call on when an error occurs
2205         * @param {Object} options.headers - headers to add to the AJAX request
2206         * @param {String} options.responseType - the response type of the the AJAX request
2207         * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
2208         * @throws {Error}
2209         * @returns {XMLHttpRequest}
2210         */
2211        makeAjaxRequest: function( url, onSuccess, onError ) {
2212            var withCredentials;
2213            var headers;
2214            var responseType;
2215
2216            // Note that our preferred API is that you pass in a single object; the named
2217            // arguments are for legacy support.
2218            if( $.isPlainObject( url ) ){
2219                onSuccess = url.success;
2220                onError = url.error;
2221                withCredentials = url.withCredentials;
2222                headers = url.headers;
2223                responseType = url.responseType || null;
2224                url = url.url;
2225            }
2226
2227            var protocol = $.getUrlProtocol( url );
2228            var request = $.createAjaxRequest( protocol === "file:" );
2229
2230            if ( !$.isFunction( onSuccess ) ) {
2231                throw new Error( "makeAjaxRequest requires a success callback" );
2232            }
2233
2234            request.onreadystatechange = function() {
2235                // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties)
2236                if ( request.readyState == 4 ) {
2237                    request.onreadystatechange = function(){};
2238
2239                    // With protocols other than http/https, a successful request status is in
2240                    // the 200's on Firefox and 0 on other browsers
2241                    if ( (request.status >= 200 && request.status < 300) ||
2242                        ( request.status === 0 &&
2243                          protocol !== "http:" &&
2244                          protocol !== "https:" )) {
2245                        onSuccess( request );
2246                    } else {
2247                        $.console.log( "AJAX request returned %d: %s", request.status, url );
2248
2249                        if ( $.isFunction( onError ) ) {
2250                            onError( request );
2251                        }
2252                    }
2253                }
2254            };
2255
2256            try {
2257                request.open( "GET", url, true );
2258
2259                if (responseType) {
2260                    request.responseType = responseType;
2261                }
2262
2263                if (headers) {
2264                    for (var headerName in headers) {
2265                        if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) {
2266                            request.setRequestHeader(headerName, headers[headerName]);
2267                        }
2268                    }
2269                }
2270
2271                if (withCredentials) {
2272                    request.withCredentials = true;
2273                }
2274
2275                request.send(null);
2276            } catch (e) {
2277                var msg = e.message;
2278
2279                /*
2280                    IE < 10 does not support CORS and an XHR request to a different origin will fail as soon
2281                    as send() is called. This is particularly easy to miss during development and appear in
2282                    production if you use a CDN or domain sharding and the security policy is likely to break
2283                    exception handlers since any attempt to access a property of the request object will
2284                    raise an access denied TypeError inside the catch block.
2285
2286                    To be friendlier, we'll check for this specific error and add a documentation pointer
2287                    to point developers in the right direction. We test the exception number because IE's
2288                    error messages are localized.
2289                */
2290                var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10;
2291                if ( oldIE && typeof ( e.number ) != "undefined" && e.number == -2147024891 ) {
2292                    msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain";
2293                }
2294
2295                $.console.log( "%s while making AJAX request: %s", e.name, msg );
2296
2297                request.onreadystatechange = function(){};
2298
2299                if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
2300                    var xdr = new window.XDomainRequest();
2301                    if (xdr) {
2302                        xdr.onload = function (e) {
2303                            if ( $.isFunction( onSuccess ) ) {
2304                                onSuccess({ // Faking an xhr object
2305                                    responseText: xdr.responseText,
2306                                    status: 200, // XDomainRequest doesn't support status codes, so we just fake one! :/
2307                                    statusText: 'OK'
2308                                });
2309                            }
2310                        };
2311                        xdr.onerror = function (e) {
2312                            if ($.isFunction(onError)) {
2313                                onError({ // Faking an xhr object
2314                                    responseText: xdr.responseText,
2315                                    status: 444, // 444 No Response
2316                                    statusText: 'An error happened. Due to an XDomainRequest deficiency we can not extract any information about this error. Upgrade your browser.'
2317                                });
2318                            }
2319                        };
2320                        try {
2321                            xdr.open('GET', url);
2322                            xdr.send();
2323                        } catch (e2) {
2324                            if ( $.isFunction( onError ) ) {
2325                                onError( request, e );
2326                            }
2327                        }
2328                    }
2329                } else {
2330                    if ( $.isFunction( onError ) ) {
2331                        onError( request, e );
2332                    }
2333                }
2334            }
2335
2336            return request;
2337        },
2338
2339        /**
2340         * Taken from jQuery 1.6.1
2341         * @function
2342         * @param {Object} options
2343         * @param {String} options.url
2344         * @param {Function} options.callback
2345         * @param {String} [options.param='callback'] The name of the url parameter
2346         *      to request the jsonp provider with.
2347         * @param {String} [options.callbackName=] The name of the callback to
2348         *      request the jsonp provider with.
2349         */
2350        jsonp: function( options ){
2351            var script,
2352                url     = options.url,
2353                head    = document.head ||
2354                    document.getElementsByTagName( "head" )[ 0 ] ||
2355                    document.documentElement,
2356                jsonpCallback = options.callbackName || 'openseadragon' + $.now(),
2357                previous      = window[ jsonpCallback ],
2358                replace       = "$1" + jsonpCallback + "$2",
2359                callbackParam = options.param || 'callback',
2360                callback      = options.callback;
2361
2362            url = url.replace( /(\=)\?(&|$)|\?\?/i, replace );
2363            // Add callback manually
2364            url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback;
2365
2366            // Install callback
2367            window[ jsonpCallback ] = function( response ) {
2368                if ( !previous ){
2369                    try{
2370                        delete window[ jsonpCallback ];
2371                    }catch(e){
2372                        //swallow
2373                    }
2374                } else {
2375                    window[ jsonpCallback ] = previous;
2376                }
2377                if( callback && $.isFunction( callback ) ){
2378                    callback( response );
2379                }
2380            };
2381
2382            script = document.createElement( "script" );
2383
2384            //TODO: having an issue with async info requests
2385            if( undefined !== options.async || false !== options.async ){
2386                script.async = "async";
2387            }
2388
2389            if ( options.scriptCharset ) {
2390                script.charset = options.scriptCharset;
2391            }
2392
2393            script.src = url;
2394
2395            // Attach handlers for all browsers
2396            script.onload = script.onreadystatechange = function( _, isAbort ) {
2397
2398                if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
2399
2400                    // Handle memory leak in IE
2401                    script.onload = script.onreadystatechange = null;
2402
2403                    // Remove the script
2404                    if ( head && script.parentNode ) {
2405                        head.removeChild( script );
2406                    }
2407
2408                    // Dereference the script
2409                    script = undefined;
2410                }
2411            };
2412            // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
2413            // This arises when a base node is used (#2709 and #4378).
2414            head.insertBefore( script, head.firstChild );
2415
2416        },
2417
2418
2419        /**
2420         * Fully deprecated. Will throw an error.
2421         * @function
2422         * @deprecated use {@link OpenSeadragon.Viewer#open}
2423         */
2424        createFromDZI: function() {
2425            throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open.";
2426        },
2427
2428        /**
2429         * Parses an XML string into a DOM Document.
2430         * @function
2431         * @param {String} string
2432         * @returns {Document}
2433         */
2434        parseXml: function( string ) {
2435            if ( window.DOMParser ) {
2436
2437                $.parseXml = function( string ) {
2438                    var xmlDoc = null,
2439                        parser;
2440
2441                    parser = new DOMParser();
2442                    xmlDoc = parser.parseFromString( string, "text/xml" );
2443                    return xmlDoc;
2444                };
2445
2446            } else if ( window.ActiveXObject ) {
2447
2448                $.parseXml = function( string ) {
2449                    var xmlDoc = null;
2450
2451                    xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
2452                    xmlDoc.async = false;
2453                    xmlDoc.loadXML( string );
2454                    return xmlDoc;
2455                };
2456
2457            } else {
2458                throw new Error( "Browser doesn't support XML DOM." );
2459            }
2460
2461            return $.parseXml( string );
2462        },
2463
2464        /**
2465         * Parses a JSON string into a Javascript object.
2466         * @function
2467         * @param {String} string
2468         * @returns {Object}
2469         */
2470        parseJSON: function(string) {
2471            if (window.JSON && window.JSON.parse) {
2472                $.parseJSON = window.JSON.parse;
2473            } else {
2474                // Should only be used by IE8 in non standards mode
2475                $.parseJSON = function(string) {
2476                    /*jshint evil:true*/
2477                    //eslint-disable-next-line no-eval
2478                    return eval('(' + string + ')');
2479                };
2480            }
2481            return $.parseJSON(string);
2482        },
2483
2484        /**
2485         * Reports whether the image format is supported for tiling in this
2486         * version.
2487         * @function
2488         * @param {String} [extension]
2489         * @returns {Boolean}
2490         */
2491        imageFormatSupported: function( extension ) {
2492            extension = extension ? extension : "";
2493            // eslint-disable-next-line no-use-before-define
2494            return !!FILEFORMATS[ extension.toLowerCase() ];
2495        }
2496
2497    });
2498
2499
2500    //TODO: $.console is often used inside a try/catch block which generally
2501    //      prevents allowings errors to occur with detection until a debugger
2502    //      is attached.  Although I've been guilty of the same anti-pattern
2503    //      I eventually was convinced that errors should naturally propagate in
2504    //      all but the most special cases.
2505    /**
2506     * A convenient alias for console when available, and a simple null
2507     * function when console is unavailable.
2508     * @static
2509     * @private
2510     */
2511    var nullfunction = function( msg ){
2512        //document.location.hash = msg;
2513    };
2514
2515    $.console = window.console || {
2516        log:    nullfunction,
2517        debug:  nullfunction,
2518        info:   nullfunction,
2519        warn:   nullfunction,
2520        error:  nullfunction,
2521        assert: nullfunction
2522    };
2523
2524
2525    /**
2526     * The current browser vendor, version, and related information regarding detected features.
2527     * @member {Object} Browser
2528     * @memberof OpenSeadragon
2529     * @static
2530     * @type {Object}
2531     * @property {OpenSeadragon.BROWSERS} vendor - One of the {@link OpenSeadragon.BROWSERS} enumeration values.
2532     * @property {Number} version
2533     * @property {Boolean} alpha - Does the browser support image alpha transparency.
2534     */
2535    $.Browser = {
2536        vendor:     $.BROWSERS.UNKNOWN,
2537        version:    0,
2538        alpha:      true
2539    };
2540
2541
2542    var FILEFORMATS = {
2543            "bmp":  false,
2544            "jpeg": true,
2545            "jpg":  true,
2546            "png":  true,
2547            "tif":  false,
2548            "wdp":  false
2549        },
2550        URLPARAMS = {};
2551
2552    (function() {
2553        //A small auto-executing routine to determine the browser vendor,
2554        //version and supporting feature sets.
2555        var ver = navigator.appVersion,
2556            ua  = navigator.userAgent,
2557            regex;
2558
2559        //console.error( 'appName: ' + navigator.appName );
2560        //console.error( 'appVersion: ' + navigator.appVersion );
2561        //console.error( 'userAgent: ' + navigator.userAgent );
2562
2563        switch( navigator.appName ){
2564            case "Microsoft Internet Explorer":
2565                if( !!window.attachEvent &&
2566                    !!window.ActiveXObject ) {
2567
2568                    $.Browser.vendor = $.BROWSERS.IE;
2569                    $.Browser.version = parseFloat(
2570                        ua.substring(
2571                            ua.indexOf( "MSIE" ) + 5,
2572                            ua.indexOf( ";", ua.indexOf( "MSIE" ) ) )
2573                        );
2574                }
2575                break;
2576            case "Netscape":
2577                if (window.addEventListener) {
2578                    if ( ua.indexOf( "Firefox" ) >= 0 ) {
2579                        $.Browser.vendor = $.BROWSERS.FIREFOX;
2580                        $.Browser.version = parseFloat(
2581                            ua.substring( ua.indexOf( "Firefox" ) + 8 )
2582                        );
2583                    } else if ( ua.indexOf( "Safari" ) >= 0 ) {
2584                        $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ?
2585                            $.BROWSERS.CHROME :
2586                            $.BROWSERS.SAFARI;
2587                        $.Browser.version = parseFloat(
2588                            ua.substring(
2589                                ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1,
2590                                ua.indexOf( "Safari" )
2591                            )
2592                        );
2593                    } else {
2594                        regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,})");
2595                        if ( regex.exec( ua ) !== null ) {
2596                            $.Browser.vendor = $.BROWSERS.IE;
2597                            $.Browser.version = parseFloat( RegExp.$1 );
2598                        }
2599                    }
2600                }
2601                break;
2602            case "Opera":
2603                $.Browser.vendor = $.BROWSERS.OPERA;
2604                $.Browser.version = parseFloat( ver );
2605                break;
2606        }
2607
2608            // ignore '?' portion of query string
2609        var query = window.location.search.substring( 1 ),
2610            parts = query.split('&'),
2611            part,
2612            sep,
2613            i;
2614
2615        for ( i = 0; i < parts.length; i++ ) {
2616            part = parts[ i ];
2617            sep  = part.indexOf( '=' );
2618
2619            if ( sep > 0 ) {
2620                var key = part.substring( 0, sep ),
2621                    value = part.substring( sep + 1 );
2622                try {
2623                    URLPARAMS[ key ] = decodeURIComponent( value );
2624                } catch (e) {
2625                    $.console.error( "Ignoring malformed URL parameter: %s=%s", key, value );
2626                }
2627            }
2628        }
2629
2630        //determine if this browser supports image alpha transparency
2631        $.Browser.alpha = !(
2632            (
2633                $.Browser.vendor == $.BROWSERS.IE &&
2634                $.Browser.version < 9
2635            ) || (
2636                $.Browser.vendor == $.BROWSERS.CHROME &&
2637                $.Browser.version < 2
2638            )
2639        );
2640
2641        //determine if this browser supports element.style.opacity
2642        $.Browser.opacity = !(
2643            $.Browser.vendor == $.BROWSERS.IE &&
2644            $.Browser.version < 9
2645        );
2646
2647    })();
2648
2649
2650    // Adding support for HTML5's requestAnimationFrame as suggested by acdha.
2651    // Implementation taken from matt synder's post here:
2652    // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/
2653    (function( w ) {
2654
2655        // most browsers have an implementation
2656        var requestAnimationFrame = w.requestAnimationFrame ||
2657            w.mozRequestAnimationFrame ||
2658            w.webkitRequestAnimationFrame ||
2659            w.msRequestAnimationFrame;
2660
2661        var cancelAnimationFrame = w.cancelAnimationFrame ||
2662            w.mozCancelAnimationFrame ||
2663            w.webkitCancelAnimationFrame ||
2664            w.msCancelAnimationFrame;
2665
2666        // polyfill, when necessary
2667        if ( requestAnimationFrame && cancelAnimationFrame ) {
2668            // We can't assign these window methods directly to $ because they
2669            // expect their "this" to be "window", so we call them in wrappers.
2670            $.requestAnimationFrame = function(){
2671                return requestAnimationFrame.apply( w, arguments );
2672            };
2673            $.cancelAnimationFrame = function(){
2674                return cancelAnimationFrame.apply( w, arguments );
2675            };
2676        } else {
2677            var aAnimQueue = [],
2678                processing = [],
2679                iRequestId = 0,
2680                iIntervalId;
2681
2682            // create a mock requestAnimationFrame function
2683            $.requestAnimationFrame = function( callback ) {
2684                aAnimQueue.push( [ ++iRequestId, callback ] );
2685
2686                if ( !iIntervalId ) {
2687                    iIntervalId = setInterval( function() {
2688                        if ( aAnimQueue.length ) {
2689                            var time = $.now();
2690                            // Process all of the currently outstanding frame
2691                            // requests, but none that get added during the
2692                            // processing.
2693                            // Swap the arrays so we don't have to create a new
2694                            // array every frame.
2695                            var temp = processing;
2696                            processing = aAnimQueue;
2697                            aAnimQueue = temp;
2698                            while ( processing.length ) {
2699                                processing.shift()[ 1 ]( time );
2700                            }
2701                        } else {
2702                            // don't continue the interval, if unnecessary
2703                            clearInterval( iIntervalId );
2704                            iIntervalId = undefined;
2705                        }
2706                    }, 1000 / 50);  // estimating support for 50 frames per second
2707                }
2708
2709                return iRequestId;
2710            };
2711
2712            // create a mock cancelAnimationFrame function
2713            $.cancelAnimationFrame = function( requestId ) {
2714                // find the request ID and remove it
2715                var i, j;
2716                for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) {
2717                    if ( aAnimQueue[ i ][ 0 ] === requestId ) {
2718                        aAnimQueue.splice( i, 1 );
2719                        return;
2720                    }
2721                }
2722
2723                // If it's not in the queue, it may be in the set we're currently
2724                // processing (if cancelAnimationFrame is called from within a
2725                // requestAnimationFrame callback).
2726                for ( i = 0, j = processing.length; i < j; i += 1 ) {
2727                    if ( processing[ i ][ 0 ] === requestId ) {
2728                        processing.splice( i, 1 );
2729                        return;
2730                    }
2731                }
2732            };
2733        }
2734    })( window );
2735
2736    /**
2737     * @private
2738     * @inner
2739     * @function
2740     * @param {Element} element
2741     * @param {Boolean} [isFixed]
2742     * @returns {Element}
2743     */
2744    function getOffsetParent( element, isFixed ) {
2745        if ( isFixed && element != document.body ) {
2746            return document.body;
2747        } else {
2748            return element.offsetParent;
2749        }
2750    }
2751
2752}(OpenSeadragon));
2753
2754
2755// Universal Module Definition, supports CommonJS, AMD and simple script tag
2756(function (root, factory) {
2757    if (typeof define === 'function' && define.amd) {
2758        // expose as amd module
2759        define([], factory);
2760    } else if (typeof module === 'object' && module.exports) {
2761        // expose as commonjs module
2762        module.exports = factory();
2763    } else {
2764        // expose as window.OpenSeadragon
2765        root.OpenSeadragon = factory();
2766    }
2767}(this, function () {
2768    return OpenSeadragon;
2769}));
2770
2771/*
2772 * OpenSeadragon - full-screen support functions
2773 *
2774 * Copyright (C) 2009 CodePlex Foundation
2775 * Copyright (C) 2010-2013 OpenSeadragon contributors
2776 *
2777 * Redistribution and use in source and binary forms, with or without
2778 * modification, are permitted provided that the following conditions are
2779 * met:
2780 *
2781 * - Redistributions of source code must retain the above copyright notice,
2782 *   this list of conditions and the following disclaimer.
2783 *
2784 * - Redistributions in binary form must reproduce the above copyright
2785 *   notice, this list of conditions and the following disclaimer in the
2786 *   documentation and/or other materials provided with the distribution.
2787 *
2788 * - Neither the name of CodePlex Foundation nor the names of its
2789 *   contributors may be used to endorse or promote products derived from
2790 *   this software without specific prior written permission.
2791 *
2792 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2793 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2794 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2795 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
2796 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2797 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
2798 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
2799 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
2800 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2801 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2802 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2803 */
2804
2805(function( $ ) {
2806    /**
2807     * Determine native full screen support we can get from the browser.
2808     * @member fullScreenApi
2809     * @memberof OpenSeadragon
2810     * @type {object}
2811     * @property {Boolean} supportsFullScreen Return true if full screen API is supported.
2812     * @property {Function} isFullScreen Return true if currently in full screen mode.
2813     * @property {Function} getFullScreenElement Return the element currently in full screen mode.
2814     * @property {Function} requestFullScreen Make a request to go in full screen mode.
2815     * @property {Function} exitFullScreen Make a request to exit full screen mode.
2816     * @property {Function} cancelFullScreen Deprecated, use exitFullScreen instead.
2817     * @property {String} fullScreenEventName Event fired when the full screen mode change.
2818     * @property {String} fullScreenErrorEventName Event fired when a request to go
2819     * in full screen mode failed.
2820     */
2821    var fullScreenApi = {
2822        supportsFullScreen: false,
2823        isFullScreen: function() { return false; },
2824        getFullScreenElement: function() { return null; },
2825        requestFullScreen: function() {},
2826        exitFullScreen: function() {},
2827        cancelFullScreen: function() {},
2828        fullScreenEventName: '',
2829        fullScreenErrorEventName: ''
2830    };
2831
2832    // check for native support
2833    if ( document.exitFullscreen ) {
2834        // W3C standard
2835        fullScreenApi.supportsFullScreen = true;
2836        fullScreenApi.getFullScreenElement = function() {
2837            return document.fullscreenElement;
2838        };
2839        fullScreenApi.requestFullScreen = function( element ) {
2840            return element.requestFullscreen();
2841        };
2842        fullScreenApi.exitFullScreen = function() {
2843            document.exitFullscreen();
2844        };
2845        fullScreenApi.fullScreenEventName = "fullscreenchange";
2846        fullScreenApi.fullScreenErrorEventName = "fullscreenerror";
2847    } else if ( document.msExitFullscreen ) {
2848        // IE 11
2849        fullScreenApi.supportsFullScreen = true;
2850        fullScreenApi.getFullScreenElement = function() {
2851            return document.msFullscreenElement;
2852        };
2853        fullScreenApi.requestFullScreen = function( element ) {
2854            return element.msRequestFullscreen();
2855        };
2856        fullScreenApi.exitFullScreen = function() {
2857            document.msExitFullscreen();
2858        };
2859        fullScreenApi.fullScreenEventName = "MSFullscreenChange";
2860        fullScreenApi.fullScreenErrorEventName = "MSFullscreenError";
2861    } else if ( document.webkitExitFullscreen ) {
2862        // Recent webkit
2863        fullScreenApi.supportsFullScreen = true;
2864        fullScreenApi.getFullScreenElement = function() {
2865            return document.webkitFullscreenElement;
2866        };
2867        fullScreenApi.requestFullScreen = function( element ) {
2868            return element.webkitRequestFullscreen();
2869        };
2870        fullScreenApi.exitFullScreen = function() {
2871            document.webkitExitFullscreen();
2872        };
2873        fullScreenApi.fullScreenEventName = "webkitfullscreenchange";
2874        fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror";
2875    } else if ( document.webkitCancelFullScreen ) {
2876        // Old webkit
2877        fullScreenApi.supportsFullScreen = true;
2878        fullScreenApi.getFullScreenElement = function() {
2879            return document.webkitCurrentFullScreenElement;
2880        };
2881        fullScreenApi.requestFullScreen = function( element ) {
2882            return element.webkitRequestFullScreen();
2883        };
2884        fullScreenApi.exitFullScreen = function() {
2885            document.webkitCancelFullScreen();
2886        };
2887        fullScreenApi.fullScreenEventName = "webkitfullscreenchange";
2888        fullScreenApi.fullScreenErrorEventName = "webkitfullscreenerror";
2889    } else if ( document.mozCancelFullScreen ) {
2890        // Firefox
2891        fullScreenApi.supportsFullScreen = true;
2892        fullScreenApi.getFullScreenElement = function() {
2893            return document.mozFullScreenElement;
2894        };
2895        fullScreenApi.requestFullScreen = function( element ) {
2896            return element.mozRequestFullScreen();
2897        };
2898        fullScreenApi.exitFullScreen = function() {
2899            document.mozCancelFullScreen();
2900        };
2901        fullScreenApi.fullScreenEventName = "mozfullscreenchange";
2902        fullScreenApi.fullScreenErrorEventName = "mozfullscreenerror";
2903    }
2904    fullScreenApi.isFullScreen = function() {
2905        return fullScreenApi.getFullScreenElement() !== null;
2906    };
2907    fullScreenApi.cancelFullScreen = function() {
2908        $.console.error("cancelFullScreen is deprecated. Use exitFullScreen instead.");
2909        fullScreenApi.exitFullScreen();
2910    };
2911
2912    // export api
2913    $.extend( $, fullScreenApi );
2914
2915})( OpenSeadragon );
2916
2917/*
2918 * OpenSeadragon - EventSource
2919 *
2920 * Copyright (C) 2009 CodePlex Foundation
2921 * Copyright (C) 2010-2013 OpenSeadragon contributors
2922 *
2923 * Redistribution and use in source and binary forms, with or without
2924 * modification, are permitted provided that the following conditions are
2925 * met:
2926 *
2927 * - Redistributions of source code must retain the above copyright notice,
2928 *   this list of conditions and the following disclaimer.
2929 *
2930 * - Redistributions in binary form must reproduce the above copyright
2931 *   notice, this list of conditions and the following disclaimer in the
2932 *   documentation and/or other materials provided with the distribution.
2933 *
2934 * - Neither the name of CodePlex Foundation nor the names of its
2935 *   contributors may be used to endorse or promote products derived from
2936 *   this software without specific prior written permission.
2937 *
2938 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2939 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2940 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2941 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
2942 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2943 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
2944 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
2945 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
2946 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2947 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2948 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2949 */
2950
2951(function($){
2952
2953/**
2954 * Event handler method signature used by all OpenSeadragon events.
2955 *
2956 * @callback EventHandler
2957 * @memberof OpenSeadragon
2958 * @param {Object} event - See individual events for event-specific properties.
2959 */
2960
2961
2962/**
2963 * @class EventSource
2964 * @classdesc For use by classes which want to support custom, non-browser events.
2965 *
2966 * @memberof OpenSeadragon
2967 */
2968$.EventSource = function() {
2969    this.events = {};
2970};
2971
2972/** @lends OpenSeadragon.EventSource.prototype */
2973$.EventSource.prototype = {
2974
2975    /**
2976     * Add an event handler to be triggered only once (or a given number of times)
2977     * for a given event.
2978     * @function
2979     * @param {String} eventName - Name of event to register.
2980     * @param {OpenSeadragon.EventHandler} handler - Function to call when event
2981     * is triggered.
2982     * @param {Object} [userData=null] - Arbitrary object to be passed unchanged
2983     * to the handler.
2984     * @param {Number} [times=1] - The number of times to handle the event
2985     * before removing it.
2986     */
2987    addOnceHandler: function(eventName, handler, userData, times) {
2988        var self = this;
2989        times = times || 1;
2990        var count = 0;
2991        var onceHandler = function(event) {
2992            count++;
2993            if (count === times) {
2994                self.removeHandler(eventName, onceHandler);
2995            }
2996            handler(event);
2997        };
2998        this.addHandler(eventName, onceHandler, userData);
2999    },
3000
3001    /**
3002     * Add an event handler for a given event.
3003     * @function
3004     * @param {String} eventName - Name of event to register.
3005     * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered.
3006     * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
3007     */
3008    addHandler: function ( eventName, handler, userData ) {
3009        var events = this.events[ eventName ];
3010        if ( !events ) {
3011            this.events[ eventName ] = events = [];
3012        }
3013        if ( handler && $.isFunction( handler ) ) {
3014            events[ events.length ] = { handler: handler, userData: userData || null };
3015        }
3016    },
3017
3018    /**
3019     * Remove a specific event handler for a given event.
3020     * @function
3021     * @param {String} eventName - Name of event for which the handler is to be removed.
3022     * @param {OpenSeadragon.EventHandler} handler - Function to be removed.
3023     */
3024    removeHandler: function ( eventName, handler ) {
3025        var events = this.events[ eventName ],
3026            handlers = [],
3027            i;
3028        if ( !events ) {
3029            return;
3030        }
3031        if ( $.isArray( events ) ) {
3032            for ( i = 0; i < events.length; i++ ) {
3033                if ( events[i].handler !== handler ) {
3034                    handlers.push( events[ i ] );
3035                }
3036            }
3037            this.events[ eventName ] = handlers;
3038        }
3039    },
3040
3041
3042    /**
3043     * Remove all event handlers for a given event type. If no type is given all
3044     * event handlers for every event type are removed.
3045     * @function
3046     * @param {String} eventName - Name of event for which all handlers are to be removed.
3047     */
3048    removeAllHandlers: function( eventName ) {
3049        if ( eventName ){
3050            this.events[ eventName ] = [];
3051        } else{
3052            for ( var eventType in this.events ) {
3053                this.events[ eventType ] = [];
3054            }
3055        }
3056    },
3057
3058    /**
3059     * Get a function which iterates the list of all handlers registered for a given event, calling the handler for each.
3060     * @function
3061     * @param {String} eventName - Name of event to get handlers for.
3062     */
3063    getHandler: function ( eventName ) {
3064        var events = this.events[ eventName ];
3065        if ( !events || !events.length ) {
3066            return null;
3067        }
3068        events = events.length === 1 ?
3069            [ events[ 0 ] ] :
3070            Array.apply( null, events );
3071        return function ( source, args ) {
3072            var i,
3073                length = events.length;
3074            for ( i = 0; i < length; i++ ) {
3075                if ( events[ i ] ) {
3076                    args.eventSource = source;
3077                    args.userData = events[ i ].userData;
3078                    events[ i ].handler( args );
3079                }
3080            }
3081        };
3082    },
3083
3084    /**
3085     * Trigger an event, optionally passing additional information.
3086     * @function
3087     * @param {String} eventName - Name of event to register.
3088     * @param {Object} eventArgs - Event-specific data.
3089     */
3090    raiseEvent: function( eventName, eventArgs ) {
3091        //uncomment if you want to get a log of all events
3092        //$.console.log( eventName );
3093        var handler = this.getHandler( eventName );
3094
3095        if ( handler ) {
3096            if ( !eventArgs ) {
3097                eventArgs = {};
3098            }
3099
3100            handler( this, eventArgs );
3101        }
3102    }
3103};
3104
3105}( OpenSeadragon ));
3106
3107/*
3108 * OpenSeadragon - MouseTracker
3109 *
3110 * Copyright (C) 2009 CodePlex Foundation
3111 * Copyright (C) 2010-2013 OpenSeadragon contributors
3112 *
3113 * Redistribution and use in source and binary forms, with or without
3114 * modification, are permitted provided that the following conditions are
3115 * met:
3116 *
3117 * - Redistributions of source code must retain the above copyright notice,
3118 *   this list of conditions and the following disclaimer.
3119 *
3120 * - Redistributions in binary form must reproduce the above copyright
3121 *   notice, this list of conditions and the following disclaimer in the
3122 *   documentation and/or other materials provided with the distribution.
3123 *
3124 * - Neither the name of CodePlex Foundation nor the names of its
3125 *   contributors may be used to endorse or promote products derived from
3126 *   this software without specific prior written permission.
3127 *
3128 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
3129 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
3130 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3131 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
3132 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3133 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
3134 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
3135 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
3136 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
3137 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3138 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3139 */
3140
3141(function ( $ ) {
3142
3143    // All MouseTracker instances
3144    var MOUSETRACKERS  = [];
3145
3146    // dictionary from hash to private properties
3147    var THIS           = {};
3148
3149
3150    /**
3151     * @class MouseTracker
3152     * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) gestures
3153     *            and keyboard events on a specified element.
3154     * @memberof OpenSeadragon
3155     * @param {Object} options
3156     *      Allows configurable properties to be entirely specified by passing
3157     *      an options object to the constructor.  The constructor also supports
3158     *      the original positional arguments 'element', 'clickTimeThreshold',
3159     *      and 'clickDistThreshold' in that order.
3160     * @param {Element|String} options.element
3161     *      A reference to an element or an element id for which the pointer/key
3162     *      events will be monitored.
3163     * @param {Boolean} [options.startDisabled=false]
3164     *      If true, event tracking on the element will not start until
3165     *      {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
3166     * @param {Number} options.clickTimeThreshold
3167     *      The number of milliseconds within which a pointer down-up event combination
3168     *      will be treated as a click gesture.
3169     * @param {Number} options.clickDistThreshold
3170     *      The maximum distance allowed between a pointer down event and a pointer up event
3171     *      to be treated as a click gesture.
3172     * @param {Number} options.dblClickTimeThreshold
3173     *      The number of milliseconds within which two pointer down-up event combinations
3174     *      will be treated as a double-click gesture.
3175     * @param {Number} options.dblClickDistThreshold
3176     *      The maximum distance allowed between two pointer click events
3177     *      to be treated as a click gesture.
3178     * @param {Number} [options.stopDelay=50]
3179     *      The number of milliseconds without pointer move before the stop
3180     *      event is fired.
3181     * @param {OpenSeadragon.EventHandler} [options.enterHandler=null]
3182     *      An optional handler for pointer enter.
3183     * @param {OpenSeadragon.EventHandler} [options.exitHandler=null]
3184     *      An optional handler for pointer exit.
3185     * @param {OpenSeadragon.EventHandler} [options.pressHandler=null]
3186     *      An optional handler for pointer press.
3187     * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null]
3188     *      An optional handler for pointer non-primary button press.
3189     * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null]
3190     *      An optional handler for pointer release.
3191     * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null]
3192     *      An optional handler for pointer non-primary button release.
3193     * @param {OpenSeadragon.EventHandler} [options.moveHandler=null]
3194     *      An optional handler for pointer move.
3195     * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null]
3196     *      An optional handler for mouse wheel scroll.
3197     * @param {OpenSeadragon.EventHandler} [options.clickHandler=null]
3198     *      An optional handler for pointer click.
3199     * @param {OpenSeadragon.EventHandler} [options.dblClickHandler=null]
3200     *      An optional handler for pointer double-click.
3201     * @param {OpenSeadragon.EventHandler} [options.dragHandler=null]
3202     *      An optional handler for the drag gesture.
3203     * @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null]
3204     *      An optional handler for after a drag gesture.
3205     * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null]
3206     *      An optional handler for the pinch gesture.
3207     * @param {OpenSeadragon.EventHandler}
3207 [options.keyDownHandler=null]
3208     *      An optional handler for keydown.
3209     * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null]
3210     *      An optional handler for keyup.
3211     * @param {OpenSeadragon.EventHandler} [options.keyHandler=null]
3212     *      An optional handler for keypress.
3213     * @param {OpenSeadragon.EventHandler} [options.focusHandler=null]
3214     *      An optional handler for focus.
3215     * @param {OpenSeadragon.EventHandler} [options.blurHandler=null]
3216     *      An optional handler for blur.
3217     * @param {Object} [options.userData=null]
3218     *      Arbitrary object to be passed unchanged to any attached handler methods.
3219     */
3220    $.MouseTracker = function ( options ) {
3221
3222        MOUSETRACKERS.push( this );
3223
3224        var args = arguments;
3225
3226        if ( !$.isPlainObject( options ) ) {
3227            options = {
3228                element:            args[ 0 ],
3229                clickTimeThreshold: args[ 1 ],
3230                clickDistThreshold: args[ 2 ]
3231            };
3232        }
3233
3234        this.hash               = Math.random(); // An unique hash for this tracker.
3235        /**
3236         * The element for which pointer events are being monitored.
3237         * @member {Element} element
3238         * @memberof OpenSeadragon.MouseTracker#
3239         */
3240        this.element            = $.getElement( options.element );
3241        /**
3242         * The number of milliseconds within which a pointer down-up event combination
3243         * will be treated as a click gesture.
3244         * @member {Number} clickTimeThreshold
3245         * @memberof OpenSeadragon.MouseTracker#
3246         */
3247        this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold;
3248        /**
3249         * The maximum distance allowed between a pointer down event and a pointer up event
3250         * to be treated as a click gesture.
3251         * @member {Number} clickDistThreshold
3252         * @memberof OpenSeadragon.MouseTracker#
3253         */
3254        this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold;
3255        /**
3256         * The number of milliseconds within which two pointer down-up event combinations
3257         * will be treated as a double-click gesture.
3258         * @member {Number} dblClickTimeThreshold
3259         * @memberof OpenSeadragon.MouseTracker#
3260         */
3261        this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold;
3262        /**
3263         * The maximum distance allowed between two pointer click events
3264         * to be treated as a click gesture.
3265         * @member {Number} clickDistThreshold
3266         * @memberof OpenSeadragon.MouseTracker#
3267         */
3268        this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold;
3269        /*eslint-disable no-multi-spaces*/
3270        this.userData              = options.userData          || null;
3271        this.stopDelay             = options.stopDelay         || 50;
3272
3273        this.enterHandler             = options.enterHandler             || null;
3274        this.exitHandler              = options.exitHandler              || null;
3275        this.pressHandler             = options.pressHandler             || null;
3276        this.nonPrimaryPressHandler   = options.nonPrimaryPressHandler   || null;
3277        this.releaseHandler           = options.releaseHandler           || null;
3278        this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null;
3279        this.moveHandler              = options.moveHandler              || null;
3280        this.scrollHandler            = options.scrollHandler            || null;
3281        this.clickHandler             = options.clickHandler             || null;
3282        this.dblClickHandler          = options.dblClickHandler          || null;
3283        this.dragHandler              = options.dragHandler              || null;
3284        this.dragEndHandler           = options.dragEndHandler           || null;
3285        this.pinchHandler             = options.pinchHandler             || null;
3286        this.stopHandler              = options.stopHandler              || null;
3287        this.keyDownHandler           = options.keyDownHandler           || null;
3288        this.keyUpHandler             = options.keyUpHandler             || null;
3289        this.keyHandler               = options.keyHandler               || null;
3290        this.focusHandler             = options.focusHandler             || null;
3291        this.blurHandler              = options.blurHandler              || null;
3292        /*eslint-enable no-multi-spaces*/
3293
3294        //Store private properties in a scope sealed hash map
3295        var _this = this;
3296
3297        /**
3298         * @private
3299         * @property {Boolean} tracking
3300         *      Are we currently tracking pointer events for this element.
3301         */
3302        THIS[ this.hash ] = {
3303            click:                 function ( event ) { onClick( _this, event ); },
3304            dblclick:              function ( event ) { onDblClick( _this, event ); },
3305            keydown:               function ( event ) { onKeyDown( _this, event ); },
3306            keyup:                 function ( event ) { onKeyUp( _this, event ); },
3307            keypress:              function ( event ) { onKeyPress( _this, event ); },
3308            focus:                 function ( event ) { onFocus( _this, event ); },
3309            blur:                  function ( event ) { onBlur( _this, event ); },
3310
3311            wheel:                 function ( event ) { onWheel( _this, event ); },
3312            mousewheel:            function ( event ) { onMouseWheel( _this, event ); },
3313            DOMMouseScroll:        function ( event ) { onMouseWheel( _this, event ); },
3314            MozMousePixelScroll:   function ( event ) { onMouseWheel( _this, event ); },
3315
3316            mouseenter:            function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only
3317            mouseleave:            function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only
3318            mouseover:             function ( event ) { onMouseOver( _this, event ); },
3319            mouseout:              function ( event ) { onMouseOut( _this, event ); },
3320            mousedown:             function ( event ) { onMouseDown( _this, event ); },
3321            mouseup:               function ( event ) { onMouseUp( _this, event ); },
3322            mouseupcaptured:       function ( event ) { onMouseUpCaptured( _this, event ); },
3323            mousemove:             function ( event ) { onMouseMove( _this, event ); },
3324            mousemovecaptured:     function ( event ) { onMouseMoveCaptured( _this, event ); },
3325
3326            touchstart:            function ( event ) { onTouchStart( _this, event ); },
3327            touchend:              function ( event ) { onTouchEnd( _this, event ); },
3328            touchendcaptured:      function ( event ) { onTouchEndCaptured( _this, event ); },
3329            touchmove:             function ( event ) { onTouchMove( _this, event ); },
3330            touchmovecaptured:     function ( event ) { onTouchMoveCaptured( _this, event ); },
3331            touchcancel:           function ( event ) { onTouchCancel( _this, event ); },
3332
3333            gesturestart:          function ( event ) { onGestureStart( _this, event ); },
3334            gesturechange:         function ( event ) { onGestureChange( _this, event ); },
3335
3336            pointerover:           function ( event ) { onPointerOver( _this, event ); },
3337            MSPointerOver:         function ( event ) { onPointerOver( _this, event ); },
3338            pointerout:            function ( event ) { onPointerOut( _this, event ); },
3339            MSPointerOut:          function ( event ) { onPointerOut( _this, event ); },
3340            pointerdown:           function ( event ) { onPointerDown( _this, event ); },
3341            MSPointerDown:         function ( event ) { onPointerDown( _this, event ); },
3342            pointerup:             function ( event ) { onPointerUp( _this, event ); },
3343            MSPointerUp:           function ( event ) { onPointerUp( _this, event ); },
3344            pointermove:           function ( event ) { onPointerMove( _this, event ); },
3345            MSPointerMove:         function ( event ) { onPointerMove( _this, event ); },
3346            pointercancel:         function ( event ) { onPointerCancel( _this, event ); },
3347            MSPointerCancel:       function ( event ) { onPointerCancel( _this, event ); },
3348            pointerupcaptured:     function ( event ) { onPointerUpCaptured( _this, event ); },
3349            pointermovecaptured:   function ( event ) { onPointerMoveCaptured( _this, event ); },
3350
3351            tracking:              false,
3352
3353            // Active pointers lists. Array of GesturePointList objects, one for each pointer device type.
3354            // GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()).
3355            // Active pointers are any pointer being tracked for this element which are in the hit-test area
3356            //     of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
3357            activePointersLists:   [],
3358
3359            // Tracking for double-click gesture
3360            lastClickPos:          null,
3361            dblClickTimeOut:       null,
3362
3363            // Tracking for pinch gesture
3364            pinchGPoints:          [],
3365            lastPinchDist:         0,
3366            currentPinchDist:      0,
3367            lastPinchCenter:       null,
3368            currentPinchCenter:    null
3369        };
3370
3371        if ( !options.startDisabled ) {
3372            this.setTracking( true );
3373        }
3374    };
3375
3376    /** @lends OpenSeadragon.MouseTracker.prototype */
3377    $.MouseTracker.prototype = {
3378
3379        /**
3380         * Clean up any events or objects created by the tracker.
3381         * @function
3382         */
3383        destroy: function () {
3384            var i;
3385
3386            stopTracking( this );
3387            this.element = null;
3388
3389            for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
3390                if ( MOUSETRACKERS[ i ] === this ) {
3391                    MOUSETRACKERS.splice( i, 1 );
3392                    break;
3393                }
3394            }
3395
3396            THIS[ this.hash ] = null;
3397            delete THIS[ this.hash ];
3398        },
3399
3400        /**
3401         * Are we currently tracking events on this element.
3402         * @deprecated Just use this.tracking
3403         * @function
3404         * @returns {Boolean} Are we currently tracking events on this element.
3405         */
3406        isTracking: function () {
3407            return THIS[ this.hash ].tracking;
3408        },
3409
3410        /**
3411         * Enable or disable whether or not we are tracking events on this element.
3412         * @function
3413         * @param {Boolean} track True to start tracking, false to stop tracking.
3414         * @returns {OpenSeadragon.MouseTracker} Chainable.
3415         */
3416        setTracking: function ( track ) {
3417            if ( track ) {
3418                startTracking( this );
3419            } else {
3420                stopTracking( this );
3421            }
3422            //chain
3423            return this;
3424        },
3425
3426        /**
3427         * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type.
3428         * @function
3429         * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
3430         * @returns {Array.<OpenSeadragon.MouseTracker.GesturePointList>}
3431         */
3432        getActivePointersListsExceptType: function ( type ) {
3433            var delegate = THIS[ this.hash ];
3434            var listArray = [];
3435
3436            for (var i = 0; i < delegate.activePointersLists.length; ++i) {
3437                if (delegate.activePointersLists[i].type !== type) {
3438                    listArray.push(delegate.activePointersLists[i]);
3439                }
3440            }
3441
3442            return listArray;
3443        },
3444
3445        /**
3446         * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type,
3447         * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type.
3448         * @function
3449         * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
3450         * @returns {OpenSeadragon.MouseTracker.GesturePointList}
3451         */
3452        getActivePointersListByType: function ( type ) {
3453            var delegate = THIS[ this.hash ],
3454                i,
3455                len = delegate.activePointersLists.length,
3456                list;
3457
3458            for ( i = 0; i < len; i++ ) {
3459                if ( delegate.activePointersLists[ i ].type === type ) {
3460                    return delegate.activePointersLists[ i ];
3461                }
3462            }
3463
3464            list = new $.MouseTracker.GesturePointList( type );
3465            delegate.activePointersLists.push( list );
3466            return list;
3467        },
3468
3469        /**
3470         * Returns the total number of pointers currently active on the tracked element.
3471         * @function
3472         * @returns {Number}
3473         */
3474        getActivePointerCount: function () {
3475            var delegate = THIS[ this.hash ],
3476                i,
3477                len = delegate.activePointersLists.length,
3478                count = 0;
3479
3480            for ( i = 0; i < len; i++ ) {
3481                count += delegate.activePointersLists[ i ].getLength();
3482            }
3483
3484            return count;
3485        },
3486
3487        /**
3488         * Implement or assign implementation to these handlers during or after
3489         * calling the constructor.
3490         * @function
3491         * @param {Object} event
3492         * @param {OpenSeadragon.MouseTracker} event.eventSource
3493         *      A reference to the tracker instance.
3494         * @param {String} event.pointerType
3495         *     "mouse", "touch", "pen", etc.
3496         * @param {OpenSeadragon.Point} event.position
3497         *      The position of the event relative to the tracked element.
3498         * @param {Number} event.buttons
3499         *      Current buttons pressed.
3500         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3501         * @param {Number} event.pointers
3502         *      Number of pointers (all types) active in the tracked element.
3503         * @param {Boolean} event.insideElementPressed
3504         *      True if the left mouse button is currently being pressed and was
3505         *      initiated inside the tracked element, otherwise false.
3506         * @param {Boolean} event.buttonDownAny
3507         *      Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
3508         * @param {Boolean} event.isTouchEvent
3509         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3510         * @param {Object} event.originalEvent
3511         *      The original event object.
3512         * @param {Boolean} event.preventDefaultAction
3513         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3514         * @param {Object} event.userData
3515         *      Arbitrary user-defined object.
3516         */
3517        enterHandler: function () { },
3518
3519        /**
3520         * Implement or assign implementation to these handlers during or after
3521         * calling the constructor.
3522         * @function
3523         * @param {Object} event
3524         * @param {OpenSeadragon.MouseTracker} event.eventSource
3525         *      A reference to the tracker instance.
3526         * @param {String} event.pointerType
3527         *     "mouse", "touch", "pen", etc.
3528         * @param {OpenSeadragon.Point} event.position
3529         *      The position of the event relative to the tracked element.
3530         * @param {Number} event.buttons
3531         *      Current buttons pressed.
3532         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3533         * @param {Number} event.pointers
3534         *      Number of pointers (all types) active in the tracked element.
3535         * @param {Boolean} event.insideElementPressed
3536         *      True if the left mouse button is currently being pressed and was
3537         *      initiated inside the tracked element, otherwise false.
3538         * @param {Boolean} event.buttonDownAny
3539         *      Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
3540         * @param {Boolean} event.isTouchEvent
3541         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3542         * @param {Object} event.originalEvent
3543         *      The original event object.
3544         * @param {Boolean} event.preventDefaultAction
3545         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3546         * @param {Object} event.userData
3547         *      Arbitrary user-defined object.
3548         */
3549        exitHandler: function () { },
3550
3551        /**
3552         * Implement or assign implementation to these handlers during or after
3553         * calling the constructor.
3554         * @function
3555         * @param {Object} event
3556         * @param {OpenSeadragon.MouseTracker} event.eventSource
3557         *      A reference to the tracker instance.
3558         * @param {String} event.pointerType
3559         *     "mouse", "touch", "pen", etc.
3560         * @param {OpenSeadragon.Point} event.position
3561         *      The position of the event relative to the tracked element.
3562         * @param {Number} event.buttons
3563         *      Current buttons pressed.
3564         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3565         * @param {Boolean} event.isTouchEvent
3566         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3567         * @param {Object} event.originalEvent
3568         *      The original event object.
3569         * @param {Boolean} event.preventDefaultAction
3570         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3571         * @param {Object} event.userData
3572         *      Arbitrary user-defined object.
3573         */
3574        pressHandler: function () { },
3575
3576        /**
3577         * Implement or assign implementation to these handlers during or after
3578         * calling the constructor.
3579         * @function
3580         * @param {Object} event
3581         * @param {OpenSeadragon.MouseTracker} event.eventSource
3582         *      A reference to the tracker instance.
3583         * @param {String} event.pointerType
3584         *     "mouse", "touch", "pen", etc.
3585         * @param {OpenSeadragon.Point} event.position
3586         *      The position of the event relative to the tracked element.
3587         * @param {Number} event.button
3588         *      Button which caused the event.
3589         *      -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3590         * @param {Number} event.buttons
3591         *      Current buttons pressed.
3592         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3593         * @param {Boolean} event.isTouchEvent
3594         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3595         * @param {Object} event.originalEvent
3596         *      The original event object.
3597         * @param {Boolean} event.preventDefaultAction
3598         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3599         * @param {Object} event.userData
3600         *      Arbitrary user-defined object.
3601         */
3602        nonPrimaryPressHandler: function () { },
3603
3604        /**
3605  
3605       * Implement or assign implementation to these handlers during or after
3606         * calling the constructor.
3607         * @function
3608         * @param {Object} event
3609         * @param {OpenSeadragon.MouseTracker} event.eventSource
3610         *      A reference to the tracker instance.
3611         * @param {String} event.pointerType
3612         *     "mouse", "touch", "pen", etc.
3613         * @param {OpenSeadragon.Point} event.position
3614         *      The position of the event relative to the tracked element.
3615         * @param {Number} event.buttons
3616         *      Current buttons pressed.
3617         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3618         * @param {Boolean} event.insideElementPressed
3619         *      True if the left mouse button is currently being pressed and was
3620         *      initiated inside the tracked element, otherwise false.
3621         * @param {Boolean} event.insideElementReleased
3622         *      True if the cursor inside the tracked element when the button was released.
3623         * @param {Boolean} event.isTouchEvent
3624         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3625         * @param {Object} event.originalEvent
3626         *      The original event object.
3627         * @param {Boolean} event.preventDefaultAction
3628         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3629         * @param {Object} event.userData
3630         *      Arbitrary user-defined object.
3631         */
3632        releaseHandler: function () { },
3633
3634        /**
3635         * Implement or assign implementation to these handlers during or after
3636         * calling the constructor.
3637         * @function
3638         * @param {Object} event
3639         * @param {OpenSeadragon.MouseTracker} event.eventSource
3640         *      A reference to the tracker instance.
3641         * @param {String} event.pointerType
3642         *     "mouse", "touch", "pen", etc.
3643         * @param {OpenSeadragon.Point} event.position
3644         *      The position of the event relative to the tracked element.
3645         * @param {Number} event.button
3646         *      Button which caused the event.
3647         *      -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3648         * @param {Number} event.buttons
3649         *      Current buttons pressed.
3650         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3651         * @param {Boolean} event.isTouchEvent
3652         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3653         * @param {Object} event.originalEvent
3654         *      The original event object.
3655         * @param {Boolean} event.preventDefaultAction
3656         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3657         * @param {Object} event.userData
3658         *      Arbitrary user-defined object.
3659         */
3660        nonPrimaryReleaseHandler: function () { },
3661
3662        /**
3663         * Implement or assign implementation to these handlers during or after
3664         * calling the constructor.
3665         * @function
3666         * @param {Object} event
3667         * @param {OpenSeadragon.MouseTracker} event.eventSource
3668         *      A reference to the tracker instance.
3669         * @param {String} event.pointerType
3670         *     "mouse", "touch", "pen", etc.
3671         * @param {OpenSeadragon.Point} event.position
3672         *      The position of the event relative to the tracked element.
3673         * @param {Number} event.buttons
3674         *      Current buttons pressed.
3675         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3676         * @param {Boolean} event.isTouchEvent
3677         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3678         * @param {Object} event.originalEvent
3679         *      The original event object.
3680         * @param {Boolean} event.preventDefaultAction
3681         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3682         * @param {Object} event.userData
3683         *      Arbitrary user-defined object.
3684         */
3685        moveHandler: function () { },
3686
3687        /**
3688         * Implement or assign implementation to these handlers during or after
3689         * calling the constructor.
3690         * @function
3691         * @param {Object} event
3692         * @param {OpenSeadragon.MouseTracker} event.eventSource
3693         *      A reference to the tracker instance.
3694         * @param {String} event.pointerType
3695         *     "mouse", "touch", "pen", etc.
3696         * @param {OpenSeadragon.Point} event.position
3697         *      The position of the event relative to the tracked element.
3698         * @param {Number} event.scroll
3699         *      The scroll delta for the event.
3700         * @param {Boolean} event.shift
3701         *      True if the shift key was pressed during this event.
3702         * @param {Boolean} event.isTouchEvent
3703         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead. Touch devices no longer generate scroll event.</span>
3704         * @param {Object} event.originalEvent
3705         *      The original event object.
3706         * @param {Boolean} event.preventDefaultAction
3707         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3708         * @param {Object} event.userData
3709         *      Arbitrary user-defined object.
3710         */
3711        scrollHandler: function () { },
3712
3713        /**
3714         * Implement or assign implementation to these handlers during or after
3715         * calling the constructor.
3716         * @function
3717         * @param {Object} event
3718         * @param {OpenSeadragon.MouseTracker} event.eventSource
3719         *      A reference to the tracker instance.
3720         * @param {String} event.pointerType
3721         *     "mouse", "touch", "pen", etc.
3722         * @param {OpenSeadragon.Point} event.position
3723         *      The position of the event relative to the tracked element.
3724         * @param {Boolean} event.quick
3725         *      True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for ignoring drag events.
3726         * @param {Boolean} event.shift
3727         *      True if the shift key was pressed during this event.
3728         * @param {Boolean} event.isTouchEvent
3729         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3730         * @param {Object} event.originalEvent
3731         *      The original event object.
3732         * @param {Boolean} event.preventDefaultAction
3733         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3734         * @param {Object} event.userData
3735         *      Arbitrary user-defined object.
3736         */
3737        clickHandler: function () { },
3738
3739        /**
3740         * Implement or assign implementation to these handlers during or after
3741         * calling the constructor.
3742         * @function
3743         * @param {Object} event
3744         * @param {OpenSeadragon.MouseTracker} event.eventSource
3745         *      A reference to the tracker instance.
3746         * @param {String} event.pointerType
3747         *     "mouse", "touch", "pen", etc.
3748         * @param {OpenSeadragon.Point} event.position
3749         *      The position of the event relative to the tracked element.
3750         * @param {Boolean} event.shift
3751         *      True if the shift key was pressed during this event.
3752         * @param {Boolean} event.isTouchEvent
3753         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3754         * @param {Object} event.originalEvent
3755         *      The original event object.
3756         * @param {Boolean} event.preventDefaultAction
3757         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3758         * @param {Object} event.userData
3759         *      Arbitrary user-defined object.
3760         */
3761        dblClickHandler: function () { },
3762
3763        /**
3764         * Implement or assign implementation to these handlers during or after
3765         * calling the constructor.
3766         * @function
3767         * @param {Object} event
3768         * @param {OpenSeadragon.MouseTracker} event.eventSource
3769         *      A reference to the tracker instance.
3770         * @param {String} event.pointerType
3771         *     "mouse", "touch", "pen", etc.
3772         * @param {OpenSeadragon.Point} event.position
3773         *      The position of the event relative to the tracked element.
3774         * @param {Number} event.buttons
3775         *      Current buttons pressed.
3776         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3777         * @param {OpenSeadragon.Point} event.delta
3778         *      The x,y components of the difference between the current position and the last drag event position.  Useful for ignoring or weighting the events.
3779         * @param {Number} event.speed
3780         *     Current computed speed, in pixels per second.
3781         * @param {Number} event.direction
3782         *     Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
3783         * @param {Boolean} event.shift
3784         *      True if the shift key was pressed during this event.
3785         * @param {Boolean} event.isTouchEvent
3786         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3787         * @param {Object} event.originalEvent
3788         *      The original event object.
3789         * @param {Boolean} event.preventDefaultAction
3790         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3791         * @param {Object} event.userData
3792         *      Arbitrary user-defined object.
3793         */
3794        dragHandler: function () { },
3795
3796        /**
3797         * Implement or assign implementation to these handlers during or after
3798         * calling the constructor.
3799         * @function
3800         * @param {Object} event
3801         * @param {OpenSeadragon.MouseTracker} event.eventSource
3802         *      A reference to the tracker instance.
3803         * @param {String} event.pointerType
3804         *     "mouse", "touch", "pen", etc.
3805         * @param {OpenSeadragon.Point} event.position
3806         *      The position of the event relative to the tracked element.
3807         * @param {Number} event.speed
3808         *     Speed at the end of a drag gesture, in pixels per second.
3809         * @param {Number} event.direction
3810         *     Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
3811         * @param {Boolean} event.shift
3812         *      True if the shift key was pressed during this event.
3813         * @param {Boolean} event.isTouchEvent
3814         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3815         * @param {Object} event.originalEvent
3816         *      The original event object.
3817         * @param {Boolean} event.preventDefaultAction
3818         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3819         * @param {Object} event.userData
3820         *      Arbitrary user-defined object.
3821         */
3822        dragEndHandler: function () { },
3823
3824        /**
3825         * Implement or assign implementation to these handlers during or after
3826         * calling the constructor.
3827         * @function
3828         * @param {Object} event
3829         * @param {OpenSeadragon.MouseTracker} event.eventSource
3830         *      A reference to the tracker instance.
3831         * @param {String} event.pointerType
3832         *     "mouse", "touch", "pen", etc.
3833         * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} event.gesturePoints
3834         *      Gesture points associated with the gesture. Velocity data can be found here.
3835         * @param {OpenSeadragon.Point} event.lastCenter
3836         *      The previous center point of the two pinch contact points relative to the tracked element.
3837         * @param {OpenSeadragon.Point} event.center
3838         *      The center point of the two pinch contact points relative to the tracked element.
3839         * @param {Number} event.lastDistance
3840         *      The previous distance between the two pinch contact points in CSS pixels.
3841         * @param {Number} event.distance
3842         *      The distance between the two pinch contact points in CSS pixels.
3843         * @param {Boolean} event.shift
3844         *      True if the shift key was pressed during this event.
3845         * @param {Object} event.originalEvent
3846         *      The original event object.
3847         * @param {Boolean} event.preventDefaultAction
3848         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3849         * @param {Object} event.userData
3850         *      Arbitrary user-defined object.
3851         */
3852        pinchHandler: function () { },
3853
3854        /**
3855         * Implement or assign implementation to these handlers during or after
3856         * calling the constructor.
3857         * @function
3858         * @param {Object} event
3859         * @param {OpenSeadragon.MouseTracker} event.eventSource
3860         *      A reference to the tracker instance.
3861         * @param {String} event.pointerType
3862         *     "mouse", "touch", "pen", etc.
3863         * @param {OpenSeadragon.Point} event.position
3864         *      The position of the event relative to the tracked element.
3865         * @param {Number} event.buttons
3866         *      Current buttons pressed.
3867         *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3868         * @param {Boolean} event.isTouchEvent
3869         *      True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3870         * @param {Object} event.originalEvent
3871         *      The original event object.
3872         * @param {Boolean} event.preventDefaultAction
3873         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3874         * @param {Object} event.userData
3875         *      Arbitrary user-defined object.
3876         */
3877        stopHandler: function () { },
3878
3879        /**
3880         * Implement or assign implementation to these handlers during or after
3881         * calling the constructor.
3882         * @function
3883         * @param {Object} event
3884         * @param {OpenSeadragon.MouseTracker} event.eventSource
3885         *      A reference to the tracker instance.
3886         * @param {Number} event.keyCode
3887         *      The key code that was pressed.
3888         * @param {Boolean} event.ctrl
3889         *      True if the ctrl key was pressed during this event.
3890         * @param {Boolean} event.shift
3891         *      True if the shift key 
3891was pressed during this event.
3892         * @param {Boolean} event.alt
3893         *      True if the alt key was pressed during this event.
3894         * @param {Boolean} event.meta
3895         *      True if the meta key was pressed during this event.
3896         * @param {Object} event.originalEvent
3897         *      The original event object.
3898         * @param {Boolean} event.preventDefaultAction
3899         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3900         * @param {Object} event.userData
3901         *      Arbitrary user-defined object.
3902         */
3903        keyDownHandler: function () { },
3904
3905        /**
3906         * Implement or assign implementation to these handlers during or after
3907         * calling the constructor.
3908         * @function
3909         * @param {Object} event
3910         * @param {OpenSeadragon.MouseTracker} event.eventSource
3911         *      A reference to the tracker instance.
3912         * @param {Number} event.keyCode
3913         *      The key code that was pressed.
3914         * @param {Boolean} event.ctrl
3915         *      True if the ctrl key was pressed during this event.
3916         * @param {Boolean} event.shift
3917         *      True if the shift key was pressed during this event.
3918         * @param {Boolean} event.alt
3919         *      True if the alt key was pressed during this event.
3920         * @param {Boolean} event.meta
3921         *      True if the meta key was pressed during this event.
3922         * @param {Object} event.originalEvent
3923         *      The original event object.
3924         * @param {Boolean} event.preventDefaultAction
3925         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3926         * @param {Object} event.userData
3927         *      Arbitrary user-defined object.
3928         */
3929        keyUpHandler: function () { },
3930
3931        /**
3932         * Implement or assign implementation to these handlers during or after
3933         * calling the constructor.
3934         * @function
3935         * @param {Object} event
3936         * @param {OpenSeadragon.MouseTracker} event.eventSource
3937         *      A reference to the tracker instance.
3938         * @param {Number} event.keyCode
3939         *      The key code that was pressed.
3940         * @param {Boolean} event.ctrl
3941         *      True if the ctrl key was pressed during this event.
3942         * @param {Boolean} event.shift
3943         *      True if the shift key was pressed during this event.
3944         * @param {Boolean} event.alt
3945         *      True if the alt key was pressed during this event.
3946         * @param {Boolean} event.meta
3947         *      True if the meta key was pressed during this event.
3948         * @param {Object} event.originalEvent
3949         *      The original event object.
3950         * @param {Boolean} event.preventDefaultAction
3951         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3952         * @param {Object} event.userData
3953         *      Arbitrary user-defined object.
3954         */
3955        keyHandler: function () { },
3956
3957        /**
3958         * Implement or assign implementation to these handlers during or after
3959         * calling the constructor.
3960         * @function
3961         * @param {Object} event
3962         * @param {OpenSeadragon.MouseTracker} event.eventSource
3963         *      A reference to the tracker instance.
3964         * @param {Object} event.originalEvent
3965         *      The original event object.
3966         * @param {Boolean} event.preventDefaultAction
3967         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3968         * @param {Object} event.userData
3969         *      Arbitrary user-defined object.
3970         */
3971        focusHandler: function () { },
3972
3973        /**
3974         * Implement or assign implementation to these handlers during or after
3975         * calling the constructor.
3976         * @function
3977         * @param {Object} event
3978         * @param {OpenSeadragon.MouseTracker} event.eventSource
3979         *      A reference to the tracker instance.
3980         * @param {Object} event.originalEvent
3981         *      The original event object.
3982         * @param {Boolean} event.preventDefaultAction
3983         *      Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3984         * @param {Object} event.userData
3985         *      Arbitrary user-defined object.
3986         */
3987        blurHandler: function () { }
3988    };
3989
3990    /**
3991     * Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick")
3992     *
3993     * @private
3994     * @member resetAllMouseTrackers
3995     * @memberof OpenSeadragon.MouseTracker
3996     */
3997    $.MouseTracker.resetAllMouseTrackers = function(){
3998        for(var i = 0; i < MOUSETRACKERS.length; i++){
3999            if (MOUSETRACKERS[i].isTracking()){
4000                MOUSETRACKERS[i].setTracking(false);
4001                MOUSETRACKERS[i].setTracking(true);
4002            }
4003        }
4004    };
4005
4006    /**
4007     * Provides continuous computation of velocity (speed and direction) of active pointers.
4008     * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than
4009     * two active gesture pointers at a time.
4010     *
4011     * @private
4012     * @member gesturePointVelocityTracker
4013     * @memberof OpenSeadragon.MouseTracker
4014     */
4015    $.MouseTracker.gesturePointVelocityTracker = (function () {
4016        var trackerPoints = [],
4017            intervalId = 0,
4018            lastTime = 0;
4019
4020        // Generates a unique identifier for a tracked gesture point
4021        var _generateGuid = function ( tracker, gPoint ) {
4022            return tracker.hash.toString() + gPoint.type + gPoint.id.toString();
4023        };
4024
4025        // Interval timer callback. Computes velocity for all tracked gesture points.
4026        var _doTracking = function () {
4027            var i,
4028                len = trackerPoints.length,
4029                trackPoint,
4030                gPoint,
4031                now = $.now(),
4032                elapsedTime,
4033                distance,
4034                speed;
4035
4036            elapsedTime = now - lastTime;
4037            lastTime = now;
4038
4039            for ( i = 0; i < len; i++ ) {
4040                trackPoint = trackerPoints[ i ];
4041                gPoint = trackPoint.gPoint;
4042                // Math.atan2 gives us just what we need for a velocity vector, as we can simply
4043                //   use cos()/sin() to extract the x/y velocity components.
4044                gPoint.direction = Math.atan2( gPoint.currentPos.y - trackPoint.lastPos.y, gPoint.currentPos.x - trackPoint.lastPos.x );
4045                // speed = distance / elapsed time
4046                distance = trackPoint.lastPos.distanceTo( gPoint.currentPos );
4047                trackPoint.lastPos = gPoint.currentPos;
4048                speed = 1000 * distance / ( elapsedTime + 1 );
4049                // Simple biased average, favors the most recent speed computation. Smooths out erratic gestures a bit.
4050                gPoint.speed = 0.75 * speed + 0.25 * gPoint.speed;
4051            }
4052        };
4053
4054        // Public. Add a gesture point to be tracked
4055        var addPoint = function ( tracker, gPoint ) {
4056            var guid = _generateGuid( tracker, gPoint );
4057
4058            trackerPoints.push(
4059                {
4060                    guid: guid,
4061                    gPoint: gPoint,
4062                    lastPos: gPoint.currentPos
4063                } );
4064
4065            // Only fire up the interval timer when there's gesture pointers to track
4066            if ( trackerPoints.length === 1 ) {
4067                lastTime = $.now();
4068                intervalId = window.setInterval( _doTracking, 50 );
4069            }
4070        };
4071
4072        // Public. Stop tracking a gesture point
4073        var removePoint = function ( tracker, gPoint ) {
4074            var guid = _generateGuid( tracker, gPoint ),
4075                i,
4076                len = trackerPoints.length;
4077            for ( i = 0; i < len; i++ ) {
4078                if ( trackerPoints[ i ].guid === guid ) {
4079                    trackerPoints.splice( i, 1 );
4080                    // Only run the interval timer if theres gesture pointers to track
4081                    len--;
4082                    if ( len === 0 ) {
4083                        window.clearInterval( intervalId );
4084                    }
4085                    break;
4086                }
4087            }
4088        };
4089
4090        return {
4091            addPoint:    addPoint,
4092            removePoint: removePoint
4093        };
4094    } )();
4095
4096
4097///////////////////////////////////////////////////////////////////////////////
4098// Pointer event model and feature detection
4099///////////////////////////////////////////////////////////////////////////////
4100
4101    $.MouseTracker.captureElement = document;
4102
4103    /**
4104     * Detect available mouse wheel event name.
4105     */
4106    $.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) ||
4107                                                ( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
4108                                    document.onmousewheel !== undefined ? 'mousewheel' :                     // Webkit and IE support at least 'mousewheel'
4109                                    'DOMMouseScroll';                                                        // Assume old Firefox
4110
4111    /**
4112     * Detect legacy mouse capture support.
4113     */
4114    $.MouseTracker.supportsMouseCapture = (function () {
4115        var divElement = document.createElement( 'div' );
4116        return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture );
4117    }());
4118
4119    /**
4120     * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
4121     */
4122    $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
4123
4124    if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
4125        // Older Firefox
4126        $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
4127    }
4128
4129    // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec.
4130    if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) {
4131        // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
4132        $.MouseTracker.havePointerEvents = true;
4133        $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" );
4134        $.MouseTracker.unprefixedPointerEvents = true;
4135        if( navigator.maxTouchPoints ) {
4136            $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints;
4137        } else {
4138            $.MouseTracker.maxTouchPoints = 0;
4139        }
4140        $.MouseTracker.haveMouseEnter = false;
4141    } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) {
4142        // IE10
4143        $.MouseTracker.havePointerEvents = true;
4144        $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
4145        $.MouseTracker.unprefixedPointerEvents = false;
4146        if( navigator.msMaxTouchPoints ) {
4147            $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints;
4148        } else {
4149            $.MouseTracker.maxTouchPoints = 0;
4150        }
4151        $.MouseTracker.haveMouseEnter = false;
4152    } else {
4153        // Legacy W3C mouse events
4154        $.MouseTracker.havePointerEvents = false;
4155        if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
4156            $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
4157            $.MouseTracker.haveMouseEnter = true;
4158        } else {
4159            $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
4160            $.MouseTracker.haveMouseEnter = false;
4161        }
4162        $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
4163        if ( 'ontouchstart' in window ) {
4164            // iOS, Android, and other W3c Touch Event implementations
4165            //    (see http://www.w3.org/TR/touch-events/)
4166            //    (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
4167            //    (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
4168            $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
4169        }
4170        if ( 'ongesturestart' in window ) {
4171            // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
4172            //   Subscribe to these to prevent default gesture handling
4173            $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
4174        }
4175        $.MouseTracker.mousePointerId = "legacy-mouse";
4176        $.MouseTracker.maxTouchPoints = 10;
4177    }
4178
4179
4180///////////////////////////////////////////////////////////////////////////////
4181// Classes and typedefs
4182///////////////////////////////////////////////////////////////////////////////
4183
4184    /**
4185     * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device.
4186     *
4187     * @typedef {Object} GesturePoint
4188     * @memberof OpenSeadragon.MouseTracker
4189     *
4190     * @property {Number} id
4191     *     Identifier unique from all other active GesturePoints for a given pointer device.
4192     * @property {String} type
4193     *     The pointer device type: "mouse", "touch", "pen", etc.
4194     * @property {Boolean} captured
4195     *     True if events for the gesture point are captured to the tracked element.
4196     * @property {Boolean} isPrimary
4197     *     True if the gesture point is a master pointer amongst the set of active pointers for each pointer type. True for mouse and primary (first) touch/pen pointers.
4198     * @property {Boolean} insideElementPressed
4199     *     True if button pressed or contact point initiated inside the screen area of the tracked element.
4200     * @property {Boolean} insideElement
4201     *     True if pointer or contact point is currently inside the bounds of the tracked element.
4202     * @property {Number} speed
4203     *     Current computed speed, in pixels per second.
4204     * @property {Number} direction
4205     *     Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
4206     * @property {OpenSeadragon.Point} contactPos
4207     *     The initial pointer contact position, relative to the page including any scrolling. Only valid if the pointer has contact (pressed, touch contact, pen contact).
4208     * @property {Number} contactTime
4209     *     The initial pointer contact time, in milliseconds. Only valid if the pointer has contact (pressed, touch contact, pen contact).
4210     * @property {OpenSeadragon.Point} lastPos
4211     *     The last pointer position, relative to the page including any scrolling.
4212     * @property {Number} lastTime
4213     *     The last pointer contact time, in milliseconds.
4214     * @property {OpenSeadragon.Point} currentPos
4215     *     The current pointer position, relative to the page including any scrolling.
4216     * @property {Number} currentTime
4217     *     The current pointer contact time, in milliseconds.
4218     */
4219
4220
4221    /**
4222     * @class GesturePointList
4223     * @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type.
4224     *            Active pointers are any pointer being tracked for this element which are in the hit-test area
4225     *            of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
4226     * @memberof OpenSeadragon.MouseTracker
4227     * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
4228     */
4229    $.MouseTracker.GesturePointList = function ( type ) {
4230        this._gPoints = [];
4231        /**
4232         * The pointer device type: "mouse", "touch", "pen", etc.
4233         * @member {String} type
4234         * @memberof OpenSeadragon.MouseTracker.GesturePointList#
4235         */
4236        this.type = type;
4237        /**
4238         * Current buttons pressed for the device.
4239         * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
4240         * @member {Number} buttons
4241         * @memberof OpenSeadragon.MouseTracker.GesturePointList#
4242         */
4243        this.buttons = 0;
4244        /**
4245         * Current number of contact points (touch points, mouse down, etc.) for the device.
4246         * @member {Number} contacts
4247         * @memberof OpenSeadragon.MouseTracker.GesturePointList#
4248         */
4249        this.contacts = 0;
4250        /**
4251         * Current number of clicks for the device. Used for multiple click gesture tracking.
4252         * @member {Number} clicks
4253         * @memberof OpenSeadragon.MouseTracker.GesturePointList#
4254         */
4255        this.clicks = 0;
4256        /**
4257         * Current number of captured pointers for the device.
4258         * @member {Number} captureCount
4259         * @memberof OpenSeadragon.MouseTracker.GesturePointList#
4260         */
4261        this.captureCount = 0;
4262    };
4263
4264    /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */
4265    $.MouseTracker.GesturePointList.prototype = {
4266        /**
4267         * @function
4268         * @returns {Number} Number of gesture points in the list.
4269         */
4270        getLength: function () {
4271            return this._gPoints.length;
4272        },
4273        /**
4274         * @function
4275         * @returns {Array.<OpenSeadragon.MouseTracker.GesturePoint>} The list of gesture points in the list as an array (read-only).
4276         */
4277        asArray: function () {
4278            return this._gPoints;
4279        },
4280        /**
4281         * @function
4282         * @param {OpenSeadragon.MouseTracker.GesturePoint} gesturePoint - A gesture point to add to the list.
4283         * @returns {Number} Number of gesture points in the list.
4284         */
4285        add: function ( gp ) {
4286            return this._gPoints.push( gp );
4287        },
4288        /**
4289         * @function
4290         * @param {Number} id - The id of the gesture point to remove from the list.
4291         * @returns {Number} Number of gesture points in the list.
4292         */
4293        removeById: function ( id ) {
4294            var i,
4295                len = this._gPoints.length;
4296            for ( i = 0; i < len; i++ ) {
4297                if ( this._gPoints[ i ].id === id ) {
4298                    this._gPoints.splice( i, 1 );
4299                    break;
4300                }
4301            }
4302            return this._gPoints.length;
4303        },
4304        /**
4305         * @function
4306         * @param {Number} index - The index of the gesture point to retrieve from the list.
4307         * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point at the given index, or null if not found.
4308         */
4309        getByIndex: function ( index ) {
4310            if ( index < this._gPoints.length) {
4311                return this._gPoints[ index ];
4312            }
4313
4314            return null;
4315        },
4316        /**
4317         * @function
4318         * @param {Number} id - The id of the gesture point to retrieve from the list.
4319         * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point with the given id, or null if not found.
4320         */
4321        getById: function ( id ) {
4322            var i,
4323                len = this._gPoints.length;
4324            for ( i = 0; i < len; i++ ) {
4325                if ( this._gPoints[ i ].id === id ) {
4326                    return this._gPoints[ i ];
4327                }
4328            }
4329            return null;
4330        },
4331        /**
4332         * @function
4333         * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The primary gesture point in the list, or null if not found.
4334         */
4335        getPrimary: function ( id ) {
4336            var i,
4337                len = this._gPoints.length;
4338            for ( i = 0; i < len; i++ ) {
4339                if ( this._gPoints[ i ].isPrimary ) {
4340                    return this._gPoints[ i ];
4341                }
4342            }
4343            return null;
4344        },
4345
4346        /**
4347         * Increment this pointer's contact count.
4348         * It will evaluate whether this pointer type is allowed to have multiple contacts.
4349         * @function
4350         */
4351        addContact: function() {
4352            ++this.contacts;
4353
4354            if (this.contacts >
4354 1 && (this.type === "mouse" || this.type === "pen")) {
4355                this.contacts = 1;
4356            }
4357        },
4358
4359        /**
4360         * Decrement this pointer's contact count.
4361         * It will make sure the count does not go below 0.
4362         * @function
4363         */
4364        removeContact: function() {
4365            --this.contacts;
4366
4367            if (this.contacts < 0) {
4368                this.contacts = 0;
4369            }
4370        }
4371    };
4372
4373
4374///////////////////////////////////////////////////////////////////////////////
4375// Utility functions
4376///////////////////////////////////////////////////////////////////////////////
4377
4378    /**
4379     * Removes all tracked pointers.
4380     * @private
4381     * @inner
4382     */
4383    function clearTrackedPointers( tracker ) {
4384        var delegate = THIS[ tracker.hash ],
4385            i,
4386            pointerListCount = delegate.activePointersLists.length;
4387
4388        for ( i = 0; i < pointerListCount; i++ ) {
4389            if ( delegate.activePointersLists[ i ].captureCount > 0 ) {
4390                $.removeEvent(
4391                    $.MouseTracker.captureElement,
4392                    'mousemove',
4393                    delegate.mousemovecaptured,
4394                    true
4395                );
4396                $.removeEvent(
4397                    $.MouseTracker.captureElement,
4398                    'mouseup',
4399                    delegate.mouseupcaptured,
4400                    true
4401                );
4402                $.removeEvent(
4403                    $.MouseTracker.captureElement,
4404                    $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4405                    delegate.pointermovecaptured,
4406                    true
4407                );
4408                $.removeEvent(
4409                    $.MouseTracker.captureElement,
4410                    $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4411                    delegate.pointerupcaptured,
4412                    true
4413                );
4414                $.removeEvent(
4415                    $.MouseTracker.captureElement,
4416                    'touchmove',
4417                    delegate.touchmovecaptured,
4418                    true
4419                );
4420                $.removeEvent(
4421                    $.MouseTracker.captureElement,
4422                    'touchend',
4423                    delegate.touchendcaptured,
4424                    true
4425                );
4426
4427                delegate.activePointersLists[ i ].captureCount = 0;
4428            }
4429        }
4430
4431        for ( i = 0; i < pointerListCount; i++ ) {
4432            delegate.activePointersLists.pop();
4433        }
4434    }
4435
4436    /**
4437     * Starts tracking pointer events on the tracked element.
4438     * @private
4439     * @inner
4440     */
4441    function startTracking( tracker ) {
4442        var delegate = THIS[ tracker.hash ],
4443            event,
4444            i;
4445
4446        if ( !delegate.tracking ) {
4447            for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
4448                event = $.MouseTracker.subscribeEvents[ i ];
4449                $.addEvent(
4450                    tracker.element,
4451                    event,
4452                    delegate[ event ],
4453                    false
4454                );
4455            }
4456
4457            clearTrackedPointers( tracker );
4458
4459            delegate.tracking = true;
4460        }
4461    }
4462
4463    /**
4464     * Stops tracking pointer events on the tracked element.
4465     * @private
4466     * @inner
4467     */
4468    function stopTracking( tracker ) {
4469        var delegate = THIS[ tracker.hash ],
4470            event,
4471            i;
4472
4473        if ( delegate.tracking ) {
4474            for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
4475                event = $.MouseTracker.subscribeEvents[ i ];
4476                $.removeEvent(
4477                    tracker.element,
4478                    event,
4479                    delegate[ event ],
4480                    false
4481                );
4482            }
4483
4484            clearTrackedPointers( tracker );
4485
4486            delegate.tracking = false;
4487        }
4488    }
4489
4490    /**
4491     * @private
4492     * @inner
4493     */
4494    function getCaptureEventParams( tracker, pointerType ) {
4495        var delegate = THIS[ tracker.hash ];
4496
4497        if ( pointerType === 'pointerevent' ) {
4498            return {
4499                upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4500                upHandler: delegate.pointerupcaptured,
4501                moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4502                moveHandler: delegate.pointermovecaptured
4503            };
4504        } else if ( pointerType === 'mouse' ) {
4505            return {
4506                upName: 'mouseup',
4507                upHandler: delegate.mouseupcaptured,
4508                moveName: 'mousemove',
4509                moveHandler: delegate.mousemovecaptured
4510            };
4511        } else if ( pointerType === 'touch' ) {
4512            return {
4513                upName: 'touchend',
4514                upHandler: delegate.touchendcaptured,
4515                moveName: 'touchmove',
4516                moveHandler: delegate.touchmovecaptured
4517            };
4518        } else {
4519            throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." );
4520        }
4521    }
4522
4523    /**
4524     * Begin capturing pointer events to the tracked element.
4525     * @private
4526     * @inner
4527     */
4528    function capturePointer( tracker, pointerType, pointerCount ) {
4529        var pointsList = tracker.getActivePointersListByType( pointerType ),
4530            eventParams;
4531
4532        pointsList.captureCount += (pointerCount || 1);
4533
4534        if ( pointsList.captureCount === 1 ) {
4535            if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
4536                tracker.element.setCapture( true );
4537            } else {
4538                eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4539                // We emulate mouse capture by hanging listeners on the document object.
4540                //    (Note we listen on the capture phase so the captured handlers will get called first)
4541                // eslint-disable-next-line no-use-before-define
4542                if (isInIframe && canAccessEvents(window.top)) {
4543                    $.addEvent(
4544                        window.top,
4545                        eventParams.upName,
4546                        eventParams.upHandler,
4547                        true
4548                    );
4549                }
4550                $.addEvent(
4551                    $.MouseTracker.captureElement,
4552                    eventParams.upName,
4553                    eventParams.upHandler,
4554                    true
4555                );
4556                $.addEvent(
4557                    $.MouseTracker.captureElement,
4558                    eventParams.moveName,
4559                    eventParams.moveHandler,
4560                    true
4561                );
4562            }
4563        }
4564    }
4565
4566
4567    /**
4568     * Stop capturing pointer events to the tracked element.
4569     * @private
4570     * @inner
4571     */
4572    function releasePointer( tracker, pointerType, pointerCount ) {
4573        var pointsList = tracker.getActivePointersListByType( pointerType ),
4574            eventParams;
4575
4576        pointsList.captureCount -= (pointerCount || 1);
4577
4578        if ( pointsList.captureCount === 0 ) {
4579            if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
4580                tracker.element.releaseCapture();
4581            } else {
4582                eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4583                // We emulate mouse capture by hanging listeners on the document object.
4584                //    (Note we listen on the capture phase so the captured handlers will get called first)
4585                // eslint-disable-next-line no-use-before-define
4586                if (isInIframe && canAccessEvents(window.top)) {
4587                    $.removeEvent(
4588                        window.top,
4589                        eventParams.upName,
4590                        eventParams.upHandler,
4591                        true
4592                    );
4593                }
4594                $.removeEvent(
4595                    $.MouseTracker.captureElement,
4596                    eventParams.moveName,
4597                    eventParams.moveHandler,
4598                    true
4599                );
4600                $.removeEvent(
4601                    $.MouseTracker.captureElement,
4602                    eventParams.upName,
4603                    eventParams.upHandler,
4604                    true
4605                );
4606            }
4607        }
4608    }
4609
4610
4611    /**
4612     * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event.
4613     * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc.
4614     * @private
4615     * @inner
4616     */
4617    function getPointerType( event ) {
4618        var pointerTypeStr;
4619        if ( $.MouseTracker.unprefixedPointerEvents ) {
4620            pointerTypeStr = event.pointerType;
4621        } else {
4622            // IE10
4623            //  MSPOINTER_TYPE_TOUCH: 0x00000002
4624            //  MSPOINTER_TYPE_PEN:   0x00000003
4625            //  MSPOINTER_TYPE_MOUSE: 0x00000004
4626            switch( event.pointerType )
4627            {
4628                case 0x00000002:
4629                    pointerTypeStr = 'touch';
4630                    break;
4631                case 0x00000003:
4632                    pointerTypeStr = 'pen';
4633                    break;
4634                case 0x00000004:
4635                    pointerTypeStr = 'mouse';
4636                    break;
4637                default:
4638                    pointerTypeStr = '';
4639            }
4640        }
4641        return pointerTypeStr;
4642    }
4643
4644
4645    /**
4646     * @private
4647     * @inner
4648     */
4649    function getMouseAbsolute( event ) {
4650        return $.getMousePosition( event );
4651    }
4652
4653    /**
4654     * @private
4655     * @inner
4656     */
4657    function getMouseRelative( event, element ) {
4658        return getPointRelativeToAbsolute( getMouseAbsolute( event ), element );
4659    }
4660
4661    /**
4662     * @private
4663     * @inner
4664     */
4665    function getPointRelativeToAbsolute( point, element ) {
4666        var offset = $.getElementOffset( element );
4667        return point.minus( offset );
4668    }
4669
4670    /**
4671     * @private
4672     * @inner
4673     */
4674    function getCenterPoint( point1, point2 ) {
4675        return new $.Point( ( point1.x + point2.x ) / 2, ( point1.y + point2.y ) / 2 );
4676    }
4677
4678
4679///////////////////////////////////////////////////////////////////////////////
4680// Device-specific DOM event handlers
4681///////////////////////////////////////////////////////////////////////////////
4682
4683    /**
4684     * @private
4685     * @inner
4686     */
4687    function onClick( tracker, event ) {
4688        if ( tracker.clickHandler ) {
4689            $.cancelEvent( event );
4690        }
4691    }
4692
4693
4694    /**
4695     * @private
4696     * @inner
4697     */
4698    function onDblClick( tracker, event ) {
4699        if ( tracker.dblClickHandler ) {
4700            $.cancelEvent( event );
4701        }
4702    }
4703
4704
4705    /**
4706     * @private
4707     * @inner
4708     */
4709    function onKeyDown( tracker, event ) {
4710        //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4711        var propagate;
4712        if ( tracker.keyDownHandler ) {
4713            event = $.getEvent( event );
4714            propagate = tracker.keyDownHandler(
4715                {
4716                    eventSource:          tracker,
4717                    keyCode:              event.keyCode ? event.keyCode : event.charCode,
4718                    ctrl:                 event.ctrlKey,
4719                    shift:                event.shiftKey,
4720                    alt:                  event.altKey,
4721                    meta:                 event.metaKey,
4722                    originalEvent:        event,
4723                    preventDefaultAction: false,
4724                    userData:             tracker.userData
4725                }
4726            );
4727            if ( !propagate ) {
4728                $.cancelEvent( event );
4729            }
4730        }
4731    }
4732
4733
4734    /**
4735     * @private
4736     * @inner
4737     */
4738    function onKeyUp( tracker, event ) {
4739        //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4740        var propagate;
4741        if ( tracker.keyUpHandler ) {
4742            event = $.getEvent( event );
4743            propagate = tracker.keyUpHandler(
4744                {
4745                    eventSource:          tracker,
4746                    keyCode:              event.keyCode ? event.keyCode : event.charCode,
4747                    ctrl:                 event.ctrlKey,
4748                    shift:                event.shiftKey,
4749                    alt:                  event.altKey,
4750                    meta:                 event.metaKey,
4751                    originalEvent:        event,
4752                    preventDefaultAction: false,
4753                    userData:             tracker.userData
4754                }
4755            );
4756            if ( !propagate ) {
4757                $.cancelEvent( event );
4758            }
4759        }
4760    }
4761
4762
4763    /**
4764     * @private
4765     * @inner
4766     */
4767    function onKeyPress( tracker, event ) {
4768        //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4769        var propagate;
4770        if ( tracker.keyHandler ) {
4771            event = $.getEvent( event );
4772            propagate = tracker.keyHandler(
4773                {
4774                    eventSource:          tracker,
4775                    keyCode:              event.keyCode ? event.keyCode : event.charCode,
4776                    ctrl:                 event.ctrlKey,
4777                    shift:                event.shiftKey,
4778                    alt:                  event.altKey,
4779                    meta:                 event.metaKey,
4780                    originalEvent:        event,
4781                    preventDefaultAction: false,
4782                    userData:             tracker.userData
4783                }
4784            );
4785            if ( !propagate ) {
4786                $.cancelEvent( event );
4787            }
4788        }
4789    }
4790
4791
4792    /**
4793     * @private
4794     * @inner
4795     */
4796    function onFocus( tracker, event ) {
4797        //console.log( "focus %s", event );
4798        var propagate;
4799        if ( tracker.focusHandler ) {
4800            event = $.getEvent( event );
4801            propagate = tracker.focusHandler(
4802                {
4803                    eventSource:          tracker,
4804                    originalEvent:        event,
4805                    preventDefaultAction: false,
4806                    userData:             tracker.userData
4807                }
4808            );
4809            if ( propagate === false ) {
4810                $.cancelEvent( event );
4811            }
4812        }
4813    }
4814
4815
4816    /**
4817     * @private
4818     * @inner
4819     */
4820    function onBlur( tracker, event ) {
4821        //console.log( "blur %s", event );
4822        var propagate;
4823        if ( tracker.blurHandler ) {
4824            event = $.getEvent( event );
4825            propagate = tracker.blurHandler(
4826                {
4827                    eventSource:          tracker,
4828                    originalEvent:        event,
4829                    preventDefaultAction: false,
4830                    userData:             tracker.userData
4831                }
4832            );
4833            if ( propagate === false ) {
4834                $.cancelEvent( event );
4835            }
4836        }
4837    }
4838
4839
4840    /**
4841     * Handler for 'wheel' events
4842     *
4843     * @private
4844     * @inner
4845     */
4846    function onWheel( tracker, event ) {
4847        handleWheelEvent( tracker, event, event );
4848    }
4849
4850
4851    /**
4852     * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events
4853     *
4854     * @private
4855     * @inner
4856     */
4857    function onMouseWheel( tracker, event ) {
4858        event = $.getEvent( event );
4859
4860        // Simulate a 'wheel' event
4861        var simulatedEvent = {
4862            target:     event.target || event.srcElement,
4863            type:       "wheel",
4864            shiftKey:   event.shiftKey || false,
4865            clientX:    event.clientX,
4866            clientY:    event.clientY,
4867            pageX:      event.pageX ? event.pageX : event.clientX,
4868            pageY:      event.pageY ? event.pageY : event.clientY,
4869            deltaMode:  event.type == "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page
4870            deltaX:     0,
4871            deltaZ:     0
4872        };
4873
4874        // Calculate deltaY
4875        if ( $.MouseTracker.wheelEventName == "mousewheel" ) {
4876            simulatedEvent.deltaY = -event.wheelDelta / $.DEFAULT_SETTINGS.pixelsPerWheelLine;
4877        } else {
4878            simulatedEvent.deltaY = event.detail;
4879        }
4880
4881        handleWheelEvent( tracker, simulatedEvent, event );
4882    }
4883
4884
4885    /**
4886     * Handles 'wheel' events.
4887     * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
4888     *
4889     * @private
4890     * @inner
4891     */
4892    function handleWheelEvent( tracker, event, originalEvent ) {
4893        var nDelta = 0,
4894            propagate;
4895
4896        // The nDelta variable is gated to provide smooth z-index scrolling
4897        //   since the mouse wheel allows for substantial deltas meant for rapid
4898        //   y-index scrolling.
4899        // event.deltaMode: 0=pixel, 1=line, 2=page
4900        // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached
4901        nDelta = event.deltaY < 0 ? 1 : -1;
4902
4903        if ( tracker.scrollHandler ) {
4904            propagate = tracker.scrollHandler(
4905                {
4906                    eventSource:          tracker,
4907                    pointerType:          'mouse',
4908                    position:             getMouseRelative( event, tracker.element ),
4909                    scroll:               nDelta,
4910                    shift:                event.shiftKey,
4911                    isTouchEvent:         false,
4912                    originalEvent:        originalEvent,
4913                    preventDefaultAction: false,
4914                    userData:             tracker.userData
4915                }
4916            );
4917            if ( propagate === false ) {
4918                $.cancelEvent( originalEvent );
4919            }
4920        }
4921    }
4922
4923
4924    /**
4925     * @private
4926     * @inner
4927     */
4928    function isParentChild( parent, child )
4929    {
4930       if ( parent === child ) {
4931           return false;
4932       }
4933       while ( child && child !== parent ) {
4934           child = child.parentNode;
4935       }
4936       return child === parent;
4937    }
4938
4939
4940    /**
4941     * Only used on IE 8
4942     *
4943     * @private
4944     * @inner
4945     */
4946    function onMouseEnter( tracker, event ) {
4947        event = $.getEvent( event );
4948
4949        handleMouseEnter( tracker, event );
4950    }
4951
4952
4953    /**
4954     * @private
4955     * @inner
4956     */
4957    function onMouseOver( tracker, event ) {
4958        event = $.getEvent( event );
4959
4960        if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
4961            return;
4962        }
4963
4964        handleMouseEnter( tracker, event );
4965    }
4966
4967
4968    /**
4969     * @private
4970     * @inner
4971     */
4972    function handleMouseEnter( tracker, event ) {
4973        var gPoint = {
4974            id: $.MouseTracker.mousePointerId,
4975            type: 'mouse',
4976            isPrimary: true,
4977            currentPos: getMouseAbsolute( event ),
4978            currentTime: $.now()
4979        };
4980
4981        updatePointersEnter( tracker, event, [ gPoint ] );
4982    }
4983
4984
4985    /**
4986     * Only used on IE 8
4987     *
4988     * @private
4989     * @inner
4990     */
4991    function onMouseLeave( tracker, event ) {
4992        event = $.getEvent( event );
4993
4994        handleMouseExit( tracker, event );
4995    }
4996
4997
4998    /**
4999     * @private
5000     * @inner
5001     */
5002    function onMouseOut( tracker, event ) {
5003        event = $.getEvent( event );
5004
5005        if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5006            return;
5007        }
5008
5009        handleMouseExit( tracker, event );
5010    }
5011
5012
5013    /**
5014     * @private
5015     * @inner
5016     */
5017    function handleMouseExit( tracker, event ) {
5018        var gPoint = {
5019            id: $.MouseTracker.mousePointerId,
5020            type: 'mouse',
5021            isPrimary: true,
5022            currentPos: getMouseAbsolute( event ),
5023            currentTime: $.now()
5024        };
5025
5026        updatePointersExit( tracker, event, [ gPoint ] );
5027    }
5028
5029
5030    /**
5031     * Returns a W3C DOM level 3 standard button value given an event.button property:
5032     *   -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen)
5033     * @private
5034     * @inner
5035     */
5036    function getStandardizedButton( button ) {
5037        if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5038            // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three
5039            // TODO: Support chorded (multiple) button presses on IE 8?
5040            if ( button === 1 ) {
5041                return 0;
5042            } else if ( button === 2 ) {
5043                return 2;
5044            } else if ( button === 4 ) {
5045                return 1;
5046            } else {
5047                return -1;
5048            }
5049        } else {
5050            return button;
5051        }
5052    }
5053
5054
5055    /**
5056     * @private
5057     * @inner
5058     */
5059    function onMouseDown( tracker, event ) {
5060        var gPoint;
5061
5062        event = $.getEvent( event );
5063
5064        gPoint = {
5065            id: $.MouseTracker.mousePointerId,
5066            type: 'mouse',
5067            isPrimary: true,
5068            currentPos: getMouseAbsolute( event ),
5069            currentTime: $.now()
5070        };
5071
5072        if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
5073            $.stopEvent( event );
5074            capturePointer( tracker, 'mouse' );
5075        }
5076
5077        if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
5078            $.cancelEvent( event );
5079        }
5080    }
5081
5082
5083    /**
5084     * @private
5085     * @inner
5086     */
5087    function onMouseUp( tracker, event ) {
5088        handleMouseUp( tracker, event );
5089    }
5090
5091    /**
5092     * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5093     * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice.
5094     *
5095     * @private
5096     * @inner
5097     */
5098    function onMouseUpCaptured( tracker, event ) {
5099        handleMouseUp( tracker, event );
5100        $.stopEvent( event );
5101    }
5102
5103
5104    /**
5105     * @private
5106     * @inner
5107     */
5108    function handleMouseUp( tracker, event ) {
5109        var gPoint;
5110
5111        event = $.getEvent( event );
5112
5113        gPoint = {
5114            id: $.MouseTracker.mousePointerId,
5115            type: 'mouse',
5116            isPrimary: true,
5117            currentPos: getMouseAbsolute( event ),
5118            currentTime: $.now()
5119        };
5120
5121        if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
5122            releasePointer( tracker, 'mouse' );
5123        }
5124    }
5125
5126
5127    /**
5128     * @private
5129     * @inner
5130     */
5131    function onMouseMove( tracker, event ) {
5132        handleMouseMove( tracker, event );
5133   }
5134
5135
5136    /**
5137     * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5138     * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5139     *
5140     * @private
5141     * @inner
5142     */
5143    function onMouseMoveCaptured( tracker, event ) {
5144        handleMouseMove( tracker, event );
5145        $.stopEvent( event );
5146    }
5147
5148
5149    /**
5150     * @private
5151     * @inner
5152     */
5153    function handleMouseMove( tracker, event ) {
5154        var gPoint;
5155
5156        event = $.getEvent( event );
5157
5158        gPoint = {
5159            id: $.MouseTracker.mousePointerId,
5160            type: 'mouse',
5161            isPrimary: true,
5162            currentPos: getMouseAbsolute( event ),
5163            currentTime: $.now()
5164        };
5165
5166        updatePointersMove( tracker, event, [ gPoint ] );
5167    }
5168
5169
5170    /**
5171     * @private
5172     * @inner
5173     */
5174    function abortContacts( tracker, event, pointsList ) {
5175        var i,
5176            gPointCount = pointsList.getLength(),
5177            abortGPoints = [];
5178
5179        // Check contact count for hoverable pointer types before aborting
5180        if (pointsList.type === 'touch' || pointsList.contacts > 0) {
5181            for ( i = 0; i < gPointCount; i++ ) {
5182                abortGPoints.push( pointsList.getByIndex( i ) );
5183            }
5184
5185            if ( abortGPoints.length > 0 ) {
5186                // simulate touchend/mouseup
5187                updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
5188                // release pointer capture
5189                pointsList.captureCount = 1;
5190                releasePointer( tracker, pointsList.type );
5191                // simulate touchleave/mouseout
5192                updatePointersExit( tracker, event, abortGPoints );
5193            }
5194        }
5195    }
5196
5197
5198    /**
5199     * @private
5200     * @inner
5201     */
5202    function onTouchStart( tracker, event ) {
5203        var time,
5204            i,
5205            j,
5206            touchCount = event.changedTouches.length,
5207            gPoints = [],
5208            parentGPoints,
5209            pointsList = tracker.getActivePointersListByType( 'touch' );
5210
5211        time = $.now();
5212
5213        if ( pointsList.getLength() > event.touches.length - touchCount ) {
5214            $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
5215            abortContacts( tracker, event, pointsList );
5216        }
5217
5218        for ( i = 0; i < touchCount; i++ ) {
5219            gPoints.push( {
5220                id: event.changedTouches[ i ].identifier,
5221                type: 'touch',
5222                // isPrimary not set - let the updatePointers functions determine it
5223                currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
5224                currentTime: time
5225            } );
5226        }
5227
5228        // simulate touchenter on our tracked element
5229        updatePointersEnter( tracker, event, gPoints );
5230
5231        // simulate touchenter on our tracked element's tracked ancestor elements
5232        for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
5233            if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
5234                parentGPoints = [];
5235                for ( j = 0; j < touchCount; j++ ) {
5236                    parentGPoints.push( {
5237                        id: event.changedTouches[ j ].identifier,
5238                        type: 'touch',
5239                        // isPrimary not set - let the updatePointers functions determine it
5240                        currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
5241                        currentTime: time
5242                    } );
5243                }
5244                updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints );
5245            }
5246        }
5247
5248        if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
5249            $.stopEvent( event );
5250            capturePointer( tracker, 'touch', touchCount );
5251        }
5252
5253        $.cancelEvent( event );
5254    }
5255
5256
5257    /**
5258     * @private
5259     * @inner
5260     */
5261    function onTouchEnd( tracker, event ) {
5262        handleTouchEnd( tracker, event );
5263    }
5264
5265
5266    /**
5267     * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
5268     * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice.
5269     *
5270     * @private
5271     * @inner
5272     */
5273    function onTouchEndCaptured( tracker, event ) {
5274        handleTouchEnd( tracker, event );
5275        $.stopEvent( event );
5276    }
5277
5278
5279    /**
5280     * @private
5281     * @inner
5282     */
5283    function handleTouchEnd( tracker, event ) {
5284        var time,
5285            i,
5286            j,
5287            touchCount = event.changedTouches.length,
5288            gPoints = [],
5289            parentGPoints;
5290
5291        time = $.now();
5292
5293        for ( i = 0; i < touchCount; i++ ) {
5294            gPoints.push( {
5295                id: event.changedTouches[ i ].identifier,
5296                type: 'touch',
5297                // isPrimary not set - let the updatePointers functions determine it
5298                currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
5299                currentTime: time
5300            } );
5301        }
5302
5303        if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
5304            releasePointer( tracker, 'touch', touchCount );
5305        }
5306
5307        // simulate touchleave on our tracked element
5308        updatePointersExit( tracker, event, gPoints );
5309
5310        // simulate touchleave on our tracked element's tracked ancestor elements
5311        for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
5312            if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
5313                parentGPoints = [];
5314                for ( j = 0; j < touchCount; j++ ) {
5315                    parentGPoints.push( {
5316                        id: event.changedTouches[ j ].identifier,
5317                        type: 'touch',
5318                        // isPrimary not set - let the updatePointers functions determine it
5319                        currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
5320                        currentTime: time
5321                    } );
5322                }
5323                updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints );
5324            }
5325        }
5326
5327        $.cancelEvent( event );
5328    }
5329
5330
5331    /**
5332     * @private
5333     * @inner
5334     */
5335    function onTouchMove( tracker, event ) {
5336        handleTouchMove( tracker, event );
5337    }
5338
5339
5340    /**
5341     * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
5342     * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5343     *
5344     * @private
5345     * @inner
5346     */
5347    function onTouchMoveCaptured( tracker, event ) {
5348        handleTouchMove( tracker, event );
5349        $.stopEvent( event );
5350    }
5351
5352
5353    /**
5354     * @private
5355     * @inner
5356     */
5357    function handleTouchMove( tracker, event ) {
5358        var i,
5359            touchCount = event.changedTouches.length,
5360            gPoints = [];
5361
5362        for ( i = 0; i < touchCount; i++ ) {
5363            gPoints.push( {
5364                id: event.changedTouches[ i ].identifier,
5365                type: 'touch',
5366                // isPrimary not set - let the updatePointers functions determine it
5367                currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
5368                currentTime: $.now()
5369            } );
5370        }
5371
5372        updatePointersMove( tracker, event, gPoints );
5373
5374        $.cancelEvent( event );
5375    }
5376
5377
5378    /**
5379     * @private
5380     * @inner
5381     */
5382    function onTouchCancel( tracker, event ) {
5383        var pointsList = tracker.getActivePointersListByType('touch');
5384
5385        abortContacts( tracker, event, pointsList );
5386    }
5387
5388
5389    /**
5390     * @private
5391     * @inner
5392     */
5393    function onGestureStart( tracker, event ) {
5394        event.stopPropagation();
5395        event.preventDefault();
5396        return false;
5397    }
5398
5399
5400    /**
5401     * @private
5402     * @inner
5403     */
5404    function onGestureChange( tracker, event ) {
5405        event.stopPropagation();
5406        event.preventDefault();
5407        return false;
5408    }
5409
5410
5411    /**
5412     * @private
5413     * @inner
5414     */
5415    function onPointerOver( tracker, event ) {
5416        var gPoint;
5417
5418        if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5419            return;
5420        }
5421
5422        gPoint = {
5423            id: event.pointerId,
5424            type: getPointerType( event ),
5425            isPrimary: event.isPrimary,
5426            currentPos: getMouseAbsolute( event ),
5427            currentTime: $.now()
5428        };
5429
5430        updatePointersEnter( tracker, event, [ gPoint ] );
5431    }
5432
5433
5434    /**
5435     * @private
5436     * @inner
5437     */
5438    function onPointerOut( tracker, event ) {
5439        var gPoint;
5440
5441        if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5442            return;
5443        }
5444
5445        gPoint = {
5446            id: event.pointerId,
5447            type: getPointerType( event ),
5448            isPrimary: event.isPrimary,
5449            currentPos: getMouseAbsolute( event ),
5450            currentTime: $.now()
5451        };
5452
5453        updatePointersExit( tracker, event, [ gPoint ] );
5454    }
5455
5456
5457    /**
5458     * @private
5459     * @inner
5460     */
5461    function onPointerDown( tracker, event ) {
5462        var gPoint;
5463
5464        gPoint = {
5465            id: event.pointerId,
5466            type: getPointerType( event ),
5467            isPrimary: event.isPrimary,
5468            currentPos: getMouseAbsolute( event ),
5469            currentTime: $.now()
5470        };
5471
5472        if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
5473            $.stopEvent( event );
5474            capturePointer( tracker, gPoint.type );
5475        }
5476
5477        if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5478            $.cancelEvent( event );
5479        }
5480    }
5481
5482
5483    /**
5484     * @private
5485     * @inner
5486     */
5487    function onPointerUp( tracker, event ) {
5488        handlePointerUp( tracker, event );
5489    }
5490
5491
5492    /**
5493     * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5494     * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice.
5495     *
5496     * @private
5497     * @inner
5498     */
5499    function onPointerUpCaptured( tracker, event ) {
5500        var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5501        if ( pointsList.getById( event.pointerId ) ) {
5502            handlePointerUp( tracker, event );
5503        }
5504        $.stopEvent( event );
5505    }
5506
5507
5508    /**
5509     * @private
5510     * @inner
5511     */
5512    function handlePointerUp( tracker, event ) {
5513        var gPoint;
5514
5515        gPoint = {
5516            id: event.pointerId,
5517            type: getPointerType( event ),
5518            isPrimary: event.isPrimary,
5519            currentPos: getMouseAbsolute( event ),
5520            currentTime: $.now()
5521        };
5522
5523        if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
5524            releasePointer( tracker, gPoint.type );
5525        }
5526    }
5527
5528
5529    /**
5530     * @private
5531     * @inner
5532     */
5533    function onPointerMove( tracker, event ) {
5534        handlePointerMove( tracker, event );
5535    }
5536
5537
5538    /**
5539     * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5540     * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5541     *
5542     * @private
5543     * @inner
5544     */
5545    function onPointerMoveCaptured( tracker, event ) {
5546        var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5547        if ( pointsList.getById( event.pointerId ) ) {
5548            handlePointerMove( tracker, event );
5549        }
5550        $.stopEvent( event );
5551    }
5552
5553
5554    /**
5555     * @private
5556     * @inner
5557     */
5558    function handlePointerMove( tracker, event ) {
5559        // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
5560        var gPoint;
5561
5562        gPoint = {
5563            id: event.pointerId,
5564            type: getPointerType( event ),
5565            isPrimary: event.isPrimary,
5566            currentPos: getMouseAbsolute( event ),
5567            currentTime: $.now()
5568        };
5569
5570        updatePointersMove( tracker, event, [ gPoint ] );
5571    }
5572
5573
5574    /**
5575     * @private
5576     * @inner
5577     */
5578    function onPointerCancel( tracker, event ) {
5579        var gPoint;
5580
5581        gPoint = {
5582            id: event.pointerId,
5583            type: getPointerType( event )
5584        };
5585
5586        updatePointersCancel( tracker, event, [ gPoint ] );
5587    }
5588
5589
5590///////////////////////////////////////////////////////////////////////////////
5591// Device-agnostic DOM event handlers
5592///////////////////////////////////////////////////////////////////////////////
5593
5594    /**
5595     * @function
5596     * @private
5597     * @inner
5598     * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
5599     *     The GesturePointList to track the pointer in.
5600     * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
5601     *      Gesture point to track.
5602     * @returns {Number} Number of gesture points in pointsList.
5603     */
5604    function startTrackingPointer( pointsList, gPoint ) {
5605
5606        // If isPrimary is not known for the pointer then set it according to our rules:
5607        //    true if the first pointer in the gesture, otherwise false
5608        if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) {
5609            if ( pointsList.getLength() === 0 ) {
5610                gPoint.isPrimary = true;
5611            } else {
5612                gPoint.isPrimary = false;
5613            }
5614        }
5615        gPoint.speed = 0;
5616        gPoint.direction = 0;
5617        gPoint.contactPos = gPoint.currentPos;
5618        gPoint.contactTime = gPoint.currentTime;
5619        gPoint.lastPos = gPoint.currentPos;
5620        gPoint.lastTime = gPoint.currentTime;
5621
5622        return pointsList.add( gPoint );
5623    }
5624
5625
5626    /**
5627     * @function
5628     * @private
5629     * @inner
5630     * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
5631     *     The GesturePointList to stop tracking the pointer on.
5632     * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
5633     *      Gesture point to stop tracking.
5634     * @returns {Number} Number of gesture points in pointsList.
5635     */
5636    function stopTrackingPointer( pointsList, gPoint ) {
5637        var listLength,
5638            primaryPoint;
5639
5640        if ( pointsList.getById( gPoint.id ) ) {
5641            listLength = pointsList.removeById( gPoint.id );
5642
5643            // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary
5644            if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) {
5645                primaryPoint = pointsList.getPrimary();
5646                if ( !primaryPoint ) {
5647                    primaryPoint = pointsList.getByIndex( 0 );
5648                    if ( primaryPoint ) {
5649                        primaryPoint.isPrimary = true;
5650                    }
5651                }
5652            }
5653        } else {
5654            listLength = pointsList.getLength();
5655        }
5656
5657        return listLength;
5658    }
5659
5660
5661    /**
5662     * @function
5663     * @private
5664     * @inner
5665     * @param {OpenSeadragon.MouseTracker} tracker
5666     *     A reference to the MouseTracker instance.
5667     * @param {Object} event
5668     *     A reference to the originating DOM event.
5669     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5670     *      Gesture points associated with the event.
5671     */
5672    function updatePointersEnter( tracker, event, gPoints ) {
5673        var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
5674            i,
5675            gPointCount = gPoints.length,
5676            curGPoint,
5677            updateGPoint,
5678            propagate;
5679
5680        for ( i = 0; i < gPointCount; i++ ) {
5681            curGPoint = gPoints[ i ];
5682            updateGPoint = pointsList.getById( curGPoint.id );
5683
5684            if ( updateGPoint ) {
5685                // Already tracking the pointer...update it
5686                updateGPoint.insideElement = true;
5687                updateGPoint.lastPos = updateGPoint.currentPos;
5688                updateGPoint.lastTime = updateGPoint.currentTime;
5689                updateGPoint.currentPos = curGPoint.currentPos;
5690                updateGPoint.currentTime = curGPoint.currentTime;
5691
5692                curGPoint = updateGPoint;
5693            } else {
5694                // Initialize for tracking and add to the tracking list
5695                curGPoint.captured = false;
5696                curGPoint.insideElementPressed = false;
5697                curGPoint.insideElement = true;
5698                startTrackingPointer( pointsList, curGPoint );
5699            }
5700
5701            // Enter
5702            if ( tracker.enterHandler ) {
5703                propagate = tracker.enterHandler(
5704                    {
5705                        eventSource:          tracker,
5706                        pointerType:          curGPoint.type,
5707                        position:             getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
5708                        buttons:              pointsList.buttons,
5709                        pointers:             tracker.getActivePointerCount(),
5710                        insideElementPressed: curGPoint.insideElementPressed,
5711                        buttonDownAny:        pointsList.buttons !== 0,
5712                        isTouchEvent:         curGPoint.type === 'touch',
5713                        originalEvent:        event,
5714                        preventDefaultAction: false,
5715                        userData:             tracker.userData
5716                    }
5717                );
5718                if ( propagate === false ) {
5719                    $.cancelEvent( event );
5720                }
5721            }
5722        }
5723    }
5724
5725
5726    /**
5727     * @function
5728     * @private
5729     * @inner
5730     * @param {OpenSeadragon.MouseTracker} tracker
5731     *     A reference to the MouseTracker instance.
5732     * @param {Object} event
5733     *     A reference to the originating DOM event.
5734     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5735     *      Gesture points associated with the event.
5736     */
5737    function updatePointersExit( tracker, event, gPoints ) {
5738        var pointsList = tracker.getActivePointersListByType(gPoints[0].type),
5739            i,
5740            gPointCount = gPoints.length,
5741            curGPoint,
5742            updateGPoint,
5743            propagate;
5744
5745        for ( i = 0; i < gPointCount; i++ ) {
5746            curGPoint = gPoints[ i ];
5747            updateGPoint = pointsList.getById( curGPoint.id );
5748
5749            if ( updateGPoint ) {
5750                // Already tracking the pointer. If captured then update it, else stop tracking it
5751                if ( updateGPoint.captured ) {
5752                    updateGPoint.insideElement = false;
5753                    updateGPoint.lastPos = updateGPoint.currentPos;
5754                    updateGPoint.lastTime = updateGPoint.currentTime;
5755                    updateGPoint.currentPos = curGPoint.currentPos;
5756                    updateGPoint.currentTime = curGPoint.currentTime;
5757                } else {
5758                    stopTrackingPointer( pointsList, updateGPoint );
5759                }
5760
5761                curGPoint = updateGPoint;
5762            }
5763
5764            // Exit
5765            if ( tracker.exitHandler ) {
5766                propagate = tracker.exitHandler(
5767                    {
5768                        eventSource:          tracker,
5769                        pointerType:          curGPoint.type,
5770                        position:             curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
5771                        buttons:              pointsList.buttons,
5772                        pointers:             tracker.getActivePointerCount(),
5773                        insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
5774                        buttonDownAny:        pointsList.buttons !== 0,
5775                        isTouchEvent:         curGPoint.type === 'touch',
5776                        originalEvent:        event,
5777                        preventDefaultAction: false,
5778                        userData:             tracker.userData
5779                    }
5780                );
5781
5782                if ( propagate === false ) {
5783                    $.cancelEvent( event );
5784                }
5785            }
5786        }
5787    }
5788
5789
5790    /**
5791     * @function
5792     * @private
5793     * @inner
5794     * @param {OpenSeadragon.MouseTracker} tracker
5795     *     A reference to the MouseTracker instance.
5796     * @param {Object} event
5797     *     A reference to the originating DOM event.
5798     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5799     *      Gesture points associated with the event.
5800     * @param {Number} buttonChanged
5801     *      The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
5802     *      Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
5803     *      only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
5804     *
5805     * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
5806     */
5807    function updatePointersDown( tracker, event, gPoints, buttonChanged ) {
5808        var delegate = THIS[ tracker.hash ],
5809            propagate,
5810            pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
5811            i,
5812            gPointCount = gPoints.length,
5813            curGPoint,
5814            updateGPoint;
5815
5816        if ( typeof event.buttons !== 'undefined' ) {
5817            pointsList.buttons = event.buttons;
5818        } else {
5819            if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5820                if ( buttonChanged === 0 ) {
5821                    // Primary
5822                    pointsList.buttons += 1;
5823                } else if ( buttonChanged === 1 ) {
5824                    // Aux
5825                    pointsList.buttons += 4;
5826                } else if ( buttonChanged === 2 ) {
5827                    // Secondary
5828                    pointsList.buttons += 2;
5829                } else if ( buttonChanged === 3 ) {
5830                    // X1 (Back)
5831                    pointsList.buttons += 8;
5832                } else if ( buttonChanged === 4 ) {
5833                    // X2 (Forward)
5834                    pointsList.buttons += 16;
5835                } else if ( buttonChanged === 5 ) {
5836                    // Pen Eraser
5837                    pointsList.buttons += 32;
5838                }
5839            } else {
5840                if ( buttonChanged === 0 ) {
5841                    // Primary
5842                    pointsList.buttons |= 1;
5843                } else if ( buttonChanged === 1 ) {
5844                    // Aux
5845                    pointsList.buttons |= 4;
5846                } else if ( buttonChanged === 2 ) {
5847                    // Secondary
5848                    pointsList.buttons |= 2;
5849                } else if ( buttonChanged === 3 ) {
5850                    // X1 (Back)
5851                    pointsList.buttons |= 8;
5852                } else if ( buttonChanged === 4 ) {
5853                    // X2 (Forward)
5854                    pointsList.buttons |= 16;
5855                } else if ( buttonChanged === 5 ) {
5856                    // Pen Eraser
5857                    pointsList.buttons |= 32;
5858                }
5859            }
5860        }
5861
5862        // Some pointers may steal control from another pointer without firing the appropriate release events
5863        // e.g. Touching a screen while click-dragging with certain mice.
5864        var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type);
5865        for (i = 0; i < otherPointsLists.length; i++) {
5866            //If another pointer has contact, simulate the release
5867            abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer
5868        }
5869
5870        // Only capture and track primary button, pen, and touch contacts
5871        if ( buttonChanged !== 0 ) {
5872            // Aux Press
5873            if ( tracker.nonPrimaryPressHandler ) {
5874                propagate = tracker.nonPrimaryPressHandler(
5875                    {
5876                        eventSource:          tracker,
5877                        pointerType:          gPoints[ 0 ].type,
5878                        position:             getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5879                        button:               buttonChanged,
5880                        buttons:              pointsList.buttons,
5881                        isTouchEvent:         gPoints[ 0 ].type === 'touch',
5882                        originalEvent:        event,
5883                        preventDefaultAction: false,
5884                        userData:             tracker.userData
5885                    }
5886                );
5887                if ( propagate === false ) {
5888                    $.cancelEvent( event );
5889                }
5890            }
5891
5892            return false;
5893        }
5894
5895        for ( i = 0; i < gPointCount; i++ ) {
5896            curGPoint = gPoints[ i ];
5897            updateGPoint = pointsList.getById( curGPoint.id );
5898
5899            if ( updateGPoint ) {
5900                // Already tracking the pointer...update it
5901                updateGPoint.captured = true;
5902                updateGPoint.insideElementPressed = true;
5903                updateGPoint.insideElement = true;
5904                updateGPoint.contactPos = curGPoint.currentPos;
5905                updateGPoint.contactTime = curGPoint.currentTime;
5906                updateGPoint.lastPos = updateGPoint.currentPos;
5907                updateGPoint.lastTime = updateGPoint.currentTime;
5908                updateGPoint.currentPos = curGPoint.currentPos;
5909                updateGPoint.currentTime = curGPoint.currentTime;
5910
5911                curGPoint = updateGPoint;
5912            } else {
5913                // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this)
5914                curGPoint.captured = true;
5915                curGPoint.insideElementPressed = true;
5916                curGPoint.insideElement = true;
5917                startTrackingPointer( pointsList, curGPoint );
5918            }
5919
5920            pointsList.addContact();
5921            //$.console.log('contacts++ ', pointsList.contacts);
5922
5923            if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5924                $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint );
5925            }
5926
5927            if ( pointsList.contacts === 1 ) {
5928                // Press
5929                if ( tracker.pressHandler ) {
5930                    propagate = tracker.pressHandler(
5931                        {
5932                            eventSource:          tracker,
5933                            pointerType:          curGPoint.type,
5934                            position:             getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ),
5935                            buttons:              pointsList.buttons,
5936                            isTouchEvent:         curGPoint.type === 'touch',
5937                            originalEvent:        event,
5938                            preventDefaultAction: false,
5939                            userData:             tracker.userData
5940                        }
5941                    );
5942                    if ( propagate === false ) {
5943                        $.cancelEvent( event );
5944                    }
5945                }
5946            } else if ( pointsList.contacts === 2 ) {
5947                if ( tracker.pinchHandler && curGPoint.type === 'touch' ) {
5948                    // Initialize for pinch
5949                    delegate.pinchGPoints = pointsList.asArray();
5950                    delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
5951                    delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
5952                }
5953            }
5954        }
5955
5956        return true;
5957    }
5958
5959
5960    /**
5961     * @function
5962     * @private
5963     * @inner
5964     * @param {OpenSeadragon.MouseTracker} tracker
5965     *     A reference to the MouseTracker instance.
5966     * @param {Object} event
5967     *     A reference to the originating DOM event.
5968     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5969     *      Gesture points associated with the event.
5970     * @param {Number} buttonChanged
5971     *      The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
5972     *      Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
5973     *      only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
5974     *
5975     * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false.
5976     */
5977    function updatePointersUp( tracker, event, gPoints, buttonChanged ) {
5978        var delegate = THIS[ tracker.hash ],
5979            pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
5980            propagate,
5981            releasePoint,
5982            releaseTime,
5983            i,
5984            gPointCount = gPoints.length,
5985            curGPoint,
5986            updateGPoint,
5987            releaseCapture = false,
5988            wasCaptured = false,
5989            quick;
5990
5991        if ( typeof event.buttons !== 'undefined' ) {
5992            pointsList.buttons = event.buttons;
5993        } else {
5994            if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5995                if ( buttonChanged === 0 ) {
5996                    // Primary
5997                    pointsList.buttons -= 1;
5998                } else if ( buttonChanged === 1 ) {
5999                    // Aux
6000                    pointsList.buttons -= 4;
6001                } else if ( buttonChanged === 2 ) {
6002                    // Secondary
6003                    pointsList.buttons -= 2;
6004                } else if ( buttonChanged === 3 ) {
6005                    // X1 (Back)
6006                    pointsList.buttons -= 8;
6007                } else if ( buttonChanged === 4 ) {
6008                    // X2 (Forward)
6009                    pointsList.buttons -= 16;
6010                } else if ( buttonChanged === 5 ) {
6011                    // Pen Eraser
6012                    pointsList.buttons -= 32;
6013                }
6014            } else {
6015                if ( buttonChanged === 0 ) {
6016                    // Primary
6017                    pointsList.buttons ^= ~1;
6018                } else if ( buttonChanged === 1 ) {
6019                    // Aux
6020                    pointsList.buttons ^= ~4;
6021                } else if ( buttonChanged === 2 ) {
6022                    // Secondary
6023                    pointsList.buttons ^= ~2;
6024                } else if ( buttonChanged === 3 ) {
6025                    // X1 (Back)
6026                    pointsList.buttons ^= ~8;
6027                } else if ( buttonChanged === 4 ) {
6028                    // X2 (Forward)
6029                    pointsList.buttons ^= ~16;
6030                } else if ( buttonChanged === 5 ) {
6031                    // Pen Eraser
6032                    pointsList.buttons ^= ~32;
6033                }
6034            }
6035        }
6036
6037        // Only capture and track primary button, pen, and touch contacts
6038        if ( buttonChanged !== 0 ) {
6039            // Aux Release
6040            if ( tracker.nonPrimaryReleaseHandler ) {
6041                propagate = tracker.nonPrimaryReleaseHandler(
6042                    {
6043                        eventSource:           tracker,
6044                        pointerType:           gPoints[ 0 ].type,
6045                        position:              getPointRelativeToAbsolute(gPoints[0].currentPos, tracker.element),
6046                        button:                buttonChanged,
6047                        buttons:               pointsList.buttons,
6048                        isTouchEvent:          gPoints[ 0 ].type === 'touch',
6049                        originalEvent:         event,
6050                        preventDefaultAction:  false,
6051                        userData:              tracker.userData
6052                    }
6053                );
6054                if ( propagate === false ) {
6055                    $.cancelEvent( event );
6056                }
6057            }
6058
6059            // A primary mouse button may have been released while the non-primary button was down
6060            var otherPointsList = tracker.getActivePointersListByType("mouse");
6061            // Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223
6062            abortContacts(tracker, event, otherPointsList); // No-op if no active pointer
6063
6064            return false;
6065        }
6066
6067        // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13)
6068        if (typeof gPoints[ 0 ].currentPos === "undefined") {
6069            abortContacts(tracker, event, pointsList);
6070
6071            return false;
6072        }
6073
6074        for ( i = 0; i < gPointCount; i++ ) {
6075            curGPoint = gPoints[ i ];
6076            updateGPoint = pointsList.getById( curGPoint.id );
6077            if ( updateGPoint ) {
6078                // Update the pointer, stop tracking it if not still in this element
6079                if ( updateGPoint.captured ) {
6080                    updateGPoint.captured = false;
6081                    releaseCapture = true;
6082                    wasCaptured = true;
6083                }
6084                updateGPoint.lastPos = updateGPoint.currentPos;
6085                updateGPoint.lastTime = updateGPoint.currentTime;
6086                updateGPoint.currentPos = curGPoint.currentPos;
6087                updateGPoint.currentTime = curGPoint.currentTime;
6088                if ( !updateGPoint.insideElement ) {
6089                    stopTrackingPointer( pointsList, updateGPoint );
6090                }
6091
6092                releasePoint = updateGPoint.currentPos;
6093                releaseTime = updateGPoint.currentTime;
6094
6095                if ( wasCaptured ) {
6096                    // Pointer was activated in our element but could have been removed in any element since events are captured to our element
6097
6098                    pointsList.removeContact();
6099                    //$.console.log('contacts-- ', pointsList.contacts);
6100
6101                    if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
6102                        $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
6103                    }
6104
6105                    if ( pointsList.contacts === 0 ) {
6106
6107                        // Release (pressed in our element)
6108                        if ( tracker.releaseHandler ) {
6109                            propagate = tracker.releaseHandler(
6110                                {
6111                                    eventSource:           tracker,
6112                                    pointerType:           updateGPoint.type,
6113                                    position:              getPointRelativeToAbsolute( releasePoint, tracker.element ),
6114                                    buttons:               pointsList.buttons,
6115                                    insideElementPressed:  updateGPoint.insideElementPressed,
6116                                    insideElementReleased: updateGPoint.insideElement,
6117                                    isTouchEvent:          updateGPoint.type === 'touch',
6118                                    originalEvent:         event,
6119                                    preventDefaultAction:  false,
6120                                    userData:              tracker.userData
6121                                }
6122                            );
6123                            if ( propagate === false ) {
6124                                $.cancelEvent( event );
6125                            }
6126                        }
6127
6128                        // Drag End
6129                        if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) {
6130                            propagate = tracker.dragEndHandler(
6131                                {
6132                                    eventSource:          tracker,
6133                                    pointerType:          updateGPoint.type,
6134                                    position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
6135                                    speed:                updateGPoint.speed,
6136                                    direction:            updateGPoint.direction,
6137                                    shift:                event.shiftKey,
6138                                    isTouchEvent:         updateGPoint.type === 'touch',
6139                                    originalEvent:        event,
6140                                    preventDefaultAction: false,
6141                                    userData:             tracker.userData
6142                                }
6143                            );
6144                            if ( propagate === false ) {
6145                                $.cancelEvent( event );
6146                            }
6147                        }
6148
6149                        // Click / Double-Click
6150                        if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) {
6151                            quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold &&
6152                                            updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold;
6153
6154                            // Click
6155                            if ( tracker.clickHandler ) {
6156                                propagate = tracker.clickHandler(
6157                                    {
6158                                        eventSource:          tracker,
6159                                        pointerType:          updateGPoint.type,
6160                                        position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
6161                                        quick:                quick,
6162                                        shift:                event.shiftKey,
6163                                        isTouchEvent:         updateGPoint.type === 'touch',
6164                                        originalEvent:        event,
6165                                        preventDefaultAction: false,
6166                                        userData:             tracker.userData
6167                                    }
6168                                );
6169                                if ( propagate === false ) {
6170                                    $.cancelEvent( event );
6171                                }
6172                            }
6173
6174                            // Double-Click
6175                            if ( tracker.dblClickHandler && quick ) {
6176                                pointsList.clicks++;
6177                                if ( pointsList.clicks === 1 ) {
6178                                    delegate.lastClickPos = releasePoint;
6179                                    /*jshint loopfunc:true*/
6180                                    delegate.dblClickTimeOut = setTimeout( function() {
6181                                        pointsList.clicks = 0;
6182                                    }, tracker.dblClickTimeThreshold );
6183                                    /*jshint loopfunc:false*/
6184                                } else if ( pointsList.clicks === 2 ) {
6185                                    clearTimeout( delegate.dblClickTimeOut );
6186                                    pointsList.clicks = 0;
6187                                    if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) {
6188                                        propagate = tracker.dblClickHandler(
6189                                            {
6190                                                eventSource:          tracker,
6191                                                pointerType:          updateGPoint.type,
6192                                                position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
6193                                                shift:                event.shiftKey,
6194                                                isTouchEvent:         updateGPoint.type === 'touch',
6195                                                originalEvent:        event,
6196                                                preventDefaultAction: false,
6197                                                userData:             tracker.userData
6198                                            }
6199                                        );
6200                                        if ( propagate === false ) {
6201                                            $.cancelEvent( event );
6202                                        }
6203                                    }
6204                                    delegate.lastClickPos = null;
6205                                }
6206                            }
6207                        }
6208                    } else if ( pointsList.contacts === 2 ) {
6209                        if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) {
6210                            // Reset for pinch
6211                            delegate.pinchGPoints = pointsList.asArray();
6212                            delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
6213                            delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
6214                        }
6215                    }
6216                } else {
6217                    // Pointer was activated in another element but removed in our element
6218
6219                    // Release (pressed in another element)
6220                    if ( tracker.releaseHandler ) {
6221                        propagate = tracker.releaseHandler(
6222                            {
6223                                eventSource:           tracker,
6224                                pointerType:           updateGPoint.type,
6225                                position:              getPointRelativeToAbsolute( releasePoint, tracker.element ),
6226                                buttons:               pointsList.buttons,
6227                                insideElementPressed:  updateGPoint.insideElementPressed,
6228                                insideElementReleased: updateGPoint.insideElement,
6229                                isTouchEvent:          updateGPoint.type === 'touch',
6230                                originalEvent:         event,
6231                                preventDefaultAction:  false,
6232                                userData:              tracker.userData
6233                            }
6234                        );
6235                        if ( propagate === false ) {
6236                            $.cancelEvent( event );
6237                        }
6238                    }
6239                }
6240            }
6241        }
6242
6243        return releaseCapture;
6244    }
6245
6246
6247    /**
6248     * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
6249     *
6250     * @function
6251     * @private
6252     * @inner
6253     * @param {OpenSeadragon.MouseTracker} tracker
6254     *     A reference to the MouseTracker instance.
6255     * @param {Object} event
6256     *     A reference to the originating DOM event.
6257     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
6258     *      Gesture points associated with the event.
6259     */
6260    function updatePointersMove( tracker, event, gPoints ) {
6261        var delegate = THIS[ tracker.hash ],
6262            pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
6263            i,
6264            gPointCount = gPoints.length,
6265            curGPoint,
6266            updateGPoint,
6267            gPointArray,
6268            delta,
6269            propagate;
6270
6271        if ( typeof event.buttons !== 'undefined' ) {
6272            pointsList.buttons = event.buttons;
6273        }
6274
6275        for ( i = 0; i < gPointCount; i++ ) {
6276            curGPoint = gPoints[ i ];
6277            updateGPoint = pointsList.getById( curGPoint.id );
6278
6279            if ( updateGPoint ) {
6280                // Already tracking the pointer...update it
6281                if ( Object.prototype.hasOwnProperty.call( curGPoint, 'isPrimary' ) ) {
6282                    updateGPoint.isPrimary = curGPoint.isPrimary;
6283                }
6284                updateGPoint.lastPos = updateGPoint.currentPos;
6285                updateGPoint.lastTime = updateGPoint.currentTime;
6286                updateGPoint.currentPos = curGPoint.currentPos;
6287                updateGPoint.currentTime = curGPoint.currentTime;
6288            } else {
6289                // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this)
6290                curGPoint.captured = false;
6291                curGPoint.insideElementPressed = false;
6292                curGPoint.insideElement = true;
6293                startTrackingPointer( pointsList, curGPoint );
6294            }
6295        }
6296
6297        // Stop (mouse only)
6298        if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) {
6299            clearTimeout( tracker.stopTimeOut );
6300            tracker.stopTimeOut = setTimeout( function() {
6301                handlePointerStop( tracker, event, gPoints[ 0 ].type );
6302            }, tracker.stopDelay );
6303        }
6304
6305        if ( pointsList.contacts === 0 ) {
6306            // Move (no contacts: hovering mouse or other hover-capable device)
6307            if ( tracker.moveHandler ) {
6308                propagate = tracker.moveHandler(
6309                    {
6310                        eventSource:          tracker,
6311                        pointerType:          gPoints[ 0 ].type,
6312                        position:             getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
6313                        buttons:              pointsList.buttons,
6314                        isTouchEvent:         gPoints[ 0 ].type === 'touch',
6315                        originalEvent:        event,
6316                        preventDefaultAction: false,
6317                        userData:             tracker.userData
6318                    }
6319                );
6320                if ( propagate === false ) {
6321                    $.cancelEvent( event );
6322                }
6323            }
6324        } else if ( pointsList.contacts === 1 ) {
6325            // Move (1 contact)
6326            if ( tracker.moveHandler ) {
6327                updateGPoint = pointsList.asArray()[ 0 ];
6328                propagate = tracker.moveHandler(
6329                    {
6330                        eventSource:          tracker,
6331                        pointerType:          updateGPoint.type,
6332                        position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
6333                        buttons:              pointsList.buttons,
6334                        isTouchEvent:         updateGPoint.type === 'touch',
6335                        originalEvent:        event,
6336                        preventDefaultAction: false,
6337                        userData:             tracker.userData
6338                    }
6339                );
6340                if ( propagate === false ) {
6341                    $.cancelEvent( event );
6342                }
6343            }
6344
6345            // Drag
6346            if ( tracker.dragHandler ) {
6347                updateGPoint = pointsList.asArray()[ 0 ];
6348                delta = updateGPoint.currentPos.minus( updateGPoint.lastPos );
6349                propagate = tracker.dragHandler(
6350                    {
6351                        eventSource:          tracker,
6352                        pointerType:          updateGPoint.type,
6353                        position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
6354                        buttons:              pointsList.buttons,
6355                        delta:                delta,
6356                        speed:                updateGPoint.speed,
6357                        direction:            updateGPoint.direction,
6358                        shift:                event.shiftKey,
6359                        isTouchEvent:         updateGPoint.type === 'touch',
6360                        originalEvent:        event,
6361                        preventDefaultAction: false,
6362                        userData:             tracker.userData
6363                    }
6364                );
6365                if ( propagate === false ) {
6366                    $.cancelEvent( event );
6367                }
6368            }
6369        } else if ( pointsList.contacts === 2 ) {
6370            // Move (2 contacts, use center)
6371            if ( tracker.moveHandler ) {
6372                gPointArray = pointsList.asArray();
6373                propagate = tracker.moveHandler(
6374                    {
6375                        eventSource:          tracker,
6376                        pointerType:          gPointArray[ 0 ].type,
6377                        position:             getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ),
6378                        buttons:              pointsList.buttons,
6379                        isTouchEvent:         gPointArray[ 0 ].type === 'touch',
6380                        originalEvent:        event,
6381                        preventDefaultAction: false,
6382                        userData:             tracker.userData
6383                    }
6384                );
6385                if ( propagate === false ) {
6386                    $.cancelEvent( event );
6387                }
6388            }
6389
6390            // Pinch
6391            if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) {
6392                delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
6393                if ( delta != delegate.currentPinchDist ) {
6394                    delegate.lastPinchDist = delegate.currentPinchDist;
6395                    delegate.currentPinchDist = delta;
6396                    delegate.lastPinchCenter = delegate.currentPinchCenter;
6397                    delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
6398                    propagate = tracker.pinchHandler(
6399                        {
6400                            eventSource:          tracker,
6401                            pointerType:          'touch',
6402                            gesturePoints:        delegate.pinchGPoints,
6403                            lastCenter:           getPointRelativeToAbsolute( delegate.lastPinchCenter, tracker.element ),
6404                            center:               getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ),
6405                            lastDistance:         delegate.lastPinchDist,
6406                            distance:             delegate.currentPinchDist,
6407                            shift:                event.shiftKey,
6408                            originalEvent:        event,
6409                            preventDefaultAction: false,
6410                            userData:             tracker.userData
6411                        }
6412                    );
6413                    if ( propagate === false ) {
6414                        $.cancelEvent( event );
6415                    }
6416                }
6417            }
6418        }
6419    }
6420
6421
6422    /**
6423     * @function
6424     * @private
6425     * @inner
6426     * @param {OpenSeadragon.MouseTracker} tracker
6427     *     A reference to the MouseTracker instance.
6428     * @param {Object} event
6429     *     A reference to the originating DOM event.
6430     * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
6431     *      Gesture points associated with the event.
6432     */
6433    function updatePointersCancel( tracker, event, gPoints ) {
6434        updatePointersUp( tracker, event, gPoints, 0 );
6435        updatePointersExit( tracker, event, gPoints );
6436    }
6437
6438
6439    /**
6440     * @private
6441     * @inner
6442     */
6443    function handlePointerStop( tracker, originalMoveEvent, pointerType ) {
6444        if ( tracker.stopHandler ) {
6445            tracker.stopHandler( {
6446                eventSource:          tracker,
6447                pointerType:          pointerType,
6448                position:             getMouseRelative( originalMoveEvent, tracker.element ),
6449                buttons:              tracker.getActivePointersListByType( pointerType ).buttons,
6450                isTouchEvent:         pointerType === 'touch',
6451                originalEvent:        originalMoveEvent,
6452                preventDefaultAction: false,
6453                userData:             tracker.userData
6454            } );
6455        }
6456    }
6457
6458    /**
6459     * True if inside an iframe, otherwise false.
6460     * @member {Boolean} isInIframe
6461     * @private
6462     * @inner
6463     */
6464    var isInIframe = (function() {
6465        try {
6466            return window.self !== window.top;
6467        } catch (e) {
6468            return true;
6469        }
6470    })();
6471
6472    /**
6473     * @function
6474     * @private
6475     * @inner
6476     * @returns {Boolean} True if the target has access rights to events, otherwise false.
6477     */
6478    function canAccessEvents (target) {
6479        try {
6480            return target.addEventListener && target.removeEventListener;
6481        } catch (e) {
6482            return false;
vendor: 4,944 bytes, lines 6483-6605
6483        }
6484    }
6485
6486}(OpenSeadragon));
6487
6488/*
6489 * OpenSeadragon - Control
6490 *
6491 * Copyright (C) 2009 CodePlex Foundation
6492 * Copyright (C) 2010-2013 OpenSeadragon contributors
6493 *
6494 * Redistribution and use in source and binary forms, with or without
6495 * modification, are permitted provided that the following conditions are
6496 * met:
6497 *
6498 * - Redistributions of source code must retain the above copyright notice,
6499 *   this list of conditions and the following disclaimer.
6500 *
6501 * - Redistributions in binary form must reproduce the above copyright
6502 *   notice, this list of conditions and the following disclaimer in the
6503 *   documentation and/or other materials provided with the distribution.
6504 *
6505 * - Neither the name of CodePlex Foundation nor the names of its
6506 *   contributors may be used to endorse or promote products derived from
6507 *   this software without specific prior written permission.
6508 *
6509 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6510 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6511 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6512 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
6513 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6514 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
6515 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
6516 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
6517 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
6518 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
6519 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6520 */
6521
6522(function( $ ){
6523
6524/**
6525 * An enumeration of supported locations where controls can be anchored.
6526 * The anchoring is always relative to the container.
6527 * @member ControlAnchor
6528 * @memberof OpenSeadragon
6529 * @static
6530 * @type {Object}
6531 * @property {Number} NONE
6532 * @property {Number} TOP_LEFT
6533 * @property {Number} TOP_RIGHT
6534 * @property {Number} BOTTOM_LEFT
6535 * @property {Number} BOTTOM_RIGHT
6536 * @property {Number} ABSOLUTE
6537 */
6538$.ControlAnchor = {
6539    NONE: 0,
6540    TOP_LEFT: 1,
6541    TOP_RIGHT: 2,
6542    BOTTOM_RIGHT: 3,
6543    BOTTOM_LEFT: 4,
6544    ABSOLUTE: 5
6545};
6546
6547/**
6548 * @class Control
6549 * @classdesc A Control represents any interface element which is meant to allow the user
6550 * to interact with the zoomable interface. Any control can be anchored to any
6551 * element.
6552 *
6553 * @memberof OpenSeadragon
6554 * @param {Element} element - the control element to be anchored in the container.
6555 * @param {Object } options - All required and optional settings for configuring a control element.
6556 * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - the position of the control
6557 *  relative to the container.
6558 * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or
6559 *  directly to the container
6560 * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior
6561 * @param {Element} container - the element to control will be anchored too.
6562 */
6563$.Control = function ( element, options, container ) {
6564    var parent = element.parentNode;
6565    if (typeof options === 'number')
6566    {
6567        $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " +
6568                        "please use an options object instead.  " +
6569                        "Support for this deprecated variant is scheduled for removal in December 2013");
6570         options = {anchor: options};
6571    }
6572    options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer;
6573    /**
6574     * True if the control should have autofade behavior.
6575     * @member {Boolean} autoFade
6576     * @memberof OpenSeadragon.Control#
6577     */
6578    this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade;
6579    /**
6580     * The element providing the user interface with some type of control (e.g. a zoom-in button).
6581     * @member {Element} element
6582     * @memberof OpenSeadragon.Control#
6583     */
6584    this.element    = element;
6585    /**
6586     * The position of the Control relative to its container.
6587     * @member {OpenSeadragon.ControlAnchor} anchor
6588     * @memberof OpenSeadragon.Control#
6589     */
6590    this.anchor     = options.anchor;
6591    /**
6592     * The Control's containing element.
6593     * @member {Element} container
6594     * @memberof OpenSeadragon.Control#
6595     */
6596    this.container  = container;
6597    /**
6598     * A neutral element surrounding the control element.
6599     * @member {Element} wrapper
6600     * @memberof OpenSeadragon.Control#
6601     */
6602    if ( this.anchor == $.ControlAnchor.ABSOLUTE ) {
6603        this.wrapper    = $.makeNeutralElement( "div" );
6604        this.wrapper.style.position = "absolute";
6605        this.wrapper.style.top = typeof (options.top) == "number" ? (options.top + 'px') : options.top;
6606        this.wrapper.style.left  = typeof (options.left) == "number" ? (options.left + 'px') : options.left;
6607        this.wrapper.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
6608        this.wrapper.style.width  = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
6609        this.wrapper.style.margin = "0px";
6610        this.wrapper.style.padding = "0px";
6611
6612        this.element.style.position = "relative";
6613        this.element.style.top = "0px";
6614        this.element.style.left = "0px";
6615        this.element.style.height = "100%";
6616        this.element.style.width = "100%";
6617    } else {
6618        this.wrapper    = $.makeNeutralElement( "div" );
6619        this.wrapper.style.display = "inline-block";
6620        if ( this.anchor == $.ControlAnchor.NONE ) {
6621            // IE6 fix
6622            this.wrapper.style.width = this.wrapper.style.height = "100%";
6623        }
6624    }
6625    this.wrapper.appendChild( this.element );
6626
6627    if (options.attachToViewer ) {
6628        if ( this.anchor == $.ControlAnchor.TOP_RIGHT ||
6629             this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) {
6630            this.container.insertBefore(
6631                this.wrapper,
6632                this.container.firstChild
6633            );
6634        } else {
6635            this.container.appendChild( this.wrapper );
6636        }
6637    } else {
6638        parent.appendChild( this.wrapper );
6639    }
6640};
6641
6642/** @lends OpenSeadragon.Control.prototype */
6643$.Control.prototype = {
6644
6645    /**
6646     * Removes the control from the container.
6647     * @function
6648     */
6649    destroy: function() {
6650        this.wrapper.removeChild( this.element );
6651        this.container.removeChild( this.wrapper );
6652    },
6653
6654    /**
6655     * Determines if the control is currently visible.
6656     * @function
6657     * @return {Boolean} true if currently visible, false otherwise.
6658     */
6659    isVisible: function() {
6660        return this.wrapper.style.display != "none";
6661    },
6662
6663    /**
6664     * Toggles the visibility of the control.
6665     * @function
6666     * @param {Boolean} visible - true to make visible, false to hide.
6667     */
6668    setVisible: function( visible ) {
6669        this.wrapper.style.display = visible ?
6670            ( this.anchor == $.ControlAnchor.ABSOLUTE ? 'block' : 'inline-block' ) :
6671            "none";
6672    },
6673
6674    /**
6675     * Sets the opacity level for the control.
6676     * @function
6677     * @param {Number} opactiy - a value between 1 and 0 inclusively.
6678     */
6679    setOpacity: function( opacity ) {
6680        if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) {
6681            $.setElementOpacity( this.element, opacity, true );
6682        } else {
6683            $.setElementOpacity( this.wrapper, opacity, true );
6684        }
6685    }
6686};
6687
6688}( OpenSeadragon ));
6689
6690/*
6691 * OpenSeadragon - ControlDock
6692 *
6693 * Copyright (C) 2009 CodePlex Foundation
6694 * Copyright (C) 2010-2013 OpenSeadragon contributors
6695 *
6696 * Redistribution and use in source and binary forms, with or without
6697 * modification, are permitted provided that the following conditions are
6698 * met:
6699 *
6700 * - Redistributions of source code must retain the above copyright notice,
6701 *   this list of conditions and the following disclaimer.
6702 *
6703 * - Redistributions in binary form must reproduce the above copyright
6704 *   notice, this list of conditions and the following disclaimer in the
6705 *   documentation and/or other materials provided with the distribution.
6706 *
6707 * - Neither the name of CodePlex Foundation nor the names of its
6708 *   contributors may be used to endorse or promote products derived from
6709 *   this software without specific prior written permission.
6710 *
6711 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6712 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6713 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6714 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
6715 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6716 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
6717 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
6718 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
6719 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
6720 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
6721 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6722 */
6723
6724(function( $ ){
6725    /**
6726     * @class ControlDock
6727     * @classdesc Provides a container element (a &lt;form&gt; element) with support for the layout of control elements.
6728     *
6729     * @memberof OpenSeadragon
6730     */
6731    $.ControlDock = function( options ){
6732        var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'],
6733            layout,
6734            i;
6735
6736        $.extend( true, this, {
6737            id: 'controldock-' + $.now() + '-' + Math.floor(Math.random() * 1000000),
6738            container: $.makeNeutralElement( 'div' ),
6739            controls: []
6740        }, options );
6741
6742        // Disable the form's submit; otherwise button clicks and return keys
6743        // can trigger it.
6744        this.container.onsubmit = function() {
6745            return false;
vendor: 11,484 bytes, lines 6746-7084
6746        };
6747
6748        if( this.element ){
6749            this.element = $.getElement( this.element );
6750            this.element.appendChild( this.container );
6751            this.element.style.position = 'relative';
6752            this.container.style.width = '100%';
6753            this.container.style.height = '100%';
6754        }
6755
6756        for( i = 0; i < layouts.length; i++ ){
6757            layout = layouts[ i ];
6758            this.controls[ layout ] = $.makeNeutralElement( "div" );
6759            this.controls[ layout ].style.position = 'absolute';
6760            if ( layout.match( 'left' ) ){
6761                this.controls[ layout ].style.left = '0px';
6762            }
6763            if ( layout.match( 'right' ) ){
6764                this.controls[ layout ].style.right = '0px';
6765            }
6766            if ( layout.match( 'top' ) ){
6767                this.controls[ layout ].style.top = '0px';
6768            }
6769            if ( layout.match( 'bottom' ) ){
6770                this.controls[ layout ].style.bottom = '0px';
6771            }
6772        }
6773
6774        this.container.appendChild( this.controls.topleft );
6775        this.container.appendChild( this.controls.topright );
6776        this.container.appendChild( this.controls.bottomright );
6777        this.container.appendChild( this.controls.bottomleft );
6778    };
6779
6780    /** @lends OpenSeadragon.ControlDock.prototype */
6781    $.ControlDock.prototype = {
6782
6783        /**
6784         * @function
6785         */
6786        addControl: function ( element, controlOptions ) {
6787            element = $.getElement( element );
6788            var div = null;
6789
6790            if ( getControlIndex( this, element ) >= 0 ) {
6791                return;     // they're trying to add a duplicate control
6792            }
6793
6794            switch ( controlOptions.anchor ) {
6795                case $.ControlAnchor.TOP_RIGHT:
6796                    div = this.controls.topright;
6797                    element.style.position = "relative";
6798                    element.style.paddingRight = "0px";
6799                    element.style.paddingTop = "0px";
6800                    break;
6801                case $.ControlAnchor.BOTTOM_RIGHT:
6802                    div = this.controls.bottomright;
6803                    element.style.position = "relative";
6804                    element.style.paddingRight = "0px";
6805                    element.style.paddingBottom = "0px";
6806                    break;
6807                case $.ControlAnchor.BOTTOM_LEFT:
6808                    div = this.controls.bottomleft;
6809                    element.style.position = "relative";
6810                    element.style.paddingLeft = "0px";
6811                    element.style.paddingBottom = "0px";
6812                    break;
6813                case $.ControlAnchor.TOP_LEFT:
6814                    div = this.controls.topleft;
6815                    element.style.position = "relative";
6816                    element.style.paddingLeft = "0px";
6817                    element.style.paddingTop = "0px";
6818                    break;
6819                case $.ControlAnchor.ABSOLUTE:
6820                    div = this.container;
6821                    element.style.margin = "0px";
6822                    element.style.padding = "0px";
6823                    break;
6824                default:
6825                case $.ControlAnchor.NONE:
6826                    div = this.container;
6827                    element.style.margin = "0px";
6828                    element.style.padding = "0px";
6829                    break;
6830            }
6831
6832            this.controls.push(
6833                new $.Control( element, controlOptions, div )
6834            );
6835            element.style.display = "inline-block";
6836        },
6837
6838
6839        /**
6840         * @function
6841         * @return {OpenSeadragon.ControlDock} Chainable.
6842         */
6843        removeControl: function ( element ) {
6844            element = $.getElement( element );
6845            var i = getControlIndex( this, element );
6846
6847            if ( i >= 0 ) {
6848                this.controls[ i ].destroy();
6849                this.controls.splice( i, 1 );
6850            }
6851
6852            return this;
6853        },
6854
6855        /**
6856         * @function
6857         * @return {OpenSeadragon.ControlDock} Chainable.
6858         */
6859        clearControls: function () {
6860            while ( this.controls.length > 0 ) {
6861                this.controls.pop().destroy();
6862            }
6863
6864            return this;
6865        },
6866
6867
6868        /**
6869         * @function
6870         * @return {Boolean}
6871         */
6872        areControlsEnabled: function () {
6873            var i;
6874
6875            for ( i = this.controls.length - 1; i >= 0; i-- ) {
6876                if ( this.controls[ i ].isVisible() ) {
6877                    return true;
6878                }
6879            }
6880
6881            return false;
6882        },
6883
6884
6885        /**
6886         * @function
6887         * @return {OpenSeadragon.ControlDock} Chainable.
6888         */
6889        setControlsEnabled: function( enabled ) {
6890            var i;
6891
6892            for ( i = this.controls.length - 1; i >= 0; i-- ) {
6893                this.controls[ i ].setVisible( enabled );
6894            }
6895
6896            return this;
6897        }
6898
6899    };
6900
6901
6902    ///////////////////////////////////////////////////////////////////////////////
6903    // Utility methods
6904    ///////////////////////////////////////////////////////////////////////////////
6905    function getControlIndex( dock, element ) {
6906        var controls = dock.controls,
6907            i;
6908
6909        for ( i = controls.length - 1; i >= 0; i-- ) {
6910            if ( controls[ i ].element == element ) {
6911                return i;
6912            }
6913        }
6914
6915        return -1;
6916    }
6917
6918}( OpenSeadragon ));
6919
6920/*
6921 * OpenSeadragon - Placement
6922 *
6923 * Copyright (C) 2010-2016 OpenSeadragon contributors
6924 *
6925 * Redistribution and use in source and binary forms, with or without
6926 * modification, are permitted provided that the following conditions are
6927 * met:
6928 *
6929 * - Redistributions of source code must retain the above copyright notice,
6930 *   this list of conditions and the following disclaimer.
6931 *
6932 * - Redistributions in binary form must reproduce the above copyright
6933 *   notice, this list of conditions and the following disclaimer in the
6934 *   documentation and/or other materials provided with the distribution.
6935 *
6936 * - Neither the name of CodePlex Foundation nor the names of its
6937 *   contributors may be used to endorse or promote products derived from
6938 *   this software without specific prior written permission.
6939 *
6940 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6941 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6942 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6943 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
6944 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6945 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
6946 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
6947 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
6948 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
6949 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
6950 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6951 */
6952
6953(function($) {
6954
6955    /**
6956     * An enumeration of positions to anchor an element.
6957     * @member Placement
6958     * @memberOf OpenSeadragon
6959     * @static
6960     * @readonly
6961     * @property {OpenSeadragon.Placement} CENTER
6962     * @property {OpenSeadragon.Placement} TOP_LEFT
6963     * @property {OpenSeadragon.Placement} TOP
6964     * @property {OpenSeadragon.Placement} TOP_RIGHT
6965     * @property {OpenSeadragon.Placement} RIGHT
6966     * @property {OpenSeadragon.Placement} BOTTOM_RIGHT
6967     * @property {OpenSeadragon.Placement} BOTTOM
6968     * @property {OpenSeadragon.Placement} BOTTOM_LEFT
6969     * @property {OpenSeadragon.Placement} LEFT
6970     */
6971    $.Placement = $.freezeObject({
6972        CENTER:       0,
6973        TOP_LEFT:     1,
6974        TOP:          2,
6975        TOP_RIGHT:    3,
6976        RIGHT:        4,
6977        BOTTOM_RIGHT: 5,
6978        BOTTOM:       6,
6979        BOTTOM_LEFT:  7,
6980        LEFT:         8,
6981        properties: {
6982            0: {
6983                isLeft: false,
6984                isHorizontallyCentered: true,
6985                isRight: false,
6986                isTop: false,
6987                isVerticallyCentered: true,
6988                isBottom: false
6989            },
6990            1: {
6991                isLeft: true,
6992                isHorizontallyCentered: false,
6993                isRight: false,
6994                isTop: true,
6995                isVerticallyCentered: false,
6996                isBottom: false
6997            },
6998            2: {
6999                isLeft: false,
7000                isHorizontallyCentered: true,
7001                isRight: false,
7002                isTop: true,
7003                isVerticallyCentered: false,
7004                isBottom: false
7005            },
7006            3: {
7007                isLeft: false,
7008                isHorizontallyCentered: false,
7009                isRight: true,
7010                isTop: true,
7011                isVerticallyCentered: false,
7012                isBottom: false
7013            },
7014            4: {
7015                isLeft: false,
7016                isHorizontallyCentered: false,
7017                isRight: true,
7018                isTop: false,
7019                isVerticallyCentered: true,
7020                isBottom: false
7021            },
7022            5: {
7023                isLeft: false,
7024                isHorizontallyCentered: false,
7025                isRight: true,
7026                isTop: false,
7027                isVerticallyCentered: false,
7028                isBottom: true
7029            },
7030            6: {
7031                isLeft: false,
7032                isHorizontallyCentered: true,
7033                isRight: false,
7034                isTop: false,
7035                isVerticallyCentered: false,
7036                isBottom: true
7037            },
7038            7: {
7039                isLeft: true,
7040                isHorizontallyCentered: false,
7041                isRight: false,
7042                isTop: false,
7043                isVerticallyCentered: false,
7044                isBottom: true
7045            },
7046            8: {
7047                isLeft: true,
7048                isHorizontallyCentered: false,
7049                isRight: false,
7050                isTop: false,
7051                isVerticallyCentered: true,
7052                isBottom: false
7053            }
7054        }
7055    });
7056
7057}(OpenSeadragon));
7058
7059/*
7060 * OpenSeadragon - Viewer
7061 *
7062 * Copyright (C) 2009 CodePlex Foundation
7063 * Copyright (C) 2010-2013 OpenSeadragon contributors
7064 *
7065 * Redistribution and use in source and binary forms, with or without
7066 * modification, are permitted provided that the following conditions are
7067 * met:
7068 *
7069 * - Redistributions of source code must retain the above copyright notice,
7070 *   this list of conditions and the following disclaimer.
7071 *
7072 * - Redistributions in binary form must reproduce the above copyright
7073 *   notice, this list of conditions and the following disclaimer in the
7074 *   documentation and/or other materials provided with the distribution.
7075 *
7076 * - Neither the name of CodePlex Foundation nor the names of its
7077 *   contributors may be used to endorse or promote products derived from
7078 *   this software without specific prior written permission.
7079 *
7080 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
7081 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7082 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7083 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
7084 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
7085 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
7086 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
7087 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
7088 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
7089 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
7090 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
7091 */
7092
7093(function( $ ){
7094
7095// dictionary from hash to private properties
7096var THIS = {};
7097var nextHash = 1;
7098
7099/**
7100 *
7101 * The main point of entry into creating a zoomable image on the page.<br>
7102 * <br>
7103 * We have provided an idiomatic javascript constructor which takes
7104 * a single object, but still support the legacy positional arguments.<br>
7105 * <br>
7106 * The options below are given in order that they appeared in the constructor
7107 * as arguments and we translate a positional call into an idiomatic call.<br>
7108 * <br>
7109 * To create a viewer, you can use either of this methods:<br>
7110 * <ul>
7111 * <li><code>var viewer = new OpenSeadragon.Viewer(options);</code></li>
7112 * <li><code>var viewer = OpenSeadragon(options);</code></li>
7113 * </ul>
7114 * @class Viewer
7115 * @classdesc The main OpenSeadragon viewer class.
7116 *
7117 * @memberof OpenSeadragon
7118 * @extends OpenSeadragon.EventSource
7119 * @extends OpenSeadragon.ControlDock
7120 * @param {OpenSeadragon.Options} options - Viewer options.
7121 *
7122 **/
7123$.Viewer = function( options ) {
7124
7125    var args  = arguments,
7126        _this = this,
7127        i;
7128
7129
7130    //backward compatibility for positional args while preferring more
7131    //idiomatic javascript options object as the only argument
7132    if( !$.isPlainObject( options ) ){
7133        options = {
7134            id:                 args[ 0 ],
7135            xmlPath:            args.length > 1 ? args[ 1 ] : undefined,
7136            prefixUrl:          args.length > 2 ? args[ 2 ] : undefined,
7137            controls:           args.length > 3 ? args[ 3 ] : undefined,
7138            overlays:           args.length > 4 ? args[ 4 ] : undefined
7139        };
7140    }
7141
7142    //options.config and the general config argument are deprecated
7143    //in favor of the more direct specification of optional settings
7144    //being pass directly on the options object
7145    if ( options.config ){
7146        $.extend( true, options, options.config );
7147        delete options.config;
7148    }
7149
7150    //Public properties
7151    //Allow the options object to override global defaults
7152    $.extend( true, this, {
7153
7154        //internal state and dom identifiers
7155        id:             options.id,
7156        hash:           options.hash || nextHash++,
7157        /**
7158         * Index for page to be shown first next time open() is called (only used in sequenceMode).
7159         * @member {Number} initialPage
7160         * @memberof OpenSeadragon.Viewer#
7161         */
7162        initialPage:    0,
7163
7164        //dom nodes
7165        /**
7166         * The parent element of this Viewer instance, passed in when the Viewer was created.
7167         * @member {Element} element
7168         * @memberof OpenSeadragon.Viewer#
7169         */
7170        element:        null,
7171        /**
7172         * A &lt;div&gt; element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.<br><br>
7173         * Child element of {@link OpenSeadragon.Viewer#element}.
7174         * @member {Element} container
7175         * @memberof OpenSeadragon.Viewer#
7176         */
7177        container:      null,
7178        /**
7179         * A &lt;div&gt; element, the element where user-input events are handled for panning and zooming.<br><br>
7180         * Child element of {@link OpenSeadragon.Viewer#container},
7181         * positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.<br><br>
7182         * The parent of {@link OpenSeadragon.Drawer#canvas} instances.
7183         * @member {Element} canvas
7184         * @memberof OpenSeadragon.Viewer#
7185         */
7186        canvas:         null,
7187
7188        // Overlays list. An overlay allows to add html on top of the viewer.
7189        overlays:           [],
7190        // Container inside the canvas where overlays are drawn.
7191        overlaysContainer:  null,
7192
7193        //private state properties
7194        previousBody:   [],
7195
7196        //This was originally initialized in the constructor and so could never
7197        //have anything in it.  now it can because we allow it to be specified
7198        //in the options and is only empty by default if not specified. Also
7199        //this array was returned from get_controls which I find confusing
7200        //since this object has a controls property which is treated in other
7201        //functions like clearControls.  I'm removing the accessors.
7202        customControls: [],
7203
7204        //These are originally not part options but declared as members
7205        //in initialize.  It's still considered idiomatic to put them here
7206        //source is here for backwards compatibility. It is not an official
7207        //part of the API and should not be relied upon.
7208        source:         null,
7209        /**
7210         * Handles rendering of tiles in the viewer. Created for each TileSource opened.
7211         * @member {OpenSeadragon.Drawer} drawer
7212         * @memberof OpenSeadragon.Viewer#
7213         */
7214        drawer:             null,
7215        /**
7216         * Keeps track of all of the tiled images in the scene.
7217         * @member {OpenSeadragon.World} world
7218         * @memberof OpenSeadragon.Viewer#
7219         */
7220        world:              null,
7221        /**
7222         * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
7223         * @member {OpenSeadragon.Viewport} viewport
7224         * @memberof OpenSeadragon.Viewer#
7225         */
7226        viewport:       null,
7227        /**
7228         * @member {OpenSeadragon.Navigator} navigator
7229         * @memberof OpenSeadragon.Viewer#
7230         */
7231        navigator:      null,
7232
7233        //A collection viewport is a separate viewport used to provide
7234        //simultaneous rendering of sets of tiles
7235        collectionViewport:     null,
7236        collectionDrawer:       null,
7237
7238        //UI image resources
7239        //TODO: rename navImages to uiImages
7240        navImages:      null,
7241
7242        //interface button controls
7243        buttons:        null,
7244
7245        //TODO: this is defunct so safely remove it
7246        profiler:       null
7247
7248    }, $.DEFAULT_SETTINGS, options );
7249
7250    // cookie for domain not exlibrisgroup.com
7251    $.DEFAULT_SETTINGS.iiifToken = options.iiifToken;
7252
7253    if ( typeof ( this.hash) === "undefined" ) {
7254        throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
7255    }
7256    if ( typeof ( THIS[ this.hash ] ) !== "undefined" ) {
7257        // We don't want to throw an error here, as the user might have discarded
7258        // the previous viewer with the same hash and now want to re
7258create it.
7259        $.console.warn("Hash " + this.hash + " has already been used.");
7260    }
7261
7262    //Private state properties
7263    THIS[ this.hash ] = {
7264        "fsBoundsDelta":     new $.Point( 1, 1 ),
7265        "prevContainerSize": null,
7266        "animating":         false,
7267        "forceRedraw":       false,
7268        "mouseInside":       false,
7269        "group":             null,
7270        // whether we should be continuously zooming
7271        "zooming":           false,
7272        // how much we should be continuously zooming by
7273        "zoomFactor":        null,
7274        "lastZoomTime":      null,
7275        "fullPage":          false,
7276        "onfullscreenchange": null
7277    };
7278
7279    this._sequenceIndex = 0;
7280    this._firstOpen = true;
7281    this._updateRequestId = null;
7282    this._loadQueue = [];
7283    this.currentOverlays = [];
7284
7285    this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices
7286
7287    //Inherit some behaviors and properties
7288    $.EventSource.call( this );
7289
7290    this.addHandler( 'open-failed', function ( event ) {
7291        var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message);
7292        _this._showMessage( msg );
7293    });
7294
7295    $.ControlDock.call( this, options );
7296
7297    //Deal with tile sources
7298    if (this.xmlPath) {
7299        //Deprecated option.  Now it is preferred to use the tileSources option
7300        this.tileSources = [ this.xmlPath ];
7301    }
7302
7303    this.element              = this.element || document.getElementById( this.id );
7304    this.canvas               = $.makeNeutralElement( "div" );
7305
7306    this.canvas.className = "openseadragon-canvas";
7307    (function( style ){
7308        style.width    = "100%";
7309        style.height   = "100%";
7310        style.overflow = "hidden";
7311        style.position = "absolute";
7312        style.top      = "0px";
7313        style.left     = "0px";
7314    }(this.canvas.style));
7315    $.setElementTouchActionNone( this.canvas );
7316    if (options.tabIndex !== "") {
7317        this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex);
7318    }
7319
7320    //the container is created through applying the ControlDock constructor above
7321    this.container.className = "openseadragon-container";
7322    (function( style ){
7323        style.width     = "100%";
7324        style.height    = "100%";
7325        style.position  = "relative";
7326        style.overflow  = "hidden";
7327        style.left      = "0px";
7328        style.top       = "0px";
7329        style.textAlign = "left";  // needed to protect against
7330    }( this.container.style ));
7331
7332    this.container.insertBefore( this.canvas, this.container.firstChild );
7333    this.element.appendChild( this.container );
7334
7335    //Used for toggling between fullscreen and default container size
7336    //TODO: these can be closure private and shared across Viewer
7337    //      instances.
7338    this.bodyWidth      = document.body.style.width;
7339    this.bodyHeight     = document.body.style.height;
7340    this.bodyOverflow   = document.body.style.overflow;
7341    this.docOverflow    = document.documentElement.style.overflow;
7342
7343    this.innerTracker = new $.MouseTracker({
7344        element:                  this.canvas,
7345        startDisabled:            !this.mouseNavEnabled,
7346        clickTimeThreshold:       this.clickTimeThreshold,
7347        clickDistThreshold:       this.clickDistThreshold,
7348        dblClickTimeThreshold:    this.dblClickTimeThreshold,
7349        dblClickDistThreshold:    this.dblClickDistThreshold,
7350        keyDownHandler:           $.delegate( this, onCanvasKeyDown ),
7351        keyHandler:               $.delegate( this, onCanvasKeyPress ),
7352        clickHandler:             $.delegate( this, onCanvasClick ),
7353        dblClickHandler:          $.delegate( this, onCanvasDblClick ),
7354        dragHandler:              $.delegate( this, onCanvasDrag ),
7355        dragEndHandler:           $.delegate( this, onCanvasDragEnd ),
7356        enterHandler:             $.delegate( this, onCanvasEnter ),
7357        exitHandler:              $.delegate( this, onCanvasExit ),
7358        pressHandler:             $.delegate( this, onCanvasPress ),
7359        releaseHandler:           $.delegate( this, onCanvasRelease ),
7360        nonPrimaryPressHandler:   $.delegate( this, onCanvasNonPrimaryPress ),
7361        nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
7362        scrollHandler:            $.delegate( this, onCanvasScroll ),
7363        pinchHandler:             $.delegate( this, onCanvasPinch )
7364    });
7365
7366    this.outerTracker = new $.MouseTracker({
7367        element:               this.container,
7368        startDisabled:         !this.mouseNavEnabled,
7369        clickTimeThreshold:    this.clickTimeThreshold,
7370        clickDistThreshold:    this.clickDistThreshold,
7371        dblClickTimeThreshold: this.dblClickTimeThreshold,
7372        dblClickDistThreshold: this.dblClickDistThreshold,
7373        enterHandler:          $.delegate( this, onContainerEnter ),
7374        exitHandler:           $.delegate( this, onContainerExit )
7375    });
7376
7377    if( this.toolbar ){
7378        this.toolbar = new $.ControlDock({ element: this.toolbar });
7379    }
7380
7381    this.bindStandardControls();
7382
vendor: 4,433 bytes, lines 7383-7499
7383    THIS[ this.hash ].prevContainerSize = _getSafeElemSize( this.container );
7384
7385    // Create the world
7386    this.world = new $.World({
7387        viewer: this
7388    });
7389
7390    this.world.addHandler('add-item', function(event) {
7391        // For backwards compatibility, we maintain the source property
7392        _this.source = _this.world.getItemAt(0).source;
7393
7394        THIS[ _this.hash ].forceRedraw = true;
7395
7396        if (!_this._updateRequestId) {
7397            _this._updateRequestId = scheduleUpdate( _this, updateMulti );
7398        }
7399    });
7400
7401    this.world.addHandler('remove-item', function(event) {
7402        // For backwards compatibility, we maintain the source property
7403        if (_this.world.getItemCount()) {
7404            _this.source = _this.world.getItemAt(0).source;
7405        } else {
7406            _this.source = null;
7407        }
7408
7409        THIS[ _this.hash ].forceRedraw = true;
7410    });
7411
7412    this.world.addHandler('metrics-change', function(event) {
7413        if (_this.viewport) {
7414            _this.viewport._setContentBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
7415        }
7416    });
7417
7418    this.world.addHandler('item-index-change', function(event) {
7419        // For backwards compatibility, we maintain the source property
7420        _this.source = _this.world.getItemAt(0).source;
7421    });
7422
7423    // Create the viewport
7424    this.viewport = new $.Viewport({
7425        containerSize:      THIS[ this.hash ].prevContainerSize,
7426        springStiffness:    this.springStiffness,
7427        animationTime:      this.animationTime,
7428        minZoomImageRatio:  this.minZoomImageRatio,
7429        maxZoomPixelRatio:  this.maxZoomPixelRatio,
7430        visibilityRatio:    this.visibilityRatio,
7431        wrapHorizontal:     this.wrapHorizontal,
7432        wrapVertical:       this.wrapVertical,
7433        defaultZoomLevel:   this.defaultZoomLevel,
7434        minZoomLevel:       this.minZoomLevel,
7435        maxZoomLevel:       this.maxZoomLevel,
7436        viewer:             this,
7437        degrees:            this.degrees,
7438        flipped:            this.flipped,
7439        navigatorRotate:    this.navigatorRotate,
7440        homeFillsViewer:    this.homeFillsViewer,
7441        margins:            this.viewportMargins
7442    });
7443
7444    this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor());
7445
7446    // Create the image loader
7447    this.imageLoader = new $.ImageLoader({
7448        jobLimit: this.imageLoaderLimit,
7449        timeout: options.timeout
7450    });
7451
7452    // Create the tile cache
7453    this.tileCache = new $.TileCache({
7454        maxImageCacheCount: this.maxImageCacheCount
7455    });
7456
7457    // Create the drawer
7458    this.drawer = new $.Drawer({
7459        viewer:             this,
7460        viewport:           this.viewport,
7461        element:            this.canvas,
7462        debugGridColor:     this.debugGridColor
7463    });
7464
7465    // Overlay container
7466    this.overlaysContainer    = $.makeNeutralElement( "div" );
7467    this.canvas.appendChild( this.overlaysContainer );
7468
7469    // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons
7470    if (!this.drawer.canRotate()) {
7471        // Disable/remove the rotate left/right buttons since they aren't supported
7472        if (this.rotateLeft) {
7473            i = this.buttons.buttons.indexOf(this.rotateLeft);
7474            this.buttons.buttons.splice(i, 1);
7475            this.buttons.element.removeChild(this.rotateLeft.element);
7476        }
7477        if (this.rotateRight) {
7478            i = this.buttons.buttons.indexOf(this.rotateRight);
7479            this.buttons.buttons.splice(i, 1);
7480            this.buttons.element.removeChild(this.rotateRight.element);
7481        }
7482    }
7483
7484    //Instantiate a navigator if configured
7485    if ( this.showNavigator){
7486        this.navigator = new $.Navigator({
7487            id:                this.navigatorId,
7488            position:          this.navigatorPosition,
7489            sizeRatio:         this.navigatorSizeRatio,
7490            maintainSizeRatio: this.navigatorMaintainSizeRatio,
7491            top:               this.navigatorTop,
7492            left:              this.navigatorLeft,
7493            width:             this.navigatorWidth,
7494            height:            this.navigatorHeight,
7495            autoResize:        this.navigatorAutoResize,
7496            autoFade:          this.navigatorAutoFade,
7497            prefixUrl:         this.prefixUrl,
7498            viewer:            this,
7499            navigatorRotate:   this.navigatorRotate,
7500            background:        this.navigatorBackground,
7501            opacity:           this.navigatorOpacity,
7502            borderColor:       this.navigatorBorderColor,
7503            displayRegionColor: this.navigatorDisplayRegionColor,
7504            crossOriginPolicy: this.crossOriginPolicy
7505        });
7506    }
7507
7508    // Sequence mode
7509    if (this.sequenceMode) {
7510        this.bindSequenceControls();
7511    }
7512
7513    // Open initial tilesources
7514    if (this.tileSources) {
7515        this.open( this.tileSources );
7516    }
7517
7518    // Add custom controls
7519    for ( i = 0; i < this.customControls.length; i++ ) {
7520        this.addControl(
7521            this.customControls[ i ].id,
7522            {anchor: this.customControls[ i ].anchor}
7523        );
7524    }
7525
7526    // Initial fade out
7527    $.requestAnimationFrame( function(){
7528        beginControlsAutoHide( _this );
7529    } );
7530
7531    // Initial canvas options
7532    if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
7533        this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
7534    }
7535
7536};
7537
7538$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
7539
7540
7541    /**
7542     * @function
7543     * @return {Boolean}
7544     */
7545    isOpen: function () {
7546        return !!this.world.getItemCount();
7547    },
7548
7549    // deprecated
7550    openDzi: function ( dzi ) {
7551        $.console.error( "[Viewer.openDzi] this function is deprecated; use Viewer.open() instead." );
7552        return this.open( dzi );
7553    },
7554
7555    // deprecated
7556    openTileSource: function ( tileSource ) {
7557        $.console.error( "[Viewer.openTileSource] this function is deprecated; use Viewer.open() instead." );
7558        return this.open( tileSource );
7559    },
7560
7561    /**
7562     * Open tiled images into the viewer, closing any others.
7563     * To get the TiledImage instance created by open, add an event listener for
7564     * {@link OpenSeadragon.Viewer.html#.event:open}, which when fired can be used to get access
7565     * to the instance, i.e., viewer.world.getItemAt(0).
7566     * @function
7567     * @param {Array|String|Object|Function} tileSources - This can be a TiledImage
7568     * specifier, a TileSource specifier, or an array of either. A TiledImage specifier
7569     * is the same as the options parameter for {@link OpenSeadragon.Viewer#addTiledImage},
7570     * except for the index property; images are added in sequence.
7571     * A TileSource specifier is anything you could pass as the tileSource property
7572     * of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}.
7573     * @param {Number} initialPage - If sequenceMode is true, display this page initially
7574     * for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property.
7575     * @return {OpenSeadragon.Viewer} Chainable.
7576     * @fires OpenSeadragon.Viewer.event:open
7577     * @fires OpenSeadragon.Viewer.event:open-failed
7578     */
7579    open: function (tileSources, initialPage) {
7580        var _this = this;
7581
7582        this.close();
7583
7584        if (!tileSources) {
7585            return;
7586        }
7587
7588        if (this.sequenceMode && $.isArray(tileSources)) {
7589            if (this.referenceStrip) {
7590                this.referenceStrip.destroy();
7591                this.referenceStrip = null;
7592            }
7593
7594            if (typeof initialPage != 'undefined' && !isNaN(initialPage)) {
7595              this.initialPage = initialPage;
7596            }
7597
7598            this.tileSources = tileSources;
7599            this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage));
7600            if (this.tileSources.length) {
7601                this.open(this.tileSources[this._sequenceIndex]);
7602
7603                if ( this.showReferenceStrip ){
7604                    this.addReferenceStrip();
7605                }
7606            }
7607
7608            this._updateSequenceButtons( this._sequenceIndex );
7609            return;
7610        }
7611
7612        if (!$.isArray(tileSources)) {
7613            tileSources = [tileSources];
7614        }
7615
7616        if (!tileSources.length) {
7617            return;
7618        }
7619
7620        this._opening = true;
7621
7622        var expected = tileSources.length;
7623        var successes = 0;
7624        var failures = 0;
7625        var failEvent;
7626
7627        var checkCompletion = function() {
7628            if (successes + failures === expected) {
7629                if (successes) {
7630                    if (_this._firstOpen || !_this.preserveViewport) {
7631                        _this.viewport.goHome( true );
7632                        _this.viewport.update();
7633                    }
7634
7635                    _this._firstOpen = false;
7636
7637                    var source = tileSources[0];
7638                    if (source.tileSource) {
7639                        source = source.tileSource;
7640                    }
7641
7642                    // Global overlays
7643                    if( _this.overlays && !_this.preserveOverlays ){
7644                        for ( var i = 0; i < _this.overlays.length; i++ ) {
7645                            _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] );
7646                        }
7647                    }
7648
7649                    _this._drawOverlays();
7650                    _this._opening = false;
7651
7652                    /**
7653                     * Raised when the viewer has opened and loaded one or more TileSources.
7654                     *
7655                     * @event open
7656                     * @memberof OpenSeadragon.Viewer
7657                     * @type {object}
7658                     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7659                     * @property {OpenSeadragon.TileSource} source - The tile source that was opened.
7660                     * @property {?Object} userData - Arbitrary subscriber-defined object.
7661                     */
7662                    // TODO: what if there are multiple sources?
7663                    _this.raiseEvent( 'open', { source: source } );
7664                } else {
7665                    _this._opening = false;
7666
7667                    /**
7668                     * Raised when an error occurs loading a TileSource.
7669                     *
7670                     * @event open-failed
7671                     * @memberof OpenSeadragon.Viewer
7672                     * @type {object}
7673                     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7674                     * @property {String} message - Information about what failed.
7675                     * @property {String} source - The tile source that failed.
7676                     * @property {?Object} userData - Arbitrary subscriber-defined object.
7677                     */
7678                    _this.raiseEvent( 'open-failed', failEvent );
7679                }
7680            }
7681        };
7682
7683        var doOne = function(options) {
7684            if (!$.isPlainObject(options) || !options.tileSource) {
7685                options = {
7686                    tileSource: options
7687                };
7688            }
7689
7690            if (options.index !== undefined) {
7691                $.console.error('[Viewer.open] setting indexes here is not supported; use addTiledImage instead');
7692                delete options.index;
7693            }
7694
7695            if (options.collectionImmediately === undefined) {
7696                options.collectionImmediately = true;
7697            }
7698
7699            var originalSuccess = options.success;
7700            options.success = function(event) {
7701                successes++;
7702
7703                // TODO: now that options has other things besides tileSource, the overlays
7704                // should probably be at the options level, not the tileSource level.
7705                if (options.tileSource.overlays) {
7706                    for (var i = 0; i < options.tileSource.overlays.length; i++) {
7707                        _this.addOverlay(options.tileSource.overlays[i]);
7708                    }
7709                }
7710
7711                if (originalSuccess) {
7712                    originalSuccess(event);
7713                }
7714
7715                checkCompletion();
7716            };
7717
7718            var originalError = options.error;
7719            options.error = function(event) {
7720                failures++;
7721
7722                if (!failEvent) {
7723                    failEvent = event;
7724                }
7725
7726                if (originalError) {
7727                    originalError(event);
7728                }
7729
7730                checkCompletion();
7731            };
7732
7733            _this.addTiledImage(options);
7734        };
7735
7736        // TileSources
7737        for (var i = 0; i < tileSources.length; i++) {
7738            doOne(tileSources[i]);
7739        }
7740
7741        return this;
7742    },
7743
7744
7745    /**
7746     * @function
7747     * @return {OpenSeadragon.Viewer} Chainable.
7748     * @fires OpenSeadragon.Viewer.event:close
7749     */
7750    close: function ( ) {
7751        if ( !THIS[ this.hash ] ) {
7752            //this viewer has already been destroyed: returning immediately
7753            return this;
7754        }
7755
7756        this._opening = false;
vendor: 18,052 bytes, lines 7757-8250
7757
7758        if ( this.navigator ) {
7759            this.navigator.close();
7760        }
7761
7762        if (!this.preserveOverlays) {
7763            this.clearOverlays();
7764            this.overlaysContainer.innerHTML = "";
7765        }
7766
7767        THIS[ this.hash ].animating = false;
7768        this.world.removeAll();
7769        this.imageLoader.clear();
7770
7771        /**
7772         * Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}).
7773         *
7774         * @event close
7775         * @memberof OpenSeadragon.Viewer
7776         * @type {object}
7777         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7778         * @property {?Object} userData - Arbitrary subscriber-defined object.
7779         */
7780        this.raiseEvent( 'close' );
7781
7782        return this;
7783    },
7784
7785
7786    /**
7787     * Function to destroy the viewer and clean up everything created by OpenSeadragon.
7788     *
7789     * Example:
7790     * var viewer = OpenSeadragon({
7791     *   [...]
7792     * });
7793     *
7794     * //when you are done with the viewer:
7795     * viewer.destroy();
7796     * viewer = null; //important
7797     *
7798     * @function
7799     */
7800    destroy: function( ) {
7801        if ( !THIS[ this.hash ] ) {
7802            //this viewer has already been destroyed: returning immediately
7803            return;
7804        }
7805
7806        this.close();
7807
7808        this.clearOverlays();
7809        this.overlaysContainer.innerHTML = "";
7810
7811        //TODO: implement this...
7812        //this.unbindSequenceControls()
7813        //this.unbindStandardControls()
7814
7815        if (this.referenceStrip) {
7816            this.referenceStrip.destroy();
7817            this.referenceStrip = null;
7818        }
7819
7820        if ( this._updateRequestId !== null ) {
7821            $.cancelAnimationFrame( this._updateRequestId );
7822            this._updateRequestId = null;
7823        }
7824
7825        if ( this.drawer ) {
7826            this.drawer.destroy();
7827        }
7828
7829        this.removeAllHandlers();
7830
7831        // Go through top element (passed to us) and remove all children
7832        // Use removeChild to make sure it handles SVG or any non-html
7833        // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15
7834        if (this.element){
7835            while (this.element.firstChild) {
7836                this.element.removeChild(this.element.firstChild);
7837            }
7838        }
7839
7840        // destroy the mouse trackers
7841        if (this.innerTracker){
7842            this.innerTracker.destroy();
7843        }
7844        if (this.outerTracker){
7845            this.outerTracker.destroy();
7846        }
7847
7848        THIS[ this.hash ] = null;
7849        delete THIS[ this.hash ];
7850
7851        // clear all our references to dom objects
7852        this.canvas = null;
7853        this.container = null;
7854
7855        // clear our reference to the main element - they will need to pass it in again, creating a new viewer
7856        this.element = null;
7857    },
7858
7859    /**
7860     * @function
7861     * @return {Boolean}
7862     */
7863    isMouseNavEnabled: function () {
7864        return this.innerTracker.isTracking();
7865    },
7866
7867    /**
7868     * @function
7869     * @param {Boolean} enabled - true to enable, false to disable
7870     * @return {OpenSeadragon.Viewer} Chainable.
7871     * @fires OpenSeadragon.Viewer.event:mouse-enabled
7872     */
7873    setMouseNavEnabled: function( enabled ){
7874        this.innerTracker.setTracking( enabled );
7875        this.outerTracker.setTracking( enabled );
7876        /**
7877         * Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}).
7878         *
7879         * @event mouse-enabled
7880         * @memberof OpenSeadragon.Viewer
7881         * @type {object}
7882         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7883         * @property {Boolean} enabled
7884         * @property {?Object} userData - Arbitrary subscriber-defined object.
7885         */
7886        this.raiseEvent( 'mouse-enabled', { enabled: enabled } );
7887        return this;
7888    },
7889
7890
7891    /**
7892     * @function
7893     * @return {Boolean}
7894     */
7895    areControlsEnabled: function () {
7896        var enabled = this.controls.length,
7897            i;
7898        for( i = 0; i < this.controls.length; i++ ){
7899            enabled = enabled && this.controls[ i ].isVisible();
7900        }
7901        return enabled;
7902    },
7903
7904
7905    /**
7906     * Shows or hides the controls (e.g. the default navigation buttons).
7907     *
7908     * @function
7909     * @param {Boolean} true to show, false to hide.
7910     * @return {OpenSeadragon.Viewer} Chainable.
7911     * @fires OpenSeadragon.Viewer.event:controls-enabled
7912     */
7913    setControlsEnabled: function( enabled ) {
7914        if( enabled ){
7915            abortControlsAutoHide( this );
7916        } else {
7917            beginControlsAutoHide( this );
7918        }
7919        /**
7920         * Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}).
7921         *
7922         * @event controls-enabled
7923         * @memberof OpenSeadragon.Viewer
7924         * @type {object}
7925         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7926         * @property {Boolean} enabled
7927         * @property {?Object} userData - Arbitrary subscriber-defined object.
7928         */
7929        this.raiseEvent( 'controls-enabled', { enabled: enabled } );
7930        return this;
7931    },
7932
7933    /**
7934     * Turns debugging mode on or off for this viewer.
7935     *
7936     * @function
7937     * @param {Boolean} true to turn debug on, false to turn debug off.
7938     */
7939    setDebugMode: function(debugMode){
7940
7941        for (var i = 0; i < this.world.getItemCount(); i++) {
7942            this.world.getItemAt(i).debugMode = debugMode;
7943        }
7944
7945        this.debugMode = debugMode;
7946        this.forceRedraw();
7947    },
7948
7949    /**
7950     * @function
7951     * @return {Boolean}
7952     */
7953    isFullPage: function () {
7954        return THIS[ this.hash ].fullPage;
7955    },
7956
7957
7958    /**
7959     * Toggle full page mode.
7960     * @function
7961     * @param {Boolean} fullPage
7962     *      If true, enter full page mode.  If false, exit full page mode.
7963     * @return {OpenSeadragon.Viewer} Chainable.
7964     * @fires OpenSeadragon.Viewer.event:pre-full-page
7965     * @fires OpenSeadragon.Viewer.event:full-page
7966     */
7967    setFullPage: function( fullPage ) {
7968
7969        var body = document.body,
7970            bodyStyle = body.style,
7971            docStyle = document.documentElement.style,
7972            _this = this,
7973            nodes,
7974            i;
7975
7976        //don't bother modifying the DOM if we are already in full page mode.
7977        if ( fullPage == this.isFullPage() ) {
7978            return this;
7979        }
7980
7981        var fullPageEventArgs = {
7982            fullPage: fullPage,
7983            preventDefaultAction: false
7984        };
7985        /**
7986         * Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
7987         *
7988         * @event pre-full-page
7989         * @memberof OpenSeadragon.Viewer
7990         * @type {object}
7991         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
7992         * @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode.
7993         * @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false.
7994         * @property {?Object} userData - Arbitrary subscriber-defined object.
7995         */
7996        this.raiseEvent( 'pre-full-page', fullPageEventArgs );
7997        if ( fullPageEventArgs.preventDefaultAction ) {
7998            return this;
7999        }
8000
8001        if ( fullPage ) {
8002
8003            this.elementSize = $.getElementSize( this.element );
8004            this.pageScroll = $.getPageScroll();
8005
8006            this.elementMargin = this.element.style.margin;
8007            this.element.style.margin = "0";
8008            this.elementPadding = this.element.style.padding;
8009            this.element.style.padding = "0";
8010
8011            this.bodyMargin = bodyStyle.margin;
8012            this.docMargin = docStyle.margin;
8013            bodyStyle.margin = "0";
8014            docStyle.margin = "0";
8015
8016            this.bodyPadding = bodyStyle.padding;
8017            this.docPadding = docStyle.padding;
8018            bodyStyle.padding = "0";
8019            docStyle.padding = "0";
8020
8021            this.bodyWidth = bodyStyle.width;
8022            this.docWidth = docStyle.width;
8023            bodyStyle.width = "100%";
8024            docStyle.width = "100%";
8025
8026            this.bodyHeight = bodyStyle.height;
8027            this.docHeight = docStyle.height;
8028            bodyStyle.height = "100%";
8029            docStyle.height = "100%";
8030
8031            //when entering full screen on the ipad it wasn't sufficient to leave
8032            //the body intact as only only the top half of the screen would
8033            //respond to touch events on the canvas, while the bottom half treated
8034            //them as touch events on the document body.  Thus we remove and store
8035            //the bodies elements and replace them when we leave full screen.
8036            this.previousBody = [];
8037            THIS[ this.hash ].prevElementParent = this.element.parentNode;
8038            THIS[ this.hash ].prevNextSibling = this.element.nextSibling;
8039            THIS[ this.hash ].prevElementWidth = this.element.style.width;
8040            THIS[ this.hash ].prevElementHeight = this.element.style.height;
8041            nodes = body.childNodes.length;
8042            for ( i = 0; i < nodes; i++ ) {
8043                this.previousBody.push( body.childNodes[ 0 ] );
8044                body.removeChild( body.childNodes[ 0 ] );
8045            }
8046
8047            //If we've got a toolbar, we need to enable the user to use css to
8048            //preserve it in fullpage mode
8049            if ( this.toolbar && this.toolbar.element ) {
8050                //save a reference to the parent so we can put it back
8051                //in the long run we need a better strategy
8052                this.toolbar.parentNode = this.toolbar.element.parentNode;
8053                this.toolbar.nextSibling = this.toolbar.element.nextSibling;
8054                body.appendChild( this.toolbar.element );
8055
8056                //Make sure the user has some ability to style the toolbar based
8057                //on the mode
8058                $.addClass( this.toolbar.element, 'fullpage' );
8059            }
8060
8061            $.addClass( this.element, 'fullpage' );
8062            body.appendChild( this.element );
8063
8064            this.element.style.height = $.getWindowSize().y + 'px';
8065            this.element.style.width = $.getWindowSize().x + 'px';
8066
8067            if ( this.toolbar && this.toolbar.element ) {
8068                this.element.style.height = (
8069                    $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
8070                ) + 'px';
8071            }
8072
8073            THIS[ this.hash ].fullPage = true;
8074
8075            // mouse will be inside container now
8076            $.delegate( this, onContainerEnter )( {} );
8077
8078        } else {
8079
8080            this.element.style.margin = this.elementMargin;
8081            this.element.style.padding = this.elementPadding;
8082
8083            bodyStyle.margin = this.bodyMargin;
8084            docStyle.margin = this.docMargin;
8085
8086            bodyStyle.padding = this.bodyPadding;
8087            docStyle.padding = this.docPadding;
8088
8089            bodyStyle.width = this.bodyWidth;
8090            docStyle.width = this.docWidth;
8091
8092            bodyStyle.height = this.bodyHeight;
8093            docStyle.height = this.docHeight;
8094
8095            body.removeChild( this.element );
8096            nodes = this.previousBody.length;
8097            for ( i = 0; i < nodes; i++ ) {
8098                body.appendChild( this.previousBody.shift() );
8099            }
8100
8101            $.removeClass( this.element, 'fullpage' );
8102            THIS[ this.hash ].prevElementParent.insertBefore(
8103                this.element,
8104                THIS[ this.hash ].prevNextSibling
8105            );
8106
8107            //If we've got a toolbar, we need to enable the user to use css to
8108            //reset it to its original state
8109            if ( this.toolbar && this.toolbar.element ) {
8110                body.removeChild( this.toolbar.element );
8111
8112                //Make sure the user has some ability to style the toolbar based
8113                //on the mode
8114                $.removeClass( this.toolbar.element, 'fullpage' );
8115
8116                this.toolbar.parentNode.insertBefore(
8117                    this.toolbar.element,
8118                    this.toolbar.nextSibling
8119                );
8120                delete this.toolbar.parentNode;
8121                delete this.toolbar.nextSibling;
8122            }
8123
8124            this.element.style.width = THIS[ this.hash ].prevElementWidth;
8125            this.element.style.height = THIS[ this.hash ].prevElementHeight;
8126
8127            // After exiting fullPage or fullScreen, it can take some time
8128            // before the browser can actually set the scroll.
8129            var restoreScrollCounter = 0;
8130            var restoreScroll = function() {
8131                $.setPageScroll( _this.pageScroll );
8132                var pageScroll = $.getPageScroll();
8133                restoreScrollCounter++;
8134                if (restoreScrollCounter < 10 &&
8135                    (pageScroll.x !== _this.pageScroll.x ||
8136                    pageScroll.y !== _this.pageScroll.y)) {
8137                    $.requestAnimationFrame( restoreScroll );
8138                }
8139            };
8140            $.requestAnimationFrame( restoreScroll );
8141
8142            THIS[ this.hash ].fullPage = false;
8143
8144            // mouse will likely be outside now
8145            $.delegate( this, onContainerExit )( { } );
8146
8147        }
8148
8149        if ( this.navigator && this.viewport ) {
8150            this.navigator.update( this.viewport );
8151        }
8152
8153        /**
8154         * Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
8155         *
8156         * @event full-page
8157         * @memberof OpenSeadragon.Viewer
8158         * @type {object}
8159         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8160         * @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode.
8161         * @property {?Object} userData - Arbitrary subscriber-defined object.
8162         */
8163        this.raiseEvent( 'full-page', { fullPage: fullPage } );
8164
8165        return this;
8166    },
8167
8168    /**
8169     * Toggle full screen mode if supported. Toggle full page mode otherwise.
8170     * @function
8171     * @param {Boolean} fullScreen
8172     *      If true, enter full screen mode.  If false, exit full screen mode.
8173     * @return {OpenSeadragon.Viewer} Chainable.
8174     * @fires OpenSeadragon.Viewer.event:pre-full-screen
8175     * @fires OpenSeadragon.Viewer.event:full-screen
8176     */
8177    setFullScreen: function( fullScreen ) {
8178        var _this = this;
8179
8180        if ( !$.supportsFullScreen ) {
8181            return this.setFullPage( fullScreen );
8182        }
8183
8184        if ( $.isFullScreen() === fullScreen ) {
8185            return this;
8186        }
8187
8188        var fullScreeEventArgs = {
8189            fullScreen: fullScreen,
8190            preventDefaultAction: false
8191        };
8192        /**
8193         * Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
8194         * Note: the pre-full-screen event is not raised when the user is exiting
8195         * full-screen mode by pressing the Esc key. In that case, consider using
8196         * the full-screen, pre-full-page or full-page events.
8197         *
8198         * @event pre-full-screen
8199         * @memberof OpenSeadragon.Viewer
8200         * @type {object}
8201         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8202         * @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode.
8203         * @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false.
8204         * @property {?Object} userData - Arbitrary subscriber-defined object.
8205         */
8206        this.raiseEvent( 'pre-full-screen', fullScreeEventArgs );
8207        if ( fullScreeEventArgs.preventDefaultAction ) {
8208            return this;
8209        }
8210
8211        if ( fullScreen ) {
8212
8213            this.setFullPage( true );
8214            // If the full page mode is not actually entered, we need to prevent
8215            // the full screen mode.
8216            if ( !this.isFullPage() ) {
8217                return this;
8218            }
8219
8220            this.fullPageStyleWidth = this.element.style.width;
8221            this.fullPageStyleHeight = this.element.style.height;
8222            this.element.style.width = '100%';
8223            this.element.style.height = '100%';
8224
8225            var onFullScreenChange = function() {
8226                var isFullScreen = $.isFullScreen();
8227                if ( !isFullScreen ) {
8228                    $.removeEvent( document, $.fullScreenEventName, onFullScreenChange );
8229                    $.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
8230
8231                    _this.setFullPage( false );
8232                    if ( _this.isFullPage() ) {
8233                        _this.element.style.width = _this.fullPageStyleWidth;
8234                        _this.element.style.height = _this.fullPageStyleHeight;
8235                    }
8236                }
8237                if ( _this.navigator && _this.viewport ) {
8238                    //09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout
8239                    setTimeout(function(){
8240                        _this.navigator.update( _this.viewport );
8241                    });
8242                }
8243                /**
8244                 * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
8245                 *
8246                 * @event full-screen
8247                 * @memberof OpenSeadragon.Viewer
8248                 * @type {object}
8249                 * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8250                 * @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode.
8251                 * @property {?Object} userData - Arbitrary subscriber-defined object.
8252                 */
8253                _this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } );
8254            };
8255            $.addEvent( document, $.fullScreenEventName, onFullScreenChange );
8256            $.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
8257
8258            $.requestFullScreen( document.body );
8259
8260        } else {
8261            $.exitFullScreen();
8262        }
8263        return this;
8264    },
8265
8266    /**
8267     * @function
8268     * @return {Boolean}
8269     */
8270    isVisible: function () {
8271        return this.container.style.visibility != "hidden";
8272    },
8273
8274
8275    /**
8276     * @function
8277     * @param {Boolean} visible
8278     * @return {OpenSeadragon.Viewer} Chainable.
8279     * @fires OpenSeadragon.Viewer.event:visible
8280     */
8281    setVisible: function( visible ){
8282        this.container.style.visibility = visible ? "" : "hidden";
8283        /**
8284         * Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}).
8285         *
8286         * @event visible
8287         * @memberof OpenSeadragon.Viewer
8288         * @type {object}
8289         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8290         * @property {Boolean} visible
8291         * @property {?Object} userData - Arbitrary subscriber-defined object.
8292         */
8293        this.raiseEvent( 'visible', { visible: visible } );
8294        return this;
8295    },
8296
8297    /**
8298     * Add a tiled image to the viewer.
8299     * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open}
8300     *  supports except arrays of images.
8301     * Note that you can specify options.width or options.height, but not both.
8302     * The other dimension will be calculated according to the item's aspect ratio.
8303     * If collectionMode is on (see {@link OpenSeadragon.Options}), the new image is
8304     * automatically arranged with the others.
8305     * @function
8306     * @param {Object} options
8307     * @param {String|Object|Function} options.tileSource - The TileSource specifier.
8308     * A String implies a url used to determine the tileSource implementation
8309     *      based on the file extension of url. JSONP is implied by *.js,
8310     *      otherwise the url is retrieved as text and the resulting text is
8311     *      introspected to determine if its json, xml, or text and parsed.
8312     * An Object implies an inline configuration which has a single
8313     *      property sufficient for being able to determine tileSource
8314     *      implementation. If the object has a property which is a function
8315     *      named 'getTileUrl', it is treated as a custom TileSource.
8316     * @param {Number} [options.index] The index of the item. Added on top of
8317     * all other items if not specified.
8318     * @param {Boolean} [options.replace=false] If true, the item at options.index will be
8319     * removed and the new item is added in its place. options.tileSource will be
8320     * interpreted and fetched if necessary before the old item is removed to avoid leaving
8321     * a gap in the world.
8322     * @param {Number} [options.x=0] The X position for the image in viewport coordinates.
8323     * @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
8324     * @param {Number} [options.width=1] The width for the image in viewport coordinates.
8325     * @param {Number} [options.height] The height for the image in viewport coordinates.
8326     * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
8327     * to fit the image into. If specified, x, y, width and height get ignored.
8328     * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
8329     * How to anchor the image in the bounds if options.fitBounds is set.
8330     * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
8331     * (portions of the image outside of this area will not be visible). Only works on
8332     * browsers that support the HTML5 canvas.
8333     * @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
8334     * @param {Boolean} [options.preload=false]  Default switch for loading hidden images (true loads, false blocks)
8335     * @param {Number} [options.degrees=0] Initial rotation of the tiled image around
8336     * its top left corner in degrees.
8337     * @param {String} [options.compositeOperation] How the image is composited onto other images.
8338     * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
8339     * overriding viewer.crossOriginPolicy.
8340     * @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
8341     * @param {Boolean} [options.loadTilesWithAjax]
8342     *      Whether to load tile data using AJAX requests.
8343     *      Defaults to the setting in {@link OpenSeadragon.Options}.
8344     * @param {Object} [options.ajaxHeaders]
8345     *      A set of headers to include when making tile AJAX requests.
8346     *      Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
8347     *      Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
8348     * requests.
8349     * @param {Function} [options.success] A function that gets called when the image is
8350     * successfully added. It's passed the event object which contains a single property:
8351     * "item", which is the resulting instance of TiledImage.
8352     * @param {Function}
8352 [options.error] A function that gets called if the image is
8353     * unable to be added. It's passed the error event object, which contains "message"
8354     * and "source" properties.
8355     * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on,
8356     * specifies whether to snap to the new arrangement immediately or to animate to it.
8357     * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
8358     * @fires OpenSeadragon.World.event:add-item
8359     * @fires OpenSeadragon.Viewer.event:add-item-failed
8360     */
8361    addTiledImage: function( options ) {
8362        $.console.assert(options, "[Viewer.addTiledImage] options is required");
8363        $.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required");
8364        $.console.assert(!options.replace || (options.index > -1 && options.index < this.world.getItemCount()),
8365            "[Viewer.addTiledImage] if options.replace is used, options.index must be a valid index in Viewer.world");
8366
8367        var _this = this;
8368
8369        if (options.replace) {
8370            options.replaceItem = _this.world.getItemAt(options.index);
8371        }
8372
8373        this._hideMessage();
8374
8375        if (options.placeholderFillStyle === undefined) {
8376            options.placeholderFillStyle = this.placeholderFillStyle;
8377        }
8378        if (options.opacity === undefined) {
8379            options.opacity = this.opacity;
8380        }
8381        if (options.preload === undefined) {
8382            options.preload = this.preload;
8383        }
8384        if (options.compositeOperation === undefined) {
8385            options.compositeOperation = this.compositeOperation;
8386        }
8387        if (options.crossOriginPolicy === undefined) {
8388            options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
8389        }
8390        if (options.ajaxWithCredentials === undefined) {
8391            options.ajaxWithCredentials = this.ajaxWithCredentials;
8392        }
8393        if (options.loadTilesWithAjax === undefined) {
8394            options.loadTilesWithAjax = this.loadTilesWithAjax;
8395        }
8396        if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
8397            options.ajaxHeaders = this.ajaxHeaders;
8398        } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
8399            options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
8400        }
8401
8402        var myQueueItem = {
8403            options: options
8404        };
8405
8406        function raiseAddItemFailed( event ) {
8407            for (var i = 0; i < _this._loadQueue.length; i++) {
8408                if (_this._loadQueue[i] === myQueueItem) {
8409                    _this._loadQueue.splice(i, 1);
8410                    break;
8411                }
8412            }
8413
8414            if (_this._loadQueue.length === 0) {
8415                refreshWorld(myQueueItem);
8416            }
8417
8418             /**
8419             * Raised when an error occurs while adding a item.
8420             * @event add-item-failed
8421             * @memberOf OpenSeadragon.Viewer
8422             * @type {object}
8423             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8424             * @property {String} message
8425             * @property {String} source
8426             * @property {Object} options The options passed to the addTiledImage method.
8427             * @property {?Object} userData - Arbitrary subscriber-defined object.
8428             */
8429            _this.raiseEvent( 'add-item-failed', event );
8430
8431            if (options.error) {
8432                options.error(event);
8433            }
8434        }
8435
8436        function refreshWorld(theItem) {
8437            if (_this.collectionMode) {
8438                _this.world.arrange({
8439                    immediately: theItem.options.collectionImmediately,
8440                    rows: _this.collectionRows,
8441                    columns: _this.collectionColumns,
8442                    layout: _this.collectionLayout,
8443                    tileSize: _this.collectionTileSize,
8444                    tileMargin: _this.collectionTileMargin
8445                });
8446                _this.world.setAutoRefigureSizes(true);
8447            }
8448        }
8449
8450        if ($.isArray(options.tileSource)) {
8451            setTimeout(function() {
8452                raiseAddItemFailed({
8453                    message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
8454                    source: options.tileSource,
8455                    options: options
8456                });
8457            });
8458            return;
8459        }
8460
8461        this._loadQueue.push(myQueueItem);
8462
8463        function processReadyItems() {
8464            var queueItem, tiledImage, optionsClone;
8465            while (_this._loadQueue.length) {
8466                queueItem = _this._loadQueue[0];
8467                if (!queueItem.tileSource) {
8468                    break;
8469                }
8470
8471                _this._loadQueue.splice(0, 1);
8472
8473                if (queueItem.options.replace) {
8474                    var newIndex = _this.world.getIndexOfItem(queueItem.options.replaceItem);
8475                    if (newIndex != -1) {
8476                        queueItem.options.index = newIndex;
8477                    }
8478                    _this.world.removeItem(queueItem.options.replaceItem);
8479                }
8480
8481                tiledImage = new $.TiledImage({
8482                    viewer: _this,
8483                    source: queueItem.tileSource,
8484                    viewport: _this.viewport,
8485                    drawer: _this.drawer,
8486                    tileCache: _this.tileCache,
8487                    imageLoader: _this.imageLoader,
8488                    x: queueItem.options.x,
8489                    y: queueItem.options.y,
8490                    width: queueItem.options.width,
8491                    height: queueItem.options.height,
8492                    fitBounds: queueItem.options.fitBounds,
8493                    fitBoundsPlacement: queueItem.options.fitBoundsPlacement,
8494                    clip: queueItem.options.clip,
8495                    placeholderFillStyle: queueItem.options.placeholderFillStyle,
8496                    opacity: queueItem.options.opacity,
8497                    preload: queueItem.options.preload,
8498                    degrees: queueItem.options.degrees,
8499                    compositeOperation: queueItem.options.compositeOperation,
8500                    springStiffness: _this.springStiffness,
8501                    animationTime: _this.animationTime,
8502                    minZoomImageRatio: _this.minZoomImageRatio,
8503                    wrapHorizontal: _this.wrapHorizontal,
8504                    wrapVertical: _this.wrapVertical,
8505                    immediateRender: _this.immediateRender,
8506                    blendTime: _this.blendTime,
8507                    alwaysBlend: _this.alwaysBlend,
8508                    minPixelRatio: _this.minPixelRatio,
8509                    smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
8510                    iOSDevice: _this.iOSDevice,
8511                    crossOriginPolicy: queueItem.options.crossOriginPolicy,
8512                    ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
8513                    loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
8514                    ajaxHeaders: queueItem.options.ajaxHeaders,
8515                    debugMode: _this.debugMode
8516                });
8517
8518                if (_this.collectionMode) {
8519                    _this.world.setAutoRefigureSizes(false);
8520                }
8521                _this.world.addItem( tiledImage, {
8522                    index: queueItem.options.index
8523                });
8524
8525                if (_this._loadQueue.length === 0) {
8526                    //this restores the autoRefigureSizes flag to true.
8527                    refreshWorld(queueItem);
8528                }
8529
8530                if (_this.world.getItemCount() === 1 && !_this.preserveViewport) {
8531                    _this.viewport.goHome(true);
8532                }
8533
8534                if (_this.navigator) {
8535                    optionsClone = $.extend({}, queueItem.options, {
8536                        replace: false, // navigator already removed the layer, nothing to replace
8537                        originalTiledImage: tiledImage,
8538                        tileSource: queueItem.tileSource
8539                    });
8540
8541                    _this.navigator.addTiledImage(optionsClone);
8542                }
8543
8544                if (queueItem.options.success) {
8545                    queueItem.options.success({
8546                        item: tiledImage
8547                    });
8548                }
8549            }
8550        }
8551
8552        getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
8553
8554            myQueueItem.tileSource = tileSource;
8555
8556            // add everybody at the front of the queue that's ready to go
8557            processReadyItems();
8558        }, function( event ) {
8559            event.options = options;
8560            raiseAddItemFailed(event);
8561
8562            // add everybody at the front of the queue that's ready to go
8563            processReadyItems();
8564        } );
8565    },
8566
8567    /**
8568     * Add a simple image to the viewer.
8569     * The options are the same as the ones in {@link OpenSeadragon.Viewer#addTiledImage}
8570     * except for options.tileSource which is replaced by options.url.
8571     * @function
8572     * @param {Object} options - See {@link OpenSeadragon.Viewer#addTiledImage}
8573     * for all the options
8574     * @param {String} options.url - The URL of the image to add.
8575     * @fires OpenSeadragon.World.event:add-item
8576     * @fires OpenSeadragon.Viewer.event:add-item-failed
8577     */
8578    addSimpleImage: function(options) {
8579        $.console.assert(options, "[Viewer.addSimpleImage] options is required");
8580        $.console.assert(options.url, "[Viewer.addSimpleImage] options.url is required");
8581
8582        var opts = $.extend({}, options, {
8583            tileSource: {
8584                type: 'image',
8585                url:  options.url
8586            }
8587        });
8588        delete opts.url;
8589        this.addTiledImage(opts);
8590    },
8591
8592    // deprecated
8593    addLayer: function( options ) {
8594        var _this = this;
8595
8596        $.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." );
8597
8598        var optionsClone = $.extend({}, options, {
8599            success: function(event) {
8600                _this.raiseEvent("add-layer", {
8601                    options: options,
8602                    drawer: event.item
8603                });
8604            },
8605            error: function(event) {
8606                _this.raiseEvent("add-layer-failed", event);
8607            }
8608        });
8609
8610        this.addTiledImage(optionsClone);
8611        return this;
8612    },
8613
8614    // deprecated
8615    getLayerAtLevel: function( level ) {
8616        $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." );
8617        return this.world.getItemAt(level);
8618    },
8619
8620    // deprecated
8621    getLevelOfLayer: function( drawer ) {
8622        $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated;
8622 use World.getIndexOfItem() instead." );
8623        return this.world.getIndexOfItem(drawer);
8624    },
8625
8626    // deprecated
8627    getLayersCount: function() {
8628        $.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." );
8629        return this.world.getItemCount();
8630    },
8631
8632    // deprecated
8633    setLayerLevel: function( drawer, level ) {
8634        $.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." );
8635        return this.world.setItemIndex(drawer, level);
8636    },
8637
8638    // deprecated
8639    removeLayer: function( drawer ) {
8640        $.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." );
8641        return this.world.removeItem(drawer);
8642    },
8643
8644    /**
8645     * Force the viewer to redraw its contents.
8646     * @returns {OpenSeadragon.Viewer} Chainable.
8647     */
8648    forceRedraw: function() {
8649        THIS[ this.hash ].forceRedraw = true;
8650        return this;
8651    },
8652
8653    /**
8654     * @function
8655     * @return {OpenSeadragon.Viewer} Chainable.
8656     */
8657    bindSequenceControls: function(){
8658
vendor: 14,242 bytes, lines 8659-8954
8659        //////////////////////////////////////////////////////////////////////////
8660        // Image Sequence Controls
8661        //////////////////////////////////////////////////////////////////////////
8662        var onFocusHandler          = $.delegate( this, onFocus ),
8663            onBlurHandler           = $.delegate( this, onBlur ),
8664            onNextHandler           = $.delegate( this, onNext ),
8665            onPreviousHandler       = $.delegate( this, onPrevious ),
8666            navImages               = this.navImages,
8667            useGroup                = true;
8668
8669        if( this.showSequenceControl ){
8670
8671            if( this.previousButton || this.nextButton ){
8672                //if we are binding to custom buttons then layout and
8673                //grouping is the responsibility of the page author
8674                useGroup = false;
8675            }
8676
8677            this.previousButton = new $.Button({
8678                element:    this.previousButton ? $.getElement( this.previousButton ) : null,
8679                clickTimeThreshold: this.clickTimeThreshold,
8680                clickDistThreshold: this.clickDistThreshold,
8681                tooltip:    $.getString( "Tooltips.PreviousPage" ),
8682                srcRest:    resolveUrl( this.prefixUrl, navImages.previous.REST ),
8683                srcGroup:   resolveUrl( this.prefixUrl, navImages.previous.GROUP ),
8684                srcHover:   resolveUrl( this.prefixUrl, navImages.previous.HOVER ),
8685                srcDown:    resolveUrl( this.prefixUrl, navImages.previous.DOWN ),
8686                onRelease:  onPreviousHandler,
8687                onFocus:    onFocusHandler,
8688                onBlur:     onBlurHandler
8689            });
8690
8691            this.nextButton = new $.Button({
8692                element:    this.nextButton ? $.getElement( this.nextButton ) : null,
8693                clickTimeThreshold: this.clickTimeThreshold,
8694                clickDistThreshold: this.clickDistThreshold,
8695                tooltip:    $.getString( "Tooltips.NextPage" ),
8696                srcRest:    resolveUrl( this.prefixUrl, navImages.next.REST ),
8697                srcGroup:   resolveUrl( this.prefixUrl, navImages.next.GROUP ),
8698                srcHover:   resolveUrl( this.prefixUrl, navImages.next.HOVER ),
8699                srcDown:    resolveUrl( this.prefixUrl, navImages.next.DOWN ),
8700                onRelease:  onNextHandler,
8701                onFocus:    onFocusHandler,
8702                onBlur:     onBlurHandler
8703            });
8704
8705            if( !this.navPrevNextWrap ){
8706                this.previousButton.disable();
8707            }
8708
8709            if (!this.tileSources || !this.tileSources.length) {
8710                this.nextButton.disable();
8711            }
8712
8713            if( useGroup ){
8714                this.paging = new $.ButtonGroup({
8715                    buttons: [
8716                        this.previousButton,
8717                        this.nextButton
8718                    ],
8719                    clickTimeThreshold: this.clickTimeThreshold,
8720                    clickDistThreshold: this.clickDistThreshold
8721                });
8722
8723                this.pagingControl = this.paging.element;
8724
8725                if( this.toolbar ){
8726                    this.toolbar.addControl(
8727                        this.pagingControl,
8728                        {anchor: $.ControlAnchor.BOTTOM_RIGHT}
8729                    );
8730                }else{
8731                    this.addControl(
8732                        this.pagingControl,
8733                        {anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT}
8734                    );
8735                }
8736            }
8737        }
8738        return this;
8739    },
8740
8741
8742    /**
8743     * @function
8744     * @return {OpenSeadragon.Viewer} Chainable.
8745     */
8746    bindStandardControls: function(){
8747        //////////////////////////////////////////////////////////////////////////
8748        // Navigation Controls
8749        //////////////////////////////////////////////////////////////////////////
8750        var beginZoomingInHandler   = $.delegate( this, beginZoomingIn ),
8751            endZoomingHandler       = $.delegate( this, endZooming ),
8752            doSingleZoomInHandler   = $.delegate( this, doSingleZoomIn ),
8753            beginZoomingOutHandler  = $.delegate( this, beginZoomingOut ),
8754            doSingleZoomOutHandler  = $.delegate( this, doSingleZoomOut ),
8755            onHomeHandler           = $.delegate( this, onHome ),
8756            onFullScreenHandler     = $.delegate( this, onFullScreen ),
8757            onRotateLeftHandler     = $.delegate( this, onRotateLeft ),
8758            onRotateRightHandler    = $.delegate( this, onRotateRight ),
8759            onFlipHandler           = $.delegate( this, onFlip),
8760            onFocusHandler          = $.delegate( this, onFocus ),
8761            onBlurHandler           = $.delegate( this, onBlur ),
8762            navImages               = this.navImages,
8763            buttons                 = [],
8764            useGroup                = true;
8765
8766
8767        if ( this.showNavigationControl ) {
8768
8769            if( this.zoomInButton || this.zoomOutButton ||
8770                this.homeButton || this.fullPageButton ||
8771                this.rotateLeftButton || this.rotateRightButton ||
8772                this.flipButton ) {
8773                //if we are binding to custom buttons then layout and
8774                //grouping is the responsibility of the page author
8775                useGroup = false;
8776            }
8777
8778            if ( this.showZoomControl ) {
8779                buttons.push( this.zoomInButton = new $.Button({
8780                    element:    this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
8781                    clickTimeThreshold: this.clickTimeThreshold,
8782                    clickDistThreshold: this.clickDistThreshold,
8783                    tooltip:    $.getString( "Tooltips.ZoomIn" ),
8784                    srcRest:    resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
8785                    srcGroup:   resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
8786                    srcHover:   resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
8787                    srcDown:    resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
8788                    onPress:    beginZoomingInHandler,
8789                    onRelease:  endZoomingHandler,
8790                    onClick:    doSingleZoomInHandler,
8791                    onEnter:    beginZoomingInHandler,
8792                    onExit:     endZoomingHandler,
8793                    onFocus:    onFocusHandler,
8794                    onBlur:     onBlurHandler
8795                }));
8796
8797                buttons.push( this.zoomOutButton = new $.Button({
8798                    element:    this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
8799                    clickTimeThreshold: this.clickTimeThreshold,
8800                    clickDistThreshold: this.clickDistThreshold,
8801                    tooltip:    $.getString( "Tooltips.ZoomOut" ),
8802                    srcRest:    resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
8803                    srcGroup:   resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
8804                    srcHover:   resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
8805                    srcDown:    resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
8806                    onPress:    beginZoomingOutHandler,
8807                    onRelease:  endZoomingHandler,
8808                    onClick:    doSingleZoomOutHandler,
8809                    onEnter:    beginZoomingOutHandler,
8810                    onExit:     endZoomingHandler,
8811                    onFocus:    onFocusHandler,
8812                    onBlur:     onBlurHandler
8813                }));
8814            }
8815
8816            if ( this.showHomeControl ) {
8817                buttons.push( this.homeButton = new $.Button({
8818                    element:    this.homeButton ? $.getElement( this.homeButton ) : null,
8819                    clickTimeThreshold: this.clickTimeThreshold,
8820                    clickDistThreshold: this.clickDistThreshold,
8821                    tooltip:    $.getString( "Tooltips.Home" ),
8822                    srcRest:    resolveUrl( this.prefixUrl, navImages.home.REST ),
8823                    srcGroup:   resolveUrl( this.prefixUrl, navImages.home.GROUP ),
8824                    srcHover:   resolveUrl( this.prefixUrl, navImages.home.HOVER ),
8825                    srcDown:    resolveUrl( this.prefixUrl, navImages.home.DOWN ),
8826                    onRelease:  onHomeHandler,
8827                    onFocus:    onFocusHandler,
8828                    onBlur:     onBlurHandler
8829                }));
8830            }
8831
8832            if ( this.showFullPageControl ) {
8833                buttons.push( this.fullPageButton = new $.Button({
8834                    element:    this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
8835                    clickTimeThreshold: this.clickTimeThreshold,
8836                    clickDistThreshold: this.clickDistThreshold,
8837                    tooltip:    $.getString( "Tooltips.FullPage" ),
8838                    srcRest:    resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
8839                    srcGroup:   resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
8840                    srcHover:   resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
8841                    srcDown:    resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
8842                    onRelease:  onFullScreenHandler,
8843                    onFocus:    onFocusHandler,
8844                    onBlur:     onBlurHandler
8845                }));
8846            }
8847
8848            if ( this.showRotationControl ) {
8849                buttons.push( this.rotateLeftButton = new $.Button({
8850                    element:    this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null,
8851                    clickTimeThreshold: this.clickTimeThreshold,
8852                    clickDistThreshold: this.clickDistThreshold,
8853                    tooltip:    $.getString( "Tooltips.RotateLeft" ),
8854                    srcRest:    resolveUrl( this.prefixUrl, navImages.rotateleft.REST ),
8855                    srcGroup:   resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ),
8856                    srcHover:   resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ),
8857                    srcDown:    resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ),
8858                    onRelease:  onRotateLeftHandler,
8859                    onFocus:    onFocusHandler,
8860                    onBlur:     onBlurHandler
8861                }));
8862
8863                buttons.push( this.rotateRightButton = new $.Button({
8864                    element:    this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null,
8865                    clickTimeThreshold: this.clickTimeThreshold,
8866                    clickDistThreshold: this.clickDistThreshold,
8867                    tooltip:    $.getString( "Tooltips.RotateRight" ),
8868                    srcRest:    resolveUrl( this.prefixUrl, navImages.rotateright.REST ),
8869                    srcGroup:   resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ),
8870                    srcHover:   resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ),
8871                    srcDown:    resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ),
8872                    onRelease:  onRotateRightHandler,
8873                    onFocus:    onFocusHandler,
8874                    onBlur:     onBlurHandler
8875                }));
8876            }
8877
8878            if ( this.showFlipControl ) {
8879                buttons.push( this.flipButton = new $.Button({
8880                    element:    this.flipButton ? $.getElement( this.flipButton ) : null,
8881                    clickTimeThreshold: this.clickTimeThreshold,
8882                    clickDistThreshold: this.clickDistThreshold,
8883                    tooltip:    $.getString( "Tooltips.Flip" ),
8884                    srcRest:    resolveUrl( this.prefixUrl, navImages.flip.REST ),
8885                    srcGroup:   resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
8886                    srcHover:   resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
8887                    srcDown:    resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
8888                    onRelease:  onFlipHandler,
8889                    onFocus:    onFocusHandler,
8890                    onBlur:     onBlurHandler
8891                }));
8892            }
8893
8894            if ( useGroup ) {
8895                this.buttons = new $.ButtonGroup({
8896                    buttons:            buttons,
8897                    clickTimeThreshold: this.clickTimeThreshold,
8898                    clickDistThreshold: this.clickDistThreshold
8899                });
8900
8901                this.navControl  = this.buttons.element;
8902                this.addHandler( 'open', $.delegate( this, lightUp ) );
8903
8904                if( this.toolbar ){
8905                    this.toolbar.addControl(
8906                        this.navControl,
8907                        {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
8908                    );
8909                } else {
8910                    this.addControl(
8911                        this.navControl,
8912                        {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
8913                    );
8914                }
8915            }
8916
8917        }
8918        return this;
8919    },
8920
8921    /**
8922     * Gets the active page of a sequence
8923     * @function
8924     * @return {Number}
8925     */
8926    currentPage: function() {
8927        return this._sequenceIndex;
8928    },
8929
8930    /**
8931     * @function
8932     * @return {OpenSeadragon.Viewer} Chainable.
8933     * @fires OpenSeadragon.Viewer.event:page
8934     */
8935    goToPage: function( page ){
8936        if( this.tileSources && page >= 0 && page < this.tileSources.length ){
8937            this._sequenceIndex = page;
8938
8939            this._updateSequenceButtons( page );
8940
8941            this.open( this.tileSources[ page ] );
8942
8943            if( this.referenceStrip ){
8944                this.referenceStrip.setFocus( page );
8945            }
8946
8947            /**
8948             * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}).
8949             *
8950             * @event page
8951             * @memberof OpenSeadragon.Viewer
8952             * @type {Object}
8953             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
8954             * @property {Number} page - The page index.
8955             * @property {?Object} userData - Arbitrary subscriber-defined object.
8956             */
8957            this.raiseEvent( 'page', { page: page } );
8958        }
8959
8960        return this;
8961    },
8962
8963   /**
8964     * Adds an html element as an overlay to the current viewport.  Useful for
8965     * highlighting words or areas of interest on an image or other zoomable
8966     * interface. The overlays added via this method are removed when the viewport
8967     * is closed which include when changing page.
8968     * @method
8969     * @param {Element|String|Object} element - A reference to an element or an id for
8970     *      the element which will be overlaid. Or an Object specifying the configuration for the overlay.
8971     *      If using an object, see {@link OpenSeadragon.Overlay} for a list of
8972     *      all available options.
8973     * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
8974     *      rectangle which will be overlaid. This is a viewport relative location.
8975     * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
8976     *      viewport which the location coordinates will be treated as relative
8977     *      to.
8978     * @param {function} [onDraw] - If supplied the callback is called when the overlay
8979     *      needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
8980     *      It is passed position, size and element.
8981     * @return {OpenSeadragon.Viewer} Chainable.
8982     * @fires OpenSeadragon.Viewer.event:add-overlay
8983     */
8984    addOverlay: function( element, location, placement, onDraw ) {
8985        var options;
8986        if( $.isPlainObject( element ) ){
8987            options = element;
8988        } else {
8989            options = {
8990                element: element,
8991                location: location,
8992                placement: placement,
8993                onDraw: onDraw
8994            };
8995        }
8996
8997        element = $.getElement( options.element );
8998
8999        if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) {
9000            // they're trying to add a duplicate overlay
9001            return this;
9002        }
9003
9004        var overlay = getOverlayObject( this, options);
9005        this.currentOverlays.push(overlay);
9006        overlay.drawHTML( this.overlaysContainer, this.viewport );
9007
9008        /**
9009         * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}).
9010         *
9011         * @event add-overlay
9012         * @memberof OpenSeadragon.Viewer
9013         * @type {object}
9014         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
9015         * @property {Element} element - The overlay element.
9016         * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
9017         * @property {OpenSeadragon.Placement} placement
9018         * @property {?Object} userData - Arbitrary subscriber-defined object.
9019         */
9020        this.raiseEvent( 'add-overlay', {
9021            element: element,
9022            location: options.location,
9023            placement: options.placement
9024        });
9025        return this;
9026    },
9027
9028    /**
9029     * Updates the overlay represented by the reference to the element or
9030     * element id moving it to the new location, relative to the new placement.
9031     * @method
9032     * @param {Element|String} element - A reference to an element or an id for
9033     *      the element which is overlaid.
9034     * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
9035     *      rectangle which will be overlaid. This is a viewport relative location.
9036     * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
9037     *      viewport which the location coordinates will be treated as relative
9038     *      to.
9039     * @return {OpenSeadragon.Viewer} Chainable.
9040     * @fires OpenSeadragon.Viewer.event:update-overlay
9041     */
9042    updateOverlay: function( element, location, placement ) {
9043        var i;
9044
9045        element = $.getElement( element );
9046        i = getOverlayIndex( this.currentOverlays, element );
9047
9048        if ( i >= 0 ) {
9049            this.currentOverlays[ i ].update( location, placement );
9050            THIS[ this.hash ].forceRedraw = true;
9051            /**
9052             * Raised when an overlay's location or placement changes
9053             * (see {@link OpenSeadragon.Viewer#updateOverlay}).
9054             *
9055             * @event update-overlay
9056             * @memberof OpenSeadragon.Viewer
9057             * @type {object}
9058             * @property {OpenSeadragon.Viewer} eventSource - A reference to the
9059             * Viewer which raised the event.
9060             * @property {Element} element
9061             * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
9062             * @property {OpenSeadragon.Placement} placement
vendor: 4,336 bytes, lines 9063-9188
9063             * @property {?Object} userData - Arbitrary subscriber-defined object.
9064             */
9065            this.raiseEvent( 'update-overlay', {
9066                element: element,
9067                location: location,
9068                placement: placement
9069            });
9070        }
9071        return this;
9072    },
9073
9074    /**
9075     * Removes an overlay identified by the reference element or element id
9076     * and schedules an update.
9077     * @method
9078     * @param {Element|String} element - A reference to the element or an
9079     *      element id which represent the ovelay content to be removed.
9080     * @return {OpenSeadragon.Viewer} Chainable.
9081     * @fires OpenSeadragon.Viewer.event:remove-overlay
9082     */
9083    removeOverlay: function( element ) {
9084        var i;
9085
9086        element = $.getElement( element );
9087        i = getOverlayIndex( this.currentOverlays, element );
9088
9089        if ( i >= 0 ) {
9090            this.currentOverlays[ i ].destroy();
9091            this.currentOverlays.splice( i, 1 );
9092            THIS[ this.hash ].forceRedraw = true;
9093            /**
9094             * Raised when an overlay is removed from the viewer
9095             * (see {@link OpenSeadragon.Viewer#removeOverlay}).
9096             *
9097             * @event remove-overlay
9098             * @memberof OpenSeadragon.Viewer
9099             * @type {object}
9100             * @property {OpenSeadragon.Viewer} eventSource - A reference to the
9101             * Viewer which raised the event.
9102             * @property {Element} element - The overlay element.
9103             * @property {?Object} userData - Arbitrary subscriber-defined object.
9104             */
9105            this.raiseEvent( 'remove-overlay', {
9106                element: element
9107            });
9108        }
9109        return this;
9110    },
9111
9112    /**
9113     * Removes all currently configured Overlays from this Viewer and schedules
9114     * an update.
9115     * @method
9116     * @return {OpenSeadragon.Viewer} Chainable.
9117     * @fires OpenSeadragon.Viewer.event:clear-overlay
9118     */
9119    clearOverlays: function() {
9120        while ( this.currentOverlays.length > 0 ) {
9121            this.currentOverlays.pop().destroy();
9122        }
9123        THIS[ this.hash ].forceRedraw = true;
9124        /**
9125         * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}).
9126         *
9127         * @event clear-overlay
9128         * @memberof OpenSeadragon.Viewer
9129         * @type {object}
9130         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
9131         * @property {?Object} userData - Arbitrary subscriber-defined object.
9132         */
9133        this.raiseEvent( 'clear-overlay', {} );
9134        return this;
9135    },
9136
9137     /**
9138     * Finds an overlay identified by the reference element or element id
9139     * and returns it as an object, return null if not found.
9140     * @method
9141     * @param {Element|String} element - A reference to the element or an
9142     *      element id which represents the overlay content.
9143     * @return {OpenSeadragon.Overlay} the matching overlay or null if none found.
9144     */
9145    getOverlayById: function( element ) {
9146        var i;
9147
9148        element = $.getElement( element );
9149        i = getOverlayIndex( this.currentOverlays, element );
9150
9151        if (i >= 0) {
9152            return this.currentOverlays[i];
9153        } else {
9154            return null;
9155        }
9156    },
9157
9158    /**
9159     * Updates the sequence buttons.
9160     * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
9161     * @private
9162     * @param {Number} Sequence Value
9163     */
9164    _updateSequenceButtons: function( page ) {
9165
9166            if ( this.nextButton ) {
9167                if(!this.tileSources || this.tileSources.length - 1 === page) {
9168                    //Disable next button
9169                    if ( !this.navPrevNextWrap ) {
9170                        this.nextButton.disable();
9171                    }
9172                } else {
9173                    this.nextButton.enable();
9174                }
9175            }
9176            if ( this.previousButton ) {
9177                if ( page > 0 ) {
9178                    //Enable previous button
9179                    this.previousButton.enable();
9180                } else {
9181                    if ( !this.navPrevNextWrap ) {
9182                        this.previousButton.disable();
9183                    }
9184                }
9185            }
9186      },
9187
9188    /**
9189     * Display a message in the viewport
9190     * @function OpenSeadragon.Viewer.prototype._showMessage
9191     * @private
9192     * @param {String} text message
9193     */
9194    _showMessage: function ( message ) {
9195        this._hideMessage();
9196
9197        var div = $.makeNeutralElement( "div" );
9198        div.appendChild( document.createTextNode( message ) );
9199
9200        this.messageDiv = $.makeCenteredNode( div );
9201
9202        $.addClass(this.messageDiv, "openseadragon-message");
9203
9204        this.container.appendChild( this.messageDiv );
9205    },
9206
9207    /**
9208     * Hide any currently displayed viewport message
9209     * @function OpenSeadragon.Viewer.prototype._hideMessage
9210     * @private
9211     */
9212    _hideMessage: function () {
9213        var div = this.messageDiv;
9214        if (div) {
9215            div.parentNode.removeChild(div);
9216            delete this.messageDiv;
9217        }
9218    },
9219
9220    /**
9221     * Gets this viewer's gesture settings for the given pointer device type.
9222     * @method
9223     * @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.).
9224     * @return {OpenSeadragon.GestureSettings}
9225     */
9226    gestureSettingsByDeviceType: function ( type ) {
9227        switch ( type ) {
9228            case 'mouse':
9229                return this.gestureSettingsMouse;
9230            case 'touch':
9231                return this.gestureSettingsTouch;
9232            case 'pen':
9233                return this.gestureSettingsPen;
9234            default:
9235                return this.gestureSettingsUnknown;
9236        }
9237    },
9238
9239    // private
9240    _drawOverlays: function() {
9241        var i,
9242            length = this.currentOverlays.length;
9243        for ( i = 0; i < length; i++ ) {
9244            this.currentOverlays[ i ].drawHTML( this.overlaysContainer, this.viewport );
9245        }
9246    },
9247
9248    /**
9249     * Cancel the "in flight" images.
9250     */
9251    _cancelPendingImages: function() {
9252        this._loadQueue = [];
9253    },
9254
9255    /**
9256     * Removes the reference strip and disables displaying it.
9257     * @function
9258     */
9259    removeReferenceStrip: function() {
9260        this.showReferenceStrip = false;
9261
9262        if (this.referenceStrip) {
9263            this.referenceStrip.destroy();
9264            this.referenceStrip = null;
9265        }
9266    },
9267
9268    /**
9269     * Enables and displays the reference strip based on the currently set tileSources.
9270     * Works only when the Viewer has sequenceMode set to true.
9271     * @function
9272     */
9273    addReferenceStrip: function() {
9274        this.showReferenceStrip = true;
9275
9276        if (this.sequenceMode) {
9277            if (this.referenceStrip) {
9278                return;
9279            }
9280
9281            if (this.tileSources.length && this.tileSources.length > 1) {
9282                this.referenceStrip = new $.ReferenceStrip({
9283                    id:          this.referenceStripElement,
9284                    position:    this.referenceStripPosition,
9285                    sizeRatio:   this.referenceStripSizeRatio,
9286                    scroll:      this.referenceStripScroll,
9287                    height:      this.referenceStripHeight,
9288                    width:       this.referenceStripWidth,
9289                    tileSources: this.tileSources,
9290                    prefixUrl:   this.prefixUrl,
9291                    useCanvas:   this.useCanvas,
9292                    viewer:      this
9293                });
9294
9295                this.referenceStrip.setFocus( this._sequenceIndex );
9296            }
9297        } else {
9298            $.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
9299        }
9300    }
9301});
9302
9303
9304/**
9305 * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y,
9306 * which was causing some calling operations to return NaN.
9307 * @returns {Point}
9308 * @private
9309 */
9310function _getSafeElemSize (oElement) {
9311    oElement = $.getElement( oElement );
9312
9313    return new $.Point(
9314        (oElement.clientWidth === 0 ? 1 : oElement.clientWidth),
9315        (oElement.clientHeight === 0 ? 1 : oElement.clientHeight)
9316    );
9317}
9318
9319
9320/**
9321 * @function
9322 * @private
9323 */
9324function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
9325    failCallback ) {
9326    var _this = viewer;
9327
9328    //allow plain xml strings or json strings to be parsed here
9329    if ( $.type( tileSource ) == 'string' ) {
9330        //xml should start with "<" and end with ">"
9331        if ( tileSource.match( /^\s*<.*>\s*$/ ) ) {
9332            tileSource = $.parseXml( tileSource );
9333        //json should start with "{" or "[" and end with "}" or "]"
9334        } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) {
9335            try {
9336              var tileSourceJ = $.parseJSON(tileSource);
9337              tileSource = tileSourceJ;
9338            } catch (e) {
9339              //tileSource = tileSource;
9340            }
9341        }
9342    }
9343
9344    function waitUntilReady(tileSource, originalTileSource) {
9345        if (tileSource.ready) {
9346            successCallback(tileSource);
9347        } else {
9348            tileSource.addHandler('ready', function () {
9349                successCallback(tileSource);
9350            });
9351            tileSource.addHandler('open-failed', function (event) {
9352                failCallback({
9353                    message: event.message,
9354                    source: originalTileSource
9355                });
9356            });
9357        }
9358    }
9359
9360    setTimeout( function() {
9361        if ( $.type( tileSource ) == 'string' ) {
9362            //If its still a string it means it must be a url at this point
9363            tileSource = new $.TileSource({
9364                url: tileSource,
9365                crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
9366                    imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
9367                ajaxWithCredentials: viewer.ajaxWithCredentials,
9368                ajaxHeaders: viewer.ajaxHeaders,
9369                useCanvas: viewer.useCanvas,
9370                success: function( event ) {
9371                    successCallback( event.tileSource );
9372                }
9373            });
9374            tileSource.addHandler( 'open-failed', function( event ) {
9375                failCallback( event );
9376            } );
9377
9378        } else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
9379            if (tileSource.crossOriginPolicy === undefined &&
9380                (imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) {
9381                tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ?
9382                    imgOptions.crossOriginPolicy : viewer.crossOriginPolicy;
9383            }
9384            if (tileSource.ajaxWithCredentials === undefined) {
9385                tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
9386            }
9387            if (tileSource.useCanvas === undefined) {
9388                tileSource.useCanvas = viewer.useCanvas;
9389            }
9390
9391            if ( $.isFunction( tileSource.getTileUrl ) ) {
9392                //Custom tile source
9393                var customTileSource = new $.TileSource( tileSource );
9394                customTileSource.getTileUrl = tileSource.getTileUrl;
9395                successCallback( customTileSource );
9396            } else {
9397                //inline configuration
9398                var $TileSource = $.TileSource.determineType( _this, tileSource );
9399                if ( !$TileSource ) {
9400                    failCallback( {
9401                        message: "Unable to load TileSource",
9402                        source: tileSource
9403                    });
9404                    return;
9405                }
9406                var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
9407                waitUntilReady(new $TileSource(options), tileSource);
9408            }
9409        } else {
9410            //can assume it's already a tile source implementation
9411            waitUntilReady(tileSource, tileSource);
9412        }
9413    });
9414}
9415
9416function getOverlayObject( viewer, overlay ) {
9417    if ( overlay instanceof $.Overlay ) {
9418        return overlay;
9419    }
9420
9421    var element = null;
9422    if ( overlay.element ) {
9423        element = $.getElement( overlay.element );
9424    } else {
9425        var id = overlay.id ?
vendor: 5,162 bytes, lines 9426-9597
9426            overlay.id :
9427            "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 );
9428
9429        element = $.getElement( overlay.id );
9430        if ( !element ) {
9431            element         = document.createElement( "a" );
9432            element.href    = "#/overlay/" + id;
9433        }
9434        element.id = id;
9435        $.addClass( element, overlay.className ?
9436            overlay.className :
9437            "openseadragon-overlay"
9438        );
9439    }
9440
9441    var location = overlay.location;
9442    var width = overlay.width;
9443    var height = overlay.height;
9444    if (!location) {
9445        var x = overlay.x;
9446        var y = overlay.y;
9447        if (overlay.px !== undefined) {
9448            var rect = viewer.viewport.imageToViewportRectangle(new $.Rect(
9449                overlay.px,
9450                overlay.py,
9451                width || 0,
9452                height || 0));
9453            x = rect.x;
9454            y = rect.y;
9455            width = width !== undefined ? rect.width : undefined;
9456            height = height !== undefined ? rect.height : undefined;
9457        }
9458        location = new $.Point(x, y);
9459    }
9460
9461    var placement = overlay.placement;
9462    if (placement && $.type(placement) === "string") {
9463        placement = $.Placement[overlay.placement.toUpperCase()];
9464    }
9465
9466    return new $.Overlay({
9467        element: element,
9468        location: location,
9469        placement: placement,
9470        onDraw: overlay.onDraw,
9471        checkResize: overlay.checkResize,
9472        width: width,
9473        height: height,
9474        rotationMode: overlay.rotationMode
9475    });
9476}
9477
9478/**
9479 * @private
9480 * @inner
9481 * Determines the index of the given overlay in the given overlays array.
9482 */
9483function getOverlayIndex( overlays, element ) {
9484    var i;
9485    for ( i = overlays.length - 1; i >= 0; i-- ) {
9486        if ( overlays[ i ].element === element ) {
9487            return i;
9488        }
9489    }
9490
9491    return -1;
9492}
9493
9494///////////////////////////////////////////////////////////////////////////////
9495// Schedulers provide the general engine for animation
9496///////////////////////////////////////////////////////////////////////////////
9497function scheduleUpdate( viewer, updateFunc ){
9498    return $.requestAnimationFrame( function(){
9499        updateFunc( viewer );
9500    } );
9501}
9502
9503
9504//provides a sequence in the fade animation
9505function scheduleControlsFade( viewer ) {
9506    $.requestAnimationFrame( function(){
9507        updateControlsFade( viewer );
9508    });
9509}
9510
9511
9512//initiates an animation to hide the controls
9513function beginControlsAutoHide( viewer ) {
9514    if ( !viewer.autoHideControls ) {
9515        return;
9516    }
9517    viewer.controlsShouldFade = true;
9518    viewer.controlsFadeBeginTime =
9519        $.now() +
9520        viewer.controlsFadeDelay;
9521
9522    window.setTimeout( function(){
9523        scheduleControlsFade( viewer );
9524    }, viewer.controlsFadeDelay );
9525}
9526
9527
9528//determines if fade animation is done or continues the animation
9529function updateControlsFade( viewer ) {
9530    var currentTime,
9531        deltaTime,
9532        opacity,
9533        i;
9534    if ( viewer.controlsShouldFade ) {
9535        currentTime = $.now();
9536        deltaTime = currentTime - viewer.controlsFadeBeginTime;
9537        opacity = 1.0 - deltaTime / viewer.controlsFadeLength;
9538
9539        opacity = Math.min( 1.0, opacity );
9540        opacity = Math.max( 0.0, opacity );
9541
9542        for ( i = viewer.controls.length - 1; i >= 0; i--) {
9543            if (viewer.controls[ i ].autoFade) {
9544                viewer.controls[ i ].setOpacity( opacity );
9545            }
9546        }
9547
9548        if ( opacity > 0 ) {
9549            // fade again
9550            scheduleControlsFade( viewer );
9551        }
9552    }
9553}
9554
9555
9556//stop the fade animation on the controls and show them
9557function abortControlsAutoHide( viewer ) {
9558    var i;
9559    viewer.controlsShouldFade = false;
9560    for ( i = viewer.controls.length - 1; i >= 0; i-- ) {
9561        viewer.controls[ i ].setOpacity( 1.0 );
9562    }
9563}
9564
9565
9566
9567///////////////////////////////////////////////////////////////////////////////
9568// Default view event handlers.
9569///////////////////////////////////////////////////////////////////////////////
9570function onFocus(){
9571    abortControlsAutoHide( this );
9572}
9573
9574function onBlur(){
9575    beginControlsAutoHide( this );
9576
9577}
9578
9579function onCanvasKeyDown( event ) {
9580    var canvasKeyDownEventArgs = {
9581      originalEvent: event.originalEvent,
9582      preventDefaultAction: event.preventDefaultAction,
9583      preventVerticalPan: event.preventVerticalPan,
9584      preventHorizontalPan: event.preventHorizontalPan
9585    };
9586
9587    /**
9588     * Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
9589     *
9590     * @event canvas-key
9591     * @memberof OpenSeadragon.Viewer
9592     * @type {object}
9593     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9594     * @property {Object} originalEvent - The original DOM event.
9595     * @property {Boolean} preventDefaultAction - Set to true to prevent default keyboard behaviour. Default: false.
9596     * @property {Boolean} preventVerticalPan - Set to true to prevent keyboard vertical panning. Default: false.
9597     * @property {Boolean} preventHorizontalPan - Set to true to prevent keyboard horizontal panning. Default: false.
9598     * @property {?Object} userData - Arbitrary subscriber-defined object.
9599     */
9600
9601    this.raiseEvent('canvas-key', canvasKeyDownEventArgs);
9602
9603    if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9604        switch( event.keyCode ){
9605            case 38://up arrow
9606                if (!canvasKeyDownEventArgs.preventVerticalPan) {
9607                  if ( event.shift ) {
9608                    this.viewport.zoomBy(1.1);
9609                  } else {
9610                    this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -this.pixelsPerArrowPress)));
9611                  }
9612                  this.viewport.applyConstraints();
9613                }
9614                return false;
9615            case 40://down arrow
9616                if (!canvasKeyDownEventArgs.preventVerticalPan) {
9617                  if ( event.shift ) {
9618                    this.viewport.zoomBy(0.9);
9619                  } else {
9620                    this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, this.pixelsPerArrowPress)));
9621                  }
9622                  this.viewport.applyConstraints();
9623                }
9624                return false;
9625            case 37://left arrow
9626                if (!canvasKeyDownEventArgs.preventHorizontalPan) {
9627                  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-this.pixelsPerArrowPress, 0)));
9628                  this.viewport.applyConstraints();
9629                }
9630                return false;
9631            case 39://right arrow
9632                if (!canvasKeyDownEventArgs.preventHorizontalPan) {
9633                  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(this.pixelsPerArrowPress, 0)));
9634                  this.viewport.applyConstraints();
9635                }
9636                return false;
9637            default:
9638                //console.log( 'navigator keycode %s', event.keyCode );
9639                return true;
9640        }
9641    } else {
9642        return true;
9643    }
9644}
9645function onCanvasKeyPress( event ) {
9646    var canvasKeyPressEventArgs = {
9647      originalEvent: event.originalEvent,
9648      preventDefaultAction: event.preventDefaultAction,
9649      preventVerticalPan: event.preventVerticalPan,
9650      preventHorizontalPan: event.preventHorizontalPan
9651    };
9652
9653    // This event is documented in onCanvasKeyDown
9654    this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
9655
9656    if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9657        switch( event.keyCode ){
9658            case 43://=|+
9659            case 61://=|+
9660                this.viewport.zoomBy(1.1);
9661                this.viewport.applyConstraints();
9662                return false;
9663            case 45://-|_
9664                this.viewport.zoomBy(0.9);
9665                this.viewport.applyConstraints();
9666                return false;
9667            case 48://0|)
9668                this.viewport.goHome();
9669                this.viewport.applyConstraints();
9670                return false;
9671            case 119://w
9672            case 87://W
9673                if (!canvasKeyPressEventArgs.preventVerticalPan) {
9674                    if ( event.shift ) {
9675                        this.viewport.zoomBy(1.1);
9676                    } else {
9677                        this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
9678                    }
9679                    this.viewport.applyConstraints();
9680                  }
9681                  return false;
9682            case 115://s
9683            case 83://S
9684                if (!canvasKeyPressEventArgs.preventVerticalPan) {
9685                  if ( event.shift ) {
9686                    this.viewport.zoomBy(0.9);
9687                  } else {
9688                    this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
9689                  }
9690                  this.viewport.applyConstraints();
9691                }
9692                return false;
9693            case 97://a
9694                if (!canvasKeyPressEventArgs.preventHorizontalPan) {
9695                  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0
9695)));
9696                  this.viewport.applyConstraints();
9697                }
9698                return false;
9699            case 100://d
9700                if (!canvasKeyPressEventArgs.preventHorizontalPan) {
9701                  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
9702                  this.viewport.applyConstraints();
9703                }
9704                return false;
9705            case 114: //r - clockwise rotation
9706              if(this.viewport.flipped){
9707                this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
9708              } else{
9709                this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
9710              }
9711              this.viewport.applyConstraints();
9712              return false;
9713            case 82: //R - counterclockwise  rotation
9714              if(this.viewport.flipped){
9715                this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
9716              } else{
9717                this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
9718              }
9719              this.viewport.applyConstraints();
9720              return false;
9721            case 102: //f
9722              this.viewport.toggleFlip();
9723              return false;
9724            default:
9725                // console.log( 'navigator keycode %s', event.keyCode );
9726                return true;
9727        }
9728    } else {
9729        return true;
9730    }
9731}
9732
9733function onCanvasClick( event ) {
9734    var gestureSettings;
9735
9736    var haveKeyboardFocus = document.activeElement == this.canvas;
9737
9738    // If we don't have keyboard focus, request it.
9739    if ( !haveKeyboardFocus ) {
9740        this.canvas.focus();
9741    }
9742    if(this.viewport.flipped){
9743        event.position.x = this.viewport.getContainerSize().x - event.position.x;
9744    }
9745
9746    var canvasClickEventArgs = {
9747        tracker: event.eventSource,
9748        position: event.position,
9749        quick: event.quick,
9750        shift: event.shift,
9751        originalEvent: event.originalEvent,
9752        preventDefaultAction: event.preventDefaultAction
9753    };
9754
9755    /**
9756     * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9757     *
9758     * @event canvas-click
9759     * @memberof OpenSeadragon.Viewer
9760     * @type {object}
9761     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9762     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9763     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9764     * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
9765     * @property {Boolean} shift - True if the shift key was pressed during this event.
9766     * @property {Object} originalEvent - The original DOM event.
9767     * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
9768     * @property {?Object} userData - Arbitrary subscriber-defined object.
9769     */
9770    this.raiseEvent( 'canvas-click', canvasClickEventArgs);
9771
9772    if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) {
9773        gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9774        if ( gestureSettings.clickToZoom ) {
9775            this.viewport.zoomBy(
9776                event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9777                gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
9778            );
9779            this.viewport.applyConstraints();
9780        }
9781    }
9782}
9783
9784function onCanvasDblClick( event ) {
9785    var gestureSettings;
9786
9787    var canvasDblClickEventArgs = {
9788        tracker: event.eventSource,
9789        position: event.position,
9790        shift: event.shift,
9791        originalEvent: event.originalEvent,
9792        preventDefaultAction: event.preventDefaultAction
9793    };
9794
9795    /**
9796     * Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9797     *
9798     * @event canvas-double-click
9799     * @memberof OpenSeadragon.Viewer
9800     * @type {object}
9801     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9802     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9803     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9804     * @property {Boolean} shift - True if the shift key was pressed during this event.
9805     * @property {Object} originalEvent - The original DOM event.
9806     * @property {Boolean} preventDefaultAction - Set to true to prevent default double tap to zoom behaviour. Default: false.
9807     * @property {?Object} userData - Arbitrary subscriber-defined object.
9808     */
9809    this.raiseEvent( 'canvas-double-click', canvasDblClickEventArgs);
9810
9811    if ( !canvasDblClickEventArgs.preventDefaultAction && this.viewport ) {
9812        gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9813        if ( gestureSettings.dblClickToZoom ) {
9814            this.viewport.zoomBy(
9815                event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9816                gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
9817            );
9818            this.viewport.applyConstraints();
9819        }
9820    }
9821}
9822
9823function onCanvasDrag( event ) {
9824    var gestureSettings;
9825
9826    var canvasDragEventArgs = {
9827        tracker: event.eventSource,
9828        position: event.position,
9829        delta: event.delta,
9830        speed: event.speed,
9831        direction: event.direction,
9832        shift: event.shift,
9833        originalEvent: event.originalEvent,
9834        preventDefaultAction: event.preventDefaultAction
9835    };
9836
9837    /**
9838     * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9839     *
9840     * @event canvas-drag
9841     * @memberof OpenSeadragon.Viewer
9842     * @type {object}
9843     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9844     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9845     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9846     * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
9847     * @property {Number} speed - Current computed speed, in pixels per second.
9848     * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
9849     * @property {Boolean} shift - True if the shift key was pressed during this event.
9850     * @property {Object} originalEvent - The original DOM event.
9851     * @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false.
9852     * @property {?Object} userData - Arbitrary subscriber-defined object.
9853     */
9854    this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
9855
9856    if ( !canvasDragEventArgs.preventDefaultAction && this.viewport ) {
9857        gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9858        if( !this.panHorizontal ){
9859            event.delta.x = 0;
9860        }
9861        if( !this.panVertical ){
9862            event.delta.y = 0;
9863        }
9864        if(this.viewport.flipped){
9865            event.delta.x = -event.delta.x;
9866        }
9867
9868        if( this.constrainDuringPan ){
9869            var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
9870
9871            this.viewport.centerSpringX.target.value += delta.x;
9872            this.viewport.centerSpringY.target.value += delta.y;
9873
9874            var bounds = this.viewport.getBounds();
9875            var constrainedBounds = this.viewport.getConstrainedBounds();
9876
9877            this.viewport.centerSpringX.target.value -= delta.x;
9878            this.viewport.centerSpringY.target.value -= delta.y;
9879
9880            if (bounds.x != constrainedBounds.x) {
9881                event.delta.x = 0;
9882            }
9883
9884            if (bounds.y != constrainedBounds.y) {
9885                event.delta.y = 0;
9886            }
9887        }
9888
9889        this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan);
9890    }
9891}
9892
9893function onCanvasDragEnd( event ) {
9894    if (!event.preventDefaultAction && this.viewport) {
9895        var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType);
9896        if (gestureSettings.flickEnabled &&
9897            event.speed >= gestureSettings.flickMinSpeed) {
9898            var amplitudeX = 0;
9899            if (this.panHorizontal) {
9900                amplitudeX = gestureSettings.flickMomentum * event.speed *
9901                    Math.cos(event.direction);
9902            }
9903            var amplitudeY = 0;
9904            if (this.panVertical) {
9905                amplitudeY = gestureSettings.flickMomentum * event.speed *
9906                    Math.sin(event.direction);
9907            }
9908            var center = this.viewport.pixelFromPoint(
9909                this.viewport.getCenter(true));
9910            var target = this.viewport.pointFromPixel(
9911                new $.Point(center.x - amplitudeX, center.y - amplitudeY));
9912            this.viewport.panTo(target, false);
9913        }
9914        this.viewport.applyConstraints();
9915    }
9916    /**
9917     * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
9918     *
9919     * @event canvas-drag-end
9920     * @memberof OpenSeadragon.Viewer
9921     * @type {object}
9922     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9923     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9924     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9925     * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second.
9926     * @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
9927     * @property {Boolean} shift - True if the shift key was pressed during this event.
9928     * @property {Object} originalEvent - The original DOM event.
9929     * @property {?Object} userData - Arbitrary subscriber-defined object.
9930     */
9931    this.raiseEvent('canvas-drag-end', {
9932        tracker: event.eventSource,
9933        position: event.position,
9934        speed: event.speed,
9935        direction: event.direction,
9936        shift: event.shift,
9937        originalEvent: event.originalEvent
9938    });
9939}
9940
9941function onCanvasEnter( event ) {
9942    /**
9943     * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element.
9944     *
9945     * @event canvas-enter
9946     * @memberof OpenSeadragon.Viewer
9947     * @type {object}
9948     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9949     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9950     * @property {String} pointerType - "mouse", "touch", "pen", etc.
9951     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9952     * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9953     * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9954     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9955     * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9956     * @property {Object} originalEvent - The original DOM event.
9957     * @property {?Object} userData - Arbitrary subscriber-defined object.
9958     */
9959    this.raiseEvent( 'canvas-enter', {
9960        tracker: event.eventSource,
9961        pointerType: event.pointerType,
9962        position: event.position,
9963        buttons: event.buttons,
9964        pointers: event.pointers,
9965        insideElementPressed: event.insideElementPressed,
9966        buttonDownAny: event.buttonDownAny,
9967        originalEvent: event.originalEvent
9968    });
9969}
9970
9971function onCanvasExit( event ) {
9972
9973    if (window.location != window.parent.location){
9974        $.MouseTracker.resetAllMouseTrackers();
9975    }
9976
9977    /**
9978     * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
9979     *
9980     * @event canvas-exit
9981     * @memberof OpenSeadragon.Viewer
9982     * @type {object}
9983     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9984     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9985     * @property {String} pointerType - "mouse", "touch", "pen", etc.
9986     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9987     * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9988     * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9989     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9990     * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9991     * @property {Object} originalEvent - The original DOM event.
9992     * @property {?Object} userData - Arbitrary subscriber-defined object.
9993     */
9994    this.raiseEvent( 'canvas-exit', {
9995        tracker: event.eventSource,
9996        pointerType: event.pointerType,
9997        position: event.position,
9998        buttons: event.buttons,
9999        pointers: event.pointers,
10000        insideElementPressed: event.insideElementPressed,
10001        buttonDownAny: event.buttonDownAny,
10002        originalEvent: event.originalEvent
10003    });
10004}
10005
10006function onCanvasPress( event ) {
10007    /**
10008     * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element.
10009     *
10010     * @event canvas-press
10011     * @memberof OpenSeadragon.Viewer
10012     * @type {object}
10013     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10014     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10015     * @property {String} pointerType - "mouse", "touch", "pen", etc.
10016     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10017     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
10018     * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
10019     * @property {Object} originalEvent - The original DOM event.
10020     * @property {?Object} userData - Arbitrary subscriber-defined object.
10021     */
10022    this.raiseEvent( 'canvas-press', {
10023        tracker: event.eventSource,
10024        pointerType: event.pointerType,
10025        position: event.position,
10026        insideElementPressed: event.insideElementPressed,
10027        insideElementReleased: event.insideElementReleased,
10028        originalEvent: event.originalEvent
10029    });
10030}
10031
10032function onCanvasRelease( event ) {
10033    /**
10034     * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
10035     *
10036     * @event canvas-release
10037     * @memberof OpenSeadragon.Viewer
10038     * @type {object}
10039     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10040     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10041     * @property {String} pointerType - "mouse", "touch", "pen", etc.
10042     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10043     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
10044     * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
10045     * @property {Object} originalEvent - The original DOM event.
10046     * @property {?Object} userData - Arbitrary subscriber-defined object.
10047     */
10048    this.raiseEvent( 'canvas-release', {
10049        tracker: event.eventSource,
10050        pointerType: event.pointerType,
10051        position: event.position,
10052        insideElementPressed: event.insideElementPressed,
10053        insideElementReleased: event.insideElementReleased,
10054        originalEvent: event.originalEvent
10055    });
10056}
10057
10058function onCanvasNonPrimaryPress( event ) {
10059    /**
10060     * Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element.
10061     *
10062     * @event canvas-nonprimary-press
10063     * @memberof OpenSeadragon.Viewer
10064     * @type {object}
10065     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10066     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10067     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10068     * @property {String} pointerType - "mouse", "touch", "pen", etc.
10069     * @property {Number} button - Button which caused the event.
10070     *      -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
10071     * @property {Number} buttons - Current buttons pressed.
10072     *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
10073     * @property {Object} originalEvent - The original DOM event.
10074     * @property {?Object} userData - Arbitrary subscriber-defined object.
10075     */
10076    this.raiseEvent( 'canvas-nonprimary-press', {
10077        tracker: event.eventSource,
10078        position: event.position,
10079        pointerType: event.pointerType,
10080        button: event.button,
10081        buttons: event.buttons,
10082        originalEvent: event.originalEvent
10083    });
10084}
10085
10086function onCanvasNonPrimaryRelease( event ) {
10087    /**
10088     * Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element.
10089     *
10090     * @event canvas-nonprimary-release
10091     * @memberof OpenSeadragon.Viewer
10092     * @type {object}
10093     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10094     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10095     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10096     * @property {String} pointerType - "mouse", "touch", "pen", etc.
10097     * @property {Number} button - Button which caused the event.
10098     *      -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
10099     * @property {Number} buttons - Current buttons pressed.
10100     *      Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
10101     * @property {Object} originalEvent - The original DOM event.
10102     * @property {?Object} userData - Arbitrary subscriber-defined object.
10103     */
10104    this.raiseEvent( 'canvas-nonprimary-release', {
10105        tracker: event.eventSource,
10106        position: event.position,
10107        pointerType: event.pointerType,
10108        button: event.button,
10109        buttons: event.buttons,
10110        originalEvent: event.originalEvent
10111    });
10112}
10113
10114function onCanvasPinch( event ) {
10115    var gestureSettings,
10116        centerPt,
10117        lastCenterPt,
10118        panByPt;
10119
10120    if ( !event.preventDefaultAction && this.viewport ) {
10121        gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
10122        if ( gestureSettings.pinchToZoom ) {
10123            centerPt = this.viewport.pointFromPixel( event.center, true );
10124            lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true );
10125            panByPt = lastCenterPt.minus( centerPt );
10126            if( !this.panHorizontal ) {
10127                panByPt.x = 0;
10128            }
10129            if( !this.panVertical ) {
10130                panByPt.y = 0;
10131            }
10132            this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true );
10133            if ( gestureSettings.zoomToRefPoint ) {
10134                this.viewport.panBy(panByPt, true);
10135            }
10136            this.viewport.applyConstraints();
10137        }
10138        if ( gestureSettings.pinchRotate ) {
10139            // Pinch rotate
10140            var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y,
10141                event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
10142            var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
10143                event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
10144            this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
10145        }
10146    }
10147    /**
10148     * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
10149     *
10150     * @event canvas-pinch
10151     * @memberof OpenSeadragon.Viewer
10152     * @type {object}
10153     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10154     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10155     * @property {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here.
10156     * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element.
10157     * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element.
10158     * @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels.
10159     * @property {Number} distance - The distance between the two pinch contact points in CSS pixels.
10160     * @property {Boolean} shift - True if the shift key was pressed during this event.
10161     * @property {Object} originalEvent - The original DOM event.
10162     * @property {?Object} userData - Arbitrary subscriber-defined object.
10163     */
10164    this.raiseEvent('canvas-pinch', {
10165        tracker: event.eventSource,
10166        gesturePoints: event.gesturePoints,
10167        lastCenter: event.lastCenter,
10168        center: event.center,
10169        lastDistance: event.lastDistance,
10170        distance: event.distance,
10171        shift: event.shift,
10172        originalEvent: event.originalEvent
10173    });
10174    //cancels event
10175    return false;
10176}
10177
10178function onCanvasScroll( event ) {
10179    var gestureSettings,
10180        factor,
10181        thisScrollTime,
10182        deltaScrollTime;
10183
10184    /* Certain scroll devices fire the scroll event way too fast so we are injecting a simple adjustment to keep things
10185     * partially normalized. If we have already fired an event within the last 'minScrollDelta' milliseconds we skip
10186     * this one and wait for the next event. */
10187    thisScrollTime = $.now();
10188    deltaScrollTime = thisScrollTime - this._lastScrollTime;
10189    if (deltaScrollTime >
10189 this.minScrollDeltaTime) {
10190        this._lastScrollTime = thisScrollTime;
10191
10192        if(this.viewport.flipped){
10193          event.position.x = this.viewport.getContainerSize().x - event.position.x;
10194        }
10195
10196        if ( !event.preventDefaultAction && this.viewport ) {
10197            gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
10198            if ( gestureSettings.scrollToZoom ) {
10199                factor = Math.pow( this.zoomPerScroll, event.scroll );
10200                this.viewport.zoomBy(
10201                    factor,
10202                    gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
10203                );
10204                this.viewport.applyConstraints();
10205            }
10206        }
10207        /**
10208         * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
10209         *
10210         * @event canvas-scroll
10211         * @memberof OpenSeadragon.Viewer
10212         * @type {object}
10213         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10214         * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10215         * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10216         * @property {Number} scroll - The scroll delta for the event.
10217         * @property {Boolean} shift - True if the shift key was pressed during this event.
10218         * @property {Object} originalEvent - The original DOM event.
10219         * @property {?Object} userData - Arbitrary subscriber-defined object.
10220         */
10221        this.raiseEvent( 'canvas-scroll', {
10222            tracker: event.eventSource,
10223            position: event.position,
10224            scroll: event.scroll,
10225            shift: event.shift,
10226            originalEvent: event.originalEvent
10227        });
10228        if (gestureSettings && gestureSettings.scrollToZoom) {
10229            //cancels event
10230            return false;
10231        }
10232    }
10233    else {
10234        gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
10235        if (gestureSettings && gestureSettings.scrollToZoom) {
10236            return false;   // We are swallowing this event
10237        }
10238    }
10239}
10240
10241function onContainerEnter( event ) {
10242    THIS[ this.hash ].mouseInside = true;
10243    abortControlsAutoHide( this );
10244    /**
10245     * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
10246     *
10247     * @event container-enter
10248     * @memberof OpenSeadragon.Viewer
10249     * @type {object}
10250     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10251     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10252     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10253     * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
10254     * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
10255     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
10256     * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
10257     * @property {Object} originalEvent - The original DOM event.
10258     * @property {?Object} userData - Arbitrary subscriber-defined object.
10259     */
10260    this.raiseEvent( 'container-enter', {
10261        tracker: event.eventSource,
10262        position: event.position,
10263        buttons: event.buttons,
10264        pointers: event.pointers,
10265        insideElementPressed: event.insideElementPressed,
10266        buttonDownAny: event.buttonDownAny,
10267        originalEvent: event.originalEvent
10268    });
10269}
10270
10271function onContainerExit( event ) {
10272    if ( event.pointers < 1 ) {
10273        THIS[ this.hash ].mouseInside = false;
10274        if ( !THIS[ this.hash ].animating ) {
10275            beginControlsAutoHide( this );
10276        }
10277    }
10278    /**
10279     * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
10280     *
10281     * @event container-exit
10282     * @memberof OpenSeadragon.Viewer
10283     * @type {object}
10284     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10285     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
10286     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
10287     * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
10288     * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
10289     * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
10290     * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
10291     * @property {Object} originalEvent - The original DOM event.
10292     * @property {?Object} userData - Arbitrary subscriber-defined object.
10293     */
10294    this.raiseEvent( 'container-exit', {
10295        tracker: event.eventSource,
10296        position: event.position,
10297        buttons: event.buttons,
10298        pointers: event.pointers,
10299        insideElementPressed: event.insideElementPressed,
10300        buttonDownAny: event.buttonDownAny,
10301        originalEvent: event.originalEvent
10302    });
10303}
10304
10305
10306///////////////////////////////////////////////////////////////////////////////
10307// Page update routines ( aka Views - for future reference )
10308///////////////////////////////////////////////////////////////////////////////
10309
10310function updateMulti( viewer ) {
10311    updateOnce( viewer );
10312
10313    // Request the next frame, unless we've been closed
10314    if ( viewer.isOpen() ) {
10315        viewer._updateRequestId = scheduleUpdate( viewer, updateMulti );
10316    } else {
10317        viewer._updateRequestId = false;
10318    }
10319}
10320
10321function updateOnce( viewer ) {
10322
10323    //viewer.profiler.beginUpdate();
10324
10325    if (viewer._opening) {
10326        return;
10327    }
10328
10329    if (viewer.autoResize) {
10330        var containerSize = _getSafeElemSize(viewer.container);
10331        var prevContainerSize = THIS[viewer.hash].prevContainerSize;
10332        if (!containerSize.equals(prevContainerSize)) {
10333            var viewport = viewer.viewport;
10334            if (viewer.preserveImageSizeOnResize) {
10335                var resizeRatio = prevContainerSize.x / containerSize.x;
10336                var zoom = viewport.getZoom() * resizeRatio;
10337                var center = viewport.getCenter();
10338                viewport.resize(containerSize, false);
10339                viewport.zoomTo(zoom, null, true);
10340                viewport.panTo(center, true);
10341            } else {
10342                // maintain image position
10343                var oldBounds = viewport.getBounds();
10344                viewport.resize(containerSize, true);
10345                viewport.fitBoundsWithConstraints(oldBounds, true);
10346            }
10347            THIS[viewer.hash].prevContainerSize = containerSize;
10348            THIS[viewer.hash].forceRedraw = true;
10349        }
10350    }
10351
10352    var viewportChange = viewer.viewport.update();
10353    var animated = viewer.world.update() || viewportChange;
10354
10355    if (viewportChange) {
10356        /**
10357         * Raised when any spring animation update occurs (zoom, pan, etc.),
10358         * before the viewer has drawn the new location.
10359         *
10360         * @event viewport-change
10361         * @memberof OpenSeadragon.Viewer
10362         * @type {object}
10363         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10364         * @property {?Object} userData - Arbitrary subscriber-defined object.
10365         */
10366        viewer.raiseEvent('viewport-change');
10367    }
10368
10369    if( viewer.referenceStrip ){
10370        animated = viewer.referenceStrip.update( viewer.viewport ) || animated;
10371    }
10372
10373    if ( !THIS[ viewer.hash ].animating && animated ) {
10374        /**
10375         * Raised when any spring animation starts (zoom, pan, etc.).
10376         *
10377         * @event animation-start
10378         * @memberof OpenSeadragon.Viewer
10379         * @type {object}
10380         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10381         * @property {?Object} userData - Arbitrary subscriber-defined object.
10382         */
10383        viewer.raiseEvent( "animation-start" );
10384        abortControlsAutoHide( viewer );
10385    }
10386
10387    if ( animated || THIS[ viewer.hash ].forceRedraw || viewer.world.needsDraw() ) {
10388        drawWorld( viewer );
10389        viewer._drawOverlays();
10390        if( viewer.navigator ){
10391          viewer.navigator.update( viewer.viewport );
10392        }
10393
10394        THIS[ viewer.hash ].forceRedraw = false;
10395
10396        if (animated) {
10397            /**
10398             * Raised when any spring animation update occurs (zoom, pan, etc.),
10399             * after the viewer has drawn the new location.
10400             *
10401             * @event animation
10402             * @memberof OpenSeadragon.Viewer
10403             * @type {object}
10404             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10405             * @property {?Object} userData - Arbitrary subscriber-defined object.
10406             */
10407            viewer.raiseEvent( "animation" );
10408        }
10409    }
10410
10411    if ( THIS[ viewer.hash ].animating && !animated ) {
10412        /**
10413         * Raised when any spring animation ends (zoom, pan, etc.).
10414         *
10415         * @event animation-finish
10416         * @memberof OpenSeadragon.Viewer
10417         * @type {object}
10418         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10419         * @property {?Object} userData - Arbitrary subscriber-defined object.
10420         */
10421        viewer.raiseEvent( "animation-finish" );
10422
10423        if ( !THIS[ viewer.hash ].mouseInside ) {
10424            beginControlsAutoHide( viewer );
10425        }
10426    }
10427
10428    THIS[ viewer.hash ].animating = animated;
10429
10430    //viewer.profiler.endUpdate();
10431}
10432
10433function drawWorld( viewer ) {
10434    viewer.imageLoader.clear();
10435    viewer.drawer.clear();
10436    viewer.world.draw();
10437
10438    /**
10439     * <em>- Needs documentation -</em>
10440     *
10441     * @event update-viewport
10442     * @memberof OpenSeadragon.Viewer
10443     * @type {object}
10444     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
10445     * @property {?Object} userData - Arbitrary subscriber-defined object.
10446     */
10447    viewer.raiseEvent( 'update-viewport', {} );
10448}
10449
10450///////////////////////////////////////////////////////////////////////////////
10451// Navigation Controls
10452///////////////////////////////////////////////////////////////////////////////
10453function resolveUrl( prefix, url ) {
10454    return prefix ? prefix + url : url;
10455}
10456
10457
10458
10459function beginZoomingIn() {
10460    THIS[ this.hash ].lastZoomTime = $.now();
10461    THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
10462    THIS[ this.hash ].zooming = true;
10463    scheduleZoom( this );
10464}
10465
10466
10467function beginZoomingOut() {
10468    THIS[ this.hash ].lastZoomTime = $.now();
10469    THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
10470    THIS[ this.hash ].zooming = true;
10471    scheduleZoom( this );
10472}
10473
10474
10475function endZooming() {
10476    THIS[ this.hash ].zooming = false;
10477}
10478
10479
10480function scheduleZoom( viewer ) {
10481    $.requestAnimationFrame( $.delegate( viewer, doZoom ) );
10482}
10483
10484
10485function doZoom() {
10486    var currentTime,
10487        deltaTime,
10488        adjustedFactor;
10489
10490    if ( THIS[ this.hash ].zooming && this.viewport) {
10491        currentTime     = $.now();
10492        deltaTime       = currentTime - THIS[ this.hash ].lastZoomTime;
10493        adjustedFactor  = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 );
10494
10495        this.viewport.zoomBy( adjustedFactor );
10496        this.viewport.applyConstraints();
10497        THIS[ this.hash ].lastZoomTime = currentTime;
10498        scheduleZoom( this );
10499    }
10500}
10501
10502
10503function doSingleZoomIn() {
10504    if ( this.viewport ) {
10505        THIS[ this.hash ].zooming = false;
10506        this.viewport.zoomBy(
10507            this.zoomPerClick / 1.0
10508        );
10509        this.viewport.applyConstraints();
10510    }
10511}
10512
10513
10514function doSingleZoomOut() {
10515    if ( this.viewport ) {
10516        THIS[ this.hash ].zooming = false;
10517        this.viewport.zoomBy(
10518            1.0 / this.zoomPerClick
10519        );
10520        this.viewport.applyConstraints();
10521    }
10522}
10523
10524
10525function lightUp() {
10526    this.buttons.emulateEnter();
10527    this.buttons.emulateExit();
10528}
10529
10530
10531function onHome() {
10532    if ( this.viewport ) {
10533        this.viewport.goHome();
10534    }
10535}
10536
10537
10538function onFullScreen() {
10539    if ( this.isFullPage() && !$.isFullScreen() ) {
10540        // Is fullPage but not fullScreen
10541        this.setFullPage( false );
10542    } else {
10543        this.setFullScreen( !this.isFullPage() );
10544    }
10545    // correct for no mouseout event on change
10546    if ( this.buttons ) {
10547        this.buttons.emulateExit();
10548    }
10549    this.fullPageButton.element.focus();
10550    if ( this.viewport ) {
10551        this.viewport.applyConstraints();
10552    }
10553}
10554
10555function onRotateLeft() {
10556    if ( this.viewport ) {
10557        var currRotation = this.viewport.getRotation();
10558
10559        if ( this.viewport.flipped ){
10560          currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
10561        } else {
10562          currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
10563        }
10564        this.viewport.setRotation(currRotation);
10565    }
10566}
10567
10568function onRotateRight() {
10569    if ( this.viewport ) {
10570        var currRotation = this.viewport.getRotation();
10571
10572        if ( this.viewport.flipped ){
10573          currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
10574        } else {
10575          currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
10576        }
10577        this.viewport.setRotation(currRotation);
10578    }
10579}
10580/**
10581 * Note: When pressed flip control button
10582 */
10583function onFlip() {
10584   this.viewport.toggleFlip();
10585}
10586
10587function onPrevious(){
10588    var previous = this._sequenceIndex - 1;
10589    if(this.navPrevNextWrap && previous < 0){
10590        previous += this.tileSources.length;
10591    }
10592    this.goToPage( previous );
10593}
10594
10595
10596function onNext(){
10597    var next = this._sequenceIndex + 1;
10598    if(this.navPrevNextWrap && next >= this.tileSources.length){
10599        next = 0;
10600    }
10601    this.goToPage( next );
10602}
10603
10604
10605}( OpenSeadragon ));
10606
10607/*
10608 * OpenSeadragon - Navigator
10609 *
10610 * Copyright (C) 2009 CodePlex Foundation
10611 * Copyright (C) 2010-2013 OpenSeadragon contributors
10612 *
10613 * Redistribution and use in source and binary forms, with or without
10614 * modification, are permitted provided that the following conditions are
10615 * met:
10616 *
10617 * - Redistributions of source code must retain the above copyright notice,
10618 *   this list of conditions and the following disclaimer.
10619 *
10620 * - Redistributions in binary form must reproduce the above copyright
10621 *   notice, this list of conditions and the following disclaimer in the
10622 *   documentation and/or other materials provided with the distribution.
10623 *
10624 * - Neither the name of CodePlex Foundation nor the names of its
10625 *   contributors may be used to endorse or promote products derived from
10626 *   this software without specific prior written permission.
10627 *
10628 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10629 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10630 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
10631 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
10632 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10633 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
10634 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
10635 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
10636 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
10637 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
10638 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10639 */
10640
10641(function( $ ){
10642
10643/**
10644 * @class Navigator
10645 * @classdesc The Navigator provides a small view of the current image as fixed
10646 * while representing the viewport as a moving box serving as a frame
10647 * of reference in the larger viewport as to which portion of the image
10648 * is currently being examined.  The navigator's viewport can be interacted
10649 * with using the keyboard or the mouse.
10650 *
10651 * @memberof OpenSeadragon
10652 * @extends OpenSeadragon.Viewer
10653 * @extends OpenSeadragon.EventSource
10654 * @param {Object} options
10655 */
10656$.Navigator = function( options ){
10657
10658    var viewer      = options.viewer,
10659        _this = this,
vendor: 5,031 bytes, lines 10660-10777
10660        viewerSize,
10661        navigatorSize;
10662
10663    //We may need to create a new element and id if they did not
10664    //provide the id for the existing element
10665    if( !options.id ){
10666        options.id              = 'navigator-' + $.now();
10667        this.element            = $.makeNeutralElement( "div" );
10668        options.controlOptions  = {
10669            anchor:           $.ControlAnchor.TOP_RIGHT,
10670            attachToViewer:   true,
10671            autoFade:         options.autoFade
10672        };
10673
10674        if( options.position ){
10675            if( 'BOTTOM_RIGHT' == options.position ){
10676               options.controlOptions.anchor = $.ControlAnchor.BOTTOM_RIGHT;
10677            } else if( 'BOTTOM_LEFT' == options.position ){
10678               options.controlOptions.anchor = $.ControlAnchor.BOTTOM_LEFT;
10679            } else if( 'TOP_RIGHT' == options.position ){
10680               options.controlOptions.anchor = $.ControlAnchor.TOP_RIGHT;
10681            } else if( 'TOP_LEFT' == options.position ){
10682               options.controlOptions.anchor = $.ControlAnchor.TOP_LEFT;
10683            } else if( 'ABSOLUTE' == options.position ){
10684               options.controlOptions.anchor = $.ControlAnchor.ABSOLUTE;
10685               options.controlOptions.top = options.top;
10686               options.controlOptions.left = options.left;
10687               options.controlOptions.height = options.height;
10688               options.controlOptions.width = options.width;
10689            }
10690        }
10691
10692    } else {
10693        this.element            = document.getElementById( options.id );
10694        options.controlOptions  = {
10695            anchor:           $.ControlAnchor.NONE,
10696            attachToViewer:   false,
10697            autoFade:         false
10698        };
10699    }
10700    this.element.id         = options.id;
10701    this.element.className  += ' navigator';
10702
10703    options = $.extend( true, {
10704        sizeRatio:     $.DEFAULT_SETTINGS.navigatorSizeRatio
10705    }, options, {
10706        element:                this.element,
10707        tabIndex:               -1, // No keyboard navigation, omit from tab order
10708        //These need to be overridden to prevent recursion since
10709        //the navigator is a viewer and a viewer has a navigator
10710        showNavigator:          false,
10711        mouseNavEnabled:        false,
10712        showNavigationControl:  false,
10713        showSequenceControl:    false,
10714        immediateRender:        true,
10715        blendTime:              0,
10716        animationTime:          0,
10717        autoResize:             options.autoResize,
10718        // prevent resizing the navigator from adding unwanted space around the image
10719        minZoomImageRatio:      1.0,
10720        background:             options.background,
10721        opacity:                options.opacity,
10722        borderColor:            options.borderColor,
10723        displayRegionColor:     options.displayRegionColor
10724    });
10725
10726    options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
10727
10728    $.setElementTouchActionNone( this.element );
10729
10730    this.borderWidth = 2;
10731    //At some browser magnification levels the display regions lines up correctly, but at some there appears to
10732    //be a one pixel gap.
10733    this.fudge = new $.Point(1, 1);
10734    this.totalBorderWidths = new $.Point(this.borderWidth * 2, this.borderWidth * 2).minus(this.fudge);
10735
10736
10737    if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) {
10738        (function( style, borderWidth ){
10739            style.margin        = '0px';
10740            style.border        = borderWidth + 'px solid ' + options.borderColor;
10741            style.padding       = '0px';
10742            style.background    = options.background;
10743            style.opacity       = options.opacity;
10744            style.overflow      = 'hidden';
10745        }( this.element.style, this.borderWidth));
10746    }
10747
10748    this.displayRegion           = $.makeNeutralElement( "div" );
10749    this.displayRegion.id        = this.element.id + '-displayregion';
10750    this.displayRegion.className = 'displayregion';
10751
10752    (function( style, borderWidth ){
10753        style.position      = 'relative';
10754        style.top           = '0px';
10755        style.left          = '0px';
10756        style.fontSize      = '0px';
10757        style.overflow      = 'hidden';
10758        style.border        = borderWidth + 'px solid ' + options.displayRegionColor;
10759        style.margin        = '0px';
10760        style.padding       = '0px';
10761        //TODO: IE doesn't like this property being set
10762        //try{ style.outline  = '2px auto #909'; }catch(e){/*ignore*/}
10763
10764        style.background    = 'transparent';
10765
10766        // We use square bracket notation on the statement below, because float is a keyword.
10767        // This is important for the Google Closure compiler, if nothing else.
10768        /*jshint sub:true */
10769        style['float']      = 'left'; //Webkit
10770
10771        style.cssFloat      = 'left'; //Firefox
10772        style.styleFloat    = 'left'; //IE
10773        style.zIndex        = 999999999;
10774        style.cursor        = 'default';
10775    }( this.displayRegion.style, this.borderWidth ));
10776
10777    this.displayRegionContainer = $.makeNeutralElement("div");
10778    this.displayRegionContainer.id = this.element.id + '-displayregioncontainer';
10779    this.displayRegionContainer.className = "displayregioncontainer";
10780    this.displayRegionContainer.style.width = "100%";
10781    this.displayRegionContainer.style.height = "100%";
10782
10783    viewer.addControl(
10784        this.element,
10785        options.controlOptions
10786    );
10787
10788    this._resizeWithViewer = options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE &&
10789        options.controlOptions.anchor != $.ControlAnchor.NONE;
10790
10791    if (options.width && options.height) {
10792        this.setWidth(options.width);
10793        this.setHeight(options.height);
10794    } else if ( this._resizeWithViewer ) {
10795        viewerSize = $.getElementSize( viewer.element );
10796        this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px';
10797        this.element.style.width  = Math.round( viewerSize.x * options.sizeRatio ) + 'px';
10798        this.oldViewerSize = viewerSize;
10799        navigatorSize = $.getElementSize( this.element );
10800        this.elementArea = navigatorSize.x * navigatorSize.y;
10801    }
10802
10803    this.oldContainerSize = new $.Point( 0, 0 );
10804
10805    $.Viewer.apply( this, [ options ] );
10806
10807    this.displayRegionContainer.appendChild(this.displayRegion);
10808    this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
10809
10810    function rotate(degrees) {
10811        _setTransformRotate(_this.displayRegionContainer, degrees);
10812        _setTransformRotate(_this.displayRegion, -degrees);
10813        _this.viewport.setRotation(degrees);
10814    }
10815    if (options.navigatorRotate) {
10816        var degrees = options.viewer.viewport ?
10817            options.viewer.viewport.getRotation() :
10818            options.viewer.degrees || 0;
10819
10820        rotate(degrees);
10821        options.viewer.addHandler("rotate", function (args) {
10822            rotate(args.degrees);
10823        });
10824    }
10825
10826
10827    // Remove the base class' (Viewer's) innerTracker and replace it with our own
10828    this.innerTracker.destroy();
10829    this.innerTracker = new $.MouseTracker({
10830        element:         this.element,
10831        dragHandler:     $.delegate( this, onCanvasDrag ),
10832        clickHandler:    $.delegate( this, onCanvasClick ),
10833        releaseHandler:  $.delegate( this, onCanvasRelease ),
10834        scrollHandler:   $.delegate( this, onCanvasScroll )
10835    });
10836
10837    this.addHandler("reset-size", function() {
10838        if (_this.viewport) {
10839            _this.viewport.goHome(true);
10840        }
10841    });
10842
10843    viewer.world.addHandler("item-index-change", function(event) {
10844        window.setTimeout(function(){
10845            var item = _this.world.getItemAt(event.previousIndex);
10846            _this.world.setItemIndex(item, event.newIndex);
10847        }, 1);
10848    });
10849
10850    viewer.world.addHandler("remove-item", function(event) {
10851        var theirItem = event.item;
10852        var myItem = _this._getMatchingItem(theirItem);
10853        if (myItem) {
10854            _this.world.removeItem(myItem);
10855        }
10856    });
10857
10858    this.update(viewer.viewport);
10859};
10860
10861$.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{
10862
10863    /**
10864     * Used to notify the navigator when its size has changed.
10865     * Especially useful when {@link OpenSeadragon.Options}.navigatorAutoResize is set to false and the navigator is resizable.
10866     * @function
10867     */
10868    updateSize: function () {
10869        if ( this.viewport ) {
10870            var containerSize = new $.Point(
10871                    (this.container.clientWidth === 0 ? 1 : this.container.clientWidth),
10872                    (this.container.clientHeight === 0 ? 1 : this.container.clientHeight)
10873                );
10874
10875            if ( !containerSize.equals( this.oldContainerSize ) ) {
10876                this.viewport.resize( containerSize, true );
10877                this.viewport.goHome(true);
10878                this.oldContainerSize = containerSize;
10879                this.drawer.clear();
10880                this.world.draw();
10881            }
10882        }
10883    },
10884
10885    /**
10886     * Explicitly sets the width of the navigator, in web coordinates. Disables automatic resizing.
10887     * @param {Number|String} width - the new width, either a number of pixels or a CSS string, such as "100%"
10888     */
10889    setWidth: function(width) {
10890        this.width = width;
10891        this.element.style.width = typeof (width) == "number" ? (width + 'px') : width;
10892        this._resizeWithViewer = false;
10893    },
10894
10895    /**
10896     * Explicitly sets the height of the navigator, in web coordinates. Disables automatic resizing.
10897     * @param {Number|String} height - the new height, either a number of pixels or a CSS string, such as "100%"
10898     */
10899    setHeight: function(height) {
10900        this.height = height;
10901        this.element.style.height = typeof (height) == "number" ? (height + 'px') : height;
10902        this._resizeWithViewer = false;
10903    },
10904
10905    /**
10906      * Flip navigator element
10907      * @param {Boolean} state - Flip state to set.
10908      */
10909    setFlip: function(state) {
10910      this.viewport.setFlip(state);
10911
10912      this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)");
10913      return this;
10914    },
10915
10916    setDisplayTransform: function(rule) {
10917      setElementTransform(this.displayRegion, rule);
10918      setElementTransform(this.canvas, rule);
10919      setElementTransform(this.element, rule);
10920    },
10921
10922    /**
10923     * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
10924     * @function
10925     * @param {OpenSeadragon.Viewport} The viewport this navigator is tracking.
10926     */
10927    update: function( viewport ) {
10928
10929        var viewerSize,
10930            newWidth,
10931            newHeight,
10932            bounds,
10933            topleft,
10934            bottomright;
10935
10936        viewerSize = $.getElementSize( this.viewer.element );
10937        if ( this._resizeWithViewer && viewerSize.x && viewerSize.y && !viewerSize.equals( this.oldViewerSize ) ) {
10938            this.oldViewerSize = viewerSize;
10939
10940            if ( this.maintainSizeRatio || !this.elementArea) {
10941                newWidth  = viewerSize.x * this.sizeRatio;
10942                newHeight = viewerSize.y * this.sizeRatio;
10943            } else {
10944                newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y));
10945                newHeight = this.elementArea / newWidth;
10946            }
10947
10948            this.element.style.width  = Math.round( newWidth ) + 'px';
10949            this.element.style.height = Math.round( newHeight ) + 'px';
10950
10951            if (!this.elementArea) {
10952                this.elementArea = newWidth * newHeight;
10953            }
10954
10955            this.updateSize();
10956        }
10957
10958        if (viewport && this.viewport) {
10959            bounds      = viewport.getBoundsNoRotate(true);
10960            topleft     = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
10961            bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
10962                .minus( this.totalBorderWidths );
10963
10964            //update style for navigator-box
10965            var style = this.displayRegion.style;
10966            style.display = this.world.getItemCount() ? 'block' : 'none';
10967
10968            style.top    = Math.round( topleft.y ) + 'px';
10969            style.left   = Math.round( topleft.x ) + 'px';
10970
10971            var width = Math.abs( topleft.x - bottomright.x );
10972            var height = Math.abs( topleft.y - bottomright.y );
10973            // make sure width and height are non-negative so IE doesn't throw
10974            style.width  = Math.round( Math.max( width, 0 ) ) + 'px';
10975            style.height = Math.round( Math.max( height, 0 ) ) + 'px';
10976        }
10977
10978    },
10979
10980    // overrides Viewer.addTiledImage
10981    addTiledImage: function(options) {
10982        var _this = this;
10983
10984        var original = options.originalTiledImage;
10985        delete options.original;
10986
10987        var optionsClone = $.extend({}, options, {
10988            success: function(event) {
10989                var myItem = event.item;
10990                myItem._originalForNavigator = original;
10991                _this._matchBounds(myItem, original, true);
10992
10993                function matchBounds() {
10994                    _this._matchBounds(myItem, original);
10995                }
10996
10997                function matchOpacity() {
10998                    _this._matchOpacity(myItem, original);
10999                }
11000
11001                function matchCompositeOperation() {
11002                    _this._matchCompositeOperation(myItem, original);
11003                }
11004
11005                original.addHandler('bounds-change', matchBounds);
11006                original.addHandler('clip-change', matchBounds);
11007                original.addHandler('opacity-change', matchOpacity);
11008                original.addHandler('composite-operation-change', matchCompositeOperation);
11009            }
11010        });
11011
11012        return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]);
11013    },
11014
11015    // private
11016    _getMatchingItem: function(theirItem) {
11017        var count = this.world.getItemCount();
11018        var item;
11019        for (var i = 0; i < count; i++) {
11020            item = this.world.getItemAt(i);
11021            if (item._originalForNavigator === theirItem) {
11022                return item;
11023            }
11024        }
11025
11026        return null;
11027    },
11028
11029    // private
11030    _matchBounds: function(myItem, theirItem, immediately) {
11031        var bounds = theirItem.getBoundsNoRotate();
11032        myItem.setPosition(bounds.getTopLeft(), immediately);
11033        myItem.setWidth(bounds.width, immediately);
11034        myItem.setRotation(theirItem.getRotation(), immediately);
11035        myItem.setClip(theirItem.getClip());
11036    },
11037
11038    // private
11039    _matchOpacity: function(myItem, theirItem) {
11040        myItem.setOpacity(theirItem.opacity);
11041    },
11042
11043    // private
11044    _matchCompositeOperation: function(myItem, theirItem) {
11045        myItem.setCompositeOperation(theirItem.compositeOperation);
11046    }
11047});
11048
11049
11050/**
11051 * @private
11052 * @inner
11053 * @function
11054 */
11055function onCanvasClick( event ) {
11056  var canvasClickEventArgs = {
11057    tracker: event.eventSource,
11058    position: event.position,
11059    quick: event.quick,
11060    shift: event.shift,
11061    originalEvent: event.originalEvent,
11062    preventDefaultAction: event.preventDefaultAction
11063  };
11064  /**
11065   * Raised when a click event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
11066   *
11067   * @event navigator-click
11068   * @memberof OpenSeadragon.Viewer
11069   * @type {object}
11070   * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11071   * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11072   * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
11073   * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
11074   * @property {Boolean} shift - True if the shift key was pressed during this event.
11075   * @property {Object} originalEvent - The original DOM event.
vendor: 4,327 bytes, lines 11076-11188
11076   * @property {?Object} userData - Arbitrary subscriber-defined object.
11077   * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
11078   */
11079
11080   this.viewer.raiseEvent('navigator-click', canvasClickEventArgs);
11081
11082   if ( !canvasClickEventArgs.preventDefaultAction && event.quick && this.viewer.viewport && (this.panVertical || this.panHorizontal)) {
11083    if(this.viewer.viewport.flipped) {
11084      event.position.x = this.viewport.getContainerSize().x - event.position.x;
11085    }
11086    var target = this.viewport.pointFromPixel(event.position);
11087    if (!this.panVertical) {
11088      // perform only horizonal pan
11089      target.y = this.viewer.viewport.getCenter(true).y;
11090    } else if (!this.panHorizontal) {
11091      // perform only vertical pan
11092      target.x = this.viewer.viewport.getCenter(true).x;
11093    }
11094    this.viewer.viewport.panTo(target);
11095    this.viewer.viewport.applyConstraints();
11096  }
11097
11098}
11099
11100/**
11101 * @private
11102 * @inner
11103 * @function
11104 */
11105function onCanvasDrag( event ) {
11106    var canvasDragEventArgs = {
11107      tracker: event.eventSource,
11108      position: event.position,
11109      delta: event.delta,
11110      speed: event.speed,
11111      direction: event.direction,
11112      shift: event.shift,
11113      originalEvent: event.originalEvent,
11114      preventDefaultAction: event.preventDefaultAction
11115    };
11116    /**
11117     * Raised when a drag event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
11118     *
11119     * @event navigator-drag
11120     * @memberof OpenSeadragon.Viewer
11121     * @type {object}
11122     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11123     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11124     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
11125     * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
11126     * @property {Number} speed - Current computed speed, in pixels per second.
11127     * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
11128     * @property {Boolean} shift - True if the shift key was pressed during this event.
11129     * @property {Object} originalEvent - The original DOM event.
11130     * @property {?Object} userData - Arbitrary subscriber-defined object.
11131     * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
11132     */
11133     this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs);
11134
11135     if ( !canvasDragEventArgs.preventDefaultAction && this.viewer.viewport ) {
11136       if( !this.panHorizontal ){
11137            event.delta.x = 0;
11138        }
11139        if( !this.panVertical ){
11140            event.delta.y = 0;
11141        }
11142
11143        if(this.viewer.viewport.flipped){
11144            event.delta.x = -event.delta.x;
11145        }
11146
11147        this.viewer.viewport.panBy(
11148            this.viewport.deltaPointsFromPixels(
11149                event.delta
11150            )
11151        );
11152        if( this.viewer.constrainDuringPan ){
11153            this.viewer.viewport.applyConstraints();
11154        }
11155    }
11156}
11157
11158
11159/**
11160 * @private
11161 * @inner
11162 * @function
11163 */
11164function onCanvasRelease( event ) {
11165    if ( event.insideElementPressed && this.viewer.viewport ) {
11166        this.viewer.viewport.applyConstraints();
11167    }
11168}
11169
11170
11171/**
11172 * @private
11173 * @inner
11174 * @function
11175 */
11176function onCanvasScroll( event ) {
11177    /**
11178     * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#navigator} element (mouse wheel, touch pinch, etc.).
11179     *
11180     * @event navigator-scroll
11181     * @memberof OpenSeadragon.Viewer
11182     * @type {object}
11183     * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11184     * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11185     * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
11186     * @property {Number} scroll - The scroll delta for the event.
11187     * @property {Boolean} shift - True if the shift key was pressed during this event.
11188     * @property {Object} originalEvent - The original DOM event.
11189     * @property {?Object} userData - Arbitrary subscriber-defined object.
11190     */
11191    this.viewer.raiseEvent( 'navigator-scroll', {
11192        tracker: event.eventSource,
11193        position: event.position,
11194        scroll: event.scroll,
11195        shift: event.shift,
11196        originalEvent: event.originalEvent
11197    });
11198
11199    //don't scroll the page up and down if the user is scrolling
11200    //in the navigator
11201    return false;
11202}
11203
11204/**
11205    * @function
11206    * @private
11207    * @param {Object} element
11208    * @param {Number} degrees
11209    */
11210function _setTransformRotate( element, degrees ) {
11211  setElementTransform(element, "rotate(" + degrees + "deg)");
11212}
11213
11214function setElementTransform( element, rule ) {
11215  element.style.webkitTransform = rule;
11216  element.style.mozTransform = rule;
11217  element.style.msTransform = rule;
11218  element.style.oTransform = rule;
11219  element.style.transform = rule;
11220}
11221
11222}( OpenSeadragon ));
11223
11224/*
11225 * OpenSeadragon - getString/setString
11226 *
11227 * Copyright (C) 2009 CodePlex Foundation
11228 * Copyright (C) 2010-2013 OpenSeadragon contributors
11229 *
11230 * Redistribution and use in source and binary forms, with or without
11231 * modification, are permitted provided that the following conditions are
11232 * met:
11233 *
11234 * - Redistributions of source code must retain the above copyright notice,
11235 *   this list of conditions and the following disclaimer.
11236 *
11237 * - Redistributions in binary form must reproduce the above copyright
11238 *   notice, this list of conditions and the following disclaimer in the
11239 *   documentation and/or other materials provided with the distribution.
11240 *
11241 * - Neither the name of CodePlex Foundation nor the names of its
11242 *   contributors may be used to endorse or promote products derived from
11243 *   this software without specific prior written permission.
11244 *
11245 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
11246 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11247 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11248 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
11249 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11250 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
11251 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
11252 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
11253 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
11254 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
11255 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11256 */
11257
11258(function( $ ){
11259
11260//TODO: I guess this is where the i18n needs to be reimplemented.  I'll look
11261//      into existing patterns for i18n in javascript but i think that mimicking
11262//      pythons gettext might be a reasonable approach.
11263var I18N = {
11264    Errors: {
11265        Dzc:            "Sorry, we don't support Deep Zoom Collections!",
11266        Dzi:            "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
11267        Xml:            "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
11268        ImageFormat:    "Sorry, we don't support {0}-based Deep Zoom Images.",
11269        Security:       "It looks like a security restriction stopped us from " +
11270                        "loading this Deep Zoom Image.",
11271        Status:         "This space unintentionally left blank ({0} {1}).",
11272        OpenFailed:     "Unable to open {0}: {1}"
11273    },
11274
11275    Tooltips: {
11276        FullPage:       "Toggle full page",
11277        Home:           "Go home",
11278        ZoomIn:         "Zoom in",
11279        ZoomOut:        "Zoom out",
11280        NextPage:       "Next page",
11281        PreviousPage:   "Previous page",
11282        RotateLeft:     "Rotate left",
11283        RotateRight:    "Rotate right",
11284        Flip:           "Flip Horizontally"
11285    }
11286};
11287
11288$.extend( $, /** @lends OpenSeadragon */{
11289
11290    /**
11291     * @function
11292     * @param {String} property
11293     */
11294    getString: function( prop ) {
11295
11296        var props   = prop.split('.'),
11297            string  = null,
11298            args    = arguments,
11299            container = I18N,
11300            i;
11301
11302        for (i = 0; i < props.length - 1; i++) {
11303            // in case not a subproperty
11304            container = container[ props[ i ] ] || {};
11305        }
vendor: 4,527 bytes, lines 11306-11445
11306        string = container[ props[ i ] ];
11307
11308        if ( typeof ( string ) != "string" ) {
11309            $.console.log( "Untranslated source string:", prop );
11310            string = ""; // FIXME: this breaks gettext()-style convention, which would return source
11311        }
11312
11313        return string.replace(/\{\d+\}/g, function(capture) {
11314            var i = parseInt( capture.match( /\d+/ ), 10 ) + 1;
11315            return i < args.length ?
11316                args[ i ] :
11317                "";
11318        });
11319    },
11320
11321    /**
11322     * @function
11323     * @param {String} property
11324     * @param {*} value
11325     */
11326    setString: function( prop, value ) {
11327
11328        var props     = prop.split('.'),
11329            container = I18N,
11330            i;
11331
11332        for ( i = 0; i < props.length - 1; i++ ) {
11333            if ( !container[ props[ i ] ] ) {
11334                container[ props[ i ] ] = {};
11335            }
11336            container = container[ props[ i ] ];
11337        }
11338
11339        container[ props[ i ] ] = value;
11340    }
11341
11342});
11343
11344}( OpenSeadragon ));
11345
11346/*
11347 * OpenSeadragon - Point
11348 *
11349 * Copyright (C) 2009 CodePlex Foundation
11350 * Copyright (C) 2010-2013 OpenSeadragon contributors
11351 *
11352 * Redistribution and use in source and binary forms, with or without
11353 * modification, are permitted provided that the following conditions are
11354 * met:
11355 *
11356 * - Redistributions of source code must retain the above copyright notice,
11357 *   this list of conditions and the following disclaimer.
11358 *
11359 * - Redistributions in binary form must reproduce the above copyright
11360 *   notice, this list of conditions and the following disclaimer in the
11361 *   documentation and/or other materials provided with the distribution.
11362 *
11363 * - Neither the name of CodePlex Foundation nor the names of its
11364 *   contributors may be used to endorse or promote products derived from
11365 *   this software without specific prior written permission.
11366 *
11367 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
11368 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11369 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11370 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
11371 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11372 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
11373 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
11374 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
11375 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
11376 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
11377 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11378 */
11379
11380(function( $ ){
11381
11382/**
11383 * @class Point
11384 * @classdesc A Point is really used as a 2-dimensional vector, equally useful for
11385 * representing a point on a plane, or the height and width of a plane
11386 * not requiring any other frame of reference.
11387 *
11388 * @memberof OpenSeadragon
11389 * @param {Number} [x] The vector component 'x'. Defaults to the origin at 0.
11390 * @param {Number} [y] The vector component 'y'. Defaults to the origin at 0.
11391 */
11392$.Point = function( x, y ) {
11393    /**
11394     * The vector component 'x'.
11395     * @member {Number} x
11396     * @memberof OpenSeadragon.Point#
11397     */
11398    this.x = typeof ( x ) == "number" ? x : 0;
11399    /**
11400     * The vector component 'y'.
11401     * @member {Number} y
11402     * @memberof OpenSeadragon.Point#
11403     */
11404    this.y = typeof ( y ) == "number" ? y : 0;
11405};
11406
11407/** @lends OpenSeadragon.Point.prototype */
11408$.Point.prototype = {
11409    /**
11410     * @function
11411     * @returns {OpenSeadragon.Point} a duplicate of this Point
11412     */
11413    clone: function() {
11414        return new $.Point(this.x, this.y);
11415    },
11416
11417    /**
11418     * Add another Point to this point and return a new Point.
11419     * @function
11420     * @param {OpenSeadragon.Point} point The point to add vector components.
11421     * @returns {OpenSeadragon.Point} A new point representing the sum of the
11422     *  vector components
11423     */
11424    plus: function( point ) {
11425        return new $.Point(
11426            this.x + point.x,
11427            this.y + point.y
11428        );
11429    },
11430
11431    /**
11432     * Subtract another Point to this point and return a new Point.
11433     * @function
11434     * @param {OpenSeadragon.Point} point The point to subtract vector components.
11435     * @returns {OpenSeadragon.Point} A new point representing the subtraction of the
11436     *  vector components
11437     */
11438    minus: function( point ) {
11439        return new $.Point(
11440            this.x - point.x,
11441            this.y - point.y
11442        );
11443    },
11444
11445    /**
11446     * Multiply this point by a factor and return a new Point.
11447     * @function
11448     * @param {Number} factor The factor to multiply vector components.
11449     * @returns {OpenSeadragon.Point} A new point representing the multiplication
11450     *  of the vector components by the factor
11451     */
11452    times: function( factor ) {
11453        return new $.Point(
11454            this.x * factor,
11455            this.y * factor
11456        );
11457    },
11458
11459    /**
11460     * Divide this point by a factor and return a new Point.
11461     * @function
11462     * @param {Number} factor The factor to divide vector components.
11463     * @returns {OpenSeadragon.Point} A new point representing the division of the
11464     *  vector components by the factor
11465     */
11466    divide: function( factor ) {
11467        return new $.Point(
11468            this.x / factor,
11469            this.y / factor
11470        );
11471    },
11472
11473    /**
11474     * Compute the opposite of this point and return a new Point.
11475     * @function
11476     * @returns {OpenSeadragon.Point} A new point representing the opposite of the
11477     *  vector components
11478     */
11479    negate: function() {
11480        return new $.Point( -this.x, -this.y );
11481    },
11482
11483    /**
11484     * Compute the distance between this point and another point.
11485     * @function
11486     * @param {OpenSeadragon.Point} point The point to compute the distance with.
11487     * @returns {Number} The distance between the 2 points
11488     */
11489    distanceTo: function( point ) {
11490        return Math.sqrt(
11491            Math.pow( this.x - point.x, 2 ) +
11492            Math.pow( this.y - point.y, 2 )
11493        );
11494    },
11495
11496    /**
11497     * Compute the squared distance between this point and another point.
11498     * Useful for optimizing things like comparing distances.
11499     * @function
11500     * @param {OpenSeadragon.Point} point The point to compute the squared distance with.
11501     * @returns {Number} The squared distance between the 2 points
11502     */
11503    squaredDistanceTo: function( point ) {
11504        return Math.pow( this.x - point.x, 2 ) +
11505            Math.pow( this.y - point.y, 2 );
11506    },
11507
11508    /**
11509     * Apply a function to each coordinate of this point and return a new point.
11510     * @function
11511     * @param {function} func The function to apply to each coordinate.
11512     * @returns {OpenSeadragon.Point} A new point with the coordinates computed
11513     * by the specified function
11514     */
11515    apply: function( func ) {
11516        return new $.Point( func( this.x ), func( this.y ) );
11517    },
11518
11519    /**
11520     * Check if this point is equal to another one.
11521     * @function
11522     * @param {OpenSeadragon.Point} point The point to compare this point with.
11523     * @returns {Boolean} true if they are equal, false otherwise.
11524     */
11525    equals: function( point ) {
11526        return (
11527            point instanceof $.Point
11528        ) && (
11529            this.x === point.x
11530        ) && (
11531            this.y === point.y
11532        );
11533    },
11534
11535    /**
11536     * Rotates the point around the specified pivot
11537     * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
11538     * @function
11539     * @param {Number} degress to rotate around the pivot.
11540     * @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate.
11541     * Defaults to the origin.
11542     * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
11543     */
11544    rotate: function (degrees, pivot) {
11545        pivot = pivot || new $.Point(0, 0);
11546        var cos;
11547        var sin;
11548        // Avoid float computations when possible
11549        if (degrees % 90 === 0) {
11550            var d = $.positiveModulo(degrees, 360);
11551            switch (d) {
11552                case 0:
11553                    cos = 1;
11554                    sin = 0;
11555                    break;
11556                case 90:
11557                    cos = 0;
11558                    sin = 1;
11559                    break;
11560                case 180:
11561                    cos = -1;
11562                    sin = 0;
11563                    break;
11564                case 270:
11565                    cos = 0;
11566                    sin = -1;
11567                    break;
11568            }
11569        } else {
11570            var angle = degrees * Math.PI / 180.0;
11571            cos = Math.cos(angle);
11572            sin = Math.sin(angle);
11573        }
11574        var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x;
11575        var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y;
11576        return new $.Point(x, y);
11577    },
11578
11579    /**
11580     * Convert this point to a string in the format (x,y) where x and y are
11581     * rounded to the nearest integer.
11582     * @function
11583     * @returns {String} A string representation of this point.
11584     */
11585    toString: function() {
11586        return "(" + (Math.round(this.x * 100) / 100) + "," + (Math.round(this.y * 100) / 100) + ")";
11587    }
11588};
11589
11590}( OpenSeadragon ));
11591
11592/*
11593 * OpenSeadragon - TileSource
11594 *
11595 * Copyright (C) 2009 CodePlex Foundation
11596 * Copyright (C) 2010-2013 OpenSeadragon contributors
11597 *
11598 * Redistribution and use in source and binary forms, with or without
11599 * modification, are permitted provided that the following conditions are
11600 * met:
11601 *
11602 * - Redistributions of source code must retain the above copyright notice,
11603 *   this list of conditions and the following disclaimer.
11604 *
11605 * - Redistributions in binary form must reproduce the above copyright
11606 *   notice, this list of conditions and the following disclaimer in the
11607 *   documentation and/or other materials provided with the distribution.
11608 *
11609 * - Neither the name of CodePlex Foundation nor the names of its
11610 *   contributors may be used to endorse or promote products derived from
11611 *   this software without specific prior written permission.
11612 *
11613 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
11614 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11615 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11616 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
11617 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11618 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
11619 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
11620 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
11621 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
11622 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
11623 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11624 */
11625
11626(function( $ ){
11627
11628
11629/**
11630 * @class TileSource
11631 * @classdesc The TileSource contains the most basic implementation required to create a
11632 * smooth transition between layers in an image pyramid. It has only a single key
11633 * interface that must be implemented to complete its key functionality:
11634 * 'getTileUrl'.  It also has several optional interfaces that can be
11635 * implemented if a new TileSource wishes to support configuration via a simple
11636 * object or array ('configure') and if the tile source supports or requires
11637 * configuration via retrieval of a document on the network ala AJAX or JSONP,
11638 * ('getImageInfo').
11639 * <br/>
11640 * By default the image pyramid is split into N layers where the image's longest
11641 * side in M (in pixels), where N is the smallest integer which satisfies
11642 *      <strong>2^(N+1) >= M</strong>.
11643 *
11644 * @memberof OpenSeadragon
11645 * @extends OpenSeadragon.EventSource
11646 * @param {Object} options
11647 *      You can either specify a URL, or literally define the TileSource (by specifying
11648 *      width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former,
11649 *      the extending class is expected to implement 'getImageInfo' and 'configure'.
11650 *      For the latter, the construction is assumed to occur through
11651 *      the extending classes implementation of 'configure'.
11652 * @param {String} [options.url]
11653 *      The URL for the data necessary for this TileSource.
11654 * @param {String} [options.referenceStripThumbnailUrl]
11655 *      The URL for a thumbnail image to be used by the reference strip
11656 * @param {Function} [options.success]
11657 *      A function to be called upon successful creation.
11658 * @param {Boolean} [options.ajaxWithCredentials]
11659 *      If this TileSource needs to make an AJAX call, this specifies whether to set
11660 *      the XHR's withCredentials (for accessing secure data).
11661 * @param {Object} [options.ajaxHeaders]
11662 *      A set of headers to include in AJAX requests.
11663 * @param {Number} [options.width]
11664 *      Width of the source image at max resolution in pixels.
11665 * @param {Number} [options.height]
11666 *      Height of the source image at max resolution in pixels.
11667 * @param {Number} [options.tileSize]
11668 *      The size of the tiles to assumed to make up each pyramid layer in pixels.
11669 *      Tile size determines the point at which the image pyramid must be
11670 *      divided into a matrix of smaller images.
11671 *      Use options.tileWidth and options.tileHeight to support non-square tiles.
11672 * @param {Number} [options.tileWidth]
11673 *      The width of the tiles to assumed to make up each pyramid layer in pixels.
11674 * @param {Number} [options.tileHeight]
11675 *      The height of the tiles to assumed to make up each pyramid layer in pixels.
11676 * @param {Number} [options.tileOverlap]
11677 *      The number of pixels each tile is expected to overlap touching tiles.
11678 * @param {Number} [options.minLevel]
11679 *      The minimum level to attempt to load.
11680 * @param {Number} [options.maxLevel]
11681 *      The maximum level to attempt to load.
11682 */
11683$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
11684    var _this = this;
11685
11686    var args = arguments,
11687        options,
11688        i;
11689
11690    if( $.isPlainObject( width ) ){
11691        options = width;
11692    }else{
11693        options = {
11694            width: args[0],
11695            height: args[1],
11696            tileSize: args[2],
11697            tileOverlap: args[3],
11698            minLevel: args[4],
11699            maxLevel: args[5]
11700        };
11701    }
11702
11703    //Tile sources supply some events, namely 'ready' when they must be configured
11704    //by asynchronously fetching their configuration data.
11705    $.EventSource.call( this );
11706
11707    //we allow options to override anything we don't treat as
11708    //required via idiomatic options or which is functionally
11709    //set depending on the state of the readiness of this tile
11710    //source
11711    $.extend( true, this, options );
11712
11713    if (!this.success) {
11714        //Any functions that are passed as arguments are bound to the ready callback
11715        for ( i = 0; i < arguments.length; i++ ) {
11716            if ( $.isFunction( arguments[ i ] ) ) {
11717                this.success = arguments[ i ];
11718                //only one callback per constructor
11719                break;
11720            }
11721        }
11722    }
11723
11724    if (this.success) {
11725        this.addHandler( 'ready', function ( event ) {
11726            _this.success( event );
11727        } );
11728    }
11729
11730    /**
11731     * Ratio of width to height
11732     * @member {Number} aspectRatio
11733     * @memberof OpenSeadragon.TileSource#
11734     */
11735    /**
11736     * Vector storing x and y dimensions ( width and height respectively ).
11737     * @member {OpenSeadragon.Point} dimensions
11738     * @memberof OpenSeadragon.TileSource#
11739     */
11740    /**
11741     * The overlap in pixels each tile shares with its adjacent neighbors.
11742     * @member {Number} tileOverlap
11743     * @memberof OpenSeadragon.TileSource#
11744     */
11745    /**
11746     * The minimum pyramid level this tile source supports or should attempt to load.
11747     * @member {Number} minLevel
11748     * @memberof OpenSeadragon.TileSource#
11749     */
11750    /**
11751     * The maximum pyramid level this tile source supports or should attempt to load.
11752     * @member {Number} maxLevel
11753     * @memberof OpenSeadragon.TileSource#
11754     */
11755    /**
11756     *
11757     * @member {Boolean} ready
11758     * @memberof OpenSeadragon.TileSource#
11759     */
11760
11761    if( 'string' == $.type( arguments[ 0 ] ) ){
11762        this.url = arguments[0];
11763    }
11764
11765    if (this.url) {
11766        //in case the getImageInfo method is overridden and/or implies an
11767        //async mechanism set some safe defaults first
11768        this.aspectRatio = 1;
11769        this.dimensions  = new $.Point( 10, 10 );
11770        this._tileWidth  = 0;
11771        this._tileHeight = 0;
11772        this.tileOverlap = 0;
11773        this.minLevel    = 0;
11774        this.maxLevel    = 0;
11775        this.ready       = false;
11776        //configuration via url implies the extending class
11777        //implements and 'configure'
11778        this.getImageInfo( this.url );
11779
11780    } else {
11781
11782        //explicit configuration via positional args in constructor
11783        //or the more idiomatic 'options' object
11784        this.ready       = true;
11785        this.aspectRatio = (options.width && options.height) ?
11786            (options.width / options.height) : 1;
11787        this.dimensions  = new $.Point( options.width, options.height );
11788
11789        if ( this.tileSize ){
11790            this._tileWidth = this._tileHeight = this.tileSize;
11791            delete this.tileSize;
11792        } else {
11793            if( this.tileWidth ){
11794                // We were passed tileWidth in options, but we want to rename it
11795                // with a leading underscore to make clear that it is not safe to directly modify it
11796                this._tileWidth = this.tileWidth;
11797                delete this.tileWidth;
11798            } else {
11799                this._tileWidth = 0;
11800            }
11801
11802            if( this.tileHeight ){
11803                // See note above about renaming this.tileWidth
11804                this._tileHeight = this.tileHeight;
11805                delete this.tileHeight;
11806            } else {
11807                this._tileHeight = 0;
11808            }
11809        }
11810
11811        this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
11812        this.minLevel    = options.minLevel ? options.minLevel : 0;
11813        this.maxLevel    = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
11814            options.maxLevel : (
11815                ( options.width && options.height ) ? Math.ceil(
11816                    Math.log( Math.max( options.width, options.height ) ) /
11817                    Math.log( 2 )
11818                ) : 0
11819            );
11820        if( this.success && $.isFunction( this.success ) ){
11821            this.success( this );
11822        }
11823    }
11824
11825
11826};
11827
11828/** @lends OpenSeadragon.TileSource.prototype */
11829$.TileSource.prototype = {
11830
11831    getTileSize: function( level ) {
11832        $.console.error(
11833            "[TileSource.getTileSize] is deprecated. " +
11834            "Use TileSource.getTileWidth() and TileSource.getTileHeight() instead"
11835        );
11836        return this._tileWidth;
11837    },
11838
11839    /**
11840     * Return the tileWidth for a given level.
11841     * Subclasses should override this if tileWidth can be different at different levels
11842     *   such as in IIIFTileSource.  Code should use this function rather than reading
11843     *   from ._tileWidth directly.
11844     * @function
11845     * @param {Number} level
11846     */
11847    getTileWidth: function( level ) {
11848        if (!this._tileWidth) {
11849            return this.getTileSize(level);
11850        }
11851        return this._tileWidth;
11852    },
11853
11854    /**
11855     * Return the tileHeight for a given level.
11856     * Subclasses should override this if tileHeight can be different at different levels
11857     *   such as in IIIFTileSource.  Code should use this function rather than reading
11858     *   from ._tileHeight directly.
11859     * @function
11860     * @param {Number} level
11861     */
11862    getTileHeight: function( level ) {
11863        if (!this._tileHeight) {
11864            return this.getTileSize(level);
11865        }
11866        return this._tileHeight;
11867    },
11868
11869    /**
11870     * @function
11871     * @param {Number} level
11872     */
11873    getLevelScale: function( level ) {
11874
11875        // see https://github.com/openseadragon/openseadragon/issues/22
11876        // we use the tilesources implementation of getLevelScale to generate
11877        // a memoized re-implementation
11878        var levelScaleCache = {},
11879            i;
11880        for( i = 0; i <= this.maxLevel; i++ ){
11881            levelScaleCache[ i ] = 1 / Math.pow(2, this.maxLevel - i);
11882        }
11883        this.getLevelScale = function( _level ){
11884            return levelScaleCache[ _level ];
11885        };
11886        return this.getLevelScale( level );
11887    },
11888
11889    /**
11890     * @function
11891     * @param {Number} level
11892     */
11893    getNumTiles: function( level ) {
11894        var scale = this.getLevelScale( level ),
11895            x = Math.ceil( scale * this.dimensions.x / this.getTileWidth(level) ),
11896            y = Math.ceil( scale * this.dimensions.y / this.getTileHeight(level) );
11897
11898        return new $.Point( x, y );
11899    },
11900
11901    /**
11902     * @function
11903     * @param {Number} level
11904     */
11905    getPixelRatio: function( level ) {
11906        var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ),
11907            rx = 1.0 / imageSizeScaled.x,
11908            ry = 1.0 / imageSizeScaled.y;
11909
11910        return new $.Point(rx, ry);
11911    },
11912
11913
11914    /**
11915     * @function
11916     * @returns {Number} The highest level in this tile source that can be contained in a single tile.
11917     */
11918    getClosestLevel: function() {
11919        var i,
11920            tiles;
11921
11922        for (i = this.minLevel + 1; i <= this.maxLevel; i++){
11923            tiles = this.getNumTiles(i);
11924            if (tiles.x > 1 || tiles.y > 1) {
11925                break;
11926            }
11927        }
11928
11929        return i - 1;
11930    },
11931
11932    /**
11933     * @function
11934     * @param {Number} level
11935     * @param {OpenSeadragon.Point} point
11936     */
11937    getTileAtPoint: function(level, point) {
11938        var validPoint = point.x >= 0 && point.x <= 1 &&
11939            point.y >= 0 && point.y <= 1 / this.aspectRatio;
11940        $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
11941
11942        var widthScaled = this.dimensions.x * this.getLevelScale(level);
11943        var pixelX = point.x * widthScaled;
11944        var pixelY = point.y * widthScaled;
11945
11946        var x = Math.floor(pixelX / this.getTileWidth(level));
11947        var y = Math.floor(pixelY / this.getTileHeight(level));
11948
11949        // When point.x == 1 or point.y == 1 / this.aspectRatio we want to
11950        // return the last tile of the row/column
11951        if (point.x >= 1) {
11952            x = this.getNumTiles(level).x - 1;
11953        }
11954        var EPSILON = 1e-15;
11955        if (point.y >= 1 / this.aspectRatio - EPSILON) {
11956            y = this.getNumTiles(level).y - 1;
11957        }
11958
11959        return new $.Point(x, y);
11960    },
11961
11962    /**
11963     * @function
11964     * @param {Number} level
11965     * @param {Number} x
11966     * @param {Number} y
11967     * @param {Boolean} [isSource=false] Whether to return the source bounds of the tile.
11968     * @returns {OpenSeadragon.Rect} Either where this tile fits (in normalized coordinates) or the
11969     * portion of the tile to use as the source of the drawing operation (in pixels), depending on
11970     * the isSource parameter.
11971     */
11972    getTileBounds: function( level, x, y, isSource ) {
11973        var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
11974            tileWidth = this.getTileWidth(level),
11975            tileHeight = this.getTileHeight(level),
11976            px = ( x === 0 ) ? 0 : tileWidth * x - this.tileOverlap,
11977            py = ( y === 0 ) ? 0 : tileHeight * y - this.tileOverlap,
11978            sx = tileWidth + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
11979            sy = tileHeight + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
11980            scale = 1.0 / dimensionsScaled.x;
11981
11982        sx = Math.min( sx, dimensionsScaled.x - px );
11983        sy = Math.min( sy, dimensionsScaled.y - py );
11984
11985        if (isSource) {
11986            return new $.Rect(0, 0, sx, sy);
11987        }
11988
11989        return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
11990    },
11991
11992
11993    /**
11994     * Responsible for retrieving, and caching the
11995     * image metadata pertinent to this TileSources implementation.
11996     * @function
11997     * @param {String} url
11998     * @throws {Error}
11999     */
12000    getImageInfo: function( url ) {
12001        var _this = this,
12002            callbackName,
12003            callback,
12004            readySource,
12005            options,
12006            urlParts,
12007            filename,
12008            lastDot;
12009
12010
12011        if( url ) {
12012            urlParts = url.split( '/' );
12013            filename = urlParts[ urlParts.length - 1 ];
12014            lastDot  = filename.lastIndexOf( '.' );
12015            if ( lastDot > -1 ) {
12016                urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot );
12017            }
12018        }
12019
12020        callback = function( data ){
12021            if( typeof (data) === "string" ) {
12022                data = $.parseXml( data );
12023            }
12024            var $TileSource = $.TileSource.determineType( _this, data, url );
12025            if ( !$TileSource ) {
12026                /**
12027                 * Raised when an error occurs loading a TileSource.
12028                 *
12029                 * @event open-failed
12030                 * @memberof OpenSeadragon.TileSource
12031                 * @type {object}
12032                 * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
12033                 * @property {String} message
12034                 * @property {String} source
12035                 * @property {?Object} userData - Arbitrary subscriber-defined object.
12036                 */
12037                _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } );
12038                return;
12039            }
12040
12041            options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
12042            if (options.ajaxWithCredentials === undefined) {
12043                options.ajaxWithCredentials = _this.ajaxWithCredentials;
12044            }
12045
12046            readySource = new $TileSource( options );
12047            _this.ready = true;
12048            /**
12049             * Raised when a TileSource is opened and initialized.
12050             *
12051             * @event ready
12052             * @memberof OpenSeadragon.TileSource
12053             * @type {object}
12054             * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
12055             * @property {Object} tileSource
vendor: 9,701 bytes, lines 12056-12314
12056             * @property {?Object} userData - Arbitrary subscriber-defined object.
12057             */
12058            _this.raiseEvent( 'ready', { tileSource: readySource } );
12059        };
12060
12061        if( url.match(/\.js$/) ){
12062            //TODO: Its not very flexible to require tile sources to end jsonp
12063            //      request for info  with a url that ends with '.js' but for
12064            //      now it's the only way I see to distinguish uniformly.
12065            callbackName = url.split('/').pop().replace('.js', '');
12066            $.jsonp({
12067                url: url,
12068                async: false,
12069                callbackName: callbackName,
12070                callback: callback
12071            });
12072        } else {
12073            // request info via xhr asynchronously.
12074            $.makeAjaxRequest( {
12075                url: url,
12076                withCredentials: this.ajaxWithCredentials,
12077                headers: this.ajaxHeaders,
12078                success: function( xhr ) {
12079                    var data = processResponse( xhr );
12080                    callback( data );
12081                },
12082                error: function ( xhr, exc ) {
12083                    var msg;
12084
12085                    /*
12086                        IE < 10 will block XHR requests to different origins. Any property access on the request
12087                        object will raise an exception which we'll attempt to handle by formatting the original
12088                        exception rather than the second one raised when we try to access xhr.status
12089                     */
12090                    try {
12091                        msg = "HTTP " + xhr.status + " attempting to load TileSource";
12092                    } catch ( e ) {
12093                        var formattedExc;
12094                        if ( typeof ( exc ) == "undefined" || !exc.toString ) {
12095                            formattedExc = "Unknown error";
12096                        } else {
12097                            formattedExc = exc.toString();
12098                        }
12099
12100                        msg = formattedExc + " attempting to load TileSource";
12101                    }
12102
12103                    /***
12104                     * Raised when an error occurs loading a TileSource.
12105                     *
12106                     * @event open-failed
12107                     * @memberof OpenSeadragon.TileSource
12108                     * @type {object}
12109                     * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
12110                     * @property {String} message
12111                     * @property {String} source
12112                     * @property {?Object} userData - Arbitrary subscriber-defined object.
12113                     */
12114                    _this.raiseEvent( 'open-failed', {
12115                        message: msg,
12116                        source: url
12117                    });
12118                }
12119            });
12120        }
12121
12122    },
12123
12124    /**
12125     * Responsible determining if a the particular TileSource supports the
12126     * data format ( and allowed to apply logic against the url the data was
12127     * loaded from, if any ). Overriding implementations are expected to do
12128     * something smart with data and / or url to determine support.  Also
12129     * understand that iteration order of TileSources is not guarunteed so
12130     * please make sure your data or url is expressive enough to ensure a simple
12131     * and sufficient mechanisim for clear determination.
12132     * @function
12133     * @param {String|Object|Array|Document} data
12134     * @param {String} url - the url the data was loaded
12135     *      from if any.
12136     * @return {Boolean}
12137     */
12138    supports: function( data, url ) {
12139        return false;
12140    },
12141
12142    /**
12143     * Responsible for parsing and configuring the
12144     * image metadata pertinent to this TileSources implementation.
12145     * This method is not implemented by this class other than to throw an Error
12146     * announcing you have to implement it.  Because of the variety of tile
12147     * server technologies, and various specifications for building image
12148     * pyramids, this method is here to allow easy integration.
12149     * @function
12150     * @param {String|Object|Array|Document} data
12151     * @param {String} url - the url the data was loaded
12152     *      from if any.
12153     * @return {Object} options - A dictionary of keyword arguments sufficient
12154     *      to configure this tile sources constructor.
12155     * @throws {Error}
12156     */
12157    configure: function( data, url ) {
12158        throw new Error( "Method not implemented." );
12159    },
12160
12161    /**
12162     * Responsible for retrieving the url which will return an image for the
12163     * region specified by the given x, y, and level components.
12164     * This method is not implemented by this class other than to throw an Error
12165     * announcing you have to implement it.  Because of the variety of tile
12166     * server technologies, and various specifications for building image
12167     * pyramids, this method is here to allow easy integration.
12168     * @function
12169     * @param {Number} level
12170     * @param {Number} x
12171     * @param {Number} y
12172     * @throws {Error}
12173     */
12174    getTileUrl: function( level, x, y ) {
12175        throw new Error( "Method not implemented." );
12176    },
12177
12178    /**
12179     * Responsible for retrieving the headers which will be attached to the image request for the
12180     * region specified by the given x, y, and level components.
12181     * This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true.
12182     * The headers returned here will override headers specified at the Viewer or TiledImage level.
12183     * Specifying a falsy value for a header will clear its existing value set at the Viewer or
12184     * TiledImage level (if any).
12185     * @function
12186     * @param {Number} level
12187     * @param {Number} x
12188     * @param {Number} y
12189     * @returns {Object}
12190     */
12191    getTileAjaxHeaders: function( level, x, y ) {
12192        return {};
12193    },
12194
12195    /**
12196     * @function
12197     * @param {Number} level
12198     * @param {Number} x
12199     * @param {Number} y
12200     */
12201    tileExists: function( level, x, y ) {
12202        var numTiles = this.getNumTiles( level );
12203        return level >= this.minLevel &&
12204               level <= this.maxLevel &&
12205               x >= 0 &&
12206               y >= 0 &&
12207               x < numTiles.x &&
12208               y < numTiles.y;
12209    }
12210};
12211
12212
12213$.extend( true, $.TileSource.prototype, $.EventSource.prototype );
12214
12215
12216/**
12217 * Decides whether to try to process the response as xml, json, or hand back
12218 * the text
12219 * @private
12220 * @inner
12221 * @function
12222 * @param {XMLHttpRequest} xhr - the completed network request
12223 */
12224function processResponse( xhr ){
12225    var responseText = xhr.responseText,
12226        status       = xhr.status,
12227        statusText,
12228        data;
12229
12230    if ( !xhr ) {
12231        throw new Error( $.getString( "Errors.Security" ) );
12232    } else if ( xhr.status !== 200 && xhr.status !== 0 ) {
12233        status     = xhr.status;
12234        statusText = ( status == 404 ) ?
12235            "Not Found" :
12236            xhr.statusText;
12237        throw new Error( $.getString( "Errors.Status", status, statusText ) );
12238    }
12239
12240    if( responseText.match(/\s*<.*/) ){
12241        try{
12242        data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
12243            xhr.responseXML :
12244            $.parseXml( responseText );
12245        } catch (e){
12246            data = xhr.responseText;
12247        }
12248    }else if( responseText.match(/\s*[\{\[].*/) ){
12249        try{
12250          data = $.parseJSON(responseText);
12251        } catch(e){
12252          data =  responseText;
12253        }
12254    }else{
12255        data = responseText;
12256    }
12257    return data;
12258}
12259
12260
12261/**
12262 * Determines the TileSource Implementation by introspection of OpenSeadragon
12263 * namespace, calling each TileSource implementation of 'isType'
12264 * @private
12265 * @inner
12266 * @function
12267 * @param {Object|Array|Document} data - the tile source configuration object
12268 * @param {String} url - the url where the tile source configuration object was
12269 *      loaded from, if any.
12270 */
12271$.TileSource.determineType = function( tileSource, data, url ){
12272    var property;
12273    for( property in OpenSeadragon ){
12274        if( property.match(/.+TileSource$/) &&
12275            $.isFunction( OpenSeadragon[ property ] ) &&
12276            $.isFunction( OpenSeadragon[ property ].prototype.supports ) &&
12277            OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url )
12278        ){
12279            return OpenSeadragon[ property ];
12280        }
12281    }
12282
12283    $.console.error( "No TileSource was able to open %s %s", url, data );
12284};
12285
12286
12287}( OpenSeadragon ));
12288
12289/*
12290 * OpenSeadragon - DziTileSource
12291 *
12292 * Copyright (C) 2009 CodePlex Foundation
12293 * Copyright (C) 2010-2013 OpenSeadragon contributors
12294 *
12295 * Redistribution and use in source and binary forms, with or without
12296 * modification, are permitted provided that the following conditions are
12297 * met:
12298 *
12299 * - Redistributions of source code must retain the above copyright notice,
12300 *   this list of conditions and the following disclaimer.
12301 *
12302 * - Redistributions in binary form must reproduce the above copyright
12303 *   notice, this list of conditions and the following disclaimer in the
12304 *   documentation and/or other materials provided with the distribution.
12305 *
12306 * - Neither the name of CodePlex Foundation nor the names of its
12307 *   contributors may be used to endorse or promote products derived from
12308 *   this software without specific prior written permission.
12309 *
12310 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12311 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12312 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12313 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
12314 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
12315 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
12316 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
12317 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
12318 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
12319 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
12320 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12321 */
12322
12323(function( $ ){
12324
12325/**
12326 * @class DziTileSource
12327 * @memberof OpenSeadragon
12328 * @extends OpenSeadragon.TileSource
12329 * @param {Number|Object} width - the pixel width of the image or the idiomatic
12330 *      options object which is used instead of positional arguments.
12331 * @param {Number} height
12332 * @param {Number} tileSize
12333 * @param {Number} tileOverlap
12334 * @param {String} tilesUrl
12335 * @param {String} fileFormat
12336 * @param {OpenSeadragon.DisplayRect[]} displayRects
12337 * @property {String} tilesUrl
12338 * @property {String} fileFormat
12339 * @property {OpenSeadragon.DisplayRect[]} displayRects
12340 */
12341$.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects, minLevel, maxLevel ) {
12342    var i,
12343        rect,
12344        level,
12345        options;
12346
12347    if( $.isPlainObject( width ) ){
12348        options = width;
12349    }else{
12350        options = {
12351            width: arguments[ 0 ],
12352            height: arguments[ 1 ],
12353            tileSize: arguments[ 2 ],
12354            tileOverlap: arguments[ 3 ],
12355            tilesUrl: arguments[ 4 ],
12356            fileFormat: arguments[ 5 ],
12357            displayRects: arguments[ 6 ],
12358            minLevel: arguments[ 7 ],
12359            maxLevel: arguments[ 8 ]
12360        };
12361    }
12362
12363    this._levelRects  = {};
12364    this.tilesUrl     = options.tilesUrl;
12365    this.fileFormat   = options.fileFormat;
12366    this.displayRects = options.displayRects;
12367
12368    if ( this.displayRects ) {
12369        for ( i = this.displayRects.length - 1; i >= 0; i-- ) {
12370            rect = this.displayRects[ i ];
12371            for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) {
12372                if ( !this._levelRects[ level ] ) {
12373                    this._levelRects[ level ] = [];
12374                }
12375                this._levelRects[ level ].push( rect );
12376            }
12377        }
12378    }
12379
12380    $.TileSource.apply( this, [ options ] );
12381
12382};
12383
12384$.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.DziTileSource.prototype */{
12385
12386
12387    /**
12388     * Determine if the data and/or url imply the image service is supported by
12389     * this tile source.
12390     * @function
12391     * @param {Object|Array} data
12392     * @param {String} optional - url
12393     */
12394    supports: function( data, url ){
12395        var ns;
12396        if ( data.Image ) {
12397            ns = data.Image.xmlns;
12398        } else if ( data.documentElement) {
12399            if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) {
12400                ns = data.documentElement.namespaceURI;
12401            }
12402        }
12403
12404        ns = (ns || '').toLowerCase();
12405
12406        return (ns.indexOf('schemas.microsoft.com/deepzoom/2008') !== -1 ||
12407            ns.indexOf('schemas.microsoft.com/deepzoom/2009') !== -1);
12408    },
12409
12410    /**
12411     *
12412     * @function
12413     * @param {Object|XMLDocument} data - the raw configuration
12414     * @param {String} url - the url the data was retrieved from if any.
12415     * @return {Object} options - A dictionary of keyword arguments sufficient
12416     *      to configure this tile sources constructor.
12417     */
12418    configure: function( data, url ){
12419
12420        var options;
12421
12422        if( !$.isPlainObject(data) ){
12423
12424            options = configureFromXML( this, data );
12425
12426        }else{
12427
12428            options = configureFromObject( this, data );
12429        }
12430
12431        if (url && !options.tilesUrl) {
12432            options.tilesUrl = url.replace(
12433                    /([^\/]+?)(\.(dzi|xml|js)?(\?[^\/]*)?)?\/?$/, '$1_files/');
12434
12435            if (url.search(/\.(dzi|xml|js)\?/) != -1) {
12436                options.queryParams = url.match(/\?.*/);
12437            }else{
12438                options.queryParams = '';
12439            }
12440        }
12441
12442        return options;
12443    },
12444
12445
12446    /**
12447     * @function
12448     * @param {Number} level
12449     * @param {Number} x
12450     * @param {Number} y
12451     */
12452    getTileUrl: function( level, x, y ) {
12453        return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat, this.queryParams ].join( '' );
12454    },
12455
12456
12457    /**
12458     * @function
12459     * @param {Number} level
12460     * @param {Number} x
12461     * @param {Number} y
12462     */
12463    tileExists: function( level, x, y ) {
12464        var rects = this._levelRects[ level ],
12465            rect,
12466            scale,
12467            xMin,
12468            yMin,
12469            xMax,
12470            yMax,
12471            i;
12472
12473        if ((this.minLevel && level < this.minLevel) || (this.maxLevel && level > this.maxLevel)) {
12474            return false;
12475        }
12476
12477        if ( !rects || !rects.length ) {
12478            return true;
12479        }
12480
12481        for ( i = rects.length - 1; i >= 0; i-- ) {
12482            rect = rects[ i ];
12483
12484            if ( level < rect.minLevel || level > rect.maxLevel ) {
12485                continue;
12486            }
12487
12488            scale = this.getLevelScale( level );
12489            xMin = rect.x * scale;
12490            yMin = rect.y * scale;
12491            xMax = xMin + rect.width * scale;
12492            yMax = yMin + rect.height * scale;
12493
12494            xMin = Math.floor( xMin / this._tileWidth );
12495            yMin = Math.floor( yMin / this._tileWidth ); // DZI tiles are square, so we just use _tileWidth
12496            xMax = Math.ceil( xMax / this._tileWidth );
12497            yMax = Math.ceil( yMax / this._tileWidth );
12498
12499            if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) {
12500                return true;
12501            }
12502        }
12503
12504        return false;
12505    }
12506});
12507
12508
12509/**
12510 * @private
12511 * @inner
12512 * @function
12513 */
12514function configureFromXML( tileSource, xmlDoc ){
12515
12516    if ( !xmlDoc || !xmlDoc.documentElement ) {
12517        throw new Error( $.getString( "Errors.Xml" ) );
12518    }
12519
12520    var root           = xmlDoc.documentElement,
12521        rootName       = root.localName || root.tagName,
12522        ns             = xmlDoc.documentElement.namespaceURI,
12523        configuration  = null,
12524        displayRects   = [],
12525        dispRectNodes,
12526        dispRectNode,
12527        rectNode,
12528        sizeNode,
12529        i;
12530
12531    if ( rootName == "Image" ) {
12532
12533        try {
12534            sizeNode = root.getElementsByTagName("Size" )[ 0 ];
12535            if (sizeNode === undefined) {
12536                sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ];
12537            }
12538
12539            configuration = {
12540                Image: {
12541                    xmlns:       "http://schemas.microsoft.com/deepzoom/2008",
12542                    Url:         root.getAttribute( "Url" ),
12543                    Format:      root.getAttribute( "Format" ),
12544                    DisplayRect: null,
12545                    Overlap:     parseInt( root.getAttribute( "Overlap" ), 10 ),
12546                    TileSize:    parseInt( root.getAttribute( "TileSize" ), 10 ),
12547                    Size: {
12548                        Height: parseInt( sizeNode.getAttribute( "Height" ), 10 ),
12549                        Width:  parseInt( sizeNode.getAttribute( "Width" ), 10 )
12550                    }
12551                }
12552            };
12553
12554            if ( !$.imageFormatSupported( configuration.Image.Format ) ) {
12555                throw new Error(
12556                    $.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() )
12557                );
12558            }
12559
12560            dispRectNodes = root.getElementsByTagName("DisplayRect" );
12561            if (dispRectNodes === undefined) {
12562                dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ];
12563            }
12564
12565            for ( i = 0; i < dispRectNodes.length; i++ ) {
12566                dispRectNode = dispRectNodes[ i ];
12567                rectNode     = dispRectNode.getElementsByTagName("Rect" )[ 0 ];
12568                if (rectNode === undefined) {
12569                    rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ];
12570                }
12571
12572                displayRects.push({
12573                    Rect: {
12574                        X: parseInt( rectNode.getAttribute( "X" ), 10 ),
12575                        Y: parseInt( rectNode.getAttribute( "Y" ), 10 ),
12576                        Width: parseInt( rectNode.getAttribute( "Width" ), 10 ),
12577                        Height: parseInt( rectNode.getAttribute( "Height" ), 10 ),
12578                        MinLevel: parseInt( dispRectNode.getAttribute( "MinLevel" ), 10 ),
12579                        MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 )
12580                    }
12581                });
12582            }
12583
12584            if( displayRects.length ){
12585                configuration.Image.DisplayRect = displayRects;
12586            }
12587
12588            return configureFromObject( tileSource, configuration );
12589
12590        } catch ( e ) {
12591            throw (e instanceof Error) ?
12592                e :
12593                new Error( $.getString("Errors.Dzi") );
12594        }
12595    } else if ( rootName == "Collection" ) {
12596        throw new Error( $.getString( "Errors.Dzc" ) );
12597    } else if ( rootName == "Error" ) {
12598        var messageNode = root.getElementsByTagName("Message")[0];
12599        var message = messageNode.firstChild.nodeValue;
12600        throw new Error(message);
12601    }
12602
12603    throw new Error( $.getString( "Errors.Dzi" ) );
12604}
12605
12606/**
12607 * @private
12608 * @inner
12609 * @function
12610 */
12611function configureFromObject( tileSource, configuration ){
12612    var imageData     = configuration.Image,
12613        tilesUrl      = imageData.Url,
12614        fileFormat    = imageData.Format,
12615        sizeData      = imageData.Size,
12616        dispRectData  = imageData.DisplayRect || [],
12617        width         = parseInt( sizeData.Width, 10 ),
12618        height        = parseInt( sizeData.Height, 10 ),
12619        tileSize      = parseInt( imageData.TileSize, 10 ),
12620        tileOverlap   = parseInt( imageData.Overlap, 10 ),
12621        displayRects  = [],
12622        rectData,
12623        i;
12624
12625    //TODO: need to figure out out to better handle image format compatibility
12626    //      which actually includes additional file formats like xml and pdf
12627    //      and plain text for various tilesource implementations to avoid low
12628    //      level errors.
12629    //
12630    //      For now, just don't perform the check.
12631    //
12632    /*if ( !imageFormatSupported( fileFormat ) ) {
12633        throw new Error(
12634            $.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
12635        );
12636    }*/
12637
12638    for ( i = 0; i < dispRectData.length; i++ ) {
vendor: 8,231 bytes, lines 12639-12844
12639        rectData = dispRectData[ i ].Rect;
12640
12641        displayRects.push( new $.DisplayRect(
12642            parseInt( rectData.X, 10 ),
12643            parseInt( rectData.Y, 10 ),
12644            parseInt( rectData.Width, 10 ),
12645            parseInt( rectData.Height, 10 ),
12646            parseInt( rectData.MinLevel, 10 ),
12647            parseInt( rectData.MaxLevel, 10 )
12648        ));
12649    }
12650
12651    return $.extend(true, {
12652        width: width, /* width *required */
12653        height: height, /* height *required */
12654        tileSize: tileSize, /* tileSize *required */
12655        tileOverlap: tileOverlap, /* tileOverlap *required */
12656        minLevel: null, /* minLevel */
12657        maxLevel: null, /* maxLevel */
12658        tilesUrl: tilesUrl, /* tilesUrl */
12659        fileFormat: fileFormat, /* fileFormat */
12660        displayRects: displayRects /* displayRects */
12661    }, configuration );
12662
12663}
12664
12665}( OpenSeadragon ));
12666
12667/*
12668 * OpenSeadragon - IIIFTileSource
12669 *
12670 * Copyright (C) 2009 CodePlex Foundation
12671 * Copyright (C) 2010-2013 OpenSeadragon contributors
12672 *
12673 * Redistribution and use in source and binary forms, with or without
12674 * modification, are permitted provided that the following conditions are
12675 * met:
12676 *
12677 * - Redistributions of source code must retain the above copyright notice,
12678 *   this list of conditions and the following disclaimer.
12679 *
12680 * - Redistributions in binary form must reproduce the above copyright
12681 *   notice, this list of conditions and the following disclaimer in the
12682 *   documentation and/or other materials provided with the distribution.
12683 *
12684 * - Neither the name of CodePlex Foundation nor the names of its
12685 *   contributors may be used to endorse or promote products derived from
12686 *   this software without specific prior written permission.
12687 *
12688 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12689 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12690 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12691 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
12692 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
12693 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
12694 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
12695 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
12696 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
12697 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
12698 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12699 */
12700
12701(function( $ ){
12702
12703/**
12704 * @class IIIFTileSource
12705 * @classdesc A client implementation of the International Image Interoperability Framework
12706 * Format: Image API 1.0 - 2.1
12707 *
12708 * @memberof OpenSeadragon
12709 * @extends OpenSeadragon.TileSource
12710 * @see http://iiif.io/api/image/
12711 * @param {String} [options.tileFormat='jpg']
12712 *      The extension that will be used when requiring tiles.
12713 */
12714$.IIIFTileSource = function( options ){
12715
12716    /* eslint-disable camelcase */
12717
12718    $.extend( true, this, options );
12719
12720    if ( !( this.height && this.width && this['@id'] ) ) {
12721        throw new Error( 'IIIF required parameters not provided.' );
12722    }
12723
12724    options.tileSizePerScaleFactor = {};
12725
12726    this.tileFormat = this.tileFormat || 'jpg';
12727
12728    this.version = options.version;
12729
12730    // N.B. 2.0 renamed scale_factors to scaleFactors
12731    if ( this.tile_width && this.tile_height ) {
12732        options.tileWidth = this.tile_width;
12733        options.tileHeight = this.tile_height;
12734    } else if ( this.tile_width ) {
12735        options.tileSize = this.tile_width;
12736    } else if ( this.tile_height ) {
12737        options.tileSize = this.tile_height;
12738    } else if ( this.tiles ) {
12739        // Version 2.0 forwards
12740        if ( this.tiles.length == 1 ) {
12741            options.tileWidth  = this.tiles[0].width;
12742            // Use height if provided, otherwise assume square tiles and use width.
12743            options.tileHeight = this.tiles[0].height || this.tiles[0].width;
12744            this.scale_factors = this.tiles[0].scaleFactors;
12745        } else {
12746            // Multiple tile sizes at different levels
12747            this.scale_factors = [];
12748            for (var t = 0; t < this.tiles.length; t++ ) {
12749                for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
12750                    var scaleFactor = this.tiles[t].scaleFactors[sf];
12751                    this.scale_factors.push(scaleFactor);
12752                    options.tileSizePerScaleFactor[scaleFactor] = {
12753                        width: this.tiles[t].width,
12754                        height: this.tiles[t].height || this.tiles[t].width
12755                    };
12756                }
12757            }
12758        }
12759    } else if ( canBeTiled(options) ) {
12760        // use the largest of tileOptions that is smaller than the short dimension
12761        var shortDim = Math.min( this.height, this.width ),
12762            tileOptions = [256, 512, 1024],
12763            smallerTiles = [];
12764
12765        for ( var c = 0; c < tileOptions.length; c++ ) {
12766            if ( tileOptions[c] <= shortDim ) {
12767                smallerTiles.push( tileOptions[c] );
12768            }
12769        }
12770
12771        if ( smallerTiles.length > 0 ) {
12772            options.tileSize = Math.max.apply( null, smallerTiles );
12773        } else {
12774            // If we're smaller than 256, just use the short side.
12775            options.tileSize = shortDim;
12776        }
12777    } else if (this.sizes && this.sizes.length > 0) {
12778        // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array.
12779        // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the
12780        // LegacyTileSource instead of performing IIIF tiling.
12781        this.emulateLegacyImagePyramid = true;
12782
12783        options.levels = constructLevels( this );
12784        // use the largest available size to define tiles
12785        $.extend( true, options, {
12786            width: options.levels[ options.levels.length - 1 ].width,
12787            height: options.levels[ options.levels.length - 1 ].height,
12788            tileSize: Math.max( options.height, options.width ),
12789            tileOverlap: 0,
12790            minLevel: 0,
12791            maxLevel: options.levels.length - 1
12792        });
12793        this.levels = options.levels;
12794    } else {
12795        $.console.error("Nothing in the info.json to construct image pyramids from");
12796    }
12797
12798    if (!options.maxLevel && !this.emulateLegacyImagePyramid) {
12799        if (!this.scale_factors) {
12800            options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2)));
12801        } else {
12802            var maxScaleFactor = Math.max.apply(null, this.scale_factors);
12803            options.maxLevel = Math.round(Math.log(maxScaleFactor) * Math.LOG2E);
12804        }
12805    }
12806
12807    $.TileSource.apply( this, [ options ] );
12808};
12809
12810$.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
12811    /**
12812     * Determine if the data and/or url imply the image service is supported by
12813     * this tile source.
12814     * @function
12815     * @param {Object|Array} data
12816     * @param {String} optional - url
12817     */
12818
12819    supports: function( data, url ) {
12820        // Version 2.0 and forwards
12821        if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
12822            return true;
12823        // Version 1.1
12824        } else if ( data['@context'] && (
12825            data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" ||
12826            data['@context'] == "http://iiif.io/api/image/1/context.json") ) {
12827            // N.B. the iiif.io context is wrong, but where the representation lives so likely to be used
12828            return true;
12829
12830        // Version 1.0
12831        } else if ( data.profile &&
12832            data.profile.indexOf("http://library.stanford.edu/iiif/image-api/compliance.html") === 0) {
12833            return true;
12834        } else if ( data.identifier && data.width && data.height ) {
12835            return true;
12836        } else if ( data.documentElement &&
12837            "info" == data.documentElement.tagName &&
12838            "http://library.stanford.edu/iiif/image-api/ns/" ==
12839                data.documentElement.namespaceURI) {
12840            return true;
12841
12842        // Not IIIF
12843        } else {
12844            return false;
12845        }
12846    },
12847
12848    /**
12849     *
12850     * @function
12851     * @param {Object} data - the raw configuration
12852     * @example <caption>IIIF 1.1 Info Looks like this</caption>
12853     * {
12854     *   "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
12855     *   "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
12856     *   "width" : 6000,
12857     *   "height" : 4000,
12858     *   "scale_factors" : [ 1, 2, 4 ],
12859     *   "tile_width" : 1024,
12860     *   "tile_height" : 1024,
12861     *   "formats" : [ "jpg", "png" ],
12862     *   "qualities" : [ "native", "grey" ],
12863     *   "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
12864     * }
12865     */
12866    configure: function( data, url ){
12867        // Try to deduce our version and fake it upwards if needed
12868        if ( !$.isPlainObject(data) ) {
12869            var options = configureFromXml10( data );
12870            options['@context'] = "http://iiif.io/api/image/1.0/context.json";
12871            options['@id'] = url.replace('/info.xml', '');
12872            options.version = 1;
12873            return options;
12874        } else {
12875            if ( !data['@context'] ) {
12876                data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
12877                data['@id'] = url.replace('/info.json', '');
12878                data.version = 1;
12879            } else {
12880                var context = data['@context'];
12881                if (Array.isArray(context)) {
12882                    for (var i = 0; i < context.length; i++) {
12883                        if (typeof context[i] === 'string' &&
12884                            ( /^http:\/\/iiif\.io\/api\/image\/[1-3]\/context\.json$/.test(context[i]) ||
12885                            context[i] === 'http://library.stanford.edu/iiif/image-api/1.1/context.json' ) ) {
12886                            context = context[i];
12887                            break;
12888                        }
12889                    }
12890                }
12891                switch (context) {
12892                    case 'http://iiif.io/api/image/1/context.json':
12893                    case 'http://library.stanford.edu/iiif/image-api/1.1/context.json':
12894                        data.version = 1;
12895                        break;
12896                    case 'http://iiif.io/api/image/2/context.json':
12897                        data.version = 2;
12898                        break;
12899                    case 'http://iiif.io/api/image/3/context.json':
12900                        data.version = 3;
12901                        break;
12902                    default:
12903                        $.console.error('Data has a @context property which contains no known IIIF context URI.');
12904                }
12905            }
12906            if ( !data['@id'] && data['id'] ) {
12907                data['@id'] = data['id'];
12908            }
12909            if(data.preferredFormats) {
12910                for (var f = 0; f < data.preferredFormats.length; f++ ) {
12911                    if ( OpenSeadragon.imageFormatSupported(data.preferredFormats[f]) ) {
12912                        data.tileFormat = data.preferredFormats[f];
12913                        break;
12914                    }
12915                }
12916            }
12917            return data;
12918        }
12919    },
12920
12921    /**
12922     * Return the tileWidth for the given level.
12923     * @function
12924     * @param {Number} level
12925     */
12926    getTileWidth: function( level ) {
12927
12928        if(this.emulateLegacyImagePyramid) {
12929            return $.TileSource.prototype.getTileWidth.call(this, level);
12930        }
12931
12932        var scaleFactor = Math.pow(2, this.maxLevel - level);
12933
12934        if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
12935            return this.tileSizePerScaleFactor[scaleFactor].width;
12936        }
12937        return this._tileWidth;
12938    },
12939
12940    /**
12941     * Return the tileHeight for the given level.
12942     * @function
12943     * @param {Number} level
12944     */
12945    getTileHeight: function( level ) {
12946
12947        if(this.emulateLegacyImagePyramid) {
12948            return $.TileSource.prototype.getTileHeight.call(this, level);
12949        }
12950
12951        var scaleFactor = Math.pow(2, this.maxLevel - level);
12952
12953        if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
12954            return this.tileSizePerScaleFactor[scaleFactor].height;
12955        }
12956        return this._tileHeight;
12957    },
12958
12959    /**
12960     * @function
12961     * @param {Number} level
12962     */
12963    getLevelScale: function ( level ) {
12964
12965        if(this.emulateLegacyImagePyramid) {
12966            var levelScale = NaN;
12967            if (this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel) {
12968                levelScale =
12969                    this.levels[level].width /
12970                    this.levels[this.maxLevel].width;
12971            }
12972            return levelScale;
12973        }
12974
12975        return $.TileSource.prototype.getLevelScale.call(this, level);
12976    },
12977
12978    /**
12979     * @function
12980     * @param {Number} level
12981     */
12982    getNumTiles: function( level ) {
12983
12984        if(this.emulateLegacyImagePyramid) {
12985            var scale = this.getLevelScale(level);
12986            if (scale) {
12987                return new $.Point(1, 1);
12988            } else {
12989                return new $.Point(0, 0);
12990            }
12991        }
12992
12993        return $.TileSource.prototype.getNumTiles.call(this, level);
12994    },
12995
12996
12997    /**
12998     * @function
12999     * @param {Number} level
13000     * @param {OpenSeadragon.Point} point
13001     */
13002    getTileAtPoint: function( level, point ) {
13003
13004        if(this.emulateLegacyImagePyramid) {
13005            return new $.Point(0, 0);
13006        }
13007
13008        return $.TileSource.prototype.getTileAtPoint.call(this, level, point);
13009    },
13010
13011
13012    /**
13013     * Responsible for retrieving the url which will return an image for the
13014     * region specified by the given x, y, and level components.
13015     * @function
13016     * @param {Number} level - z index
13017     * @param {Number} x
13018     * @param {Number} y
13019     * @throws {Error}
13020     */
13021    getTileUrl: function( level, x, y ){
13022
13023        if(this.emulateLegacyImagePyramid) {
13024            var url = null;
13025            if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
13026                url = this.levels[ level ].url;
13027            }
13028            return url;
13029        }
13030
13031        //# constants
13032        var IIIF_ROTATION = '0',
13033            //## get the scale (level as a decimal)
13034            scale = Math.pow( 0.5, this.maxLevel - level ),
13035
13036            //# image dimensions at this level
13037            levelWidth = Math.ceil( this.width * scale ),
13038            levelHeight = Math.ceil( this.height * scale ),
13039
13040            //## iiif region
13041            tileWidth,
13042            tileHeight,
13043            iiifTileSizeWidth,
13044            iiifTileSizeHeight,
13045            iiifRegion,
13046            iiifTileX,
13047            iiifTileY,
13048            iiifTileW,
13049            iiifTileH,
13050            iiifSize,
13051            iiifSizeW,
13052            iiifSizeH,
13053            iiifQuality,
13054            uri;
13055
13056        tileWidth = this.getTileWidth(level);
13057        tileHeight = this.getTileHeight(level);
13058        iiifTileSizeWidth = Math.ceil( tileWidth / scale );
13059        iiifTileSizeHeight = Math.ceil( tileHeight / scale );
13060        if (this.version === 1) {
13061            iiifQuality = "native." + this.tileFormat;
13062        } else {
13063            iiifQuality = "default." + this.tileFormat;
13064        }
13065        if ( levelWidth < tileWidth && levelHeight < tileHeight ){
13066            if ( this.version === 2 && levelWidth === this.width ) {
13067                iiifSize = "max";
13068            } else if ( this.version === 3 && levelWidth === this.width && levelHeight === this.height ) {
13069                iiifSize = "max";
13070            } else if ( this.version === 3 ) {
13071                iiifSize = levelWidth + "," + levelHeight;
13072            } else {
13073                iiifSize = levelWidth + ",";
13074            }
13075            iiifRegion = 'full';
13076        } else {
13077            iiifTileX = x * iiifTileSizeWidth;
13078            iiifTileY = y * iiifTileSizeHeight;
13079            iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX );
13080            iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY );
13081            if ( x === 0 && y === 0 && iiifTileW === this.width && iiifTileH === this.height ) {
13082                iiifRegion = "full";
13083            } else {
13084                iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
13085            }
13086            iiifSizeW = Math.ceil( iiifTileW * scale );
13087            iiifSizeH = Math.ceil( iiifTileH * scale );
13088            if ( this.version === 2 && iiifSizeW === this.width ) {
13089                iiifSize = "max";
13090            } else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) {
13091                iiifSize = "max";
13092            } else if (this.version === 3) {
13093                iiifSize = iiifSizeW + "," + iiifSizeH;
13094            } else {
13095                iiifSize = iiifSizeW + ",";
13096            }
13097        }
13098        uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' )  + "?token=" + $.DEFAULT_SETTINGS.iiifToken;
13099
13100        return uri;
13101    },
13102
13103    __testonly__: {
13104        canBeTiled: canBeTiled,
13105        constructLevels: constructLevels
13106    }
13107
13108  });
13109
13110    /**
13111     * Determine whether arbitrary tile requests can be made against a service with the given profile
13112     * @function
13113     * @param {array} profile - IIIF profile array
13114     * @throws {Error}
13115     */
13116    function canBeTiled ( options ) {
13117        var level0Profiles = [
13118            "http://library.stanford.edu/iiif/image-api/compliance.html#level0",
13119            "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0",
13120            "http://iiif.io/api/image/2/level0.json",
13121            "level0",
13122            "https://iiif.io/api/image/3/level0.json"
13123        ];
13124        var profileLevel = Array.isArray(options.profile) ? options.profile[0] : options.profile;
13125        var isLevel0 = (level0Profiles.indexOf(profileLevel) !== -1);
13126        var hasCanoncicalSizeFeature = false;
13127        if ( options.version === 2 && options.profile.length > 1 && options.profile[1].supports ) {
13128            hasCanoncicalSizeFeature = options.profile[1].supports.indexOf( "sizeByW" ) !== -1;
13129        }
13130        if ( options.version === 3 && options.extraFeatures ) {
13131            hasCanoncicalSizeFeature = options.extraFeatures.indexOf( "sizeByWh" ) !== -1;
13132        }
13133        return !isLevel0 || hasCanoncicalSizeFeature;
13134    }
13135
13136    /**
13137     * Build the legacy pyramid URLs (one tile per level)
13138     * @function
13139     * @param {object} options - infoJson
13140     * @throws {Error}
13141     */
13142    function constructLevels(options) {
13143        var levels = [];
13144        for(var i = 0; i < options.sizes.length; i++) {
13145            levels.push({
13146                url: options['@id'] + '/full/' + options.sizes[i].width + ',' +
13147                    (options.version === 3 ? options.sizes[i].height : '') +
13148                    '/0/default.' + options.tileFormat,
13149                width: options.sizes[i].width,
13150                height: options.sizes[i].height
13151            });
13152        }
13153        return levels.sort(function(a, b) {
13154            return a.width - b.width;
13155        });
13156    }
13157
13158
13159    function configureFromXml10(xmlDoc) {
13160        //parse the xml
13161        if ( !xmlDoc || !xmlDoc.documentElement ) {
13162            throw new Error( $.getString( "Errors.Xml" ) );
13163        }
13164
13165        var root            = xmlDoc.documentElement,
13166            rootName        = root.tagName,
13167            configuration   = null;
13168
13169        if ( rootName == "info" ) {
13170            try {
13171                configuration = {};
13172                parseXML10( root, configuration );
13173                return configuration;
13174
13175            } catch ( e ) {
13176                throw (e instanceof Error) ?
13177                    e :
13178                    new Error( $.getString("Errors.IIIF") );
13179            }
13180        }
13181        throw new Error( $.getString( "Errors.IIIF" ) );
13182    }
13183
13184    function parseXML10( node, configuration, property ) {
13185        var i,
13186            value;
13187        if ( node.nodeType == 3 && property ) {//text node
13188            value = node.nodeValue.trim();
13189            if( value.match(/^\d*$/)){
13190                value = Number( value );
13191            }
13192            if( !configuration[ property ] ){
13193                configuration[ property ] = value;
13194            }else{
13195                if( !$.isArray( configuration[ property ] ) ){
13196                    configuration[ property ] = [ configuration[ property ] ];
13197                }
13198                configuration[ property ].push( value );
13199            }
13200        } else if( node.nodeType == 1 ){
13201            for( i = 0; i < node.childNodes.length; i++ ){
13202                parseXML10( node.childNodes[ i ], configuration, node.nodeName );
13203            }
13204        }
13205    }
13206
13207
13208
13209}( OpenSeadragon ));
13210
13211/*
13212 * OpenSeadragon - OsmTileSource
13213 *
13214 * Copyright (C) 2009 CodePlex Foundation
13215 * Copyright (C) 2010-2013 OpenSeadragon contributors
13216 *
13217 * Redistribution and use in source and binary forms, with or without
13218 * modification, are permitted provided that the following conditions are
13219 * met:
13220 *
13221 * - Redistributions of source code must retain the above copyright notice,
13222 *   this list of conditions and the following disclaimer.
13223 *
13224 * - Redistributions in binary form must reproduce the above copyright
13225 *   notice, this list of conditions and the following disclaimer in the
13226 *   documentation and/or other materials provided with the distribution.
13227 *
13228 * - Neither the name of CodePlex Foundation nor the names of its
13229 *   contributors may be used to endorse or promote products derived from
13230 *   this software without specific prior written permission.
13231 *
13232 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
13233 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13234 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
13235 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
13236 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13237 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
13238 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
13239 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
13240 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
13241 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
13242 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13243 */
13244
13245/*
13246 * Derived from the OSM tile source in Rainer Simon's seajax-utils project
13247 * <http://github.com/rsimon/seajax-utils>.  Rainer Simon has contributed
13248 * the included code to the OpenSeadragon project under the New BSD license;
13249 * see <https://github.com/openseadragon/openseadragon/issues/58>.
13250 */
13251
13252
13253(function( $ ){
13254
13255/**
13256 * @class OsmTileSource
13257 * @classdesc A tilesource implementation for OpenStreetMap.<br><br>
13258 *
13259 * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In  Deep
13260 * Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of
13261 * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a
13262 * difference of log2(256)=8 levels.<br><br>
13263 *
13264 * Note 2. Image dimension. According to the OSM Wiki
13265 * (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels)
13266 * the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256
13267 * pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864
13268 * pixels.
13269 *
13270 * @memberof OpenSeadragon
13271 * @extends OpenSeadragon.TileSource
13272 * @param {Number|Object} width - the pixel width of the image or the idiomatic
13273 *      options object which is used instead of positional arguments.
13274 * @param {Number} height
13275 * @param {Number} tileSize
13276 * @param {Number} tileOverlap
13277 * @param {String} tilesUrl
13278 */
13279$.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
13280    var options;
13281
13282    if( $.isPlainObject( width ) ){
13283        options = width;
13284    }else{
13285        options = {
13286            width: arguments[0],
13287            height: arguments[1],
13288            tileSize: arguments[2],
13289            tileOverlap: arguments[3],
13290            tilesUrl: arguments[4]
13291        };
13292    }
13293    //apply default setting for standard public OpenStreatMaps service
13294    //but allow them to be specified so fliks can host there own instance
13295    //or apply against other services supportting the same standard
13296    if( !options.width || !options.height ){
13297        options.width = 65572864;
13298        options.height = 65572864;
13299    }
13300    if( !options.tileSize ){
13301        options.tileSize = 256;
13302        options.tileOverlap = 0;
13303    }
13304    if( !options.tilesUrl ){
13305        options.tilesUrl = "http://tile.openstreetmap.org/";
13306    }
13307    options.minLevel = 8;
13308
13309    $.TileSource.apply( this, [ options ] );
13310
13311};
13312
13313$.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.OsmTileSource.prototype */{
13314
13315
13316    /**
13317     * Determine if the data and/or url imply the image service is supported by
13318     * this tile source.
13319     * @function
13320     * @param {Object|Array} data
13321     * @param {String} optional - url
13322     */
13323    supports: function( data, url ){
13324        return (
13325            data.type &&
13326            "openstreetmaps" == data.type
13327        );
13328    },
13329
13330    /**
13331     *
13332     * @function
13333     * @param {Object} data - the raw configuration
13334     * @param {String} url - the url the data was retrieved from if any.
13335     * @return {Object} options - A dictionary of keyword arguments sufficient
13336     *      to configure this tile sources constructor.
13337     */
13338    configure: function( data, url ){
13339        return data;
13340    },
13341
13342
13343    /**
13344     * @function
13345     * @param {Number} level
13346     * @param {Number} x
13347     * @param {Number} y
13348     */
13349    getTileUrl: function( level, x, y ) {
13350        return this.tilesUrl + (level - 8) + "/" + x + "/" + y + ".png";
13351    }
13352});
13353
13354
13355}( OpenSeadragon ));
13356
13357/*
13358 * OpenSeadragon - TmsTileSource
13359 *
13360 * Copyright (C) 2009 CodePlex Foundation
13361 * Copyright (C) 2010-2013 OpenSeadragon contributors
13362 *
13363 * Redistribution and use in source and binary forms, with or without
13364 * modification, are permitted provided that the following conditions are
13365 * met:
13366 *
13367 * - Redistributions of source code must retain the above copyright notice,
13368 *   this list of conditions and the following disclaimer.
13369 *
13370 * - Redistributions in binary form must reproduce the above copyright
13371 *   notice, this list of conditions and the following disclaimer in the
13372 *   documentation and/or other materials provided with the distribution.
13373 *
13374 * - Neither the name of CodePlex Foundation nor the names of its
13375 *   contributors may be used to endorse or promote products derived from
13376 *   this software without specific prior written permission.
13377 *
13378 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
13379 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13380 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
13381 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
13382 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13383 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
13384 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
13385 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
13386 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
13387 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
13388 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13389 */
13390
13391/*
13392 * Derived from the TMS tile source in Rainer Simon's seajax-utils project
13393 * <http://github.com/rsimon/seajax-utils>.  Rainer Simon has contributed
13394 * the included code to the OpenSeadragon project under the New BSD license;
13395 * see <https://github.com/openseadragon/openseadragon/issues/58>.
13396 */
13397
13398
13399(function( $ ){
13400
13401/**
13402 * @class TmsTileSource
13403 * @classdesc A tilesource implementation for Tiled Map Services (TMS).
13404 * TMS tile scheme ( [ as supported by OpenLayers ] is described here
13405 * ( http://openlayers.org/dev/examples/tms.html ).
13406 *
13407 * @memberof OpenSeadragon
13408 * @extends OpenSeadragon.TileSource
13409 * @param {Number|Object} width - the pixel width of the image or the idiomatic
13410 *      options object which is used instead of positional arguments.
13411 * @param {Number} height
13412 * @param {Number} tileSize
13413 * @param {Number} tileOverlap
13414 * @param {String} tilesUrl
13415 */
13416$.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
13417    var options;
13418
13419    if( $.isPlainObject( width ) ){
13420        options = width;
13421    }else{
13422        options = {
13423            width: arguments[0],
13424            height: arguments[1],
13425            tileSize: arguments[2],
13426            tileOverlap: arguments[3],
13427            tilesUrl: arguments[4]
13428        };
13429    }
13430    // TMS has integer multiples of 256 for width/height and adds buffer
13431    // if necessary -> account for this!
13432    var bufferedWidth = Math.ceil(options.width / 256) * 256,
13433        bufferedHeight = Math.ceil(options.height / 256) * 256,
13434        max;
13435
13436    // Compute number of zoomlevels in this tileset
13437    if (bufferedWidth > bufferedHeight) {
13438        max = bufferedWidth / 256;
13439    } else {
13440        max = bufferedHeight / 256;
13441    }
13442    options.maxLevel = Math.ceil(Math.log(max) / Math.log(2)) - 1;
13443    options.tileSize = 256;
13444    options.width = bufferedWidth;
13445    options.height = bufferedHeight;
13446
13447    $.TileSource.apply( this, [ options ] );
13448
13449};
13450
13451$.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TmsTileSource.prototype */{
13452
13453
13454    /**
13455     * Determine if the data and/or url imply the image service is supported by
13456     * this tile source.
13457     * @function
13458     * @param {Object|Array} data
13459     * @param {String} optional - url
13460     */
13461    supports: function( data, url ){
13462        return ( data.type && "tiledmapservice" == data.type );
13463    },
13464
13465    /**
13466     *
13467     * @function
13468     * @param {Object} data - the raw configuration
13469     * @param {String} url - the url the data was retrieved from if any.
13470     * @return {Object} options - A dictionary of keyword arguments sufficient
13471     *      to configure this tile sources constructor.
13472     */
13473    configure: function( data, url ){
13474        return data;
13475    },
13476
13477
13478    /**
13479     * @function
13480     * @param {Number} level
13481     * @param {Number} x
13482     * @param {Number} y
13483     */
13484    getTileUrl: function( level, x, y ) {
13485        // Convert from Deep Zoom definition to TMS zoom definition
13486        var yTiles = this.getNumTiles( level ).y - 1;
13487
13488        return this.tilesUrl + level + "/" + x + "/" + (yTiles - y) + ".png";
13489    }
13490});
13491
13492
13493}( OpenSeadragon ));
13494
13495(function($) {
13496
13497    /**
13498     * @class ZoomifyTileSource
13499     * @classdesc A tilesource implementation for the zoomify format.
13500     *
13501     * A description of the format can be found here:
13502     * https://ecommons.cornell.edu/bitstream/handle/1813/5410/Introducing_Zoomify_Image.pdf
13503     *
13504     * There are two ways of creating a zoomify tilesource for openseadragon
13505     *
13506     * 1) Supplying all necessary information in the tilesource object. A minimal example object for this method looks like this:
13507     *
13508     * {
13509     *      type: "zoomifytileservice",
13510     *      width: 1000,
13511     *      height: 1000,
13512     *      tilesUrl: "/test/data/zoomify/"
13513     * }
13514     *
13515     * The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the path to the image _directory_.
13516     *
13517     * 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED)
13518     *
13519     * When creating zoomify formatted images one "xml" like file with name ImageProperties.xml
13520     * will be created as well. Here is an example of such a file:
13521     *
13522     * <IMAGE_PROPERTIES WIDTH="1000" HEIGHT="1000" NUMTILES="21" NUM
13522IMAGES="1" VERSION="1.8" TILESIZE="256" />
13523     *
13524     * To use this xml file as metadata source you must supply the path to the ImageProperties.xml file and leave out all other parameters:
13525     * As stated above, this method of loading a zoomify tilesource is currently not supported
13526     *
13527     * {
13528     *      type: "zoomifytileservice",
13529     *      tilesUrl: "/test/data/zoomify/ImageProperties.xml"
13530     * }
13531
13532    *
13533    * @memberof OpenSeadragon
13534     * @extends OpenSeadragon.TileSource
13535     * @param {Number} width - the pixel width of the image.
13536     * @param {Number} height
13537     * @param {Number} tileSize
13538     * @param {String} tilesUrl
13539     */
13540    $.ZoomifyTileSource = function(options) {
13541        options.tileSize = 256;
13542
13543        var currentImageSize = {
13544            x: options.width,
13545            y: options.height
13546        };
13547        options.imageSizes = [{
13548            x: options.width,
13549            y: options.height
13550        }];
13551        options.gridSize = [this._getGridSize(options.width, options.height, options.tileSize)];
13552
13553        while (parseInt(currentImageSize.x, 10) > options.tileSize || parseInt(currentImageSize.y, 10) > options.tileSize) {
13554            currentImageSize.x = Math.floor(currentImageSize.x / 2);
13555            currentImageSize.y = Math.floor(currentImageSize.y / 2);
13556            options.imageSizes.push({
13557                x: currentImageSize.x,
13558                y: currentImageSize.y
13559            });
13560            options.gridSize.push(this._getGridSize(currentImageSize.x, currentImageSize.y, options.tileSize));
13561        }
13562        options.imageSizes.reverse();
13563        options.gridSize.reverse();
13564        options.minLevel = 0;
13565        options.maxLevel = options.gridSize.length - 1;
13566
13567        OpenSeadragon.TileSource.apply(this, [options]);
13568    };
13569
13570    $.extend($.ZoomifyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ZoomifyTileSource.prototype */ {
13571
13572        //private
13573        _getGridSize: function(width, height, tileSize) {
13574            return {
13575                x: Math.ceil(width / tileSize),
13576                y: Math.ceil(height / tileSize)
13577            };
13578        },
13579
13580        //private
13581        _calculateAbsoluteTileNumber: function(level, x, y) {
13582            var num = 0;
13583            var size = {};
13584
13585            //Sum up all tiles below the level we want the number of tiles
13586            for (var z = 0; z < level; z++) {
13587                size = this.gridSize[z];
13588                num += size.x * size.y;
13589            }
13590            //Add the tiles of the level
13591            size = this.gridSize[level];
13592            num += size.x * y + x;
13593            return num;
13594        },
13595
13596        /**
13597         * Determine if the data and/or url imply the image service is supported by
13598         * this tile source.
13599         * @function
13600         * @param {Object|Array} data
13601         * @param {String} optional - url
13602         */
13603        supports: function(data, url) {
13604            return (data.type && "zoomifytileservice" == data.type);
13605        },
13606
13607        /**
13608         *
13609         * @function
13610         * @param {Object} data - the raw configuration
13611         * @param {String} url - the url the data was retrieved from if any.
13612         * @return {Object} options - A dictionary of keyword arguments sufficient
13613         *      to configure this tile sources constructor.
13614         */
13615        configure: function(data, url) {
13616            return data;
13617        },
13618
13619        /**
13620         * @function
13621         * @param {Number} level
13622         * @param {Number} x
13623         * @param {Number} y
13624         */
13625        getTileUrl: function(level, x, y) {
13626            //console.log(level);
13627            var result = 0;
13628            var num = this._calculateAbsoluteTileNumber(level, x, y);
13629            result = Math.floor(num / 256);
13630            return this.tilesUrl + 'TileGroup' + result + '/' + level + '-' + x + '-' + y + '.jpg';
13631
13632        }
13633    });
13634
13635}(OpenSeadragon));
13636
13637
13638/*
13639 * OpenSeadragon - LegacyTileSource
13640 *
13641 * Copyright (C) 2009 CodePlex Foundation
13642 * Copyright (C) 2010-2013 OpenSeadragon contributors
13643 *
13644 * Redistribution and use in source and binary forms, with or without
13645 * modification, are permitted provided that the following conditions are
13646 * met:
13647 *
13648 * - Redistributions of source code must retain the above copyright notice,
13649 *   this list of conditions and the following disclaimer.
13650 *
13651 * - Redistributions in binary form must reproduce the above copyright
13652 *   notice, this list of conditions and the following disclaimer in the
13653 *   documentation and/or other materials provided with the distribution.
13654 *
13655 * - Neither the name of CodePlex Foundation nor the names of its
13656 *   contributors may be used to endorse or promote products derived from
13657 *   this software without specific prior written permission.
13658 *
13659 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
13660 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13661 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
13662 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
13663 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13664 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
13665 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
13666 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
13667 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
13668 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
13669 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13670 */
13671
13672(function( $ ){
13673
13674/**
13675 * @class LegacyTileSource
13676 * @classdesc The LegacyTileSource allows simple, traditional image pyramids to be loaded
13677 * into an OpenSeadragon Viewer.  Basically, this translates to the historically
13678 * common practice of starting with a 'master' image, maybe a tiff for example,
13679 * and generating a set of 'service' images like one or more thumbnails, a medium
13680 * resolution image and a high resolution image in standard web formats like
13681 * png or jpg.
13682 *
13683 * @memberof OpenSeadragon
13684 * @extends OpenSeadragon.TileSource
13685 * @param {Array} levels An array of file descriptions, each is an object with
13686 *      a 'url', a 'width', and a 'height'.  Overriding classes can expect more
13687 *      properties but these properties are sufficient for this implementation.
13688 *      Additionally, the levels are required to be listed in order from
13689 *      smallest to largest.
13690 * @property {Number} aspectRatio
13691 * @property {Number} dimensions
13692 * @property {Number} tileSize
13693 * @property {Number} tileOverlap
13694 * @property {Number} minLevel
13695 * @property {Number} maxLevel
13696 * @property {Array}  levels
13697 */
13698$.LegacyTileSource = function( levels ) {
13699
13700    var options,
13701        width,
13702        height;
13703
13704    if( $.isArray( levels ) ){
13705        options = {
13706            type: 'legacy-image-pyramid',
13707            levels: levels
13708        };
13709    }
13710
13711    //clean up the levels to make sure we support all formats
13712    options.levels = filterFiles( options.levels );
13713
13714    if ( options.levels.length > 0 ) {
13715        width = options.levels[ options.levels.length - 1 ].width;
13716        height = options.levels[ options.levels.length - 1 ].height;
13717    }
13718    else {
13719        width = 0;
13720        height = 0;
13721        $.console.error( "No supported image formats found" );
13722    }
13723
13724    $.extend( true, options, {
13725        width: width,
13726        height: height,
13727        tileSize: Math.max( height, width ),
13728        tileOverlap: 0,
13729        minLevel: 0,
13730        maxLevel: options.levels.length > 0 ? options.levels.length - 1 : 0
13731    } );
13732
13733    $.TileSource.apply( this, [ options ] );
13734
13735    this.levels = options.levels;
13736};
13737
13738$.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.LegacyTileSource.prototype */{
13739    /**
13740     * Determine if the data and/or url imply the image service is supported by
13741     * this tile source.
13742     * @function
13743     * @param {Object|Array} data
13744     * @param {String} optional - url
13745     */
13746    supports: function( data, url ){
13747        return (
13748            data.type &&
13749            "legacy-image-pyramid" == data.type
13750        ) || (
13751            data.documentElement &&
13752            "legacy-image-pyramid" == data.documentElement.getAttribute('type')
13753        );
13754    },
13755
13756
13757    /**
13758     *
13759     * @function
13760     * @param {Object|XMLDocument} configuration - the raw configuration
13761     * @param {String} dataUrl - the url the data was retrieved from if any.
13762     * @return {Object} options - A dictionary of keyword arguments sufficient
13763     *      to configure this tile sources constructor.
13764     */
13765    configure: function( configuration, dataUrl ){
13766
13767        var options;
13768
13769        if( !$.isPlainObject(configuration) ){
13770
13771            options = configureFromXML( this, configuration );
13772
13773        }else{
13774
13775            options = configureFromObject( this, configuration );
13776        }
13777
13778        return options;
13779
13780    },
13781
13782    /**
13783     * @function
13784     * @param {Number} level
13785     */
13786    getLevelScale: function ( level ) {
13787        var levelScale = NaN;
13788        if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
13789            levelScale =
13790                this.levels[ level ].width /
13791                this.levels[ this.maxLevel ].width;
13792        }
13793        return levelScale;
13794    },
13795
13796    /**
13797     * @function
13798     * @param {Number} level
13799     */
13800    getNumTiles: function( level ) {
13801        var scale = this.getLevelScale( level );
13802        if ( scale ){
13803            return new $.Point( 1, 1 );
13804        } else {
13805            return new $.Point( 0, 0 );
13806        }
13807    },
13808
13809    /**
13810     * This method is not implemented by this class other than to throw an Error
13811     * announcing you have to implement it.  Because of the variety of tile
13812     * server technologies, and various specifications for building image
13813     * pyramids, this method is here to allow easy integration.
13814     * @function
13815     * @param {Number} level
13816     * @param {Number} x
13817     * @param {Number} y
13818     * @throws {Error}
13819     */
13820    getTileUrl: function ( level, x, y ) {
13821        var url = null;
13822        if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) {
13823            url = this.levels[ level ].url;
13824        }
13825        return url;
13826    }
13827} );
13828
13829/**
13830 * This method removes any files from the Array which don't conform to our
13831 * basic requirements for a 'level' in the LegacyTileSource.
13832 * @private
13833 * @inner
13834 * @function
13835 */
13836function filterFiles( files ){
13837    var filtered = [],
13838        file,
13839        i;
13840    for( i = 0; i < files.length; i++ ){
13841        file = files[ i ];
13842        if( file.height &&
13843            file.width &&
13844            file.url ){
13845            //This is sufficient to serve as a level
13846            filtered.push({
13847                url: file.url,
13848                width: Number( file.width ),
13849                height: Number( file.height )
13850            });
13851        }
13852        else {
13853            $.console.error( 'Unsupported image format: %s', file.url ? file.url : '<no URL>' );
13854        }
13855    }
13856
13857    return filtered.sort(function(a, b) {
13858        return a.height - b.height;
13859    });
13860
13861}
13862
13863/**
13864 * @private
13865 * @inner
13866 * @function
13867 */
13868function configureFromXML( tileSource, xmlDoc ){
13869
13870    if ( !xmlDoc || !xmlDoc.documentElement ) {
13871        throw new Error( $.getString( "Errors.Xml" ) );
13872    }
13873
13874    var root         = xmlDoc.documentElement,
13875        rootName     = root.tagName,
13876        conf         = null,
13877        levels       = [],
13878        level,
13879        i;
13880
13881    if ( rootName == "image" ) {
13882
13883        try {
13884            conf = {
13885                type:        root.getAttribute( "type" ),
13886                levels:      []
13887            };
13888
13889            levels = root.getElementsByTagName( "level" );
13890            for ( i = 0; i < levels.length; i++ ) {
13891                level = levels[ i ];
13892
13893                conf.levels.push({
13894                    url:    level.getAttribute( "url" ),
13895                    width:  parseInt( level.getAttribute( "width" ), 10 ),
13896                    height: parseInt( level.getAttribute( "height" ), 10 )
13897                });
13898            }
13899
13900            return configureFromObject( tileSource, conf );
13901
13902        } catch ( e ) {
13903            throw (e instanceof Error) ?
13904                e :
13905                new Error( 'Unknown error parsing Legacy Image Pyramid XML.' );
13906        }
13907    } else if ( rootName == "collection" ) {
13908        throw new Error( 'Legacy Image Pyramid Collections not yet supported.' );
13909    } else if ( rootName == "error" ) {
13910        throw new Error( 'Error: ' + xmlDoc );
13911    }
13912
13913    throw new Error( 'Unknown element ' + rootName );
13914}
13915
13916/**
13917 * @private
13918 * @inner
13919 * @function
13920 */
13921function configureFromObject( tileSource, configuration ){
13922
13923    return configuration.levels;
13924
13925}
13926
13927}( OpenSeadragon ));
13928
13929/*
13930 * OpenSeadragon - ImageTileSource
13931 *
13932 * Copyright (C) 2009 CodePlex Foundation
13933 * Copyright (C) 2010-2013 OpenSeadragon contributors
13934 *
13935 * Redistribution and use in source and binary forms, with or without
13936 * modification, are permitted provided that the following conditions are
13937 * met:
13938 *
13939 * - Redistributions of source code must retain the above copyright notice,
13940 *   this list of conditions and the following disclaimer.
13941 *
13942 * - Redistributions in binary form must reproduce the above copyright
13943 *   notice, this list of conditions and the following disclaimer in the
13944 *   documentation and/or other materials provided with the distribution.
13945 *
13946 * - Neither the name of CodePlex Foundation nor the names of its
13947 *   contributors may be used to endorse or promote products derived from
13948 *   this software without specific prior written permission.
13949 *
13950 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
13951 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13952 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
13953 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
13954 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13955 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
13956 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
13957 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
13958 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
13959 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
13960 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13961 */
13962
13963(function ($) {
13964
13965    /**
13966     * @class ImageTileSource
13967     * @classdesc The ImageTileSource allows a simple image to be loaded
13968     * into an OpenSeadragon Viewer.
13969     * There are 2 ways to open an ImageTileSource:
13970     * 1. viewer.open({type: 'image', url: fooUrl});
13971     * 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
13972     *
13973     * With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
13974     * useCanvas options are inherited from the viewer if they are not
13975     * specified directly in the options object.
13976     *
13977     * @memberof OpenSeadragon
13978     * @extends OpenSeadragon.TileSource
13979     * @param {Object} options Options object.
13980     * @param {String} options.url URL of the image
13981     * @param {Boolean} [options.buildPyramid=true] If set to true (default), a
13982     * pyramid will be built internally to provide a better downsampling.
13983     * @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are
13984     * 'Anonymous', 'use-credentials', and false. If false, image requests will
13985     * not use CORS preventing internal pyramid building for images from other
13986     * domains.
13987     * @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
13988     * the withCredentials XHR flag for AJAX requests (when loading tile sources).
13989     * @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
13990     * of the canvas API.
13991     */
13992    $.ImageTileSource = function (options) {
13993
13994        options = $.extend({
13995            buildPyramid: true,
13996            crossOriginPolicy: false,
13997            ajaxWithCredentials: false,
13998            useCanvas: true
13999        }, options);
14000        $.TileSource.apply(this, [options]);
14001
14002    };
14003
14004    $.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{
14005        /**
14006         * Determine if the data and/or url imply the image service is supported by
14007         * this tile source.
14008         * @function
14009         * @param {Object|Array} data
14010         * @param {String} optional - url
14011         */
14012        supports: function (data, url) {
14013            return data.type && data.type === "image";
14014        },
14015        /**
14016         *
14017         * @function
14018         * @param {Object} options - the options
14019         * @param {String} dataUrl - the url the image was retrieved from, if any.
14020         * @return {Object} options - A dictionary of keyword arguments sufficient
14021         *      to configure this tile sources constructor.
14022         */
14023        configure: function (options, dataUrl) {
14024            return options;
14025        },
14026        /**
14027         * Responsible for retrieving, and caching the
14028         * image metadata pertinent to this TileSources implementation.
14029         * @function
14030         * @param {String} url
14031         * @throws {Error}
14032         */
14033        getImageInfo: function (url) {
14034            var image = this._image = new Image();
14035            var _this = this;
14036
14037            if (this.crossOriginPolicy) {
14038                image.crossOrigin = this.crossOriginPolicy;
14039            }
14040            if (this.ajaxWithCredentials) {
14041                image.useCredentials = this.ajaxWithCredentials;
14042            }
14043
14044            $.addEvent(image, 'load', function () {
14045                /* IE8 fix since it has no naturalWidth and naturalHeight */
14046                _this.width = Object.prototype.hasOwnProperty.call(image, 'naturalWidth') ? image.naturalWidth : image.width;
14047                _this.height = Object.prototype.hasOwnProperty.call(image, 'naturalHeight') ? image.naturalHeight : image.height;
14048                _this.aspectRatio = _this.width / _this.height;
14049                _this.dimensions = new $.Point(_this.width, _this.height);
14050                _this._tileWidth = _this.width;
14051                _this._tileHeight = _this.height;
14052                _this.tileOverlap = 0;
14053                _this.minLevel = 0;
14054                _this.levels = _this._buildLevels();
14055                _this.maxLevel = _this.levels.length - 1;
14056
14057                _this.ready = true;
14058
14059                // Note: this event is documented elsewhere, in TileSource
14060                _this.raiseEvent('ready', {tileSource: _this});
14061            });
14062
14063            $.addEvent(image, 'error', function () {
14064                // Note: this event is documented elsewhere, in TileSource
14065                _this.raiseEvent('open-failed', {
14066                    message: "Error loading image at " + url,
14067                    source: url
14068                });
14069            });
14070
14071            image.src = url;
14072        },
14073        /**
14074         * @function
14075         * @param {Number} level
14076         */
14077        getLevelScale: function (level) {
14078            var levelScale = NaN;
14079            if (level >= this.minLevel && level <= this.maxLevel) {
14080                levelScale =
14081                        this.levels[level].width /
14082                        this.levels[this.maxLevel].width;
14083            }
14084            return levelScale;
14085        },
14086        /**
14087         * @function
14088         * @param {Number} level
14089         */
14090        getNumTiles: function (level) {
14091            var scale = this.getLevelScale(level);
14092            if (scale) {
14093                return new $.Point(1, 1);
14094            } else {
14095                return new $.Point(0, 0);
14096            }
14097        },
14098        /**
14099         * Retrieves a tile url
14100         * @function
14101         * @param {Number} level Level of the tile
14102         * @param {Number} x x coordinate of the tile
14103         * @param {Number} y y coordinate of the tile
14104         */
14105        getTileUrl: function (level, x, y) {
14106            var url = null;
14107            if (level >= this.minLevel && level <= this.maxLevel) {
14108                url = this.levels[level].url;
14109            }
14110            return url;
14111        },
14112        /**
14113         * Retrieves a tile context 2D
14114         * @function
14115         * @param {Number} level Level of the tile
14116         * @param {Number} x x coordinate of the tile
14117         * @param {Number} y y coordinate of the tile
14118         */
14119        getContext2D: function (level, x, y) {
14120            var context = null;
14121            if (level >= this.minLevel && level <= this.maxLevel) {
14122                context = this.levels[level].context2D;
14123            }
14124            return context;
14125        },
14126
14127        // private
14128        //
14129        // Builds the different levels of the pyramid if possible
14130        // (i.e. if canvas API enabled and no canvas tainting issue).
14131        _buildLevels: function () {
14132            var levels = [{
14133                    url: this._image.src,
14134                    /* IE8 fix since it has no naturalWidth and naturalHeight */
14135                    width: Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width,
14136                    height:  Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height
14137                }];
14138
14139            if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
14140                // We don't need the image anymore. Allows it to be GC.
14141                delete this._image;
14142                return levels;
14143            }
14144
14145            /* IE8 fix since it has no naturalWidth and naturalHeight */
14146            var currentWidth = Object.prototype.hasOwnProperty.call(this._image, 'naturalWidth') ? this._image.naturalWidth : this._image.width;
14147            var currentHeight = Object.prototype.hasOwnProperty.call(this._image, 'naturalHeight') ? this._image.naturalHeight : this._image.height;
14148
14149
14150            var bigCanvas = document.createElement("canvas");
14151            var bigContext = bigCanvas.getContext("2d");
14152
14153            bigCanvas.width = currentWidth;
14154            bigCanvas.height = currentHeight;
14155            bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight);
14156            // We cache the context of the highest level because the browser
14157            // is a lot faster at downsampling something it already has
14158            // downsampled before.
14159            levels[0].context2D = bigContext;
14160            // We don't need the image anymore. Allows it to be GC.
14161            delete this._image;
14162
14163            if ($.isCanvasTainted(bigCanvas)) {
14164                // If the canvas is tainted, we can't compute the pyramid.
14165                return levels;
14166            }
14167
14168            // We build smaller levels until either width or height becomes
14169            // 1 pixel wide.
14170            while (currentWidth >= 2 && currentHeight >= 2) {
14171                currentWidth = Math.floor(currentWidth / 2);
14172                currentHeight = Math.floor(currentHeight / 2);
14173                var smallCanvas = document.createElement("canvas");
14174                var smallContext = smallCanvas.getContext("2d");
14175                smallCanvas.width = currentWidth;
14176                smallCanvas.height = currentHeight;
14177                smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight);
14178
14179                levels.splice(0, 0, {
14180                    context2D: smallContext,
14181                    width: currentWidth,
14182                    height: currentHeight
14183                });
14184
14185                bigCanvas = smallCanvas;
14186                bigContext = smallContext;
14187            }
14188            return levels;
14189        }
14190    });
14191
14192}(OpenSeadragon));
14193
14194/*
14195 * OpenSeadragon - TileSourceCollection
14196 *
14197 * Copyright (C) 2009 CodePlex Foundation
14198 * Copyright (C) 2010-2013 OpenSeadragon contributors
14199 *
14200 * Redistribution and use in source and binary forms, with or without
14201 * modification, are permitted provided that the following conditions are
14202 * met:
14203 *
14204 * - Redistributions of source code must retain the above copyright notice,
14205 *   this list of conditions and the following disclaimer.
14206 *
14207 * - Redistributions in binary form must reproduce the above copyright
14208 *   notice, this list of conditions and the following disclaimer in the
14209 *   documentation and/or other materials provided with the distribution.
14210 *
14211 * - Neither the name of CodePlex Foundation nor the names of its
14212 *   contributors may be used to endorse or promote products derived from
14213 *   this software without specific prior written permission.
14214 *
14215 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14216 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14217 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14218 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
14219 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14220 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14221 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14222 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14223 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14224 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14225 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14226 */
14227
14228(function($) {
14229
14230// deprecated
14231$.TileSourceCollection = function(tileSize, tileSources, rows, layout) {
14232    $.console.error('TileSourceCollection is deprecated; use World instead');
14233};
14234
14235}(OpenSeadragon));
14236
14237/*
14238 * OpenSeadragon - Button
14239 *
14240 * Copyright (C) 2009 CodePlex Foundation
14241 * Copyright (C) 2010-2013 OpenSeadragon contributors
14242 *
14243 * Redistribution and use in source and binary forms, with or without
14244 * modification, are permitted provided that the following conditions are
14245 * met:
14246 *
14247 * - Redistributions of source code must retain the above copyright notice,
14248 *   this list of conditions and the following disclaimer.
14249 *
14250 * - Redistributions in binary form must reproduce the above copyright
14251 *   notice, this list of conditions and the following disclaimer in the
14252 *   documentation and/or other materials provided with the distribution.
14253 *
14254 * - Neither the name of CodePlex Foundation nor the names of its
14255 *   contributors may be used to endorse or promote products derived from
14256 *   this software without specific prior written permission.
14257 *
14258 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14259 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14260 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14261 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
14262 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14263 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14264 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14265 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14266 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14267 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14268 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14269 */
14270
14271(function( $ ){
14272
14273/**
14274 * An enumeration of button states
14275 * @member ButtonState
14276 * @memberof OpenSeadragon
14277 * @static
14278 * @type {Object}
14279 * @property {Number} REST
14280 * @property {Number} GROUP
14281 * @property {Number} HOVER
14282 * @property {Number} DOWN
14283 */
14284$.ButtonState = {
14285    REST:   0,
14286    GROUP:  1,
14287    HOVER:  2,
14288    DOWN:   3
14289};
14290
14291/**
14292 * @class Button
14293 * @classdesc Manages events, hover states for individual buttons, tool-tips, as well
14294 * as fading the buttons out when the user has not interacted with them
14295 * for a specified period.
14296 *
14297 * @memberof OpenSeadragon
14298 * @extends OpenSeadragon.EventSource
14299 * @param {Object} options
14300 * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML &lt;div&gt; element is created.
14301 * @param {String} [options.tooltip=null] Provides context help for the button when the
14302 *  user hovers over it.
14303 * @param {String} [options.srcRest=null] URL of image to use in 'rest' state.
14304 * @param {String} [options.srcGroup=null] URL of image to use in 'up' state.
14305 * @param {String} [options.srcHover=null] URL of image to use in 'hover' state.
14306 * @param {String} [options.srcDown=null] URL of image to use in 'down' state.
14307 * @param {Number} [options.fadeDelay=0] How long to wait before fading.
14308 * @param {Number} [options.fadeLength=2000] How long should it take to fade the button.
14309 * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}.
14310 * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}.
14311 * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}.
14312 * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}.
14313 * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}.
14314 * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}.
14315 * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}.
14316 */
14317$.Button = function( options ) {
14318
14319    var _this = this;
14320
14321    $.EventSource.call( this );
14322
14323    $.extend( true, this, {
14324
14325        tooltip:            null,
14326        srcRest:            null,
14327        srcGroup:           null,
14328        srcHover:           null,
14329        srcDown:            null,
14330        clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
14331        clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
14332        /**
14333         * How long to wait before fading.
14334         * @member {Number} fadeDelay
14335         * @memberof OpenSeadragon.Button#
14336         */
14337        fadeDelay:          0,
14338        /**
14339         * How long should it take to fade the button.
14340         * @member {Number} fadeLength
14341         * @memberof OpenSeadragon.Button#
14342         */
14343        fadeLength:         2000,
14344        onPress:            null,
14345        onRelease:          null,
14346        onClick:            null,
14347        onEnter:            null,
14348        onExit:             null,
14349        onFocus:            null,
14350        onBlur:             null
14351
14352    }, options );
14353
14354    /**
14355     * The button element.
14356     * @member {Element} element
14357     * @memberof OpenSeadragon.Button#
14358     */
14359    this.element = options.element || $.makeNeutralElement("div");
14360
14361    //if the user has specified the element to bind the control to explicitly
14362    //then do not add the default control images
14363    if ( !options.element ) {
14364        this.imgRest      = $.makeTransparentImage( this.srcRest );
14365        this.imgGroup     = $.makeTransparentImage( this.srcGroup );
14366        this.imgHover     = $.makeTransparentImage( this.srcHover );
14367        this.imgDown      = $.makeTransparentImage( this.srcDown );
14368
14369        this.imgRest.alt  =
14370        this.imgGroup.alt =
14371        this.imgHover.alt =
14372        this.imgDown.alt  =
14373            this.tooltip;
14374
14375        this.element.style.position = "relative";
14376        $.setElementTouchActionNone( this.element );
14377
14378        this.imgGroup.style.position =
14379        this.imgHover.style.position =
14380        this.imgDown.style.position  =
14381            "absolute";
14382
14383        this.imgGroup.style.top =
14384        this.imgHover.style.top =
14385        this.imgDown.style.top  =
14386            "0px";
14387
14388        this.imgGroup.style.left =
14389        this.imgHover.style.left =
14390        this.imgDown.style.left  =
14391            "0px";
14392
14393        this.imgHover.style.visibility =
14394        this.imgDown.style.visibility  =
14395            "hidden";
14396
14397        if ($.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3) {
14398            this.imgGroup.style.top =
14399            this.imgHover.style.top =
14400            this.imgDown.style.top  =
14401                "";
14402        }
14403
14404        this.element.appendChild( this.imgRest );
14405        this.element.appendChild( this.imgGroup );
14406        this.element.appendChild( this.imgHover );
14407        this.element.appendChild( this.imgDown );
14408    }
14409
14410
14411    this.addHandler("press", this.onPress);
14412    this.addHandler("release", this.onRelease);
14413    this.addHandler("click", this.onClick);
14414    this.addHandler("enter", this.onEnter);
14415    this.addHandler("exit", this.onExit);
14416    this.addHandler("focus", this.onFocus);
14417    this.addHandler("blur", this.onBlur);
14418
14419    /**
14420     * The button's current state.
14421     * @member {OpenSeadragon.ButtonState} currentState
14422     * @memberof OpenSeadragon.Button#
14423     */
14424    this.currentState = $.ButtonState.GROUP;
14425
14426    // When the button last began to fade.
14427    this.fadeBeginTime  = null;
14428    // Whether this button should fade after user stops interacting with the viewport.
14429    this.shouldFade     = false;
vendor: 16,114 bytes, lines 14430-14887
14430
14431    this.element.style.display  = "inline-block";
14432    this.element.style.position = "relative";
14433    this.element.title          = this.tooltip;
14434
14435    /**
14436     * Tracks mouse/touch/key events on the button.
14437     * @member {OpenSeadragon.MouseTracker} tracker
14438     * @memberof OpenSeadragon.Button#
14439     */
14440    this.tracker = new $.MouseTracker({
14441
14442        element:            this.element,
14443        clickTimeThreshold: this.clickTimeThreshold,
14444        clickDistThreshold: this.clickDistThreshold,
14445
14446        enterHandler: function( event ) {
14447            if ( event.insideElementPressed ) {
14448                inTo( _this, $.ButtonState.DOWN );
14449                /**
14450                 * Raised when the cursor enters the Button element.
14451                 *
14452                 * @event enter
14453                 * @memberof OpenSeadragon.Button
14454                 * @type {object}
14455                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14456                 * @property {Object} originalEvent - The original DOM event.
14457                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14458                 */
14459                _this.raiseEvent( "enter", { originalEvent: event.originalEvent } );
14460            } else if ( !event.buttonDownAny ) {
14461                inTo( _this, $.ButtonState.HOVER );
14462            }
14463        },
14464
14465        focusHandler: function ( event ) {
14466            this.enterHandler( event );
14467            /**
14468             * Raised when the Button element receives focus.
14469             *
14470             * @event focus
14471             * @memberof OpenSeadragon.Button
14472             * @type {object}
14473             * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14474             * @property {Object} originalEvent - The original DOM event.
14475             * @property {?Object} userData - Arbitrary subscriber-defined object.
14476             */
14477            _this.raiseEvent( "focus", { originalEvent: event.originalEvent } );
14478        },
14479
14480        exitHandler: function( event ) {
14481            outTo( _this, $.ButtonState.GROUP );
14482            if ( event.insideElementPressed ) {
14483                /**
14484                 * Raised when the cursor leaves the Button element.
14485                 *
14486                 * @event exit
14487                 * @memberof OpenSeadragon.Button
14488                 * @type {object}
14489                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14490                 * @property {Object} originalEvent - The original DOM event.
14491                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14492                 */
14493                _this.raiseEvent( "exit", { originalEvent: event.originalEvent } );
14494            }
14495        },
14496
14497        blurHandler: function ( event ) {
14498            this.exitHandler( event );
14499            /**
14500             * Raised when the Button element loses focus.
14501             *
14502             * @event blur
14503             * @memberof OpenSeadragon.Button
14504             * @type {object}
14505             * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14506             * @property {Object} originalEvent - The original DOM event.
14507             * @property {?Object} userData - Arbitrary subscriber-defined object.
14508             */
14509            _this.raiseEvent( "blur", { originalEvent: event.originalEvent } );
14510        },
14511
14512        pressHandler: function ( event ) {
14513            inTo( _this, $.ButtonState.DOWN );
14514            /**
14515             * Raised when a mouse button is pressed or touch occurs in the Button element.
14516             *
14517             * @event press
14518             * @memberof OpenSeadragon.Button
14519             * @type {object}
14520             * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14521             * @property {Object} originalEvent - The original DOM event.
14522             * @property {?Object} userData - Arbitrary subscriber-defined object.
14523             */
14524            _this.raiseEvent( "press", { originalEvent: event.originalEvent } );
14525        },
14526
14527        releaseHandler: function( event ) {
14528            if ( event.insideElementPressed && event.insideElementReleased ) {
14529                outTo( _this, $.ButtonState.HOVER );
14530                /**
14531                 * Raised when the mouse button is released or touch ends in the Button element.
14532                 *
14533                 * @event release
14534                 * @memberof OpenSeadragon.Button
14535                 * @type {object}
14536                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14537                 * @property {Object} originalEvent - The original DOM event.
14538                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14539                 */
14540                _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
14541            } else if ( event.insideElementPressed ) {
14542                outTo( _this, $.ButtonState.GROUP );
14543            } else {
14544                inTo( _this, $.ButtonState.HOVER );
14545            }
14546        },
14547
14548        clickHandler: function( event ) {
14549            if ( event.quick ) {
14550                /**
14551                 * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
14552                 *
14553                 * @event click
14554                 * @memberof OpenSeadragon.Button
14555                 * @type {object}
14556                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14557                 * @property {Object} originalEvent - The original DOM event.
14558                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14559                 */
14560                _this.raiseEvent("click", { originalEvent: event.originalEvent });
14561            }
14562        },
14563
14564        keyHandler: function( event ){
14565            //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode);
14566            if( 13 === event.keyCode ){
14567                /***
14568                 * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
14569                 *
14570                 * @event click
14571                 * @memberof OpenSeadragon.Button
14572                 * @type {object}
14573                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14574                 * @property {Object} originalEvent - The original DOM event.
14575                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14576                 */
14577                _this.raiseEvent( "click", { originalEvent: event.originalEvent } );
14578                /***
14579                 * Raised when the mouse button is released or touch ends in the Button element.
14580                 *
14581                 * @event release
14582                 * @memberof OpenSeadragon.Button
14583                 * @type {object}
14584                 * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
14585                 * @property {Object} originalEvent - The original DOM event.
14586                 * @property {?Object} userData - Arbitrary subscriber-defined object.
14587                 */
14588                _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
14589                return false;
14590            }
14591            return true;
14592        }
14593
14594    });
14595
14596    outTo( this, $.ButtonState.REST );
14597};
14598
14599$.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{
14600
14601    /**
14602     * TODO: Determine what this function is intended to do and if it's actually
14603     * useful as an API point.
14604     * @function
14605     */
14606    notifyGroupEnter: function() {
14607        inTo( this, $.ButtonState.GROUP );
14608    },
14609
14610    /**
14611     * TODO: Determine what this function is intended to do and if it's actually
14612     * useful as an API point.
14613     * @function
14614     */
14615    notifyGroupExit: function() {
14616        outTo( this, $.ButtonState.REST );
14617    },
14618
14619    /**
14620     * @function
14621     */
14622    disable: function(){
14623        this.notifyGroupExit();
14624        this.element.disabled = true;
14625        $.setElementOpacity( this.element, 0.2, true );
14626    },
14627
14628    /**
14629     * @function
14630     */
14631    enable: function(){
14632        this.element.disabled = false;
14633        $.setElementOpacity( this.element, 1.0, true );
14634        this.notifyGroupEnter();
14635    }
14636
14637});
14638
14639
14640function scheduleFade( button ) {
14641    $.requestAnimationFrame(function(){
14642        updateFade( button );
14643    });
14644}
14645
14646function updateFade( button ) {
14647    var currentTime,
14648        deltaTime,
14649        opacity;
14650
14651    if ( button.shouldFade ) {
14652        currentTime = $.now();
14653        deltaTime   = currentTime - button.fadeBeginTime;
14654        opacity     = 1.0 - deltaTime / button.fadeLength;
14655        opacity     = Math.min( 1.0, opacity );
14656        opacity     = Math.max( 0.0, opacity );
14657
14658        if( button.imgGroup ){
14659            $.setElementOpacity( button.imgGroup, opacity, true );
14660        }
14661        if ( opacity > 0 ) {
14662            // fade again
14663            scheduleFade( button );
14664        }
14665    }
14666}
14667
14668function beginFading( button ) {
14669    button.shouldFade = true;
14670    button.fadeBeginTime = $.now() + button.fadeDelay;
14671    window.setTimeout( function(){
14672        scheduleFade( button );
14673    }, button.fadeDelay );
14674}
14675
14676function stopFading( button ) {
14677    button.shouldFade = false;
14678    if( button.imgGroup ){
14679        $.setElementOpacity( button.imgGroup, 1.0, true );
14680    }
14681}
14682
14683function inTo( button, newState ) {
14684
14685    if( button.element.disabled ){
14686        return;
14687    }
14688
14689    if ( newState >= $.ButtonState.GROUP &&
14690         button.currentState == $.ButtonState.REST ) {
14691        stopFading( button );
14692        button.currentState = $.ButtonState.GROUP;
14693    }
14694
14695    if ( newState >= $.ButtonState.HOVER &&
14696         button.currentState == $.ButtonState.GROUP ) {
14697        if( button.imgHover ){
14698            button.imgHover.style.visibility = "";
14699        }
14700        button.currentState = $.ButtonState.HOVER;
14701    }
14702
14703    if ( newState >= $.ButtonState.DOWN &&
14704         button.currentState == $.ButtonState.HOVER ) {
14705        if( button.imgDown ){
14706            button.imgDown.style.visibility = "";
14707        }
14708        button.currentState = $.ButtonState.DOWN;
14709    }
14710}
14711
14712
14713function outTo( button, newState ) {
14714
14715    if( button.element.disabled ){
14716        return;
14717    }
14718
14719    if ( newState <= $.ButtonState.HOVER &&
14720         button.currentState == $.ButtonState.DOWN ) {
14721        if( button.imgDown ){
14722            button.imgDown.style.visibility = "hidden";
14723        }
14724        button.currentState = $.ButtonState.HOVER;
14725    }
14726
14727    if ( newState <= $.ButtonState.GROUP &&
14728         button.currentState == $.ButtonState.HOVER ) {
14729        if( button.imgHover ){
14730            button.imgHover.style.visibility = "hidden";
14731        }
14732        button.currentState = $.ButtonState.GROUP;
14733    }
14734
14735    if ( newState <= $.ButtonState.REST &&
14736         button.currentState == $.ButtonState.GROUP ) {
14737        beginFading( button );
14738        button.currentState = $.ButtonState.REST;
14739    }
14740}
14741
14742
14743
14744}( OpenSeadragon ));
14745
14746/*
14747 * OpenSeadragon - ButtonGroup
14748 *
14749 * Copyright (C) 2009 CodePlex Foundation
14750 * Copyright (C) 2010-2013 OpenSeadragon contributors
14751 *
14752 * Redistribution and use in source and binary forms, with or without
14753 * modification, are permitted provided that the following conditions are
14754 * met:
14755 *
14756 * - Redistributions of source code must retain the above copyright notice,
14757 *   this list of conditions and the following disclaimer.
14758 *
14759 * - Redistributions in binary form must reproduce the above copyright
14760 *   notice, this list of conditions and the following disclaimer in the
14761 *   documentation and/or other materials provided with the distribution.
14762 *
14763 * - Neither the name of CodePlex Foundation nor the names of its
14764 *   contributors may be used to endorse or promote products derived from
14765 *   this software without specific prior written permission.
14766 *
14767 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14768 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14769 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14770 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
14771 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14772 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14773 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14774 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14775 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14776 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14777 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14778 */
14779
14780(function( $ ){
14781/**
14782 * @class ButtonGroup
14783 * @classdesc Manages events on groups of buttons.
14784 *
14785 * @memberof OpenSeadragon
14786 * @param {Object} options - A dictionary of settings applied against the entire group of buttons.
14787 * @param {Array} options.buttons Array of buttons
14788 * @param {Element} [options.element] Element to use as the container
14789 **/
14790$.ButtonGroup = function( options ) {
14791
14792    $.extend( true, this, {
14793        /**
14794         * An array containing the buttons themselves.
14795         * @member {Array} buttons
14796         * @memberof OpenSeadragon.ButtonGroup#
14797         */
14798        buttons:            [],
14799        clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
14800        clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
14801        labelText:          ""
14802    }, options );
14803
14804    // copy the button elements  TODO: Why?
14805    var buttons = this.buttons.concat([]),
14806        _this = this,
14807        i;
14808
14809    /**
14810     * The shared container for the buttons.
14811     * @member {Element} element
14812     * @memberof OpenSeadragon.ButtonGroup#
14813     */
14814    this.element = options.element || $.makeNeutralElement( "div" );
14815
14816    // TODO What if there IS an options.group specified?
14817    if( !options.group ){
14818        this.element.style.display = "inline-block";
14819        //this.label   = $.makeNeutralElement( "label" );
14820        //TODO: support labels for ButtonGroups
14821        //this.label.innerHTML = this.labelText;
14822        //this.element.appendChild( this.label );
14823        for ( i = 0; i < buttons.length; i++ ) {
14824            this.element.appendChild( buttons[ i ].element );
14825        }
14826    }
14827
14828    $.setElementTouchActionNone( this.element );
14829
14830    /**
14831     * Tracks mouse/touch/key events across the group of buttons.
14832     * @member {OpenSeadragon.MouseTracker} tracker
14833     * @memberof OpenSeadragon.ButtonGroup#
14834     */
14835    this.tracker = new $.MouseTracker({
14836        element:            this.element,
14837        clickTimeThreshold: this.clickTimeThreshold,
14838        clickDistThreshold: this.clickDistThreshold,
14839        enterHandler: function ( event ) {
14840            var i;
14841            for ( i = 0; i < _this.buttons.length; i++ ) {
14842                _this.buttons[ i ].notifyGroupEnter();
14843            }
14844        },
14845        exitHandler: function ( event ) {
14846            var i;
14847            if ( !event.insideElementPressed ) {
14848                for ( i = 0; i < _this.buttons.length; i++ ) {
14849                    _this.buttons[ i ].notifyGroupExit();
14850                }
14851            }
14852        },
14853    });
14854};
14855
14856/** @lends OpenSeadragon.ButtonGroup.prototype */
14857$.ButtonGroup.prototype = {
14858
14859    /**
14860     * TODO: Figure out why this is used on the public API and if a more useful
14861     * api can be created.
14862     * @function
14863     * @private
14864     */
14865    emulateEnter: function() {
14866        this.tracker.enterHandler( { eventSource: this.tracker } );
14867    },
14868
14869    /**
14870     * TODO: Figure out why this is used on the public API and if a more useful
14871     * api can be created.
14872     * @function
14873     * @private
14874     */
14875    emulateExit: function() {
14876        this.tracker.exitHandler( { eventSource: this.tracker } );
14877    }
14878};
14879
14880
14881}( OpenSeadragon ));
14882
14883/*
14884 * OpenSeadragon - Rect
14885 *
14886 * Copyright (C) 2009 CodePlex Foundation
14887 * Copyright (C) 2010-2013
vendor: 3,365 bytes, lines 14887-14972
14887 OpenSeadragon contributors
14888 *
14889 * Redistribution and use in source and binary forms, with or without
14890 * modification, are permitted provided that the following conditions are
14891 * met:
14892 *
14893 * - Redistributions of source code must retain the above copyright notice,
14894 *   this list of conditions and the following disclaimer.
14895 *
14896 * - Redistributions in binary form must reproduce the above copyright
14897 *   notice, this list of conditions and the following disclaimer in the
14898 *   documentation and/or other materials provided with the distribution.
14899 *
14900 * - Neither the name of CodePlex Foundation nor the names of its
14901 *   contributors may be used to endorse or promote products derived from
14902 *   this software without specific prior written permission.
14903 *
14904 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14905 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14906 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14907 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
14908 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14909 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14910 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14911 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14912 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14913 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14914 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14915 */
14916
14917(function($) {
14918
14919/**
14920 * @class Rect
14921 * @classdesc A Rectangle is described by it top left coordinates (x, y), width,
14922 * height and degrees of rotation around (x, y).
14923 * Note that the coordinate system used is the one commonly used with images:
14924 * x increases when going to the right
14925 * y increases when going to the bottom
14926 * degrees increases clockwise with 0 being the horizontal
14927 *
14928 * The constructor normalizes the rectangle to always have 0 <= degrees < 90
14929 *
14930 * @memberof OpenSeadragon
14931 * @param {Number} [x=0] The vector component 'x'.
14932 * @param {Number} [y=0] The vector component 'y'.
14933 * @param {Number} [width=0] The vector component 'width'.
14934 * @param {Number} [height=0] The vector component 'height'.
14935 * @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees.
14936 */
14937$.Rect = function(x, y, width, height, degrees) {
14938    /**
14939     * The vector component 'x'.
14940     * @member {Number} x
14941     * @memberof OpenSeadragon.Rect#
14942     */
14943    this.x = typeof (x) === "number" ? x : 0;
14944    /**
14945     * The vector component 'y'.
14946     * @member {Number} y
14947     * @memberof OpenSeadragon.Rect#
14948     */
14949    this.y = typeof (y) === "number" ? y : 0;
14950    /**
14951     * The vector component 'width'.
14952     * @member {Number} width
14953     * @memberof OpenSeadragon.Rect#
14954     */
14955    this.width  = typeof (width) === "number" ? width : 0;
14956    /**
14957     * The vector component 'height'.
14958     * @member {Number} height
14959     * @memberof OpenSeadragon.Rect#
14960     */
14961    this.height = typeof (height) === "number" ? height : 0;
14962
14963    /**
14964     * The rotation of the rectangle, in degrees.
14965     * @member {Number} degrees
14966     * @memberof OpenSeadragon.Rect#
14967     */
14968    this.degrees = typeof (degrees) === "number" ? degrees : 0;
14969
14970    // Normalizes the rectangle.
14971    this.degrees = $.positiveModulo(this.degrees, 360);
14972    
14972var newTopLeft, newWidth;
14973    if (this.degrees >= 270) {
14974        newTopLeft = this.getTopRight();
14975        this.x = newTopLeft.x;
14976        this.y = newTopLeft.y;
14977        newWidth = this.height;
14978        this.height = this.width;
14979        this.width = newWidth;
14980        this.degrees -= 270;
14981    } else if (this.degrees >= 180) {
14982        newTopLeft = this.getBottomRight();
14983        this.x = newTopLeft.x;
14984        this.y = newTopLeft.y;
14985        this.degrees -= 180;
14986    } else if (this.degrees >= 90) {
14987        newTopLeft = this.getBottomLeft();
14988        this.x = newTopLeft.x;
14989        this.y = newTopLeft.y;
14990        newWidth = this.height;
14991        this.height = this.width;
14992        this.width = newWidth;
14993        this.degrees -= 90;
14994    }
14995};
14996
14997/**
14998 * Builds a rectangle having the 3 specified points as summits.
14999 * @static
15000 * @memberof OpenSeadragon.Rect
15001 * @param {OpenSeadragon.Point} topLeft
15002 * @param {OpenSeadragon.Point} topRight
15003 * @param {OpenSeadragon.Point} bottomLeft
15004 * @returns {OpenSeadragon.Rect}
15005 */
vendor: 10,271 bytes, lines 15006-15313
15006$.Rect.fromSummits = function(topLeft, topRight, bottomLeft) {
15007    var width = topLeft.distanceTo(topRight);
15008    var height = topLeft.distanceTo(bottomLeft);
15009    var diff = topRight.minus(topLeft);
15010    var radians = Math.atan(diff.y / diff.x);
15011    if (diff.x < 0) {
15012        radians += Math.PI;
15013    } else if (diff.y < 0) {
15014        radians += 2 * Math.PI;
15015    }
15016    return new $.Rect(
15017        topLeft.x,
15018        topLeft.y,
15019        width,
15020        height,
15021        radians / Math.PI * 180);
15022};
15023
15024/** @lends OpenSeadragon.Rect.prototype */
15025$.Rect.prototype = {
15026    /**
15027     * @function
15028     * @returns {OpenSeadragon.Rect} a duplicate of this Rect
15029     */
15030    clone: function() {
15031        return new $.Rect(
15032            this.x,
15033            this.y,
15034            this.width,
15035            this.height,
15036            this.degrees);
15037    },
15038
15039    /**
15040     * The aspect ratio is simply the ratio of width to height.
15041     * @function
15042     * @returns {Number} The ratio of width to height.
15043     */
15044    getAspectRatio: function() {
15045        return this.width / this.height;
15046    },
15047
15048    /**
15049     * Provides the coordinates of the upper-left corner of the rectangle as a
15050     * point.
15051     * @function
15052     * @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of
15053     *  the rectangle.
15054     */
15055    getTopLeft: function() {
15056        return new $.Point(
15057            this.x,
15058            this.y
15059        );
15060    },
15061
15062    /**
15063     * Provides the coordinates of the bottom-right corner of the rectangle as a
15064     * point.
15065     * @function
15066     * @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of
15067     *  the rectangle.
15068     */
15069    getBottomRight: function() {
15070        return new $.Point(this.x + this.width, this.y + this.height)
15071            .rotate(this.degrees, this.getTopLeft());
15072    },
15073
15074    /**
15075     * Provides the coordinates of the top-right corner of the rectangle as a
15076     * point.
15077     * @function
15078     * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of
15079     *  the rectangle.
15080     */
15081    getTopRight: function() {
15082        return new $.Point(this.x + this.width, this.y)
15083            .rotate(this.degrees, this.getTopLeft());
15084    },
15085
15086    /**
15087     * Provides the coordinates of the bottom-left corner of the rectangle as a
15088     * point.
15089     * @function
15090     * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of
15091     *  the rectangle.
15092     */
15093    getBottomLeft: function() {
15094        return new $.Point(this.x, this.y + this.height)
15095            .rotate(this.degrees, this.getTopLeft());
15096    },
15097
15098    /**
15099     * Computes the center of the rectangle.
15100     * @function
15101     * @returns {OpenSeadragon.Point} The center of the rectangle as represented
15102     *  as represented by a 2-dimensional vector (x,y)
15103     */
15104    getCenter: function() {
15105        return new $.Point(
15106            this.x + this.width / 2.0,
15107            this.y + this.height / 2.0
15108        ).rotate(this.degrees, this.getTopLeft());
15109    },
15110
15111    /**
15112     * Returns the width and height component as a vector OpenSeadragon.Point
15113     * @function
15114     * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the
15115     *  the width and height of the rectangle.
15116     */
15117    getSize: function() {
15118        return new $.Point(this.width, this.height);
15119    },
15120
15121    /**
15122     * Determines if two Rectangles have equivalent components.
15123     * @function
15124     * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
15125     * @return {Boolean} 'true' if all components are equal, otherwise 'false'.
15126     */
15127    equals: function(other) {
15128        return (other instanceof $.Rect) &&
15129            this.x === other.x &&
15130            this.y === other.y &&
15131            this.width === other.width &&
15132            this.height === other.height &&
15133            this.degrees === other.degrees;
15134    },
15135
15136    /**
15137    * Multiply all dimensions (except degrees) in this Rect by a factor and
15138    * return a new Rect.
15139    * @function
15140    * @param {Number} factor The factor to multiply vector components.
15141    * @returns {OpenSeadragon.Rect} A new rect representing the multiplication
15142    *  of the vector components by the factor
15143    */
15144    times: function(factor) {
15145        return new $.Rect(
15146            this.x * factor,
15147            this.y * factor,
15148            this.width * factor,
15149            this.height * factor,
15150            this.degrees);
15151    },
15152
15153    /**
15154    * Translate/move this Rect by a vector and return new Rect.
15155    * @function
15156    * @param {OpenSeadragon.Point} delta The translation vector.
15157    * @returns {OpenSeadragon.Rect} A new rect with altered position
15158    */
15159    translate: function(delta) {
15160        return new $.Rect(
15161            this.x + delta.x,
15162            this.y + delta.y,
15163            this.width,
15164            this.height,
15165            this.degrees);
15166    },
15167
15168    /**
15169     * Returns the smallest rectangle that will contain this and the given
15170     * rectangle bounding boxes.
15171     * @param {OpenSeadragon.Rect} rect
15172     * @return {OpenSeadragon.Rect} The new rectangle.
15173     */
15174    union: function(rect) {
15175        var thisBoundingBox = this.getBoundingBox();
15176        var otherBoundingBox = rect.getBoundingBox();
15177
15178        var left = Math.min(thisBoundingBox.x, otherBoundingBox.x);
15179        var top = Math.min(thisBoundingBox.y, otherBoundingBox.y);
15180        var right = Math.max(
15181            thisBoundingBox.x + thisBoundingBox.width,
15182            otherBoundingBox.x + otherBoundingBox.width);
15183        var bottom = Math.max(
15184            thisBoundingBox.y + thisBoundingBox.height,
15185            otherBoundingBox.y + otherBoundingBox.height);
15186
15187        return new $.Rect(
15188            left,
15189            top,
15190            right - left,
15191            bottom - top);
15192    },
15193
15194    /**
15195     * Returns the bounding box of the intersection of this rectangle with the
15196     * given rectangle.
15197     * @param {OpenSeadragon.Rect} rect
15198     * @return {OpenSeadragon.Rect} the bounding box of the intersection
15199     * or null if the rectangles don't intersect.
15200     */
15201    intersection: function(rect) {
15202        // Simplified version of Weiler Atherton clipping algorithm
15203        // https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm
15204        // Because we just want the bounding box of the intersection,
15205        // we can just compute the bounding box of:
15206        // 1. all the summits of this which are inside rect
15207        // 2. all the summits of rect which are inside this
15208        // 3. all the intersections of rect and this
15209        var EPSILON = 0.0000000001;
15210
15211        var intersectionPoints = [];
15212
15213        var thisTopLeft = this.getTopLeft();
15214        if (rect.containsPoint(thisTopLeft, EPSILON)) {
15215            intersectionPoints.push(thisTopLeft);
15216        }
15217        var thisTopRight = this.getTopRight();
15218        if (rect.containsPoint(thisTopRight, EPSILON)) {
15219            intersectionPoints.push(thisTopRight);
15220        }
15221        var thisBottomLeft = this.getBottomLeft();
15222        if (rect.containsPoint(thisBottomLeft, EPSILON)) {
15223            intersectionPoints.push(thisBottomLeft);
15224        }
15225        var thisBottomRight = this.getBottomRight();
15226        if (rect.containsPoint(thisBottomRight, EPSILON)) {
15227            intersectionPoints.push(thisBottomRight);
15228        }
15229
15230        var rectTopLeft = rect.getTopLeft();
15231        if (this.containsPoint(rectTopLeft, EPSILON)) {
15232            intersectionPoints.push(rectTopLeft);
15233        }
15234        var rectTopRight = rect.getTopRight();
15235        if (this.containsPoint(rectTopRight, EPSILON)) {
15236            intersectionPoints.push(rectTopRight);
15237        }
15238        var rectBottomLeft = rect.getBottomLeft();
15239        if (this.containsPoint(rectBottomLeft, EPSILON)) {
15240            intersectionPoints.push(rectBottomLeft);
15241        }
15242        var rectBottomRight = rect.getBottomRight();
15243        if (this.containsPoint(rectBottomRight, EPSILON)) {
15244            intersectionPoints.push(rectBottomRight);
15245        }
15246
15247        var thisSegments = this._getSegments();
15248        var rectSegments = rect._getSegments();
15249        for (var i = 0; i < thisSegments.length; i++) {
15250            var thisSegment = thisSegments[i];
15251            for (var j = 0; j < rectSegments.length; j++) {
15252                var rectSegment = rectSegments[j];
15253                var intersect = getIntersection(thisSegment[0], thisSegment[1],
15254                    rectSegment[0], rectSegment[1]);
15255                if (intersect) {
15256                    intersectionPoints.push(intersect);
15257                }
15258            }
15259        }
15260
15261        // Get intersection point of segments [a,b] and [c,d]
15262        function getIntersection(a, b, c, d) {
15263            // http://stackoverflow.com/a/1968345/1440403
15264            var abVector = b.minus(a);
15265            var cdVector = d.minus(c);
15266
15267            var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y;
15268            if (denom === 0) {
15269                return null;
15270            }
15271
15272            var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom;
15273            var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom;
15274
15275            if (-EPSILON <= s && s <= 1 - EPSILON &&
15276                -EPSILON <= t && t <= 1 - EPSILON) {
15277                return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y);
15278            }
15279            return null;
15280        }
15281
15282        if (intersectionPoints.length === 0) {
15283            return null;
15284        }
15285
15286        var minX = intersectionPoints[0].x;
15287        var maxX = intersectionPoints[0].x;
15288        var minY = intersectionPoints[0].y;
15289        var maxY = intersectionPoints[0].y;
15290        for (var k = 1; k < intersectionPoints.length; k++) {
15291            var point = intersectionPoints[k];
15292            if (point.x < minX) {
15293                minX = point.x;
15294            }
15295            if (point.x > maxX) {
15296                maxX = point.x;
15297            }
15298            if (point.y < minY) {
15299                minY = point.y;
15300            }
15301            if (point.y > maxY) {
15302                maxY = point.y;
15303            }
15304        }
15305        return new $.Rect(minX, minY, maxX - minX, maxY - minY);
15306    },
15307
15308    // private
15309    _getSegments: function() {
15310        var topLeft = this.getTopLeft();
15311        var topRight = this.getTopRight();
15312        var bottomLeft = this.getBottomLeft();
15313        var bottomRight = this.getBottomRight();
15314        return [[topLeft, topRight],
15315            [topRight, bottomRight],
15316            [bottomRight, bottomLeft],
15317            [bottomLeft, topLeft]];
15318    },
15319
15320    /**
15321     * Rotates a rectangle around a point.
15322     * @function
15323     * @param {Number} degrees The angle in degrees to rotate.
15324     * @param {OpenSeadragon.Point} [pivot] The point about which to rotate.
15325     * Defaults to the center of the rectangle.
15326     * @return {OpenSeadragon.Rect}
15327     */
15328    rotate: function(degrees, pivot) {
15329        degrees = $.positiveModulo(degrees, 360);
15330        if (degrees === 0) {
15331            return this.clone();
15332        }
15333
15334        pivot = pivot || this.getCenter();
15335        var newTopLeft = this.getTopLeft().rotate(degrees, pivot);
15336        var newTopRight = this.getTopRight().rotate(degrees, pivot);
15337
15338        var diff = newTopRight.minus(newTopLeft);
15339        // Handle floating point error
15340        diff = diff.apply(function(x) {
15341            var EPSILON = 1e-15;
15342            return Math.abs(x) < EPSILON ? 0 : x;
15343        });
15344        var radians = Math.atan(diff.y / diff.x);
15345        if (diff.x < 0) {
15346            radians += Math.PI;
15347        } else if (diff.y < 0) {
15348            radians += 2 * Math.PI;
15349        }
15350        return new $.Rect(
15351            newTopLeft.x,
15352            newTopLeft.y,
15353            this.width,
15354            this.height,
15355            radians / Math.PI * 180);
15356    },
15357
15358    /**
15359     * Retrieves the smallest horizontal (degrees=0) rectangle which contains
15360     * this rectangle.
15361     * @returns {OpenSeadragon.Rect}
15362     */
15363    getBoundingBox: function() {
15364        if (this.degrees === 0) {
15365            return this.clone();
15366        }
15367        var topLeft = this.getTopLeft();
15368        var topRight = this.getTopRight();
15369        var bottomLeft = this.getBottomLeft();
15370        var bottomRight = this.getBottomRight();
15371        var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
15372        var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
15373        var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
15374        var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
15375        return new $.Rect(
15376            minX,
15377            minY,
15378            maxX - minX,
15379            maxY - minY);
15380    },
15381
15382    /**
15383     * Retrieves the smallest horizontal (degrees=0) rectangle which contains
15384     * this rectangle and has integers x, y, width and height
15385     * @returns {OpenSeadragon.Rect}
15386     */
15387    getIntegerBoundingBox: function() {
15388        var boundingBox = this.getBoundingBox();
15389        var x = Math.floor(boundingBox.x);
15390        var y = Math.floor(boundingBox.y);
15391        var width = Math.ceil(boundingBox.width + boundingBox.x - x);
15392        var height = Math.ceil(boundingBox.height + boundingBox.y - y);
15393        return new $.Rect(x, y, width, height);
15394    },
15395
15396    /**
15397     * Determines whether a point is inside this rectangle (edge included).
15398     * @function
15399     * @param {OpenSeadragon.Point} point
15400     * @param {Number} [epsilon=0] the margin of error allowed
15401     * @returns {Boolean} true if the point is inside this rectangle, false
15402     * otherwise.
15403     */
15404    containsPoint: function(point, epsilon) {
15405        epsilon = epsilon || 0;
15406
15407        // See http://stackoverflow.com/a/2752754/1440403 for explanation
15408        var topLeft = this.getTopLeft();
15409        var topRight = this.getTopRight();
15410        var bottomLeft = this.getBottomLeft();
15411        var topDiff = topRight.minus(topLeft);
15412        var leftDiff = bottomLeft.minus(topLeft);
15413
15414        return ((point.x - topLeft.x) * topDiff.x +
15415            (point.y - topLeft.y) * topDiff.y >= -epsilon) &&
15416
15417            ((point.x - topRight.x) * topDiff.x +
15418            (point.y - topRight.y) * topDiff.y <= epsilon) &&
15419
15420            ((point.x - topLeft.x) * leftDiff.x +
15421            (point.y - topLeft.y) * leftDiff.y >= -epsilon) &&
15422
15423            ((point.x - bottomLeft.x) * leftDiff.x +
15424            (point.y - bottomLeft.y) * leftDiff.y <= epsilon);
15425    },
15426
15427    /**
15428     * Provides a string representation of the rectangle which is useful for
15429     * debugging.
15430     * @function
15431     * @returns {String} A string representation of the rectangle.
15432     */
15433    toString: function() {
15434        return "[" +
15435            (Math.round(this.x * 100) / 100) + ", " +
15436            (Math.round(this.y * 100) / 100) + ", " +
15437            (Math.round(this.width * 100) / 100) + "x" +
15438            (Math.round(this.height * 100) / 100) + ", " +
15439            (Math.round(this.degrees * 100) / 100) + "deg" +
15440            "]";
15441    }
15442};
15443
15444
15445}(OpenSeadragon));
15446
15447/*
15448 * OpenSeadragon - ReferenceStrip
15449 *
15450 * Copyright (C) 2009 CodePlex Foundation
15451 * Copyright (C) 2010-2013 OpenSeadragon contributors
15452 *
15453 * Redistribution and use in source and binary forms, with or without
15454 * modification, are permitted provided that the following conditions are
15455 * met:
15456 *
15457 * - Redistributions of source code must retain the above copyright notice,
15458 *   this list of conditions and the following disclaimer.
15459 *
15460 * - Redistributions in binary form must reproduce the above copyright
15461 *   notice, this list of conditions and the following disclaimer in the
15462 *   documentation and/or other materials provided with the distribution.
15463 *
15464 * - Neither the name of CodePlex Foundation nor the names of its
15465 *   contributors may be used to endorse or promote products derived from
15466 *   this software without specific prior written permission.
15467 *
15468 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15469 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15470 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
15471 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
15472 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
15473 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15474 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
15475 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
15476 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
15477 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
15478 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15479 */
15480
15481(function ( $ ) {
15482
15483// dictionary from id to private properties
15484var THIS = {};
15485
15486/**
15487 *  The CollectionDrawer is a reimplementation if the Drawer API that
15488 *  focuses on allowing a viewport to be redefined as a collection
15489 *  of smaller viewports, defined by a clear number of rows and / or
15490 *  columns of which each item in the matrix of viewports has its own
15491 *  source.
15492 *
15493 *  This idea is a reexpression of the idea of dzi collections
15494 *  which allows a clearer algorithm to reuse the tile sources already
15495 *  supported by OpenSeadragon, in heterogenious or homogenious
15496 *  sequences just like mixed groups already supported by the viewer
15497 *  for the purpose of image sequnces.
15498 *
15499 *  TODO:   The difficult part of this feature is figuring out how to express
15500 *          this functionality as a combination of the functionality already
15501 *          provided by Drawer, Viewport, TileSource, and Navigator.  It may
15502 *          require better abstraction at those points in order to efficiently
15503 *          reuse those paradigms.
15504 */
15505/**
15506 * @class ReferenceStrip
15507 * @memberof OpenSeadragon
15508 * @param {Object} options
15509 */
15510$.ReferenceStrip = function ( options ) {
15511
15512    var _this       = this,
15513        viewer      = options.viewer,
15514        viewerSize  = $.getElementSize( viewer.element ),
15515        element,
15516        style,
15517        i;
15518
15519    //We may need to create a new element and id if they did not
15520    //provide the id for the existing element
15521    if ( !options.id ) {
15522        options.id              = 'referencestrip-' + $.now();
15523        this.element            = $.makeNeutralElement( "div" );
15524        this.element.id         = options.id;
15525        this.element.className  = 'referencestrip';
15526    }
15527
15528    options = $.extend( true, {
15529        sizeRatio:  $.DEFAULT_SETTINGS.referenceStripSizeRatio,
15530        position:   $.DEFAULT_SETTINGS.referenceStripPosition,
15531        scroll:     $.DEFAULT_SETTINGS.referenceStripScroll,
15532        clickTimeThreshold:  $.DEFAULT_SETTINGS.clickTimeThreshold
15533    }, options, {
15534        //required overrides
15535        element:                this.element,
15536        //These need to be overridden to prevent recursion since
15537        //the navigator is a viewer and a viewer has a navigator
15538        showNavigator:          false,
15539        mouseNavEnabled:        false,
15540        showNavigationControl:  false,
15541        showSequenceControl:    false
15542    } );
15543
15544    $.extend( this, options );
15545    //Private state properties
15546    THIS[this.id] = {
15547        "animating":           false
15548    };
15549
15550    this.minPixelRatio = this.viewer.minPixelRatio;
15551
15552    style = this.element.style;
15553    style.marginTop     = '0px';
15554    style.marginRight   = '0px';
15555    style.marginBottom  = '0px';
15556    style.marginLeft    = '0px';
15557    style.left          = '0px';
15558    style.bottom        = '0px';
15559    style.border        = '0px';
15560    style.background    = '#000';
15561    style.position      = 'relative';
15562
15563    $.setElementTouchActionNone( this.element );
15564
15565    $.setElementOpacity( this.element, 0.8 );
15566
15567    this.viewer = viewer;
15568    this.innerTracker = new $.MouseTracker( {
15569        element:        this.element,
15570        dragHandler:    $.delegate( this, onStripDrag ),
15571        scrollHandler:  $.delegate( this, onStripScroll ),
15572        enterHandler:   $.delegate( this, onStripEnter ),
15573        exitHandler:    $.delegate( this, onStripExit ),
15574        keyDownHandler: $.delegate( this, onKeyDown ),
15575        keyHandler:     $.delegate( this, onKeyPress )
15576    } );
15577
15578    //Controls the position and orientation of the reference strip and sets the
15579    //appropriate width and height
15580    if ( options.width && options.height ) {
15581        this.element.style.width  = options.width + 'px';
15582        this.element.style.height = options.height + 'px';
15583        viewer.addControl(
15584            this.element,
15585            { anchor: $.ControlAnchor.BOTTOM_LEFT }
15586        );
15587    } else {
15588        if ( "horizontal" == options.scroll ) {
15589            this.element.style.width = (
15590                viewerSize.x *
15591                options.sizeRatio *
15592                viewer.tileSources.length
15593            ) + ( 12 * viewer.tileSources.length ) + 'px';
15594
15595            this.element.style.height = (
15596                viewerSize.y *
15597                options.sizeRatio
15598            ) + 'px';
15599
15600            viewer.addControl(
15601                this.element,
15602                { anchor: $.ControlAnchor.BOTTOM_LEFT }
15603            );
15604        } else {
15605            this.element.style.height = (
15606                viewerSize.y *
15607                options.sizeRatio *
15608                viewer.tileSources.length
15609            ) + ( 12 * viewer.tileSources.length ) + 'px';
15610
15611            this.element.style.width = (
15612                viewerSize.x *
15613                options.sizeRatio
15614            ) + 'px';
15615
15616            viewer.addControl(
15617                this.element,
15618                { anchor: $.ControlAnchor.TOP_LEFT }
15619            );
15620
15621        }
15622    }
15623
15624    this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
15625    this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
15626    this.panels = [];
15627    this.miniViewers = {};
15628
15629    /*jshint loopfunc:true*/
15630    for ( i = 0; i < viewer.tileSources.length; i++ ) {
15631
15632        element = $.makeNeutralElement( 'div' );
15633        element.id = this.element.id + "-" + i;
15634
15635        element.style.width         = _this.panelWidth + 'px';
15636        element.style.height        = _this.panelHeight + 'px';
15637        element.style.display       = 'inline';
15638        element.style.float         = 'left'; //Webkit
15639        element.style.cssFloat      = 'left'; //Firefox
15640        element.style.styleFloat    = 'left'; //IE
15641        element.style.padding       = '2px';
15642        $.setElementTouchActionNone( element );
15643
15644        element.innerTracker = new $.MouseTracker( {
15645            element:            element,
15646            clickTimeThreshold: this.clickTimeThreshold,
15647            clickDistThreshold: this.clickDistThreshold,
15648            pressHandler: function ( event ) {
15649                event.eventSource.dragging = $.now();
15650            },
15651            releaseHandler: function ( event ) {
15652                var tracker = event.eventSource,
15653                    id      = tracker.element.id,
15654                    page    = Number( id.split( '-' )[2] ),
15655                    now     = $.now();
15656
15657                if ( event.insideElementPressed &&
15658                     event.insideElementReleased &&
15659                     tracker.dragging &&
15660                     ( now - tracker.dragging ) < tracker.clickTimeThreshold ) {
15661                    tracker.dragging = null;
15662                    viewer.goToPage( page );
15663                }
15664            }
15665        } );
15666
15667        this.element.appendChild( element );
15668
15669        element.activePanel = false;
15670
15671        this.panels.push( element );
15672
15673    }
15674    loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.x, 0 );
15675    this.setFocus( 0 );
15676
15677};
15678
15679$.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.prototype */{
15680
15681    /**
15682     * @function
15683     */
15684    setFocus: function ( page ) {
15685        var element      = this.element.querySelector('#' + this.element.id + '-' + page ),
15686            viewerSize   = $.getElementSize( this.viewer.canvas ),
15687            scrollWidth  = Number( this.element.style.width.replace( 'px', '' ) ),
15688            scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
15689            offsetLeft   = -Number( this.element.style.marginLeft.replace( 'px', '' ) ),
15690            offsetTop    = -Number( this.element.style.marginTop.replace( 'px', '' ) ),
15691            offset;
15692
15693        if ( this.currentSelected !== element ) {
15694            if ( this.currentSelected ) {
15695                this.currentSelected.style.background = '#000';
15696            }
15697            this.currentSelected = element;
15698            this.currentSelected.style.background = '#999';
15699
15700            if ( 'horizontal' == this.scroll ) {
15701                //right left
15702                offset = ( Number( page ) ) * ( this.panelWidth + 3 );
15703                if ( offset > offsetLeft + viewerSize.x - this.panelWidth ) {
15704                    offset = Math.min( offset, ( scrollWidth - viewerSize.x ) );
15705                    this.element.style.marginLeft = -offset + 'px';
15706                    loadPanels( this, viewerSize.x, -offset );
15707                } else if ( offset < offsetLeft ) {
15708                    offset = Math.max( 0, offset - viewerSize.x / 2 );
15709                    this.element.style.marginLeft = -offset + 'px';
15710                    loadPanels( this, viewerSize.x, -offset );
15711                }
15712            } else {
15713                offset = ( Number( page ) ) * ( this.panelHeight + 3 );
15714                if ( offset > offsetTop + viewerSize.y - this.panelHeight ) {
15715                    offset = Math.min( offset, ( scrollHeight - viewerSize.y ) );
15716                    this.element.style.marginTop = -offset + 'px';
15717                    loadPanels( this, viewerSize.y, -offset );
15718                } else if ( offset < offsetTop ) {
15719                    offset = Math.max( 0, offset - viewerSize.y / 2 );
15720                    this.element.style.marginTop = -offset + 'px';
15721                    loadPanels( this, viewerSize.y, -offset );
15722                }
15723            }
15724
15725            this.currentPage = page;
15726            onStripEnter.call( this, { eventSource: this.innerTracker } );
15727        }
15728    },
15729
15730    /**
15731     * @function
15732     */
15733    update: function () {
15734        if ( THIS[this.id].animating ) {
15735            $.console.log( 'image reference strip update' );
15736            return true;
15737        }
15738        return false;
15739    },
15740
15741    // Overrides Viewer.destroy
15742    destroy: function() {
15743        if (this.miniViewers) {
15744          for (var key in this.miniViewers) {
15745            this.miniViewers[key].destroy();
15746          }
15747        }
15748
15749        if (this.element) {
15750            this.element.parentNode.removeChild(this.element);
15751        }
15752    }
15753
15754} );
15755
15756
15757
15758
15759/**
15760 * @private
15761 * @inner
15762 * @function
15763 */
15764function onStripDrag( event ) {
15765
15766    var offsetLeft   = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
15767        offsetTop    = Number( this.element.style.marginTop.replace( 'px', '' ) ),
15768        scrollWidth  = Number( this.element.style.width.replace( 'px', '' ) ),
15769        scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
15770        viewerSize   = $.getElementSize( this.viewer.canvas );
15771    this.dragging = true;
15772    if ( this.element ) {
15773        if ( 'horizontal' == this.scroll ) {
15774            if ( -event.delta.x > 0 ) {
15775                //forward
15776                if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
15777                    this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
15778                    loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
15779                }
15780            } else if ( -event.delta.x < 0 ) {
15781                //reverse
15782                if ( offsetLeft < 0 ) {
15783                    this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
15784                    loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
15785                }
15786            }
15787        } else {
15788            if ( -event.delta.y > 0 ) {
15789                //forward
15790                if ( offsetTop > -( scrollHeight - viewerSize.y ) ) {
15791                    this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
15792                    loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
15793                }
15794            } else if ( -event.delta.y < 0 ) {
15795                //reverse
15796                if ( offsetTop < 0 ) {
15797                    this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
15798                    loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
15799                }
15800            }
15801        }
15802    }
15803    return false;
15804
15805}
15806
15807
15808
15809/**
15810 * @private
15811 * @inner
15812 * @function
15813 */
15814function onStripScroll( event ) {
15815    var offsetLeft   = Number( this.element.style.marginLeft.replace( 'px', '' ) ),
15816        offsetTop    = Number( this.element.style.marginTop.replace( 'px', '' ) ),
15817        scrollWidth  = Number( this.element.style.width.replace( 'px', '' ) ),
15818        scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
vendor: 3,308 bytes, lines 15819-15898
15819        viewerSize   = $.getElementSize( this.viewer.canvas );
15820    if ( this.element ) {
15821        if ( 'horizontal' == this.scroll ) {
15822            if ( event.scroll > 0 ) {
15823                //forward
15824                if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
15825                    this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
15826                    loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
15827                }
15828            } else if ( event.scroll < 0 ) {
15829                //reverse
15830                if ( offsetLeft < 0 ) {
15831                    this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
15832                    loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
15833                }
15834            }
15835        } else {
15836            if ( event.scroll < 0 ) {
15837                //scroll up
15838                if ( offsetTop > viewerSize.y - scrollHeight ) {
15839                    this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
15840                    loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
15841                }
15842            } else if ( event.scroll > 0 ) {
15843                //scroll dowm
15844                if ( offsetTop < 0 ) {
15845                    this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
15846                    loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
15847                }
15848            }
15849        }
15850    }
15851    //cancels event
15852    return false;
15853}
15854
15855
15856function loadPanels( strip, viewerSize, scroll ) {
15857    var panelSize,
15858        activePanelsStart,
15859        activePanelsEnd,
15860        miniViewer,
15861        style,
15862        i,
15863        element;
15864    if ( 'horizontal' == strip.scroll ) {
15865        panelSize = strip.panelWidth;
15866    } else {
15867        panelSize = strip.panelHeight;
15868    }
15869    activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5;
15870    activePanelsEnd = Math.ceil( ( Math.abs( scroll ) + viewerSize ) / panelSize ) + 1;
15871    activePanelsStart = activePanelsEnd - activePanelsStart;
15872    activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart;
15873
15874    for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) {
15875        element = strip.panels[i];
15876        if ( !element.activePanel ) {
15877            var miniTileSource;
15878            var originalTileSource = strip.viewer.tileSources[i];
15879            if (originalTileSource.referenceStripThumbnailUrl) {
15880                miniTileSource = {
15881                    type: 'image',
15882                    url: originalTileSource.referenceStripThumbnailUrl
15883                };
15884            } else {
15885                miniTileSource = originalTileSource;
15886            }
15887            miniViewer = new $.Viewer( {
15888                id:                     element.id,
15889                tileSources:            [miniTileSource],
15890                element:                element,
15891                navigatorSizeRatio:     strip.sizeRatio,
15892                showNavigator:          false,
15893                mouseNavEnabled:        false,
15894                showNavigationControl:  false,
15895                showSequenceControl:    false,
15896                immediateRender:        true,
15897                blendTime:              0,
15898                animationTime:          0,
15899                loadTilesWithAjax:      strip.viewer.loadTilesWithAjax,
15900                ajaxHeaders:            strip.viewer.ajaxHeaders,
15901                useCanvas:              strip.useCanvas
15902            } );
15903
15904            miniViewer.displayRegion           = $.makeNeutralElement( "div" );
15905            miniViewer.displayRegion.id        = element.id + '-displayregion';
15906            miniViewer.displayRegion.className = 'displayregion';
15907
15908            style               = miniViewer.displayRegion.style;
15909            style.position      = 'relative';
15910            style.top           = '0px';
15911            style.left          = '0px';
15912            style.fontSize      = '0px';
15913            style.overflow      = 'hidden';
15914            style.float         = 'left'; //Webkit
15915            style.cssFloat      = 'left'; //Firefox
15916            style.styleFloat    = 'left'; //IE
15917            style.zIndex        = 999999999;
15918            style.cursor        = 'default';
15919            style.width         = ( strip.panelWidth - 4 ) + 'px';
15920            style.height        = ( strip.panelHeight - 4 ) + 'px';
15921
15922            // TODO: What is this for? Future keyboard navigation support?
15923            miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
15924                element: miniViewer.displayRegion,
15925                startDisabled: true
15926            } );
15927
15928            element.getElementsByTagName( 'div' )[0].appendChild(
15929                miniViewer.displayRegion
15930            );
15931
15932            strip.miniViewers[element.id] = miniViewer;
15933
15934            element.activePanel = true;
15935        }
15936    }
15937}
15938
15939
15940/**
15941 * @private
15942 * @inner
15943 * @function
15944 */
15945function onStripEnter( event ) {
15946    var element = event.eventSource.element;
15947
15948    //$.setElementOpacity(element, 0.8);
15949
15950    //element.style.border = '1px solid #555';
15951    //element.style.background = '#000';
15952
15953    if ( 'horizontal' == this.scroll ) {
15954
15955        //element.style.paddingTop = "0px";
15956        element.style.marginBottom = "0px";
15957
15958    } else {
15959
15960        //element.style.paddingRight = "0px";
15961        element.style.marginLeft = "0px";
15962
15963    }
15964    return false;
15965}
15966
15967
15968/**
15969 * @private
15970 * @inner
15971 * @function
15972 */
15973function onStripExit( event ) {
15974    var element = event.eventSource.element;
15975
15976    if ( 'horizontal' == this.scroll ) {
15977
15978        //element.style.paddingTop = "10px";
15979        element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px";
15980
15981    } else {
15982
15983        //element.style.paddingRight = "10px";
15984        element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px";
15985
15986    }
15987    return false;
15988}
15989
15990
15991/**
15992 * @private
15993 * @inner
15994 * @function
15995 */
15996function onKeyDown( event ) {
15997    //console.log( event.keyCode );
15998
15999    if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
16000        switch ( event.keyCode ) {
16001            case 38: //up arrow
16002                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
16003                return false;
16004            case 40: //down arrow
16005                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
16006                return false;
16007            case 37: //left arrow
16008                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
16009                return false;
16010            case 39: //right arrow
16011                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
16012                return false;
16013            default:
16014                //console.log( 'navigator keycode %s', event.keyCode );
16015                return true;
16016        }
16017    } else {
16018        return true;
16019    }
16020}
16021
16022
16023/**
16024 * @private
16025 * @inner
16026 * @function
16027 */
16028function onKeyPress( event ) {
16029    //console.log( event.keyCode );
16030
16031    if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
16032        switch ( event.keyCode ) {
16033            case 61: //=|+
16034                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
16035                return false;
16036            case 45: //-|_
16037                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
16038                return false;
16039            case 48: //0|)
16040            case 119: //w
16041            case 87: //W
16042                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
16043                return false;
16044            case 115: //s
16045            case 83: //S
16046                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
16047                return false;
16048            case 97: //a
16049                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
16050                return false;
16051            case 100: //d
16052                onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
16053                return false;
16054            default:
16055                //console.log( 'navigator keycode %s', event.keyCode );
16056                return true;
16057        }
16058    } else {
16059        return true;
16060    }
16061}
16062
16063}(OpenSeadragon));
16064
16065/*
16066 * OpenSeadragon - DisplayRect
16067 *
16068 * Copyright (C) 2009 CodePlex Foundation
16069 * Copyright (C) 2010-2013 OpenSeadragon contributors
16070 *
16071 * Redistribution and use in source and binary forms, with or without
16072 * modification, are permitted provided that the following conditions are
16073 * met:
16074 *
16075 * - Redistributions of source code must retain the above copyright notice,
16076 *   this list of conditions and the following disclaimer.
16077 *
16078 * - Redistributions in binary form must reproduce the above copyright
16079 *   notice, this list of conditions and the following disclaimer in the
16080 *   documentation and/or other materials provided with the distribution.
16081 *
16082 * - Neither the name of CodePlex Foundation nor the names of its
16083 *   contributors may be used to endorse or promote products derived from
16084 *   this software without specific prior written permission.
16085 *
16086 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16087 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16088 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16089 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
16090 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16091 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
16092 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
16093 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
16094 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
16095 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
16096 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16097 */
16098
16099(function( $ ){
16100
16101/**
16102 * @class DisplayRect
16103 * @classdesc A display rectangle is very similar to {@link OpenSeadragon.Rect} but adds two
16104 * fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels
16105 * for this rectangle.
16106 *
16107 * @memberof OpenSeadragon
16108 * @extends OpenSeadragon.Rect
16109 * @param {Number} x The vector component 'x'.
16110 * @param {Number} y The vector component 'y'.
16111 * @param {Number} width The vector component 'height'.
16112 * @param {Number} height The vector component 'width'.
16113 * @param {Number} minLevel The lowest zoom level supported.
16114 * @param {Number} maxLevel The highest zoom level supported.
16115 */
16116$.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) {
16117    $.Rect.apply( this, [ x, y, width, height ] );
16118
16119    /**
16120     * The lowest zoom level supported.
16121     * @member {Number} minLevel
16122     * @memberof OpenSeadragon.DisplayRect#
16123     */
16124    this.minLevel = minLevel;
16125    /**
16126     * The highest zoom level supported.
16127     * @member {Number} maxLevel
16128     * @memberof OpenSeadragon.DisplayRect#
16129     */
16130    this.maxLevel = maxLevel;
16131};
16132
16133$.extend( $.DisplayRect.prototype, $.Rect.prototype );
16134
16135}( OpenSeadragon ));
16136
16137/*
16138 * OpenSeadragon - Spring
16139 *
16140 * Copyright (C) 2009 CodePlex Foundation
16141 * Copyright (C) 2010-2013 OpenSeadragon contributors
16142 *
16143 * Redistribution and use in source and binary forms, with or without
16144 * modification, are permitted provided that the following conditions are
16145 * met:
16146 *
16147 * - Redistributions of source code must retain the above copyright notice,
16148 *   this list of conditions and the following disclaimer.
16149 *
16150 * - Redistributions in binary form must reproduce the above copyright
16151 *   notice, this list of conditions and the following disclaimer in the
16152 *   documentation and/or other materials provided with the distribution.
16153 *
16154 * - Neither the name of CodePlex Foundation nor the names of its
16155 *   contributors may be used to endorse or promote products derived from
16156 *   this software without specific prior written permission.
16157 *
16158 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16159 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16160 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16161 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
16162 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16163 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
16164 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
16165 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
16166 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
16167 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
16168 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16169 */
16170
16171(function( $ ){
16172
16173/**
16174 * @class Spring
16175 * @memberof OpenSeadragon
16176 * @param {Object} options - Spring configuration settings.
16177 * @param {Number} options.springStiffness - Spring stiffness. Must be greater than zero.
16178 * The closer to zero, the closer to linear animation.
16179 * @param {Number} options.animationTime - Animation duration per spring, in seconds.
16180 * Must be zero or greater.
16181 * @param {Number} [options.initial=0] - Initial value of spring.
16182 * @param {Boolean} [options.exponential=false] - Whether this spring represents
16183 * an exponential scale (such as zoom) and should be animated accordingly. Note that
16184 * exponential springs must have non-zero values.
16185 */
16186$.Spring = function( options ) {
16187    var args = arguments;
16188
16189    if( typeof ( options ) != 'object' ){
16190        //allows backward compatible use of ( initialValue, config ) as
16191        //constructor parameters
16192        options = {
16193            initial: args.length && typeof ( args[ 0 ] ) == "number" ?
16194                args[ 0 ] :
16195                undefined,
16196            /**
16197             * Spring stiffness.
16198             * @member {Number} springStiffness
16199             * @memberof OpenSeadragon.Spring#
16200             */
16201            springStiffness: args.length > 1 ?
16202                args[ 1 ].springStiffness :
16203                5.0,
16204            /**
16205             * Animation duration per spring.
16206             * @member {Number} animationTime
16207             * @memberof OpenSeadragon.Spring#
16208             */
16209            animationTime: args.length > 1 ?
16210                args[ 1 ].animationTime :
16211                1.5
16212        };
16213    }
16214
16215    $.console.assert(typeof options.springStiffness === "number" && options.springStiffness !== 0,
16216        "[OpenSeadragon.Spring] options.springStiffness must be a non-zero number");
16217
16218    $.console.assert(typeof options.animationTime === "number" && options.animationTime >= 0,
16219        "[OpenSeadragon.Spring] options.animationTime must be a number greater than or equal to 0");
16220
16221    if (options.exponential) {
16222        this._exponential = true;
16223        delete options.exponential;
16224    }
16225
16226    $.extend( true, this, options);
16227
16228    /**
16229     * @member {Object} current
16230     * @memberof OpenSeadragon.Spring#
16231     * @property {Number} value
16232     * @property {Number} time
16233     */
16234    this.current = {
16235        value: typeof ( this.initial ) == "number" ?
16236            this.initial :
16237            (this._exponential ? 0 : 1),
16238        time:  $.now() // always work in milliseconds
16239    };
16240
16241    $.console.assert(!this._exponential || this.current.value !== 0,
16242        "[OpenSeadragon.Spring] value must be non-zero for exponential springs");
16243
16244    /**
16245     * @member {Object} start
16246     * @memberof OpenSeadragon.Spring#
16247     * @property {Number} value
16248     * @property {Number} time
16249     */
16250    this.start = {
16251        value: this.current.value,
16252        time:  this.current.time
16253    };
16254
16255    /**
16256     * @member {Object} target
16257     * @memberof OpenSeadragon.Spring#
16258     * @property {Number} value
16259     * @property {Number} time
16260     */
16261    this.target = {
16262        value: this.current.value,
16263        time:  this.current.time
16264    };
16265
16266    if (this._exponential) {
16267        this.start._logValue = Math.log(this.start.value);
16268        this.target._logValue = Math.log(this.target.value);
16269        this.current._logValue = Math.log(this.current.value);
16270    }
16271};
16272
16273/** @lends OpenSeadragon.Spring.prototype */
16274$.Spring.prototype = {
16275
16276    /**
16277     * @function
16278     * @param {Number} target
16279     */
16280    resetTo: function( target ) {
16281        $.console.assert(!this._exponential || target !== 0,
16282            "[OpenSeadragon.Spring.resetTo] target must be non-zero for exponential springs");
16283
16284        this.start.value = this.target.value = this.current.value = target;
16285        this.start.time = this.target.time = this.current.time = $.now();
16286
16287        if (this._exponential) {
vendor: 4,653 bytes, lines 16288-16424
16288            this.start._logValue = Math.log(this.start.value);
16289            this.target._logValue = Math.log(this.target.value);
16290            this.current._logValue = Math.log(this.current.value);
16291        }
16292    },
16293
16294    /**
16295     * @function
16296     * @param {Number} target
16297     */
16298    springTo: function( target ) {
16299        $.console.assert(!this._exponential || target !== 0,
16300            "[OpenSeadragon.Spring.springTo] target must be non-zero for exponential springs");
16301
16302        this.start.value  = this.current.value;
16303        this.start.time   = this.current.time;
16304        this.target.value = target;
16305        this.target.time  = this.start.time + 1000 * this.animationTime;
16306
16307        if (this._exponential) {
16308            this.start._logValue = Math.log(this.start.value);
16309            this.target._logValue = Math.log(this.target.value);
16310        }
16311    },
16312
16313    /**
16314     * @function
16315     * @param {Number} delta
16316     */
16317    shiftBy: function( delta ) {
16318        this.start.value  += delta;
16319        this.target.value += delta;
16320
16321        if (this._exponential) {
16322            $.console.assert(this.target.value !== 0 && this.start.value !== 0,
16323                "[OpenSeadragon.Spring.shiftBy] spring value must be non-zero for exponential springs");
16324
16325            this.start._logValue = Math.log(this.start.value);
16326            this.target._logValue = Math.log(this.target.value);
16327        }
16328    },
16329
16330    setExponential: function(value) {
16331        this._exponential = value;
16332
16333        if (this._exponential) {
16334            $.console.assert(this.current.value !== 0 && this.target.value !== 0 && this.start.value !== 0,
16335                "[OpenSeadragon.Spring.setExponential] spring value must be non-zero for exponential springs");
16336
16337            this.start._logValue = Math.log(this.start.value);
16338            this.target._logValue = Math.log(this.target.value);
16339            this.current._logValue = Math.log(this.current.value);
16340        }
16341    },
16342
16343    /**
16344     * @function
16345     * @returns true if the value got updated, false otherwise
16346     */
16347    update: function() {
16348        this.current.time  = $.now();
16349
16350        var startValue, targetValue;
16351        if (this._exponential) {
16352            startValue = this.start._logValue;
16353            targetValue = this.target._logValue;
16354        } else {
16355            startValue = this.start.value;
16356            targetValue = this.target.value;
16357        }
16358
16359        var currentValue = (this.current.time >= this.target.time) ?
16360            targetValue :
16361            startValue +
16362                ( targetValue - startValue ) *
16363                transform(
16364                    this.springStiffness,
16365                    ( this.current.time - this.start.time ) /
16366                    ( this.target.time - this.start.time )
16367                );
16368
16369        var oldValue = this.current.value;
16370        if (this._exponential) {
16371            this.current.value = Math.exp(currentValue);
16372        } else {
16373            this.current.value = currentValue;
16374        }
16375
16376        return oldValue != this.current.value;
16377    },
16378
16379    /**
16380     * Returns whether the spring is at the target value
16381     * @function
16382     * @returns {Boolean} True if at target value, false otherwise
16383     */
16384    isAtTargetValue: function() {
16385        return this.current.value === this.target.value;
16386    }
16387};
16388
16389/**
16390 * @private
16391 */
16392function transform( stiffness, x ) {
16393    return ( 1.0 - Math.exp( stiffness * -x ) ) /
16394        ( 1.0 - Math.exp( -stiffness ) );
16395}
16396
16397}( OpenSeadragon ));
16398
16399/*
16400 * OpenSeadragon - ImageLoader
16401 *
16402 * Copyright (C) 2009 CodePlex Foundation
16403 * Copyright (C) 2010-2013 OpenSeadragon contributors
16404
16405 * Redistribution and use in source and binary forms, with or without
16406 * modification, are permitted provided that the following conditions are
16407 * met:
16408 *
16409 * - Redistributions of source code must retain the above copyright notice,
16410 *   this list of conditions and the following disclaimer.
16411 *
16412 * - Redistributions in binary form must reproduce the above copyright
16413 *   notice, this list of conditions and the following disclaimer in the
16414 *   documentation and/or other materials provided with the distribution.
16415 *
16416 * - Neither the name of CodePlex Foundation nor the names of its
16417 *   contributors may be used to endorse or promote products derived from
16418 *   this software without specific prior written permission.
16419 *
16420 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16421 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16422 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16423 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
16424 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16425 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
16426 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
16427 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
16428 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
16429 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
16430 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16431 */
16432
16433(function($){
16434
16435/**
16436 * @private
16437 * @class ImageJob
16438 * @classdesc Handles downloading of a single image.
16439 * @param {Object} options - Options for this ImageJob.
16440 * @param {String} [options.src] - URL of image to download.
16441 * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
16442 * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
16443 * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
16444 * @param {Function} [options.callback] - Called once image has been downloaded.
16445 * @param {Function} [options.abort] - Called when this image job is aborted.
16446 * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
16447 */
16448function ImageJob (options) {
16449
16450    $.extend(true, this, {
16451        timeout: $.DEFAULT_SETTINGS.timeout,
16452        jobId: null
16453    }, options);
16454
16455    /**
16456     * Image object which will contain downloaded image.
16457     * @member {Image} image
16458     * @memberof OpenSeadragon.ImageJob#
16459     */
16460    this.image = null;
16461}
16462
16463ImageJob.prototype = {
16464    errorMsg: null,
16465
16466    /**
16467     * Starts the image job.
16468     * @method
16469     */
16470    start: function(){
16471        var self = this;
16472        var selfAbort = this.abort;
16473
16474        this.image = new Image();
16475
16476        this.image.onload = function(){
16477            self.finish(true);
16478        };
16479        this.image.onabort = this.image.onerror = function() {
16480            self.errorMsg = "Image load aborted";
16481            self.finish(false);
16482        };
16483
16484        this.jobId = window.setTimeout(function(){
16485            self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)";
16486            self.finish(false);
16487        }, this.timeout);
16488
16489        // Load the tile with an AJAX request if the loadWithAjax option is
16490        // set. Otherwise load the image by setting the source proprety of the image object.
16491        if (this.loadWithAjax) {
16492            this.request = $.makeAjaxRequest({
16493                url: this.src,
16494                withCredentials: this.ajaxWithCredentials,
16495                headers: this.ajaxHeaders,
16496                responseType: "arraybuffer",
16497                success: function(request) {
16498                    var blb;
16499                    // Make the raw data into a blob.
16500                    // BlobBuilder fallback adapted from
16501                    // http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
16502                    try {
16503                        blb = new window.Blob([request.response]);
16504                    } catch (e) {
16505                        var BlobBuilder = (
16506                            window.BlobBuilder ||
16507                            window.WebKitBlobBuilder ||
16508                            window.MozBlobBuilder ||
16509                            window.MSBlobBuilder
16510                        );
16511                        if (e.name === 'TypeError' && BlobBuilder) {
16512                            var bb = new BlobBuilder();
16513                            bb.append(request.response);
16514                            blb = bb.getBlob();
16515                        }
16516                    }
16517                    // If the blob is empty for some reason consider the image load a failure.
16518                    if (blb.size === 0) {
16519                        self.errorMsg = "Empty image response.";
16520                        self.finish(false);
16521                    }
16522                    // Create a URL for the blob data and make it the source of the image object.
16523                    // This will still trigger Image.onload to indicate a successful tile load.
16524                    var url = (window.URL || window.webkitURL).createObjectURL(blb);
16525                    self.image.src = url;
16526                },
16527                error: function(request) {
16528                    self.errorMsg = "Image load aborted - XHR error";
16529                    self.finish(false);
16530                }
16531            });
16532
16533            // Provide a function to properly abort the request.
16534            this.abort = function() {
16535                self.request.abort();
16536
16537                // Call the existing abort function if available
16538                if (typeof selfAbort === "function") {
16539                    selfAbort();
16540                }
16541            };
16542        } else {
16543            if (this.crossOriginPolicy !== false) {
16544                this.image.crossOrigin = this.crossOriginPolicy;
16545            }
16546
16547            this.image.src = this.src;
16548        }
16549    },
16550
16551    finish: function(successful) {
16552        this.image.onload = this.image.onerror = this.image.onabort = null;
16553        if (!successful) {
16554            this.image = null;
16555        }
16556
16557        if (this.jobId) {
16558            window.clearTimeout(this.jobId);
16559        }
16560
16561        this.callback(this);
16562    }
16563
16564};
16565
16566/**
16567 * @class ImageLoader
16568 * @memberof OpenSeadragon
16569 * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
16570 * You generally won't have to interact with the ImageLoader directly.
16571 * @param {Object} options - Options for this ImageLoader.
16572 * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
16573 * @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete.
16574 */
16575$.ImageLoader = function(options) {
16576
16577    $.extend(true, this, {
16578        jobLimit:       $.DEFAULT_SETTINGS.imageLoaderLimit,
16579        timeout:        $.DEFAULT_SETTINGS.timeout,
16580        jobQueue:       [],
16581        jobsInProgress: 0
16582    }, options);
16583
16584};
16585
16586/** @lends OpenSeadragon.ImageLoader.prototype */
16587$.ImageLoader.prototype = {
16588
16589    /**
16590     * Add an unloaded image to the loader queue.
16591     * @method
16592     * @param {Object} options - Options for this job.
16593     * @param {String} [options.src] - URL of image to download.
16594     * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
16595     * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
16596     * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
16597     * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
16598     * requests.
16599     * @param {Function} [options.callback] - Called once image has been downloaded.
16600     * @param {Function} [options.abort] - Called when this image job is aborted.
16601     */
16602    addJob: function(options) {
16603        var _this = this,
16604            complete = function(job) {
16605                completeJob(_this, job, options.callback);
16606            },
16607            jobOptions = {
16608                src: options.src,
16609                loadWithAjax: options.loadWithAjax,
16610                ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
16611                crossOriginPolicy: options.crossOriginPolicy,
16612                ajaxWithCredentials: options.ajaxWithCredentials,
16613                callback: complete,
16614                abort: options.abort,
16615                timeout: this.timeout
16616            },
16617            newJob = new ImageJob(jobOptions);
16618
16619        if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
16620            newJob.start();
16621            this.jobsInProgress++;
16622        }
16623        else {
16624            this.jobQueue.push( newJob );
16625        }
16626    },
16627
16628    /**
16629     * Clear any unstarted image loading jobs from the queue.
16630     * @method
16631     */
16632    clear: function() {
16633        for( var i = 0; i < this.jobQueue.length; i++ ) {
16634            var job = this.jobQueue[i];
16635            if ( typeof job.abort === "function" ) {
16636                job.abort();
16637            }
16638        }
16639
16640        this.jobQueue = [];
16641    }
16642};
16643
16644/**
16645 * Cleans up ImageJob once completed.
16646 * @method
16647 * @private
16648 * @param loader - ImageLoader used to start job.
16649 * @param job - The ImageJob that has completed.
16650 * @param callback - Called once cleanup is finished.
16651 */
16652function completeJob(loader, job, callback) {
16653    var nextJob;
16654
16655    loader.jobsInProgress--;
16656
16657    if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
16658        nextJob = loader.jobQueue.shift();
16659        nextJob.start();
16660        loader.jobsInProgress++;
16661    }
16662
16663    callback(job.image, job.errorMsg, job.request);
16664}
16665
16666}(OpenSeadragon));
16667
16668/*
16669 * OpenSeadragon - Tile
16670 *
16671 * Copyright (C) 2009 CodePlex Foundation
16672 * Copyright (C) 2010-2013 OpenSeadragon contributors
16673 *
16674 * Redistribution and use in source and binary forms, with or without
16675 * modification, are permitted provided that the following conditions are
16676 * met:
16677 *
16678 * - Redistributions of source code must retain the above copyright notice,
16679 *   this list of conditions and the following disclaimer.
16680 *
16681 * - Redistributions in binary form must reproduce the above copyright
16682 *   notice, this list of conditions and the following disclaimer in the
16683 *   documentation and/or other materials provided with the distribution.
16684 *
16685 * - Neither the name of CodePlex Foundation nor the names of its
16686 *   contributors may be used to endorse or promote products derived from
16687 *   this software without specific prior written permission.
16688 *
16689 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16690 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16691 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16692 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
16693 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
16694 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
16695 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
16696 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
16697 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
16698 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
16699 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16700 */
16701
16702(function( $ ){
16703
16704/**
16705 * @class Tile
16706 * @memberof OpenSeadragon
16707 * @param {Number} level The zoom level this tile belongs to.
16708 * @param {Number} x The vector component 'x'.
16709 * @param {Number} y The vector component 'y'.
16710 * @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized
16711 *      coordinates.
16712 * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
16713 *      this tile failed to load? )
16714 * @param {String} url The URL of this tile's image.
16715 * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
16716 * is provided directly by the tile source.
16717 * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
16718 * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
16719 * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
16720 * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
16721 * with HTML the entire tile is always used.
16722 */
16723$.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds) {
16724    /**
16725     * The zoom level this tile belongs to.
16726     * @member {Number} level
16727     * @memberof OpenSeadragon.Tile#
16728     */
16729    this.level   = level;
16730    /**
16731     * The vector component 'x'.
16732     * @member {Number} x
16733     * @memberof OpenSeadragon.Tile#
16734     */
16735    this.x       = x;
16736    /**
16737     * The vector component 'y'.
16738     * @member {Number} y
16739     * @memberof OpenSeadragon.Tile#
16740     */
16741    this.y       = y;
16742    /**
16743     * Where this tile fits, in normalized coordinates
16744     * @member {OpenSeadragon.Rect} bounds
16745     * @memberof OpenSeadragon.Tile#
16746     */
16747    this.bounds  = bounds;
16748    /**
16749    * The portion of the tile to use as the source of the drawing operation, in pixels. Note that
16750    * this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
16751    * @member {OpenSeadragon.Rect} sourceBounds
16752    * @memberof OpenSeadragon.Tile#
16753    */
16754    this.sourceBounds = sourceBounds;
16755    /**
16756     * Is this tile a part of a sparse image? Also has this tile failed to load?
16757     * @member {Boolean} exists
16758     * @memberof OpenSeadragon.Tile#
16759     */
16760    this.exists  = exists;
16761    /**
16762     * The URL of this tile's image.
16763     * @member {String} url
16764     * @memberof OpenSeadragon.Tile#
16765     */
16766    this.url     = url;
16767    /**
16768     * The context2D of this tile if it is provided directly by the tile source.
16769     * @member {CanvasRenderingContext2D} context2D
16770     * @memberOf OpenSeadragon.Tile#
16771     */
16772    this.context2D = context2D;
16773    /**
16774     * Whether to load this tile's image with an AJAX request.
16775     * @member {Boolean} loadWithAjax
16776     * @memberof OpenSeadragon.Tile#
16777     */
16778    this.loadWithAjax = loadWithAjax;
16779    /**
16780     * The headers to be used in requesting this tile's image.
16781     * Only used if loadWithAjax is set to true.
16782     * @member {Object} ajaxHeaders
16783     * @memberof OpenSeadragon.Tile#
16784     */
16785    this.ajaxHeaders = ajaxHeaders;
16786    /**
16787     * The unique cache key for this tile.
16788     * @member {String} cacheKey
16789     * @memberof OpenSeadragon.Tile#
16790     */
16791    if (this.ajaxHeaders) {
16792        this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders);
16793    } else {
16794        this.cacheKey = this.url;
16795    }
16796    /**
16797     * Is this tile loaded?
16798     * @member {Boolean} loaded
16799     * @memberof OpenSeadragon.Tile#
16800     */
16801    this.loaded  = false;
16802    /**
16803     * Is this tile loading?
16804     * @member {Boolean} loading
16805     * @memberof OpenSeadragon.Tile#
16806     */
16807    this.loading = false;
16808
16809    /**
16810     * The HTML div element for this tile
16811     * @member {Element} element
16812     * @memberof OpenSeadragon.Tile#
16813     */
16814    this.element    = null;
16815    /**
16816     * The HTML img element for this tile.
16817     * @member {Element} imgElement
16818     * @memberof OpenSeadragon.Tile#
16819     */
16820    this.imgElement = null;
16821    /**
16822     * The Image object for this tile.
16823     * @member {Object} image
16824     * @memberof OpenSeadragon.Tile#
16825     */
16826    this.image      = null;
16827
16828    /**
16829     * The alias of this.element.style.
16830     * @member {String} style
16831     * @memberof OpenSeadragon.Tile#
16832     */
16833    this.style      = null;
16834    /**
16835     * This tile's position on screen, in pixels.
16836     * @member {OpenSeadragon.Point} position
16837     * @memberof OpenSeadragon.Tile#
16838     */
16839    this.position   = null;
16840    /**
16841     * This tile's size on screen, in pixels.
16842     * @member {OpenSeadragon.Point} size
16843     * @memberof OpenSeadragon.Tile#
16844     */
16845    this.size       = null;
16846    /**
16847     * The start time of this tile's blending.
16848     * @member {Number} blendStart
16849     * @memberof OpenSeadragon.Tile#
16850     */
16851    this.blendStart = null;
16852    /**
16853     * The current opacity this tile should be.
16854     * @member {Number} opacity
16855     * @memberof OpenSeadragon.Tile#
16856     */
16857    this.opacity    = null;
16858    /**
16859     * The squared distance of this tile to the viewport center.
16860     * Use for comparing tiles.
16861     * @private
16862     * @member {Number} squaredDistance
16863     * @memberof OpenSeadragon.Tile#
16864     */
16865    this.squaredDistance   = null;
16866    /**
16867     * The visibility score of this tile.
16868     * @member {Number} visibility
16869     * @memberof OpenSeadragon.Tile#
16870     */
16871    this.visibility = null;
16872
16873    /**
16874     * Whether this tile is currently being drawn.
16875     * @member {Boolean} beingDrawn
16876     * @memberof OpenSeadragon.Tile#
16877     */
16878    this.beingDrawn     = false;
16879
16880    /**
16881     * Timestamp the tile was last touched.
16882     * @member {Number} lastTouchTime
16883     * @memberof OpenSeadragon.Tile#
16884     */
16885    this.lastTouchTime  = 0;
16886
16887    /**
16888     * Whether this tile is in the right-most column for its level.
16889     * @member {Boolean} isRightMost
16890     * @memberof OpenSeadragon.Tile#
16891     */
16892    this.isRightMost = false;
16893
16894    /**
16895     * Whether this tile is in the bottom-most row for its level.
16896     * @member {Boolean} isBottomMost
16897     * @memberof OpenSeadragon.Tile#
16898     */
16899    this.isBottomMost = false;
16900};
16901
16902/** @lends OpenSeadragon.Tile.prototype */
16903$.Tile.prototype = {
16904
16905    /**
16906     * Provides a string representation of this tiles level and (x,y)
16907     * components.
16908     * @function
16909     * @returns {String}
16910     */
16911    toString: function() {
16912        return this.level + "/" + this.x + "_" + this.y;
16913    },
16914
16915    // private
16916    _hasTransparencyChannel: function() {
16917        return !!this.context2D || this.url.match('.png');
16918    },
16919
16920    /**
16921     * Renders the tile in an html container.
16922     * @function
16923     * @param {Element} container
16924     */
16925    drawHTML: function( container ) {
16926        if (!this.cacheImageRecord) {
16927            $.console.warn(
16928                '[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
16929                this.toString());
16930            return;
16931        }
16932
16933        if ( !this.loaded ) {
16934            $.console.warn(
16935                "Attempting to draw tile %s when it's not yet loaded.",
16936                this.toString()
16937            );
16938            return;
16939        }
16940
16941        //EXPERIMENTAL - trying to figure out how to scale the container
16942        //               content during animation of the container size.
16943
16944        if ( !this.element ) {
16945            this.element                              = $.makeNeutralElement( "div" );
16946            this.imgElement                           = this.cacheImageRecord.getImage().cloneNode();
16947            this.imgElement.style.msInterpolationMode = "nearest-neighbor";
16948            this.imgElement.style.width               = "100%";
16949            this.imgElement.style.height              = "100%";
16950
16951            this.style                     = this.element.style;
16952            this.style.position            = "absolute";
16953        }
16954        if ( this.element.parentNode != container ) {
16955            container.appendChild( this.element );
16956        }
16957        if ( this.imgElement.parentNode != this.element ) {
16958            this.element.appendChild( this.imgElement );
16959        }
16960
16961        this.style.top     = this.position.y + "px";
16962        this.style.left    = this.position.x + "px";
16963        this.style.height  = this.size.y + "px";
16964        this.style.width   = this.size.x + "px";
16965
16966        $.setElementOpacity( this.element, this.opacity );
16967    },
16968
16969    /**
16970     * Renders the tile in a canvas-based context.
16971     * @function
16972     * @param {Canvas} context
16973     * @param {Function} drawingHandler - Method for firing the drawing event.
16974     * drawingHandler({context, tile, rendered})
16975     * where <code>rendered</code> is the context with the pre-drawn image.
16976     * @param {Number} [scale=1] - Apply a scale to position and size
16977     * @param {OpenSeadragon.Point} [translate] - A translation vector
16978     */
16979    drawCanvas: function( context, drawingHandler, scale, translate ) {
16980
16981        var position = this.position.times($.pixelDensityRatio),
16982            size     = this.size.times($.pixelDensityRatio),
16983            rendered;
16984
16985        if (!this.context2D && !this.cacheImageRecord) {
16986            $.console.warn(
16987                '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
16988                this.toString());
16989            return;
16990        }
16991
16992        rendered = this.context2D || this.cacheImageRecord.getRenderedContext();
16993
16994        if ( !this.loaded || !rendered ){
16995            $.console.warn(
16996                "Attempting to draw tile %s when it's not yet loaded.",
16997                this.toString()
16998            );
16999
17000            return;
17001        }
17002
17003        context.save();
17004
17005        context.globalAlpha = this.opacity;
17006
17007        if (typeof scale === 'number' && scale !== 1) {
17008            // draw tile at a different scale
17009            position = position.times(scale);
17010            size = size.times(scale);
17011        }
17012
17013        if (translate instanceof $.Point) {
17014            // shift tile position slightly
17015            position = position.plus(translate);
17016        }
17017
17018        //if we are supposed to be rendering fully opaque rectangle,
17019        //ie its done fading or fading is turned off, and if we are drawing
17020        //an image with an alpha channel, then the only way
17021        //to avoid seeing the tile underneath is to clear the rectangle
17022        if (context.globalAlpha === 1 && this._hasTransparencyChannel()) {
17023            //clearing only the inside of the rectangle occupied
17024            //by the png prevents edge flikering
17025            context.clearRect(
17026                position.x,
17027                position.y,
17028                size.x,
17029                size.y
17030            );
17031        }
17032
17033        // This gives the application a chance to make image manipulation
17034        // changes as we are rendering the image
17035        drawingHandler({context: context, tile: this, rendered: rendered});
17036
17037        var sourceWidth, sourceHeight;
17038        if (this.sourceBounds) {
17039            sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
17040            sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
17041        } else {
17042            sourceWidth = rendered.canvas.width;
17043            sourceHeight = rendered.canvas.height;
17044        }
17045
17046        context.drawImage(
17047            rendered.canvas,
17048            0,
17049            0,
17050            sourceWidth,
17051            sourceHeight,
17052            position.x,
17053            position.y,
17054            size.x,
17055            size.y
17056        );
17057
17058        context.restore();
17059    },
17060
17061    /**
17062     * Get the ratio between current and original size.
17063     * @function
17064     * @return {Float}
17065     */
17066    getScaleForEdgeSmoothing: function() {
17067        var context;
17068        if (this.cacheImageRecord) {
17069            context = this.cacheImageRecord.getRenderedContext();
17070        } else if (this.context2D) {
17071            context = this.context2D;
17072        } else {
17073            $.console.warn(
17074                '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
17075                this.toString());
17076            return 1;
17077        }
17078        return context.canvas.width / (this.size.x * $.pixelDensityRatio);
17079    },
17080
17081    /**
17082     * Get a translation vector that when applied to the tile position produces integer coordinates.
17083     * Needed to avoid swimming and twitching.
17084     * @function
17085     * @param {Number} [scale=1] - Scale to be applied to position.
17086     * @return {OpenSeadragon.Point}
17087     */
17088    getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
17089        // The translation vector must have positive values, otherwise the image goes a bit off
17090        // the sketch canvas to the top and left and we must use negative coordinates to repaint it
17091        // to the main canvas. In that case, some browsers throw:
17092        // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
17093        var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
vendor: 21,041 bytes, lines 17094-17630
17094        var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
17095        return new $.Point(x, y).minus(
17096            this.position
17097                .times($.pixelDensityRatio)
17098                .times(scale || 1)
17099                .apply(function(x) {
17100                    return x % 1;
17101                })
17102        );
17103    },
17104
17105    /**
17106     * Removes tile from its container.
17107     * @function
17108     */
17109    unload: function() {
17110        if ( this.imgElement && this.imgElement.parentNode ) {
17111            this.imgElement.parentNode.removeChild( this.imgElement );
17112        }
17113        if ( this.element && this.element.parentNode ) {
17114            this.element.parentNode.removeChild( this.element );
17115        }
17116
17117        this.element    = null;
17118        this.imgElement = null;
17119        this.loaded     = false;
17120        this.loading    = false;
17121    }
17122};
17123
17124}( OpenSeadragon ));
17125
17126/*
17127 * OpenSeadragon - Overlay
17128 *
17129 * Copyright (C) 2009 CodePlex Foundation
17130 * Copyright (C) 2010-2013 OpenSeadragon contributors
17131 *
17132 * Redistribution and use in source and binary forms, with or without
17133 * modification, are permitted provided that the following conditions are
17134 * met:
17135 *
17136 * - Redistributions of source code must retain the above copyright notice,
17137 *   this list of conditions and the following disclaimer.
17138 *
17139 * - Redistributions in binary form must reproduce the above copyright
17140 *   notice, this list of conditions and the following disclaimer in the
17141 *   documentation and/or other materials provided with the distribution.
17142 *
17143 * - Neither the name of CodePlex Foundation nor the names of its
17144 *   contributors may be used to endorse or promote products derived from
17145 *   this software without specific prior written permission.
17146 *
17147 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17148 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17149 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17150 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
17151 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
17152 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
17153 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
17154 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
17155 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
17156 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
17157 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17158 */
17159
17160(function($) {
17161
17162    /**
17163     * An enumeration of positions that an overlay may be assigned relative to
17164     * the viewport.
17165     * It is identical to OpenSeadragon.Placement but is kept for backward
17166     * compatibility.
17167     * @member OverlayPlacement
17168     * @memberof OpenSeadragon
17169     * @see OpenSeadragon.Placement
17170     * @static
17171     * @readonly
17172     * @type {Object}
17173     * @property {Number} CENTER
17174     * @property {Number} TOP_LEFT
17175     * @property {Number} TOP
17176     * @property {Number} TOP_RIGHT
17177     * @property {Number} RIGHT
17178     * @property {Number} BOTTOM_RIGHT
17179     * @property {Number} BOTTOM
17180     * @property {Number} BOTTOM_LEFT
17181     * @property {Number} LEFT
17182     */
17183    $.OverlayPlacement = $.Placement;
17184
17185    /**
17186     * An enumeration of possible ways to handle overlays rotation
17187     * @member OverlayRotationMode
17188     * @memberOf OpenSeadragon
17189     * @static
17190     * @readonly
17191     * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
17192     * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
17193     * the viewport. If the overlay contains text, it will get rotated as well.
17194     * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
17195     * taking the size of the bounding box of the rotated bounds.
17196     * Only valid for overlays with Rect location and scalable in both directions.
17197     */
17198    $.OverlayRotationMode = $.freezeObject({
17199        NO_ROTATION: 1,
17200        EXACT: 2,
17201        BOUNDING_BOX: 3
17202    });
17203
17204    /**
17205     * @class Overlay
17206     * @classdesc Provides a way to float an HTML element on top of the viewer element.
17207     *
17208     * @memberof OpenSeadragon
17209     * @param {Object} options
17210     * @param {Element} options.element
17211     * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
17212     * location of the overlay on the image. If a {@link OpenSeadragon.Point}
17213     * is specified, the overlay will be located at this location with respect
17214     * to the placement option. If a {@link OpenSeadragon.Rect} is specified,
17215     * the overlay will be placed at this location with the corresponding width
17216     * and height and placement TOP_LEFT.
17217     * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
17218     * Defines what part of the overlay should be at the specified options.location
17219     * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
17220     * @param {Boolean} [options.checkResize=true] Set to false to avoid to
17221     * check the size of the overlay every time it is drawn in the directions
17222     * which are not scaled. It will improve performances but will cause a
17223     * misalignment if the overlay size changes.
17224     * @param {Number} [options.width] The width of the overlay in viewport
17225     * coordinates. If specified, the width of the overlay will be adjusted when
17226     * the zoom changes.
17227     * @param {Number} [options.height] The height of the overlay in viewport
17228     * coordinates. If specified, the height of the overlay will be adjusted when
17229     * the zoom changes.
17230     * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
17231     * How to handle the rotation of the viewport.
17232     */
17233    $.Overlay = function(element, location, placement) {
17234
17235        /**
17236         * onDraw callback signature used by {@link OpenSeadragon.Overlay}.
17237         *
17238         * @callback OnDrawCallback
17239         * @memberof OpenSeadragon.Overlay
17240         * @param {OpenSeadragon.Point} position
17241         * @param {OpenSeadragon.Point} size
17242         * @param {Element} element
17243         */
17244
17245        var options;
17246        if ($.isPlainObject(element)) {
17247            options = element;
17248        } else {
17249            options = {
17250                element: element,
17251                location: location,
17252                placement: placement
17253            };
17254        }
17255
17256        this.element = options.element;
17257        this.style = options.element.style;
17258        this._init(options);
17259    };
17260
17261    /** @lends OpenSeadragon.Overlay.prototype */
17262    $.Overlay.prototype = {
17263
17264        // private
17265        _init: function(options) {
17266            this.location = options.location;
17267            this.placement = options.placement === undefined ?
17268                $.Placement.TOP_LEFT : options.placement;
17269            this.onDraw = options.onDraw;
17270            this.checkResize = options.checkResize === undefined ?
17271                true : options.checkResize;
17272
17273            // When this.width is not null, the overlay get scaled horizontally
17274            this.width = options.width === undefined ? null : options.width;
17275
17276            // When this.height is not null, the overlay get scaled vertically
17277            this.height = options.height === undefined ? null : options.height;
17278
17279            this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;
17280
17281            // Having a rect as location is a syntactic sugar
17282            if (this.location instanceof $.Rect) {
17283                this.width = this.location.width;
17284                this.height = this.location.height;
17285                this.location = this.location.getTopLeft();
17286                this.placement = $.Placement.TOP_LEFT;
17287            }
17288
17289            // Deprecated properties kept for backward compatibility.
17290            this.scales = this.width !== null && this.height !== null;
17291            this.bounds = new $.Rect(
17292                this.location.x, this.location.y, this.width, this.height);
17293            this.position = this.location;
17294        },
17295
17296        /**
17297         * Internal function to adjust the position of an overlay
17298         * depending on it size and placement.
17299         * @function
17300         * @param {OpenSeadragon.Point} position
17301         * @param {OpenSeadragon.Point} size
17302         */
17303        adjust: function(position, size) {
17304            var properties = $.Placement.properties[this.placement];
17305            if (!properties) {
17306                return;
17307            }
17308            if (properties.isHorizontallyCentered) {
17309                position.x -= size.x / 2;
17310            } else if (properties.isRight) {
17311                position.x -= size.x;
17312            }
17313            if (properties.isVerticallyCentered) {
17314                position.y -= size.y / 2;
17315            } else if (properties.isBottom) {
17316                position.y -= size.y;
17317            }
17318        },
17319
17320        /**
17321         * @function
17322         */
17323        destroy: function() {
17324            var element = this.element;
17325            var style = this.style;
17326
17327            if (element.parentNode) {
17328                element.parentNode.removeChild(element);
17329                //this should allow us to preserve overlays when required between
17330                //pages
17331                if (element.prevElementParent) {
17332                    style.display = 'none';
17333                    //element.prevElementParent.insertBefore(
17334                    //    element,
17335                    //    element.prevNextSibling
17336                    //);
17337                    document.body.appendChild(element);
17338                }
17339            }
17340
17341            // clear the onDraw callback
17342            this.onDraw = null;
17343
17344            style.top = "";
17345            style.left = "";
17346            style.position = "";
17347
17348            if (this.width !== null) {
17349                style.width = "";
17350            }
17351            if (this.height !== null) {
17352                style.height = "";
17353            }
17354            var transformOriginProp = $.getCssPropertyWithVendorPrefix(
17355                'transformOrigin');
17356            var transformProp = $.getCssPropertyWithVendorPrefix(
17357                'transform');
17358            if (transformOriginProp && transformProp) {
17359                style[transformOriginProp] = "";
17360                style[transformProp] = "";
17361            }
17362        },
17363
17364        /**
17365         * @function
17366         * @param {Element} container
17367         */
17368        drawHTML: function(container, viewport) {
17369            var element = this.element;
17370            if (element.parentNode !== container) {
17371                //save the source parent for later if we need it
17372                element.prevElementParent = element.parentNode;
17373                element.prevNextSibling = element.nextSibling;
17374                container.appendChild(element);
17375
17376                // have to set position before calculating size, fix #1116
17377                this.style.position = "absolute";
17378                // this.size is used by overlays which don't get scaled in at
17379                // least one direction when this.checkResize is set to false.
17380                this.size = $.getElementSize(element);
17381            }
17382
17383            var positionAndSize = this._getOverlayPositionAndSize(viewport);
17384
17385            var position = positionAndSize.position;
17386            var size = this.size = positionAndSize.size;
17387            var rotate = positionAndSize.rotate;
17388
17389            // call the onDraw callback if it exists to allow one to overwrite
17390            // the drawing/positioning/sizing of the overlay
17391            if (this.onDraw) {
17392                this.onDraw(position, size, this.element);
17393            } else {
17394                var style = this.style;
17395                style.left = position.x + "px";
17396                style.top = position.y + "px";
17397                if (this.width !== null) {
17398                    style.width = size.x + "px";
17399                }
17400                if (this.height !== null) {
17401                    style.height = size.y + "px";
17402                }
17403                var transformOriginProp = $.getCssPropertyWithVendorPrefix(
17404                    'transformOrigin');
17405                var transformProp = $.getCssPropertyWithVendorPrefix(
17406                    'transform');
17407                if (transformOriginProp && transformProp) {
17408                    if (rotate) {
17409                        style[transformOriginProp] = this._getTransformOrigin();
17410                        style[transformProp] = "rotate(" + rotate + "deg)";
17411                    } else {
17412                        style[transformOriginProp] = "";
17413                        style[transformProp] = "";
17414                    }
17415                }
17416
17417                if (style.display !== 'none') {
17418                    style.display = 'block';
17419                }
17420            }
17421        },
17422
17423        // private
17424        _getOverlayPositionAndSize: function(viewport) {
17425            var position = viewport.pixelFromPoint(this.location, true);
17426            var size = this._getSizeInPixels(viewport);
17427            this.adjust(position, size);
17428
17429            var rotate = 0;
17430            if (viewport.degrees &&
17431                this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
17432                // BOUNDING_BOX is only valid if both directions get scaled.
17433                // Get replaced by EXACT otherwise.
17434                if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
17435                    this.width !== null && this.height !== null) {
17436                    var rect = new $.Rect(position.x, position.y, size.x, size.y);
17437                    var boundingBox = this._getBoundingBox(rect, viewport.degrees);
17438                    position = boundingBox.getTopLeft();
17439                    size = boundingBox.getSize();
17440                } else {
17441                    rotate = viewport.degrees;
17442                }
17443            }
17444
17445            return {
17446                position: position,
17447                size: size,
17448                rotate: rotate
17449            };
17450        },
17451
17452        // private
17453        _getSizeInPixels: function(viewport) {
17454            var width = this.size.x;
17455            var height = this.size.y;
17456            if (this.width !== null || this.height !== null) {
17457                var scaledSize = viewport.deltaPixelsFromPointsNoRotate(
17458                    new $.Point(this.width || 0, this.height || 0), true);
17459                if (this.width !== null) {
17460                    width = scaledSize.x;
17461                }
17462                if (this.height !== null) {
17463                    height = scaledSize.y;
17464                }
17465            }
17466            if (this.checkResize &&
17467                (this.width === null || this.height === null)) {
17468                var eltSize = this.size = $.getElementSize(this.element);
17469                if (this.width === null) {
17470                    width = eltSize.x;
17471                }
17472                if (this.height === null) {
17473                    height = eltSize.y;
17474                }
17475            }
17476            return new $.Point(width, height);
17477        },
17478
17479        // private
17480        _getBoundingBox: function(rect, degrees) {
17481            var refPoint = this._getPlacementPoint(rect);
17482            return rect.rotate(degrees, refPoint).getBoundingBox();
17483        },
17484
17485        // private
17486        _getPlacementPoint: function(rect) {
17487            var result = new $.Point(rect.x, rect.y);
17488            var properties = $.Placement.properties[this.placement];
17489            if (properties) {
17490                if (properties.isHorizontallyCentered) {
17491                    result.x += rect.width / 2;
17492                } else if (properties.isRight) {
17493                    result.x += rect.width;
17494                }
17495                if (properties.isVerticallyCentered) {
17496                    result.y += rect.height / 2;
17497                } else if (properties.isBottom) {
17498                    result.y += rect.height;
17499                }
17500            }
17501            return result;
17502        },
17503
17504        // private
17505        _getTransformOrigin: function() {
17506            var result = "";
17507            var properties = $.Placement.properties[this.placement];
17508            if (!properties) {
17509                return result;
17510            }
17511            if (properties.isLeft) {
17512                result = "left";
17513            } else if (properties.isRight) {
17514                result = "right";
17515            }
17516            if (properties.isTop) {
17517                result += " top";
17518            } else if (properties.isBottom) {
17519                result += " bottom";
17520            }
17521            return result;
17522        },
17523
17524        /**
17525         * Changes the overlay settings.
17526         * @function
17527         * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
17528         * If an object is specified, the options are the same than the constructor
17529         * except for the element which can not be changed.
17530         * @param {OpenSeadragon.Placement} placement
17531         */
17532        update: function(location, placement) {
17533            var options = $.isPlainObject(location) ? location : {
17534                location: location,
17535                placement: placement
17536            };
17537            this._init({
17538                location: options.location || this.location,
17539                placement: options.placement !== undefined ?
17540                    options.placement : this.placement,
17541                onDraw: options.onDraw || this.onDraw,
17542                checkResize: options.checkResize || this.checkResize,
17543                width: options.width !== undefined ? options.width : this.width,
17544                height: options.height !== undefined ? options.height : this.height,
17545                rotationMode: options.rotationMode || this.rotationMode
17546            });
17547        },
17548
17549        /**
17550         * Returns the current bounds of the overlay in viewport coordinates
17551         * @function
17552         * @param {OpenSeadragon.Viewport} viewport the viewport
17553         * @returns {OpenSeadragon.Rect} overlay bounds
17554         */
17555        getBounds: function(viewport) {
17556            $.console.assert(viewport,
17557                'A viewport must now be passed to Overlay.getBounds.');
17558            var width = this.width;
17559            var height = this.height;
17560            if (width === null || height === null) {
17561                var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
17562                if (width === null) {
17563                    width = size.x;
17564                }
17565                if (height === null) {
17566                    height = size.y;
17567                }
17568            }
17569            var location = this.location.clone();
17570            this.adjust(location, new $.Point(width, height));
17571            return this._adjustBoundsForRotation(
17572                viewport, new $.Rect(location.x, location.y, width, height));
17573        },
17574
17575        // private
17576        _adjustBoundsForRotation: function(viewport, bounds) {
17577            if (!viewport ||
17578                viewport.degrees === 0 ||
17579                this.rotationMode === $.OverlayRotationMode.EXACT) {
17580                return bounds;
17581            }
17582            if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
17583                // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
17584                if (this.width === null || this.height === null) {
17585                    return bounds;
17586                }
17587                // It is easier to just compute the position and size and
17588                // convert to viewport coordinates.
17589                var positionAndSize = this._getOverlayPositionAndSize(viewport);
17590                return viewport.viewerElementToViewportRectangle(new $.Rect(
17591                    positionAndSize.position.x,
17592                    positionAndSize.position.y,
17593                    positionAndSize.size.x,
17594                    positionAndSize.size.y));
17595            }
17596
17597            // NO_ROTATION case
17598            return bounds.rotate(-viewport.degrees,
17599                this._getPlacementPoint(bounds));
17600        }
17601    };
17602
17603}(OpenSeadragon));
17604
17605/*
17606 * OpenSeadragon - Drawer
17607 *
17608 * Copyright (C) 2009 CodePlex Foundation
17609 * Copyright (C) 2010-2013 OpenSeadragon contributors
17610 *
17611 * Redistribution and use in source and binary forms, with or without
17612 * modification, are permitted provided that the following conditions are
17613 * met:
17614 *
17615 * - Redistributions of source code must retain the above copyright notice,
17616 *   this list of conditions and the following disclaimer.
17617 *
17618 * - Redistributions in binary form must reproduce the above copyright
17619 *   notice, this list of conditions and the following disclaimer in the
17620 *   documentation and/or other materials provided with the distribution.
17621 *
17622 * - Neither the name of CodePlex Foundation nor the names of its
17623 *   contributors may be used to endorse or promote products derived from
17624 *   this software without specific prior written permission.
17625 *
17626 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17627 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17628 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17629 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
17630 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
17631 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
17632 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
17633 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
17634 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
17635 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
17636 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17637 */
17638
17639(function( $ ){
17640
17641/**
17642 * @class Drawer
17643 * @memberof OpenSeadragon
17644 * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
17645 * @param {Object} options - Options for this Drawer.
17646 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
17647 * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
17648 * @param {Element} options.element - Parent element.
17649 * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
17650 */
17651$.Drawer = function( options ) {
17652
17653    $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
17654
17655    //backward compatibility for positional args while preferring more
17656    //idiomatic javascript options object as the only argument
17657    var args  = arguments;
17658
17659    if( !$.isPlainObject( options ) ){
17660        options = {
17661            source:     args[ 0 ], // Reference to Viewer tile source.
17662            viewport:   args[ 1 ], // Reference to Viewer viewport.
17663            element:    args[ 2 ]  // Parent element.
17664        };
17665    }
17666
17667    $.console.assert( options.viewport, "[Drawer] options.viewport is required" );
17668    $.console.assert( options.element, "[Drawer] options.element is required" );
17669
17670    if ( options.source ) {
17671        $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
17672    }
17673
17674    this.viewer = options.viewer;
17675    this.viewport = options.viewport;
17676    this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
17677    if (options.opacity) {
17678        $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
17679    }
17680
17681    this.useCanvas  = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
17682    /**
17683     * The parent element of this Drawer instance, passed in when the Drawer was created.
17684     * The parent of {@link OpenSeadragon.Drawer#canvas}.
17685     * @member {Element} container
17686     * @memberof OpenSeadragon.Drawer#
17687     */
17688    this.container  = $.getElement( options.element );
17689    /**
17690     * A &lt;canvas&gt; element if the browser supports them, otherwise a &lt;div&gt; element.
17691     * Child element of {@link OpenSeadragon.Drawer#container}.
17692     * @member {Element} canvas
17693     * @memberof OpenSeadragon.Drawer#
17694     */
17695    this.canvas     = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
17696    /**
17697     * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a &lt;canvas&gt; element, otherwise null.
17698     * @member {Object} context
17699     * @memberof OpenSeadragon.Drawer#
17700     */
17701    this.context    = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
17702
17703    /**
17704     * Sketch canvas used to temporarily draw tiles which cannot be drawn directly
17705     * to the main canvas due to opacity. Lazily initialized.
17706     */
17707    this.sketchCanvas = null;
17708    this.sketchContext = null;
17709
17710    /**
17711     * @member {Element} element
17712     * @memberof OpenSeadragon.Drawer#
17713     * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
17714     */
17715    this.element    = this.container;
17716
17717    // We force our container to ltr because our drawing math doesn't work in rtl.
17718    // This issue only affects our canvas renderer, but we do it always for consistency.
17719    // Note that this means overlays you want to be rtl need to be explicitly set to rtl.
17720    this.container.dir = 'ltr';
17721
17722    // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
17723    if (this.useCanvas) {
17724        var viewportSize = this._calculateCanvasSize();
17725        this.canvas.width = viewportSize.x;
17726        this.canvas.height = viewportSize.y;
17727    }
17728
17729    this.canvas.style.width     = "100%";
17730    this.canvas.style.height    = "100%";
17731    this.canvas.style.position  = "absolute";
17732    $.setElementOpacity( this.canvas, this.opacity, true );
17733
17734    // explicit left-align
17735    this.container.style.textAlign = "left";
17736    this.container.appendChild( this.canvas );
17737
17738    // Image smoothing for canvas rendering (only if canvas is used).
17739    // Canvas default is "true", so this will only be changed if user specified "fal
17739se".
17740    this._imageSmoothingEnabled = true;
17741};
17742
17743/** @lends OpenSeadragon.Drawer.prototype */
17744$.Drawer.prototype = {
17745    // deprecated
17746    addOverlay: function( element, location, placement, onDraw ) {
17747        $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
17748        this.viewer.addOverlay( element, location, placement, onDraw );
17749        return this;
17750    },
17751
17752    // deprecated
17753    updateOverlay: function( element, location, placement ) {
17754        $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
17755        this.viewer.updateOverlay( element, location, placement );
17756        return this;
17757    },
17758
17759    // deprecated
17760    removeOverlay: function( element ) {
17761        $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
17762        this.viewer.removeOverlay( element );
17763        return this;
17764    },
17765
17766    // deprecated
17767    clearOverlays: function() {
17768        $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
17769        this.viewer.clearOverlays();
17770        return this;
17771    },
17772
17773    /**
17774     * This function converts the given point from to the drawer coordinate by
17775     * multiplying it with the pixel density.
17776     * This function does not take rotation into account, thus assuming provided
17777     * point is at 0 degree.
17778     * @param {OpenSeadragon.Point} point - the pixel point to convert
17779     */
17780    viewportCoordToDrawerCoord: function(point) {
17781        var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
17782        return new $.Point(
17783            vpPoint.x * $.pixelDensityRatio,
17784            vpPoint.y * $.pixelDensityRatio
17785        );
17786    },
17787
17788    /**
17789     * This function will create multiple polygon paths on the drawing context by provided polygons,
17790     * then clip the context to the paths.
17791     * @param {(OpenSeadragon.Point[])[]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
17792     * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
17793     */
17794    clipWithPolygons: function (polygons, useSketch) {
17795        if (!this.useCanvas) {
17796            return;
17797        }
17798        var context = this._getContext(useSketch);
17799        context.beginPath();
17800        polygons.forEach(function (polygon) {
17801            polygon.forEach(function (coord, i) {
17802                context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
17803          });
17804        });
17805        context.clip();
17806    },
17807
17808    /**
17809     * Set the opacity of the drawer.
17810     * @param {Number} opacity
17811     * @return {OpenSeadragon.Drawer} Chainable.
17812     */
17813    setOpacity: function( opacity ) {
17814        $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
17815        var world = this.viewer.world;
17816        for (var i = 0; i < world.getItemCount(); i++) {
17817            world.getItemAt( i ).setOpacity( opacity );
17818        }
17819        return this;
17820    },
17821
17822    /**
17823     * Get the opacity of the drawer.
17824     * @returns {Number}
17825     */
17826    getOpacity: function() {
17827        $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
17828        var world = this.viewer.world;
17829        var maxOpacity = 0;
17830        for (var i = 0; i < world.getItemCount(); i++) {
17831            var opacity = world.getItemAt( i ).getOpacity();
17832            if ( opacity > maxOpacity ) {
17833                maxOpacity = opacity;
17834            }
17835        }
17836        return maxOpacity;
17837    },
17838
17839    // deprecated
17840    needsUpdate: function() {
17841        $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
17842        return this.viewer.world.needsDraw();
17843    },
17844
17845    // deprecated
17846    numTilesLoaded: function() {
17847        $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
17848        return this.viewer.tileCache.numTilesLoaded();
17849    },
17850
17851    // deprecated
17852    reset: function() {
17853        $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
17854        this.viewer.world.resetItems();
17855        return this;
17856    },
17857
17858    // deprecated
17859    update: function() {
17860        $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
17861        this.clear();
17862        this.viewer.world.draw();
17863        return this;
17864    },
17865
17866    /**
17867     * @return {Boolean} True if rotation is supported.
17868     */
17869    canRotate: function() {
17870        return this.useCanvas;
17871    },
17872
17873    /**
17874     * Destroy the drawer (unload current loaded tiles)
17875     */
17876    destroy: function() {
17877        //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
17878        this.canvas.width  = 1;
17879        this.canvas.height = 1;
17880        this.sketchCanvas = null;
17881        this.sketchContext = null;
17882    },
17883
17884    /**
17885     * Clears the Drawer so it's ready to draw another frame.
17886     */
17887    clear: function() {
17888        this.canvas.innerHTML = "";
17889        if ( this.useCanvas ) {
17890            var viewportSize = this._calculateCanvasSize();
17891            if( this.canvas.width != viewportSize.x ||
17892                this.canvas.height != viewportSize.y ) {
17893                this.canvas.width = viewportSize.x;
17894                this.canvas.height = viewportSize.y;
17895                this._updateImageSmoothingEnabled(this.context);
17896                if ( this.sketchCanvas !== null ) {
17897                    var sketchCanvasSize = this._calculateSketchCanvasSize();
17898                    this.sketchCanvas.width = sketchCanvasSize.x;
17899                    this.sketchCanvas.height = sketchCanvasSize.y;
17900                    this._updateImageSmoothingEnabled(this.sketchContext);
17901                }
17902            }
17903            this._clear();
17904        }
17905    },
17906
17907    _clear: function (useSketch, bounds) {
17908        if (!this.useCanvas) {
17909            return;
17910        }
17911        var context = this._getContext(useSketch);
17912        if (bounds) {
17913            context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
17914        } else {
17915            var canvas = context.canvas;
17916            context.clearRect(0, 0, canvas.width, canvas.height);
17917        }
17918    },
17919
17920    /**
17921     * Scale from OpenSeadragon viewer rectangle to drawer rectangle
17922     * (ignoring rotation)
17923     * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
17924     * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
17925     */
17926    viewportToDrawerRectangle: function(rectangle) {
17927        var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
17928        var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
17929
17930        return new $.Rect(
17931            topLeft.x * $.pixelDensityRatio,
17932            topLeft.y * $.pixelDensityRatio,
17933            size.x * $.pixelDensityRatio,
17934            size.y * $.pixelDensityRatio
17935        );
17936    },
17937
17938    /**
17939     * Draws the given tile.
17940     * @param {OpenSeadragon.Tile} tile - The tile to draw.
17941     * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
17942     * drawingHandler({context, tile, rendered})
17943     * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
17944     * where <code>rendered</code> is the context with the pre-drawn image.
17945     * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
17946     * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
17947     */
17948    drawTile: function(tile, drawingHandler, useSketch, scale, translate) {
17949        $.console.assert(tile, '[Drawer.drawTile] tile is required');
17950        $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
17951
17952        if (this.useCanvas) {
17953            var context = this._getContext(useSketch);
17954            scale = scale || 1;
17955            tile.drawCanvas(context, drawingHandler, scale, translate);
17956        } else {
17957            tile.drawHTML( this.canvas );
17958        }
17959    },
17960
17961    _getContext: function( useSketch ) {
17962        var context = this.context;
17963        if ( useSketch ) {
17964            if (this.sketchCanvas === null) {
17965                this.sketchCanvas = document.createElement( "canvas" );
17966                var sketchCanvasSize = this._calculateSketchCanvasSize();
17967                this.sketchCanvas.width = sketchCanvasSize.x;
17968                this.sketchCanvas.height = sketchCanvasSize.y;
17969                this.sketchContext = this.sketchCanvas.getContext( "2d" );
17970
17971                // If the viewport is not currently rotated, the sketchCanvas
17972                // will have the same size as the main canvas. However, if
17973                // the viewport get rotated later on, we will need to resize it.
17974                if (this.viewport.getRotation() === 0) {
17975                    var self = this;
17976                    this.viewer.addHandler('rotate', function resizeSketchCanvas() {
17977                        if (self.viewport.getRotation() === 0) {
17978                            return;
17979                        }
17980                        self.viewer.removeHandler('rotate', resizeSketchCanvas);
17981                        var sketchCanvasSize = self._calculateSketchCanvasSize();
17982                        self.sketchCanvas.width = sketchCanvasSize.x;
17983                        self.sketchCanvas.height = sketchCanvasSize.y;
17984                    });
17985                }
17986                this._updateImageSmoothingEnabled(this.sketchContext);
17987            }
17988            context = this.sketchContext;
17989        }
17990        return context;
17991    },
17992
17993    // private
17994    saveContext: function( useSketch ) {
17995        if (!this.useCanvas) {
17996            return;
17997        }
17998
17999        this._getContext( useSketch ).save();
18000    },
18001
18002    // private
18003    restoreContext: function( useSketch ) {
18004        if (!this.useCanvas) {
18005            return;
18006        }
18007
18008        this._getContext( useSketch ).restore();
18009    },
18010
18011    // private
18012    setClip: function(rect, useSketch) {
18013        if (!this.useCanvas) {
18014            return;
18015        }
18016
18017        var context = this._getContext( useSketch );
18018        context.beginPath();
18019        context.rect(rect.x, rect.y, rect.width, rect.height);
18020        context.clip();
18021    },
18022
18023    // private
18024    drawRectangle: function(rect, fillStyle, useSketch) {
18025        if (!this.useCanvas) {
18026            return;
18027        }
18028
18029        var context = this._getContext( useSketch );
18030        context.save();
18031        context.fillStyle = fillStyle;
18032        context.fillRect(rect.x, rect.y, rect.width, rect.height);
18033        context.restore();
18034    },
18035
18036    /**
18037     * Blends the sketch canvas in the main canvas.
18038     * @param {Object} options The options
18039     * @param {Float} options.opacity The opacity of the blending.
18040     * @param {Float} [options.scale=1] The scale at which tiles were drawn on
18041     * the sketch. Default is 1.
18042     * Use scale to draw at a lower scale and then enlarge onto the main canvas.
18043     * @param {OpenSeadragon.Point} [options.translate] A translation vector
18044     * that was used to draw the tiles
18045     * @param {String} [options.compositeOperation] - How the image is
18046     * composited onto other images; see compositeOperation in
18047     * {@link OpenSeadragon.Options} for possible values.
18048     * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
18049     * canvas to blend in the main canvas. If specified, options.scale and
18050     * options.translate get ignored.
18051     */
18052    blendSketch: function(opacity, scale, translate, compositeOperation) {
18053        var options = opacity;
18054        if (!$.isPlainObject(options)) {
18055            options = {
18056                opacity: opacity,
18057                scale: scale,
18058                translate: translate,
18059                compositeOperation: compositeOperation
18060            };
18061        }
18062        if (!this.useCanvas || !this.sketchCanvas) {
18063            return;
18064        }
18065        opacity = options.opacity;
18066        compositeOperation = options.compositeOperation;
18067        var bounds = options.bounds;
18068
18069        this.context.save();
18070        this.context.globalAlpha = opacity;
18071        if (compositeOperation) {
18072            this.context.globalCompositeOperation = compositeOperation;
18073        }
18074        if (bounds) {
18075            // Internet Explorer, Microsoft Edge, and Safari have problems
18076            // when you call context.drawImage with negative x or y
18077            // or x + width or y + height greater than the canvas width or height respectively.
18078            if (bounds.x < 0) {
18079                bounds.width += bounds.x;
18080                bounds.x = 0;
18081            }
18082            if (bounds.x + bounds.width > this.canvas.width) {
18083                bounds.width = this.canvas.width - bounds.x;
18084            }
18085            if (bounds.y < 0) {
18086                bounds.height += bounds.y;
18087                bounds.y = 0;
18088            }
18089            if (bounds.y + bounds.height > this.canvas.height) {
18090                bounds.height = this.canvas.height - bounds.y;
18091            }
18092
18093            this.context.drawImage(
18094                this.sketchCanvas,
18095                bounds.x,
18096                bounds.y,
18097                bounds.width,
18098                bounds.height,
18099                bounds.x,
18100                bounds.y,
18101                bounds.width,
18102                bounds.height
18103            );
18104        } else {
18105            scale = options.scale || 1;
18106            translate = options.translate;
18107            var position = translate instanceof $.Point ?
18108                translate : new $.Point(0, 0);
18109
18110            var widthExt = 0;
18111            var heightExt = 0;
18112            if (translate) {
18113                var widthDiff = this.sketchCanvas.width - this.canvas.width;
18114                var heightDiff = this.sketchCanvas.height - this.canvas.height;
18115                widthExt = Math.round(widthDiff / 2);
18116                heightExt = Math.round(heightDiff / 2);
18117            }
18118            this.context.drawImage(
18119                this.sketchCanvas,
18120                position.x - widthExt * scale,
18121                position.y - heightExt * scale,
18122                (this.canvas.width + 2 * widthExt) * scale,
18123                (this.canvas.height + 2 * heightExt) * scale,
18124                -widthExt,
18125                -heightExt,
18126                this.canvas.width + 2 * widthExt,
18127                this.canvas.height + 2 * heightExt
18128            );
18129        }
18130        this.context.restore();
18131    },
18132
18133    // private
18134    drawDebugInfo: function(tile, count, i, tiledImage) {
18135        if ( !this.useCanvas ) {
18136            return;
18137        }
18138
18139        var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
18140        var context = this.context;
18141        context.save();
18142        context.lineWidth = 2 * $.pixelDensityRatio;
18143        context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
18144        context.strokeStyle = this.debugGridColor[colorIndex];
18145        context.fillStyle = this.debugGridColor[colorIndex];
18146
18147        if ( this.viewport.degrees !== 0 ) {
18148            this._offsetForRotation({degrees: this.viewport.degrees});
18149        }
18150        if (tiledImage.getRotation(true) % 360 !== 0) {
18151            this._offsetForRotation({
18152                degrees: tiledImage.getRotation(true),
18153                point: tiledImage.viewport.pixelFromPointNoRotate(
18154                    tiledImage._getRotationPoint(true), true)
18155            });
18156        }
18157        if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
18158          if(tiledImage._drawer.viewer.viewport.getFlip()) {
18159              tiledImage._drawer._flip();
18160          }
18161        }
18162
18163        context.strokeRect(
18164            tile.position.x * $.pixelDensityRatio,
18165            tile.position.y * $.pixelDensityRatio,
18166            tile.size.x * $.pixelDensityRatio,
18167            tile.size.y * $.pixelDensityRatio
18168        );
18169
18170        var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
18171        var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
18172
18173        // Rotate the text the right way around.
18174        context.translate( tileCenterX, tileCenterY );
18175        context.rotate( Math.PI / 180 * -this.viewport.degrees );
18176        context.translate( -tileCenterX, -tileCenterY );
18177
18178        if( tile.x === 0 && tile.y === 0 ){
18179            context.fillText(
18180                "Zoom: " + this.viewport.getZoom(),
18181                tile.position.x * $.pixelDensityRatio,
18182                (tile.position.y - 30) * $.pixelDensityRatio
18183            );
18184            context.fillText(
18185                "Pan: " + this.viewport.getBounds().toString(),
18186                tile.position.x * $.pixelDensityRatio,
18187                (tile.position.y - 20) * $.pixelDensityRatio
18188            );
18189        }
18190        context.fillText(
18191            "Level: " + tile.level,
18192            (tile.position.x + 10) * $.pixelDensityRatio,
18193            (tile.position.y + 20) * $.pixelDensityRatio
18194        );
18195        context.fillText(
18196            "Column: " + tile.x,
18197            (tile.position.x + 10) * $.pixelDensityRatio,
18198            (tile.position.y + 30) * $.pixelDensityRatio
18199        );
18200        context.fillText(
18201            "Row: " + tile.y,
18202            (tile.position.x + 10) * $.pixelDensityRatio,
18203            (tile.position.y + 40) * $.pixelDensityRatio
18204        );
18205        context.fillText(
18206            "Order: " + i + " of " + count,
18207            (tile.position.x + 10) * $.pixelDensityRatio,
18208            (tile.position.y + 50) * $.pixelDensityRatio
18209        );
18210        context.fillText(
18211            "Size: " + tile.size.toString(),
18212            (tile.position.x + 10) * $.pixelDensityRatio,
18213            (tile.position.y + 60) * $.pixelDensityRatio
18214        );
18215        context.fillText(
18216            "Position: " + tile.position.toString(),
18217            (tile.position.x + 10) * $.pixelDensityRatio,
18218            (tile.position.y + 70) * $.pixelDensityRatio
18219        );
18220
18221        if ( this.viewport.degrees !== 0 ) {
18222            this._restoreRotationChanges();
18223        }
18224        if (tiledImage.getRotation(true) % 360 !== 0) {
18225            this._restoreRotationChanges();
18226        }
18227
18228        if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
18229          if(tiledImage._drawer.viewer.viewport.getFlip()) {
18230              tiledImage._drawer._flip();
18231          }
18232        }
18233
18234        context.restore();
18235    },
18236
18237    // private
18238    debugRect: function(rect) {
18239        if ( this.useCanvas ) {
18240            var context = this.context;
18241            context.save();
18242            context.lineWidth = 2 * $.pixelDensityRatio;
18243            context.strokeStyle = this.debugGridColor[0];
18244            context.fillStyle = this.debugGridColor[0];
18245
18246            context.strokeRect(
18247                rect.x * $.pixelDensityRatio,
18248                rect.y * $.pixelDensityRatio,
18249                rect.width * $.pixelDensityRatio,
18250                rect.height * $.pixelDensityRatio
18251            );
18252
18253            context.restore();
18254        }
18255    },
18256
18257    /**
18258     * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
18259     *
18260     * @function
18261     * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
18262     * drawn smoothly on the canvas; see imageSmoothingEnabled in
18263     * {@link OpenSeadragon.Options} for more explanation.
18264     */
18265    setImageSmoothingEnabled: function(imageSmoothingEnabled){
18266        if ( this.useCanvas ) {
18267            this._imageSmoothingEnabled = imageSmoothingEnabled;
18268            this._updateImageSmoothingEnabled(this.context);
18269            this.viewer.forceRedraw();
18270        }
18271    },
18272
18273    // private
18274    _updateImageSmoothingEnabled: function(context){
18275        context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
18276        context.imageSmoothingEnabled = this._imageSmoothingEnabled;
18277    },
18278
18279    /**
18280     * Get the canvas size
18281     * @param {Boolean} sketch If set to true return the size of the sketch canvas
18282     * @returns {OpenSeadragon.Point} The size of the canvas
18283     */
18284    getCanvasSize: function(sketch) {
18285        var canvas = this._getContext(sketch).canvas;
18286        return new $.Point(canvas.width, canvas.height);
18287    },
18288
18289    getCanvasCenter: function() {
18290        return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
18291    },
18292
18293    // private
18294    _offsetForRotation: function(options) {
18295        var point = options.point ?
18296            options.point.times($.pixelDensityRatio) :
18297            this.getCanvasCenter();
18298
18299        var context = this._getContext(options.useSketch);
18300        context.save();
18301
18302        context.translate(point.x, point.y);
18303        if(this.viewer.viewport.flipped){
18304          context.rotate(Math.PI / 180 * -options.degrees);
18305          context.scale(-1, 1);
18306        } else{
18307          context.rotate(Math.PI / 180 * options.degrees);
18308        }
18309        context.translate(-point.x, -point.y);
18310    },
18311
18312    // private
18313    _flip: function(options) {
18314      options = options || {};
18315      var point = options.point ?
18316        options.point.times($.pixelDensityRatio) :
18317        this.getCanvasCenter();
18318      var context = this._getContext(options.useSketch);
18319
18320      context.translate(point.x, 0);
18321      context.scale(-1, 1);
18322      context.translate(-point.x, 0);
18323    },
18324
18325    // private
18326    _restoreRotationChanges: function(useSketch) {
18327        var context = this._getContext(useSketch);
18328        context.restore();
18329    },
18330
18331    // private
18332    _calculateCanvasSize: function() {
18333        var pixelDensityRatio = $.pixelDensityRatio;
18334        var viewportSize = this.viewport.getContainerSize();
18335        return {
18336            // canvas width and height are integers
18337            x: Math.round(viewportSize.x * pixelDensityRatio),
18338            y: Math.round(viewportSize.y * pixelDensityRatio)
18339        };
18340    },
18341
18342    // private
18343    _calculateSketchCanvasSize: function() {
18344        var canvasSize = this._calculateCanvasSize();
18345        if (this.viewport.getRotation() === 0) {
18346            return canvasSize;
18347        }
18348        // If the viewport is rotated, we need a larger sketch canvas in order
18349        // to support edge smoothing.
18350        var sketchCanvasSize = Math.ceil(Math.sqrt(
18351            canvasSize.x * canvasSize.x +
18352            canvasSize.y * canvasSize.y));
18353        return {
18354            x: sketchCanvasSize,
18355            y: sketchCanvasSize
18356        };
18357    }
18358};
18359
18360}( OpenSeadragon ));
18361
18362/*
18363 * OpenSeadragon - Viewport
18364 *
18365 * Copyright (C) 2009 CodePlex Foundation
18366 * Copyright (C) 2010-2013 OpenSeadragon contributors
18367 *
18368 * Redistribution and use in source and binary forms, with or without
18369 * modification, are permitted provided that the following conditions are
18370 * met:
18371 *
18372 * - Redistributions of source code must retain the above copyright notice,
18373 *   this list of conditions and the following disclaimer.
18374 *
18375 * - Redistributions in binary form must reproduce the above copyright
18376 *   notice, this list of conditions and the following disclaimer in the
18377 *   documentation and/or other materials provided with the distribution.
18378 *
18379 * - Neither the name of CodePlex Foundation nor the names of its
18380 *   contributors may be used to endorse or promote products derived from
18381 *   this software without specific prior written permission.
18382 *
18383 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18384 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18385 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18386 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
18387 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18388 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
18389 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
18390 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
18391 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
18392 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
18393 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18394 */
18395
18396(function( $ ){
18397
18398
18399/**
18400 * @class Viewport
18401 * @memberof OpenSeadragon
18402 * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)
18403 * for an {@link OpenSeadragon.Viewer}.
18404 * @param {Object} options - Options for this Viewport.
18405 * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.
18406 * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.
18407 * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.
18408 * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.
18409 * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.
18410 * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.
18411 * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.
18412 * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.
18413 * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.
18414 * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.
18415 * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.
18416 * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.
18417 * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.
18418 */
18419$.Viewport = function( options ) {
18420
18421    //backward compatibility for positional args while preferring more
18422    //idiomatic javascript options object as the only argument
18423    var args = arguments;
18424    if (args.length && args[0] instanceof $.Point) {
18425        options = {
18426            containerSize:  args[0],
18427            contentSize:    args[1],
18428            config:         args[2]
18429        };
18430    }
18431
18432    //options.config and the general config argument are deprecated
18433    //in favor of the more direct specification of optional settings
18434    //being passed directly on the options object
18435    if ( options.config ){
18436        $.extend( true, options, options.config );
18437        delete options.config;
18438    }
18439
18440    this._margins = $.extend({
18441        left: 0,
18442        top: 0,
18443        right: 0,
18444        bottom: 0
18445    }, options.margins || {});
18446
18447    delete options.margins;
18448
18449    $.extend( true, this, {
18450
18451        //required settings
18452        containerSize:      null,
18453        contentSize:        null,
18454
18455        //internal state properties
18456        zoomPoint:          null,
18457        viewer:           null,
18458
18459        //configurable options
18460        springStiffness:    $.DEFAULT_SETTINGS.springStiffness,
18461        animationTime:      $.DEFAULT_SETTINGS.animationTime,
18462        minZoomImageRatio:  $.DEFAULT_SETTINGS.minZoomImageRatio,
18463        maxZoomPixelRatio:  $.DEFAULT_SETTINGS.maxZoomPixelRatio,
18464        visibilityRatio:    $.DEFAULT_SETTINGS.visibilityRatio,
18465        wrapHorizontal:     $.DEFAULT_SETTINGS.wrapHorizontal,
18466        wrapVertical:       $.DEFAULT_SETTINGS.wrapVertical,
18467        defaultZoomLevel:   $.DEFAULT_SETTINGS.defaultZoomLevel,
18468        minZoomLevel:       $.DEFAULT_SETTINGS.minZoomLevel,
18469        maxZoomLevel:       $.DEFAULT_SETTINGS.maxZoomLevel,
18470        degrees:            $.DEFAULT_SETTINGS.degrees,
18471        flipped:            $.DEFAULT_SETTINGS.flipped,
18472        homeFillsViewer:    $.DEFAULT_SETTINGS.homeFillsViewer
18473
18474    }, options );
18475
18476    this._updateContainerInnerSize();
18477
18478    this.centerSpringX = new $.Spring({
18479        initial: 0,
18480        springStiffness: this.springStiffness,
18481        animationTime:   this.animationTime
18482    });
18483    this.centerSpringY = new $.Spring({
18484        initial: 0,
18485        springStiffness: this.springStiffness,
18486        animationTime:   this.animationTime
18487    });
18488    this.zoomSpring    = new $.Spring({
18489        exponential: true,
18490        initial: 1,
18491        springStiffness: this.springStiffness,
18492        animationTime:   this.animationTime
18493    });
18494
18495    this._oldCenterX = this.centerSpringX.current.value;
18496    this._oldCenterY = this.centerSpringY.current.value;
18497    this._oldZoom    = this.zoomSpring.current.value;
18498
18499    this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);
18500
18501    this.goHome(true);
18502    this.update();
18503};
18504
18505/** @lends OpenSeadragon.Viewport.prototype */
18506$.Viewport.prototype = {
18507    /**
18508     * Updates the viewport's home bounds and constraints for the given content size.
18509     * @function
18510     * @param {OpenSeadragon.Point} contentSize - size of the content in content units
18511     * @return {OpenSeadragon.Viewport} Chainable.
18512     * @fires OpenSeadragon.Viewer.event:reset-size
18513     */
18514    resetContentSize: function(contentSize) {
18515        $.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required");
18516        $.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point");
18517        $.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0");
18518        $.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0");
18519
18520        this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
18521        return this;
18522    },
18523
18524    // deprecated
18525    setHomeBounds: function(bounds, contentFactor) {
18526        $.console.error("[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually.");
18527        this._setContentBounds(bounds, contentFactor);
18528    },
18529
18530    // Set the viewport's content bounds
18531    // @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates
18532    // without rotation
18533    // @param {Number} contentFactor - how many content units per viewport unit
18534    // @fires OpenSeadragon.Viewer.event:reset-size
18535    // @private
18536    _setContentBounds: function(bounds, contentFactor) {
18537        $.console.assert(bounds, "[Viewport._setContentBounds] bounds is required");
18538        $.console.assert(bounds instanceof $.Rect, "[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect");
18539        $.console.assert(bounds.width > 0, "[Viewport._setContentBounds] bounds.width must be greater than 0");
18540        $.console.assert(bounds.height > 0, "[Viewport._setContentBounds] bounds.height must be greater than 0");
18541
18542        this._contentBoundsNoRotate = bounds.clone();
18543        this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(
18544            contentFactor);
18545
18546        this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();
18547        this._contentSize = this._contentBounds.getSize().times(contentFactor);
18548        this._contentAspectRatio = this._contentSize.x / this._contentSize.y;
18549
18550        if (this.viewer) {
18551            /**
18552             * Raised when the viewer's content size or home bounds are reset
18553             * (see {@link OpenSeadragon.Viewport#resetContentSize}).
18554             *
18555             * @event reset-size
18556             * @memberof OpenSeadragon.Viewer
18557             * @type {object}
18558             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
18559             * @property {OpenSeadragon.Point} contentSize
18560             * @property {OpenSeadragon.Rect} contentBounds - Content bounds.
18561             * @property {OpenSeadragon.Rect} homeBounds - Content bounds.
18562             * Deprecated use contentBounds instead.
18563             * @property {Number} contentFactor
18564             * @property {?Object} userData - Arbitrary subscriber-defined object.
18565             */
18566            this.viewer.raiseEvent('reset-size', {
18567                contentSize: this._contentSizeNoRotate.clone(),
18568                contentFactor: contentFactor,
18569                homeBounds: this._contentBoundsNoRotate.clone(),
18570                contentBounds: this._contentBounds.clone()
18571            });
18572        }
18573    },
18574
18575    /**
18576     * Returns the home zoom in "viewport zoom" value.
18577     * @function
18578     * @returns {Number} The home zoom in "viewport zoom".
18579     */
18580    getHomeZoom: function() {
18581        if (this.defaultZoomLevel) {
18582            return this.defaultZoomLevel;
18583        }
18584
18585        var aspectFactor = this._contentAspectRatio / this.getAspectRatio();
18586        var output;
18587        if (this.homeFillsViewer) { // fill the viewer and clip the image
18588            output = aspectFactor >= 1 ? aspectFactor : 1;
18589        } else {
18590            output = aspectFactor >= 1 ? 1 : aspectFactor;
18591        }
18592
18593        return output / this._contentBounds.width;
18594    },
18595
18596    /**
18597     * Returns the home bounds in viewport coordinates.
18598     * @function
18599     * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
18600     */
18601    getHomeBounds: function() {
18602        return this.getHomeBoundsNoRotate().rotate(-this.getRotation());
18603    },
18604
18605    /**
18606     * Returns the home bounds in viewport coordinates.
18607     * This method ignores the viewport rotation. Use
18608     * {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account.
18609     * @function
18610     * @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
18611     */
18612    getHomeBoundsNoRotate: function() {
18613        var center = this._contentBounds.getCenter();
18614        var width  = 1.0 / this.getHomeZoom();
18615        var height = width / this.getAspectRatio();
18616
18617        return new $.Rect(
18618            center.x - (width / 2.0),
18619            center.y - (height / 2.0),
18620            width,
18621            height
18622        );
18623    },
18624
18625    /**
18626     * @function
18627     * @param {Boolean} immediately
18628     * @fires OpenSeadragon.Viewer.event:home
18629     */
18630    goHome: function(immediately) {
18631        if (this.viewer) {
18632            /**
18633             * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).
18634             *
18635             * @event home
18636             * @memberof OpenSeadragon.Viewer
18637             * @type {object}
18638             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
18639             * @property {Boolean} immediately
vendor: 5,300 bytes, lines 18640-18797
18640             * @property {?Object} userData - Arbitrary subscriber-defined object.
18641             */
18642            this.viewer.raiseEvent('home', {
18643                immediately: immediately
18644            });
18645        }
18646        return this.fitBounds(this.getHomeBounds(), immediately);
18647    },
18648
18649    /**
18650     * @function
18651     */
18652    getMinZoom: function() {
18653        var homeZoom = this.getHomeZoom(),
18654            zoom = this.minZoomLevel ?
18655            this.minZoomLevel :
18656                this.minZoomImageRatio * homeZoom;
18657
18658        return zoom;
18659    },
18660
18661    /**
18662     * @function
18663     */
18664    getMaxZoom: function() {
18665        var zoom = this.maxZoomLevel;
18666        if (!zoom) {
18667            zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
18668            zoom /= this._contentBounds.width;
18669        }
18670
18671        return Math.max( zoom, this.getHomeZoom() );
18672    },
18673
18674    /**
18675     * @function
18676     */
18677    getAspectRatio: function() {
18678        return this._containerInnerSize.x / this._containerInnerSize.y;
18679    },
18680
18681    /**
18682     * @function
18683     * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.
18684     */
18685    getContainerSize: function() {
18686        return new $.Point(
18687            this.containerSize.x,
18688            this.containerSize.y
18689        );
18690    },
18691
18692    /**
18693     * The margins push the "home" region in from the sides by the specified amounts.
18694     * @function
18695     * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.
18696     */
18697    getMargins: function() {
18698        return $.extend({}, this._margins); // Make a copy so we are not returning our original
18699    },
18700
18701    /**
18702     * The margins push the "home" region in from the sides by the specified amounts.
18703     * @function
18704     * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.
18705     */
18706    setMargins: function(margins) {
18707        $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object');
18708
18709        this._margins = $.extend({
18710            left: 0,
18711            top: 0,
18712            right: 0,
18713            bottom: 0
18714        }, margins);
18715
18716        this._updateContainerInnerSize();
18717        if (this.viewer) {
18718            this.viewer.forceRedraw();
18719        }
18720    },
18721
18722    /**
18723     * Returns the bounds of the visible area in viewport coordinates.
18724     * @function
18725     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18726     * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
18727     */
18728    getBounds: function(current) {
18729        return this.getBoundsNoRotate(current).rotate(-this.getRotation());
18730    },
18731
18732    /**
18733     * Returns the bounds of the visible area in viewport coordinates.
18734     * This method ignores the viewport rotation. Use
18735     * {@link OpenSeadragon.Viewport#getBounds} to take it into account.
18736     * @function
18737     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18738     * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
18739     */
18740    getBoundsNoRotate: function(current) {
18741        var center = this.getCenter(current);
18742        var width  = 1.0 / this.getZoom(current);
18743        var height = width / this.getAspectRatio();
18744
18745        return new $.Rect(
18746            center.x - (width / 2.0),
18747            center.y - (height / 2.0),
18748            width,
18749            height
18750        );
18751    },
18752
18753    /**
18754     * @function
18755     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18756     * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
18757     * including the space taken by margins, in viewport coordinates.
18758     */
18759    getBoundsWithMargins: function(current) {
18760        return this.getBoundsNoRotateWithMargins(current).rotate(
18761            -this.getRotation(), this.getCenter(current));
18762    },
18763
18764    /**
18765     * @function
18766     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18767     * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
18768     * including the space taken by margins, in viewport coordinates.
18769     */
18770    getBoundsNoRotateWithMargins: function(current) {
18771        var bounds = this.getBoundsNoRotate(current);
18772        var factor = this._containerInnerSize.x * this.getZoom(current);
18773        bounds.x -= this._margins.left / factor;
18774        bounds.y -= this._margins.top / factor;
18775        bounds.width += (this._margins.left + this._margins.right) / factor;
18776        bounds.height += (this._margins.top + this._margins.bottom) / factor;
18777        return bounds;
18778    },
18779
18780    /**
18781     * @function
18782     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18783     */
18784    getCenter: function( current ) {
18785        var centerCurrent = new $.Point(
18786                this.centerSpringX.current.value,
18787                this.centerSpringY.current.value
18788            ),
18789            centerTarget = new $.Point(
18790                this.centerSpringX.target.value,
18791                this.centerSpringY.target.value
18792            ),
18793            oldZoomPixel,
18794            zoom,
18795            width,
18796            height,
18797            bounds,
18798            newZoomPixel,
18799            deltaZoomPixels,
18800            deltaZoomPoints;
18801
18802        if ( current ) {
18803            return centerCurrent;
18804        } else if ( !this.zoomPoint ) {
18805            return centerTarget;
18806        }
18807
18808        oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
18809
18810        zoom    = this.getZoom();
18811        width   = 1.0 / zoom;
18812        height  = width / this.getAspectRatio();
18813        bounds  = new $.Rect(
18814            centerCurrent.x - width / 2.0,
18815            centerCurrent.y - height / 2.0,
18816            width,
18817            height
18818        );
18819
18820        newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);
18821        deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
18822        deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );
18823
18824        return centerTarget.plus( deltaZoomPoints );
18825    },
18826
18827    /**
18828     * @function
18829     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
18830     */
18831    getZoom: function( current ) {
18832        if ( current ) {
18833            return this.zoomSpring.current.value;
18834        } else {
18835            return this.zoomSpring.target.value;
18836        }
18837    },
18838
18839    // private
18840    _applyZoomConstraints: function(zoom) {
18841        return Math.max(
18842            Math.min(zoom, this.getMaxZoom()),
18843            this.getMinZoom());
18844    },
18845
18846    /**
18847     * @function
18848     * @private
18849     * @param {OpenSeadragon.Rect} bounds
18850     * @return {OpenSeadragon.Rect} constrained bounds.
18851     */
18852    _applyBoundaryConstraints: function(bounds) {
18853        var newBounds = new $.Rect(
18854                bounds.x,
18855                bounds.y,
18856                bounds.width,
18857                bounds.height);
18858
18859        if (this.wrapHorizontal) {
18860            //do nothing
18861        } else {
18862            var horizontalThreshold = this.visibilityRatio * newBounds.width;
18863            var boundsRight = newBounds.x + newBounds.width;
18864            var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;
18865            var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;
18866            var rightDx = contentRight - newBounds.x - horizontalThreshold;
18867
18868            if (horizontalThreshold > this._contentBoundsNoRotate.width) {
18869                newBounds.x += (leftDx + rightDx) / 2;
18870            } else if (rightDx < 0) {
18871                newBounds.x += rightDx;
18872            } else if (leftDx > 0) {
18873                newBounds.x += leftDx;
18874            }
18875        }
18876
18877        if (this.wrapVertical) {
18878            //do nothing
18879        } else {
18880            var verticalThreshold   = this.visibilityRatio * newBounds.height;
18881            var boundsBottom = newBounds.y + newBounds.height;
18882            var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;
18883            var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;
18884            var bottomDy = contentBottom - newBounds.y - verticalThreshold;
18885
18886            if (verticalThreshold > this._contentBoundsNoRotate.height) {
18887                newBounds.y += (topDy + bottomDy) / 2;
18888            } else if (bottomDy < 0) {
18889                newBounds.y += bottomDy;
18890            } else if (topDy > 0) {
18891                newBounds.y += topDy;
18892            }
18893        }
18894
18895        return newBounds;
18896    },
18897
18898    /**
18899     * @function
18900     * @private
18901     * @param {Boolean} [immediately=false] - whether the function that triggered this event was
18902     * called with the "immediately" flag
18903     */
18904    _raiseConstraintsEvent: function(immediately) {
18905        if (this.viewer) {
18906            /**
18907             * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).
18908             *
18909             * @event constrain
18910             * @memberof OpenSeadragon.Viewer
18911             * @type {object}
18912             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
18913             * @property {Boolean} immediately - whether the function that triggered this event was
18914             * called with the "immediately" flag
18915             * @property {?Object} userData - Arbitrary subscriber-defined object.
18916             */
18917            this.viewer.raiseEvent( 'constrain', {
18918                immediately: immediately
18919            });
18920        }
18921    },
18922
18923    /**
18924     * Enforces the minZoom, maxZoom and visibilityRatio constraints by
18925     * zooming and panning to the closest acceptable zoom and location.
18926     * @function
18927     * @param {Boolean} [immediately=false]
18928     * @return {OpenSeadragon.Viewport} Chainable.
18929     * @fires OpenSeadragon.Viewer.event:constrain
18930     */
18931    applyConstraints: function(immediately) {
18932        var actualZoom = this.getZoom();
18933        var constrainedZoom = this._applyZoomConstraints(actualZoom);
18934
18935        if (actualZoom !== constrainedZoom) {
18936            this.zoomTo(constrainedZoom, this.zoomPoint, immediately);
18937        }
18938
18939        var bounds = this.getBoundsNoRotate();
18940        var constrainedBounds = this._applyBoundaryConstraints(bounds);
18941        this._raiseConstraintsEvent(immediately);
18942
18943        if (bounds.x !== constrainedBounds.x ||
18944            bounds.y !== constrainedBounds.y ||
18945            immediately) {
18946            this.fitBounds(
18947                constrainedBounds.rotate(-this.getRotation()),
18948                immediately);
18949        }
18950        return this;
18951    },
18952
18953    /**
18954     * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}
18955     * @function
18956     * @param {Boolean} [immediately=false]
18957     * @return {OpenSeadragon.Viewport} Chainable.
18958     * @fires OpenSeadragon.Viewer.event:constrain
18959     */
18960    ensureVisible: function(immediately) {
18961        return this.applyConstraints(immediately);
18962    },
18963
18964    /**
18965     * @function
18966     * @private
18967     * @param {OpenSeadragon.Rect} bounds
18968     * @param {Object} options (immediately=false, constraints=false)
18969     * @return {OpenSeadragon.Viewport} Chainable.
18970     */
18971    _fitBounds: function(bounds, options) {
18972        options = options || {};
18973        var immediately = options.immediately || false;
18974        var constraints = options.constraints || false;
18975
18976        var aspect = this.getAspectRatio();
18977        var center = bounds.getCenter();
18978
18979        // Compute width and height of bounding box.
18980        var newBounds = new $.Rect(
18981            bounds.x,
18982            bounds.y,
18983            bounds.width,
18984            bounds.height,
18985            bounds.degrees + this.getRotation())
18986            .getBoundingBox();
18987
18988        if (newBounds.getAspectRatio() >= aspect) {
18989            newBounds.height = newBounds.width / aspect;
18990        } else {
18991            newBounds.width = newBounds.height * aspect;
18992        }
18993
18994        // Compute x and y from width, height and center position
18995        newBounds.x = center.x - newBounds.width / 2;
18996        newBounds.y = center.y - newBounds.height / 2;
18997        var newZoom = 1.0 / newBounds.width;
18998
18999        if (constraints) {
19000            var newBoundsAspectRatio = newBounds.getAspectRatio();
19001            var newConstrainedZoom = this._applyZoomConstraints(newZoom);
19002
19003            if (newZoom !== newConstrainedZoom) {
19004                newZoom = newConstrainedZoom;
19005                newBounds.width = 1.0 / newZoom;
19006                newBounds.x = center.x - newBounds.width / 2;
19007                newBounds.height = newBounds.width / newBoundsAspectRatio;
19008                newBounds.y = center.y - newBounds.height / 2;
19009            }
19010
19011            newBounds = this._applyBoundaryConstraints(newBounds);
19012            center = newBounds.getCenter();
19013            this._raiseConstraintsEvent(immediately);
19014        }
19015
19016        if (immediately) {
19017            this.panTo(center, true);
19018            return this.zoomTo(newZoom, null, true);
19019        }
19020
19021        this.panTo(this.getCenter(true), true);
19022        this.zoomTo(this.getZoom(true), null, true);
19023
19024        var oldBounds = this.getBounds();
19025        var oldZoom   = this.getZoom();
19026
19027        if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
19028            this.zoomTo(newZoom, true);
19029            return this.panTo(center, immediately);
19030        }
19031
19032        newBounds = newBounds.rotate(-this.getRotation());
19033        var referencePoint = newBounds.getTopLeft().times(newZoom)
19034            .minus(oldBounds.getTopLeft().times(oldZoom))
19035            .divide(newZoom - oldZoom);
19036
19037        return this.zoomTo(newZoom, referencePoint, immediately);
19038    },
19039
19040    /**
19041     * Makes the viewport zoom and pan so that the specified boun
19041ds take
19042     * as much space as possible in the viewport.
19043     * Note: this method ignores the constraints (minZoom, maxZoom and
19044     * visibilityRatio).
19045     * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce
19046     * them.
19047     * @function
19048     * @param {OpenSeadragon.Rect} bounds
19049     * @param {Boolean} [immediately=false]
19050     * @return {OpenSeadragon.Viewport} Chainable.
19051     */
19052    fitBounds: function(bounds, immediately) {
19053        return this._fitBounds(bounds, {
19054            immediately: immediately,
19055            constraints: false
19056        });
19057    },
19058
19059    /**
19060     * Makes the viewport zoom and pan so that the specified bounds take
19061     * as much space as possible in the viewport while enforcing the constraints
19062     * (minZoom, maxZoom and visibilityRatio).
19063     * Note: because this method enforces the constraints, part of the
19064     * provided bounds may end up outside of the viewport.
19065     * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them.
19066     * @function
19067     * @param {OpenSeadragon.Rect} bounds
19068     * @param {Boolean} [immediately=false]
19069     * @return {OpenSeadragon.Viewport} Chainable.
19070     */
19071    fitBoundsWithConstraints: function(bounds, immediately) {
19072        return this._fitBounds(bounds, {
19073            immediately: immediately,
19074            constraints: true
19075        });
19076    },
19077
19078    /**
19079     * Zooms so the image just fills the viewer vertically.
19080     * @param {Boolean} immediately
19081     * @return {OpenSeadragon.Viewport} Chainable.
19082     */
19083    fitVertically: function(immediately) {
19084        var box = new $.Rect(
19085            this._contentBounds.x + (this._contentBounds.width / 2),
19086            this._contentBounds.y,
19087            0,
19088            this._contentBounds.height);
19089        return this.fitBounds(box, immediately);
19090    },
19091
19092    /**
19093     * Zooms so the image just fills the viewer horizontally.
19094     * @param {Boolean} immediately
19095     * @return {OpenSeadragon.Viewport} Chainable.
19096     */
19097    fitHorizontally: function(immediately) {
19098        var box = new $.Rect(
19099            this._contentBounds.x,
19100            this._contentBounds.y + (this._contentBounds.height / 2),
19101            this._contentBounds.width,
19102            0);
19103        return this.fitBounds(box, immediately);
19104    },
19105
19106
19107    /**
19108     * Returns bounds taking constraints into account
19109     * Added to improve constrained panning
19110     * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
19111     * @return {OpenSeadragon.Viewport} Chainable.
19112     */
19113    getConstrainedBounds: function(current) {
19114        var bounds,
19115            constrainedBounds;
19116
19117        bounds = this.getBounds(current);
19118
19119        constrainedBounds = this._applyBoundaryConstraints(bounds);
19120
19121        return constrainedBounds;
19122    },
19123
19124    /**
19125     * @function
19126     * @param {OpenSeadragon.Point} delta
19127     * @param {Boolean} immediately
19128     * @return {OpenSeadragon.Viewport} Chainable.
19129     * @fires OpenSeadragon.Viewer.event:pan
19130     */
19131    panBy: function( delta, immediately ) {
19132        var center = new $.Point(
19133            this.centerSpringX.target.value,
19134            this.centerSpringY.target.value
19135        );
19136        return this.panTo( center.plus( delta ), immediately );
19137    },
19138
19139    /**
19140     * @function
19141     * @param {OpenSeadragon.Point} center
19142     * @param {Boolean} immediately
19143     * @return {OpenSeadragon.Viewport} Chainable.
19144     * @fires OpenSeadragon.Viewer.event:pan
19145     */
19146    panTo: function( center, immediately ) {
19147        if ( immediately ) {
19148            this.centerSpringX.resetTo( center.x );
19149            this.centerSpringY.resetTo( center.y );
19150        } else {
19151            this.centerSpringX.springTo( center.x );
19152            this.centerSpringY.springTo( center.y );
19153        }
19154
19155        if( this.viewer ){
19156            /**
19157             * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).
19158             *
19159             * @event pan
19160             * @memberof OpenSeadragon.Viewer
19161             * @type {object}
19162             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
19163             * @property {OpenSeadragon.Point} center
19164             * @property {Boolean} immediately
19165             * @property {?Object} userData - Arbitrary subscriber-defined object.
19166             */
19167            this.viewer.raiseEvent( 'pan', {
19168                center: center,
19169                immediately: immediately
19170            });
19171        }
19172
19173        return this;
19174    },
19175
19176    /**
19177     * @function
19178     * @return {OpenSeadragon.Viewport} Chainable.
19179     * @fires OpenSeadragon.Viewer.event:zoom
19180     */
19181    zoomBy: function(factor, refPoint, immediately) {
19182        return this.zoomTo(
19183            this.zoomSpring.target.value * factor, refPoint, immediately);
19184    },
19185
19186    /**
19187     * Zooms to the specified zoom level
19188     * @function
19189     * @param {Number} zoom The zoom level to zoom to.
19190     * @param {OpenSeadragon.Point} [refPoint] The point which will stay at
19191     * the same screen location. Defaults to the viewport center.
19192     * @param {Boolean} [immediately=false]
19193     * @return {OpenSeadragon.Viewport} Chainable.
19194     * @fires OpenSeadragon.Viewer.event:zoom
19195     */
19196    zoomTo: function(zoom, refPoint, immediately) {
19197        var _this = this;
19198
19199        this.zoomPoint = refPoint instanceof $.Point &&
19200            !isNaN(refPoint.x) &&
19201            !isNaN(refPoint.y) ?
19202            refPoint :
19203            null;
19204
19205        if (immediately) {
19206            this._adjustCenterSpringsForZoomPoint(function() {
19207                _this.zoomSpring.resetTo(zoom);
19208            });
19209        } else {
19210            this.zoomSpring.springTo(zoom);
19211        }
19212
19213        if (this.viewer) {
19214            /**
19215             * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).
19216             *
19217             * @event zoom
19218             * @memberof OpenSeadragon.Viewer
19219             * @type {object}
19220             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
19221             * @property {Number} zoom
19222             * @property {OpenSeadragon.Point} refPoint
19223             * @property {Boolean} immediately
19224             * @property {?Object} userData - Arbitrary subscriber-defined object.
19225             */
19226            this.viewer.raiseEvent('zoom', {
19227                zoom: zoom,
19228                refPoint: refPoint,
19229                immediately: immediately
19230            });
19231        }
19232
19233        return this;
19234    },
19235
19236    /**
19237     * Rotates this viewport to the angle specified.
19238     * @function
19239     * @param {Number} degrees The degrees to set the rotation to.
19240     * @return {OpenSeadragon.Viewport} Chainable.
19241     */
19242    setRotation: function(degrees) {
19243        if (!this.viewer || !this.viewer.drawer.canRotate()) {
19244            return this;
19245        }
19246        this.degrees = $.positiveModulo(degrees, 360);
19247        this._setContentBounds(
19248            this.viewer.world.getHomeBounds(),
19249            this.viewer.world.getContentFactor());
19250        this.viewer.forceRedraw();
19251
19252        /**
19253         * Raised when rotation has been changed.
19254         *
19255         * @event rotate
19256         * @memberof OpenSeadragon.Viewer
19257         * @type {object}
19258         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
19259         * @property {Number} degrees - The number of degrees the rotation was set to.
19260         * @property {?Object} userData - Arbitrary subscriber-defined object.
19261         */
19262        this.viewer.raiseEvent('rotate', {"degrees": degrees});
19263        return this;
19264    },
19265
19266    /**
19267     * Gets the current rotation in degrees.
19268     * @function
19269     * @return {Number} The current rotation in degrees.
19270     */
19271    getRotation: function() {
19272        return this.degrees;
19273    },
19274
19275    /**
19276     * @function
19277     * @return {OpenSeadragon.Viewport} Chainable.
19278     * @fires OpenSeadragon.Viewer.event:resize
19279     */
19280    resize: function( newContainerSize, maintain ) {
19281        var oldBounds = this.getBoundsNoRotate(),
19282            newBounds = oldBounds,
19283            widthDeltaFactor;
19284
19285        this.containerSize.x = newContainerSize.x;
19286        this.containerSize.y = newContainerSize.y;
19287
19288        this._updateContainerInnerSize();
19289
19290        if ( maintain ) {
19291            // TODO: widthDeltaFactor will always be 1; probably not what's intended
19292            widthDeltaFactor = newContainerSize.x / this.containerSize.x;
19293            newBounds.width  = oldBounds.width * widthDeltaFactor;
19294            newBounds.height = newBounds.width / this.getAspectRatio();
19295        }
19296
19297        if( this.viewer ){
19298            /**
19299             * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
19300             *
19301             * @event resize
19302             * @memberof OpenSeadragon.Viewer
19303             * @type {object}
19304             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
19305             * @property {OpenSeadragon.Point} newContainerSize
19306             * @property {Boolean} maintain
vendor: 16,384 bytes, lines 19307-19706
19307             * @property {?Object} userData - Arbitrary subscriber-defined object.
19308             */
19309            this.viewer.raiseEvent( 'resize', {
19310                newContainerSize: newContainerSize,
19311                maintain: maintain
19312            });
19313        }
19314
19315        return this.fitBounds( newBounds, true );
19316    },
19317
19318    // private
19319    _updateContainerInnerSize: function() {
19320        this._containerInnerSize = new $.Point(
19321            Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),
19322            Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))
19323        );
19324    },
19325
19326    /**
19327     * Update the zoom and center (X and Y) springs.
19328     * @function
19329     * @returns {Boolean} True if any change has been made, false otherwise.
19330     */
19331    update: function() {
19332        var _this = this;
19333        this._adjustCenterSpringsForZoomPoint(function() {
19334            _this.zoomSpring.update();
19335        });
19336
19337        this.centerSpringX.update();
19338        this.centerSpringY.update();
19339
19340        var changed = this.centerSpringX.current.value !== this._oldCenterX ||
19341            this.centerSpringY.current.value !== this._oldCenterY ||
19342            this.zoomSpring.current.value !== this._oldZoom;
19343
19344        this._oldCenterX = this.centerSpringX.current.value;
19345        this._oldCenterY = this.centerSpringY.current.value;
19346        this._oldZoom    = this.zoomSpring.current.value;
19347
19348        return changed;
19349    },
19350
19351    _adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {
19352        if (this.zoomPoint) {
19353            var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
19354            zoomSpringHandler();
19355            var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
19356
19357            var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
19358            var deltaZoomPoints = this.deltaPointsFromPixels(
19359                deltaZoomPixels, true);
19360
19361            this.centerSpringX.shiftBy(deltaZoomPoints.x);
19362            this.centerSpringY.shiftBy(deltaZoomPoints.y);
19363
19364            if (this.zoomSpring.isAtTargetValue()) {
19365                this.zoomPoint = null;
19366            }
19367        } else {
19368            zoomSpringHandler();
19369        }
19370    },
19371
19372    /**
19373     * Convert a delta (translation vector) from viewport coordinates to pixels
19374     * coordinates. This method does not take rotation into account.
19375     * Consider using deltaPixelsFromPoints if you need to account for rotation.
19376     * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
19377     * @param {Boolean} [current=false] - Pass true for the current location;
19378     * defaults to false (target location).
19379     * @returns {OpenSeadragon.Point}
19380     */
19381    deltaPixelsFromPointsNoRotate: function(deltaPoints, current) {
19382        return deltaPoints.times(
19383            this._containerInnerSize.x * this.getZoom(current)
19384        );
19385    },
19386
19387    /**
19388     * Convert a delta (translation vector) from viewport coordinates to pixels
19389     * coordinates.
19390     * @param {OpenSeadragon.Point} deltaPoints - The translation vector to convert.
19391     * @param {Boolean} [current=false] - Pass true for the current location;
19392     * defaults to false (target location).
19393     * @returns {OpenSeadragon.Point}
19394     */
19395    deltaPixelsFromPoints: function(deltaPoints, current) {
19396        return this.deltaPixelsFromPointsNoRotate(
19397            deltaPoints.rotate(this.getRotation()),
19398            current);
19399    },
19400
19401    /**
19402     * Convert a delta (translation vector) from pixels coordinates to viewport
19403     * coordinates. This method does not take rotation into account.
19404     * Consider using deltaPointsFromPixels if you need to account for rotation.
19405     * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
19406     * @param {Boolean} [current=false] - Pass true for the current location;
19407     * defaults to false (target location).
19408     * @returns {OpenSeadragon.Point}
19409     */
19410    deltaPointsFromPixelsNoRotate: function(deltaPixels, current) {
19411        return deltaPixels.divide(
19412            this._containerInnerSize.x * this.getZoom(current)
19413        );
19414    },
19415
19416    /**
19417     * Convert a delta (translation vector) from pixels coordinates to viewport
19418     * coordinates.
19419     * @param {OpenSeadragon.Point} deltaPixels - The translation vector to convert.
19420     * @param {Boolean} [current=false] - Pass true for the current location;
19421     * defaults to false (target location).
19422     * @returns {OpenSeadragon.Point}
19423     */
19424    deltaPointsFromPixels: function(deltaPixels, current) {
19425        return this.deltaPointsFromPixelsNoRotate(deltaPixels, current)
19426            .rotate(-this.getRotation());
19427    },
19428
19429    /**
19430     * Convert viewport coordinates to pixels coordinates.
19431     * This method does not take rotation into account.
19432     * Consider using pixelFromPoint if you need to account for rotation.
19433     * @param {OpenSeadragon.Point} point the viewport coordinates
19434     * @param {Boolean} [current=false] - Pass true for the current location;
19435     * defaults to false (target location).
19436     * @returns {OpenSeadragon.Point}
19437     */
19438    pixelFromPointNoRotate: function(point, current) {
19439        return this._pixelFromPointNoRotate(
19440            point, this.getBoundsNoRotate(current));
19441    },
19442
19443    /**
19444     * Convert viewport coordinates to pixel coordinates.
19445     * @param {OpenSeadragon.Point} point the viewport coordinates
19446     * @param {Boolean} [current=false] - Pass true for the current location;
19447     * defaults to false (target location).
19448     * @returns {OpenSeadragon.Point}
19449     */
19450    pixelFromPoint: function(point, current) {
19451        return this._pixelFromPoint(point, this.getBoundsNoRotate(current));
19452    },
19453
19454    // private
19455    _pixelFromPointNoRotate: function(point, bounds) {
19456        return point.minus(
19457            bounds.getTopLeft()
19458        ).times(
19459            this._containerInnerSize.x / bounds.width
19460        ).plus(
19461            new $.Point(this._margins.left, this._margins.top)
19462        );
19463    },
19464
19465    // private
19466    _pixelFromPoint: function(point, bounds) {
19467        return this._pixelFromPointNoRotate(
19468            point.rotate(this.getRotation(), this.getCenter(true)),
19469            bounds);
19470    },
19471
19472    /**
19473     * Convert pixel coordinates to viewport coordinates.
19474     * This method does not take rotation into account.
19475     * Consider using pointFromPixel if you need to account for rotation.
19476     * @param {OpenSeadragon.Point} pixel Pixel coordinates
19477     * @param {Boolean} [current=false] - Pass true for the current location;
19478     * defaults to false (target location).
19479     * @returns {OpenSeadragon.Point}
19480     */
19481    pointFromPixelNoRotate: function(pixel, current) {
19482        var bounds = this.getBoundsNoRotate(current);
19483        return pixel.minus(
19484            new $.Point(this._margins.left, this._margins.top)
19485        ).divide(
19486            this._containerInnerSize.x / bounds.width
19487        ).plus(
19488            bounds.getTopLeft()
19489        );
19490    },
19491
19492    /**
19493     * Convert pixel coordinates to viewport coordinates.
19494     * @param {OpenSeadragon.Point} pixel Pixel coordinates
19495     * @param {Boolean} [current=false] - Pass true for the current location;
19496     * defaults to false (target location).
19497     * @returns {OpenSeadragon.Point}
19498     */
19499    pointFromPixel: function(pixel, current) {
19500        return this.pointFromPixelNoRotate(pixel, current).rotate(
19501            -this.getRotation(),
19502            this.getCenter(true)
19503        );
19504    },
19505
19506    // private
19507    _viewportToImageDelta: function( viewerX, viewerY ) {
19508        var scale = this._contentBoundsNoRotate.width;
19509        return new $.Point(
19510            viewerX * this._contentSizeNoRotate.x / scale,
19511            viewerY * this._contentSizeNoRotate.x / scale);
19512    },
19513
19514    /**
19515     * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
19516     * This method can be called either by passing X,Y coordinates or an
19517     * OpenSeadragon.Point
19518     * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.
19519     * @function
19520     * @param {(OpenSeadragon.Point|Number)} viewerX either a point or the X
19521     * coordinate in viewport coordinate system.
19522     * @param {Number} [viewerY] Y coordinate in viewport coordinate system.
19523     * @return {OpenSeadragon.Point} a point representing the coordinates in the image.
19524     */
19525    viewportToImageCoordinates: function(viewerX, viewerY) {
19526        if (viewerX instanceof $.Point) {
19527            //they passed a point instead of individual components
19528            return this.viewportToImageCoordinates(viewerX.x, viewerX.y);
19529        }
19530
19531        if (this.viewer) {
19532            var count = this.viewer.world.getItemCount();
19533            if (count > 1) {
19534                $.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' +
19535                    'with multi-image; use TiledImage.viewportToImageCoordinates instead.');
19536            } else if (count === 1) {
19537                // It is better to use TiledImage.viewportToImageCoordinates
19538                // because this._contentBoundsNoRotate can not be relied on
19539                // with clipping.
19540                var item = this.viewer.world.getItemAt(0);
19541                return item.viewportToImageCoordinates(viewerX, viewerY, true);
19542            }
19543        }
19544
19545        return this._viewportToImageDelta(
19546            viewerX - this._contentBoundsNoRotate.x,
19547            viewerY - this._contentBoundsNoRotate.y);
19548    },
19549
19550    // private
19551    _imageToViewportDelta: function( imageX, imageY ) {
19552        var scale = this._contentBoundsNoRotate.width;
19553        return new $.Point(
19554            imageX / this._contentSizeNoRotate.x * scale,
19555            imageY / this._contentSizeNoRotate.x * scale);
19556    },
19557
19558    /**
19559     * Translates from image coordinate system to OpenSeadragon viewer coordinate system
19560     * This method can be called either by passing X,Y coordinates or an
19561     * OpenSeadragon.Point
19562     * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.
19563     * @function
19564     * @param {(OpenSeadragon.Point | Number)} imageX the point or the
19565     * X coordinate in image coordinate system.
19566     * @param {Number} [imageY] Y coordinate in image coordinate system.
19567     * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.
19568     */
19569    imageToViewportCoordinates: function(imageX, imageY) {
19570        if (imageX instanceof $.Point) {
19571            //they passed a point instead of individual components
19572            return this.imageToViewportCoordinates(imageX.x, imageX.y);
19573        }
19574
19575        if (this.viewer) {
19576            var count = this.viewer.world.getItemCount();
19577            if (count > 1) {
19578                $.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' +
19579                    'with multi-image; use TiledImage.imageToViewportCoordinates instead.');
19580            } else if (count === 1) {
19581                // It is better to use TiledImage.viewportToImageCoordinates
19582                // because this._contentBoundsNoRotate can not be relied on
19583                // with clipping.
19584                var item = this.viewer.world.getItemAt(0);
19585                return item.imageToViewportCoordinates(imageX, imageY, true);
19586            }
19587        }
19588
19589        var point = this._imageToViewportDelta(imageX, imageY);
19590        point.x += this._contentBoundsNoRotate.x;
19591        point.y += this._contentBoundsNoRotate.y;
19592        return point;
19593    },
19594
19595    /**
19596     * Translates from a rectangle which describes a portion of the image in
19597     * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
19598     * This method can be called either by passing X,Y,width,height or an
19599     * OpenSeadragon.Rect
19600     * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.
19601     * @function
19602     * @param {(OpenSeadragon.Rect | Number)} imageX the rectangle or the X
19603     * coordinate of the top left corner of the rectangle in image coordinate system.
19604     * @param {Number} [imageY] the Y coordinate of the top left corner of the rectangle
19605     * in image coordinate system.
19606     * @param {Number} [pixelWidth] the width in pixel of the rectangle.
19607     * @param {Number} [pixelHeight] the height in pixel of the rectangle.
19608     * @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates
19609     */
19610    imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {
19611        var rect = imageX;
19612        if (!(rect instanceof $.Rect)) {
19613            //they passed individual components instead of a rectangle
19614            rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
19615        }
19616
19617        if (this.viewer) {
19618            var count = this.viewer.world.getItemCount();
19619            if (count > 1) {
19620                $.console.error('[Viewport.imageToViewportRectangle] is not accurate ' +
19621                    'with multi-image; use TiledImage.imageToViewportRectangle instead.');
19622            } else if (count === 1) {
19623                // It is better to use TiledImage.imageToViewportRectangle
19624                // because this._contentBoundsNoRotate can not be relied on
19625                // with clipping.
19626                var item = this.viewer.world.getItemAt(0);
19627                return item.imageToViewportRectangle(
19628                    imageX, imageY, pixelWidth, pixelHeight, true);
19629            }
19630        }
19631
19632        var coordA = this.imageToViewportCoordinates(rect.x, rect.y);
19633        var coordB = this._imageToViewportDelta(rect.width, rect.height);
19634        return new $.Rect(
19635            coordA.x,
19636            coordA.y,
19637            coordB.x,
19638            coordB.y,
19639            rect.degrees
19640        );
19641    },
19642
19643    /**
19644     * Translates from a rectangle which describes a portion of
19645     * the viewport in point coordinates to image rectangle coordinates.
19646     * This method can be called either by passing X,Y,width,height or an
19647     * OpenSeadragon.Rect
19648     * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.
19649     * @function
19650     * @param {(OpenSeadragon.Rect | Number)} viewerX either a rectangle or
19651     * the X coordinate of the top left corner of the rectangle in viewport
19652     * coordinate system.
19653     * @param {Number} [viewerY] the Y coordinate of the top left corner of the rectangle
19654     * in viewport coordinate system.
19655     * @param {Number} [pointWidth] the width of the rectangle in viewport coordinate system.
19656     * @param {Number} [pointHeight] the height of the rectangle in viewport coordinate system.
19657     */
19658    viewportToImageRectangle: function(viewerX, viewerY, pointWidth, pointHeight) {
19659        var rect = viewerX;
19660        if (!(rect instanceof $.Rect)) {
19661            //they passed individual components instead of a rectangle
19662            rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
19663        }
19664
19665        if (this.viewer) {
19666            var count = this.viewer.world.getItemCount();
19667            if (count > 1) {
19668                $.console.error('[Viewport.viewportToImageRectangle] is not accurate ' +
19669                    'with multi-image; use TiledImage.viewportToImageRectangle instead.');
19670            } else if (count === 1) {
19671                // It is better to use TiledImage.viewportToImageCoordinates
19672                // because this._contentBoundsNoRotate can not be relied on
19673                // with clipping.
19674                var item = this.viewer.world.getItemAt(0);
19675                return item.viewportToImageRectangle(
19676                    viewerX, viewerY, pointWidth, pointHeight, true);
19677            }
19678        }
19679
19680        var coordA = this.viewportToImageCoordinates(rect.x, rect.y);
19681        var coordB = this._viewportToImageDelta(rect.width, rect.height);
19682        return new $.Rect(
19683            coordA.x,
19684            coordA.y,
19685            coordB.x,
19686            coordB.y,
19687            rect.degrees
19688        );
19689    },
19690
19691    /**
19692     * Convert pixel coordinates relative to the viewer element to image
19693     * coordinates.
19694     * Note: not accurate with multi-image.
19695     * @param {OpenSeadragon.Point} pixel
19696     * @returns {OpenSeadragon.Point}
19697     */
19698    viewerElementToImageCoordinates: function( pixel ) {
19699        var point = this.pointFromPixel( pixel, true );
19700        return this.viewportToImageCoordinates( point );
19701    },
19702
19703    /**
19704     * Convert pixel coordinates relative to the image to
19705     * viewer element coordinates.
19706     * Note: not accurat
19706e with multi-image.
19707     * @param {OpenSeadragon.Point} pixel
19708     * @returns {OpenSeadragon.Point}
19709     */
19710    imageToViewerElementCoordinates: function( pixel ) {
19711        var point = this.imageToViewportCoordinates( pixel );
19712        return this.pixelFromPoint( point, true );
19713    },
19714
19715    /**
19716     * Convert pixel coordinates relative to the window to image coordinates.
19717     * Note: not accurate with multi-image.
19718     * @param {OpenSeadragon.Point} pixel
19719     * @returns {OpenSeadragon.Point}
19720     */
19721    windowToImageCoordinates: function(pixel) {
19722        $.console.assert(this.viewer,
19723            "[Viewport.windowToImageCoordinates] the viewport must have a viewer.");
19724        var viewerCoordinates = pixel.minus(
19725                $.getElementPosition(this.viewer.element));
19726        return this.viewerElementToImageCoordinates(viewerCoordinates);
19727    },
19728
19729    /**
19730     * Convert image coordinates to pixel coordinates relative to the window.
19731     * Note: not accurate with multi-image.
19732     * @param {OpenSeadragon.Point} pixel
19733     * @returns {OpenSeadragon.Point}
19734     */
19735    imageToWindowCoordinates: function(pixel) {
19736        $.console.assert(this.viewer,
19737            "[Viewport.imageToWindowCoordinates] the viewport must have a viewer.");
19738        var viewerCoordinates = this.imageToViewerElementCoordinates(pixel);
19739        return viewerCoordinates.plus(
19740                $.getElementPosition(this.viewer.element));
19741    },
19742
19743    /**
19744     * Convert pixel coordinates relative to the viewer element to viewport
19745     * coordinates.
19746     * @param {OpenSeadragon.Point} pixel
19747     * @returns {OpenSeadragon.Point}
19748     */
19749    viewerElementToViewportCoordinates: function( pixel ) {
19750        return this.pointFromPixel( pixel, true );
19751    },
19752
19753    /**
19754     * Convert viewport coordinates to pixel coordinates relative to the
19755     * viewer element.
19756     * @param {OpenSeadragon.Point} point
19757     * @returns {OpenSeadragon.Point}
19758     */
19759    viewportToViewerElementCoordinates: function( point ) {
19760        return this.pixelFromPoint( point, true );
19761    },
19762
19763    /**
19764     * Convert a rectangle in pixel coordinates relative to the viewer element
19765     * to viewport coordinates.
19766     * @param {OpenSeadragon.Rect} rectangle the rectangle to convert
19767     * @returns {OpenSeadragon.Rect} the converted rectangle
19768     */
19769    viewerElementToViewportRectangle: function(rectangle) {
19770        return $.Rect.fromSummits(
19771            this.pointFromPixel(rectangle.getTopLeft(), true),
19772            this.pointFromPixel(rectangle.getTopRight(), true),
19773            this.pointFromPixel(rectangle.getBottomLeft(), true)
19774        );
19775    },
19776
19777    /**
19778     * Convert a rectangle in viewport coordinates to pixel coordinates relative
19779     * to the viewer element.
19780     * @param {OpenSeadragon.Rect} rectangle the rectangle to convert
19781     * @returns {OpenSeadragon.Rect} the converted rectangle
19782     */
19783    viewportToViewerElementRectangle: function(rectangle) {
19784        return $.Rect.fromSummits(
19785            this.pixelFromPoint(rectangle.getTopLeft(), true),
19786            this.pixelFromPoint(rectangle.getTopRight(), true),
19787            this.pixelFromPoint(rectangle.getBottomLeft(), true)
19788        );
19789    },
19790
19791    /**
19792     * Convert pixel coordinates relative to the window to viewport coordinates.
19793     * @param {OpenSeadragon.Point} pixel
19794     * @returns {OpenSeadragon.Point}
19795     */
19796    windowToViewportCoordinates: function(pixel) {
19797        $.console.assert(this.viewer,
19798            "[Viewport.windowToViewportCoordinates] the viewport must have a viewer.");
19799        var viewerCoordinates = pixel.minus(
19800                $.getElementPosition(this.viewer.element));
19801        return this.viewerElementToViewportCoordinates(viewerCoordinates);
19802    },
19803
19804    /**
19805     * Convert viewport coordinates to pixel coordinates relative to the window.
19806     * @param {OpenSeadragon.Point} point
19807     * @returns {OpenSeadragon.Point}
19808     */
19809    viewportToWindowCoordinates: function(point) {
19810        $.console.assert(this.viewer,
19811            "[Viewport.viewportToWindowCoordinates] the viewport must have a viewer.");
19812        var viewerCoordinates = this.viewportToViewerElementCoordinates(point);
19813        return viewerCoordinates.plus(
19814                $.getElementPosition(this.viewer.element));
19815    },
19816
19817    /**
19818     * Convert a viewport zoom to an image zoom.
19819     * Image zoom: ratio of the original image size to displayed image size.
19820     * 1 means original image size, 0.5 half size...
19821     * Viewport zoom: ratio of the displayed image's width to viewport'
19821s width.
19822     * 1 means identical width, 2 means image's width is twice the viewport's width...
19823     * Note: not accurate with multi-image.
19824     * @function
19825     * @param {Number} viewportZoom The viewport zoom
19826     * target zoom.
19827     * @returns {Number} imageZoom The image zoom
19828     */
19829    viewportToImageZoom: function(viewportZoom) {
19830        if (this.viewer) {
19831            var count = this.viewer.world.getItemCount();
19832            if (count > 1) {
19833                $.console.error('[Viewport.viewportToImageZoom] is not ' +
19834                    'accurate with multi-image.');
19835            } else if (count === 1) {
19836                // It is better to use TiledImage.viewportToImageZoom
19837                // because this._contentBoundsNoRotate can not be relied on
19838                // with clipping.
19839                var item = this.viewer.world.getItemAt(0);
19840                return item.viewportToImageZoom(viewportZoom);
19841            }
19842        }
19843
19844        var imageWidth = this._contentSizeNoRotate.x;
19845        var containerWidth = this._containerInnerSize.x;
19846        var scale = this._contentBoundsNoRotate.width;
19847        var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;
19848        return viewportZoom * viewportToImageZoomRatio;
19849    },
19850
19851    /**
19852     * Convert an image zoom to a viewport zoom.
19853     * Image zoom: ratio of the original image size to displayed image size.
19854     * 1 means original image size, 0.5 half size...
19855     * Viewport zoom: ratio of the displayed image's width to viewport's width.
19856     * 1 means identical width, 2 means image's width is twice the viewport's width...
19857     * Note: not accurate with multi-image.
19858     * @function
19859     * @param {Number} imageZoom The image zoom
19860     * target zoom.
19861     * @returns {Number} viewportZoom The viewport zoom
19862     */
19863    imageToViewportZoom: function(imageZoom) {
19864        if (this.viewer) {
19865            var count = this.viewer.world.getItemCount();
19866            if (count > 1) {
19867                $.console.error('[Viewport.imageToViewportZoom] is not accurate ' +
19868                    'with multi-image.');
19869            } else if (count === 1) {
19870                // It is better to use TiledImage.imageToViewportZoom
19871                // because this._contentBoundsNoRotate can not be relied on
19872                // with clipping.
19873                var item = this.viewer.world.getItemAt(0);
19874                return item.imageToViewportZoom(imageZoom);
19875            }
19876        }
19877
19878        var imageWidth = this._contentSizeNoRotate.x;
19879        var containerWidth = this._containerInnerSize.x;
19880        var scale = this._contentBoundsNoRotate.width;
19881        var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
19882        return imageZoom * viewportToImageZoomRatio;
19883    },
19884
19885    /**
19886     * Toggles flip state and demands a new drawing on navigator and viewer objects.
19887     * @function
19888     * @return {OpenSeadragon.Viewport} Chainable.
19889     */
19890    toggleFlip: function() {
19891      this.setFlip(!this.getFlip());
19892      return this;
19893    },
19894
19895    /**
19896     * Get flip state stored on viewport.
19897     * @function
19898     * @return {Boolean} Flip state.
19899     */
19900    getFlip: function() {
19901      return this.flipped;
19902    },
19903
19904    /**
19905     * Sets flip state according to the state input argument.
19906     * @function
19907     * @param {Boolean} state - Flip state to set.
19908     * @return {OpenSeadragon.Viewport} Chainable.
19909     */
19910    setFlip: function( state ) {
19911      if ( this.flipped === state ) {
19912        return this;
19913      }
19914
19915      this.flipped = state;
19916      if(this.viewer.navigator){
19917        this.viewer.navigator.setFlip(this.getFlip());
19918      }
19919      this.viewer.forceRedraw();
19920
19921      /**
19922       * Raised when flip state has been changed.
19923       *
19924       * @event flip
19925       * @memberof OpenSeadragon.Viewer
19926       * @type {object}
19927       * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
19928       * @property {Number} flipped - The flip state after this change.
19929       * @property {?Object} userData - Arbitrary subscriber-defined object.
19930       */
19931      this.viewer.raiseEvent('flip', {"flipped": state});
19932      return this;
19933    }
19934
19935};
19936
19937}( OpenSeadragon ));
19938
19939/*
19940 * OpenSeadragon - TiledImage
19941 *
19942 * Copyright (C) 2009 CodePlex Foundation
19943 * Copyright (C) 2010-2013 OpenSeadragon contributors
19944 *
19945 * Redistribution and use in source and binary forms, with or without
19946 * modification, are permitted provided that the following conditions are
19947 * met:
19948 *
19949 * - Redistributions of source code must retain the above copyright notice,
19950 *   this list of conditions and the following disclaimer.
19951 *
19952 * - Redistributions in binary form must reproduce the above copyright
19953 *   notice, this list of conditions and the following disclaimer in the
19954 *   documentation and/or other materials provided with the distribution.
19955 *
19956 * - Neither the name of CodePlex Foundation nor the names of its
19957 *   contributors may be used to endorse or promote products derived from
19958 *   this software without specific prior written permission.
19959 *
19960 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19961 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19962 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19963 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
19964 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19965 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
19966 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19967 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
19968 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
19969 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
19970 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19971 */
19972
19973(function( $ ){
19974
19975/**
19976 * You shouldn't have to create a TiledImage instance directly; get it asynchronously by
19977 * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead.
19978 * @class TiledImage
19979 * @memberof OpenSeadragon
19980 * @extends OpenSeadragon.EventSource
19981 * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
19982 * A new instance is created for each TileSource opened.
19983 * @param {Object} options - Configuration for this TiledImage.
19984 * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage.
19985 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage.
19986 * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use.
19987 * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto.
19988 * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use.
19989 * @param {Number} [options.x=0] - Left position, in viewport coordinates.
19990 * @param {Number} [options.y=0] - Top position, in viewport coordinates.
19991 * @param {Number} [options.width=1] - Width, in viewport coordinates.
19992 * @param {Number} [options.height] - Height, in viewport coordinates.
19993 * @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
19994 * to fit the image into. If specified, x, y, width and height get ignored.
19995 * @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
19996 * How to anchor the image in the bounds if options.fitBounds is set.
19997 * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
19998 * (portions of the image outside of this area will not be visible). Only works on
19999 * browsers that support the HTML5 canvas.
20000 * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}.
20001 * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}.
20002 * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}.
20003 * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}.
20004 * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}.
20005 * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}.
20006 * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
20007 * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
20008 * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
20009 * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
20010 * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.
20011 * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.
20012 * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.
20013 * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.
20014 * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
20015 * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
20016 * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
20017 * @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.
20018 * @param {Boolean} [options.loadTilesWithAjax]
20019 *      Whether to load tile data using AJAX requests.
20020 *      Defaults to the setting in {@link OpenSeadragon.Options}.
20021 * @param {Object} [options.ajaxHeaders={}]
20022 *      A set of headers to include when making tile AJAX requests.
20023 */
20024$.TiledImage = function( options ) {
20025    var _this = this;
20026    /**
20027     * The {@link OpenSeadragon.TileSource} that defines this TiledImage.
20028     * @member {OpenSeadragon.TileSource} source
20029     * @memberof OpenSeadragon.TiledImage#
20030     */
20031    $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" );
20032    $.console.assert( options.drawer, "[TiledImage] options.drawer is required" );
20033    $.console.assert( options.viewer, "[TiledImage] options.viewer is required" );
20034    $.console.assert( options.imageLoader, "[TiledImage] options.imageLoader is required" );
20035    $.console.assert( options.source, "[TiledImage] options.source is required" );
20036    $.console.assert(!options.clip || options.clip instanceof $.Rect,
20037        "[TiledImage] options.clip must be an OpenSeadragon.Rect if present");
20038
20039    $.EventSource.call( this );
20040
20041    this._tileCache = options.tileCache;
20042    delete options.tileCache;
20043
20044    this._drawer = options.drawer;
20045    delete options.drawer;
20046
20047    this._imageLoader = options.imageLoader;
20048    delete options.imageLoader;
20049
20050    if (options.clip instanceof $.Rect) {
20051        this._clip = options.clip.clone();
20052    }
20053
20054    delete options.clip;
20055
20056    var x = options.x || 0;
20057    delete options.x;
20058    var y = options.y || 0;
20059    delete options.y;
20060
20061    // Ratio of zoomable image height to width.
20062    this.normHeight = options.source.dimensions.y / options.source.dimensions.x;
20063    this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y;
20064
20065    var scale = 1;
20066    if ( options.width ) {
20067        scale = options.width;
20068        delete options.width;
20069
20070        if ( options.height ) {
20071            $.console.error( "specifying both width and height to a tiledImage is not supported" );
20072            delete options.height;
20073        }
20074    } else if ( options.height ) {
20075        scale = options.height / this.normHeight;
20076        delete options.height;
20077    }
20078
20079    var fitBounds = options.fitBounds;
20080    delete options.fitBounds;
20081    var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;
20082    delete options.fitBoundsPlacement;
20083
20084    var degrees = options.degrees || 0;
20085    delete options.degrees;
20086
20087    $.extend( true, this, {
20088
20089        //internal state properties
20090        viewer:         null,
20091        tilesMatrix:    {},    // A '3d' dictionary [level][x][y] --> Tile.
20092        coverage:       {},    // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.
20093        loadingCoverage: {},   // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
20094        lastDrawn:      [],    // An unordered list of Tiles drawn last frame.
20095        lastResetTime:  0,     // Last time for which the tiledImage was reset.
20096        _midDraw:       false, // Is the tiledImage currently updating the viewport?
20097        _needsDraw:     true,  // Does the tiledImage need to update the viewport again?
20098        _hasOpaqueTile: false,  // Do we have even one fully opaque tile?
20099        _tilesLoading:  0,     // The number of pending tile requests.
20100        //configurable settings
20101        springStiffness:        $.DEFAULT_SETTINGS.springStiffness,
20102        animationTime:          $.DEFAULT_SETTINGS.animationTime,
vendor: 17,158 bytes, lines 20103-20532
20103        minZoomImageRatio:      $.DEFAULT_SETTINGS.minZoomImageRatio,
20104        wrapHorizontal:         $.DEFAULT_SETTINGS.wrapHorizontal,
20105        wrapVertical:           $.DEFAULT_SETTINGS.wrapVertical,
20106        immediateRender:        $.DEFAULT_SETTINGS.immediateRender,
20107        blendTime:              $.DEFAULT_SETTINGS.blendTime,
20108        alwaysBlend:            $.DEFAULT_SETTINGS.alwaysBlend,
20109        minPixelRatio:          $.DEFAULT_SETTINGS.minPixelRatio,
20110        smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
20111        iOSDevice:              $.DEFAULT_SETTINGS.iOSDevice,
20112        debugMode:              $.DEFAULT_SETTINGS.debugMode,
20113        crossOriginPolicy:      $.DEFAULT_SETTINGS.crossOriginPolicy,
20114        ajaxWithCredentials:    $.DEFAULT_SETTINGS.ajaxWithCredentials,
20115        placeholderFillStyle:   $.DEFAULT_SETTINGS.placeholderFillStyle,
20116        opacity:                $.DEFAULT_SETTINGS.opacity,
20117        preload:                $.DEFAULT_SETTINGS.preload,
20118        compositeOperation:     $.DEFAULT_SETTINGS.compositeOperation
20119    }, options );
20120
20121    this._preload = this.preload;
20122    delete this.preload;
20123
20124    this._fullyLoaded = false;
20125
20126    this._xSpring = new $.Spring({
20127        initial: x,
20128        springStiffness: this.springStiffness,
20129        animationTime: this.animationTime
20130    });
20131
20132    this._ySpring = new $.Spring({
20133        initial: y,
20134        springStiffness: this.springStiffness,
20135        animationTime: this.animationTime
20136    });
20137
20138    this._scaleSpring = new $.Spring({
20139        initial: scale,
20140        springStiffness: this.springStiffness,
20141        animationTime: this.animationTime
20142    });
20143
20144    this._degreesSpring = new $.Spring({
20145        initial: degrees,
20146        springStiffness: this.springStiffness,
20147        animationTime: this.animationTime
20148    });
20149
20150    this._updateForScale();
20151
20152    if (fitBounds) {
20153        this.fitBounds(fitBounds, fitBoundsPlacement, true);
20154    }
20155
20156    // We need a callback to give image manipulation a chance to happen
20157    this._drawingHandler = function(args) {
20158      /**
20159       * This event is fired just before the tile is drawn giving the application a chance to alter the image.
20160       *
20161       * NOTE: This event is only fired when the drawer is using a &lt;canvas&gt;.
20162       *
20163       * @event tile-drawing
20164       * @memberof OpenSeadragon.Viewer
20165       * @type {object}
20166       * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
20167       * @property {OpenSeadragon.Tile} tile - The Tile being drawn.
20168       * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
20169       * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into.
20170       * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery.
20171       * @property {?Object} userData - Arbitrary subscriber-defined object.
20172       */
20173        _this.viewer.raiseEvent('tile-drawing', $.extend({
20174            tiledImage: _this
20175        }, args));
20176    };
20177};
20178
20179$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{
20180    /**
20181     * @returns {Boolean} Whether the TiledImage needs to be drawn.
20182     */
20183    needsDraw: function() {
20184        return this._needsDraw;
20185    },
20186
20187    /**
20188     * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.
20189     */
20190    getFullyLoaded: function() {
20191        return this._fullyLoaded;
20192    },
20193
20194    // private
20195    _setFullyLoaded: function(flag) {
20196        if (flag === this._fullyLoaded) {
20197            return;
20198        }
20199
20200        this._fullyLoaded = flag;
20201
20202        /**
20203         * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage
20204         * to draw at the current view have been loaded) changes.
20205         *
20206         * @event fully-loaded-change
20207         * @memberof OpenSeadragon.TiledImage
20208         * @type {object}
20209         * @property {Boolean} fullyLoaded - The new "fully loaded" value.
20210         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
20211         * @property {?Object} userData - Arbitrary subscriber-defined object.
20212         */
20213        this.raiseEvent('fully-loaded-change', {
20214            fullyLoaded: this._fullyLoaded
20215        });
20216    },
20217
20218    /**
20219     * Clears all tiles and triggers an update on the next call to
20220     * {@link OpenSeadragon.TiledImage#update}.
20221     */
20222    reset: function() {
20223        this._tileCache.clearTilesFor(this);
20224        this.lastResetTime = $.now();
20225        this._needsDraw = true;
20226    },
20227
20228    /**
20229     * Updates the TiledImage's bounds, animating if needed.
20230     * @returns {Boolean} Whether the TiledImage animated.
20231     */
20232    update: function() {
20233        var xUpdated = this._xSpring.update();
20234        var yUpdated = this._ySpring.update();
20235        var scaleUpdated = this._scaleSpring.update();
20236        var degreesUpdated = this._degreesSpring.update();
20237
20238        if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
20239            this._updateForScale();
20240            this._needsDraw = true;
20241            return true;
20242        }
20243
20244        return false;
20245    },
20246
20247    /**
20248     * Draws the TiledImage to its Drawer.
20249     */
20250    draw: function() {
20251        if (this.opacity !== 0 || this._preload) {
20252            this._midDraw = true;
20253            this._updateViewport();
20254            this._midDraw = false;
20255        }
20256        // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
20257        else {
20258            this._needsDraw = false;
20259        }
20260    },
20261
20262    /**
20263     * Destroy the TiledImage (unload current loaded tiles).
20264     */
20265    destroy: function() {
20266        this.reset();
20267    },
20268
20269    /**
20270     * Get this TiledImage's bounds in viewport coordinates.
20271     * @param {Boolean} [current=false] - Pass true for the current location;
20272     * false for target location.
20273     * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
20274     */
20275    getBounds: function(current) {
20276        return this.getBoundsNoRotate(current)
20277            .rotate(this.getRotation(current), this._getRotationPoint(current));
20278    },
20279
20280    /**
20281     * Get this TiledImage's bounds in viewport coordinates without taking
20282     * rotation into account.
20283     * @param {Boolean} [current=false] - Pass true for the current location;
20284     * false for target location.
20285     * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates.
20286     */
20287    getBoundsNoRotate: function(current) {
20288        return current ?
20289            new $.Rect(
20290                this._xSpring.current.value,
20291                this._ySpring.current.value,
20292                this._worldWidthCurrent,
20293                this._worldHeightCurrent) :
20294            new $.Rect(
20295                this._xSpring.target.value,
20296                this._ySpring.target.value,
20297                this._worldWidthTarget,
20298                this._worldHeightTarget);
20299    },
20300
20301    // deprecated
20302    getWorldBounds: function() {
20303        $.console.error('[TiledImage.getWorldBounds] is deprecated; use TiledImage.getBounds instead');
20304        return this.getBounds();
20305    },
20306
20307    /**
20308     * Get the bounds of the displayed part of the tiled image.
20309     * @param {Boolean} [current=false] Pass true for the current location,
20310     * false for the target location.
20311     * @returns {$.Rect} The clipped bounds in viewport coordinates.
20312     */
20313    getClippedBounds: function(current) {
20314        var bounds = this.getBoundsNoRotate(current);
20315        if (this._clip) {
20316            var worldWidth = current ?
20317                this._worldWidthCurrent : this._worldWidthTarget;
20318            var ratio = worldWidth / this.source.dimensions.x;
20319            var clip = this._clip.times(ratio);
20320            bounds = new $.Rect(
20321                bounds.x + clip.x,
20322                bounds.y + clip.y,
20323                clip.width,
20324                clip.height);
20325        }
20326        return bounds.rotate(this.getRotation(current), this._getRotationPoint(current));
20327    },
20328
20329    /**
20330     * @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.
20331     */
20332    getContentSize: function() {
20333        return new $.Point(this.source.dimensions.x, this.source.dimensions.y);
20334    },
20335
20336    // private
20337    _viewportToImageDelta: function( viewerX, viewerY, current ) {
20338        var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);
20339        return new $.Point(viewerX * (this.source.dimensions.x / scale),
20340            viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale));
20341    },
20342
20343    /**
20344     * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
20345     * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.
20346     * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system.
20347     * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system.
20348     * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
20349     * @return {OpenSeadragon.Point} A point representing the coordinates in the image.
20350     */
20351    viewportToImageCoordinates: function(viewerX, viewerY, current) {
20352        var point;
20353        if (viewerX instanceof $.Point) {
20354            //they passed a point instead of individual components
20355            current = viewerY;
20356            point = viewerX;
20357        } else {
20358            point = new $.Point(viewerX, viewerY);
20359        }
20360
20361        point = point.rotate(-this.getRotation(current), this._getRotationPoint(current));
20362        return current ?
20363            this._viewportToImageDelta(
20364                point.x - this._xSpring.current.value,
20365                point.y - this._ySpring.current.value) :
20366            this._viewportToImageDelta(
20367                point.x - this._xSpring.target.value,
20368                point.y - this._ySpring.target.value);
20369    },
20370
20371    // private
20372    _imageToViewportDelta: function( imageX, imageY, current ) {
20373        var scale = (current ? this._scaleSpring.current.value : this._scaleSpring.target.value);
20374        return new $.Point((imageX / this.source.dimensions.x) * scale,
20375            (imageY / this.source.dimensions.y / this.contentAspectX) * scale);
20376    },
20377
20378    /**
20379     * Translates from image coordinate system to OpenSeadragon viewer coordinate system
20380     * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}.
20381     * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system.
20382     * @param {Number} [imageY] - The Y coordinate in image coordinate system.
20383     * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
20384     * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport.
20385     */
20386    imageToViewportCoordinates: function(imageX, imageY, current) {
20387        if (imageX instanceof $.Point) {
20388            //they passed a point instead of individual components
20389            current = imageY;
20390            imageY = imageX.y;
20391            imageX = imageX.x;
20392        }
20393
20394        var point = this._imageToViewportDelta(imageX, imageY);
20395        if (current) {
20396            point.x += this._xSpring.current.value;
20397            point.y += this._ySpring.current.value;
20398        } else {
20399            point.x += this._xSpring.target.value;
20400            point.y += this._ySpring.target.value;
20401        }
20402
20403        return point.rotate(this.getRotation(current), this._getRotationPoint(current));
20404    },
20405
20406    /**
20407     * Translates from a rectangle which describes a portion of the image in
20408     * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
20409     * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.
20410     * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system.
20411     * @param {Number} [imageY] - The top coordinate in image coordinate system.
20412     * @param {Number} [pixelWidth] - The width in pixel of the rectangle.
20413     * @param {Number} [pixelHeight] - The height in pixel of the rectangle.
20414     * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
20415     * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.
20416     */
20417    imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight, current) {
20418        var rect = imageX;
20419        if (rect instanceof $.Rect) {
20420            //they passed a rect instead of individual components
20421            current = imageY;
20422        } else {
20423            rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
20424        }
20425
20426        var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);
20427        var coordB = this._imageToViewportDelta(rect.width, rect.height, current);
20428
20429        return new $.Rect(
20430            coordA.x,
20431            coordA.y,
20432            coordB.x,
20433            coordB.y,
20434            rect.degrees + this.getRotation(current)
20435        );
20436    },
20437
20438    /**
20439     * Translates from a rectangle which describes a portion of
20440     * the viewport in point coordinates to image rectangle coordinates.
20441     * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}.
20442     * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system.
20443     * @param {Number} [viewerY] - The top coordinate in viewport coordinate system.
20444     * @param {Number} [pointWidth] - The width in viewport coordinate system.
20445     * @param {Number} [pointHeight] - The height in viewport coordinate system.
20446     * @param {Boolean} [current=false] - Pass true to use the current location; false for target location.
20447     * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.
20448     */
20449    viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {
20450        var rect = viewerX;
20451        if (viewerX instanceof $.Rect) {
20452            //they passed a rect instead of individual components
20453            current = viewerY;
20454        } else {
20455            rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
20456        }
20457
20458        var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);
20459        var coordB = this._viewportToImageDelta(rect.width, rect.height, current);
20460
20461        return new $.Rect(
20462            coordA.x,
20463            coordA.y,
20464            coordB.x,
20465            coordB.y,
20466            rect.degrees - this.getRotation(current)
20467        );
20468    },
20469
20470    /**
20471     * Convert pixel coordinates relative to the viewer element to image
20472     * coordinates.
20473     * @param {OpenSeadragon.Point} pixel
20474     * @returns {OpenSeadragon.Point}
20475     */
20476    viewerElementToImageCoordinates: function( pixel ) {
20477        var point = this.viewport.pointFromPixel( pixel, true );
20478        return this.viewportToImageCoordinates( point );
20479    },
20480
20481    /**
20482     * Convert pixel coordinates relative to the image to
20483     * viewer element coordinates.
20484     * @param {OpenSeadragon.Point} pixel
20485     * @returns {OpenSeadragon.Point}
20486     */
20487    imageToViewerElementCoordinates: function( pixel ) {
20488        var point = this.imageToViewportCoordinates( pixel );
20489        return this.viewport.pixelFromPoint( point, true );
20490    },
20491
20492    /**
20493     * Convert pixel coordinates relative to the window to image coordinates.
20494     * @param {OpenSeadragon.Point} pixel
20495     * @returns {OpenSeadragon.Point}
20496     */
20497    windowToImageCoordinates: function( pixel ) {
20498        var viewerCoordinates = pixel.minus(
20499                OpenSeadragon.getElementPosition( this.viewer.element ));
20500        return this.viewerElementToImageCoordinates( viewerCoordinates );
20501    },
20502
20503    /**
20504     * Convert image coordinates to pixel coordinates relative to the window.
20505     * @param {OpenSeadragon.Point} pixel
20506     * @returns {OpenSeadragon.Point}
20507     */
20508    imageToWindowCoordinates: function( pixel ) {
20509        var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
20510        return viewerCoordinates.plus(
20511                OpenSeadragon.getElementPosition( this.viewer.element ));
20512    },
20513
20514    // private
20515    // Convert rectangle in viewport coordinates to this tiled image point
20516    // coordinates (x in [0, 1] and y in [0, aspectRatio])
20517    _viewportToTiledImageRectangle: function(rect) {
20518        var scale = this._scaleSpring.current.value;
20519        rect = rect.rotate(-this.getRotation(true), this._getRotationPoint(true));
20520        return new $.Rect(
20521            (rect.x - this._xSpring.current.value) / scale,
20522            (rect.y - this._ySpring.current.value) / scale,
20523            rect.width / scale,
20524            rect.height / scale,
20525            rect.degrees);
20526    },
20527
20528    /**
20529     * Convert a viewport zoom to an image zoom.
20530     * Image zoom: ratio of the original image size to displayed image size.
20531     * 1 means original image size, 0.5 half size...
20532     * Viewport zoom: ratio of the displayed image's width to viewport'
20532s width.
20533     * 1 means identical width, 2 means image's width is twice the viewport's width...
20534     * @function
20535     * @param {Number} viewportZoom The viewport zoom
20536     * @returns {Number} imageZoom The image zoom
20537     */
20538    viewportToImageZoom: function( viewportZoom ) {
20539        var ratio = this._scaleSpring.current.value *
20540                this.viewport._containerInnerSize.x / this.source.dimensions.x;
20541        return ratio * viewportZoom;
20542    },
20543
20544    /**
20545     * Convert an image zoom to a viewport zoom.
20546     * Image zoom: ratio of the original image size to displayed image size.
20547     * 1 means original image size, 0.5 half size...
20548     * Viewport zoom: ratio of the displayed image's width to viewport's width.
20549     * 1 means identical width, 2 means image's width is twice the viewport's width...
20550     * Note: not accurate with multi-image.
20551     * @function
20552     * @param {Number} imageZoom The image zoom
20553     * @returns {Number} viewportZoom The viewport zoom
20554     */
20555    imageToViewportZoom: function( imageZoom ) {
20556        var ratio = this._scaleSpring.current.value *
20557                this.viewport._containerInnerSize.x / this.source.dimensions.x;
20558        return imageZoom / ratio;
20559    },
20560
20561    /**
20562     * Sets the TiledImage's position in the world.
20563     * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates.
20564     * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately.
20565     * @fires OpenSeadragon.TiledImage.event:bounds-change
20566     */
20567    setPosition: function(position, immediately) {
20568        var sameTarget = (this._xSpring.target.value === position.x &&
20569            this._ySpring.target.value === position.y);
20570
20571        if (immediately) {
20572            if (sameTarget && this._xSpring.current.value === position.x &&
20573                    this._ySpring.current.value === position.y) {
20574                return;
20575            }
20576
20577            this._xSpring.resetTo(position.x);
20578            this._ySpring.resetTo(position.y);
20579            this._needsDraw = true;
20580        } else {
20581            if (sameTarget) {
20582                return;
20583            }
20584
20585            this._xSpring.springTo(position.x);
20586            this._ySpring.springTo(position.y);
20587            this._needsDraw = true;
20588        }
20589
20590        if (!sameTarget) {
20591            this._raiseBoundsChange();
20592        }
20593    },
20594
20595    /**
20596     * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio.
20597     * @param {Number} width - The new width, in viewport coordinates.
20598     * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.
20599     * @fires OpenSeadragon.TiledImage.event:bounds-change
20600     */
20601    setWidth: function(width, immediately) {
20602        this._setScale(width, immediately);
20603    },
20604
20605    /**
20606     * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio.
20607     * @param {Number} height - The new height, in viewport coordinates.
20608     * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately.
20609     * @fires OpenSeadragon.TiledImage.event:bounds-change
20610     */
20611    setHeight: function(height, immediately) {
20612        this._setScale(height / this.normHeight, immediately);
20613    },
20614
20615    /**
20616     * Sets an array of polygons to crop the TiledImage during draw tiles.
20617     * The render function will use the default non-zero winding rule.
20618     * @param Polygons represented in an array of point object in image coordinates.
20619     * Example format: [
20620     *  [{x: 197, y:172}, {x: 226, y:172}, {x: 226, y:198}, {x: 197, y:198}], // First polygon
20621     *  [{x: 328, y:200}, {x: 330, y:199}, {x: 332, y:201}, {x: 329, y:202}]  // Second polygon
20622     *  [{x: 321, y:201}, {x: 356, y:205}, {x: 341, y:250}] // Third polygon
20623     * ]
20624     */
20625    setCroppingPolygons: function( polygons ) {
20626
20627        var isXYObject = function(obj) {
20628            return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number');
20629        };
20630
20631        var objectToSimpleXYObject = function(objs) {
20632            return objs.map(function(obj) {
20633                try {
20634                    if (isXYObject(obj)) {
20635                        return { x: obj.x, y: obj.y };
20636                    } else {
20637                        throw new Error();
20638                    }
20639                } catch(e) {
20640                    throw new Error('A Provided cropping polygon point is not supported');
20641                }
20642            });
20643        };
20644
20645        try {
20646            if (!$.isArray(polygons)) {
20647                throw new Error('Provided cropping polygon is not an array');
20648            }
20649            this._croppingPolygons = polygons.map(function(polygon){
20650                return objectToSimpleXYObject(polygon);
20651            });
20652        } catch (e) {
20653            $.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported');
20654            $.console.error(e);
20655            this._croppingPolygons = null;
20656        }
20657    },
20658
20659    /**
20660     * Resets the cropping polygons, thus next render will remove all cropping
20661     * polygon effects.
20662     */
20663    resetCroppingPolygons: function() {
20664        this._croppingPolygons = null;
20665    },
20666
20667    /**
20668     * Positions and scales the TiledImage to fit in the specified bounds.
20669     * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change
20670     * twice
20671     * @param {OpenSeadragon.Rect} bounds The bounds to fit the image into.
20672     * @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER]
20673     * How to anchor the image in the bounds.
20674     * @param {Boolean} [immediately=false] Whether to animate to the new size
20675     * or snap immediately.
20676     * @fires OpenSeadragon.TiledImage.event:bounds-change
20677     */
20678    fitBounds: function(bounds, anchor, immediately) {
20679        anchor = anchor || $.Placement.CENTER;
20680        var anchorProperties = $.Placement.properties[anchor];
20681        var aspectRatio = this.contentAspectX;
20682        var xOffset = 0;
20683        var yOffset = 0;
20684        var displayedWidthRatio = 1;
20685        var displayedHeightRatio = 1;
20686        if (this._clip) {
20687            aspectRatio = this._clip.getAspectRatio();
20688            displayedWidthRatio = this._clip.width / this.source.dimensions.x;
20689            displayedHeightRatio = this._clip.height / this.source.dimensions.y;
20690            if (bounds.getAspectRatio() > aspectRatio) {
20691                xOffset = this._clip.x / this._clip.height * bounds.height;
20692                yOffset = this._clip.y / this._clip.height * bounds.height;
20693            } else {
20694                xOffset = this._clip.x / this._clip.width * bounds.width;
20695                yOffset = this._clip.y / this._clip.width * bounds.width;
20696            }
20697        }
20698
20699        if (bounds.getAspectRatio() > aspectRatio) {
20700            // We will have margins on the X axis
20701            var height = bounds.height / displayedHeightRatio;
20702            var marginLeft = 0;
20703            if (anchorProperties.isHorizontallyCentered) {
20704                marginLeft = (bounds.width - bounds.height * aspectRatio) / 2;
20705            } else if (anchorProperties.isRight) {
20706                marginLeft = bounds.width - bounds.height * aspectRatio;
20707            }
20708            this.setPosition(
20709                new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset),
20710                immediately);
20711            this.setHeight(height, immediately);
20712        } else {
20713            // We will have margins on the Y axis
20714            var width = bounds.width / displayedWidthRatio;
20715            var marginTop = 0;
20716            if (anchorProperties.isVerticallyCentered) {
20717                marginTop = (bounds.height - bounds.width / aspectRatio) / 2;
20718            } else if (anchorProperties.isBottom) {
20719                marginTop = bounds.height - bounds.width / aspectRatio;
20720            }
20721            this.setPosition(
20722                new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop),
20723                immediately);
20724            this.setWidth(width, immediately);
20725        }
20726    },
20727
20728    /**
20729     * @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle,
20730     * in image pixels, or null if none.
20731     */
20732    getClip: function() {
20733        if (this._clip) {
20734            return this._clip.clone();
20735        }
20736
20737        return null;
20738    },
20739
20740    /**
20741     * @param {OpenSeadragon.Rect|null} newClip - An area, in image pixels, to clip to
20742     * (portions of the image outside of this area will not be visible). Only works on
20743     * browsers that support the HTML5 canvas.
vendor: 4,722 bytes, lines 20744-20884
20744     * @fires OpenSeadragon.TiledImage.event:clip-change
20745     */
20746    setClip: function(newClip) {
20747        $.console.assert(!newClip || newClip instanceof $.Rect,
20748            "[TiledImage.setClip] newClip must be an OpenSeadragon.Rect or null");
20749
20750        if (newClip instanceof $.Rect) {
20751            this._clip = newClip.clone();
20752        } else {
20753            this._clip = null;
20754        }
20755
20756        this._needsDraw = true;
20757        /**
20758         * Raised when the TiledImage's clip is changed.
20759         * @event clip-change
20760         * @memberOf OpenSeadragon.TiledImage
20761         * @type {object}
20762         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20763         * TiledImage which raised the event.
20764         * @property {?Object} userData - Arbitrary subscriber-defined object.
20765         */
20766        this.raiseEvent('clip-change');
20767    },
20768
20769    /**
20770     * @returns {Number} The TiledImage's current opacity.
20771     */
20772    getOpacity: function() {
20773        return this.opacity;
20774    },
20775
20776    /**
20777     * @param {Number} opacity Opacity the tiled image should be drawn at.
20778     * @fires OpenSeadragon.TiledImage.event:opacity-change
20779     */
20780    setOpacity: function(opacity) {
20781        if (opacity === this.opacity) {
20782            return;
20783        }
20784
20785        this.opacity = opacity;
20786        this._needsDraw = true;
20787        /**
20788         * Raised when the TiledImage's opacity is changed.
20789         * @event opacity-change
20790         * @memberOf OpenSeadragon.TiledImage
20791         * @type {object}
20792         * @property {Number} opacity - The new opacity value.
20793         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20794         * TiledImage which raised the event.
20795         * @property {?Object} userData - Arbitrary subscriber-defined object.
20796         */
20797        this.raiseEvent('opacity-change', {
20798            opacity: this.opacity
20799        });
20800    },
20801
20802    /**
20803     * @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.
20804     */
20805    getPreload: function() {
20806        return this._preload;
20807    },
20808
20809    /**
20810     * Set true to load even when hidden. Set false to block loading when hidden.
20811     */
20812    setPreload: function(preload) {
20813        this._preload = !!preload;
20814        this._needsDraw = true;
20815    },
20816
20817    /**
20818     * Get the rotation of this tiled image in degrees.
20819     * @param {Boolean} [current=false] True for current rotation, false for target.
20820     * @returns {Number} the rotation of this tiled image in degrees.
20821     */
20822    getRotation: function(current) {
20823        return current ?
20824            this._degreesSpring.current.value :
20825            this._degreesSpring.target.value;
20826    },
20827
20828    /**
20829     * Set the current rotation of this tiled image in degrees.
20830     * @param {Number} degrees the rotation in degrees.
20831     * @param {Boolean} [immediately=false] Whether to animate to the new angle
20832     * or rotate immediately.
20833     * @fires OpenSeadragon.TiledImage.event:bounds-change
20834     */
20835    setRotation: function(degrees, immediately) {
20836        if (this._degreesSpring.target.value === degrees &&
20837            this._degreesSpring.isAtTargetValue()) {
20838            return;
20839        }
20840        if (immediately) {
20841            this._degreesSpring.resetTo(degrees);
20842        } else {
20843            this._degreesSpring.springTo(degrees);
20844        }
20845        this._needsDraw = true;
20846        this._raiseBoundsChange();
20847    },
20848
20849    /**
20850     * Get the point around which this tiled image is rotated
20851     * @private
20852     * @param {Boolean} current True for current rotation point, false for target.
20853     * @returns {OpenSeadragon.Point}
20854     */
20855    _getRotationPoint: function(current) {
20856        return this.getBoundsNoRotate(current).getCenter();
20857    },
20858
20859    /**
20860     * @returns {String} The TiledImage's current compositeOperation.
20861     */
20862    getCompositeOperation: function() {
20863        return this.compositeOperation;
20864    },
20865
20866    /**
20867     * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
20868     * @fires OpenSeadragon.TiledImage.event:composite-operation-change
20869     */
20870    setCompositeOperation: function(compositeOperation) {
20871        if (compositeOperation === this.compositeOperation) {
20872            return;
20873        }
20874
20875        this.compositeOperation = compositeOperation;
20876        this._needsDraw = true;
20877        /**
20878         * Raised when the TiledImage's opacity is changed.
20879         * @event composite-operation-change
20880         * @memberOf OpenSeadragon.TiledImage
20881         * @type {object}
20882         * @property {String} compositeOperation - The new compositeOperation value.
20883         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20884         * TiledImage which raised the event.
20885         * @property {?Object} userData - Arbitrary subscriber-defined object.
20886         */
20887        this.raiseEvent('composite-operation-change', {
20888            compositeOperation: this.compositeOperation
20889        });
20890    },
20891
20892    // private
20893    _setScale: function(scale, immediately) {
20894        var sameTarget = (this._scaleSpring.target.value === scale);
20895        if (immediately) {
20896            if (sameTarget && this._scaleSpring.current.value === scale) {
20897                return;
20898            }
20899
20900            this._scaleSpring.resetTo(scale);
20901            this._updateForScale();
20902            this._needsDraw = true;
20903        } else {
20904            if (sameTarget) {
20905                return;
20906            }
20907
20908            this._scaleSpring.springTo(scale);
20909            this._updateForScale();
20910            this._needsDraw = true;
20911        }
20912
20913        if (!sameTarget) {
20914            this._raiseBoundsChange();
20915        }
20916    },
20917
20918    // private
20919    _updateForScale: function() {
20920        this._worldWidthTarget = this._scaleSpring.target.value;
20921        this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value;
20922        this._worldWidthCurrent = this._scaleSpring.current.value;
20923        this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value;
20924    },
20925
20926    // private
20927    _raiseBoundsChange: function() {
20928        /**
20929         * Raised when the TiledImage's bounds are changed.
20930         * Note that this event is triggered only when the animation target is changed;
20931         * not for every frame of animation.
20932         * @event bounds-change
20933         * @memberOf OpenSeadragon.TiledImage
20934         * @type {object}
20935         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the
20936         * TiledImage which raised the event.
20937         * @property {?Object} userData - Arbitrary subscriber-defined object.
20938         */
20939        this.raiseEvent('bounds-change');
20940    },
20941
20942    // private
20943    _isBottomItem: function() {
20944        return this.viewer.world.getItemAt(0) === this;
20945    },
20946
20947    // private
20948    _getLevelsInterval: function() {
20949        var lowestLevel = Math.max(
20950            this.source.minLevel,
20951            Math.floor(Math.log(this.minZoomImageRatio) / Math.log(2))
20952        );
20953        var currentZeroRatio = this.viewport.deltaPixelsFromPointsNoRotate(
20954            this.source.getPixelRatio(0), true).x *
20955            this._scaleSpring.current.value;
20956        var highestLevel = Math.min(
20957            Math.abs(this.source.maxLevel),
20958            Math.abs(Math.floor(
20959                Math.log(currentZeroRatio / this.minPixelRatio) / Math.log(2)
20960            ))
20961        );
20962
20963        // Calculations for the interval of levels to draw
20964        // can return invalid intervals; fix that here if necessary
20965        highestLevel = Math.max(highestLevel, this.source.minLevel || 0);
20966        lowestLevel = Math.min(lowestLevel, highestLevel);
20967        return {
20968            lowestLevel: lowestLevel,
20969            highestLevel: highestLevel
20970        };
20971    },
20972
20973    /**
20974     * @private
20975     * @inner
20976     * Pretty much every other line in this needs to be documented so it's clear
20977     * how each piece of this routine contributes to the drawing process.  That's
20978     * why there are so many TODO's inside this function.
20979     */
20980    _updateViewport: function() {
20981        this._needsDraw = false;
20982        this._tilesLoading = 0;
20983        this.loadingCoverage = {};
20984
20985        // Reset tile's internal drawn state
20986        while (this.lastDrawn.length > 0) {
20987            var tile = this.lastDrawn.pop();
20988            tile.beingDrawn = false;
20989        }
20990
20991        var viewport = this.viewport;
20992        var drawArea = this._viewportToTiledImageRectangle(
20993            viewport.getBoundsWithMargins(true));
20994
20995        if (!this.wrapHorizontal && !this.wrapVertical) {
20996            var tiledImageBounds = this._viewportToTiledImageRectangle(
20997                this.getClippedBounds(true));
20998            drawArea = drawArea.intersection(tiledImageBounds);
20999            if (drawArea === null) {
21000                return;
21001            }
21002        }
21003
21004        var levelsInterval = this._getLevelsInterval();
21005        var lowestLevel = levelsInterval.lowestLevel;
21006        var highestLevel = levelsInterval.highestLevel;
21007        var bestTile = null;
21008        var haveDrawn = false;
21009        var currentTime = $.now();
21010
21011        // Update any level that will be drawn
21012        for (var level = highestLevel; level >= lowestLevel; level--) {
21013            var drawLevel = false;
21014
21015            //Avoid calculations for draw if we have already drawn this
21016            var currentRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
21017                this.source.getPixelRatio(level),
21018                true
21019            ).x * this._scaleSpring.current.value;
21020
21021            if (level === lowestLevel ||
21022                (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio)) {
21023                drawLevel = true;
21024                haveDrawn = true;
21025            } else if (!haveDrawn) {
21026                continue;
21027            }
21028
21029            //Perform calculations for draw if we haven't drawn this
21030            var targetRenderPixelRatio = viewport.deltaPixelsFromPointsNoRotate(
21031                this.source.getPixelRatio(level),
21032                false
21033            ).x * this._scaleSpring.current.value;
21034
21035            var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(
21036                this.source.getPixelRatio(
21037                    Math.max(
21038                        this.source.getClosestLevel(),
21039                        0
21040                    )
21041                ),
21042                false
21043            ).x * this._scaleSpring.current.value;
21044
21045            var optimalRatio = this.immediateRender ? 1 : targetZeroRatio;
21046            var levelOpacity = Math.min(1, (currentRenderPixelRatio - 0.5) / 0.5);
21047            var levelVisibility = optimalRatio / Math.abs(
21048                optimalRatio - targetRenderPixelRatio
21049            );
21050
21051            // Update the level and keep track of 'best' tile to load
21052            bestTile = updateLevel(
21053                this,
21054                haveDrawn,
21055                drawLevel,
21056                level,
21057                levelOpacity,
21058                levelVisibility,
21059                drawArea,
21060                currentTime,
21061                bestTile
21062            );
21063
21064            // Stop the loop if lower-res tiles would all be covered by
21065            // already drawn tiles
21066            if (providesCoverage(this.coverage, level)) {
21067                break;
21068            }
21069        }
21070
21071        // Perform the actual drawing
21072        drawTiles(this, this.lastDrawn);
21073
21074        // Load the new 'best' tile
21075        if (bestTile && !bestTile.context2D) {
21076            loadTile(this, bestTile, currentTime);
21077            this._needsDraw = true;
21078            this._setFullyLoaded(false);
21079        } else {
21080            this._setFullyLoaded(this._tilesLoading === 0);
21081        }
21082    },
21083
21084    // private
21085    _getCornerTiles: function(level, topLeftBound, bottomRightBound) {
21086        var leftX;
21087        var rightX;
21088        if (this.wrapHorizontal) {
21089            leftX = $.positiveModulo(topLeftBound.x, 1);
21090            rightX = $.positiveModulo(bottomRightBound.x, 1);
21091        } else {
21092            leftX = Math.max(0, topLeftBound.x);
21093            rightX = Math.min(1, bottomRightBound.x);
21094        }
21095        var topY;
21096        var bottomY;
21097        var aspectRatio = 1 / this.source.aspectRatio;
21098        if (this.wrapVertical) {
21099            topY = $.positiveModulo(topLeftBound.y, aspectRatio);
21100            bottomY = $.positiveModulo(bottomRightBound.y, aspectRatio);
21101        } else {
21102            topY = Math.max(0, topLeftBound.y);
21103            bottomY = Math.min(aspectRatio, bottomRightBound.y);
21104        }
21105
21106        var topLeftTile = this.source.getTileAtPoint(level, new $.Point(leftX, topY));
21107        var bottomRightTile = this.source.getTileAtPoint(level, new $.Point(rightX, bottomY));
21108        var numTiles  = this.source.getNumTiles(level);
21109
21110        if (this.wrapHorizontal) {
21111            topLeftTile.x += numTiles.x * Math.floor(topLeftBound.x);
21112            bottomRightTile.x += numTiles.x * Math.floor(bottomRightBound.x);
21113        }
21114        if (this.wrapVertical) {
21115            topLeftTile.y += numTiles.y * Math.floor(topLeftBound.y / aspectRatio);
21116            bottomRightTile.y += numTiles.y * Math.floor(bottomRightBound.y / aspectRatio);
21117        }
21118
21119        return {
21120            topLeft: topLeftTile,
21121            bottomRight: bottomRightTile,
21122        };
21123    }
21124});
21125
21126/**
21127 * @private
21128 * @inner
21129 * Updates all tiles at a given resolution level.
21130 * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
21131 * @param {Boolean} haveDrawn
21132 * @param {Boolean} drawLevel
21133 * @param {Number} level
21134 * @param {Number} levelOpacity
21135 * @param {Number} levelVisibility
21136 * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile.
21137 * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile.
21138 * @param {Number} currentTime
21139 * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
21140 */
21141function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
21142    levelVisibility, drawArea, currentTime, best) {
21143
21144    var topLeftBound = drawArea.getBoundingBox().getTopLeft();
21145    var bottomRightBound = drawArea.getBoundingBox().getBottomRight();
21146
21147    if (tiledImage.viewer) {
21148        /**
21149         * <em>- Needs documentation -</em>
21150         *
21151         * @event update-level
21152         * @memberof OpenSeadragon.Viewer
21153         * @type {object}
21154         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
21155         * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
21156         * @property {Object} havedrawn
21157         * @property {Object} level
21158         * @property {Object} opacity
21159         * @property {Object} visibility
21160         * @property {OpenSeadragon.Rect} drawArea
21161         * @property {Object} topleft deprecated, use drawArea instead
21162         * @property {Object} bottomright deprecated, use drawArea instead
21163         * @property {Object} currenttime
21164         * @property {Object} best
21165         * @property {?Object} userData - Arbitrary subscriber-defined object.
21166         */
21167        tiledImage.viewer.raiseEvent('update-level', {
21168            tiledImage: tiledImage,
21169            havedrawn: haveDrawn,
21170            level: level,
21171            opacity: levelOpacity,
21172            visibility: levelVisibility,
21173            drawArea: drawArea,
21174            topleft: topLeftBound,
21175            bottomright: bottomRightBound,
21176            currenttime: currentTime,
21177            best: best
21178        });
21179    }
21180
21181    resetCoverage(tiledImage.coverage, level);
21182    resetCoverage(tiledImage.loadingCoverage, level);
21183
21184    //OK, a new drawing so do your calculations
21185    var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);
21186    var topLeftTile = cornerTiles.topLeft;
21187    var bottomRightTile = cornerTiles.bottomRight;
21188    var numberOfTiles  = tiledImage.source.getNumTiles(level);
21189
21190    var viewportCenter = tiledImage.viewport.pixelFromPoint(
21191        tiledImage.viewport.getCenter());
21192    for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) {
21193        for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) {
21194
21195            // Optimisation disabled with wrapping because getTileBounds does not
21196            // work correctly with x and y outside of the number of tiles
21197            if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
21198                var tileBounds = tiledImage.source.getTileBounds(level, x, y);
21199                if (drawArea.intersection(tileBounds) === null) {
21200                    // This tile is outside of the viewport, no need to draw it
21201                    continue;
21202                }
21203            }
21204
21205            best = updateTile(
21206                tiledImage,
21207                drawLevel,
21208                haveDrawn,
21209                x, y,
21210                level,
21211                levelOpacity,
21212                levelVisibility,
21213                viewportCenter,
21214                numberOfTiles,
21215                currentTime,
21216                best
21217            );
21218
21219        }
21220    }
21221
21222    return best;
21223}
21224
21225/**
21226 * @private
21227 * @inner
21228 * Update a single tile at a particular resolution level.
21229 * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
21230 * @param {Boolean} haveDrawn
21231 * @param {Boolean} drawLevel
21232 * @param {Number} x
21233 * @param {Number} y
21234 * @param {Number} level
21235 * @param {Number} levelOpacity
21236 * @param {Number} levelVisibility
21237 * @param {OpenSeadragon.Point} viewportCenter
21238 * @param {Number} numberOfTiles
21239 * @param {Number} currentTime
21240 * @param {OpenSeadragon.Tile} best - The current "best" tile to draw.
21241 */
21242function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
21243
21244    var tile = getTile(
21245            x, y,
21246            level,
21247            tiledImage,
21248            tiledImage.source,
21249            tiledImage.tilesMatrix,
21250            currentTime,
21251            numberOfTiles,
21252            tiledImage._worldWidthCurrent,
21253            tiledImage._worldHeightCurrent
21254        ),
21255        drawTile = drawLevel;
21256
21257    if( tiledImage.viewer ){
21258        /**
21259         * <em>- Needs documentation -</em>
21260         *
21261         * @event update-tile
21262         * @memberof OpenSeadragon.Viewer
21263         * @type {object}
21264         * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
21265         * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
21266         * @property {OpenSeadragon.Tile} tile
21267         * @property {?Object} userData - Arbitrary subscriber-defined object.
21268         */
21269        tiledImage.viewer.raiseEvent( 'update-tile', {
21270            tiledImage: tiledImage,
21271            tile: tile
21272        });
21273    }
21274
21275    setCoverage( tiledImage.coverage, level, x, y, false );
21276
21277    var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);
21278    setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);
21279
21280    if ( !tile.exists ) {
21281        return best;
21282    }
21283
21284    if ( haveDrawn && !drawTile ) {
21285        if ( isCovered( tiledImage.coverage, level, x, y ) ) {
21286            setCoverage( tiledImage.coverage, level, x, y, true );
21287        } else {
21288            drawTile = true;
21289        }
21290    }
21291
21292    if ( !drawTile ) {
21293        return best;
21294    }
21295
21296    positionTile(
21297        tile,
21298        tiledImage.source.tileOverlap,
21299        tiledImage.viewport,
21300        viewportCenter,
21301        levelVisibility,
21302        tiledImage
21303    );
21304
21305    if (!tile.loaded) {
21306        if (tile.context2D) {
21307            setTileLoaded(tiledImage, tile);
21308        } else {
21309            var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);
21310            if (imageRecord) {
21311                var image = imageRecord.getImage();
21312                setTileLoaded(tiledImage, tile, image);
21313            }
21314        }
21315    }
21316
21317    if ( tile.loaded ) {
21318        var needsDraw = blendTile(
21319            tiledImage,
21320            tile,
21321            x, y,
21322            level,
21323            levelOpacity,
21324            currentTime
21325        );
21326
21327        if ( needsDraw ) {
21328            tiledImage._needsDraw = true;
21329        }
21330    } else if ( tile.loading ) {
21331        // the tile is already in the download queue
21332        tiledImage._tilesLoading++;
21333    } else if (!loadingCoverage) {
21334        best = compareTiles( best, tile );
21335    }
21336
21337    return best;
21338}
21339
21340/**
21341 * @private
21342 * @inner
21343 * Obtains a tile at the given location.
21344 * @param {Number} x
21345 * @param {Number} y
21346 * @param {Number} level
21347 * @param {OpenSeadragon.TiledImage} tiledImage
21348 * @param {OpenSeadragon.TileSource} tileSource
21349 * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
21350 * @param {Number} time
21351 * @param {Number} numTiles
21352 * @param {Number} worldWidth
21353 * @param {Number} worldHeight
21354 * @returns {OpenSeadragon.Tile}
21355 */
21356function getTile(
21357    x, y,
21358    level,
21359    tiledImage,
21360    tileSource,
21361    tilesMatrix,
21362    time,
21363    numTiles,
21364    worldWidth,
21365    worldHeight
21366) {
21367    var xMod,
21368        yMod,
21369        bounds,
21370        sourceBounds,
21371        exists,
21372        url,
21373        ajaxHeaders,
21374        context2D,
21375        tile;
21376
21377    if ( !tilesMatrix[ level ] ) {
21378        tilesMatrix[ level ] = {};
21379    }
21380    if ( !tilesMatrix[ level ][ x ] ) {
21381        tilesMatrix[ level ][ x ] = {};
21382    }
21383
21384    if ( !tilesMatrix[ level ][ x ][ y ] ) {
21385        xMod    = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
21386        yMod    = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
21387        bounds  = tileSource.getTileBounds( level, xMod, yMod );
21388        sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );
21389        exists  = tileSource.tileExists( level, xMod, yMod );
21390        url     = tileSource.getTileUrl( level, xMod, yMod );
21391
21392        // Headers are only applicable if loadTilesWithAjax is set
21393        if (tiledImage.loadTilesWithAjax) {
21394            ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );
21395            // Combine tile AJAX headers with tiled image AJAX headers (if applicable)
21396            if ($.isPlainObject(tiledImage.ajaxHeaders)) {
21397                ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);
21398            }
21399        } else {
21400            ajaxHeaders = null;
21401        }
21402
21403        context2D = tileSource.getContext2D ?
21404            tileSource.getContext2D(level, xMod, yMod) : undefined;
21405
21406        bounds.x += ( x - xMod ) / numTiles.x;
21407        bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
21408
21409        tile = new $.Tile(
21410            level,
21411            x,
21412            y,
21413            bounds,
21414            exists,
21415            url,
21416            context2D,
21417            tiledImage.loadTilesWithAjax,
21418            ajaxHeaders,
21419            sourceBounds
21420        );
21421
21422        if (xMod === numTiles.x - 1) {
21423            tile.isRightMost = true;
21424        }
21425
21426        if (yMod === numTiles.y - 1) {
21427            tile.isBottomMost = true;
21428        }
21429
21430        tilesMatrix[ level ][ x ][ y ] = tile;
21431    }
21432
21433    tile = tilesMatrix[ level ][ x ][ y ];
21434    tile.lastTouchTime = time;
21435
21436    return tile;
21437}
21438
21439/**
21440 * @private
21441 * @inner
21442 * Dispatch a job to the ImageLoader to load the Image for a Tile.
21443 * @param {OpenSeadragon.TiledImage} tiledImage
21444 * @param {OpenSeadragon.Tile} tile
21445 * @param {Number} time
21446 */
21447function loadTile( tiledImage, tile, time ) {
21448    tile.loading = true;
21449    tiledImage._imageLoader.addJob({
21450        src: tile.url,
21451        loadWithAjax: tile.loadWithAjax,
21452        ajaxHeaders: tile.ajaxHeaders,
21453        crossOriginPolicy: tiledImage.crossOriginPolicy,
21454        ajaxWithCredentials: tiledImage.ajaxWithCredentials,
21455        callback: function( image, errorMsg, tileRequest ){
21456            onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );
21457        },
21458        abort: function() {
21459            tile.loading = false;
21460        }
21461    });
21462}
21463
21464/**
21465 * @private
21466 * @inner
21467 * Callback fired when a Tile's Image finished downloading.
21468 * @param {OpenSeadragon.TiledImage} tiledImage
21469 * @param {OpenSeadragon.Tile} tile
21470 * @param {Number} time
21471 * @param {Image} image
21472 * @param {String} errorMsg
21473 * @param {XMLHttpRequest} tileRequest
21474 */
21475function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {
21476    if ( !image ) {
21477        $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
21478        /**
21479         * Triggered when a tile fails to load.
21480         *
21481         * @event tile-load-failed
21482         * @memberof OpenSeadragon.Viewer
21483         * @type {object}
21484         * @property {OpenSeadragon.Tile} tile - The tile that failed to load.
21485         * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.
21486         * @property {number} time - The time in milliseconds when the tile load began.
21487         * @property {string} message - The error message.
21488         * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.
21489         */
21490        tiledImage.viewer.raiseEvent("tile-load-failed", {
21491            tile: tile,
21492            tiledImage: tiledImage,
21493            time: time,
21494            message: errorMsg,
21495            tileRequest: tileRequest
21496        });
21497        tile.loading = false;
21498        tile.exists = false;
21499        return;
21500    }
21501
21502    if ( time < tiledImage.lastResetTime ) {
21503        $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
21504        tile.loading = false;
21505        return;
21506    }
21507
21508    var finish = function() {
21509        var cutoff = tiledImage.source.getClosestLevel();
21510        setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);
21511    };
21512
21513    // Check if we're mid-update; this can happen on IE8 because image load events for
21514    // cached images happen immediately there
21515    if ( !tiledImage._midDraw ) {
21516        finish();
21517    } else {
21518        // Wait until after the update, in case caching unloads any tiles
21519        window.setTimeout( finish, 1);
21520    }
21521}
21522
21523/**
21524 * @private
21525 * @inner
21526 * @param {OpenSeadragon.TiledImage} tiledImage
21527 * @param {OpenSeadragon.Tile} tile
21528 * @param {Image} image
21529 * @param {Number} cutoff
21530 */
21531function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {
21532    var increment = 0;
21533
21534    function getCompletionCallback() {
21535        increment++;
21536        return completionCallback;
21537    }
21538
21539    function completionCallback() {
21540        increment--;
21541        if (increment === 0) {
21542            tile.loading = false;
21543            tile.loaded = true;
21544            if (!tile.context2D) {
21545                tiledImage._tileCache.cacheTile({
21546                    image: image,
21547                    tile: tile,
21548                    cutoff: cutoff,
21549                    tiledImage: tiledImage
21550                });
21551            }
21552            tiledImage._needsDraw = true;
21553        }
21554    }
21555
21556    /**
21557     * Triggered when a tile has just been loaded in memory. That means that the
21558     * image has been downloaded and can be modified before being drawn to the canvas.
21559     *
21560     * @event tile-loaded
21561     * @memberof OpenSeadragon.Viewer
21562     * @type {object}
21563     * @property {Image} image - The image of the tile.
21564     * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
21565     * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
21566     * @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).
21567     * @property {function} getCompletionCallback - A function giving a callback to call
21568     * when the asynchronous processing of the image is done. The image will be
21569     * marked as entirely loaded when the callback has been called once for each
21570     * call to getCompletionCallback.
21571     */
21572    tiledImage.viewer.raiseEvent("tile-loaded", {
21573        tile: tile,
21574        tiledImage: tiledImage,
21575        tileRequest: tileRequest,
21576        image: image,
21577        getCompletionCallback: getCompletionCallback
21578    });
21579    // In case the completion callback is never called, we at least force it once.
21580    getCompletionCallback()();
21581}
21582
21583/**
21584 * @private
21585 * @inner
21586 * @param {OpenSeadragon.Tile} tile
21587 * @param {Boolean} overlap
21588 * @param {OpenSeadragon.Viewport} viewport
21589 * @param {OpenSeadragon.Point} viewportCenter
21590 * @param {Number} levelVisibility
21591 * @param {OpenSeadragon.TiledImage} tiledImage
21592 */
21593function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){
21594    var boundsTL     = tile.bounds.getTopLeft();
21595
21596    boundsTL.x *= tiledImage._scaleSpring.current.value;
21597    boundsTL.y *= tiledImage._scaleSpring.current.value;
21598    boundsTL.x += tiledImage._xSpring.current.value;
21599    boundsTL.y += tiledImage._ySpring.current.value;
21600
21601    var boundsSize   = tile.bounds.getSize();
21602
21603    boundsSize.x *= tiledImage._scaleSpring.current.value;
21604    boundsSize.y *= tiledImage._scaleSpring.current.value;
21605
21606    var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
21607        positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
21608        sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
21609        sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
21610        tileCenter = positionT.plus( sizeT.divide( 2 ) ),
21611        tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter );
21612
21613    if ( !overlap ) {
21614        sizeC = sizeC.plus( new $.Point( 1, 1 ) );
21615    }
21616
21617    if (tile.isRightMost && tiledImage.wrapHorizontal) {
21618        sizeC.x += 0.75; // Otherwise Firefox and Safari show seams
21619    }
21620
21621    if (tile.isBottomMost && tiledImage.wrapVertical) {
21622        sizeC.y += 0.75; // Otherwise Firefox and Safari show seams
21623    }
21624
21625    tile.position   = positionC;
21626    tile.size       = sizeC;
21627    tile.squaredDistance   = tileSquaredDistance;
21628    tile.visibility = levelVisibility;
21629}
21630
21631/**
21632 * @private
21633 * @inner
21634 * Updates the opacity of a tile according to the time it has been on screen
21635 * to perform a fade-in.
21636 * Updates coverage once a tile is fully opaque.
21637 * Returns whether the fade-in has completed.
21638 *
21639 * @param {OpenSeadragon.TiledImage} tiledImage
21640 * @param {OpenSeadragon.Tile} tile
21641 * @param {Number} x
21642 * @param {Number} y
21643 * @param {Number} level
21644 * @param {Number} levelOpacity
21645 * @param {Number} currentTime
21646 * @returns {Boolean}
21647 */
21648function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
21649    var blendTimeMillis = 1000 * tiledImage.blendTime,
21650        deltaTime,
21651        opacity;
21652
21653    if ( !tile.blendStart ) {
21654        tile.blendStart = currentTime;
21655    }
21656
21657    deltaTime   = currentTime - tile.blendStart;
21658    opacity     = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
21659
21660    if ( tiledImage.alwaysBlend ) {
21661        opacity *= levelOpacity;
21662    }
21663
21664    tile.opacity = opacity;
21665
21666    tiledImage.lastDrawn.push( tile );
21667
21668    if ( opacity === 1 ) {
21669        setCoverage( tiledImage.coverage, level, x, y, true );
21670        tiledImage._hasOpaqueTile = true;
21671    } else if ( deltaTime < blendTimeMillis ) {
21672        return true;
21673    }
21674
21675    return false;
21676}
21677
21678/**
21679 * @private
21680 * @inner
21681 * Returns true if the given tile provides coverage to lower-level tiles of
21682 * lower resolution representing the same content. If neither x nor y is
21683 * given, returns true if the entire visible level provides coverage.
21684 *
21685 * Note that out-of-bounds tiles provide coverage in this sense, since
21686 * there's no content that they would need to cover. Tiles at non-existent
21687 * levels that are within the image bounds, however, do not.
21688 *
21689 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21690 * @param {Number} level - The resolution level of the tile.
21691 * @param {Number} x - The X position of the tile.
21692 * @param {Number} y - The Y position of the tile.
21693 * @returns {Boolean}
21694 */
21695function providesCoverage( coverage, level, x, y ) {
21696    var rows,
21697        cols,
21698        i, j;
21699
21700    if ( !coverage[ level ] ) {
21701        return false;
21702    }
21703
21704    if ( x === undefined || y === undefined ) {
21705        rows = coverage[ level ];
21706        for ( i in rows ) {
21707            if ( Object.prototype.hasOwnProperty.call( rows, i ) ) {
21708                cols = rows[ i ];
21709                for ( j in cols ) {
21710                    if ( Object.prototype.hasOwnProperty.call( cols, j ) && !cols[ j ] ) {
21711                        return false;
21712                    }
21713                }
21714            }
21715        }
21716
21717        return true;
21718    }
21719
21720    return (
21721        coverage[ level ][ x] === undefined ||
21722        coverage[ level ][ x ][ y ] === undefined ||
21723        coverage[ level ][ x ][ y ] === true
21724    );
21725}
21726
21727/**
21728 * @private
21729 * @inner
21730 * Returns true if the given tile is completely covered by higher-level
21731 * tiles of higher resolution representing the same content. If neither x
21732 * nor y is given, returns true if the entire visible level is covered.
21733 *
21734 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21735 * @param {Number} level - The resolution level of the tile.
21736 * @param {Number} x - The X position of the tile.
21737 * @param {Number} y - The Y position of the tile.
21738 * @returns {Boolean}
21739 */
21740function isCovered( coverage, level, x, y ) {
21741    if ( x === undefined || y === undefined ) {
21742        return providesCoverage( coverage, level + 1 );
21743    } else {
21744        return (
21745             providesCoverage( coverage, level + 1, 2 * x, 2 * y ) &&
21746             providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) &&
21747             providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) &&
21748             providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 )
21749        );
21750    }
21751}
21752
21753/**
21754 * @private
21755 * @inner
21756 * Sets whether the given tile provides coverage or not.
21757 *
21758 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21759 * @param {Number} level - The resolution level of the tile.
21760 * @param {Number} x - The X position of the tile.
21761 * @param {Number} y - The Y position of the tile.
21762 * @param {Boolean} covers - Whether the tile provides coverage.
21763 */
21764function setCoverage( coverage, level, x, y, covers ) {
21765    if ( !coverage[ level ] ) {
21766        $.console.warn(
21767            "Setting coverage for a tile before its level's coverage has been reset: %s",
21768            level
21769        );
21770        return;
21771    }
21772
21773    if ( !coverage[ level ][ x ] ) {
21774        coverage[ level ][ x ] = {};
21775    }
21776
21777    coverage[ level ][ x ][ y ] = covers;
21778}
21779
21780/**
21781 * @private
21782 * @inner
21783 * Resets coverage information for the given level. This should be called
21784 * after every draw routine. Note that at the beginning of the next draw
21785 * routine, coverage for every visible tile should be explicitly set.
21786 *
21787 * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
21788 * @param {Number} level - The resolution level of tiles to completely reset.
21789 */
21790function resetCoverage( coverage, level ) {
21791    coverage[ level ] = {};
21792}
21793
21794/**
21795 * @private
21796 * @inner
21797 * Determines whether the 'last best' tile for the area is better than the
21798 * tile in question.
21799 *
21800 * @param {OpenSeadragon.Tile} previousBest
21801 * @param {OpenSeadragon.Tile} tile
21802 * @returns {OpenSeadragon.Tile} The new best tile.
21803 */
21804function compareTiles( previousBest, tile ) {
21805    if ( !previousBest ) {
21806        return tile;
21807    }
21808
21809    if ( tile.visibility > previousBest.visibility ) {
21810        return tile;
21811    } else if ( tile.visibility == previousBest.visibility ) {
21812        if ( tile.squaredDistance < previousBest.squaredDistance ) {
21813            return tile;
21814        }
21815    }
21816
21817    return previousBest;
21818}
21819
21820/**
21821 * @private
21822 * @inner
21823 * Draws a TiledImage.
21824 * @param {OpenSeadragon.TiledImage} tiledImage
21825 * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
21826 */
21827function drawTiles( tiledImage, lastDrawn ) {
21828    if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
21829        return;
21830    }
21831
21832    var tile = lastDrawn[0];
21833    var useSketch;
21834
21835    if (tile) {
21836        useSketch = tiledImage.opacity < 1 ||
21837            (tiledImage.compositeOperation &&
21838                tiledImage.compositeOperation !== 'source-over') ||
21839            (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());
21840    }
21841
21842    var sketchScale;
21843    var sketchTranslate;
21844
21845    var zoom = tiledImage.viewport.getZoom(true);
21846    var imageZoom = tiledImage.viewportToImageZoom(zoom);
21847
21848    if (lastDrawn.length > 1 &&
21849        imageZoom > tiledImage.smoothTileEdgesMinZoom &&
21850        !tiledImage.iOSDevice &&
21851        tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation.
21852        $.supportsCanvas) {
21853        // When zoomed in a lot (>100%) the tile edges are visible.
21854        // So we have to composite them at ~100% and scale them up together.
21855        // Note: Disabled on iOS devices per default as it causes a native crash
21856        useSketch = true;
21857        sketchScale = tile.getScaleForEdgeSmoothing();
21858        sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
21859            tiledImage._drawer.getCanvasSize(false),
21860            tiledImage._drawer.getCanvasSize(true));
21861    }
21862
21863    var bounds;
21864    if (useSketch) {
21865        if (!sketchScale) {
21866            // Except when edge smoothing, we only clean the part of the
21867            // sketch canvas we are going to use for performance reasons.
21868            bounds = tiledImage.viewport.viewportToViewerElementRectangle(
21869                tiledImage.getClippedBounds(true))
21870                .getIntegerBoundingBox();
21871
21872            if(tiledImage._drawer.viewer.viewport.getFlip()) {
21873              if (tiledImage.viewport.degrees !== 0 || tiledImage.getRotation(true) % 360 !== 0){
21874                bounds.x = tiledImage._drawer.viewer.container.clientWidth - (bounds.x + bounds.width);
21875              }
21876            }
21877
21878            bounds = bounds.times($.pixelDensityRatio);
21879        }
21880        tiledImage._drawer._clear(true, bounds);
21881    }
21882
21883    // When scaling, we must rotate only when blending the sketch canvas to
21884    // avoid interpolation
21885    if (!sketchScale) {
21886        if (tiledImage.viewport.degrees !== 0) {
21887            tiledImage._drawer._offsetForRotation({
21888                degrees: tiledImage.viewport.degrees,
21889                useSketch: useSketch
21890            });
21891        }
21892        if (tiledImage.getRotation(true) % 360 !== 0) {
21893            tiledImage._drawer._offsetForRotation({
21894                degrees: tiledImage.getRotation(true),
21895                point: tiledImage.viewport.pixelFromPointNoRotate(
21896                    tiledImage._getRotationPoint(true), true),
21897                useSketch: useSketch
21898            });
21899        }
21900
21901        if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
21902          if(tiledImage._drawer.viewer.viewport.getFlip()) {
21903              tiledImage._drawer._flip();
21904          }
21905        }
21906    }
21907
21908    var usedClip = false;
21909    if ( tiledImage._clip ) {
21910        tiledImage._drawer.saveContext(useSketch);
21911
21912        var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
21913        box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
21914        var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
21915        if (sketchScale) {
21916            clipRect = clipRect.times(sketchScale);
21917        }
21918        if (sketchTranslate) {
21919            clipRect = clipRect.translate(sketchTranslate);
21920        }
21921        tiledImage._drawer.setClip(clipRect, useSketch);
21922
21923        usedClip = true;
21924    }
21925
21926    if (tiledImage._croppingPolygons) {
21927        tiledImage._drawer.saveContext(useSketch);
21928        try {
21929            var polygons = tiledImage._croppingPolygons.map(function (polygon) {
21930                return polygon.map(function (coord) {
21931                    var point = tiledImage
21932                        .imageToViewportCoordinates(coord.x, coord.y, true)
21933                        .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
21934                    var clipPoint = tiledImage._drawer.viewportCoordToDrawerCoord(point);
21935                    if (sketchScale) {
21936                        clipPoint = clipPoint.times(sketchScale);
21937                    }
21938                    return clipPoint;
21939                });
21940            });
21941            tiledImage._drawer.clipWithPolygons(polygons, useSketch);
21942        } catch (e) {
21943            $.console.error(e);
21944        }
21945        usedClip = true;
21946    }
21947
21948    if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
21949        var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
21950        if (sketchScale) {
21951            placeholderRect = placeholderRect.times(sketchScale);
21952        }
21953        if (sketchTranslate) {
21954            placeholderRect = placeholderRect.translate(sketchTranslate);
21955        }
21956
21957        var fillStyle = null;
21958        if ( typeof tiledImage.placeholderFillStyle === "function" ) {
21959            fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context);
21960        }
21961        else {
21962            fillStyle = tiledImage.placeholderFillStyle;
21963        }
21964
21965        tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
21966    }
21967
21968    for (var i = lastDrawn.length - 1; i >= 0; i--) {
21969        tile = lastDrawn[ i ];
21970        tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );
21971        tile.beingDrawn = true;
21972
21973        if( tiledImage.viewer ){
21974            /**
21975             * <em>- Needs documentation -</em>
21976             *
21977             * @event tile-drawn
21978             * @memberof OpenSeadragon.Viewer
21979             * @type {object}
21980             * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
21981             * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
21982             * @property {OpenSeadragon.Tile} tile
21983             * @property {?Object} userData - Arbitrary subscriber-defined object.
21984             */
21985            tiledImage.viewer.raiseEvent( 'tile-drawn', {
21986                tiledImage: tiledImage,
21987                tile: tile
21988            });
21989        }
21990    }
21991
21992    if ( usedClip ) {
21993        tiledImage._drawer.restoreContext( useSketch );
21994    }
21995
21996    if (!sketchScale) {
21997        if (tiledImage.getRotation(true) % 360 !== 0) {
21998            tiledImage._drawer._restoreRotationChanges(useSketch);
21999        }
22000        if (tiledImage.viewport.degrees !== 0) {
22001            tiledImage._drawer._restoreRotationChanges(useSketch);
22002        }
22003    }
22004
22005    if (useSketch) {
22006        if (sketchScale) {
22007            if (tiledImage.viewport.degrees !== 0) {
22008                tiledImage._drawer._offsetForRotation({
22009                    degrees: tiledImage.viewport.degrees,
22010                    useSketch: false
22011                });
22012            }
22013            if (tiledImage.getRotation(true) % 360 !== 0) {
22014                tiledImage._drawer._offsetForRotation({
22015                    degrees: tiledImage.getRotation(true),
22016                    point: tiledImage.viewport.pixelFromPointNoRotate(
22017                        tiledImage._getRotationPoint(true), true),
22018                    useSketch: false
22019                });
22020            }
22021        }
22022        tiledImage._drawer.blendSketch({
22023            opacity: tiledImage.opacity,
22024            scale: sketchScale,
22025            translate: sketchTranslate,
22026            compositeOperation: tiledImage.compositeOperation,
22027            bounds: bounds
22028        });
22029        if (sketchScale) {
22030            if (tiledImage.getRotation(true) % 360 !== 0) {
22031                tiledImage._drawer._restoreRotationChanges(false);
22032            }
22033            if (tiledImage.viewport.degrees !== 0) {
22034                tiledImage._drawer._restoreRotationChanges(false);
22035            }
22036        }
22037    }
22038
22039    if (!sketchScale) {
22040      if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
22041        if(tiledImage._drawer.viewer.viewport.getFlip()) {
22042            tiledImage._drawer._flip();
22043        }
22044      }
22045    }
22046
22047    drawDebugInfo( tiledImage, lastDrawn );
22048}
22049
22050/**
22051 * @private
22052 * @inner
22053 * Draws special debug information for a TiledImage if in debug mode.
22054 * @param {OpenSeadragon.TiledImage} tiledImage
22055 * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
22056 */
22057function drawDebugInfo( tiledImage, lastDrawn ) {
22058    if( tiledImage.debugMode ) {
22059        for ( var i = lastDrawn.length - 1; i >= 0; i-- ) {
22060            var tile = lastDrawn[ i ];
22061            try {
22062                tiledImage._drawer.drawDebugInfo(
22063                    tile, lastDrawn.length, i, tiledImage);
22064            } catch(e) {
22065                $.console.error(e);
22066            }
22067        }
22068    }
22069}
22070
22071}( OpenSeadragon ));
22072
22073/*
22074 * OpenSeadragon - TileCache
22075 *
22076 * Copyright (C) 2009 CodePlex Foundation
22077 * Copyright (C) 2010-2013 OpenSeadragon contributors
22078 *
22079 * Redistribution and use in source and binary forms, with or without
22080 * modification, are permitted provided that the following conditions are
22081 * met:
22082 *
22083 * - Redistributions of source code must retain the above copyright notice,
22084 *   this list of conditions and the following disclaimer.
22085 *
22086 * - Redistributions in binary form must reproduce the above copyright
22087 *   notice, this list of conditions and the following disclaimer in the
22088 *   documentation and/or other materials provided with the distribution.
22089 *
22090 * - Neither the name of CodePlex Foundation nor the names of its
22091 *   contributors may be used to endorse or promote products derived from
22092 *   this software without specific prior written permission.
22093 *
22094 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22095 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22096 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22097 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
22098 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22099 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22100 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22101 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22102 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22103 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22104 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22105 */
22106
22107(function( $ ){
22108
22109// private class
22110var TileRecord = function( options ) {
22111    $.console.assert( options, "[TileCache.cacheTile] options is required" );
22112    $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
22113    $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
22114    this.tile = options.tile;
22115    this.tiledImage = options.tiledImage;
22116};
22117
22118// private class
22119var ImageRecord = function(options) {
22120    $.console.assert( options, "[ImageRecord] options is required" );
22121    $.console.assert( options.image, "[ImageRecord] options.image is required" );
22122    this._image = options.image;
22123    this._tiles = [];
22124};
22125
22126ImageRecord.prototype = {
22127    destroy: function() {
22128        this._image = null;
22129        this._renderedContext = null;
22130        this._tiles = null;
22131    },
22132
22133    getImage: function() {
22134        return this._image;
22135    },
22136
22137    getRenderedContext: function() {
22138        if (!this._renderedContext) {
22139            var canvas = document.createElement( 'canvas' );
22140            canvas.width = this._image.width;
22141            canvas.height = this._image.height;
22142            this._renderedContext = canvas.getContext('2d');
22143            this._renderedContext.drawImage( this._image, 0, 0 );
22144            //since we are caching the prerendered image on a canvas
22145            //allow the image to not be held in memory
22146            this._image = null;
22147        }
22148        return this._renderedContext;
22149    },
22150
22151    setRenderedContext: function(renderedContext) {
22152        $.console.error("ImageRecord.setRenderedContext is deprecated. " +
22153                "The rendered context should be created by the ImageRecord " +
22154                "itself when calling ImageRecord.getRenderedContext.");
22155        this._renderedContext = renderedContext;
22156    },
22157
22158    addTile: function(tile) {
22159        $.console.assert(tile, '[ImageRecord.addTile] tile is required');
22160        this._tiles.push(tile);
22161    },
22162
22163    removeTile: function(tile) {
22164        for (var i = 0; i < this._tiles.length; i++) {
22165            if (this._tiles[i] === tile) {
22166                this._tiles.splice(i, 1);
22167                return;
22168            }
22169        }
22170
22171        $.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);
22172    },
22173
22174    getTileCount: function() {
22175        return this._tiles.length;
22176    }
22177};
22178
22179/**
22180 * @class TileCache
22181 * @memberof OpenSeadragon
22182 * @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}.
22183 * You generally won't have to interact with the TileCache directly.
22184 * @param {Object} options - Configuration for this TileCache.
22185 * @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in
22186 * {@link OpenSeadragon.Options} for details.
22187 */
22188$.TileCache = function( options ) {
22189    options = options || {};
22190
22191    this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;
22192    this._tilesLoaded = [];
22193    this._imagesLoaded = [];
22194    this._imagesLoadedCount = 0;
22195};
22196
22197/** @lends OpenSeadragon.TileCache.prototype */
22198$.TileCache.prototype = {
22199    /**
22200     * @returns {Number} The total number of tiles that have been loaded by
22201     * this TileCache.
22202     */
22203    numTilesLoaded: function() {
22204        return this._tilesLoaded.length;
22205    },
22206
22207    /**
22208     * Caches the specified tile, removing an old tile if necessary to stay under the
22209     * maxImageCacheCount specified on construction. Note that if multiple tiles reference
22210     * the same image, there may be more tiles than maxImageCacheCount; the goal is to keep
22211     * the number of images below that number. Note, as well, that even the number of images
22212     * may temporarily surpass that number, but should eventually come back down to the max specified.
22213     * @param {Object} options - Tile info.
22214     * @param {OpenSeadragon.Tile} options.tile - The tile to cache.
22215     * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
22216     * @param {Image} options.image - The image of the tile to cache.
22217     * @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
22218     * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
22219     * function will release an old tile. The cutoff option specifies a tile level at or below which
22220     * tiles will not be released.
22221     */
22222    cacheTile: function( options ) {
22223        $.console.assert( options, "[TileCache.cacheTile] options is required" );
22224        $.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
22225        $.console.assert( options.tile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
22226        $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
22227
22228        var cutoff = options.cutoff || 0;
22229        var insertionIndex = this._tilesLoaded.length;
22230
22231        var imageRecord = this._imagesLoaded[options.tile.cacheKey];
22232        if (!imageRecord) {
22233            $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
22234            imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({
22235                image: options.image
22236            });
22237
22238            this._imagesLoadedCount++;
22239        }
22240
22241        imageRecord.addTile(options.tile);
22242        options.tile.cacheImageRecord = imageRecord;
22243
22244        // Note that just because we're unloading a tile doesn't necessarily mean
22245        // we're unloading an image. With repeated calls it should sort itself out, though.
22246        if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
22247            var worstTile       = null;
22248            var worstTileIndex  = -1;
22249            var worstTileRecord = null;
22250            var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
22251
22252            for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
22253                prevTileRecord = this._tilesLoaded[ i ];
22254                prevTile = prevTileRecord.tile;
22255
22256                if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {
22257                    continue;
22258                } else if ( !worstTile ) {
22259                    worstTile       = prevTile;
22260                    worstTileIndex  = i;
22261                    worstTileRecord = prevTileRecord;
22262                    continue;
22263                }
22264
22265                prevTime    = prevTile.lastTouchTime;
22266                worstTime   = worstTile.lastTouchTime;
22267                prevLevel   = prevTile.level;
22268                worstLevel  = worstTile.level;
22269
22270                if ( prevTime < worstTime ||
22271                   ( prevTime == worstTime && prevLevel > worstLevel ) ) {
22272                    worstTile       = prevTile;
22273                    worstTileIndex  = i;
22274                    worstTileRecord = prevTileRecord;
22275                }
22276            }
22277
22278            if ( worstTile && worstTileIndex >= 0 ) {
22279                this._unloadTile(worstTileRecord);
22280                insertionIndex = worstTileIndex;
22281            }
22282        }
22283
22284        this._tilesLoaded[ insertionIndex ] = new TileRecord({
22285            tile: options.tile,
22286            tiledImage: options.tiledImage
22287        });
22288    },
22289
22290    /**
22291     * Clears all tiles associated with the specified tiledImage.
22292     * @param {OpenSeadragon.TiledImage} tiledImage
22293     */
22294    clearTilesFor: function( tiledImage ) {
22295        $.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required');
22296        var tileRecord;
22297        for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
22298            tileRecord = this._tilesLoaded[ i ];
22299            if ( tileRecord.tiledImage === tiledImage ) {
22300                this._unloadTile(tileRecord);
22301                this._tilesLoaded.splice( i, 1 );
22302                i--;
22303            }
22304        }
22305    },
22306
22307    // private
22308    getImageRecord: function(cacheKey) {
22309        $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');
22310        return this._imagesLoaded[cacheKey];
22311    },
22312
22313    // private
22314    _unloadTile: function(tileRecord) {
22315        $.console.assert(tileRecord, '[TileCache._unloadTile] tileRecord is required');
22316        var tile = tileRecord.tile;
22317        var tiledImage = tileRecord.tiledImage;
22318
22319        tile.unload();
22320        tile.cacheImageRecord = null;
22321
22322        var imageRecord = this._imagesLoaded[tile.cacheKey];
22323        imageRecord.removeTile(tile);
22324        if (!imageRecord.getTileCount()) {
22325            imageRecord.destroy();
22326            delete this._imagesLoaded[tile.cacheKey];
22327            this._imagesLoadedCount--;
22328        }
22329
22330        /**
22331         * Triggered when a tile has just been unloaded from memory.
22332         *
22333         * @event tile-unloaded
22334         * @memberof OpenSeadragon.Viewer
22335         * @type {object}
22336         * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the unloaded tile.
22337         * @property {OpenSeadragon.Tile} tile - The tile which has been unloaded.
22338         */
22339        tiledImage.viewer.raiseEvent("tile-unloaded", {
22340            tile: tile,
22341            tiledImage: tiledImage
22342        });
22343    }
22344};
22345
22346}( OpenSeadragon ));
22347
22348/*
22349 * OpenSeadragon - World
22350 *
22351 * Copyright (C) 2009 CodePlex Foundation
22352 * Copyright (C) 2010-2013 OpenSeadragon contributors
22353 *
22354 * Redistribution and use in source and binary forms, with or without
22355 * modification, are permitted provided that the following conditions are
22356 * met:
22357 *
22358 * - Redistributions of source code must retain the above copyright notice,
22359 *   this list of conditions and the following disclaimer.
22360 *
22361 * - Redistributions in binary form must reproduce the above copyright
22362 *   notice, this list of conditions and the following disclaimer in the
22363 *   documentation and/or other materials provided with the distribution.
22364 *
22365 * - Neither the name of CodePlex Foundation nor the names of its
22366 *   contributors may be used to endorse or promote products derived from
22367 *   this software without specific prior written permission.
22368 *
22369 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22370 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22371 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22372 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
22373 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22374 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22375 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22376 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22377 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22378 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22379 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22380 */
22381
22382(function( $ ){
22383
22384/**
22385 * @class World
22386 * @memberof OpenSeadragon
22387 * @extends OpenSeadragon.EventSource
22388 * @classdesc Keeps track of all of the tiled images in the scene.
22389 * @param {Object} options - World options.
22390 * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World.
22391 **/
22392$.World = function( options ) {
22393    var _this = this;
22394
22395    $.console.assert( options.viewer, "[World] options.viewer is required" );
22396
22397    $.EventSource.call( this );
22398
22399    this.viewer = options.viewer;
22400    this._items = [];
22401    this._needsDraw = false;
22402    this._autoRefigureSizes = true;
22403    this._needsSizesFigured = false;
22404    this._delegatedFigureSizes = function(event) {
22405        if (_this._autoRefigureSizes) {
22406            _this._figureSizes();
22407        } else {
22408            _this._needsSizesFigured = true;
22409        }
22410    };
22411
22412    this._figureSizes();
22413};
22414
22415$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{
22416    /**
22417     * Add the specified item.
22418     * @param {OpenSeadragon.TiledImage} item - The item to add.
22419     * @param {Number} [options.index] - Index for the item. If not specified, goes at the top.
22420     * @fires OpenSeadragon.World.event:add-item
22421     * @fires OpenSeadragon.World.event:metrics-change
22422     */
22423    addItem: function( item, options ) {
22424        $.console.assert(item, "[World.addItem] item is required");
22425        $.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time");
22426
22427        options = options || {};
22428        if (options.index !== undefined) {
22429            var index = Math.max(0, Math.min(this._items.length, options.index));
22430            this._items.splice(index, 0, item);
22431        } else {
22432            this._items.push( item );
22433        }
22434
22435        if (this._autoRefigureSizes) {
22436            this._figureSizes();
22437        } else {
22438            this._needsSizesFigured = true;
22439        }
22440
22441        this._needsDraw = true;
22442
22443        item.addHandler('bounds-change', this._delegatedFigureSizes);
22444        item.addHandler('clip-change', this._delegatedFigureSizes);
22445
22446        /**
22447         * Raised when an item is added to the World.
22448         * @event add-item
22449         * @memberOf OpenSeadragon.World
22450         * @type {object}
22451         * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.
22452         * @property {OpenSeadragon.TiledImage} item - The item that has been added.
22453         * @property {?Object} userData - Arbitrary subscriber-defined object.
22454         */
22455        this.raiseEvent( 'add-item', {
22456            item: item
22457        } );
22458    },
22459
22460    /**
22461     * Get the item at the specified index.
22462     * @param {Number} index - The item's index.
22463     * @returns {OpenSeadragon.TiledImage} The item at the specified index.
22464     */
22465    getItemAt: function( index ) {
22466        $.console.assert(index !== undefined, "[World.getItemAt] index is required");
22467        return this._items[ index ];
22468    },
22469
22470    /**
22471     * Get the index of the given item or -1 if not present.
22472     * @param {OpenSeadragon.TiledImage} item - The item.
22473     * @returns {Number} The index of the item or -1 if not present.
22474     */
22475    getIndexOfItem: function( item ) {
22476        $.console.assert(item, "[World.getIndexOfItem] item is required");
22477        return $.indexOf( this._items, item );
22478    },
22479
22480    /**
22481     * @returns {Number} The number of items used.
22482     */
22483    getItemCount: function() {
22484        return this._items.length;
22485    },
22486
22487    /**
22488     * Change the index of a item so that it appears over or under others.
22489     * @param {OpenSeadragon.TiledImage} item - The item to move.
22490     * @param {Number} index - The new index.
22491     * @fires OpenSeadragon.World.event:item-index-change
22492     */
22493    setItemIndex: function( item, index ) {
22494        $.console.assert(item, "[World.setItemIndex] item is required");
22495        $.console.assert(index !== undefined, "[World.setItemIndex] index is required");
22496
22497        var oldIndex = this.getIndexOfItem( item );
22498
22499        if ( index >= this._items.length ) {
22500            throw new Error( "Index bigger than number of layers." );
22501        }
22502
22503        if ( index === oldIndex || oldIndex === -1 ) {
22504            return;
22505        }
22506
22507        this._items.splice( oldIndex, 1 );
22508        this._items.splice( index, 0, item );
22509        this._needsDraw = true;
22510
22511        /**
22512         * Raised when the order of the indexes has been changed.
22513         * @event item-index-change
22514         * @memberOf OpenSeadragon.World
22515         * @type {object}
22516         * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
22517         * @property {OpenSeadragon.TiledImage} item - The item whose index has
22518         * been changed
22519         * @property {Number} previousIndex - The previous index of the item
22520         * @property {Number} newIndex - The new index of the item
22521         * @property {?Object} userData - Arbitrary subscriber-defined object.
22522         */
22523        this.raiseEvent( 'item-index-change', {
22524            item: item,
22525            previousIndex: oldIndex,
22526            newIndex: index
22527        } );
22528    },
22529
22530    /**
22531     * Remove an item.
22532     * @param {OpenSeadragon.TiledImage} item - The item to remove.
22533     * @fires OpenSeadragon.World.event:remove-item
22534     * @fires OpenSeadragon.World.event:metrics-change
22535     */
22536    removeItem: function( item ) {
22537        $.console.assert(item, "[World.removeItem] item is required");
22538
22539        var index = $.indexOf(this._items, item );
22540        if ( index === -1 ) {
22541            return;
22542        }
22543
22544        item.removeHandler('bounds-change', this._delegatedFigureSizes);
22545        item.removeHandler('clip-change', this._delegatedFigureSizes);
22546        item.destroy();
22547        this._items.splice( index, 1 );
22548        this._figureSizes();
22549        this._needsDraw = true;
22550        this._raiseRemoveItem(item);
22551    },
22552
22553    /**
22554     * Remove all items.
22555     * @fires OpenSeadragon.World.event:remove-item
22556     * @fires OpenSeadragon.World.event:metrics-change
22557     */
22558    removeAll: function() {
22559        // We need to make sure any pending images are canceled so the world items don't get messed up
22560        this.viewer._cancelPendingImages();
22561        var item;
22562        var i;
22563        for (i = 0; i < this._items.length; i++) {
22564            item = this._items[i];
22565            item.removeHandler('bounds-change', this._delegatedFigureSizes);
22566            item.removeHandler('clip-change', this._delegatedFigureSizes);
22567            item.destroy();
22568        }
22569
22570        var removedItems = this._items;
22571        this._items = [];
22572        this._figureSizes();
22573        this._needsDraw = true;
22574
22575        for (i = 0; i < removedItems.length; i++) {
22576            item = removedItems[i];
22577            this._raiseRemoveItem(item);
22578        }
22579    },
22580
22581    /**
22582     * Clears all tiles and triggers updates for all items.
22583     */
22584    resetItems: function() {
22585        for ( var i = 0; i < this._items.length; i++ ) {
22586            this._items[i].reset();
22587        }
22588    },
22589
22590    /**
22591     * Updates (i.e. animates bounds of) all items.
22592     */
22593    update: function() {
22594        var animated = false;
22595        for ( var i = 0; i < this._items.length; i++ ) {
22596            animated = this._items[i].update() || animated;
22597        }
22598
22599        return animated;
22600    },
22601
22602    /**
22603     * Draws all items.
22604     */
22605    draw: function() {
22606        for ( var i = 0; i < this._items.length; i++ ) {
22607            this._items[i].draw();
22608        }
22609
22610        this._needsDraw = false;
22611    },
22612
22613    /**
22614     * @returns {Boolean} true if any items need updating.
22615     */
22616    needsDraw: function() {
22617        for ( var i = 0; i < this._items.length; i++ ) {
22618            if ( this._items[i].needsDraw() ) {
22619                return true;
22620            }
22621        }
22622        return this._needsDraw;
22623    },
22624
22625    /**
22626     * @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates.
22627     */
22628    getHomeBounds: function() {
22629        return this._homeBounds.clone();
22630    },
22631
22632    /**
22633     * To facilitate zoom constraints, we keep track of the pixel density of the
22634     * densest item in the World (i.e. the item whose content size to viewport size
22635     * ratio is the highest) and save it as this "content factor".
22636     * @returns {Number} the number of content units per viewport unit.
22637     */
22638    getContentFactor: function() {
22639        return this._contentFactor;
22640    },
22641
22642    /**
22643     * As a performance optimization, setting this flag to false allows the bounds-change event handler
22644     * on tiledImages to skip calculations on the world bounds. If a lot of images are going to be positioned in
22645     * rapid succession, this is a good idea. When finished, setAutoRefigureSizes should be called with true
22646     * or the system may behave oddly.
22647     * @param {Boolean} [value] The value to which to set the flag.
22648     */
22649    setAutoRefigureSizes: function(value) {
22650        this._autoRefigureSizes = value;
22651        if (value & this._needsSizesFigured) {
22652            this._figureSizes();
22653            this._needsSizesFigured = false;
22654        }
22655    },
22656
22657    /**
22658     * Arranges all of the TiledImages with the specified settings.
22659     * @param {Object} options - Specifies how to arrange.
22660     * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
22661     * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
22662     * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.
22663     * @param {Number} [options.columns] - See collectionColumns in {@link OpenSeadragon.Options}.
22664     * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
22665     * @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
22666     * @fires OpenSeadragon.World.event:metrics-change
22667     */
22668    arrange: function(options) {
22669        options = options || {};
22670        var immediately = options.immediately || false;
22671        var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
22672        var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
22673        var columns = options.columns || $.DEFAULT_SETTINGS.collectionColumns;
22674        var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
22675        var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
22676        var increment = tileSize + tileMargin;
22677        var wrap;
22678        if (!options.rows && columns) {
22679            wrap = columns;
22680        } else {
22681            wrap = Math.ceil(this._items.length / rows);
22682        }
22683        var x = 0;
22684        var y = 0;
22685        var item, box, width, height, position;
22686
22687        this.setAutoRefigureSizes(false);
22688        for (var i = 0; i < this._items.length; i++) {
22689            if (i && (i % wrap) === 0) {
22690                if (layout === 'horizontal') {
22691                    y += increment;
22692                    x = 0;
22693                } else {
22694                    x += increment;
22695                    y = 0;
22696                }
22697            }
22698
22699            item = this._items[i];
22700            box = item.getBounds();
22701            if (box.width > box.height) {
22702                width = tileSize;
22703            } else {
22704                width = tileSize * (box.width / box.height);
22705            }
22706
22707            height = width * (box.height / box.width);
22708            position = new $.Point(x + ((tileSize - width) / 2),
22709                y + ((tileSize - height) / 2));
22710
22711            item.setPosition(position, immediately);
22712            item.setWidth(width, immediately);
22713
22714            if (layout === 'horizontal') {
22715                x += increment;
22716            } else {
22717                y += increment;
22718            }
22719        }
22720        this.setAutoRefigureSizes(true);
22721    },
22722
22723    // private
22724    _figureSizes: function() {
22725        var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null;
22726        var oldContentSize = this._contentSize ? this._contentSize.clone() : null;
22727        var oldContentFactor = this._contentFactor || 0;
22728
22729        if (!this._items.length) {
22730            this._homeBounds = new $.Rect(0, 0, 1, 1);
22731            this._contentSize = new $.Point(1, 1);
22732            this._contentFactor = 1;
22733        } else {
22734            var item = this._items[0];
22735            var bounds = item.getBounds();
22736            this._contentFactor = item.getContentSize().x / bounds.width;
22737            var clippedBounds = item.getClippedBounds().getBoundingBox();
22738            var left = clippedBounds.x;
22739            var top = clippedBounds.y;
22740            var right = clippedBounds.x + clippedBounds.width;
22741            var bottom = clippedBounds.y + clippedBounds.height;
22742            for (var i = 1; i < this._items.length; i++) {
22743                item = this._items[i];
22744                bounds = item.getBounds();
22745                this._contentFactor = Math.max(this._contentFactor,
22746                    item.getContentSize().x / bounds.width);
22747                clippedBounds = item.getClippedBounds().getBoundingBox();
22748                left = Math.min(left, clippedBounds.x);
22749                top = Math.min(top, clippedBounds.y);
22750                right = Math.max(right, clippedBounds.x + clippedBounds.width);
vendor: 1,623 bytes, lines 22751-22792
22751                bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);
22752            }
22753
22754            this._homeBounds = new $.Rect(left, top, right - left, bottom - top);
22755            this._contentSize = new $.Point(
22756                this._homeBounds.width * this._contentFactor,
22757                this._homeBounds.height * this._contentFactor);
22758        }
22759
22760        if (this._contentFactor !== oldContentFactor ||
22761            !this._homeBounds.equals(oldHomeBounds) ||
22762            !this._contentSize.equals(oldContentSize)) {
22763            /**
22764             * Raised when the home bounds or content factor change.
22765             * @event metrics-change
22766             * @memberOf OpenSeadragon.World
22767             * @type {object}
22768             * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
22769             * @property {?Object} userData - Arbitrary subscriber-defined object.
22770             */
22771            this.raiseEvent('metrics-change', {});
22772        }
22773    },
22774
22775    // private
22776    _raiseRemoveItem: function(item) {
22777        /**
22778         * Raised when an item is removed.
22779         * @event remove-item
22780         * @memberOf OpenSeadragon.World
22781         * @type {object}
22782         * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
22783         * @property {OpenSeadragon.TiledImage} item - The item's underlying item.
22784         * @property {?Object} userData - Arbitrary subscriber-defined object.
22785         */
22786        this.raiseEvent( 'remove-item', { item: item } );
22787    }
22788});
22789
22790}( OpenSeadragon ));
22791
22792//# sourceMappingURL=openseadragon.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.