diff --git a/src/drawer.js b/src/drawer.js index 2e100384..70cb1a84 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -87,8 +87,7 @@ $.Drawer = function( options ) { //internal state / configurable settings - overlays: [], // An unordered list of Overlays added. - collectionOverlays: {}, + collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer. //configurable settings maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount, @@ -148,18 +147,6 @@ $.Drawer = function( options ) { this.container.style.textAlign = "left"; this.container.appendChild( this.canvas ); - //create the correct type of overlay by convention if the overlays - //are not already OpenSeadragon.Overlays - for( i = 0; i < this.overlays.length; i++ ){ - if( $.isPlainObject( this.overlays[ i ] ) ){ - - this.overlays[ i ] = addOverlayFromConfiguration( this, this.overlays[ i ]); - - } else if ( $.isFunction( this.overlays[ i ] ) ){ - //TODO - } - } - //this.profiler = new $.Profiler(); }; @@ -177,57 +164,15 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @param {OpenSeadragon.OverlayPlacement} placement - The position of the * viewport which the location coordinates will be treated as relative * to. - * @param {function} onDraw - If supplied the callback is called when the overlay + * @param {function} onDraw - If supplied the callback is called when the overlay * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. * It is passed position, size and element. * @fires OpenSeadragon.Viewer.event:add-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#addOverlay} instead. */ addOverlay: function( element, location, placement, onDraw ) { - var options; - if( $.isPlainObject( element ) ){ - options = element; - } else { - options = { - element: element, - location: location, - placement: placement, - onDraw: onDraw - }; - } - - element = $.getElement(options.element); - - if ( getOverlayIndex( this.overlays, element ) >= 0 ) { - // they're trying to add a duplicate overlay - return; - } - - this.overlays.push( new $.Overlay({ - element: element, - location: options.location, - placement: options.placement, - onDraw: options.onDraw - }) ); - this.updateAgain = true; - if( this.viewer ){ - /** - * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Drawer#addOverlay}). - * - * @event add-overlay - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {Element} element - The overlay element. - * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @property {OpenSeadragon.OverlayPlacement} placement - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'add-overlay', { - element: element, - location: options.location, - placement: options.placement - }); - } + $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); + this.viewer.addOverlay( element, location, placement, onDraw ); return this; }, @@ -242,36 +187,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * to. * @return {OpenSeadragon.Drawer} Chainable. * @fires OpenSeadragon.Viewer.event:update-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#updateOverlay} instead. */ updateOverlay: function( element, location, placement ) { - var i; - - element = $.getElement( element ); - i = getOverlayIndex( this.overlays, element ); - - if ( i >= 0 ) { - this.overlays[ i ].update( location, placement ); - this.updateAgain = true; - } - if( this.viewer ){ - /** - * Raised when an overlay's location or placement changes (see {@link OpenSeadragon.Drawer#updateOverlay}). - * - * @event update-overlay - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {Element} element - * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location - * @property {OpenSeadragon.OverlayPlacement} placement - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'update-overlay', { - element: element, - location: location, - placement: placement - }); - } + $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); + this.viewer.updateOverlay( element, location, placement ); return this; }, @@ -283,33 +203,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * element id which represent the ovelay content to be removed. * @return {OpenSeadragon.Drawer} Chainable. * @fires OpenSeadragon.Viewer.event:remove-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#removeOverlay} instead. */ removeOverlay: function( element ) { - var i; - - element = $.getElement( element ); - i = getOverlayIndex( this.overlays, element ); - - if ( i >= 0 ) { - this.overlays[ i ].destroy(); - this.overlays.splice( i, 1 ); - this.updateAgain = true; - } - if( this.viewer ){ - /** - * Raised when an overlay is removed from the viewer (see {@link OpenSeadragon.Drawer#removeOverlay}). - * - * @event remove-overlay - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {Element} element - The overlay element. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'remove-overlay', { - element: element - }); - } + $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); + this.viewer.updateOverlay( element ); return this; }, @@ -319,28 +217,14 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @method * @return {OpenSeadragon.Drawer} Chainable. * @fires OpenSeadragon.Viewer.event:clear-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#clearOverlays} instead. */ clearOverlays: function() { - while ( this.overlays.length > 0 ) { - this.overlays.pop().destroy(); - this.updateAgain = true; - } - if( this.viewer ){ - /** - * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}). - * - * @event clear-overlay - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'clear-overlay', {} ); - } + $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); + this.viewer.clearOverlays(); return this; }, - /** * Returns whether the Drawer is scheduled for an update at the * soonest possible opportunity. @@ -467,61 +351,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ } }; -/** - * @private - * @inner - */ - function addOverlayFromConfiguration( drawer, overlay ){ - - var element = null, - rect = ( overlay.height && overlay.width ) ? new $.Rect( - overlay.x || overlay.px, - overlay.y || overlay.py, - overlay.width, - overlay.height - ) : new $.Point( - overlay.x || overlay.px, - overlay.y || overlay.py - ), - id = overlay.id ? - overlay.id : - "openseadragon-overlay-"+Math.floor(Math.random()*10000000); - - element = $.getElement(overlay.id); - if( !element ){ - element = document.createElement("a"); - element.href = "#/overlay/"+id; - } - element.id = id; - $.addClass( element, overlay.className ? - overlay.className : - "openseadragon-overlay" - ); - - - if(overlay.px !== undefined){ - //if they specified 'px' so it's in pixel coordinates so - //we need to translate to viewport coordinates - rect = drawer.viewport.imageToViewportRectangle( rect ); - } - - if( overlay.placement ){ - return new $.Overlay({ - element: element, - location: drawer.viewport.pointFromPixel(rect), - placement: $.OverlayPlacement[overlay.placement.toUpperCase()], - onDraw: overlay.onDraw - }); - }else{ - return new $.Overlay({ - element: element, - location: rect, - onDraw: overlay.onDraw - }); - } - -} - /** * @private * @inner @@ -695,7 +524,6 @@ function updateViewport( drawer ) { //TODO drawTiles( drawer, drawer.lastDrawn ); - drawOverlays( drawer.viewport, drawer.overlays, drawer.container ); //TODO if ( best ) { @@ -1142,23 +970,6 @@ function resetCoverage( coverage, level ) { coverage[ level ] = {}; } -/** - * @private - * @inner - * Determines the 'z-index' of the given overlay. Overlays are ordered in - * a z-index based on the order they are added to the Drawer. - */ -function getOverlayIndex( overlays, element ) { - var i; - for ( i = overlays.length - 1; i >= 0; i-- ) { - if ( overlays[ i ].element == element ) { - return i; - } - } - - return -1; -} - /** * @private * @inner @@ -1196,28 +1007,6 @@ function finishLoadingImage( image, callback, successful, jobid ){ } - -function drawOverlays( viewport, overlays, container ){ - var i, - length = overlays.length; - for ( i = 0; i < length; i++ ) { - drawOverlay( viewport, overlays[ i ], container ); - } -} - -function drawOverlay( viewport, overlay, container ){ - - overlay.position = viewport.pixelFromPoint( - overlay.bounds.getTopLeft(), - true - ); - overlay.size = viewport.deltaPixelsFromPoints( - overlay.bounds.getSize(), - true - ); - overlay.drawHTML( container, viewport ); -} - function drawTiles( drawer, lastDrawn ){ var i, tile, @@ -1297,7 +1086,7 @@ function drawTiles( drawer, lastDrawn ){ ')'; } - drawer.addOverlay( + drawer.viewer.addOverlay( viewer.element, tile.bounds ); diff --git a/src/openseadragon.js b/src/openseadragon.js index 611d1cd4..a151f7e3 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -136,6 +136,28 @@ * is an Array of objects, it is used to create a * {@link OpenSeadragon.LegacyTileSource}. * + * @property {Array} overlays Array of objects defining permanent overlays of + * the viewer. The overlays added via this option and later removed with + * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new + * image is opened. + * To add overlays which can be definitively removed, one must use + * {@link OpenSeadragon.Viewer#addOverlay} + * If displaying a sequence of images, the overlays can be associated + * with a specific page by passing the overlays array to the page's + * tile source configuration. + * Expected properties: + * * x, y, (or px, py for pixel coordinates) to define the location. + * * width, height in point if using x,y or in pixels if using px,py. If width + * and height are specified, the overlay size is adjusted when zooming, + * otherwise the size stays the size of the content (or the size defined by CSS). + * * className to associate a class to the overlay + * * id to set the overlay element. If an element with this id already exists, + * it is reused, otherwise it is created. If not specified, a new element is + * created. + * * placement a string to define the relative position to the viewport. + * Only used if no width and height are specified. Default: 'TOP_LEFT'. + * See {@link OpenSeadragon.OverlayPlacement} for possible values. + * * @property {String} [xmlPath=null] * DEPRECATED. A relative path to load a DZI file from the server. * Prefer the newer Options.tileSources. diff --git a/src/overlay.js b/src/overlay.js index 29445389..accb6ea4 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -35,7 +35,8 @@ (function( $ ){ /** - * An enumeration of positions that an overlay may be assigned relative to the viewport. + * An enumeration of positions that an overlay may be assigned relative to + * the viewport. * @member OverlayPlacement * @memberof OpenSeadragon * @static @@ -69,8 +70,14 @@ * @memberof OpenSeadragon * @param {Object} options * @param {Element} options.element - * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - * @param {OpenSeadragon.OverlayPlacement} options.placement - Only used if location is an {@link OpenSeadragon.Point}. + * @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. + * @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT] + * Relative position to the viewport. + * Only used if location is a {@link OpenSeadragon.Point}. * @param {OpenSeadragon.Overlay.OnDrawCallback} options.onDraw */ $.Overlay = function( element, location, placement ) { @@ -86,9 +93,9 @@ */ var options; - if( $.isPlainObject( element ) ){ + if ( $.isPlainObject( element ) ) { options = element; - } else{ + } else { options = { element: element, location: location, @@ -174,7 +181,7 @@ 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, @@ -209,9 +216,15 @@ viewport.viewer.drawer.canvas.width / 2, viewport.viewer.drawer.canvas.height / 2 ), - degrees = viewport.degrees, - position, - size, + degrees = viewport.degrees, + position = viewport.pixelFromPoint( + this.bounds.getTopLeft(), + true + ), + size = viewport.deltaPixelsFromPoints( + this.bounds.getSize(), + true + ), overlayCenter; if ( element.parentNode != container ) { @@ -225,8 +238,8 @@ this.size = $.getElementSize( element ); } - position = this.position; - size = this.size; + this.position = position; + this.size = size; this.adjust( position, size ); diff --git a/src/point.js b/src/point.js index b3cd928a..38aad1f1 100644 --- a/src/point.js +++ b/src/point.js @@ -76,10 +76,10 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Substract another Point to this point and return a new Point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the + * @param {OpenSeadragon.Point} point The point to substract vector components. + * @returns {OpenSeadragon.Point} A new point representing the substraction of the * vector components */ minus: function( point ) { @@ -90,11 +90,11 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Multiply this point by a factor and return a new Point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @param {Number} factor The factor to multiply vector components. + * @returns {OpenSeadragon.Point} A new point representing the multiplication + * of the vector components by the factor */ times: function( factor ) { return new $.Point( @@ -104,11 +104,11 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Divide this point by a factor and return a new Point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @param {Number} factor The factor to divide vector components. + * @returns {OpenSeadragon.Point} A new point representing the division of the + * vector components by the factor */ divide: function( factor ) { return new $.Point( @@ -118,10 +118,9 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Compute the opposite of this point and return a new Point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the + * @returns {OpenSeadragon.Point} A new point representing the opposite of the * vector components */ negate: function() { @@ -129,11 +128,10 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Compute the distance between this point and another point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @param {OpenSeadragon.Point} point The point to compute the distance with. + * @returns {Number} The distance between the 2 points */ distanceTo: function( point ) { return Math.sqrt( @@ -143,22 +141,21 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Apply a function to each coordinate of this point and return a new point. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @param {function} func The function to apply to each coordinate. + * @returns {OpenSeadragon.Point} A new point with the coordinates computed + * by the specified function */ apply: function( func ) { return new $.Point( func( this.x ), func( this.y ) ); }, /** - * Add another Point to this point and return a new Point. + * Check if this point is equal to another one. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @param {OpenSeadragon.Point} point The point to compare this point with. + * @returns {Boolean} true if they are equal, false otherwise. */ equals: function( point ) { return ( @@ -186,11 +183,10 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ }, /** - * Add another Point to this point and return a new Point. + * Convert this point to a string in the format (x,y) where x and y are + * rounded to the nearest integer. * @function - * @param {OpenSeadragon.Point} point The point to add vector components. - * @returns {OpenSeadragon.Point} A new point representing the sum of the - * vector components + * @returns {String} A string representation of this point. */ toString: function() { return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")"; diff --git a/src/viewer.js b/src/viewer.js index cd4b5614..157e6ef1 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -74,8 +74,7 @@ $.Viewer = function( options ) { xmlPath: args.length > 1 ? args[ 1 ] : undefined, prefixUrl: args.length > 2 ? args[ 2 ] : undefined, controls: args.length > 3 ? args[ 3 ] : undefined, - overlays: args.length > 4 ? args[ 4 ] : undefined, - overlayControls: args.length > 5 ? args[ 5 ] : undefined + overlays: args.length > 4 ? args[ 4 ] : undefined }; } @@ -127,9 +126,8 @@ $.Viewer = function( options ) { */ canvas: null, - //TODO: not sure how to best describe these - overlays: [], - overlayControls:[], + // Overlays list. An overlay allows to add html on top of the viewer. + overlays: [], //private state properties previousBody: [], @@ -211,6 +209,7 @@ $.Viewer = function( options ) { }; this._updateRequestId = null; + this.currentOverlays = []; //Inherit some behaviors and properties $.EventSource.call( this ); @@ -572,9 +571,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.navigator.close(); } - if ( this.drawer ) { - this.drawer.clearOverlays(); - } + this.clearOverlays(); this.source = null; this.drawer = null; @@ -1334,6 +1331,173 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, return this; }, + /** + * Adds an html element as an overlay to the current viewport. Useful for + * highlighting words or areas of interest on an image or other zoomable + * interface. The overlays added via this method are removed when the viewport + * 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 overlayed. Or an Object specifying the configuration for the overlay + * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or + * rectangle which will be overlayed. + * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * viewport which the location coordinates will be treated as relative + * to. + * @param {function} onDraw - If supplied the callback is called when the overlay + * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. + * It is passed position, size and element. + * @return {OpenSeadragon.Viewer} Chainable. + * @fires OpenSeadragon.Viewer.event:add-overlay + */ + addOverlay: function( element, location, placement, onDraw ) { + var options; + if( $.isPlainObject( element ) ){ + options = element; + } else { + options = { + element: element, + location: location, + placement: placement, + onDraw: onDraw + }; + } + + element = $.getElement( options.element ); + + if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) { + // they're trying to add a duplicate overlay + return this; + } + this.currentOverlays.push( getOverlayObject( this, options ) ); + THIS[ this.hash ].forceRedraw = true; + /** + * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}). + * + * @event add-overlay + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {Element} element - The overlay element. + * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location + * @property {OpenSeadragon.OverlayPlacement} placement + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'add-overlay', { + element: element, + location: options.location, + placement: options.placement + }); + return this; + }, + + /** + * Updates the overlay represented by the reference to the element or + * element id moving it to the new location, relative to the new placement. + * @method + * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or + * rectangle which will be overlayed. + * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * viewport which the location coordinates will be treated as relative + * to. + * @return {OpenSeadragon.Viewer} Chainable. + * @fires OpenSeadragon.Viewer.event:update-overlay + */ + updateOverlay: function( element, location, placement ) { + var i; + + element = $.getElement( element ); + i = getOverlayIndex( this.currentOverlays, element ); + + if ( i >= 0 ) { + this.currentOverlays[ i ].update( location, placement ); + THIS[ this.hash ].forceRedraw = true; + /** + * Raised when an overlay's location or placement changes + * (see {@link OpenSeadragon.Viewer#updateOverlay}). + * + * @event update-overlay + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the + * Viewer which raised the event. + * @property {Element} element + * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location + * @property {OpenSeadragon.OverlayPlacement} placement + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'update-overlay', { + element: element, + location: location, + placement: placement + }); + } + return this; + }, + + /** + * Removes an overlay identified by the reference element or element id + * and schedules an update. + * @method + * @param {Element|String} element - A reference to the element or an + * element id which represent the ovelay content to be removed. + * @return {OpenSeadragon.Viewer} Chainable. + * @fires OpenSeadragon.Viewer.event:remove-overlay + */ + removeOverlay: function( element ) { + var i; + + element = $.getElement( element ); + i = getOverlayIndex( this.currentOverlays, element ); + + if ( i >= 0 ) { + this.currentOverlays[ i ].destroy(); + this.currentOverlays.splice( i, 1 ); + THIS[ this.hash ].forceRedraw = true; + /** + * Raised when an overlay is removed from the viewer + * (see {@link OpenSeadragon.Viewer#removeOverlay}). + * + * @event remove-overlay + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the + * Viewer which raised the event. + * @property {Element} element - The overlay element. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'remove-overlay', { + element: element + }); + } + return this; + }, + + /** + * Removes all currently configured Overlays from this Viewer and schedules + * an update. + * @method + * @return {OpenSeadragon.Viewer} Chainable. + * @fires OpenSeadragon.Viewer.event:clear-overlay + */ + clearOverlays: function() { + while ( this.currentOverlays.length > 0 ) { + this.currentOverlays.pop().destroy(); + } + THIS[ this.hash ].forceRedraw = true; + /** + * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}). + * + * @event clear-overlay + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'clear-overlay', {} ); + return this; + }, + /** * Updates the sequence buttons. * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons @@ -1419,9 +1583,8 @@ function _getSafeElemSize (oElement) { * @private */ function openTileSource( viewer, source ) { - var _this = viewer, - overlay, - i; + var i, + _this = viewer; if ( _this.source ) { _this.close( ); @@ -1455,7 +1618,7 @@ function openTileSource( viewer, source ) { //minZoomLevel: this.minZoomLevel, //maxZoomLevel: this.maxZoomLevel }); - }else{ + } else { if( source ){ _this.source = source; } @@ -1488,7 +1651,6 @@ function openTileSource( viewer, source ) { source: _this.source, viewport: _this.viewport, element: _this.canvas, - overlays: [].concat( _this.overlays ).concat( _this.source.overlays ), maxImageCacheCount: _this.maxImageCacheCount, imageLoaderLimit: _this.imageLoaderLimit, minZoomImageRatio: _this.minZoomImageRatio, @@ -1538,7 +1700,6 @@ function openTileSource( viewer, source ) { tileSources: source, tileHost: _this.tileHost, prefixUrl: _this.prefixUrl, - overlays: _this.overlays, viewer: _this }); } @@ -1556,7 +1717,6 @@ function openTileSource( viewer, source ) { tileSources: _this.tileSources, tileHost: _this.tileHost, prefixUrl: _this.prefixUrl, - overlays: _this.overlays, viewer: _this }); } @@ -1567,40 +1727,10 @@ function openTileSource( viewer, source ) { THIS[ _this.hash ].forceRedraw = true; _this._updateRequestId = scheduleUpdate( _this, updateMulti ); - //Assuming you had programatically created a bunch of overlays - //and added them via configuration - for ( i = 0; i < _this.overlayControls.length; i++ ) { - - overlay = _this.overlayControls[ i ]; - - if ( overlay.point ) { - - _this.drawer.addOverlay( - overlay.id, - new $.Point( - overlay.point.X, - overlay.point.Y - ), - $.OverlayPlacement.TOP_LEFT - ); - - } else { - - _this.drawer.addOverlay( - overlay.id, - new $.Rect( - overlay.rect.Point.X, - overlay.rect.Point.Y, - overlay.rect.Width, - overlay.rect.Height - ), - overlay.placement - ); - - } - } VIEWERS[ _this.hash ] = _this; + loadOverlays( _this ); + /** * Raised when the viewer has opened and loaded one or more TileSources. * @@ -1616,8 +1746,98 @@ function openTileSource( viewer, source ) { return _this; } +function loadOverlays( _this ) { + _this.currentOverlays = []; + for ( var i = 0; i < _this.overlays.length; i++ ) { + _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] ); + } + for ( var j = 0; j < _this.source.overlays.length; j++ ) { + _this.currentOverlays[ i + j ] = + getOverlayObject( _this, _this.source.overlays[ j ] ); + } +} +function getOverlayObject( viewer, overlay ) { + if ( overlay instanceof $.Overlay ) { + return overlay; + } + var element = null; + if ( overlay.element ) { + element = $.getElement( overlay.element ); + } else { + var id = overlay.id ? + overlay.id : + "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 ); + + element = $.getElement( overlay.id ); + if ( !element ) { + element = document.createElement( "a" ); + element.href = "#/overlay/" + id; + } + element.id = id; + $.addClass( element, overlay.className ? + overlay.className : + "openseadragon-overlay" + ); + } + + var location = overlay.location; + if ( !location ) { + var rect = ( overlay.height && overlay.width ) ? new $.Rect( + overlay.x || overlay.px, + overlay.y || overlay.py, + overlay.width, + overlay.height + ) : new $.Point( + overlay.x || overlay.px, + overlay.y || overlay.py + ); + if( overlay.px !== undefined ) { + //if they specified 'px' so it's in pixel coordinates so + //we need to translate to viewport coordinates + rect = viewer.viewport.imageToViewportRectangle( rect ); + } + location = overlay.placement ? viewer.viewport.pointFromPixel( rect ) : + rect; + } + + var placement = overlay.placement; + if ( placement && ( $.type( placement ) === "string" ) ) { + placement = $.OverlayPlacement[ overlay.placement.toUpperCase() ]; + } + + return new $.Overlay({ + element: element, + location: location, + placement: placement, + onDraw: overlay.onDraw + }); +} + +/** + * @private + * @inner + * Determines the index of the given overlay in the given overlays array. + */ +function getOverlayIndex( overlays, element ) { + var i; + for ( i = overlays.length - 1; i >= 0; i-- ) { + if ( overlays[ i ].element === element ) { + return i; + } + } + + return -1; +} + +function drawOverlays( viewport, overlays, container ) { + var i, + length = overlays.length; + for ( i = 0; i < length; i++ ) { + overlays[ i ].drawHTML( container, viewport ); + } +} /////////////////////////////////////////////////////////////////////////////// // Schedulers provide the general engine for animation @@ -1991,6 +2211,7 @@ function updateOnce( viewer ) { if ( animated ) { viewer.drawer.update(); + drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.canvas ); if( viewer.navigator ){ viewer.navigator.update( viewer.viewport ); } @@ -2006,6 +2227,7 @@ function updateOnce( viewer ) { viewer.raiseEvent( "animation" ); } else if ( THIS[ viewer.hash ].forceRedraw || viewer.drawer.needsUpdate() ) { viewer.drawer.update(); + drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.canvas ); if( viewer.navigator ){ viewer.navigator.update( viewer.viewport ); } diff --git a/src/viewport.js b/src/viewport.js index 65230f4a..9049e3d6 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -920,12 +920,12 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ /** * Convert pixel coordinates relative to the image to * viewer element coordinates. - * @param {OpenSeadragon.Point} point + * @param {OpenSeadragon.Point} pixel * @returns {OpenSeadragon.Point} */ - imageToViewerElementCoordinates: function( point ) { - var pixel = this.pixelFromPoint( point, true ); - return this.imageToViewportCoordinates( pixel ); + imageToViewerElementCoordinates: function( pixel ) { + var point = this.imageToViewportCoordinates( pixel ); + return this.pixelFromPoint( point, true ); }, /** diff --git a/test/overlays.js b/test/overlays.js new file mode 100644 index 00000000..47e0af89 --- /dev/null +++ b/test/overlays.js @@ -0,0 +1,344 @@ +/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal */ + +( function() { + var viewer; + + module( "Overlays", { + setup: function() { + 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 ) { + viewer.close(); + } + }; + + function waitForViewer( handler, count ) { + if ( typeof count !== "number" ) { + count = 0; + } + var ready = viewer.isOpen() && + viewer.drawer !== null && + !viewer.drawer.needsUpdate() && + 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 ) { + handler(); + } else if ( count < 50 ) { + count++; + setTimeout( function() { + waitForViewer( handler, count ); + }, 100 ); + } else { + console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + + ":" + viewer.drawer.needsUpdate() ); + handler(); + } + } + + asyncTest( 'Overlays via viewer options', function() { + + viewer = OpenSeadragon( { + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + springStiffness: 100, // Faster animation = faster tests + overlays: [ { + x: 0.1, + y: 0.4, + width: 0.09, + height: 0.09, + id: "overlay" + } ] + } ); + viewer.addHandler( 'open', openHandler ); + + function 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." ); + + viewer.addHandler( 'open', openPageHandler ); + viewer.goToPage( 1 ); + } + + function 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." ); + + viewer.addHandler( 'close', closeHandler ); + viewer.close(); + } + + function 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." ); + + start(); + } + } ); + + asyncTest( 'Page Overlays via viewer options', function() { + + viewer = OpenSeadragon( { + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: [ { + Image: { + xmlns: "http://schemas.microsoft.com/deepzoom/2008", + Url: "/test/data/testpattern_files/", + Format: "jpg", + Overlap: "1", + TileSize: "254", + Size: { + Width: 1000, + Height: 1000 + } + }, + overlays: [ { + x: 0.1, + y: 0.4, + width: 0.09, + height: 0.09, + id: "overlay" + } ] + }, { + Image: { + xmlns: "http://schemas.microsoft.com/deepzoom/2008", + Url: "/test/data/testpattern_files/", + Format: "jpg", + Overlap: "1", + TileSize: "254", + Size: { + Width: 1000, + Height: 1000 + } + } + } ], + springStiffness: 100 // Faster animation = faster tests + } ); + viewer.addHandler( 'open', openHandler ); + + function 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." ); + + viewer.addHandler( 'open', openPageHandler ); + viewer.goToPage( 1 ); + } + + function 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." ); + + viewer.addHandler( 'close', closeHandler ); + viewer.close(); + } + + function 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." ); + + start(); + } + } ); + + asyncTest( 'Overlays via addOverlay method', function() { + + viewer = OpenSeadragon( { + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + springStiffness: 100 // Faster animation = faster tests + } ); + viewer.addHandler( 'open', openHandler ); + + function 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." ); + + 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 ); + } + + function 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." ); + + viewer.addHandler( 'close', closeHandler ); + viewer.close(); + } + + function 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." ); + + start(); + } + + } ); + + asyncTest( 'Overlays size in pixels', function() { + + viewer = OpenSeadragon( { + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + springStiffness: 100, // Faster animation = faster tests + overlays: [ { + px: 13, + py: 120, + width: 124, + height: 132, + id: "overlay" + }, { + px: 400, + py: 500, + id: "fixed-overlay" + }] + } ); + + function checkOverlayPosition( contextMessage ) { + var viewport = viewer.viewport; + + var expPosition = viewport.imageToViewerElementCoordinates( + new OpenSeadragon.Point( 13, 120 ) ).apply( Math.floor ); + var actPosition = $( "#overlay" ).position(); + equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); + equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage ); + + var zoom = viewport.viewportToImageZoom( viewport.getZoom( true ) ); + var expectedWidth = Math.ceil( 124 * zoom ); + var expectedHeight = Math.ceil( 132 * zoom ); + equal( $( "#overlay" ).width(), expectedWidth, "Width mismatch " + contextMessage ); + equal( $( "#overlay" ).height( ), expectedHeight, "Height mismatch " + contextMessage ); + + + expPosition = viewport.imageToViewerElementCoordinates( + new OpenSeadragon.Point( 400, 500 ) ).apply( Math.floor ); + 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 ); + + equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); + equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + } + + 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.goHome(); + waitForViewer( function() { + checkOverlayPosition( "after goHome using image coordinates" ); + start(); + } ); + } ); + + } ); + } ); + + asyncTest( 'Overlays size in points', function() { + + viewer = OpenSeadragon( { + id: 'example-overlays', + prefixUrl: '/build/openseadragon/images/', + tileSources: [ '/test/data/testpattern.dzi', '/test/data/testpattern.dzi' ], + springStiffness: 100, // Faster animation = faster tests + overlays: [ { + x: 0.2, + y: 0.1, + width: 0.5, + height: 0.1, + id: "overlay" + },{ + x: 0.5, + y: 0.6, + id: "fixed-overlay" + } ] + } ); + + function checkOverlayPosition( contextMessage ) { + var viewport = viewer.viewport; + + var expPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.floor ); + var actPosition = $( "#overlay" ).position(); + equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage ); + equal( actPosition.top, expPosition.y, "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 ); + + + expPosition = viewport.viewportToViewerElementCoordinates( + new OpenSeadragon.Point( 0.5, 0.6 ) ).apply( Math.floor ); + 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 ); + + equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage ); + equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage ); + } + + 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.goHome(); + waitForViewer( function() { + checkOverlayPosition( "after goHome using viewport coordinates" ); + start(); + } ); + } ); + + } ); + } ); + +} )(); diff --git a/test/test.css b/test/test.css index 88dca730..9ff8c406 100644 --- a/test/test.css +++ b/test/test.css @@ -18,7 +18,7 @@ width: 300px; } -#unitsexample { +#unitsexample, #example-overlays { height: 500px; width: 500px; } diff --git a/test/test.html b/test/test.html index 1d5c1ce6..351a5c2e 100644 --- a/test/test.html +++ b/test/test.html @@ -27,6 +27,7 @@ +