diff --git a/src/viewport.js b/src/viewport.js index ddc26f0e..6f5ee841 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -46,7 +46,8 @@ $.Viewport = function( options ) { //backward compatibility for positional args while prefering more //idiomatic javascript options object as the only argument - var args = arguments; + var args = arguments, + that = this; if( args.length && args[ 0 ] instanceof $.Point ){ options = { containerSize: args[ 0 ], @@ -87,7 +88,244 @@ $.Viewport = function( options ) { degrees: $.DEFAULT_SETTINGS.degrees }, options ); + + /** + * @function + * @param {OpenSeadragon.Rect} bounds + * @param {Object} options (immediately=null, constraints=false) + * @return {OpenSeadragon.Viewport} Chainable. + */ + function fitBounds( bounds, options ) { + var newOptions = options || {}; + var immediately = newOptions.immediately || null; + var constraints = newOptions.constraints || false; + + var aspect = that.getAspectRatio(), + center = bounds.getCenter(), + newBounds = new $.Rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height + ), + oldBounds, + oldZoom, + newZoom, + referencePoint, + newBoundsAspectRatio, + newConstrainedZoom; + + if ( newBounds.getAspectRatio() >= aspect ) { + newBounds.height = bounds.width / aspect; + newBounds.y = center.y - newBounds.height / 2; + } else { + newBounds.width = bounds.height * aspect; + newBounds.x = center.x - newBounds.width / 2; + } + + if ( constraints ) { + newBoundsAspectRatio = newBounds.getAspectRatio(); + } + + that.panTo( that.getCenter( true ), true ); + that.zoomTo( that.getZoom( true ), null, true ); + + oldBounds = that.getBounds(); + oldZoom = that.getZoom(); + newZoom = 1.0 / newBounds.width; + + if ( constraints ) { + newConstrainedZoom = Math.max( + Math.min(newZoom, that.getMaxZoom() ), + that.getMinZoom() + ); + + if (newZoom !== newConstrainedZoom) { + newZoom = newConstrainedZoom; + newBounds.width = 1.0 / newZoom; + newBounds.x = center.x - newBounds.width / 2; + newBounds.height = newBounds.width / newBoundsAspectRatio; + newBounds.y = center.y - newBounds.height / 2; + } + + newBounds = applyBoundaryConstraints( newBounds, immediately ); + + if( that.viewer ){ + /** + * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). + * + * @event constrain + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {Boolean} immediately + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + that.viewer.raiseEvent( 'constrain', { + immediately: immediately + }); + } + } + + if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) { + return that.panTo( constraints ? newBounds.getCenter() : center, immediately ); + } + + referencePoint = oldBounds.getTopLeft().times( + that.containerSize.x / oldBounds.width + ).minus( + newBounds.getTopLeft().times( + that.containerSize.x / newBounds.width + ) + ).divide( + that.containerSize.x / oldBounds.width - + that.containerSize.x / newBounds.width + ); + + return that.zoomTo( newZoom, referencePoint, immediately ); + } + + /** + * @function + * @param {OpenSeadragon.Rect} bounds + * @param {Boolean} immediately + * @return {OpenSeadragon.Viewport} Chainable. + */ + this.fitBounds = function( bounds, immediately ) { + return fitBounds( bounds, { + immediately: immediately, + constraints: false + } ); + }; + + /** + * @function + * @param {OpenSeadragon.Rect} bounds + * @param {Boolean} immediately + * @return {OpenSeadragon.Viewport} Chainable. + */ + this.fitBoundsWithConstraints = function( bounds, immediately ) { + return fitBounds( bounds, { + immediately: immediately, + constraints: true + } ); + }; + /** + * @function + * @param {OpenSeadragon.Rect} bounds + * @param {Boolean} immediately + * @return {OpenSeadragon.Rect} constrained bounds. + */ + function applyBoundaryConstraints( bounds, immediately ) { + var horizontalThreshold, + verticalThreshold, + left, + right, + top, + bottom, + dx = 0, + dy = 0, + newBounds = new $.Rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height + ); + + horizontalThreshold = that.visibilityRatio * newBounds.width; + verticalThreshold = that.visibilityRatio * newBounds.height; + + left = newBounds.x + newBounds.width; + right = 1 - newBounds.x; + top = newBounds.y + newBounds.height; + bottom = that.contentAspectY - newBounds.y; + + if ( that.wrapHorizontal ) { + //do nothing + } else { + if ( left < horizontalThreshold ) { + dx = horizontalThreshold - left; + } + if ( right < horizontalThreshold ) { + dx = dx ? + ( dx + right - horizontalThreshold ) / 2 : + ( right - horizontalThreshold ); + } + } + + if ( that.wrapVertical ) { + //do nothing + } else { + if ( top < verticalThreshold ) { + dy = ( verticalThreshold - top ); + } + if ( bottom < verticalThreshold ) { + dy = dy ? + ( dy + bottom - verticalThreshold ) / 2 : + ( bottom - verticalThreshold ); + } + } + + if ( dx || dy || immediately ) { + newBounds.x += dx; + newBounds.y += dy; + if( newBounds.width > 1 ){ + newBounds.x = 0.5 - newBounds.width/2; + } + if( newBounds.height > that.contentAspectY ){ + newBounds.y = that.contentAspectY/2 - newBounds.height/2; + } + } + + return newBounds; + } + + /** + * @function + * @return {OpenSeadragon.Viewport} Chainable. + * @fires OpenSeadragon.Viewer.event:constrain + */ + this.applyConstraints = function( immediately ) { + var actualZoom = this.getZoom(), + constrainedZoom = Math.max( + Math.min( actualZoom, this.getMaxZoom() ), + this.getMinZoom() + ), + bounds, + constrainedBounds; + + if ( actualZoom != constrainedZoom ) { + this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); + } + + bounds = this.getBounds(); + + constrainedBounds = applyBoundaryConstraints( bounds, immediately ); + + if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){ + this.fitBounds( constrainedBounds, immediately ); + } + + if( this.viewer ){ + /** + * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). + * + * @event constrain + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {Boolean} immediately + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent( 'constrain', { + immediately: immediately + }); + } + + return this; + }; + this.centerSpringX = new $.Spring({ initial: 0, springStiffness: this.springStiffness, @@ -319,121 +557,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ return this.zoomSpring.target.value; } }, - - /** - * @function - * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately - * @return {OpenSeadragon.Rect} constrained bounds. - */ - _applyBoundaryConstraints: function( bounds, immediately ) { - var horizontalThreshold, - verticalThreshold, - left, - right, - top, - bottom, - dx = 0, - dy = 0, - newBounds = new $.Rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height - ); - - horizontalThreshold = this.visibilityRatio * newBounds.width; - verticalThreshold = this.visibilityRatio * newBounds.height; - - left = newBounds.x + newBounds.width; - right = 1 - newBounds.x; - top = newBounds.y + newBounds.height; - bottom = this.contentAspectY - newBounds.y; - - if ( this.wrapHorizontal ) { - //do nothing - } else { - if ( left < horizontalThreshold ) { - dx = horizontalThreshold - left; - } - if ( right < horizontalThreshold ) { - dx = dx ? - ( dx + right - horizontalThreshold ) / 2 : - ( right - horizontalThreshold ); - } - } - - if ( this.wrapVertical ) { - //do nothing - } else { - if ( top < verticalThreshold ) { - dy = ( verticalThreshold - top ); - } - if ( bottom < verticalThreshold ) { - dy = dy ? - ( dy + bottom - verticalThreshold ) / 2 : - ( bottom - verticalThreshold ); - } - } - - if ( dx || dy || immediately ) { - newBounds.x += dx; - newBounds.y += dy; - if( newBounds.width > 1 ){ - newBounds.x = 0.5 - newBounds.width/2; - } - if( newBounds.height > this.contentAspectY ){ - newBounds.y = this.contentAspectY/2 - newBounds.height/2; - } - } - - return newBounds; - }, - - /** - * @function - * @return {OpenSeadragon.Viewport} Chainable. - * @fires OpenSeadragon.Viewer.event:constrain - */ - applyConstraints: function( immediately ) { - var actualZoom = this.getZoom(), - constrainedZoom = Math.max( - Math.min( actualZoom, this.getMaxZoom() ), - this.getMinZoom() - ), - bounds, - constrainedBounds; - - if ( actualZoom != constrainedZoom ) { - this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); - } - - bounds = this.getBounds(); - - constrainedBounds = this._applyBoundaryConstraints( bounds, immediately ); - - if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){ - this.fitBounds( constrainedBounds, immediately ); - } - - if( this.viewer ){ - /** - * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). - * - * @event constrain - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {Boolean} immediately - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'constrain', { - immediately: immediately - }); - } - - return this; - }, /** * @function @@ -443,128 +566,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ return this.applyConstraints( immediately ); }, - /** - * @function - * @param {OpenSeadragon.Rect} bounds - * @param {Object} options (immediately=null, constraints=false) - * @return {OpenSeadragon.Viewport} Chainable. - */ - _fitBounds: function( bounds, options ) { - var newOptions = options || {}; - var immediately = newOptions.immediately || null; - var constraints = newOptions.constraints || false; - - var aspect = this.getAspectRatio(), - center = bounds.getCenter(), - newBounds = new $.Rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height - ), - oldBounds, - oldZoom, - newZoom, - referencePoint, - newBoundsAspectRatio, - newConstrainedZoom; - - if ( newBounds.getAspectRatio() >= aspect ) { - newBounds.height = bounds.width / aspect; - newBounds.y = center.y - newBounds.height / 2; - } else { - newBounds.width = bounds.height * aspect; - newBounds.x = center.x - newBounds.width / 2; - } - - if ( constraints ) { - newBoundsAspectRatio = newBounds.getAspectRatio(); - } - - this.panTo( this.getCenter( true ), true ); - this.zoomTo( this.getZoom( true ), null, true ); - - oldBounds = this.getBounds(); - oldZoom = this.getZoom(); - newZoom = 1.0 / newBounds.width; - - if ( constraints ) { - newConstrainedZoom = Math.max( - Math.min(newZoom, this.getMaxZoom() ), - this.getMinZoom() - ); - - if (newZoom !== newConstrainedZoom) { - newZoom = newConstrainedZoom; - newBounds.width = 1.0 / newZoom; - newBounds.x = center.x - newBounds.width / 2; - newBounds.height = newBounds.width / newBoundsAspectRatio; - newBounds.y = center.y - newBounds.height / 2; - } - - newBounds = this._applyBoundaryConstraints( newBounds, immediately ); - - if( this.viewer ){ - /** - * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). - * - * @event constrain - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {Boolean} immediately - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'constrain', { - immediately: immediately - }); - } - } - - if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) { - return this.panTo( constraints ? newBounds.getCenter() : center, immediately ); - } - - referencePoint = oldBounds.getTopLeft().times( - this.containerSize.x / oldBounds.width - ).minus( - newBounds.getTopLeft().times( - this.containerSize.x / newBounds.width - ) - ).divide( - this.containerSize.x / oldBounds.width - - this.containerSize.x / newBounds.width - ); - - return this.zoomTo( newZoom, referencePoint, immediately ); - }, - - /** - * @function - * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately - * @return {OpenSeadragon.Viewport} Chainable. - */ - fitBounds: function( bounds, immediately ) { - return this._fitBounds( bounds, { - immediately: immediately, - constraints: false - } ); - }, - - /** - * @function - * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately - * @return {OpenSeadragon.Viewport} Chainable. - */ - fitBoundsWithConstraints: function( bounds, immediately ) { - return this._fitBounds( bounds, { - immediately: immediately, - constraints: true - } ); - }, - /** * @function * @param {Boolean} immediately