\n * OpenSeadragon provides an html interface for creating\n * deep zoom user interfaces. The simplest examples include deep\n * zoom for large resolution images, and complex examples include\n * zoomable map interfaces driven by SVG files.\n *
\n *\n */\n\n/**\n * @module OpenSeadragon\n *\n */\n\n/**\n * @namespace OpenSeadragon\n *\n * @classdesc The root namespace for OpenSeadragon. All utility methods\n * and classes are defined on or below this namespace.\n *\n */\n\n\n// Typedefs\n\n /**\n * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer.\n *\n * @typedef {Object} Options\n * @memberof OpenSeadragon\n *\n * @property {String} id\n * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided.\n * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.\n *\n * @property {Element} element\n * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.\n * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.\n *\n * @property {Array|String|Function|Object} [tileSources=null]\n * Tile source(s) to open initially. This is a complex parameter; see\n * {@link OpenSeadragon.Viewer#open} for details.\n *\n * @property {Number} [tabIndex=0]\n * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0\n * source order is used. A negative value omits the viewer from the tabbing order.\n *\n * @property {Array} overlays Array of objects defining permanent overlays of\n * the viewer. The overlays added via this option and later removed with\n * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new\n * image is opened.\n * To add overlays which can be definitively removed, one must use\n * {@link OpenSeadragon.Viewer#addOverlay}\n * If displaying a sequence of images, the overlays can be associated\n * with a specific page by passing the overlays array to the page's\n * tile source configuration.\n * Expected properties:\n * * x, y, (or px, py for pixel coordinates) to define the location.\n * * width, height in point if using x,y or in pixels if using px,py. If width\n * and height are specified, the overlay size is adjusted when zooming,\n * otherwise the size stays the size of the content (or the size defined by CSS).\n * * className to associate a class to the overlay\n * * id to set the overlay element. If an element with this id already exists,\n * it is reused, otherwise it is created. If not specified, a new element is\n * created.\n * * placement a string to define the relative position to the viewport.\n * Only used if no width and height are specified. Default: 'TOP_LEFT'.\n * See {@link OpenSeadragon.OverlayPlacement} for possible values.\n *\n * @property {String} [xmlPath=null]\n * DEPRECATED. A relative path to load a DZI file from the server.\n * Prefer the newer Options.tileSources.\n *\n * @property {String} [prefixUrl='/images/']\n * Prepends the prefixUrl to navImages paths, which is very useful\n * since the default paths are rarely useful for production\n * environments.\n *\n * @property {OpenSeadragon.NavImages} [navImages]\n * An object with a property for each button or other built-in navigation\n * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.\n * Each of those in turn provides an image path for each state of the button\n * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the\n * image paths, by default assume there is a folder on the servers root path\n * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust\n * these paths, prefer setting the option.prefixUrl rather than overriding\n * every image path directly through this setting.\n *\n * @property {Boolean} [debugMode=false]\n * TODO: provide an in-screen panel providing event detail feedback.\n *\n * @property {String} [debugGridColor='#437AB2']\n *\n * @property {Number} [blendTime=0]\n * Specifies the duration of animation as higher or lower level tiles are\n * replacing the existing tile.\n *\n * @property {Boolean} [alwaysBlend=false]\n * Forces the tile to always blend. By default the tiles skip blending\n * when the blendTime is surpassed and the current animation frame would\n * not complete the blend.\n *\n * @property {Boolean} [autoHideControls=true]\n * If the user stops interacting with the viewport, fade the navigation\n * controls. Useful for presentation since the controls are by default\n * floated on top of the image the user is viewing.\n *\n * @property {Boolean} [immediateRender=false]\n * Render the best closest level first, ignoring the lowering levels which\n * provide the effect of very blurry to sharp. It is recommended to change\n * setting to true for mobile devices.\n *\n * @property {Number} [defaultZoomLevel=0]\n * Zoom level to use when image is first opened or the home button is clicked.\n * If 0, adjusts to fit viewer.\n *\n * @property {Number} [opacity=1]\n * Default opacity of the tiled images (1=opaque, 0=transparent)\n *\n * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]\n * Draws a colored rectangle behind the tile if it is not loaded yet.\n * You can pass a CSS color value like \"#FF8800\".\n * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern.\n *\n * @property {Number} [degrees=0]\n * Initial rotation.\n *\n * @property {Number} [minZoomLevel=null]\n *\n * @property {Number} [maxZoomLevel=null]\n *\n * @property {Boolean} [homeFillsViewer=false]\n * Make the 'home' button fill the viewer and clip the image, instead\n * of fitting the image to the viewer and letterboxing.\n *\n * @property {Boolean} [panHorizontal=true]\n * Allow horizontal pan.\n *\n * @property {Boolean} [panVertical=true]\n * Allow vertical pan.\n *\n * @property {Boolean} [constrainDuringPan=false]\n *\n * @property {Boolean} [wrapHorizontal=false]\n * Set to true to force the image to wrap horizontally within the viewport.\n * Useful for maps or images representing the surface of a sphere or cylinder.\n *\n * @property {Boolean} [wrapVertical=false]\n * Set to true to force the image to wrap vertically within the viewport.\n * Useful for maps or images representing the surface of a sphere or cylinder.\n *\n * @property {Number} [minZoomImageRatio=0.9]\n * The minimum percentage ( expressed as a number between 0 and 1 ) of\n * the viewport height or width at which the zoom out will be constrained.\n * Setting it to 0, for example will allow you to zoom out infinity.\n *\n * @property {Number} [maxZoomPixelRatio=1.1]\n * The maximum ratio to allow a zoom-in to affect the highest level pixel\n * ratio. This can be set to Infinity to allow 'infinite' zooming into the\n * image though it is less effective visually if the HTML5 Canvas is not\n * availble on the viewing device.\n *\n * @property {Boolean} [autoResize=true]\n * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.\n *\n * @property {Boolean} [preserveImageSizeOnResize=false]\n * Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).\n *\n * @property {Number} [minScrollDeltaTime=50]\n * Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll\n * events between different devices, causing the faster devices to slow down enough to make the zoom control\n * more manageable.\n *\n * @property {Number} [pixelsPerWheelLine=40]\n * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.\n *\n * @property {Number} [visibilityRatio=0.5]\n * The percentage ( as a number from 0 to 1 ) of the source image which\n * must be kept within the viewport. If the image is dragged beyond that\n * limit, it will 'bounce' back until the minimum visibility ratio is\n * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to\n * true will provide the effect of an infinitely scrolling viewport.\n *\n * @property {Object} [viewportMargins={}]\n * Pushes the \"home\" region in from the sides by the specified amounts.\n * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.\n *\n * @property {Number} [imageLoaderLimit=0]\n * The maximum number of image requests to make concurrently. By default\n * it is set to 0 allowing the browser to make the maximum number of\n * image requests in parallel as allowed by the browsers policy.\n *\n * @property {Number} [clickTimeThreshold=300]\n * The number of milliseconds within which a pointer down-up event combination\n * will be treated as a click gesture.\n *\n * @property {Number} [clickDistThreshold=5]\n * The maximum distance allowed between a pointer down event and a pointer up event\n * to be treated as a click gesture.\n *\n * @property {Number} [dblClickTimeThreshold=300]\n * The number of milliseconds within which two pointer down-up event combinations\n * will be treated as a double-click gesture.\n *\n * @property {Number} [dblClickDistThreshold=20]\n * The maximum distance allowed between two pointer click events\n * to be treated as a double-click gesture.\n *\n * @property {Number} [springStiffness=6.5]\n *\n * @property {Number} [animationTime=1.2]\n * Specifies the animation duration per each {@link OpenSeadragon.Spring}\n * which occur when the image is dragged or zoomed.\n *\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]\n * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})\n * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture\n * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture\n * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true\n * then clickToZoom should be set to false to prevent multiple zooms.\n * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture\n * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture\n * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\n * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\n * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\n *\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]\n * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})\n * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture\n * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture\n * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true\n * then clickToZoom should be set to false to prevent multiple zooms.\n * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture\n * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture\n * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\n * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\n * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\n *\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]\n * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})\n * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture\n * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture\n * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true\n * then clickToZoom should be set to false to prevent multiple zooms.\n * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture\n * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture\n * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\n * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\n * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\n *\n * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]\n * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})\n * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture\n * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture\n * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true\n * then clickToZoom should be set to false to prevent multiple zooms.\n * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture\n * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture\n * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)\n * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture\n * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.\n *\n * @property {Number} [zoomPerClick=2.0]\n * The \"zoom distance\" per mouse click or touch tap. Note: Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).\n *\n * @property {Number} [zoomPerScroll=1.2]\n * The \"zoom distance\" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).\n *\n * @property {Number} [zoomPerSecond=1.0]\n * The number of seconds to animate a single zoom event over.\n *\n * @property {Boolean} [showNavigator=false]\n * Set to true to make the navigator minimap appear.\n *\n * @property {String} [navigatorId=navigator-GENERATED DATE]\n * The ID of a div to hold the navigator minimap.\n * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored.\n * If an ID is not specified, a div element will be generated and placed on top of the main image.\n *\n * @property {String} [navigatorPosition='TOP_RIGHT']\n * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.rendered
is the context with the pre-drawn image.\n */\n drawCanvas: function( context, drawingHandler ) {\n\n var position = this.position,\n size = this.size,\n rendered;\n\n if (!this.context2D && !this.cacheImageRecord) {\n $.console.warn(\n '[Tile.drawCanvas] attempting to draw tile %s when it\\'s not cached',\n this.toString());\n return;\n }\n\n rendered = this.context2D || this.cacheImageRecord.getRenderedContext();\n\n if ( !this.loaded || !rendered ){\n $.console.warn(\n \"Attempting to draw tile %s when it's not yet loaded.\",\n this.toString()\n );\n\n return;\n }\n\n context.save();\n\n context.globalAlpha = this.opacity;\n\n //if we are supposed to be rendering fully opaque rectangle,\n //ie its done fading or fading is turned off, and if we are drawing\n //an image with an alpha channel, then the only way\n //to avoid seeing the tile underneath is to clear the rectangle\n if (context.globalAlpha === 1 &&\n (this.context2D || this.url.match('.png'))) {\n //clearing only the inside of the rectangle occupied\n //by the png prevents edge flikering\n context.clearRect(\n (position.x * $.pixelDensityRatio)+1,\n (position.y * $.pixelDensityRatio)+1,\n (size.x * $.pixelDensityRatio)-2,\n (size.y * $.pixelDensityRatio)-2\n );\n\n }\n\n // This gives the application a chance to make image manipulation\n // changes as we are rendering the image\n drawingHandler({context: context, tile: this, rendered: rendered});\n\n context.drawImage(\n rendered.canvas,\n 0,\n 0,\n rendered.canvas.width,\n rendered.canvas.height,\n position.x * $.pixelDensityRatio,\n position.y * $.pixelDensityRatio,\n size.x * $.pixelDensityRatio,\n size.y * $.pixelDensityRatio\n );\n\n context.restore();\n },\n\n /**\n * Removes tile from its container.\n * @function\n */\n unload: function() {\n if ( this.imgElement && this.imgElement.parentNode ) {\n this.imgElement.parentNode.removeChild( this.imgElement );\n }\n if ( this.element && this.element.parentNode ) {\n this.element.parentNode.removeChild( this.element );\n }\n\n this.element = null;\n this.imgElement = null;\n this.loaded = false;\n this.loading = false;\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - Overlay\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n /**\n * An enumeration of positions that an overlay may be assigned relative to\n * the viewport.\n * @member OverlayPlacement\n * @memberof OpenSeadragon\n * @static\n * @type {Object}\n * @property {Number} CENTER\n * @property {Number} TOP_LEFT\n * @property {Number} TOP\n * @property {Number} TOP_RIGHT\n * @property {Number} RIGHT\n * @property {Number} BOTTOM_RIGHT\n * @property {Number} BOTTOM\n * @property {Number} BOTTOM_LEFT\n * @property {Number} LEFT\n */\n $.OverlayPlacement = {\n CENTER: 0,\n TOP_LEFT: 1,\n TOP: 2,\n TOP_RIGHT: 3,\n RIGHT: 4,\n BOTTOM_RIGHT: 5,\n BOTTOM: 6,\n BOTTOM_LEFT: 7,\n LEFT: 8\n };\n\n /**\n * @class Overlay\n * @classdesc Provides a way to float an HTML element on top of the viewer element.\n *\n * @memberof OpenSeadragon\n * @param {Object} options\n * @param {Element} options.element\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The\n * location of the overlay on the image. If a {@link OpenSeadragon.Point}\n * is specified, the overlay will keep a constant size independently of the\n * zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will\n * be adjusted when the zoom changes.\n * @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT]\n * Relative position to the viewport.\n * Only used if location is a {@link OpenSeadragon.Point}.\n * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]\n * @param {Boolean} [options.checkResize=true] Set to false to avoid to\n * check the size of the overlay everytime it is drawn when using a\n * {@link OpenSeadragon.Point} as options.location. It will improve\n * performances but will cause a misalignment if the overlay size changes.\n */\n $.Overlay = function( element, location, placement ) {\n\n /**\n * onDraw callback signature used by {@link OpenSeadragon.Overlay}.\n *\n * @callback OnDrawCallback\n * @memberof OpenSeadragon.Overlay\n * @param {OpenSeadragon.Point} position\n * @param {OpenSeadragon.Point} size\n * @param {Element} element\n */\n\n var options;\n if ( $.isPlainObject( element ) ) {\n options = element;\n } else {\n options = {\n element: element,\n location: location,\n placement: placement\n };\n }\n\n this.element = options.element;\n this.scales = options.location instanceof $.Rect;\n this.bounds = new $.Rect(\n options.location.x,\n options.location.y,\n options.location.width,\n options.location.height\n );\n this.position = new $.Point(\n options.location.x,\n options.location.y\n );\n this.size = new $.Point(\n options.location.width,\n options.location.height\n );\n this.style = options.element.style;\n // rects are always top-left\n this.placement = options.location instanceof $.Point ?\n options.placement :\n $.OverlayPlacement.TOP_LEFT;\n this.onDraw = options.onDraw;\n this.checkResize = options.checkResize === undefined ?\n true : options.checkResize;\n };\n\n $.Overlay.prototype = /** @lends OpenSeadragon.Overlay.prototype */{\n\n /**\n * @function\n * @param {OpenSeadragon.OverlayPlacement} position\n * @param {OpenSeadragon.Point} size\n */\n adjust: function( position, size ) {\n switch ( this.placement ) {\n case $.OverlayPlacement.TOP_LEFT:\n break;\n case $.OverlayPlacement.TOP:\n position.x -= size.x / 2;\n break;\n case $.OverlayPlacement.TOP_RIGHT:\n position.x -= size.x;\n break;\n case $.OverlayPlacement.RIGHT:\n position.x -= size.x;\n position.y -= size.y / 2;\n break;\n case $.OverlayPlacement.BOTTOM_RIGHT:\n position.x -= size.x;\n position.y -= size.y;\n break;\n case $.OverlayPlacement.BOTTOM:\n position.x -= size.x / 2;\n position.y -= size.y;\n break;\n case $.OverlayPlacement.BOTTOM_LEFT:\n position.y -= size.y;\n break;\n case $.OverlayPlacement.LEFT:\n position.y -= size.y / 2;\n break;\n default:\n case $.OverlayPlacement.CENTER:\n position.x -= size.x / 2;\n position.y -= size.y / 2;\n break;\n }\n },\n\n /**\n * @function\n */\n destroy: function() {\n var element = this.element,\n style = this.style;\n\n if ( element.parentNode ) {\n element.parentNode.removeChild( element );\n //this should allow us to preserve overlays when required between\n //pages\n if ( element.prevElementParent ) {\n style.display = 'none';\n //element.prevElementParent.insertBefore(\n // element,\n // element.prevNextSibling\n //);\n document.body.appendChild( element );\n }\n }\n\n // clear the onDraw callback\n this.onDraw = null;\n\n style.top = \"\";\n style.left = \"\";\n style.position = \"\";\n\n if ( this.scales ) {\n style.width = \"\";\n style.height = \"\";\n }\n },\n\n /**\n * @function\n * @param {Element} container\n */\n drawHTML: function( container, viewport ) {\n var element = this.element,\n style = this.style,\n scales = this.scales,\n degrees = viewport.degrees,\n position = viewport.pixelFromPoint(\n this.bounds.getTopLeft(),\n true\n ),\n size,\n overlayCenter;\n\n if ( element.parentNode != container ) {\n //save the source parent for later if we need it\n element.prevElementParent = element.parentNode;\n element.prevNextSibling = element.nextSibling;\n container.appendChild( element );\n this.size = $.getElementSize( element );\n }\n\n if ( scales ) {\n size = viewport.deltaPixelsFromPoints(\n this.bounds.getSize(),\n true\n );\n } else if ( this.checkResize ) {\n size = $.getElementSize( element );\n } else {\n size = this.size;\n }\n\n this.position = position;\n this.size = size;\n\n this.adjust( position, size );\n\n position = position.apply( Math.round );\n size = size.apply( Math.round );\n\n // rotate the position of the overlay\n // TODO only rotate overlays if in canvas mode\n // TODO replace the size rotation with CSS3 transforms\n // TODO add an option to overlays to not rotate with the image\n // Currently only rotates position and size\n if( degrees !== 0 && this.scales ) {\n overlayCenter = new $.Point( size.x / 2, size.y / 2 );\n\n var drawerCenter = new $.Point(\n viewport.viewer.drawer.canvas.width / 2,\n viewport.viewer.drawer.canvas.height / 2\n );\n position = position.plus( overlayCenter ).rotate(\n degrees,\n drawerCenter\n ).minus( overlayCenter );\n\n size = size.rotate( degrees, new $.Point( 0, 0 ) );\n size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) );\n }\n\n // call the onDraw callback if it exists to allow one to overwrite\n // the drawing/positioning/sizing of the overlay\n if ( this.onDraw ) {\n this.onDraw( position, size, element );\n } else {\n style.left = position.x + \"px\";\n style.top = position.y + \"px\";\n style.position = \"absolute\";\n\n if (style.display != 'none') {\n style.display = 'block';\n }\n\n if ( scales ) {\n style.width = size.x + \"px\";\n style.height = size.y + \"px\";\n }\n }\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location\n * @param {OpenSeadragon.OverlayPlacement} position\n */\n update: function( location, placement ) {\n this.scales = location instanceof $.Rect;\n this.bounds = new $.Rect(\n location.x,\n location.y,\n location.width,\n location.height\n );\n // rects are always top-left\n this.placement = location instanceof $.Point ?\n placement :\n $.OverlayPlacement.TOP_LEFT;\n }\n\n };\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - Drawer\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n/**\n * @class Drawer\n * @memberof OpenSeadragon\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\n * @param {Object} options - Options for this Drawer.\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.\n * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.\n * @param {Element} options.element - Parent element.\n * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.\n */\n$.Drawer = function( options ) {\n\n $.console.assert( options.viewer, \"[Drawer] options.viewer is required\" );\n\n //backward compatibility for positional args while prefering more\n //idiomatic javascript options object as the only argument\n var args = arguments;\n\n if( !$.isPlainObject( options ) ){\n options = {\n source: args[ 0 ], // Reference to Viewer tile source.\n viewport: args[ 1 ], // Reference to Viewer viewport.\n element: args[ 2 ] // Parent element.\n };\n }\n\n $.console.assert( options.viewport, \"[Drawer] options.viewport is required\" );\n $.console.assert( options.element, \"[Drawer] options.element is required\" );\n\n if ( options.source ) {\n $.console.error( \"[Drawer] options.source is no longer accepted; use TiledImage instead\" );\n }\n\n this.viewer = options.viewer;\n this.viewport = options.viewport;\n this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;\n if (options.opacity) {\n $.console.error( \"[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead\" );\n }\n\n this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );\n /**\n * The parent element of this Drawer instance, passed in when the Drawer was created.\n * The parent of {@link OpenSeadragon.Drawer#canvas}.\n * @member {Element} container\n * @memberof OpenSeadragon.Drawer#\n */\n this.container = $.getElement( options.element );\n /**\n * A <canvas> element if the browser supports them, otherwise a <div> element.\n * Child element of {@link OpenSeadragon.Drawer#container}.\n * @member {Element} canvas\n * @memberof OpenSeadragon.Drawer#\n */\n this.canvas = $.makeNeutralElement( this.useCanvas ? \"canvas\" : \"div\" );\n /**\n * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null.\n * @member {Object} context\n * @memberof OpenSeadragon.Drawer#\n */\n this.context = this.useCanvas ? this.canvas.getContext( \"2d\" ) : null;\n\n /**\n * Sketch canvas used to temporarily draw tiles which cannot be drawn directly\n * to the main canvas due to opacity. Lazily initialized.\n */\n this.sketchCanvas = null;\n this.sketchContext = null;\n\n /**\n * @member {Element} element\n * @memberof OpenSeadragon.Drawer#\n * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.\n */\n this.element = this.container;\n\n // We force our container to ltr because our drawing math doesn't work in rtl.\n // This issue only affects our canvas renderer, but we do it always for consistency.\n // Note that this means overlays you want to be rtl need to be explicitly set to rtl.\n this.container.dir = 'ltr';\n\n // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density\n if (this.useCanvas) {\n var viewportSize = this._calculateCanvasSize();\n this.canvas.width = viewportSize.x;\n this.canvas.height = viewportSize.y;\n }\n\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n this.canvas.style.position = \"absolute\";\n $.setElementOpacity( this.canvas, this.opacity, true );\n\n // explicit left-align\n this.container.style.textAlign = \"left\";\n this.container.appendChild( this.canvas );\n};\n\n$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{\n // deprecated\n addOverlay: function( element, location, placement, onDraw ) {\n $.console.error(\"drawer.addOverlay is deprecated. Use viewer.addOverlay instead.\");\n this.viewer.addOverlay( element, location, placement, onDraw );\n return this;\n },\n\n // deprecated\n updateOverlay: function( element, location, placement ) {\n $.console.error(\"drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.\");\n this.viewer.updateOverlay( element, location, placement );\n return this;\n },\n\n // deprecated\n removeOverlay: function( element ) {\n $.console.error(\"drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.\");\n this.viewer.removeOverlay( element );\n return this;\n },\n\n // deprecated\n clearOverlays: function() {\n $.console.error(\"drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.\");\n this.viewer.clearOverlays();\n return this;\n },\n\n /**\n * Set the opacity of the drawer.\n * @param {Number} opacity\n * @return {OpenSeadragon.Drawer} Chainable.\n */\n setOpacity: function( opacity ) {\n $.console.error(\"drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.\");\n var world = this.viewer.world;\n for (var i = 0; i < world.getItemCount(); i++) {\n world.getItemAt( i ).setOpacity( opacity );\n }\n return this;\n },\n\n /**\n * Get the opacity of the drawer.\n * @returns {Number}\n */\n getOpacity: function() {\n $.console.error(\"drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.\");\n var world = this.viewer.world;\n var maxOpacity = 0;\n for (var i = 0; i < world.getItemCount(); i++) {\n var opacity = world.getItemAt( i ).getOpacity();\n if ( opacity > maxOpacity ) {\n maxOpacity = opacity;\n }\n }\n return maxOpacity;\n },\n\n // deprecated\n needsUpdate: function() {\n $.console.error( \"[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead.\" );\n return this.viewer.world.needsDraw();\n },\n\n // deprecated\n numTilesLoaded: function() {\n $.console.error( \"[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead.\" );\n return this.viewer.tileCache.numTilesLoaded();\n },\n\n // deprecated\n reset: function() {\n $.console.error( \"[Drawer.reset] this function is deprecated. Use World.resetItems instead.\" );\n this.viewer.world.resetItems();\n return this;\n },\n\n // deprecated\n update: function() {\n $.console.error( \"[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead.\" );\n this.clear();\n this.viewer.world.draw();\n return this;\n },\n\n /**\n * @return {Boolean} True if rotation is supported.\n */\n canRotate: function() {\n return this.useCanvas;\n },\n\n /**\n * Destroy the drawer (unload current loaded tiles)\n */\n destroy: function() {\n //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)\n this.canvas.width = 1;\n this.canvas.height = 1;\n this.sketchCanvas = null;\n this.sketchContext = null;\n },\n\n /**\n * Clears the Drawer so it's ready to draw another frame.\n */\n clear: function() {\n this.canvas.innerHTML = \"\";\n if ( this.useCanvas ) {\n var viewportSize = this._calculateCanvasSize();\n if( this.canvas.width != viewportSize.x ||\n this.canvas.height != viewportSize.y ) {\n this.canvas.width = viewportSize.x;\n this.canvas.height = viewportSize.y;\n if ( this.sketchCanvas !== null ) {\n this.sketchCanvas.width = this.canvas.width;\n this.sketchCanvas.height = this.canvas.height;\n }\n }\n this._clear();\n }\n },\n\n _clear: function ( useSketch ) {\n if ( !this.useCanvas ) {\n return;\n }\n var context = this._getContext( useSketch );\n var canvas = context.canvas;\n context.clearRect( 0, 0, canvas.width, canvas.height );\n },\n\n /**\n * Translates from OpenSeadragon viewer rectangle to drawer rectangle.\n * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.\n * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.\n */\n viewportToDrawerRectangle: function(rectangle) {\n var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true);\n var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true);\n\n return new $.Rect(\n topLeft.x * $.pixelDensityRatio,\n topLeft.y * $.pixelDensityRatio,\n size.x * $.pixelDensityRatio,\n size.y * $.pixelDensityRatio\n );\n },\n\n /**\n * Draws the given tile.\n * @param {OpenSeadragon.Tile} tile - The tile to draw.\n * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.\n * drawingHandler({context, tile, rendered})\n * @param {Boolean} useSketch - Whether to use the sketch canvas or not.\n * where rendered
is the context with the pre-drawn image.\n */\n drawTile: function( tile, drawingHandler, useSketch ) {\n $.console.assert(tile, '[Drawer.drawTile] tile is required');\n $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');\n\n if ( this.useCanvas ) {\n var context = this._getContext( useSketch );\n // TODO do this in a more performant way\n // specifically, don't save,rotate,restore every time we draw a tile\n if( this.viewport.degrees !== 0 ) {\n this._offsetForRotation( tile, this.viewport.degrees, useSketch );\n tile.drawCanvas( context, drawingHandler );\n this._restoreRotationChanges( tile, useSketch );\n } else {\n tile.drawCanvas( context, drawingHandler );\n }\n } else {\n tile.drawHTML( this.canvas );\n }\n },\n\n _getContext: function( useSketch ) {\n var context = this.context;\n if ( useSketch ) {\n if (this.sketchCanvas === null) {\n this.sketchCanvas = document.createElement( \"canvas\" );\n this.sketchCanvas.width = this.canvas.width;\n this.sketchCanvas.height = this.canvas.height;\n this.sketchContext = this.sketchCanvas.getContext( \"2d\" );\n }\n context = this.sketchContext;\n }\n return context;\n },\n\n // private\n saveContext: function( useSketch ) {\n if (!this.useCanvas) {\n return;\n }\n\n this._getContext( useSketch ).save();\n },\n\n // private\n restoreContext: function( useSketch ) {\n if (!this.useCanvas) {\n return;\n }\n\n this._getContext( useSketch ).restore();\n },\n\n // private\n setClip: function(rect, useSketch) {\n if (!this.useCanvas) {\n return;\n }\n\n var context = this._getContext( useSketch );\n context.beginPath();\n context.rect(rect.x, rect.y, rect.width, rect.height);\n context.clip();\n },\n\n // private\n drawRectangle: function(rect, fillStyle, useSketch) {\n if (!this.useCanvas) {\n return;\n }\n\n var context = this._getContext( useSketch );\n context.save();\n context.fillStyle = fillStyle;\n context.fillRect(rect.x, rect.y, rect.width, rect.height);\n context.restore();\n },\n\n /**\n * Blends the sketch canvas in the main canvas.\n * @param {Float} opacity The opacity of the blending.\n * @returns {undefined}\n */\n blendSketch: function(opacity) {\n if (!this.useCanvas || !this.sketchCanvas) {\n return;\n }\n\n this.context.save();\n this.context.globalAlpha = opacity;\n this.context.drawImage(this.sketchCanvas, 0, 0);\n this.context.restore();\n },\n\n // private\n drawDebugInfo: function( tile, count, i ){\n if ( !this.useCanvas ) {\n return;\n }\n\n var context = this.context;\n context.save();\n context.lineWidth = 2 * $.pixelDensityRatio;\n context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';\n context.strokeStyle = this.debugGridColor;\n context.fillStyle = this.debugGridColor;\n\n if ( this.viewport.degrees !== 0 ) {\n this._offsetForRotation( tile, this.viewport.degrees );\n }\n\n context.strokeRect(\n tile.position.x * $.pixelDensityRatio,\n tile.position.y * $.pixelDensityRatio,\n tile.size.x * $.pixelDensityRatio,\n tile.size.y * $.pixelDensityRatio\n );\n\n var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;\n var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;\n\n // Rotate the text the right way around.\n context.translate( tileCenterX, tileCenterY );\n context.rotate( Math.PI / 180 * -this.viewport.degrees );\n context.translate( -tileCenterX, -tileCenterY );\n\n if( tile.x === 0 && tile.y === 0 ){\n context.fillText(\n \"Zoom: \" + this.viewport.getZoom(),\n tile.position.x * $.pixelDensityRatio,\n (tile.position.y - 30) * $.pixelDensityRatio\n );\n context.fillText(\n \"Pan: \" + this.viewport.getBounds().toString(),\n tile.position.x * $.pixelDensityRatio,\n (tile.position.y - 20) * $.pixelDensityRatio\n );\n }\n context.fillText(\n \"Level: \" + tile.level,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 20) * $.pixelDensityRatio\n );\n context.fillText(\n \"Column: \" + tile.x,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 30) * $.pixelDensityRatio\n );\n context.fillText(\n \"Row: \" + tile.y,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 40) * $.pixelDensityRatio\n );\n context.fillText(\n \"Order: \" + i + \" of \" + count,\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 50) * $.pixelDensityRatio\n );\n context.fillText(\n \"Size: \" + tile.size.toString(),\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 60) * $.pixelDensityRatio\n );\n context.fillText(\n \"Position: \" + tile.position.toString(),\n (tile.position.x + 10) * $.pixelDensityRatio,\n (tile.position.y + 70) * $.pixelDensityRatio\n );\n\n if ( this.viewport.degrees !== 0 ) {\n this._restoreRotationChanges( tile );\n }\n context.restore();\n },\n\n // private\n debugRect: function(rect) {\n if ( this.useCanvas ) {\n var context = this.context;\n context.save();\n context.lineWidth = 2 * $.pixelDensityRatio;\n context.strokeStyle = this.debugGridColor;\n context.fillStyle = this.debugGridColor;\n\n context.strokeRect(\n rect.x * $.pixelDensityRatio,\n rect.y * $.pixelDensityRatio,\n rect.width * $.pixelDensityRatio,\n rect.height * $.pixelDensityRatio\n );\n\n context.restore();\n }\n },\n\n // private\n _offsetForRotation: function( tile, degrees, useSketch ){\n var cx = this.canvas.width / 2,\n cy = this.canvas.height / 2;\n\n var context = this._getContext( useSketch );\n context.save();\n\n context.translate(cx, cy);\n context.rotate( Math.PI / 180 * degrees);\n context.translate(-cx, -cy);\n },\n\n // private\n _restoreRotationChanges: function( tile, useSketch ){\n var context = this._getContext( useSketch );\n context.restore();\n },\n\n // private\n _calculateCanvasSize: function() {\n var pixelDensityRatio = $.pixelDensityRatio;\n var viewportSize = this.viewport.getContainerSize();\n return {\n x: viewportSize.x * pixelDensityRatio,\n y: viewportSize.y * pixelDensityRatio\n };\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - Viewport\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n\n/**\n * @class Viewport\n * @memberof OpenSeadragon\n * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)\n * for an {@link OpenSeadragon.Viewer}.\n * @param {Object} options - Options for this Viewport.\n * @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.\n * @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.\n * @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.\n * @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.\n * @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.\n * @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.\n * @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.\n */\n$.Viewport = function( options ) {\n\n //backward compatibility for positional args while prefering more\n //idiomatic javascript options object as the only argument\n var args = arguments;\n if( args.length && args[ 0 ] instanceof $.Point ){\n options = {\n containerSize: args[ 0 ],\n contentSize: args[ 1 ],\n config: args[ 2 ]\n };\n }\n\n //options.config and the general config argument are deprecated\n //in favor of the more direct specification of optional settings\n //being passed directly on the options object\n if ( options.config ){\n $.extend( true, options, options.config );\n delete options.config;\n }\n\n this._margins = $.extend({\n left: 0,\n top: 0,\n right: 0,\n bottom: 0\n }, options.margins || {});\n\n delete options.margins;\n\n $.extend( true, this, {\n\n //required settings\n containerSize: null,\n contentSize: null,\n\n //internal state properties\n zoomPoint: null,\n viewer: null,\n\n //configurable options\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\n animationTime: $.DEFAULT_SETTINGS.animationTime,\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\n maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,\n visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\n defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,\n minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,\n maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,\n degrees: $.DEFAULT_SETTINGS.degrees,\n homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer\n\n }, options );\n\n this._updateContainerInnerSize();\n\n this.centerSpringX = new $.Spring({\n initial: 0,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n this.centerSpringY = new $.Spring({\n initial: 0,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n this.zoomSpring = new $.Spring({\n exponential: true,\n initial: 1,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._oldCenterX = this.centerSpringX.current.value;\n this._oldCenterY = this.centerSpringY.current.value;\n this._oldZoom = this.zoomSpring.current.value;\n\n if (this.contentSize) {\n this.resetContentSize( this.contentSize );\n } else {\n this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1);\n }\n\n this.goHome( true );\n this.update();\n};\n\n$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{\n /**\n * Updates the viewport's home bounds and constraints for the given content size.\n * @function\n * @param {OpenSeadragon.Point} contentSize - size of the content in content units\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:reset-size\n */\n resetContentSize: function( contentSize ){\n $.console.assert(contentSize, \"[Viewport.resetContentSize] contentSize is required\");\n $.console.assert(contentSize instanceof $.Point, \"[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point\");\n $.console.assert(contentSize.x > 0, \"[Viewport.resetContentSize] contentSize.x must be greater than 0\");\n $.console.assert(contentSize.y > 0, \"[Viewport.resetContentSize] contentSize.y must be greater than 0\");\n\n this.setHomeBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);\n return this;\n },\n\n /**\n * Updates the viewport's home bounds and constraints.\n * @function\n * @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates\n * @param {Number} contentFactor - how many content units per viewport unit\n * @fires OpenSeadragon.Viewer.event:reset-size\n */\n setHomeBounds: function(bounds, contentFactor) {\n $.console.assert(bounds, \"[Viewport.setHomeBounds] bounds is required\");\n $.console.assert(bounds instanceof $.Rect, \"[Viewport.setHomeBounds] bounds must be an OpenSeadragon.Rect\");\n $.console.assert(bounds.width > 0, \"[Viewport.setHomeBounds] bounds.width must be greater than 0\");\n $.console.assert(bounds.height > 0, \"[Viewport.setHomeBounds] bounds.height must be greater than 0\");\n\n this.homeBounds = bounds.clone();\n this.contentSize = this.homeBounds.getSize().times(contentFactor);\n this.contentAspectX = this.contentSize.x / this.contentSize.y;\n this.contentAspectY = this.contentSize.y / this.contentSize.x;\n\n if( this.viewer ){\n /**\n * Raised when the viewer's content size or home bounds are reset\n * (see {@link OpenSeadragon.Viewport#resetContentSize},\n * {@link OpenSeadragon.Viewport#setHomeBounds}).\n *\n * @event reset-size\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} contentSize\n * @property {OpenSeadragon.Rect} homeBounds\n * @property {Number} contentFactor\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'reset-size', {\n contentSize: this.contentSize.clone(),\n contentFactor: contentFactor,\n homeBounds: this.homeBounds.clone()\n });\n }\n },\n\n /**\n * @function\n */\n getHomeZoom: function() {\n if( this.defaultZoomLevel ){\n return this.defaultZoomLevel;\n } else {\n var aspectFactor =\n this.contentAspectX / this.getAspectRatio();\n\n var output;\n if( this.homeFillsViewer ){ // fill the viewer and clip the image\n output = ( aspectFactor >= 1) ?\n aspectFactor :\n 1;\n } else {\n output = ( aspectFactor >= 1 ) ?\n 1 :\n aspectFactor;\n }\n\n return output / this.homeBounds.width;\n }\n },\n\n /**\n * @function\n */\n getHomeBounds: function() {\n var center = this.homeBounds.getCenter( ),\n width = 1.0 / this.getHomeZoom( ),\n height = width / this.getAspectRatio();\n\n return new $.Rect(\n center.x - ( width / 2.0 ),\n center.y - ( height / 2.0 ),\n width,\n height\n );\n },\n\n /**\n * @function\n * @param {Boolean} immediately\n * @fires OpenSeadragon.Viewer.event:home\n */\n goHome: function( immediately ) {\n if( this.viewer ){\n /**\n * Raised when the \"home\" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).\n *\n * @event home\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'home', {\n immediately: immediately\n });\n }\n return this.fitBounds( this.getHomeBounds(), immediately );\n },\n\n /**\n * @function\n */\n getMinZoom: function() {\n var homeZoom = this.getHomeZoom(),\n zoom = this.minZoomLevel ?\n this.minZoomLevel :\n this.minZoomImageRatio * homeZoom;\n\n return zoom;\n },\n\n /**\n * @function\n */\n getMaxZoom: function() {\n var zoom = this.maxZoomLevel;\n if (!zoom) {\n zoom = this.contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;\n zoom /= this.homeBounds.width;\n }\n\n return Math.max( zoom, this.getHomeZoom() );\n },\n\n /**\n * @function\n */\n getAspectRatio: function() {\n return this._containerInnerSize.x / this._containerInnerSize.y;\n },\n\n /**\n * @function\n * @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.\n */\n getContainerSize: function() {\n return new $.Point(\n this.containerSize.x,\n this.containerSize.y\n );\n },\n\n /**\n * @function\n * The margins push the \"home\" region in from the sides by the specified amounts.\n * @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.\n */\n getMargins: function() {\n return $.extend({}, this._margins); // Make a copy so we are not returning our original\n },\n\n /**\n * @function\n * The margins push the \"home\" region in from the sides by the specified amounts.\n * @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.\n */\n setMargins: function(margins) {\n $.console.assert($.type(margins) === 'object', '[Viewport.setMargins] margins must be an object');\n\n this._margins = $.extend({\n left: 0,\n top: 0,\n right: 0,\n bottom: 0\n }, margins);\n\n this._updateContainerInnerSize();\n this.viewer.forceRedraw();\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.\n */\n getBounds: function( current ) {\n var center = this.getCenter( current ),\n width = 1.0 / this.getZoom( current ),\n height = width / this.getAspectRatio();\n\n return new $.Rect(\n center.x - ( width / 2.0 ),\n center.y - ( height / 2.0 ),\n width,\n height\n );\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,\n * including the space taken by margins, in viewport coordinates.\n */\n getBoundsWithMargins: function( current ) {\n var bounds = this.getBounds(current);\n var factor = this._containerInnerSize.x * this.getZoom(current);\n bounds.x -= this._margins.left / factor;\n bounds.y -= this._margins.top / factor;\n bounds.width += (this._margins.left + this._margins.right) / factor;\n bounds.height += (this._margins.top + this._margins.bottom) / factor;\n return bounds;\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n getCenter: function( current ) {\n var centerCurrent = new $.Point(\n this.centerSpringX.current.value,\n this.centerSpringY.current.value\n ),\n centerTarget = new $.Point(\n this.centerSpringX.target.value,\n this.centerSpringY.target.value\n ),\n oldZoomPixel,\n zoom,\n width,\n height,\n bounds,\n newZoomPixel,\n deltaZoomPixels,\n deltaZoomPoints;\n\n if ( current ) {\n return centerCurrent;\n } else if ( !this.zoomPoint ) {\n return centerTarget;\n }\n\n oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);\n\n zoom = this.getZoom();\n width = 1.0 / zoom;\n height = width / this.getAspectRatio();\n bounds = new $.Rect(\n centerCurrent.x - width / 2.0,\n centerCurrent.y - height / 2.0,\n width,\n height\n );\n\n newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);\n deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );\n deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );\n\n return centerTarget.plus( deltaZoomPoints );\n },\n\n /**\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n getZoom: function( current ) {\n if ( current ) {\n return this.zoomSpring.current.value;\n } else {\n return this.zoomSpring.target.value;\n }\n },\n\n /**\n * @function\n * @private\n * @param {OpenSeadragon.Rect} bounds\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Rect} constrained bounds.\n */\n _applyBoundaryConstraints: function( bounds, immediately ) {\n var dx = 0,\n dy = 0,\n newBounds = new $.Rect(\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height\n );\n\n var horizontalThreshold = this.visibilityRatio * newBounds.width;\n var verticalThreshold = this.visibilityRatio * newBounds.height;\n\n if ( this.wrapHorizontal ) {\n //do nothing\n } else {\n var thresholdLeft = newBounds.x + (newBounds.width - horizontalThreshold);\n if (this.homeBounds.x > thresholdLeft) {\n dx = this.homeBounds.x - thresholdLeft;\n }\n\n var homeRight = this.homeBounds.x + this.homeBounds.width;\n var thresholdRight = newBounds.x + horizontalThreshold;\n if (homeRight < thresholdRight) {\n var newDx = homeRight - thresholdRight;\n if (dx) {\n dx = (dx + newDx) / 2;\n } else {\n dx = newDx;\n }\n }\n }\n\n if ( this.wrapVertical ) {\n //do nothing\n } else {\n var thresholdTop = newBounds.y + (newBounds.height - verticalThreshold);\n if (this.homeBounds.y > thresholdTop) {\n dy = this.homeBounds.y - thresholdTop;\n }\n\n var homeBottom = this.homeBounds.y + this.homeBounds.height;\n var thresholdBottom = newBounds.y + verticalThreshold;\n if (homeBottom < thresholdBottom) {\n var newDy = homeBottom - thresholdBottom;\n if (dy) {\n dy = (dy + newDy) / 2;\n } else {\n dy = newDy;\n }\n }\n }\n\n if ( dx || dy ) {\n newBounds.x += dx;\n newBounds.y += dy;\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).\n *\n * @event constrain\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'constrain', {\n immediately: immediately\n });\n }\n\n return newBounds;\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:constrain\n */\n applyConstraints: function( immediately ) {\n var actualZoom = this.getZoom(),\n constrainedZoom = Math.max(\n Math.min( actualZoom, this.getMaxZoom() ),\n this.getMinZoom()\n ),\n bounds,\n constrainedBounds;\n\n if ( actualZoom != constrainedZoom ) {\n this.zoomTo( constrainedZoom, this.zoomPoint, immediately );\n }\n\n bounds = this.getBounds();\n\n constrainedBounds = this._applyBoundaryConstraints( bounds, immediately );\n\n if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){\n this.fitBounds( constrainedBounds, immediately );\n }\n\n return this;\n },\n\n /**\n * @function\n * @param {Boolean} immediately\n */\n ensureVisible: function( immediately ) {\n return this.applyConstraints( immediately );\n },\n\n /**\n * @function\n * @private\n * @param {OpenSeadragon.Rect} bounds\n * @param {Object} options (immediately=false, constraints=false)\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n _fitBounds: function( bounds, options ) {\n options = options || {};\n var immediately = options.immediately || false;\n var constraints = options.constraints || false;\n\n var aspect = this.getAspectRatio(),\n center = bounds.getCenter(),\n newBounds = new $.Rect(\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height\n ),\n oldBounds,\n oldZoom,\n newZoom,\n referencePoint,\n newBoundsAspectRatio,\n newConstrainedZoom;\n\n if ( newBounds.getAspectRatio() >= aspect ) {\n newBounds.height = bounds.width / aspect;\n newBounds.y = center.y - newBounds.height / 2;\n } else {\n newBounds.width = bounds.height * aspect;\n newBounds.x = center.x - newBounds.width / 2;\n }\n\n if ( constraints ) {\n newBoundsAspectRatio = newBounds.getAspectRatio();\n }\n\n this.panTo( this.getCenter( true ), true );\n this.zoomTo( this.getZoom( true ), null, true );\n\n oldBounds = this.getBounds();\n oldZoom = this.getZoom();\n newZoom = 1.0 / newBounds.width;\n\n if ( constraints ) {\n newConstrainedZoom = Math.max(\n Math.min(newZoom, this.getMaxZoom() ),\n this.getMinZoom()\n );\n\n if (newZoom !== newConstrainedZoom) {\n newZoom = newConstrainedZoom;\n newBounds.width = 1.0 / newZoom;\n newBounds.x = center.x - newBounds.width / 2;\n newBounds.height = newBounds.width / newBoundsAspectRatio;\n newBounds.y = center.y - newBounds.height / 2;\n }\n\n newBounds = this._applyBoundaryConstraints( newBounds, immediately );\n center = newBounds.getCenter();\n }\n\n if (immediately) {\n this.panTo( center, true );\n return this.zoomTo(newZoom, null, true);\n }\n\n if (Math.abs(newZoom - oldZoom) < 0.00000001 ||\n Math.abs(newBounds.width - oldBounds.width) < 0.00000001) {\n return this.panTo( center, immediately );\n }\n\n referencePoint = oldBounds.getTopLeft().times(\n this._containerInnerSize.x / oldBounds.width\n ).minus(\n newBounds.getTopLeft().times(\n this._containerInnerSize.x / newBounds.width\n )\n ).divide(\n this._containerInnerSize.x / oldBounds.width -\n this._containerInnerSize.x / newBounds.width\n );\n\n return this.zoomTo( newZoom, referencePoint, immediately );\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Rect} bounds\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitBounds: function( bounds, immediately ) {\n return this._fitBounds( bounds, {\n immediately: immediately,\n constraints: false\n } );\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Rect} bounds\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitBoundsWithConstraints: function( bounds, immediately ) {\n return this._fitBounds( bounds, {\n immediately: immediately,\n constraints: true\n } );\n },\n\n /**\n * Zooms so the image just fills the viewer vertically.\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitVertically: function( immediately ) {\n var box = new $.Rect(this.homeBounds.x + (this.homeBounds.width / 2), this.homeBounds.y,\n 0, this.homeBounds.height);\n\n return this.fitBounds( box, immediately );\n },\n\n /**\n * Zooms so the image just fills the viewer horizontally.\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n fitHorizontally: function( immediately ) {\n var box = new $.Rect(this.homeBounds.x, this.homeBounds.y + (this.homeBounds.height / 2),\n this.homeBounds.width, 0);\n\n return this.fitBounds( box, immediately );\n },\n\n\n /**\n * @function\n * @param {OpenSeadragon.Point} delta\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:pan\n */\n panBy: function( delta, immediately ) {\n var center = new $.Point(\n this.centerSpringX.target.value,\n this.centerSpringY.target.value\n );\n delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) );\n return this.panTo( center.plus( delta ), immediately );\n },\n\n /**\n * @function\n * @param {OpenSeadragon.Point} center\n * @param {Boolean} immediately\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:pan\n */\n panTo: function( center, immediately ) {\n if ( immediately ) {\n this.centerSpringX.resetTo( center.x );\n this.centerSpringY.resetTo( center.y );\n } else {\n this.centerSpringX.springTo( center.x );\n this.centerSpringY.springTo( center.y );\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).\n *\n * @event pan\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} center\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'pan', {\n center: center,\n immediately: immediately\n });\n }\n\n return this;\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:zoom\n */\n zoomBy: function( factor, refPoint, immediately ) {\n if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) {\n refPoint = refPoint.rotate(\n -this.degrees,\n new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value )\n );\n }\n return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately );\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:zoom\n */\n zoomTo: function( zoom, refPoint, immediately ) {\n\n this.zoomPoint = refPoint instanceof $.Point &&\n !isNaN(refPoint.x) &&\n !isNaN(refPoint.y) ?\n refPoint :\n null;\n\n if ( immediately ) {\n this.zoomSpring.resetTo( zoom );\n } else {\n this.zoomSpring.springTo( zoom );\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).\n *\n * @event zoom\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {Number} zoom\n * @property {OpenSeadragon.Point} refPoint\n * @property {Boolean} immediately\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'zoom', {\n zoom: zoom,\n refPoint: refPoint,\n immediately: immediately\n });\n }\n\n return this;\n },\n\n /**\n * Rotates this viewport to the angle specified.\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n */\n setRotation: function( degrees ) {\n if( !( this.viewer && this.viewer.drawer.canRotate() ) ) {\n return this;\n }\n\n degrees = ( degrees + 360 ) % 360;\n this.degrees = degrees;\n this.viewer.forceRedraw();\n\n /**\n * Raised when rotation has been changed.\n *\n * @event rotate\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.\n * @property {Number} degrees - The number of degrees the rotation was set to.\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n if (this.viewer !== null)\n {\n this.viewer.raiseEvent('rotate', {\"degrees\": degrees});\n }\n return this;\n },\n\n /**\n * Gets the current rotation in degrees.\n * @function\n * @return {Number} The current rotation in degrees.\n */\n getRotation: function() {\n return this.degrees;\n },\n\n /**\n * @function\n * @return {OpenSeadragon.Viewport} Chainable.\n * @fires OpenSeadragon.Viewer.event:resize\n */\n resize: function( newContainerSize, maintain ) {\n var oldBounds = this.getBounds(),\n newBounds = oldBounds,\n widthDeltaFactor;\n\n this.containerSize.x = newContainerSize.x;\n this.containerSize.y = newContainerSize.y;\n\n this._updateContainerInnerSize();\n\n if ( maintain ) {\n // TODO: widthDeltaFactor will always be 1; probably not what's intended\n widthDeltaFactor = newContainerSize.x / this.containerSize.x;\n newBounds.width = oldBounds.width * widthDeltaFactor;\n newBounds.height = newBounds.width / this.getAspectRatio();\n }\n\n if( this.viewer ){\n /**\n * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).\n *\n * @event resize\n * @memberof OpenSeadragon.Viewer\n * @type {object}\n * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.\n * @property {OpenSeadragon.Point} newContainerSize\n * @property {Boolean} maintain\n * @property {?Object} userData - Arbitrary subscriber-defined object.\n */\n this.viewer.raiseEvent( 'resize', {\n newContainerSize: newContainerSize,\n maintain: maintain\n });\n }\n\n return this.fitBounds( newBounds, true );\n },\n\n // private\n _updateContainerInnerSize: function() {\n this._containerInnerSize = new $.Point(\n Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),\n Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))\n );\n },\n\n /**\n * @function\n */\n update: function() {\n var oldZoomPixel,\n newZoomPixel,\n deltaZoomPixels,\n deltaZoomPoints;\n\n if (this.zoomPoint) {\n oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true );\n }\n\n this.zoomSpring.update();\n\n if (this.zoomPoint && this.zoomSpring.current.value != this._oldZoom) {\n newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );\n deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );\n deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );\n\n this.centerSpringX.shiftBy( deltaZoomPoints.x );\n this.centerSpringY.shiftBy( deltaZoomPoints.y );\n } else {\n this.zoomPoint = null;\n }\n\n this.centerSpringX.update();\n this.centerSpringY.update();\n\n var changed = this.centerSpringX.current.value != this._oldCenterX ||\n this.centerSpringY.current.value != this._oldCenterY ||\n this.zoomSpring.current.value != this._oldZoom;\n\n this._oldCenterX = this.centerSpringX.current.value;\n this._oldCenterY = this.centerSpringY.current.value;\n this._oldZoom = this.zoomSpring.current.value;\n\n return changed;\n },\n\n\n /**\n * Convert a delta (translation vector) from pixels coordinates to viewport coordinates\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n deltaPixelsFromPoints: function( deltaPoints, current ) {\n return deltaPoints.times(\n this._containerInnerSize.x * this.getZoom( current )\n );\n },\n\n /**\n * Convert a delta (translation vector) from viewport coordinates to pixels coordinates.\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n deltaPointsFromPixels: function( deltaPixels, current ) {\n return deltaPixels.divide(\n this._containerInnerSize.x * this.getZoom( current )\n );\n },\n\n /**\n * Convert image pixel coordinates to viewport coordinates.\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n pixelFromPoint: function( point, current ) {\n return this._pixelFromPoint(point, this.getBounds( current ));\n },\n\n // private\n _pixelFromPoint: function( point, bounds ) {\n return point.minus(\n bounds.getTopLeft()\n ).times(\n this._containerInnerSize.x / bounds.width\n ).plus(\n new $.Point(this._margins.left, this._margins.top)\n );\n },\n\n /**\n * Convert viewport coordinates to image pixel coordinates.\n * @function\n * @param {Boolean} current - Pass true for the current location; defaults to false (target location).\n */\n pointFromPixel: function( pixel, current ) {\n var bounds = this.getBounds( current );\n return pixel.minus(\n new $.Point(this._margins.left, this._margins.top)\n ).divide(\n this._containerInnerSize.x / bounds.width\n ).plus(\n bounds.getTopLeft()\n );\n },\n\n // private\n _viewportToImageDelta: function( viewerX, viewerY ) {\n var scale = this.homeBounds.width;\n return new $.Point(viewerX * (this.contentSize.x / scale),\n viewerY * ((this.contentSize.y * this.contentAspectX) / scale));\n },\n\n /**\n * Translates from OpenSeadragon viewer coordinate system to image coordinate system.\n * This method can be called either by passing X,Y coordinates or an\n * OpenSeadragon.Point\n * Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.\n * @function\n * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system.\n * @param {Number} viewerX X coordinate in viewport coordinate system.\n * @param {Number} viewerY Y coordinate in viewport coordinate system.\n * @return {OpenSeadragon.Point} a point representing the coordinates in the image.\n */\n viewportToImageCoordinates: function( viewerX, viewerY ) {\n if ( arguments.length == 1 ) {\n //they passed a point instead of individual components\n return this.viewportToImageCoordinates( viewerX.x, viewerX.y );\n }\n\n if (this.viewer && this.viewer.world.getItemCount() > 1) {\n $.console.error('[Viewport.viewportToImageCoordinates] is not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.');\n }\n\n return this._viewportToImageDelta(viewerX - this.homeBounds.x, viewerY - this.homeBounds.y);\n },\n\n // private\n _imageToViewportDelta: function( imageX, imageY ) {\n var scale = this.homeBounds.width;\n return new $.Point((imageX / this.contentSize.x) * scale,\n (imageY / this.contentSize.y / this.contentAspectX) * scale);\n },\n\n /**\n * Translates from image coordinate system to OpenSeadragon viewer coordinate system\n * This method can be called either by passing X,Y coordinates or an\n * OpenSeadragon.Point\n * Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.\n * @function\n * @param {OpenSeadragon.Point} imageX the point in image coordinate system.\n * @param {Number} imageX X coordinate in image coordinate system.\n * @param {Number} imageY Y coordinate in image coordinate system.\n * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.\n */\n imageToViewportCoordinates: function( imageX, imageY ) {\n if ( arguments.length == 1 ) {\n //they passed a point instead of individual components\n return this.imageToViewportCoordinates( imageX.x, imageX.y );\n }\n\n if (this.viewer && this.viewer.world.getItemCount() > 1) {\n $.console.error('[Viewport.imageToViewportCoordinates] is not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.');\n }\n\n var point = this._imageToViewportDelta(imageX, imageY);\n point.x += this.homeBounds.x;\n point.y += this.homeBounds.y;\n return point;\n },\n\n /**\n * Translates from a rectangle which describes a portion of the image in\n * pixel coordinates to OpenSeadragon viewport rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an\n * OpenSeadragon.Rect\n * Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.\n * @function\n * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system.\n * @param {Number} imageX the X coordinate of the top left corner of the rectangle\n * in image coordinate system.\n * @param {Number} imageY the Y coordinate of the top left corner of the rectangle\n * in image coordinate system.\n * @param {Number} pixelWidth the width in pixel of the rectangle.\n * @param {Number} pixelHeight the height in pixel of the rectangle.\n */\n imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) {\n var coordA,\n coordB,\n rect;\n if( arguments.length == 1 ) {\n //they passed a rectangle instead of individual components\n rect = imageX;\n return this.imageToViewportRectangle(\n rect.x, rect.y, rect.width, rect.height\n );\n }\n\n coordA = this.imageToViewportCoordinates(\n imageX, imageY\n );\n coordB = this._imageToViewportDelta(\n pixelWidth, pixelHeight\n );\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y\n );\n },\n\n /**\n * Translates from a rectangle which describes a portion of\n * the viewport in point coordinates to image rectangle coordinates.\n * This method can be called either by passing X,Y,width,height or an\n * OpenSeadragon.Rect\n * Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.\n * @function\n * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system.\n * @param {Number} viewerX the X coordinate of the top left corner of the rectangle\n * in viewport coordinate system.\n * @param {Number} imageY the Y coordinate of the top left corner of the rectangle\n * in viewport coordinate system.\n * @param {Number} pointWidth the width of the rectangle in viewport coordinate system.\n * @param {Number} pointHeight the height of the rectangle in viewport coordinate system.\n */\n viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) {\n var coordA,\n coordB,\n rect;\n if ( arguments.length == 1 ) {\n //they passed a rectangle instead of individual components\n rect = viewerX;\n return this.viewportToImageRectangle(\n rect.x, rect.y, rect.width, rect.height\n );\n }\n\n coordA = this.viewportToImageCoordinates( viewerX, viewerY );\n coordB = this._viewportToImageDelta(pointWidth, pointHeight);\n return new $.Rect(\n coordA.x,\n coordA.y,\n coordB.x,\n coordB.y\n );\n },\n\n /**\n * Convert pixel coordinates relative to the viewer element to image\n * coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n viewerElementToImageCoordinates: function( pixel ) {\n var point = this.pointFromPixel( pixel, true );\n return this.viewportToImageCoordinates( point );\n },\n\n /**\n * Convert pixel coordinates relative to the image to\n * viewer element coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToViewerElementCoordinates: function( pixel ) {\n var point = this.imageToViewportCoordinates( pixel );\n return this.pixelFromPoint( point, true );\n },\n\n /**\n * Convert pixel coordinates relative to the window to image coordinates.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n windowToImageCoordinates: function( pixel ) {\n var viewerCoordinates = pixel.minus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n return this.viewerElementToImageCoordinates( viewerCoordinates );\n },\n\n /**\n * Convert image coordinates to pixel coordinates relative to the window.\n * Note: not accurate with multi-image.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n imageToWindowCoordinates: function( pixel ) {\n var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );\n return viewerCoordinates.plus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n },\n\n /**\n * Convert pixel coordinates relative to the viewer element to viewport\n * coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n viewerElementToViewportCoordinates: function( pixel ) {\n return this.pointFromPixel( pixel, true );\n },\n\n /**\n * Convert viewport coordinates to pixel coordinates relative to the\n * viewer element.\n * @param {OpenSeadragon.Point} point\n * @returns {OpenSeadragon.Point}\n */\n viewportToViewerElementCoordinates: function( point ) {\n return this.pixelFromPoint( point, true );\n },\n\n /**\n * Convert pixel coordinates relative to the window to viewport coordinates.\n * @param {OpenSeadragon.Point} pixel\n * @returns {OpenSeadragon.Point}\n */\n windowToViewportCoordinates: function( pixel ) {\n var viewerCoordinates = pixel.minus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n return this.viewerElementToViewportCoordinates( viewerCoordinates );\n },\n\n /**\n * Convert viewport coordinates to pixel coordinates relative to the window.\n * @param {OpenSeadragon.Point} point\n * @returns {OpenSeadragon.Point}\n */\n viewportToWindowCoordinates: function( point ) {\n var viewerCoordinates = this.viewportToViewerElementCoordinates( point );\n return viewerCoordinates.plus(\n OpenSeadragon.getElementPosition( this.viewer.element ));\n },\n\n /**\n * Convert a viewport zoom to an image zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * Note: not accurate with multi-image.\n * @function\n * @param {Number} viewportZoom The viewport zoom\n * target zoom.\n * @returns {Number} imageZoom The image zoom\n */\n viewportToImageZoom: function( viewportZoom ) {\n if (this.viewer && this.viewer.world.getItemCount() > 1) {\n $.console.error('[Viewport.viewportToImageZoom] is not accurate with multi-image.');\n }\n\n var imageWidth = this.contentSize.x;\n var containerWidth = this._containerInnerSize.x;\n var scale = this.homeBounds.width;\n var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;\n return viewportZoom * viewportToImageZoomRatio;\n },\n\n /**\n * Convert an image zoom to a viewport zoom.\n * Image zoom: ratio of the original image size to displayed image size.\n * 1 means original image size, 0.5 half size...\n * Viewport zoom: ratio of the displayed image's width to viewport's width.\n * 1 means identical width, 2 means image's width is twice the viewport's width...\n * Note: not accurate with multi-image.\n * @function\n * @param {Number} imageZoom The image zoom\n * target zoom.\n * @returns {Number} viewportZoom The viewport zoom\n */\n imageToViewportZoom: function( imageZoom ) {\n if (this.viewer && this.viewer.world.getItemCount() > 1) {\n $.console.error('[Viewport.imageToViewportZoom] is not accurate with multi-image.');\n }\n\n var imageWidth = this.contentSize.x;\n var containerWidth = this._containerInnerSize.x;\n var scale = this.homeBounds.width;\n var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;\n return imageZoom * viewportToImageZoomRatio;\n }\n};\n\n}( OpenSeadragon ));\n\n/*\n * OpenSeadragon - TiledImage\n *\n * Copyright (C) 2009 CodePlex Foundation\n * Copyright (C) 2010-2013 OpenSeadragon contributors\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * - Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * - Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * - Neither the name of CodePlex Foundation nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n(function( $ ){\n\n/**\n * You shouldn't have to create a TiledImage directly; use {@link OpenSeadragon.Viewer#open}\n * or {@link OpenSeadragon.Viewer#addTiledImage} instead.\n * @class TiledImage\n * @memberof OpenSeadragon\n * @extends OpenSeadragon.EventSource\n * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.\n * A new instance is created for each TileSource opened.\n * @param {Object} options - Configuration for this TiledImage.\n * @param {OpenSeadragon.TileSource} options.source - The TileSource that defines this TiledImage.\n * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this TiledImage.\n * @param {OpenSeadragon.TileCache} options.tileCache - The TileCache for this TiledImage to use.\n * @param {OpenSeadragon.Drawer} options.drawer - The Drawer for this TiledImage to draw onto.\n * @param {OpenSeadragon.ImageLoader} options.imageLoader - The ImageLoader for this TiledImage to use.\n * @param {Number} [options.x=0] - Left position, in viewport coordinates.\n * @param {Number} [options.y=0] - Top position, in viewport coordinates.\n * @param {Number} [options.width=1] - Width, in viewport coordinates.\n * @param {Number} [options.height] - Height, in viewport coordinates.\n * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to\n * (portions of the image outside of this area will not be visible). Only works on\n * browsers that support the HTML5 canvas.\n * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.immediateRender] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.\n * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.\n * @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.\n * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.\n * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.\n * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.\n */\n$.TiledImage = function( options ) {\n var _this = this;\n\n $.console.assert( options.tileCache, \"[TiledImage] options.tileCache is required\" );\n $.console.assert( options.drawer, \"[TiledImage] options.drawer is required\" );\n $.console.assert( options.viewer, \"[TiledImage] options.viewer is required\" );\n $.console.assert( options.imageLoader, \"[TiledImage] options.imageLoader is required\" );\n $.console.assert( options.source, \"[TiledImage] options.source is required\" );\n $.console.assert(!options.clip || options.clip instanceof $.Rect,\n \"[TiledImage] options.clip must be an OpenSeadragon.Rect if present\");\n\n $.EventSource.call( this );\n\n this._tileCache = options.tileCache;\n delete options.tileCache;\n\n this._drawer = options.drawer;\n delete options.drawer;\n\n this._imageLoader = options.imageLoader;\n delete options.imageLoader;\n\n if (options.clip instanceof $.Rect) {\n this._clip = options.clip.clone();\n }\n\n delete options.clip;\n\n var x = options.x || 0;\n delete options.x;\n var y = options.y || 0;\n delete options.y;\n\n // Ratio of zoomable image height to width.\n this.normHeight = options.source.dimensions.y / options.source.dimensions.x;\n this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y;\n\n var scale = 1;\n if ( options.width ) {\n scale = options.width;\n delete options.width;\n\n if ( options.height ) {\n $.console.error( \"specifying both width and height to a tiledImage is not supported\" );\n delete options.height;\n }\n } else if ( options.height ) {\n scale = options.height / this.normHeight;\n delete options.height;\n }\n\n $.extend( true, this, {\n\n //internal state properties\n viewer: null,\n tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.\n coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.\n lastDrawn: [], // An unordered list of Tiles drawn last frame.\n lastResetTime: 0, // Last time for which the tiledImage was reset.\n _midDraw: false, // Is the tiledImage currently updating the viewport?\n _needsDraw: true, // Does the tiledImage need to update the viewport again?\n _hasOpaqueTile: false, // Do we have even one fully opaque tile?\n\n //configurable settings\n springStiffness: $.DEFAULT_SETTINGS.springStiffness,\n animationTime: $.DEFAULT_SETTINGS.animationTime,\n minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,\n wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,\n wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,\n immediateRender: $.DEFAULT_SETTINGS.immediateRender,\n blendTime: $.DEFAULT_SETTINGS.blendTime,\n alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,\n minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,\n debugMode: $.DEFAULT_SETTINGS.debugMode,\n crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,\n placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,\n opacity: $.DEFAULT_SETTINGS.opacity\n\n }, options );\n\n this._xSpring = new $.Spring({\n initial: x,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._ySpring = new $.Spring({\n initial: y,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._scaleSpring = new $.Spring({\n initial: scale,\n springStiffness: this.springStiffness,\n animationTime: this.animationTime\n });\n\n this._updateForScale();\n\n // We need a callback to give image manipulation a chance to happen\n this._drawingHandler = function(args) {\n /**\n * This event is fired just before the tile is drawn giving the application a chance to alter the image.\n *\n * NOTE: This event is only fired when the drawer is using a