diff --git a/changelog.txt b/changelog.txt index abf513ec..8829d474 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,12 @@ OPENSEADRAGON CHANGELOG * BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and Viewport.contentAspectY have been removed. (#846) +* BREAKING CHANGE: The Overlay.getBounds method now takes the viewport as parameter. (#896) +* DEPRECATION: Overlay.scales, Overlay.bounds and Overlay.position have been deprecated. (#896) + * Overlay.width !== null should be used to test whether the overlay scales horizontally + * Overlay.height !== null should be used to test whether the overlay scales vertically + * The Overlay.getBounds method should be used to get the bounds of the overlay in viewport coordinates + * Overlay.location replaces Overlay.position * DEPRECATION: Viewport.setHomeBounds has been deprecated (#846) * DEPRECATION: the Viewport constructor is now ignoring the contentSize option (#846) * Tile edge smoothing at high zoom (#764) @@ -30,6 +36,8 @@ OPENSEADRAGON CHANGELOG * Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872) * Added addOnceHandler method to EventSource (#887) * Added TiledImage.fitBounds method (#888) +* Overlays can now be scaled in a single dimension by providing a point location and either width or height (#896) +* Added full rotation support to overlays (#729, #193) 2.1.0: diff --git a/src/openseadragon.js b/src/openseadragon.js index 07523516..94c7de6a 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1337,6 +1337,49 @@ if (typeof define === 'function' && define.amd) { return window.getComputedStyle( element, "" ); }, + /** + * Returns the property with the correct vendor prefix appended. + * @param {String} property the property name + * @returns {String} the property with the correct prefix or null if not + * supported. + */ + getCssPropertyWithVendorPrefix: function(property) { + var memo = {}; + + $.getCssPropertyWithVendorPrefix = function(property) { + if (memo[property] !== undefined) { + return memo[property]; + } + var style = document.createElement('div').style; + var result = null; + if (style[property] !== undefined) { + result = property; + } else { + var prefixes = ['Webkit', 'Moz', 'MS', 'O', + 'webkit', 'moz', 'ms', 'o']; + var suffix = $.capitalizeFirstLetter(property); + for (var i = 0; i < prefixes.length; i++) { + var prop = prefixes[i] + suffix; + if (style[prop] !== undefined) { + result = prop; + break; + } + } + } + memo[property] = result; + return result; + }; + return $.getCssPropertyWithVendorPrefix(property); + }, + + /** + * Capitalizes the first letter of a string + * @param {String} string + * @returns {String} The string with the first letter capitalized + */ + capitalizeFirstLetter: function(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + }, /** * Determines if a point is within the bounding rectangle of the given element (hit-test). diff --git a/src/overlay.js b/src/overlay.js index 7e121950..76eb347c 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -32,7 +32,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -(function( $ ){ +(function($) { /** * An enumeration of positions that an overlay may be assigned relative to @@ -55,6 +55,23 @@ */ $.OverlayPlacement = $.Placement; + /** + * An enumeration of possible ways to handle overlays rotation + * @memberOf OpenSeadragon + * @static + * @property {Number} NO_ROTATION The overlay ignore the viewport rotation. + * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with + * the viewport. If the overlay contains text, it will get rotated as well. + * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by + * taking the size of the bounding box of the rotated bounds. + * Only valid for overlays with Rect location and scalable in both directions. + */ + $.OverlayRotationMode = { + NO_ROTATION: 1, + EXACT: 2, + BOUNDING_BOX: 3 + }; + /** * @class Overlay * @classdesc Provides a way to float an HTML element on top of the viewer element. @@ -64,19 +81,27 @@ * @param {Element} options.element * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The * location of the overlay on the image. If a {@link OpenSeadragon.Point} - * is specified, the overlay will keep a constant size independently of the - * zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will - * be adjusted when the zoom changes. + * is specified, the overlay will be located at this location with respect + * to the placement option. If a {@link OpenSeadragon.Rect} is specified, + * the overlay will be placed at this location with the corresponding width + * and height and placement TOP_LEFT. * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT] - * Relative position to the viewport. - * Only used if location is a {@link OpenSeadragon.Point}. + * Defines what part of the overlay should be at the specified options.location * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw] * @param {Boolean} [options.checkResize=true] Set to false to avoid to - * check the size of the overlay everytime it is drawn when using a - * {@link OpenSeadragon.Point} as options.location. It will improve - * performances but will cause a misalignment if the overlay size changes. + * check the size of the overlay everytime it is drawn in the directions + * which are not scaled. It will improve performances but will cause a + * misalignment if the overlay size changes. + * @param {Number} [options.width] The width of the overlay in viewport + * coordinates. If specified, the width of the overlay will be adjusted when + * the zoom changes. + * @param {Number} [options.height] The height of the overlay in viewport + * coordinates. If specified, the height of the overlay will be adjusted when + * the zoom changes. + * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT] + * How to handle the rotation of the viewport. */ - $.Overlay = function( element, location, placement ) { + $.Overlay = function(element, location, placement) { /** * onDraw callback signature used by {@link OpenSeadragon.Overlay}. @@ -89,7 +114,7 @@ */ var options; - if ( $.isPlainObject( element ) ) { + if ($.isPlainObject(element)) { options = element; } else { options = { @@ -99,35 +124,49 @@ }; } - this.element = options.element; - this.scales = options.location instanceof $.Rect; - this.bounds = new $.Rect( - options.location.x, - options.location.y, - options.location.width, - options.location.height - ); - this.position = new $.Point( - options.location.x, - options.location.y - ); - this.size = new $.Point( - options.location.width, - options.location.height - ); - this.style = options.element.style; - // rects are always top-left - this.placement = options.location instanceof $.Point ? - options.placement : $.Placement.TOP_LEFT; - this.onDraw = options.onDraw; - this.checkResize = options.checkResize === undefined ? - true : options.checkResize; + this.element = options.element; + this.style = options.element.style; + this._init(options); }; /** @lends OpenSeadragon.Overlay.prototype */ $.Overlay.prototype = { + // private + _init: function(options) { + this.location = options.location; + this.placement = options.placement === undefined ? + $.Placement.TOP_LEFT : options.placement; + this.onDraw = options.onDraw; + this.checkResize = options.checkResize === undefined ? + true : options.checkResize; + + // When this.width is not null, the overlay get scaled horizontally + this.width = options.width === undefined ? null : options.width; + + // When this.height is not null, the overlay get scaled vertically + this.height = options.height === undefined ? null : options.height; + + this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT; + + // Having a rect as location is a syntactic sugar + if (this.location instanceof $.Rect) { + this.width = this.location.width; + this.height = this.location.height; + this.location = this.location.getTopLeft(); + this.placement = $.Placement.TOP_LEFT; + } + + // Deprecated properties kept for backward compatibility. + this.scales = this.width !== null && this.height !== null; + this.bounds = new $.Rect( + this.location.x, this.location.y, this.width, this.height); + this.position = this.location; + }, + /** + * Internal function to adjust the position of an overlay + * depending on it size and placement. * @function * @param {OpenSeadragon.Point} position * @param {OpenSeadragon.Point} size @@ -153,20 +192,20 @@ * @function */ destroy: function() { - var element = this.element, - style = this.style; + var element = this.element; + var style = this.style; - if ( element.parentNode ) { - element.parentNode.removeChild( element ); + if (element.parentNode) { + element.parentNode.removeChild(element); //this should allow us to preserve overlays when required between //pages - if ( element.prevElementParent ) { + if (element.prevElementParent) { style.display = 'none'; //element.prevElementParent.insertBefore( // element, // element.prevNextSibling //); - document.body.appendChild( element ); + document.body.appendChild(element); } } @@ -177,121 +216,258 @@ style.left = ""; style.position = ""; - if ( this.scales ) { + if (this.width !== null) { style.width = ""; + } + if (this.height !== null) { style.height = ""; } + var transformOriginProp = $.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = $.getCssPropertyWithVendorPrefix( + 'transform'); + if (transformOriginProp && transformProp) { + style[transformOriginProp] = ""; + style[transformProp] = ""; + } }, /** * @function * @param {Element} container */ - drawHTML: function( container, viewport ) { - var element = this.element, - style = this.style, - scales = this.scales, - degrees = viewport.degrees, - position = viewport.pixelFromPoint( - this.bounds.getTopLeft(), - true - ), - size, - overlayCenter; - - if ( element.parentNode != container ) { + drawHTML: function(container, viewport) { + var element = this.element; + if (element.parentNode !== container) { //save the source parent for later if we need it - element.prevElementParent = element.parentNode; - element.prevNextSibling = element.nextSibling; - container.appendChild( element ); - this.size = $.getElementSize( element ); + element.prevElementParent = element.parentNode; + element.prevNextSibling = element.nextSibling; + container.appendChild(element); + + // this.size is used by overlays which don't get scaled in at + // least one direction when this.checkResize is set to false. + this.size = $.getElementSize(element); } - if ( scales ) { - size = viewport.deltaPixelsFromPoints( - this.bounds.getSize(), - true - ); - } else if ( this.checkResize ) { - size = $.getElementSize( element ); - } else { - size = this.size; - } + var positionAndSize = this._getOverlayPositionAndSize(viewport); - this.position = position; - this.size = size; - - this.adjust( position, size ); - - position = position.apply( Math.round ); - size = size.apply( Math.round ); - - // rotate the position of the overlay - // TODO only rotate overlays if in canvas mode - // TODO replace the size rotation with CSS3 transforms - // TODO add an option to overlays to not rotate with the image - // Currently only rotates position and size - if( degrees !== 0 && this.scales ) { - overlayCenter = new $.Point( size.x / 2, size.y / 2 ); - - var drawerCenter = new $.Point( - viewport.viewer.drawer.canvas.width / 2, - viewport.viewer.drawer.canvas.height / 2 - ); - position = position.plus( overlayCenter ).rotate( - degrees, - drawerCenter - ).minus( overlayCenter ); - - size = size.rotate( degrees, new $.Point( 0, 0 ) ); - size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) ); - } + var position = positionAndSize.position; + var size = this.size = positionAndSize.size; + var rotate = positionAndSize.rotate; // call the onDraw callback if it exists to allow one to overwrite // the drawing/positioning/sizing of the overlay - if ( this.onDraw ) { - this.onDraw( position, size, element ); + if (this.onDraw) { + this.onDraw(position, size, this.element); } else { - style.left = position.x + "px"; - style.top = position.y + "px"; + var style = this.style; + style.left = position.x + "px"; + style.top = position.y + "px"; + if (this.width !== null) { + style.width = size.x + "px"; + } + if (this.height !== null) { + style.height = size.y + "px"; + } + var transformOriginProp = $.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = $.getCssPropertyWithVendorPrefix( + 'transform'); + if (transformOriginProp && transformProp) { + if (rotate) { + style[transformOriginProp] = this._getTransformOrigin(); + style[transformProp] = "rotate(" + rotate + "deg)"; + } else { + style[transformOriginProp] = ""; + style[transformProp] = ""; + } + } style.position = "absolute"; - if (style.display != 'none') { - style.display = 'block'; - } - - if ( scales ) { - style.width = size.x + "px"; - style.height = size.y + "px"; + if (style.display !== 'none') { + style.display = 'block'; } } }, - /** - * @function - * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @param {OpenSeadragon.Placement} position - */ - update: function( location, placement ) { - this.scales = location instanceof $.Rect; - this.bounds = new $.Rect( - location.x, - location.y, - location.width, - location.height - ); - // rects are always top-left - this.placement = location instanceof $.Point ? - placement : $.Placement.TOP_LEFT; + // private + _getOverlayPositionAndSize: function(viewport) { + var position = viewport.pixelFromPoint(this.location, true); + var size = this._getSizeInPixels(viewport); + this.adjust(position, size); + + var rotate = 0; + if (viewport.degrees && + this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) { + // BOUNDING_BOX is only valid if both directions get scaled. + // Get replaced by EXACT otherwise. + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX && + this.width !== null && this.height !== null) { + var rect = new $.Rect(position.x, position.y, size.x, size.y); + var boundingBox = this._getBoundingBox(rect, viewport.degrees); + position = boundingBox.getTopLeft(); + size = boundingBox.getSize(); + } else { + rotate = viewport.degrees; + } + } + + return { + position: position, + size: size, + rotate: rotate + }; + }, + + // private + _getSizeInPixels: function(viewport) { + var width = this.size.x; + var height = this.size.y; + if (this.width !== null || this.height !== null) { + var scaledSize = viewport.deltaPixelsFromPointsNoRotate( + new $.Point(this.width || 0, this.height || 0), true); + if (this.width !== null) { + width = scaledSize.x; + } + if (this.height !== null) { + height = scaledSize.y; + } + } + if (this.checkResize && + (this.width === null || this.height === null)) { + var eltSize = this.size = $.getElementSize(this.element); + if (this.width === null) { + width = eltSize.x; + } + if (this.height === null) { + height = eltSize.y; + } + } + return new $.Point(width, height); + }, + + // private + _getBoundingBox: function(rect, degrees) { + var refPoint = this._getPlacementPoint(rect); + return rect.rotate(degrees, refPoint).getBoundingBox(); + }, + + // private + _getPlacementPoint: function(rect) { + var result = new $.Point(rect.x, rect.y); + var properties = $.Placement.properties[this.placement]; + if (properties) { + if (properties.isHorizontallyCentered) { + result.x += rect.width / 2; + } else if (properties.isRight) { + result.x += rect.width; + } + if (properties.isVerticallyCentered) { + result.y += rect.height / 2; + } else if (properties.isBottom) { + result.y += rect.height; + } + } + return result; + }, + + // private + _getTransformOrigin: function() { + var result = ""; + var properties = $.Placement.properties[this.placement]; + if (!properties) { + return result; + } + if (properties.isLeft) { + result = "left"; + } else if (properties.isRight) { + result = "right"; + } + if (properties.isTop) { + result += " top"; + } else if (properties.isBottom) { + result += " bottom"; + } + return result; }, /** + * Changes the overlay settings. * @function + * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location + * If an object is specified, the options are the same than the constructor + * except for the element which can not be changed. + * @param {OpenSeadragon.Placement} position + */ + update: function(location, placement) { + var options = $.isPlainObject(location) ? location : { + location: location, + placement: placement + }; + this._init({ + location: options.location || this.location, + placement: options.placement !== undefined ? + options.placement : this.placement, + onDraw: options.onDraw || this.onDraw, + checkResize: options.checkResize || this.checkResize, + width: options.width !== undefined ? options.width : this.width, + height: options.height !== undefined ? options.height : this.height, + rotationMode: options.rotationMode || this.rotationMode + }); + }, + + /** + * Returns the current bounds of the overlay in viewport coordinates + * @function + * @param {OpenSeadragon.Viewport} viewport the viewport * @returns {OpenSeadragon.Rect} overlay bounds */ - getBounds: function() { - return this.bounds.clone(); + getBounds: function(viewport) { + $.console.assert(viewport, + 'A viewport must now be passed to Overlay.getBounds.'); + var width = this.width; + var height = this.height; + if (width === null || height === null) { + var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true); + if (width === null) { + width = size.x; + } + if (height === null) { + height = size.y; + } + } + var location = this.location.clone(); + this.adjust(location, new $.Point(width, height)); + return this._adjustBoundsForRotation( + viewport, new $.Rect(location.x, location.y, width, height)); + }, + + // private + _adjustBoundsForRotation: function(viewport, bounds) { + if (!viewport || + viewport.degrees === 0 || + this.rotationMode === $.OverlayRotationMode.EXACT) { + return bounds; + } + if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) { + // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT + if (this.width === null || this.height === null) { + return bounds; + } + // It is easier to just compute the position and size and + // convert to viewport coordinates. + var positionAndSize = this._getOverlayPositionAndSize(viewport); + return viewport.viewerElementToViewportRectangle(new $.Rect( + positionAndSize.position.x, + positionAndSize.position.y, + positionAndSize.size.x, + positionAndSize.size.y)); + } + + // NO_ROTATION case + return bounds.rotate(-viewport.degrees, + this._getPlacementPoint(bounds)); } }; -}( OpenSeadragon )); +}(OpenSeadragon)); diff --git a/src/rectangle.js b/src/rectangle.js index cae13e88..afe5da18 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -110,6 +110,33 @@ $.Rect = function(x, y, width, height, degrees) { } }; +/** + * Builds a rectangle having the 3 specified points as summits. + * @static + * @memberof OpenSeadragon.Rect + * @param {OpenSeadragon.Point} topLeft + * @param {OpenSeadragon.Point} topRight + * @param {OpenSeadragon.Point} bottomLeft + * @returns {OpenSeadragon.Rect} + */ +$.Rect.fromSummits = function(topLeft, topRight, bottomLeft) { + var width = topLeft.distanceTo(topRight); + var height = topLeft.distanceTo(bottomLeft); + var diff = topRight.minus(topLeft); + var radians = Math.atan(diff.y / diff.x); + if (diff.x < 0) { + radians += Math.PI; + } else if (diff.y < 0) { + radians += 2 * Math.PI; + } + return new $.Rect( + topLeft.x, + topLeft.y, + width, + height, + radians / Math.PI * 180); +}; + /** @lends OpenSeadragon.Rect.prototype */ $.Rect.prototype = { /** @@ -284,7 +311,7 @@ $.Rect.prototype = { * Rotates a rectangle around a point. * @function * @param {Number} degrees The angle in degrees to rotate. - * @param {OpenSeadragon.Point} pivot The point about which to rotate. + * @param {OpenSeadragon.Point} [pivot] The point about which to rotate. * Defaults to the center of the rectangle. * @return {OpenSeadragon.Rect} */ diff --git a/src/viewer.js b/src/viewer.js index d3cce4b8..b2d6e5f2 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1788,7 +1788,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * is closed which include when changing page. * @method * @param {Element|String|Object} element - A reference to an element or an id for - * the element which will be overlayed. Or an Object specifying the configuration for the overlay + * the element which will be overlayed. Or an Object specifying the configuration for the overlay. + * If using an object, see {@link OpenSeadragon.Overlay} for a list of + * all available options. * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * rectangle which will be overlayed. This is a viewport relative location. * @param {OpenSeadragon.Placement} placement - The position of the @@ -2199,32 +2201,23 @@ function getOverlayObject( viewer, overlay ) { } var location = overlay.location; - if ( !location ) { - if ( overlay.width && overlay.height ) { - location = overlay.px !== undefined ? - viewer.viewport.imageToViewportRectangle( new $.Rect( - overlay.px, - overlay.py, - overlay.width, - overlay.height - ) ) : - new $.Rect( - overlay.x, - overlay.y, - overlay.width, - overlay.height - ); - } else { - location = overlay.px !== undefined ? - viewer.viewport.imageToViewportCoordinates( new $.Point( - overlay.px, - overlay.py - ) ) : - new $.Point( - overlay.x, - overlay.y - ); + var width = overlay.width; + var height = overlay.height; + if (!location) { + var x = overlay.x; + var y = overlay.y; + if (overlay.px !== undefined) { + var rect = viewer.viewport.imageToViewportRectangle(new $.Rect( + overlay.px, + overlay.py, + width || 0, + height || 0)); + x = rect.x; + y = rect.y; + width = width !== undefined ? rect.width : undefined; + height = height !== undefined ? rect.height : undefined; } + location = new $.Point(x, y); } var placement = overlay.placement; @@ -2237,7 +2230,10 @@ function getOverlayObject( viewer, overlay ) { location: location, placement: placement, onDraw: overlay.onDraw, - checkResize: overlay.checkResize + checkResize: overlay.checkResize, + width: width, + height: height, + rotationMode: overlay.rotationMode }); } diff --git a/src/viewport.js b/src/viewport.js index 6f418f13..a2a9cc26 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -1312,6 +1312,34 @@ $.Viewport.prototype = { return this.pixelFromPoint( point, true ); }, + /** + * Convert a rectangle in pixel coordinates relative to the viewer element + * to viewport coordinates. + * @param {OpenSeadragon.Rect} rectangle the rectangle to convert + * @returns {OpenSeadragon.Rect} the converted rectangle + */ + viewerElementToViewportRectangle: function(rectangle) { + return $.Rect.fromSummits( + this.pointFromPixel(rectangle.getTopLeft(), true), + this.pointFromPixel(rectangle.getTopRight(), true), + this.pointFromPixel(rectangle.getBottomLeft(), true) + ); + }, + + /** + * Convert a rectangle in viewport coordinates to pixel coordinates relative + * to the viewer element. + * @param {OpenSeadragon.Rect} rectangle the rectangle to convert + * @returns {OpenSeadragon.Rect} the converted rectangle + */ + viewportToViewerElementRectangle: function(rectangle) { + return $.Rect.fromSummits( + this.pixelFromPoint(rectangle.getTopLeft(), true), + this.pixelFromPoint(rectangle.getTopRight(), true), + this.pixelFromPoint(rectangle.getBottomLeft(), true) + ); + }, + /** * Convert pixel coordinates relative to the window to viewport coordinates. * @param {OpenSeadragon.Point} pixel diff --git a/test/demo/overlay.html b/test/demo/overlay.html index a3be7724..527ebef3 100644 --- a/test/demo/overlay.html +++ b/test/demo/overlay.html @@ -14,7 +14,9 @@
- + + + 0deg
diff --git a/test/modules/overlays.js b/test/modules/overlays.js index bb317050..76e91272 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -1,109 +1,111 @@ /* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, testLog */ -( function() { +(function() { var viewer; + // jQuery.position can give results quite different than what set in style.left + var epsilon = 1; - module( "Overlays", { + module("Overlays", { setup: function() { - var example = $( '
' ).appendTo( "#qunit-fixture" ); - var fixedOverlay = $( '
' ).appendTo( example ); - fixedOverlay.width( 70 ); - fixedOverlay.height( 60 ); + var example = $('
').appendTo("#qunit-fixture"); + var fixedOverlay = $('
').appendTo(example); + fixedOverlay.width(70); + fixedOverlay.height(60); testLog.reset(); }, teardown: function() { resetTestVariables(); } - } ); + }); var resetTestVariables = function() { - if ( viewer ) { + if (viewer) { viewer.close(); } }; - function waitForViewer( handler, count ) { - if ( typeof count !== "number" ) { + function waitForViewer(handler, count) { + if (typeof count !== "number") { count = 0; } var ready = viewer.isOpen() && viewer.drawer !== null && !viewer.world.needsDraw() && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).x, - viewer.viewport.getBounds().x, 0.000 ) && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).y, - viewer.viewport.getBounds().y, 0.000 ) && - Util.equalsWithVariance( viewer.viewport.getBounds( true ).width, - viewer.viewport.getBounds().width, 0.000 ); + Util.equalsWithVariance(viewer.viewport.getBounds(true).x, + viewer.viewport.getBounds().x, 0.000) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).y, + viewer.viewport.getBounds().y, 0.000) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).width, + viewer.viewport.getBounds().width, 0.000); - if ( ready ) { + if (ready) { handler(); - } else if ( count < 50 ) { + } else if (count < 50) { count++; - setTimeout( function() { - waitForViewer( handler, count ); - }, 100 ); + setTimeout(function() { + waitForViewer(handler, count); + }, 100); } else { - console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + - ":" + viewer.world.needsDraw() ); + console.log("waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + + ":" + viewer.world.needsDraw()); handler(); } } - asyncTest( 'Overlays via viewer options', function() { + asyncTest('Overlays via viewer options', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'], springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0.1, y: 0.4, width: 0.09, height: 0.09, id: "overlay" - } ] - } ); - viewer.addHandler( 'open', openHandler ); + }] + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 1, "Global overlay should be added." ); - equal( viewer.currentOverlays.length, 1, "Global overlay should be open." ); + equal(viewer.overlays.length, 1, "Global overlay should be added."); + equal(viewer.currentOverlays.length, 1, "Global overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 1, "Global overlay should stay after page switch." ); - equal( viewer.currentOverlays.length, 1, "Global overlay should re-open after page switch." ); + equal(viewer.overlays.length, 1, "Global overlay should stay after page switch."); + equal(viewer.currentOverlays.length, 1, "Global overlay should re-open after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 1, "Global overlay should not be removed on close." ); - equal( viewer.currentOverlays.length, 0, "Global overlay should be closed on close." ); + equal(viewer.overlays.length, 1, "Global overlay should not be removed on close."); + equal(viewer.currentOverlays.length, 0, "Global overlay should be closed on close."); start(); } - } ); + }); - asyncTest( 'Page Overlays via viewer options', function() { + asyncTest('Page Overlays via viewer options', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ { + tileSources: [{ Image: { xmlns: "http://schemas.microsoft.com/deepzoom/2008", Url: "/test/data/testpattern_files/", @@ -115,13 +117,13 @@ Height: 1000 } }, - overlays: [ { + overlays: [{ x: 0.1, y: 0.4, width: 0.09, height: 0.09, id: "overlay" - } ] + }] }, { Image: { xmlns: "http://schemas.microsoft.com/deepzoom/2008", @@ -134,96 +136,96 @@ Height: 1000 } } - } ], + }], springStiffness: 100 // Faster animation = faster tests - } ); - viewer.addHandler( 'open', openHandler ); + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added." ); - equal( viewer.currentOverlays.length, 1, "Page overlay should be open." ); + equal(viewer.overlays.length, 0, "No global overlay should be added."); + equal(viewer.currentOverlays.length, 1, "Page overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added after page switch." ); - equal( viewer.currentOverlays.length, 0, "No page overlay should be opened after page switch." ); + equal(viewer.overlays.length, 0, "No global overlay should be added after page switch."); + equal(viewer.currentOverlays.length, 0, "No page overlay should be opened after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added on close." ); - equal( viewer.currentOverlays.length, 0, "Page overlay should be closed on close." ); + equal(viewer.overlays.length, 0, "No global overlay should be added on close."); + equal(viewer.currentOverlays.length, 0, "Page overlay should be closed on close."); start(); } - } ); + }); - asyncTest( 'Overlays via addOverlay method', function() { + asyncTest('Overlays via addOverlay method', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', - tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'], springStiffness: 100 // Faster animation = faster tests - } ); - viewer.addHandler( 'open', openHandler ); + }); + viewer.addHandler('open', openHandler); function openHandler() { - viewer.removeHandler( 'open', openHandler ); + viewer.removeHandler('open', openHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added." ); - equal( viewer.currentOverlays.length, 0, "No overlay should be open." ); + equal(viewer.overlays.length, 0, "No global overlay should be added."); + equal(viewer.currentOverlays.length, 0, "No overlay should be open."); - var rect = new OpenSeadragon.Rect( 0.1, 0.1, 0.1, 0.1 ); - var overlay = $( "
" ).prop( "id", "overlay" ).get( 0 ); - viewer.addOverlay( overlay, rect ); - equal( viewer.overlays.length, 0, "No manual overlay should be added as global overlay." ); - equal( viewer.currentOverlays.length, 1, "A manual overlay should be open." ); + var rect = new OpenSeadragon.Rect(0.1, 0.1, 0.1, 0.1); + var overlay = $("
").prop("id", "overlay").get(0); + viewer.addOverlay(overlay, rect); + equal(viewer.overlays.length, 0, "No manual overlay should be added as global overlay."); + equal(viewer.currentOverlays.length, 1, "A manual overlay should be open."); - viewer.addHandler( 'open', openPageHandler ); - viewer.goToPage( 1 ); + viewer.addHandler('open', openPageHandler); + viewer.goToPage(1); } function openPageHandler() { - viewer.removeHandler( 'open', openPageHandler ); + viewer.removeHandler('open', openPageHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added after page switch." ); - equal( viewer.currentOverlays.length, 0, "Manual overlay should be removed after page switch." ); + equal(viewer.overlays.length, 0, "No global overlay should be added after page switch."); + equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed after page switch."); - viewer.addHandler( 'close', closeHandler ); + viewer.addHandler('close', closeHandler); viewer.close(); } function closeHandler() { - viewer.removeHandler( 'close', closeHandler ); + viewer.removeHandler('close', closeHandler); - equal( viewer.overlays.length, 0, "No global overlay should be added on close." ); - equal( viewer.currentOverlays.length, 0, "Manual overlay should be removed on close." ); + equal(viewer.overlays.length, 0, "No global overlay should be added on close."); + equal(viewer.currentOverlays.length, 0, "Manual overlay should be removed on close."); start(); } - } ); + }); - asyncTest( 'Overlays size in pixels', function() { + asyncTest('Overlays size in pixels', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ px: 13, py: 120, width: 124, @@ -234,60 +236,68 @@ py: 500, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); - function checkOverlayPosition( contextMessage ) { + function checkOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.imageToViewerElementCoordinates( - new OpenSeadragon.Point( 13, 120 ) ).apply( Math.round ); - var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(13, 120)); + var actPosition = $("#overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); - var zoom = viewport.viewportToImageZoom( viewport.getZoom( true ) ); - var expectedWidth = Math.round( 124 * zoom ); - var expectedHeight = Math.round( 132 * zoom ); - equal( $( "#overlay" ).width(), expectedWidth, "Width mismatch " + contextMessage ); - equal( $( "#overlay" ).height( ), expectedHeight, "Height mismatch " + contextMessage ); + var zoom = viewport.viewportToImageZoom(viewport.getZoom(true)); + var expectedWidth = 124 * zoom; + var expectedHeight = 132 * zoom; + Util.assessNumericValue($("#overlay").width(), expectedWidth, epsilon, + "Width mismatch " + contextMessage); + Util.assessNumericValue($("#overlay").height(), expectedHeight, epsilon, + "Height mismatch " + contextMessage); expPosition = viewport.imageToViewerElementCoordinates( - new OpenSeadragon.Point( 400, 500 ) ).apply( Math.round ); - actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(400, 500)); + actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); - equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); - equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon, + "Fixed overlay width mismatch " + contextMessage); + Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon, + "Fixed overlay height mismatch " + contextMessage); } - waitForViewer( function() { - checkOverlayPosition( "after opening using image coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after opening using image coordinates"); - viewer.viewport.zoomBy( 1.1 ).panBy( new OpenSeadragon.Point( 0.1, 0.2 ) ); - waitForViewer( function() { - checkOverlayPosition( "after zoom and pan using image coordinates" ); + viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2)); + waitForViewer(function() { + checkOverlayPosition("after zoom and pan using image coordinates"); viewer.viewport.goHome(); - waitForViewer( function() { - checkOverlayPosition( "after goHome using image coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after goHome using image coordinates"); start(); - } ); - } ); + }); + }); - } ); - } ); + }); + }); - asyncTest( 'Overlays size in points', function() { + asyncTest('Overlays size in points', function() { - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0.2, y: 0.1, width: 0.5, @@ -298,62 +308,70 @@ y: 0.6, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); - function checkOverlayPosition( contextMessage ) { + function checkOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round ); - var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.2, 0.1)); + var actPosition = $("#overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); var expectedSize = viewport.deltaPixelsFromPoints( - new OpenSeadragon.Point( 0.5, 0.1 ) ); - equal( $( "#overlay" ).width(), expectedSize.x, "Width mismatch " + contextMessage ); - equal( $( "#overlay" ).height(), expectedSize.y, "Height mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.1)); + Util.assessNumericValue($("#overlay").width(), expectedSize.x, epsilon, + "Width mismatch " + contextMessage); + Util.assessNumericValue($("#overlay").height(), expectedSize.y, epsilon, + "Height mismatch " + contextMessage); expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ).apply( Math.round ); - actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)); + actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); - equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); - equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon, + "Fixed overlay width mismatch " + contextMessage); + Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon, + "Fixed overlay height mismatch " + contextMessage); } - waitForViewer( function() { - checkOverlayPosition( "after opening using viewport coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after opening using viewport coordinates"); - viewer.viewport.zoomBy( 1.1 ).panBy( new OpenSeadragon.Point( 0.1, 0.2 ) ); - waitForViewer( function() { - checkOverlayPosition( "after zoom and pan using viewport coordinates" ); + viewer.viewport.zoomBy(1.1).panBy(new OpenSeadragon.Point(0.1, 0.2)); + waitForViewer(function() { + checkOverlayPosition("after zoom and pan using viewport coordinates"); viewer.viewport.goHome(); - waitForViewer( function() { - checkOverlayPosition( "after goHome using viewport coordinates" ); + waitForViewer(function() { + checkOverlayPosition("after goHome using viewport coordinates"); start(); - } ); - } ); + }); + }); - } ); - } ); + }); + }); - asyncTest( 'Overlays placement', function() { + asyncTest('Overlays placement', function() { - var scalableOverlayLocation = new OpenSeadragon.Rect( 0.2, 0.1, 0.5, 0.1 ); - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var scalableOverlayLocation = new OpenSeadragon.Rect(0.2, 0.1, 0.5, 0.1); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: scalableOverlayLocation.x, y: scalableOverlayLocation.y, width: scalableOverlayLocation.width, @@ -365,204 +383,727 @@ y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "TOP_LEFT" - } ] - } ); + }] + }); // Scalable overlays are always TOP_LEFT - function checkScalableOverlayPosition( contextMessage ) { + function checkScalableOverlayPosition(contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round ); - var actPosition = $( "#overlay" ).position(); - equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.2, 0.1)); + var actPosition = $("#overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Y position mismatch " + contextMessage); } - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); - var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); + var actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { + waitForViewer(function() { - checkScalableOverlayPosition( "with TOP_LEFT placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( 0, 0 ), - "with TOP_LEFT placement." ); + checkScalableOverlayPosition("with TOP_LEFT placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(0, 0), + "with TOP_LEFT placement."); // Check that legacy OpenSeadragon.OverlayPlacement is still working - viewer.updateOverlay( "overlay", scalableOverlayLocation, - OpenSeadragon.OverlayPlacement.CENTER ); - viewer.updateOverlay( "fixed-overlay", fixedOverlayLocation, - OpenSeadragon.OverlayPlacement.CENTER ); + viewer.updateOverlay("overlay", scalableOverlayLocation, + OpenSeadragon.OverlayPlacement.CENTER); + viewer.updateOverlay("fixed-overlay", fixedOverlayLocation, + OpenSeadragon.OverlayPlacement.CENTER); - setTimeout( function() { - checkScalableOverlayPosition( "with CENTER placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with CENTER placement." ); + setTimeout(function() { + checkScalableOverlayPosition("with CENTER placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with CENTER placement."); // Check that new OpenSeadragon.Placement is working - viewer.updateOverlay( "overlay", scalableOverlayLocation, - OpenSeadragon.Placement.BOTTOM_RIGHT ); - viewer.updateOverlay( "fixed-overlay", fixedOverlayLocation, - OpenSeadragon.Placement.BOTTOM_RIGHT ); - setTimeout( function() { - checkScalableOverlayPosition( "with BOTTOM_RIGHT placement." ); - checkFixedOverlayPosition( new OpenSeadragon.Point( -70, -60 ), - "with BOTTOM_RIGHT placement." ); + viewer.updateOverlay("overlay", scalableOverlayLocation, + OpenSeadragon.Placement.BOTTOM_RIGHT); + viewer.updateOverlay("fixed-overlay", fixedOverlayLocation, + OpenSeadragon.Placement.BOTTOM_RIGHT); + setTimeout(function() { + checkScalableOverlayPosition("with BOTTOM_RIGHT placement."); + checkFixedOverlayPosition(new OpenSeadragon.Point(-70, -60), + "with BOTTOM_RIGHT placement."); start(); - }, 100 ); + }, 100); - }, 100 ); + }, 100); - } ); - } ); + }); + }); - asyncTest( 'Overlays placement and resizing check', function() { + asyncTest('Overlays placement and resizing check', function() { - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: fixedOverlayLocation.x, y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "CENTER", checkResize: true - } ] - } ); + }] + }); - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); - var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); + var actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 70,60." ); + waitForViewer(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 70,60."); - $( "#fixed-overlay" ).width( 50 ); - $( "#fixed-overlay" ).height( 40 ); + $("#fixed-overlay").width(50); + $("#fixed-overlay").height(40); // The resizing of the overlays is not detected by the viewer's loop. viewer.forceRedraw(); - setTimeout( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -25, -20 ), - "with overlay of size 50,40." ); + setTimeout(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-25, -20), + "with overlay of size 50,40."); // Restore original size - $( "#fixed-overlay" ).width( 70 ); - $( "#fixed-overlay" ).height( 60 ); + $("#fixed-overlay").width(70); + $("#fixed-overlay").height(60); start(); - }, 100 ); - } ); + }, 100); + }); - } ); + }); - asyncTest( 'Overlays placement and no resizing check', function() { + asyncTest('Overlays placement and no resizing check', function() { - var fixedOverlayLocation = new OpenSeadragon.Point( 0.5, 0.6 ); + var fixedOverlayLocation = new OpenSeadragon.Point(0.5, 0.6); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: fixedOverlayLocation.x, y: fixedOverlayLocation.y, id: "fixed-overlay", placement: "CENTER", checkResize: false - } ] - } ); + }] + }); - function checkFixedOverlayPosition( expectedOffset, contextMessage ) { + function checkFixedOverlayPosition(expectedOffset, contextMessage) { var viewport = viewer.viewport; var expPosition = viewport.viewportToViewerElementCoordinates( - new OpenSeadragon.Point( 0.5, 0.6 ) ) - .apply( Math.round ) - .plus( expectedOffset ); - var actPosition = $( "#fixed-overlay" ).position(); - equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage ); - equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage ); + new OpenSeadragon.Point(0.5, 0.6)) + .plus(expectedOffset); + var actPosition = $("#fixed-overlay").position(); + Util.assessNumericValue(actPosition.left, expPosition.x, epsilon, + "Fixed overlay X position mismatch " + contextMessage); + Util.assessNumericValue(actPosition.top, expPosition.y, epsilon, + "Fixed overlay Y position mismatch " + contextMessage); } - waitForViewer( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 70,60." ); + waitForViewer(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 70,60."); - $( "#fixed-overlay" ).width( 50 ); - $( "#fixed-overlay" ).height( 40 ); + $("#fixed-overlay").width(50); + $("#fixed-overlay").height(40); // The resizing of the overlays is not detected by the viewer's loop. viewer.forceRedraw(); - setTimeout( function() { - checkFixedOverlayPosition( new OpenSeadragon.Point( -35, -30 ), - "with overlay of size 50,40." ); + setTimeout(function() { + checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30), + "with overlay of size 50,40."); // Restore original size - $( "#fixed-overlay" ).width( 70 ); - $( "#fixed-overlay" ).height( 60 ); + $("#fixed-overlay").width(70); + $("#fixed-overlay").height(60); start(); - }, 100 ); - } ); + }, 100); + }); - } ); + }); // ---------- asyncTest('overlays appear immediately', function() { equal($('#immediate-overlay0').length, 0, 'overlay 0 does not exist'); equal($('#immediate-overlay1').length, 0, 'overlay 1 does not exist'); - viewer = OpenSeadragon( { + viewer = OpenSeadragon({ id: 'example-overlays', prefixUrl: '/build/openseadragon/images/', tileSources: '/test/data/testpattern.dzi', springStiffness: 100, // Faster animation = faster tests - overlays: [ { + overlays: [{ x: 0, y: 0, id: "immediate-overlay0" - } ] - } ); + }] + }); viewer.addHandler('open', function() { equal($('#immediate-overlay0').length, 1, 'overlay 0 exists'); - viewer.addOverlay( { + viewer.addOverlay({ x: 0, y: 0, id: "immediate-overlay1" - } ); + }); equal($('#immediate-overlay1').length, 1, 'overlay 1 exists'); start(); }); }); -} )( ); + // ---------- + asyncTest('Overlay scaled horizontally only', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "horizontally-scaled-overlay", + x: 0, + y: 0, + width: 1 + }); + + var width = $("#horizontally-scaled-overlay").width(); + var height = 100; + var zoom = 1.1; + $("#horizontally-scaled-overlay").get(0).style.height = height + "px"; + + viewer.viewport.zoomBy(zoom); + + waitForViewer(function() { + var newWidth = $("#horizontally-scaled-overlay").width(); + var newHeight = $("#horizontally-scaled-overlay").height(); + equal(newWidth, width * zoom, "Width should be scaled."); + equal(newHeight, height, "Height should not be scaled."); + + start(); + }); + }); + }); + + // ---------- + asyncTest('Overlay scaled vertically only', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "vertically-scaled-overlay", + x: 0, + y: 0, + height: 1 + }); + + var width = 100; + var height = $("#vertically-scaled-overlay").height(); + var zoom = 1.1; + $("#vertically-scaled-overlay").get(0).style.width = width + "px"; + + viewer.viewport.zoomBy(zoom); + + waitForViewer(function() { + var newWidth = $("#vertically-scaled-overlay").width(); + var newHeight = $("#vertically-scaled-overlay").height(); + equal(newWidth, width, "Width should not be scaled."); + equal(newHeight, height * zoom, "Height should be scaled."); + + start(); + }); + }); + }); + + // ---------- + asyncTest('Overlay.getBounds', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100 // Faster animation = faster tests + }); + + viewer.addHandler('open', function() { + viewer.addOverlay({ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT + }); + viewer.addOverlay({ + id: "horizontally-scaled-overlay", + x: 0.5, + y: 0.5, + width: 1, + placement: OpenSeadragon.Placement.CENTER + }); + viewer.addOverlay({ + id: "vertically-scaled-overlay", + x: 0, + y: 0.5, + height: 1, + placement: OpenSeadragon.Placement.LEFT + }); + viewer.addOverlay({ + id: "not-scaled-overlay", + x: 1, + y: 0, + placement: OpenSeadragon.Placement.TOP_RIGHT + }); + + var notScaledWidth = 100; + var notScaledHeight = 100; + $("#horizontally-scaled-overlay").get(0).style.height = notScaledHeight + "px"; + $("#vertically-scaled-overlay").get(0).style.width = notScaledWidth + "px"; + $("#not-scaled-overlay").get(0).style.width = notScaledWidth + "px"; + $("#not-scaled-overlay").get(0).style.height = notScaledHeight + "px"; + + var notScaledSize = viewer.viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewer.viewport); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + + actualBounds = viewer.getOverlayById("horizontally-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y); + ok(expectedBounds.equals(actualBounds), + "The horizontally scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + actualBounds = viewer.getOverlayById("vertically-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 0, 0, notScaledSize.x, 1); + ok(expectedBounds.equals(actualBounds), + "The vertically scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + actualBounds = viewer.getOverlayById("not-scaled-overlay") + .getBounds(viewer.viewport); + expectedBounds = new OpenSeadragon.Rect( + 1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y); + ok(expectedBounds.equals(actualBounds), + "The not scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Fully scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedSize = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)); + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(1, 1)) + .minus(expectedSize); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedSize.x, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedSize.y, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1) + .rotate(-45, new OpenSeadragon.Point(1, 1)); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Horizontally scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "horizontally-scaled-overlay", + x: 0.5, + y: 0.5, + width: 1, + placement: OpenSeadragon.Placement.CENTER, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#horizontally-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.height = notScaledHeight + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedWidth = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)).x; + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(0.5, 0.5)) + .minus(new OpenSeadragon.Point(expectedWidth / 2, notScaledHeight / 2)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Horizontally scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Horizontally scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedWidth, epsilon, + "Horizontally scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, notScaledHeight, epsilon, + "Horizontally scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("horizontally-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y) + .rotate(-45, new OpenSeadragon.Point(0.5, 0.5)); + ok(expectedBounds.equals(actualBounds), + "The horizontally scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Vertically scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "vertically-scaled-overlay", + x: 0, + y: 0.5, + height: 1, + placement: OpenSeadragon.Placement.LEFT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#vertically-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.width = notScaledWidth + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedHeight = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)).y; + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(0, 0.5)) + .minus(new OpenSeadragon.Point(0, expectedHeight / 2)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Vertically scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Vertically scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, notScaledWidth, epsilon, + "Vertically scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedHeight, epsilon, + "Vertically scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("vertically-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0, 0, notScaledSize.x, 1) + .rotate(-45, new OpenSeadragon.Point(0, 0.5)); + ok(expectedBounds.equals(actualBounds), + "The vertically scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Not scaled overlay rotation mode NO_ROTATION', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "not-scaled-overlay", + x: 1, + y: 0, + placement: OpenSeadragon.Placement.TOP_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION + }] + }); + + viewer.addOnceHandler('open', function() { + var $overlay = $("#not-scaled-overlay"); + var notScaledWidth = 100; + var notScaledHeight = 100; + $overlay.get(0).style.width = notScaledWidth + "px"; + $overlay.get(0).style.height = notScaledHeight + "px"; + + var viewport = viewer.viewport; + var notScaledSize = viewport.deltaPointsFromPixelsNoRotate( + new OpenSeadragon.Point(notScaledWidth, notScaledHeight)); + + // Force refresh to takes new dimensions into account. + viewer._drawOverlays(); + + var expectedPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point(1, 0)) + .minus(new OpenSeadragon.Point(notScaledWidth, 0)); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon, + "Not scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon, + "Not scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, notScaledWidth, epsilon, + "Not scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, notScaledHeight, epsilon, + "Not scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("not-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y) + .rotate(-45, new OpenSeadragon.Point(1, 0)); + ok(expectedBounds.equals(actualBounds), + "Not scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); + + // ---------- + asyncTest('Fully scaled overlay rotation mode BOUNDING_BOX', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedRect = viewport.viewportToViewerElementRectangle( + new OpenSeadragon.Rect(0, 0, 1, 1)).getBoundingBox(); + var actualPosition = $overlay.position(); + Util.assessNumericValue(actualPosition.left, expectedRect.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(actualPosition.top, expectedRect.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedRect.width, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedRect.height, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect( + 0.5, -0.5, Math.sqrt(2), Math.sqrt(2), 45); + var boundsEpsilon = 0.000001; + Util.assessNumericValue(actualBounds.x, expectedBounds.x, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.x"); + Util.assessNumericValue(actualBounds.y, expectedBounds.y, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.y"); + Util.assessNumericValue(actualBounds.width, expectedBounds.width, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.width"); + Util.assessNumericValue(actualBounds.height, expectedBounds.height, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.height"); + Util.assessNumericValue(actualBounds.degrees, expectedBounds.degrees, boundsEpsilon, + "The fully scaled overlay should have adjusted bounds.degrees"); + + start(); + }); + }); + + // ---------- + asyncTest('Fully scaled overlay rotation mode EXACT', function() { + viewer = OpenSeadragon({ + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: '/test/data/testpattern.dzi', + springStiffness: 100, // Faster animation = faster tests + degrees: 45, + overlays: [{ + id: "fully-scaled-overlay", + x: 1, + y: 1, + width: 1, + height: 1, + placement: OpenSeadragon.Placement.BOTTOM_RIGHT, + rotationMode: OpenSeadragon.OverlayRotationMode.EXACT + }] + }); + + viewer.addOnceHandler('open', function() { + var viewport = viewer.viewport; + + var $overlay = $("#fully-scaled-overlay"); + var expectedSize = viewport.deltaPixelsFromPointsNoRotate( + new OpenSeadragon.Point(1, 1)); + var expectedPosition = viewport.pixelFromPoint( + new OpenSeadragon.Point(1, 1)) + .minus(expectedSize); + // We can't rely on jQuery.position with transforms. + var actualStyle = $overlay.get(0).style; + var left = Number(actualStyle.left.replace("px", "")); + var top = Number(actualStyle.top.replace("px", "")); + Util.assessNumericValue(left, expectedPosition.x, epsilon, + "Scaled overlay position.x should adjust to rotation."); + Util.assessNumericValue(top, expectedPosition.y, epsilon, + "Scaled overlay position.y should adjust to rotation."); + + var actualWidth = $overlay.width(); + var actualHeight = $overlay.height(); + Util.assessNumericValue(actualWidth, expectedSize.x, epsilon, + "Scaled overlay width should not adjust to rotation."); + Util.assessNumericValue(actualHeight, expectedSize.y, epsilon, + "Scaled overlay height should not adjust to rotation."); + + var transformOriginProp = OpenSeadragon.getCssPropertyWithVendorPrefix( + 'transformOrigin'); + var transformProp = OpenSeadragon.getCssPropertyWithVendorPrefix( + 'transform'); + var transformOrigin = actualStyle[transformOriginProp]; + // Some browsers replace "right bottom" by "100% 100%" + ok(transformOrigin.match(/(100% 100%)|(right bottom)/), + "Transform origin should be right bottom. Got: " + transformOrigin); + equal(actualStyle[transformProp], "rotate(45deg)", + "Transform should be rotate(45deg)."); + + var actualBounds = viewer.getOverlayById("fully-scaled-overlay") + .getBounds(viewport); + var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1); + ok(expectedBounds.equals(actualBounds), + "The fully scaled overlay should have bounds " + + expectedBounds + " but found " + actualBounds); + + start(); + }); + }); +})();