First commit
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/*
|
4
.travis.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "0.8"
|
||||||
|
- "0.6"
|
63
README
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
== OpenSeadragon
|
||||||
|
|
||||||
|
This project is a fork of the OpenSeadragon project at http://openseadragon.codeplex.com/
|
||||||
|
|
||||||
|
We are forking it primarily to do heavy clean-up on the code base, simplify the build, and see what happens from there.
|
||||||
|
|
||||||
|
== On the Web
|
||||||
|
|
||||||
|
[ Current url: http://thatcher.github.com/openseadragon/ ]
|
||||||
|
|
||||||
|
== Building from Source
|
||||||
|
|
||||||
|
Building from source is easy with 'ant'. The result is included in the
|
||||||
|
distribution as 'openseadragon.js', the version being recording in the header.
|
||||||
|
|
||||||
|
> ant
|
||||||
|
|
||||||
|
== Licenses
|
||||||
|
|
||||||
|
OpenSeadragon was initially released with a New BSD License ( preserved below ), while
|
||||||
|
work done by Chris Thatcher is additionally licensed under the MIT License.
|
||||||
|
|
||||||
|
=== Original license preserved below
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
License: New BSD License (BSD)
|
||||||
|
Copyright (c) 2010, OpenSeadragon
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of OpenSeadragon nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
=== MIT License
|
||||||
|
|
||||||
|
--------------------------------------
|
||||||
|
(c) Christopher Thatcher 2011, 2012. All rights reserved.
|
||||||
|
|
||||||
|
Licensed with the MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
111
grunt.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*global module:false*/
|
||||||
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
|
// Project configuration.
|
||||||
|
grunt.initConfig({
|
||||||
|
lint: {
|
||||||
|
files: ['grunt.js', 'src/*.js']
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
openseadragon: {
|
||||||
|
src: [
|
||||||
|
'src/openseadragon.js',
|
||||||
|
'src/eventhandler.js',
|
||||||
|
'src/mousetracker.js',
|
||||||
|
'src/control.js',
|
||||||
|
'src/controldock.js',
|
||||||
|
'src/viewer.js',
|
||||||
|
'src/navigator.js',
|
||||||
|
'src/strings.js',
|
||||||
|
'src/point.js',
|
||||||
|
'src/tilesource.js',
|
||||||
|
'src/dzitilesource.js',
|
||||||
|
'src/legacytilesource.js',
|
||||||
|
'src/tilesourcecollection.js',
|
||||||
|
'src/button.js',
|
||||||
|
'src/buttongroup.js',
|
||||||
|
'src/rectangle.js',
|
||||||
|
'src/referencestrip.js',
|
||||||
|
'src/displayrectangle.js',
|
||||||
|
'src/spring.js',
|
||||||
|
'src/tile.js',
|
||||||
|
'src/overlay.js',
|
||||||
|
'src/drawer.js',
|
||||||
|
'src/viewport.js'
|
||||||
|
],
|
||||||
|
dest: 'build/openseadragon.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
openseadragon: {
|
||||||
|
src: ['build/openseadragon.js'],
|
||||||
|
dest: 'build/openseadragon.min.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
files: ['grunt.js', 'src/*.js'],
|
||||||
|
tasks: 'concat'
|
||||||
|
},
|
||||||
|
jshint: {
|
||||||
|
options: {
|
||||||
|
onecase: false,
|
||||||
|
immed: false,
|
||||||
|
debug: false,
|
||||||
|
evil: false,
|
||||||
|
strict: false,
|
||||||
|
multistr: false,
|
||||||
|
wsh: false,
|
||||||
|
couch: false,
|
||||||
|
laxbreak: true,
|
||||||
|
rhino: false,
|
||||||
|
globalstrict: false,
|
||||||
|
supernew: false,
|
||||||
|
laxcomma: false,
|
||||||
|
asi: false,
|
||||||
|
es5: false,
|
||||||
|
scripturl: false,
|
||||||
|
withstmt: false,
|
||||||
|
bitwise: true,
|
||||||
|
eqeqeq: false,
|
||||||
|
shadow: false,
|
||||||
|
expr: false,
|
||||||
|
noarg: true,
|
||||||
|
newcap: true,
|
||||||
|
forin: false,
|
||||||
|
regexdash: false,
|
||||||
|
node: false,
|
||||||
|
dojo: false,
|
||||||
|
eqnull: false,
|
||||||
|
browser: true,
|
||||||
|
mootools: false,
|
||||||
|
iterator: false,
|
||||||
|
undef: true,
|
||||||
|
latedef: true,
|
||||||
|
nonstandard: false,
|
||||||
|
trailing: false,
|
||||||
|
jquery: true,
|
||||||
|
loopfunc: false,
|
||||||
|
boss: false,
|
||||||
|
nonew: true,
|
||||||
|
funcscope: false,
|
||||||
|
regexp: false,
|
||||||
|
lastsemic: false,
|
||||||
|
smarttabs: false,
|
||||||
|
devel: false,
|
||||||
|
esnext: false,
|
||||||
|
sub: false,
|
||||||
|
curly: false,
|
||||||
|
prototypejs: false,
|
||||||
|
proto: false,
|
||||||
|
plusplus: false,
|
||||||
|
noempty: false
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default task.
|
||||||
|
grunt.registerTask('default', 'concat min');
|
||||||
|
|
||||||
|
};
|
BIN
images/fullpage_grouphover.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
BIN
images/fullpage_hover.png
Executable file
After Width: | Height: | Size: 5.1 KiB |
BIN
images/fullpage_pressed.png
Executable file
After Width: | Height: | Size: 5.1 KiB |
BIN
images/fullpage_rest.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
BIN
images/home_grouphover.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
BIN
images/home_hover.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
BIN
images/home_pressed.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
BIN
images/home_rest.png
Executable file
After Width: | Height: | Size: 4.9 KiB |
BIN
images/next_grouphover.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
images/next_hover.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/next_pressed.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/next_rest.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/previous_grouphover.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
images/previous_hover.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/previous_pressed.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
images/previous_rest.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
images/zoomin_grouphover.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
BIN
images/zoomin_hover.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
BIN
images/zoomin_pressed.png
Executable file
After Width: | Height: | Size: 5.1 KiB |
BIN
images/zoomin_rest.png
Executable file
After Width: | Height: | Size: 4.9 KiB |
BIN
images/zoomout_grouphover.png
Executable file
After Width: | Height: | Size: 4.5 KiB |
BIN
images/zoomout_hover.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
BIN
images/zoomout_pressed.png
Executable file
After Width: | Height: | Size: 4.9 KiB |
BIN
images/zoomout_rest.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
21
licenses/mit.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
(c) Christopher Thatcher 2011, 2012. All rights reserved.
|
||||||
|
|
||||||
|
Licensed with the MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
36
licenses/new-bsd.txt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
This license is preserved from the OpenSeadragon project at
|
||||||
|
http://openseadragon.codeplex.com/ as per the text below.
|
||||||
|
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
License: New BSD License (BSD)
|
||||||
|
Copyright (c) 2010, OpenSeadragon
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of OpenSeadragon nor the names of its contributors may be
|
||||||
|
used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
-------------------------------------
|
350
src/button.js
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of button states including, REST, GROUP, HOVER, and DOWN
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
$.ButtonState = {
|
||||||
|
REST: 0,
|
||||||
|
GROUP: 1,
|
||||||
|
HOVER: 2,
|
||||||
|
DOWN: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages events, hover states for individual buttons, tool-tips, as well
|
||||||
|
* as fading the bottons out when the user has not interacted with them
|
||||||
|
* for a specified period.
|
||||||
|
* @class
|
||||||
|
* @extends OpenSeadragon.EventHandler
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {String} options.tooltip Provides context help for the button we the
|
||||||
|
* user hovers over it.
|
||||||
|
* @param {String} options.srcRest URL of image to use in 'rest' state
|
||||||
|
* @param {String} options.srcGroup URL of image to use in 'up' state
|
||||||
|
* @param {String} options.srcHover URL of image to use in 'hover' state
|
||||||
|
* @param {String} options.srcDown URL of image to use in 'domn' state
|
||||||
|
* @param {Element} [options.element] Element to use as a container for the
|
||||||
|
* button.
|
||||||
|
* @property {String} tooltip Provides context help for the button we the
|
||||||
|
* user hovers over it.
|
||||||
|
* @property {String} srcRest URL of image to use in 'rest' state
|
||||||
|
* @property {String} srcGroup URL of image to use in 'up' state
|
||||||
|
* @property {String} srcHover URL of image to use in 'hover' state
|
||||||
|
* @property {String} srcDown URL of image to use in 'domn' state
|
||||||
|
* @property {Object} config Configurable settings for this button. DEPRECATED.
|
||||||
|
* @property {Element} [element] Element to use as a container for the
|
||||||
|
* button.
|
||||||
|
* @property {Number} fadeDelay How long to wait before fading
|
||||||
|
* @property {Number} fadeLength How long should it take to fade the button.
|
||||||
|
* @property {Number} fadeBeginTime When the button last began to fade.
|
||||||
|
* @property {Boolean} shouldFade Whether this button should fade after user
|
||||||
|
* stops interacting with the viewport.
|
||||||
|
this.fadeDelay = 0; // begin fading immediately
|
||||||
|
this.fadeLength = 2000; // fade over a period of 2 seconds
|
||||||
|
this.fadeBeginTime = null;
|
||||||
|
this.shouldFade = false;
|
||||||
|
*/
|
||||||
|
$.Button = function( options ) {
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
$.EventHandler.call( this );
|
||||||
|
|
||||||
|
$.extend( true, this, {
|
||||||
|
|
||||||
|
tooltip: null,
|
||||||
|
srcRest: null,
|
||||||
|
srcGroup: null,
|
||||||
|
srcHover: null,
|
||||||
|
srcDown: null,
|
||||||
|
clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
|
||||||
|
clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
|
||||||
|
// begin fading immediately
|
||||||
|
fadeDelay: 0,
|
||||||
|
// fade over a period of 2 seconds
|
||||||
|
fadeLength: 2000,
|
||||||
|
onPress: null,
|
||||||
|
onRelease: null,
|
||||||
|
onClick: null,
|
||||||
|
onEnter: null,
|
||||||
|
onExit: null,
|
||||||
|
onFocus: null,
|
||||||
|
onBlur: null
|
||||||
|
|
||||||
|
}, options );
|
||||||
|
|
||||||
|
this.element = options.element || $.makeNeutralElement( "button" );
|
||||||
|
this.element.href = this.element.href || '#';
|
||||||
|
|
||||||
|
//if the user has specified the element to bind the control to explicitly
|
||||||
|
//then do not add the default control images
|
||||||
|
if( !options.element ){
|
||||||
|
this.imgRest = $.makeTransparentImage( this.srcRest );
|
||||||
|
this.imgGroup = $.makeTransparentImage( this.srcGroup );
|
||||||
|
this.imgHover = $.makeTransparentImage( this.srcHover );
|
||||||
|
this.imgDown = $.makeTransparentImage( this.srcDown );
|
||||||
|
|
||||||
|
this.element.appendChild( this.imgRest );
|
||||||
|
this.element.appendChild( this.imgGroup );
|
||||||
|
this.element.appendChild( this.imgHover );
|
||||||
|
this.element.appendChild( this.imgDown );
|
||||||
|
|
||||||
|
this.imgGroup.style.position =
|
||||||
|
this.imgHover.style.position =
|
||||||
|
this.imgDown.style.position =
|
||||||
|
"absolute";
|
||||||
|
|
||||||
|
this.imgGroup.style.top =
|
||||||
|
this.imgHover.style.top =
|
||||||
|
this.imgDown.style.top =
|
||||||
|
"0px";
|
||||||
|
|
||||||
|
this.imgGroup.style.left =
|
||||||
|
this.imgHover.style.left =
|
||||||
|
this.imgDown.style.left =
|
||||||
|
"0px";
|
||||||
|
|
||||||
|
this.imgHover.style.visibility =
|
||||||
|
this.imgDown.style.visibility =
|
||||||
|
"hidden";
|
||||||
|
|
||||||
|
if ( $.Browser.vendor == $.BROWSERS.FIREFOX && $.Browser.version < 3 ){
|
||||||
|
this.imgGroup.style.top =
|
||||||
|
this.imgHover.style.top =
|
||||||
|
this.imgDown.style.top =
|
||||||
|
"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.addHandler( "onPress", this.onPress );
|
||||||
|
this.addHandler( "onRelease", this.onRelease );
|
||||||
|
this.addHandler( "onClick", this.onClick );
|
||||||
|
this.addHandler( "onEnter", this.onEnter );
|
||||||
|
this.addHandler( "onExit", this.onExit );
|
||||||
|
this.addHandler( "onFocus", this.onFocus );
|
||||||
|
this.addHandler( "onBlur", this.onBlur );
|
||||||
|
|
||||||
|
this.currentState = $.ButtonState.GROUP;
|
||||||
|
|
||||||
|
this.fadeBeginTime = null;
|
||||||
|
this.shouldFade = false;
|
||||||
|
|
||||||
|
this.element.style.display = "inline-block";
|
||||||
|
this.element.style.position = "relative";
|
||||||
|
this.element.title = this.tooltip;
|
||||||
|
|
||||||
|
this.tracker = new $.MouseTracker({
|
||||||
|
|
||||||
|
element: this.element,
|
||||||
|
clickTimeThreshold: this.clickTimeThreshold,
|
||||||
|
clickDistThreshold: this.clickDistThreshold,
|
||||||
|
|
||||||
|
enterHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
|
||||||
|
if ( buttonDownElement ) {
|
||||||
|
inTo( _this, $.ButtonState.DOWN );
|
||||||
|
_this.raiseEvent( "onEnter", _this );
|
||||||
|
} else if ( !buttonDownAny ) {
|
||||||
|
inTo( _this, $.ButtonState.HOVER );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
focusHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
|
||||||
|
this.enterHandler( tracker, position, buttonDownElement, buttonDownAny );
|
||||||
|
_this.raiseEvent( "onFocus", _this );
|
||||||
|
},
|
||||||
|
|
||||||
|
exitHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
|
||||||
|
outTo( _this, $.ButtonState.GROUP );
|
||||||
|
if ( buttonDownElement ) {
|
||||||
|
_this.raiseEvent( "onExit", _this );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
blurHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
|
||||||
|
this.exitHandler( tracker, position, buttonDownElement, buttonDownAny );
|
||||||
|
_this.raiseEvent( "onBlur", _this );
|
||||||
|
},
|
||||||
|
|
||||||
|
pressHandler: function( tracker, position ) {
|
||||||
|
inTo( _this, $.ButtonState.DOWN );
|
||||||
|
_this.raiseEvent( "onPress", _this );
|
||||||
|
},
|
||||||
|
|
||||||
|
releaseHandler: function( tracker, position, insideElementPress, insideElementRelease ) {
|
||||||
|
if ( insideElementPress && insideElementRelease ) {
|
||||||
|
outTo( _this, $.ButtonState.HOVER );
|
||||||
|
_this.raiseEvent( "onRelease", _this );
|
||||||
|
} else if ( insideElementPress ) {
|
||||||
|
outTo( _this, $.ButtonState.GROUP );
|
||||||
|
} else {
|
||||||
|
inTo( _this, $.ButtonState.HOVER );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clickHandler: function( tracker, position, quick, shift ) {
|
||||||
|
if ( quick ) {
|
||||||
|
_this.raiseEvent("onClick", _this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keyHandler: function( tracker, key ){
|
||||||
|
//console.log( "%s : handling key %s!", _this.tooltip, key);
|
||||||
|
if( 13 === key ){
|
||||||
|
_this.raiseEvent( "onClick", _this );
|
||||||
|
_this.raiseEvent( "onRelease", _this );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).setTracking( true );
|
||||||
|
|
||||||
|
outTo( this, $.ButtonState.REST );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.Button.prototype, $.EventHandler.prototype, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Determine what this function is intended to do and if it's actually
|
||||||
|
* useful as an API point.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.Button.prototype.notifyGroupEnter
|
||||||
|
*/
|
||||||
|
notifyGroupEnter: function() {
|
||||||
|
inTo( this, $.ButtonState.GROUP );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Determine what this function is intended to do and if it's actually
|
||||||
|
* useful as an API point.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.Button.prototype.notifyGroupExit
|
||||||
|
*/
|
||||||
|
notifyGroupExit: function() {
|
||||||
|
outTo( this, $.ButtonState.REST );
|
||||||
|
},
|
||||||
|
|
||||||
|
disable: function(){
|
||||||
|
this.notifyGroupExit();
|
||||||
|
this.element.disabled = true;
|
||||||
|
$.setElementOpacity( this.element, 0.2, true );
|
||||||
|
},
|
||||||
|
|
||||||
|
enable: function(){
|
||||||
|
this.element.disabled = false;
|
||||||
|
$.setElementOpacity( this.element, 1.0, true );
|
||||||
|
this.notifyGroupEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function scheduleFade( button ) {
|
||||||
|
window.setTimeout(function(){
|
||||||
|
updateFade( button );
|
||||||
|
}, 20 );
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFade( button ) {
|
||||||
|
var currentTime,
|
||||||
|
deltaTime,
|
||||||
|
opacity;
|
||||||
|
|
||||||
|
if ( button.shouldFade ) {
|
||||||
|
currentTime = +new Date();
|
||||||
|
deltaTime = currentTime - button.fadeBeginTime;
|
||||||
|
opacity = 1.0 - deltaTime / button.fadeLength;
|
||||||
|
opacity = Math.min( 1.0, opacity );
|
||||||
|
opacity = Math.max( 0.0, opacity );
|
||||||
|
|
||||||
|
if( button.imgGroup ){
|
||||||
|
$.setElementOpacity( button.imgGroup, opacity, true );
|
||||||
|
}
|
||||||
|
if ( opacity > 0 ) {
|
||||||
|
// fade again
|
||||||
|
scheduleFade( button );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginFading( button ) {
|
||||||
|
button.shouldFade = true;
|
||||||
|
button.fadeBeginTime = +new Date() + button.fadeDelay;
|
||||||
|
window.setTimeout( function(){
|
||||||
|
scheduleFade( button );
|
||||||
|
}, button.fadeDelay );
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopFading( button ) {
|
||||||
|
button.shouldFade = false;
|
||||||
|
if( button.imgGroup ){
|
||||||
|
$.setElementOpacity( button.imgGroup, 1.0, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inTo( button, newState ) {
|
||||||
|
|
||||||
|
if( button.element.disabled ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState >= $.ButtonState.GROUP &&
|
||||||
|
button.currentState == $.ButtonState.REST ) {
|
||||||
|
stopFading( button );
|
||||||
|
button.currentState = $.ButtonState.GROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState >= $.ButtonState.HOVER &&
|
||||||
|
button.currentState == $.ButtonState.GROUP ) {
|
||||||
|
if( button.imgHover ){
|
||||||
|
button.imgHover.style.visibility = "";
|
||||||
|
}
|
||||||
|
button.currentState = $.ButtonState.HOVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState >= $.ButtonState.DOWN &&
|
||||||
|
button.currentState == $.ButtonState.HOVER ) {
|
||||||
|
if( button.imgDown ){
|
||||||
|
button.imgDown.style.visibility = "";
|
||||||
|
}
|
||||||
|
button.currentState = $.ButtonState.DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function outTo( button, newState ) {
|
||||||
|
|
||||||
|
if( button.element.disabled ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState <= $.ButtonState.HOVER &&
|
||||||
|
button.currentState == $.ButtonState.DOWN ) {
|
||||||
|
if( button.imgDown ){
|
||||||
|
button.imgDown.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
button.currentState = $.ButtonState.HOVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState <= $.ButtonState.GROUP &&
|
||||||
|
button.currentState == $.ButtonState.HOVER ) {
|
||||||
|
if( button.imgHover ){
|
||||||
|
button.imgHover.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
button.currentState = $.ButtonState.GROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( newState <= $.ButtonState.REST &&
|
||||||
|
button.currentState == $.ButtonState.GROUP ) {
|
||||||
|
beginFading( button );
|
||||||
|
button.currentState = $.ButtonState.REST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
111
src/buttongroup.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
/**
|
||||||
|
* Manages events on groups of buttons.
|
||||||
|
* @class
|
||||||
|
* @param {Object} options - a dictionary of settings applied against the entire
|
||||||
|
* group of buttons
|
||||||
|
* @param {Array} options.buttons Array of buttons
|
||||||
|
* @param {Element} [options.group] Element to use as the container,
|
||||||
|
* @param {Object} options.config Object with Viewer settings ( TODO: is
|
||||||
|
* this actually used anywhere? )
|
||||||
|
* @param {Function} [options.enter] Function callback for when the mouse
|
||||||
|
* enters group
|
||||||
|
* @param {Function} [options.exit] Function callback for when mouse leaves
|
||||||
|
* the group
|
||||||
|
* @param {Function} [options.release] Function callback for when mouse is
|
||||||
|
* released
|
||||||
|
* @property {Array} buttons - An array containing the buttons themselves.
|
||||||
|
* @property {Element} element - The shared container for the buttons.
|
||||||
|
* @property {Object} config - Configurable settings for the group of buttons.
|
||||||
|
* @property {OpenSeadragon.MouseTracker} tracker - Tracks mouse events accross
|
||||||
|
* the group of buttons.
|
||||||
|
**/
|
||||||
|
$.ButtonGroup = function( options ) {
|
||||||
|
|
||||||
|
$.extend( true, this, {
|
||||||
|
buttons: [],
|
||||||
|
clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
|
||||||
|
clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
|
||||||
|
labelText: ""
|
||||||
|
}, options );
|
||||||
|
|
||||||
|
// copy the botton elements
|
||||||
|
var buttons = this.buttons.concat([]),
|
||||||
|
_this = this,
|
||||||
|
i;
|
||||||
|
|
||||||
|
this.element = options.element || $.makeNeutralElement( "fieldgroup" );
|
||||||
|
|
||||||
|
if( !options.group ){
|
||||||
|
this.label = $.makeNeutralElement( "label" );
|
||||||
|
//TODO: support labels for ButtonGroups
|
||||||
|
//this.label.innerHTML = this.labelText;
|
||||||
|
this.element.style.display = "inline-block";
|
||||||
|
this.element.appendChild( this.label );
|
||||||
|
for ( i = 0; i < buttons.length; i++ ) {
|
||||||
|
this.element.appendChild( buttons[ i ].element );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tracker = new $.MouseTracker({
|
||||||
|
element: this.element,
|
||||||
|
clickTimeThreshold: this.clickTimeThreshold,
|
||||||
|
clickDistThreshold: this.clickDistThreshold,
|
||||||
|
enterHandler: function() {
|
||||||
|
var i;
|
||||||
|
for ( i = 0; i < _this.buttons.length; i++ ) {
|
||||||
|
_this.buttons[ i ].notifyGroupEnter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exitHandler: function() {
|
||||||
|
var i,
|
||||||
|
buttonDownElement = arguments.length > 2 ?
|
||||||
|
arguments[ 2 ] :
|
||||||
|
null;
|
||||||
|
if ( !buttonDownElement ) {
|
||||||
|
for ( i = 0; i < _this.buttons.length; i++ ) {
|
||||||
|
_this.buttons[ i ].notifyGroupExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
releaseHandler: function() {
|
||||||
|
var i,
|
||||||
|
insideElementRelease = arguments.length > 3 ?
|
||||||
|
arguments[ 3 ] :
|
||||||
|
null;
|
||||||
|
if ( !insideElementRelease ) {
|
||||||
|
for ( i = 0; i < _this.buttons.length; i++ ) {
|
||||||
|
_this.buttons[ i ].notifyGroupExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setTracking( true );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ButtonGroup.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Figure out why this is used on the public API and if a more useful
|
||||||
|
* api can be created.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.ButtonGroup.prototype.emulateEnter
|
||||||
|
*/
|
||||||
|
emulateEnter: function() {
|
||||||
|
this.tracker.enterHandler();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Figure out why this is used on the public API and if a more useful
|
||||||
|
* api can be created.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.ButtonGroup.prototype.emulateExit
|
||||||
|
*/
|
||||||
|
emulateExit: function() {
|
||||||
|
this.tracker.exitHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
106
src/control.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of supported locations where controls can be anchored,
|
||||||
|
* including NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, and BOTTOM_LEFT.
|
||||||
|
* The anchoring is always relative to the container
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
$.ControlAnchor = {
|
||||||
|
NONE: 0,
|
||||||
|
TOP_LEFT: 1,
|
||||||
|
TOP_RIGHT: 2,
|
||||||
|
BOTTOM_RIGHT: 3,
|
||||||
|
BOTTOM_LEFT: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Control represents any interface element which is meant to allow the user
|
||||||
|
* to interact with the zoomable interface. Any control can be anchored to any
|
||||||
|
* element.
|
||||||
|
* @class
|
||||||
|
* @param {Element} element - the contol element to be anchored in the container.
|
||||||
|
* @param {OpenSeadragon.ControlAnchor} anchor - the location to anchor at.
|
||||||
|
* @param {Element} container - the element to control will be anchored too.
|
||||||
|
*
|
||||||
|
* @property {Element} element - the element providing the user interface with
|
||||||
|
* some type of control. Eg a zoom-in button
|
||||||
|
* @property {OpenSeadragon.ControlAnchor} anchor - the position of the control
|
||||||
|
* relative to the container.
|
||||||
|
* @property {Element} container - the element within with the control is
|
||||||
|
* positioned.
|
||||||
|
* @property {Element} wrapper - a nuetral element surrounding the control
|
||||||
|
* element.
|
||||||
|
*/
|
||||||
|
$.Control = function ( element, anchor, container ) {
|
||||||
|
this.element = element;
|
||||||
|
this.anchor = anchor;
|
||||||
|
this.container = container;
|
||||||
|
this.wrapper = $.makeNeutralElement( "span" );
|
||||||
|
this.wrapper.style.display = "inline-block";
|
||||||
|
this.wrapper.appendChild( this.element );
|
||||||
|
|
||||||
|
if ( this.anchor == $.ControlAnchor.NONE ) {
|
||||||
|
// IE6 fix
|
||||||
|
this.wrapper.style.width = this.wrapper.style.height = "100%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.anchor == $.ControlAnchor.TOP_RIGHT ||
|
||||||
|
this.anchor == $.ControlAnchor.BOTTOM_RIGHT ) {
|
||||||
|
this.container.insertBefore(
|
||||||
|
this.wrapper,
|
||||||
|
this.container.firstChild
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.container.appendChild( this.wrapper );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Control.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the control from the container.
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
this.wrapper.removeChild( this.element );
|
||||||
|
this.container.removeChild( this.wrapper );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the control is currently visible.
|
||||||
|
* @function
|
||||||
|
* @return {Boolean} true if currenly visible, false otherwise.
|
||||||
|
*/
|
||||||
|
isVisible: function() {
|
||||||
|
return this.wrapper.style.display != "none";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of the control.
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} visible - true to make visible, false to hide.
|
||||||
|
*/
|
||||||
|
setVisible: function( visible ) {
|
||||||
|
this.wrapper.style.display = visible ?
|
||||||
|
"inline-block" :
|
||||||
|
"none";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the opacity level for the control.
|
||||||
|
* @function
|
||||||
|
* @param {Number} opactiy - a value between 1 and 0 inclusively.
|
||||||
|
*/
|
||||||
|
setOpacity: function( opacity ) {
|
||||||
|
if ( this.element[ $.SIGNAL ] && $.Browser.vendor == $.BROWSERS.IE ) {
|
||||||
|
$.setElementOpacity( this.element, opacity, true );
|
||||||
|
} else {
|
||||||
|
$.setElementOpacity( this.wrapper, opacity, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
186
src/controldock.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
//id hash for private properties;
|
||||||
|
var THIS = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
$.ControlDock = function( options ){
|
||||||
|
var layouts = [ 'topleft', 'topright', 'bottomright', 'bottomleft'],
|
||||||
|
layout,
|
||||||
|
i;
|
||||||
|
|
||||||
|
$.extend( true, this, {
|
||||||
|
id: 'controldock-'+(+new Date())+'-'+Math.floor(Math.random()*1000000),
|
||||||
|
container: $.makeNeutralElement('form'),
|
||||||
|
controls: []
|
||||||
|
}, options );
|
||||||
|
|
||||||
|
if( this.element ){
|
||||||
|
this.element = $.getElement( this.element );
|
||||||
|
this.element.appendChild( this.container );
|
||||||
|
this.element.style.position = 'relative';
|
||||||
|
this.container.style.width = '100%';
|
||||||
|
this.container.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
for( i = 0; i < layouts.length; i++ ){
|
||||||
|
layout = layouts[ i ];
|
||||||
|
this.controls[ layout ] = $.makeNeutralElement( "div" );
|
||||||
|
this.controls[ layout ].style.position = 'absolute';
|
||||||
|
if ( layout.match( 'left' ) ){
|
||||||
|
this.controls[ layout ].style.left = '0px';
|
||||||
|
}
|
||||||
|
if ( layout.match( 'right' ) ){
|
||||||
|
this.controls[ layout ].style.right = '0px';
|
||||||
|
}
|
||||||
|
if ( layout.match( 'top' ) ){
|
||||||
|
this.controls[ layout ].style.top = '0px';
|
||||||
|
}
|
||||||
|
if ( layout.match( 'bottom' ) ){
|
||||||
|
this.controls[ layout ].style.bottom = '0px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container.appendChild( this.controls.topleft );
|
||||||
|
this.container.appendChild( this.controls.topright );
|
||||||
|
this.container.appendChild( this.controls.bottomright );
|
||||||
|
this.container.appendChild( this.controls.bottomleft );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ControlDock.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
addControl: function ( element, anchor ) {
|
||||||
|
element = $.getElement( element );
|
||||||
|
var div = null;
|
||||||
|
|
||||||
|
if ( getControlIndex( this, element ) >= 0 ) {
|
||||||
|
return; // they're trying to add a duplicate control
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( anchor ) {
|
||||||
|
case $.ControlAnchor.TOP_RIGHT:
|
||||||
|
div = this.controls.topright;
|
||||||
|
element.style.position = "relative";
|
||||||
|
element.style.paddingRight = "0px";
|
||||||
|
element.style.paddingTop = "0px";
|
||||||
|
break;
|
||||||
|
case $.ControlAnchor.BOTTOM_RIGHT:
|
||||||
|
div = this.controls.bottomright;
|
||||||
|
element.style.position = "relative";
|
||||||
|
element.style.paddingRight = "0px";
|
||||||
|
element.style.paddingBottom = "0px";
|
||||||
|
break;
|
||||||
|
case $.ControlAnchor.BOTTOM_LEFT:
|
||||||
|
div = this.controls.bottomleft;
|
||||||
|
element.style.position = "relative";
|
||||||
|
element.style.paddingLeft = "0px";
|
||||||
|
element.style.paddingBottom = "0px";
|
||||||
|
break;
|
||||||
|
case $.ControlAnchor.TOP_LEFT:
|
||||||
|
div = this.controls.topleft;
|
||||||
|
element.style.position = "relative";
|
||||||
|
element.style.paddingLeft = "0px";
|
||||||
|
element.style.paddingTop = "0px";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case $.ControlAnchor.NONE:
|
||||||
|
div = this.container;
|
||||||
|
element.style.margin = "0px";
|
||||||
|
element.style.padding = "0px";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controls.push(
|
||||||
|
new $.Control( element, anchor, div )
|
||||||
|
);
|
||||||
|
element.style.display = "inline-block";
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||||
|
*/
|
||||||
|
removeControl: function ( element ) {
|
||||||
|
element = $.getElement( element );
|
||||||
|
var i = getControlIndex( this, element );
|
||||||
|
|
||||||
|
if ( i >= 0 ) {
|
||||||
|
this.controls[ i ].destroy();
|
||||||
|
this.controls.splice( i, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||||
|
*/
|
||||||
|
clearControls: function () {
|
||||||
|
while ( this.controls.length > 0 ) {
|
||||||
|
this.controls.pop().destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
areControlsEnabled: function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for ( i = this.controls.length - 1; i >= 0; i-- ) {
|
||||||
|
if ( this.controls[ i ].isVisible() ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @return {OpenSeadragon.ControlDock} Chainable.
|
||||||
|
*/
|
||||||
|
setControlsEnabled: function( enabled ) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for ( i = this.controls.length - 1; i >= 0; i-- ) {
|
||||||
|
this.controls[ i ].setVisible( enabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utility methods
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
function getControlIndex( dock, element ) {
|
||||||
|
var controls = dock.controls,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for ( i = controls.length - 1; i >= 0; i-- ) {
|
||||||
|
if ( controls[ i ].element == element ) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
29
src/displayrectangle.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A display rectanlge is very similar to the OpenSeadragon.Rect but adds two
|
||||||
|
* fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels
|
||||||
|
* for this rectangle.
|
||||||
|
* @class
|
||||||
|
* @extends OpenSeadragon.Rect
|
||||||
|
* @param {Number} x The vector component 'x'.
|
||||||
|
* @param {Number} y The vector component 'y'.
|
||||||
|
* @param {Number} width The vector component 'height'.
|
||||||
|
* @param {Number} height The vector component 'width'.
|
||||||
|
* @param {Number} minLevel The lowest zoom level supported.
|
||||||
|
* @param {Number} maxLevel The highest zoom level supported.
|
||||||
|
* @property {Number} minLevel The lowest zoom level supported.
|
||||||
|
* @property {Number} maxLevel The highest zoom level supported.
|
||||||
|
*/
|
||||||
|
$.DisplayRect = function( x, y, width, height, minLevel, maxLevel ) {
|
||||||
|
$.Rect.apply( this, [ x, y, width, height ] );
|
||||||
|
|
||||||
|
this.minLevel = minLevel;
|
||||||
|
this.maxLevel = maxLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.DisplayRect.prototype, $.Rect.prototype );
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
1104
src/drawer.js
Normal file
331
src/dzitilesource.js
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @extends OpenSeadragon.TileSource
|
||||||
|
* @param {Number|Object} width - the pixel width of the image or the idiomatic
|
||||||
|
* options object which is used instead of positional arguments.
|
||||||
|
* @param {Number} height
|
||||||
|
* @param {Number} tileSize
|
||||||
|
* @param {Number} tileOverlap
|
||||||
|
* @param {String} tilesUrl
|
||||||
|
* @param {String} fileFormat
|
||||||
|
* @param {OpenSeadragon.DisplayRect[]} displayRects
|
||||||
|
* @property {String} tilesUrl
|
||||||
|
* @property {String} fileFormat
|
||||||
|
* @property {OpenSeadragon.DisplayRect[]} displayRects
|
||||||
|
*/
|
||||||
|
$.DziTileSource = function( width, height, tileSize, tileOverlap, tilesUrl, fileFormat, displayRects ) {
|
||||||
|
var i,
|
||||||
|
rect,
|
||||||
|
level,
|
||||||
|
options;
|
||||||
|
|
||||||
|
if( $.isPlainObject( width ) ){
|
||||||
|
options = width;
|
||||||
|
}else{
|
||||||
|
options = {
|
||||||
|
width: arguments[ 0 ],
|
||||||
|
height: arguments[ 1 ],
|
||||||
|
tileSize: arguments[ 2 ],
|
||||||
|
tileOverlap: arguments[ 3 ],
|
||||||
|
tilesUrl: arguments[ 4 ],
|
||||||
|
fileFormat: arguments[ 5 ],
|
||||||
|
displayRects: arguments[ 6 ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this._levelRects = {};
|
||||||
|
this.tilesUrl = options.tilesUrl;
|
||||||
|
this.fileFormat = options.fileFormat;
|
||||||
|
this.displayRects = options.displayRects;
|
||||||
|
|
||||||
|
if ( this.displayRects ) {
|
||||||
|
for ( i = this.displayRects.length - 1; i >= 0; i-- ) {
|
||||||
|
rect = this.displayRects[ i ];
|
||||||
|
for ( level = rect.minLevel; level <= rect.maxLevel; level++ ) {
|
||||||
|
if ( !this._levelRects[ level ] ) {
|
||||||
|
this._levelRects[ level ] = [];
|
||||||
|
}
|
||||||
|
this._levelRects[ level ].push( rect );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.TileSource.apply( this, [ options ] );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.DziTileSource.prototype, $.TileSource.prototype, {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the data and/or url imply the image service is supported by
|
||||||
|
* this tile source.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.supports
|
||||||
|
* @param {Object|Array} data
|
||||||
|
* @param {String} optional - url
|
||||||
|
*/
|
||||||
|
supports: function( data, url ){
|
||||||
|
var ns;
|
||||||
|
if ( data.Image ) {
|
||||||
|
ns = data.Image.xmlns;
|
||||||
|
} else if ( data.documentElement && "Image" == data.documentElement.tagName ) {
|
||||||
|
ns = data.documentElement.namespaceURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( "http://schemas.microsoft.com/deepzoom/2008" == ns ||
|
||||||
|
"http://schemas.microsoft.com/deepzoom/2009" == ns );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.configure
|
||||||
|
* @param {Object|XMLDocument} data - the raw configuration
|
||||||
|
* @param {String} url - the url the data was retreived from if any.
|
||||||
|
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||||
|
* to configure this tile sources constructor.
|
||||||
|
*/
|
||||||
|
configure: function( data, url ){
|
||||||
|
|
||||||
|
var dziPath,
|
||||||
|
dziName,
|
||||||
|
tilesUrl,
|
||||||
|
options,
|
||||||
|
host;
|
||||||
|
|
||||||
|
if( !$.isPlainObject(data) ){
|
||||||
|
|
||||||
|
options = configureFromXML( this, data );
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
options = configureFromObject( this, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( url && !options.tilesUrl ){
|
||||||
|
if( !( 'http' == url.substring( 0, 4 ) ) ){
|
||||||
|
host = location.protocol + '//' + location.host;
|
||||||
|
}
|
||||||
|
dziPath = url.split('/');
|
||||||
|
dziName = dziPath.pop();
|
||||||
|
dziName = dziName.substring(0, dziName.lastIndexOf('.'));
|
||||||
|
dziPath = '/' + dziPath.join('/') + '/' + dziName + '_files/';
|
||||||
|
tilesUrl = dziPath;
|
||||||
|
if( host ){
|
||||||
|
tilesUrl = host + tilesUrl;
|
||||||
|
}
|
||||||
|
options.tilesUrl = tilesUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.getTileUrl
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
getTileUrl: function( level, x, y ) {
|
||||||
|
return [ this.tilesUrl, level, '/', x, '_', y, '.', this.fileFormat ].join( '' );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.tileExists
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
tileExists: function( level, x, y ) {
|
||||||
|
var rects = this._levelRects[ level ],
|
||||||
|
rect,
|
||||||
|
scale,
|
||||||
|
xMin,
|
||||||
|
yMin,
|
||||||
|
xMax,
|
||||||
|
yMax,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if ( !rects || !rects.length ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( i = rects.length - 1; i >= 0; i-- ) {
|
||||||
|
rect = rects[ i ];
|
||||||
|
|
||||||
|
if ( level < rect.minLevel || level > rect.maxLevel ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale = this.getLevelScale( level );
|
||||||
|
xMin = rect.x * scale;
|
||||||
|
yMin = rect.y * scale;
|
||||||
|
xMax = xMin + rect.width * scale;
|
||||||
|
yMax = yMin + rect.height * scale;
|
||||||
|
|
||||||
|
xMin = Math.floor( xMin / this.tileSize );
|
||||||
|
yMin = Math.floor( yMin / this.tileSize );
|
||||||
|
xMax = Math.ceil( xMax / this.tileSize );
|
||||||
|
yMax = Math.ceil( yMax / this.tileSize );
|
||||||
|
|
||||||
|
if ( xMin <= x && x < xMax && yMin <= y && y < yMax ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function configureFromXML( tileSource, xmlDoc ){
|
||||||
|
|
||||||
|
if ( !xmlDoc || !xmlDoc.documentElement ) {
|
||||||
|
throw new Error( $.getString( "Errors.Xml" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = xmlDoc.documentElement,
|
||||||
|
rootName = root.tagName,
|
||||||
|
configuration = null,
|
||||||
|
displayRects = [],
|
||||||
|
dispRectNodes,
|
||||||
|
dispRectNode,
|
||||||
|
rectNode,
|
||||||
|
sizeNode,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if ( rootName == "Image" ) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
sizeNode = root.getElementsByTagName( "Size" )[ 0 ];
|
||||||
|
configuration = {
|
||||||
|
Image: {
|
||||||
|
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
|
||||||
|
Format: root.getAttribute( "Format" ),
|
||||||
|
DisplayRect: null,
|
||||||
|
Overlap: parseInt( root.getAttribute( "Overlap" ) ),
|
||||||
|
TileSize: parseInt( root.getAttribute( "TileSize" ) ),
|
||||||
|
Size: {
|
||||||
|
Height: parseInt( sizeNode.getAttribute( "Height" ) ),
|
||||||
|
Width: parseInt( sizeNode.getAttribute( "Width" ) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( !$.imageFormatSupported( configuration.Image.Format ) ) {
|
||||||
|
throw new Error(
|
||||||
|
$.getString( "Errors.ImageFormat", configuration.Image.Format.toUpperCase() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispRectNodes = root.getElementsByTagName( "DisplayRect" );
|
||||||
|
for ( i = 0; i < dispRectNodes.length; i++ ) {
|
||||||
|
dispRectNode = dispRectNodes[ i ];
|
||||||
|
rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
|
||||||
|
|
||||||
|
displayRects.push({
|
||||||
|
Rect: {
|
||||||
|
X: parseInt( rectNode.getAttribute( "X" ) ),
|
||||||
|
Y: parseInt( rectNode.getAttribute( "Y" ) ),
|
||||||
|
Width: parseInt( rectNode.getAttribute( "Width" ) ),
|
||||||
|
Height: parseInt( rectNode.getAttribute( "Height" ) ),
|
||||||
|
MinLevel: 0, // ignore MinLevel attribute, bug in Deep Zoom Composer
|
||||||
|
MaxLevel: parseInt( dispRectNode.getAttribute( "MaxLevel" ) )
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if( displayRects.length ){
|
||||||
|
configuration.Image.DisplayRect = displayRects;
|
||||||
|
}
|
||||||
|
|
||||||
|
return configureFromObject( tileSource, configuration );
|
||||||
|
|
||||||
|
} catch ( e ) {
|
||||||
|
throw (e instanceof Error) ?
|
||||||
|
e :
|
||||||
|
new Error( $.getString("Errors.Dzi") );
|
||||||
|
}
|
||||||
|
} else if ( rootName == "Collection" ) {
|
||||||
|
throw new Error( $.getString( "Errors.Dzc" ) );
|
||||||
|
} else if ( rootName == "Error" ) {
|
||||||
|
return processDZIError( root );
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error( $.getString( "Errors.Dzi" ) );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function configureFromObject( tileSource, configuration ){
|
||||||
|
var imageData = configuration.Image,
|
||||||
|
tilesUrl = imageData.Url,
|
||||||
|
fileFormat = imageData.Format,
|
||||||
|
sizeData = imageData.Size,
|
||||||
|
dispRectData = imageData.DisplayRect || [],
|
||||||
|
width = parseInt( sizeData.Width ),
|
||||||
|
height = parseInt( sizeData.Height ),
|
||||||
|
tileSize = parseInt( imageData.TileSize ),
|
||||||
|
tileOverlap = parseInt( imageData.Overlap ),
|
||||||
|
displayRects = [],
|
||||||
|
rectData,
|
||||||
|
i;
|
||||||
|
|
||||||
|
//TODO: need to figure out out to better handle image format compatibility
|
||||||
|
// which actually includes additional file formats like xml and pdf
|
||||||
|
// and plain text for various tilesource implementations to avoid low
|
||||||
|
// level errors.
|
||||||
|
//
|
||||||
|
// For now, just don't perform the check.
|
||||||
|
//
|
||||||
|
/*if ( !imageFormatSupported( fileFormat ) ) {
|
||||||
|
throw new Error(
|
||||||
|
$.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
for ( i = 0; i < dispRectData.length; i++ ) {
|
||||||
|
rectData = dispRectData[ i ].Rect;
|
||||||
|
|
||||||
|
displayRects.push( new $.DisplayRect(
|
||||||
|
parseInt( rectData.X ),
|
||||||
|
parseInt( rectData.Y ),
|
||||||
|
parseInt( rectData.Width ),
|
||||||
|
parseInt( rectData.Height ),
|
||||||
|
0, // ignore MinLevel attribute, bug in Deep Zoom Composer
|
||||||
|
parseInt( rectData.MaxLevel )
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: width, /* width *required */
|
||||||
|
height: height, /* height *required */
|
||||||
|
tileSize: tileSize, /* tileSize *required */
|
||||||
|
tileOverlap: tileOverlap, /* tileOverlap *required */
|
||||||
|
minLevel: null, /* minLevel */
|
||||||
|
maxLevel: null, /* maxLevel */
|
||||||
|
tilesUrl: tilesUrl, /* tilesUrl */
|
||||||
|
fileFormat: fileFormat, /* fileFormat */
|
||||||
|
displayRects: displayRects /* displayRects */
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
97
src/eventhandler.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function($){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For use by classes which want to support custom, non-browser events.
|
||||||
|
* TODO: This is an aweful name! This thing represents an "event source",
|
||||||
|
* not an "event handler". PLEASE change the to EventSource. Also please
|
||||||
|
* change 'addHandler', 'removeHandler' and 'raiseEvent' to 'bind',
|
||||||
|
* 'unbind', and 'trigger' respectively. Finally add a method 'one' which
|
||||||
|
* automatically unbinds a listener after the first triggered event that
|
||||||
|
* matches.
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
$.EventHandler = function() {
|
||||||
|
this.events = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
$.EventHandler.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event handler for a given event.
|
||||||
|
* @function
|
||||||
|
* @param {String} eventName - Name of event to register.
|
||||||
|
* @param {Function} handler - Function to call when event is triggered.
|
||||||
|
*/
|
||||||
|
addHandler: function( eventName, handler ) {
|
||||||
|
var events = this.events[ eventName ];
|
||||||
|
if( !events ){
|
||||||
|
this.events[ eventName ] = events = [];
|
||||||
|
}
|
||||||
|
if( handler && $.isFunction( handler ) ){
|
||||||
|
events[ events.length ] = handler;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific event handler for a given event.
|
||||||
|
* @function
|
||||||
|
* @param {String} eventName - Name of event for which the handler is to be removed.
|
||||||
|
* @param {Function} handler - Function to be removed.
|
||||||
|
*/
|
||||||
|
removeHandler: function( eventName, handler ) {
|
||||||
|
//Start Thatcher - unneccessary indirection. Also, because events were
|
||||||
|
// - not actually being removed, we need to add the code
|
||||||
|
// - to do the removal ourselves. TODO
|
||||||
|
var events = this.events[ eventName ];
|
||||||
|
if ( !events ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//End Thatcher
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrive the list of all handlers registered for a given event.
|
||||||
|
* @function
|
||||||
|
* @param {String} eventName - Name of event to get handlers for.
|
||||||
|
*/
|
||||||
|
getHandler: function( eventName ) {
|
||||||
|
var events = this.events[ eventName ];
|
||||||
|
if ( !events || !events.length ){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
events = events.length === 1 ?
|
||||||
|
[ events[ 0 ] ] :
|
||||||
|
Array.apply( null, events );
|
||||||
|
return function( source, args ) {
|
||||||
|
var i,
|
||||||
|
length = events.length;
|
||||||
|
for ( i = 0; i < length; i++ ) {
|
||||||
|
if( events[ i ] ){
|
||||||
|
events[ i ]( source, args );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger an event, optionally passing additional information.
|
||||||
|
* @function
|
||||||
|
* @param {String} eventName - Name of event to register.
|
||||||
|
* @param {Function} handler - Function to call when event is triggered.
|
||||||
|
*/
|
||||||
|
raiseEvent: function( eventName, eventArgs ) {
|
||||||
|
var handler = this.getHandler( eventName );
|
||||||
|
|
||||||
|
if ( handler ) {
|
||||||
|
if ( !eventArgs ) {
|
||||||
|
eventArgs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
handler( this, eventArgs );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
262
src/legacytilesource.js
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LegacyTileSource allows simple, traditional image pyramids to be loaded
|
||||||
|
* into an OpenSeadragon Viewer. Basically, this translates to the historically
|
||||||
|
* common practice of starting with a 'master' image, maybe a tiff for example,
|
||||||
|
* and generating a set of 'service' images like one or more thumbnails, a medium
|
||||||
|
* resolution image and a high resolution image in standard web formats like
|
||||||
|
* png or jpg.
|
||||||
|
* @class
|
||||||
|
* @param {Array} levels An array of file descriptions, each is an object with
|
||||||
|
* a 'url', a 'width', and a 'height'. Overriding classes can expect more
|
||||||
|
* properties but these properties are sufficient for this implementation.
|
||||||
|
* Additionally, the levels are required to be listed in order from
|
||||||
|
* smallest to largest.
|
||||||
|
* @property {Number} aspectRatio
|
||||||
|
* @property {Number} dimensions
|
||||||
|
* @property {Number} tileSize
|
||||||
|
* @property {Number} tileOverlap
|
||||||
|
* @property {Number} minLevel
|
||||||
|
* @property {Number} maxLevel
|
||||||
|
* @property {Array} levels
|
||||||
|
*/
|
||||||
|
$.LegacyTileSource = function( levels ) {
|
||||||
|
|
||||||
|
var options,
|
||||||
|
width,
|
||||||
|
height;
|
||||||
|
|
||||||
|
if( $.isArray( levels ) ){
|
||||||
|
options = {
|
||||||
|
type: 'legacy-image-pyramid',
|
||||||
|
levels: levels
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//clean up the levels to make sure we support all formats
|
||||||
|
options.levels = filterFiles( options.levels );
|
||||||
|
width = options.levels[ options.levels.length - 1 ].width;
|
||||||
|
height = options.levels[ options.levels.length - 1 ].height;
|
||||||
|
|
||||||
|
$.extend( true, options, {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
tileSize: Math.max( height, width ),
|
||||||
|
tileOverlap: 0,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: options.levels.length - 1
|
||||||
|
});
|
||||||
|
|
||||||
|
$.TileSource.apply( this, [ options ] );
|
||||||
|
|
||||||
|
this.levels = options.levels;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, {
|
||||||
|
/**
|
||||||
|
* Determine if the data and/or url imply the image service is supported by
|
||||||
|
* this tile source.
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.supports
|
||||||
|
* @param {Object|Array} data
|
||||||
|
* @param {String} optional - url
|
||||||
|
*/
|
||||||
|
supports: function( data, url ){
|
||||||
|
return (
|
||||||
|
data.type &&
|
||||||
|
"legacy-image-pyramid" == data.type
|
||||||
|
) || (
|
||||||
|
data.documentElement &&
|
||||||
|
"legacy-image-pyramid" == data.documentElement.getAttribute('type')
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.DziTileSource.prototype.configure
|
||||||
|
* @param {Object|XMLDocument} configuration - the raw configuration
|
||||||
|
* @param {String} dataUrl - the url the data was retreived from if any.
|
||||||
|
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||||
|
* to configure this tile sources constructor.
|
||||||
|
*/
|
||||||
|
configure: function( configuration, dataUrl ){
|
||||||
|
|
||||||
|
var options;
|
||||||
|
|
||||||
|
if( !$.isPlainObject(configuration) ){
|
||||||
|
|
||||||
|
options = configureFromXML( this, configuration );
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
options = configureFromObject( this, configuration );
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
*/
|
||||||
|
getLevelScale: function( level ) {
|
||||||
|
var levelScale = NaN;
|
||||||
|
if ( level >= this.minLevel && level <= this.maxLevel ){
|
||||||
|
levelScale =
|
||||||
|
this.levels[ level ].width /
|
||||||
|
this.levels[ this.maxLevel ].width;
|
||||||
|
}
|
||||||
|
return levelScale;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
*/
|
||||||
|
getNumTiles: function( level ) {
|
||||||
|
var scale = this.getLevelScale( level );
|
||||||
|
if ( scale ){
|
||||||
|
return new $.Point( 1, 1 );
|
||||||
|
} else {
|
||||||
|
return new $.Point( 0, 0 );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {OpenSeadragon.Point} point
|
||||||
|
*/
|
||||||
|
getTileAtPoint: function( level, point ) {
|
||||||
|
return new $.Point( 0, 0 );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is not implemented by this class other than to throw an Error
|
||||||
|
* announcing you have to implement it. Because of the variety of tile
|
||||||
|
* server technologies, and various specifications for building image
|
||||||
|
* pyramids, this method is here to allow easy integration.
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
getTileUrl: function( level, x, y ) {
|
||||||
|
var url = null;
|
||||||
|
if( level >= this.minLevel && level <= this.maxLevel ){
|
||||||
|
url = this.levels[ level ].url;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method removes any files from the Array which dont conform to our
|
||||||
|
* basic requirements for a 'level' in the LegacyTileSource.
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function filterFiles( files ){
|
||||||
|
var filtered = [],
|
||||||
|
file,
|
||||||
|
i;
|
||||||
|
for( i = 0; i < files.length; i++ ){
|
||||||
|
file = files[ i ];
|
||||||
|
if( file.height &&
|
||||||
|
file.width &&
|
||||||
|
file.url && (
|
||||||
|
file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)$/) || (
|
||||||
|
file.mimetype &&
|
||||||
|
file.mimetype.toLowerCase().match(/^.*\/(png|jpg|jpeg|gif)$/)
|
||||||
|
)
|
||||||
|
) ){
|
||||||
|
//This is sufficient to serve as a level
|
||||||
|
filtered.push({
|
||||||
|
url: file.url,
|
||||||
|
width: Number( file.width ),
|
||||||
|
height: Number( file.height )
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered.sort(function(a,b){
|
||||||
|
return a.height - b.height;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function configureFromXML( tileSource, xmlDoc ){
|
||||||
|
|
||||||
|
if ( !xmlDoc || !xmlDoc.documentElement ) {
|
||||||
|
throw new Error( $.getString( "Errors.Xml" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = xmlDoc.documentElement,
|
||||||
|
rootName = root.tagName,
|
||||||
|
conf = null,
|
||||||
|
levels = [],
|
||||||
|
level,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if ( rootName == "image" ) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
conf = {
|
||||||
|
type: root.getAttribute( "type" ),
|
||||||
|
levels: []
|
||||||
|
};
|
||||||
|
|
||||||
|
levels = root.getElementsByTagName( "level" );
|
||||||
|
for ( i = 0; i < levels.length; i++ ) {
|
||||||
|
level = levels[ i ];
|
||||||
|
|
||||||
|
conf.levels .push({
|
||||||
|
url: level.getAttribute( "url" ),
|
||||||
|
width: parseInt( level.getAttribute( "width" ), 10 ),
|
||||||
|
height: parseInt( level.getAttribute( "height" ), 10 )
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return configureFromObject( tileSource, conf );
|
||||||
|
|
||||||
|
} catch ( e ) {
|
||||||
|
throw (e instanceof Error) ?
|
||||||
|
e :
|
||||||
|
new Error( 'Unknown error parsing Legacy Image Pyramid XML.' );
|
||||||
|
}
|
||||||
|
} else if ( rootName == "collection" ) {
|
||||||
|
throw new Error( 'Legacy Image Pyramid Collections not yet supported.' );
|
||||||
|
} else if ( rootName == "error" ) {
|
||||||
|
throw new Error( 'Error: ' + xmlDoc );
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error( 'Unknown element ' + rootName );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function configureFromObject( tileSource, configuration ){
|
||||||
|
|
||||||
|
return configuration.levels;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
1052
src/mousetracker.js
Normal file
303
src/navigator.js
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Navigator provides a small view of the current image as fixed
|
||||||
|
* while representing the viewport as a moving box serving as a frame
|
||||||
|
* of reference in the larger viewport as to which portion of the image
|
||||||
|
* is currently being examined. The navigators viewport can be interacted
|
||||||
|
* with using the keyboard or the mouse.
|
||||||
|
* @class
|
||||||
|
* @name OpenSeadragon.Navigator
|
||||||
|
* @extends OpenSeadragon.Viewer
|
||||||
|
* @extends OpenSeadragon.EventHandler
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {String} options.viewerId
|
||||||
|
*/
|
||||||
|
$.Navigator = function( options ){
|
||||||
|
|
||||||
|
var _this = this,
|
||||||
|
viewer = options.viewer,
|
||||||
|
viewerSize = $.getElementSize( viewer.element );
|
||||||
|
|
||||||
|
//We may need to create a new element and id if they did not
|
||||||
|
//provide the id for the existing element
|
||||||
|
if( !options.id ){
|
||||||
|
options.id = 'navigator-' + (+new Date());
|
||||||
|
this.element = $.makeNeutralElement( "div" );
|
||||||
|
this.element.id = options.id;
|
||||||
|
this.element.className = 'navigator';
|
||||||
|
}
|
||||||
|
|
||||||
|
options = $.extend( true, {
|
||||||
|
sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio
|
||||||
|
}, options, {
|
||||||
|
element: this.element,
|
||||||
|
//These need to be overridden to prevent recursion since
|
||||||
|
//the navigator is a viewer and a viewer has a navigator
|
||||||
|
showNavigator: false,
|
||||||
|
mouseNavEnabled: false,
|
||||||
|
showNavigationControl: false,
|
||||||
|
showSequenceControl: false
|
||||||
|
});
|
||||||
|
|
||||||
|
options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
|
||||||
|
|
||||||
|
(function( style ){
|
||||||
|
style.marginTop = '0px';
|
||||||
|
style.marginRight = '0px';
|
||||||
|
style.marginBottom = '0px';
|
||||||
|
style.marginLeft = '0px';
|
||||||
|
style.border = '2px solid #555';
|
||||||
|
style.background = '#000';
|
||||||
|
style.opacity = 0.8;
|
||||||
|
style.overflow = 'hidden';
|
||||||
|
}( this.element.style ));
|
||||||
|
|
||||||
|
this.displayRegion = $.makeNeutralElement( "textarea" );
|
||||||
|
this.displayRegion.id = this.element.id + '-displayregion';
|
||||||
|
this.displayRegion.className = 'displayregion';
|
||||||
|
|
||||||
|
(function( style ){
|
||||||
|
style.position = 'relative';
|
||||||
|
style.top = '0px';
|
||||||
|
style.left = '0px';
|
||||||
|
style.fontSize = '0px';
|
||||||
|
style.overflow = 'hidden';
|
||||||
|
style.border = '2px solid #900';
|
||||||
|
|
||||||
|
//TODO: IE doesnt like this property being set
|
||||||
|
//try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
|
||||||
|
|
||||||
|
style.background = 'transparent';
|
||||||
|
|
||||||
|
// We use square bracket notation on the statement below, because float is a keyword.
|
||||||
|
// This is important for the Google Closure compliler, if nothing else.
|
||||||
|
/*jshint sub:true */
|
||||||
|
style['float'] = 'left'; //Webkit
|
||||||
|
|
||||||
|
style.cssFloat = 'left'; //Firefox
|
||||||
|
style.styleFloat = 'left'; //IE
|
||||||
|
style.zIndex = 999999999;
|
||||||
|
style.cursor = 'default';
|
||||||
|
}( this.displayRegion.style ));
|
||||||
|
|
||||||
|
this.element.innerTracker = new $.MouseTracker({
|
||||||
|
element: this.element,
|
||||||
|
scrollHandler: function(){
|
||||||
|
//dont scroll the page up and down if the user is scrolling
|
||||||
|
//in the navigator
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).setTracking( true );
|
||||||
|
|
||||||
|
this.displayRegion.innerTracker = new $.MouseTracker({
|
||||||
|
element: this.displayRegion,
|
||||||
|
clickTimeThreshold: this.clickTimeThreshold,
|
||||||
|
clickDistThreshold: this.clickDistThreshold,
|
||||||
|
clickHandler: $.delegate( this, onCanvasClick ),
|
||||||
|
dragHandler: $.delegate( this, onCanvasDrag ),
|
||||||
|
releaseHandler: $.delegate( this, onCanvasRelease ),
|
||||||
|
scrollHandler: $.delegate( this, onCanvasScroll ),
|
||||||
|
focusHandler: function(){
|
||||||
|
var point = $.getElementPosition( _this.viewer.element );
|
||||||
|
|
||||||
|
window.scrollTo( 0, point.y );
|
||||||
|
|
||||||
|
_this.viewer.setControlsEnabled( true );
|
||||||
|
(function( style ){
|
||||||
|
style.border = '2px solid #437AB2';
|
||||||
|
//style.outline = '2px auto #437AB2';
|
||||||
|
}( this.element.style ));
|
||||||
|
|
||||||
|
},
|
||||||
|
blurHandler: function(){
|
||||||
|
_this.viewer.setControlsEnabled( false );
|
||||||
|
(function( style ){
|
||||||
|
style.border = '2px solid #900';
|
||||||
|
//style.outline = '2px auto #900';
|
||||||
|
}( this.element.style ));
|
||||||
|
},
|
||||||
|
keyHandler: function(tracker, keyCode, shiftKey){
|
||||||
|
//console.log( keyCode );
|
||||||
|
switch( keyCode ){
|
||||||
|
case 61://=|+
|
||||||
|
_this.viewer.viewport.zoomBy(1.1);
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 45://-|_
|
||||||
|
_this.viewer.viewport.zoomBy(0.9);
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 48://0|)
|
||||||
|
_this.viewer.viewport.goHome();
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 119://w
|
||||||
|
case 87://W
|
||||||
|
case 38://up arrow
|
||||||
|
if (shiftKey)
|
||||||
|
_this.viewer.viewport.zoomBy(1.1);
|
||||||
|
else
|
||||||
|
_this.viewer.viewport.panBy(new $.Point(0, -0.05));
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 115://s
|
||||||
|
case 83://S
|
||||||
|
case 40://down arrow
|
||||||
|
if (shiftKey)
|
||||||
|
_this.viewer.viewport.zoomBy(0.9);
|
||||||
|
else
|
||||||
|
_this.viewer.viewport.panBy(new $.Point(0, 0.05));
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 97://a
|
||||||
|
case 37://left arrow
|
||||||
|
_this.viewer.viewport.panBy(new $.Point(-0.05, 0));
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
case 100://d
|
||||||
|
case 39://right arrow
|
||||||
|
_this.viewer.viewport.panBy(new $.Point(0.05, 0));
|
||||||
|
_this.viewer.viewport.applyConstraints();
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
//console.log( 'navigator keycode %s', keyCode );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setTracking( true ); // default state
|
||||||
|
|
||||||
|
/*this.displayRegion.outerTracker = new $.MouseTracker({
|
||||||
|
element: this.container,
|
||||||
|
clickTimeThreshold: this.clickTimeThreshold,
|
||||||
|
clickDistThreshold: this.clickDistThreshold,
|
||||||
|
enterHandler: $.delegate( this, onContainerEnter ),
|
||||||
|
exitHandler: $.delegate( this, onContainerExit ),
|
||||||
|
releaseHandler: $.delegate( this, onContainerRelease )
|
||||||
|
}).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/
|
||||||
|
|
||||||
|
|
||||||
|
viewer.addControl(
|
||||||
|
this.element,
|
||||||
|
$.ControlAnchor.TOP_RIGHT
|
||||||
|
);
|
||||||
|
|
||||||
|
if( options.width && options.height ){
|
||||||
|
this.element.style.width = options.width + 'px';
|
||||||
|
this.element.style.height = options.height + 'px';
|
||||||
|
} else {
|
||||||
|
this.element.style.width = ( viewerSize.x * options.sizeRatio ) + 'px';
|
||||||
|
this.element.style.height = ( viewerSize.y * options.sizeRatio ) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
$.Viewer.apply( this, [ options ] );
|
||||||
|
|
||||||
|
this.element.getElementsByTagName('form')[0].appendChild( this.displayRegion );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.Navigator.prototype.update
|
||||||
|
*/
|
||||||
|
update: function( viewport ){
|
||||||
|
|
||||||
|
var bounds,
|
||||||
|
topleft,
|
||||||
|
bottomright;
|
||||||
|
|
||||||
|
if( viewport && this.viewport ){
|
||||||
|
bounds = viewport.getBounds( true );
|
||||||
|
topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() );
|
||||||
|
bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight() );
|
||||||
|
|
||||||
|
//update style for navigator-box
|
||||||
|
(function(style){
|
||||||
|
|
||||||
|
style.top = topleft.y + 'px';
|
||||||
|
style.left = topleft.x + 'px';
|
||||||
|
|
||||||
|
var width = Math.abs( topleft.x - bottomright.x ) - 3; // TODO: What's this magic number mean?
|
||||||
|
var height = Math.abs( topleft.y - bottomright.y ) - 3;
|
||||||
|
// make sure width and height are non-negative so IE doesn't throw
|
||||||
|
style.width = Math.max( width, 0 ) + 'px';
|
||||||
|
style.height = Math.max( height, 0 ) + 'px';
|
||||||
|
|
||||||
|
}( this.displayRegion.style ));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onCanvasClick( tracker, position, quick, shift ) {
|
||||||
|
this.displayRegion.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onCanvasDrag( tracker, position, delta, shift ) {
|
||||||
|
if ( this.viewer.viewport ) {
|
||||||
|
if( !this.panHorizontal ){
|
||||||
|
delta.x = 0;
|
||||||
|
}
|
||||||
|
if( !this.panVertical ){
|
||||||
|
delta.y = 0;
|
||||||
|
}
|
||||||
|
this.viewer.viewport.panBy(
|
||||||
|
this.viewport.deltaPointsFromPixels(
|
||||||
|
delta
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onCanvasRelease( tracker, position, insideElementPress, insideElementRelease ) {
|
||||||
|
if ( insideElementPress && this.viewer.viewport ) {
|
||||||
|
this.viewer.viewport.applyConstraints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onCanvasScroll( tracker, position, scroll, shift ) {
|
||||||
|
var factor;
|
||||||
|
if ( this.viewer.viewport ) {
|
||||||
|
factor = Math.pow( this.zoomPerScroll, scroll );
|
||||||
|
this.viewer.viewport.zoomBy(
|
||||||
|
factor,
|
||||||
|
//this.viewport.pointFromPixel( position, true )
|
||||||
|
this.viewport.getCenter()
|
||||||
|
);
|
||||||
|
this.viewer.viewport.applyConstraints();
|
||||||
|
}
|
||||||
|
//cancels event
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
1873
src/openseadragon.js
Normal file
173
src/overlay.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of positions that an overlay may be assigned relative
|
||||||
|
* to the viewport including CENTER, TOP_LEFT (default), TOP, TOP_RIGHT,
|
||||||
|
* RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, and LEFT.
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
$.OverlayPlacement = {
|
||||||
|
CENTER: 0,
|
||||||
|
TOP_LEFT: 1,
|
||||||
|
TOP: 2,
|
||||||
|
TOP_RIGHT: 3,
|
||||||
|
RIGHT: 4,
|
||||||
|
BOTTOM_RIGHT: 5,
|
||||||
|
BOTTOM: 6,
|
||||||
|
BOTTOM_LEFT: 7,
|
||||||
|
LEFT: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Overlay provides a
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
$.Overlay = function( element, location, placement ) {
|
||||||
|
this.element = element;
|
||||||
|
this.scales = location instanceof $.Rect;
|
||||||
|
this.bounds = new $.Rect(
|
||||||
|
location.x,
|
||||||
|
location.y,
|
||||||
|
location.width,
|
||||||
|
location.height
|
||||||
|
);
|
||||||
|
this.position = new $.Point(
|
||||||
|
location.x,
|
||||||
|
location.y
|
||||||
|
);
|
||||||
|
this.size = new $.Point(
|
||||||
|
location.width,
|
||||||
|
location.height
|
||||||
|
);
|
||||||
|
this.style = element.style;
|
||||||
|
// rects are always top-left
|
||||||
|
this.placement = location instanceof $.Point ?
|
||||||
|
placement :
|
||||||
|
$.OverlayPlacement.TOP_LEFT;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Overlay.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.OverlayPlacement} position
|
||||||
|
* @param {OpenSeadragon.Point} size
|
||||||
|
*/
|
||||||
|
adjust: function( position, size ) {
|
||||||
|
switch ( this.placement ) {
|
||||||
|
case $.OverlayPlacement.TOP_LEFT:
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.TOP:
|
||||||
|
position.x -= size.x / 2;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.TOP_RIGHT:
|
||||||
|
position.x -= size.x;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.RIGHT:
|
||||||
|
position.x -= size.x;
|
||||||
|
position.y -= size.y / 2;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.BOTTOM_RIGHT:
|
||||||
|
position.x -= size.x;
|
||||||
|
position.y -= size.y;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.BOTTOM:
|
||||||
|
position.x -= size.x / 2;
|
||||||
|
position.y -= size.y;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.BOTTOM_LEFT:
|
||||||
|
position.y -= size.y;
|
||||||
|
break;
|
||||||
|
case $.OverlayPlacement.LEFT:
|
||||||
|
position.y -= size.y / 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case $.OverlayPlacement.CENTER:
|
||||||
|
position.x -= size.x / 2;
|
||||||
|
position.y -= size.y / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
var element = this.element,
|
||||||
|
style = this.style;
|
||||||
|
|
||||||
|
if ( element.parentNode ) {
|
||||||
|
element.parentNode.removeChild( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
style.top = "";
|
||||||
|
style.left = "";
|
||||||
|
style.position = "";
|
||||||
|
|
||||||
|
if ( this.scales ) {
|
||||||
|
style.width = "";
|
||||||
|
style.height = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Element} container
|
||||||
|
*/
|
||||||
|
drawHTML: function( container ) {
|
||||||
|
var element = this.element,
|
||||||
|
style = this.style,
|
||||||
|
scales = this.scales,
|
||||||
|
position,
|
||||||
|
size;
|
||||||
|
|
||||||
|
if ( element.parentNode != container ) {
|
||||||
|
container.appendChild( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !scales ) {
|
||||||
|
this.size = $.getElementSize( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
position = this.position;
|
||||||
|
size = this.size;
|
||||||
|
|
||||||
|
this.adjust( position, size );
|
||||||
|
|
||||||
|
position = position.apply( Math.floor );
|
||||||
|
size = size.apply( Math.ceil );
|
||||||
|
|
||||||
|
style.left = position.x + "px";
|
||||||
|
style.top = position.y + "px";
|
||||||
|
style.position = "absolute";
|
||||||
|
|
||||||
|
if ( scales ) {
|
||||||
|
style.width = size.x + "px";
|
||||||
|
style.height = size.y + "px";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location
|
||||||
|
* @param {OpenSeadragon.OverlayPlacement} 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 :
|
||||||
|
$.OverlayPlacement.TOP_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
143
src/point.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Point is really used as a 2-dimensional vector, equally useful for
|
||||||
|
* representing a point on a plane, or the height and width of a plane
|
||||||
|
* not requiring any other frame of reference.
|
||||||
|
* @class
|
||||||
|
* @param {Number} [x] The vector component 'x'. Defaults to the origin at 0.
|
||||||
|
* @param {Number} [y] The vector component 'y'. Defaults to the origin at 0.
|
||||||
|
* @property {Number} [x] The vector component 'x'.
|
||||||
|
* @property {Number} [y] The vector component 'y'.
|
||||||
|
*/
|
||||||
|
$.Point = function( x, y ) {
|
||||||
|
this.x = typeof ( x ) == "number" ? x : 0;
|
||||||
|
this.y = typeof ( y ) == "number" ? y : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Point.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
plus: function( point ) {
|
||||||
|
return new $.Point(
|
||||||
|
this.x + point.x,
|
||||||
|
this.y + point.y
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
minus: function( point ) {
|
||||||
|
return new $.Point(
|
||||||
|
this.x - point.x,
|
||||||
|
this.y - point.y
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
times: function( factor ) {
|
||||||
|
return new $.Point(
|
||||||
|
this.x * factor,
|
||||||
|
this.y * factor
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
divide: function( factor ) {
|
||||||
|
return new $.Point(
|
||||||
|
this.x / factor,
|
||||||
|
this.y / factor
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
negate: function() {
|
||||||
|
return new $.Point( -this.x, -this.y );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
distanceTo: function( point ) {
|
||||||
|
return Math.sqrt(
|
||||||
|
Math.pow( this.x - point.x, 2 ) +
|
||||||
|
Math.pow( this.y - point.y, 2 )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
apply: function( func ) {
|
||||||
|
return new $.Point( func( this.x ), func( this.y ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
equals: function( point ) {
|
||||||
|
return (
|
||||||
|
point instanceof $.Point
|
||||||
|
) && (
|
||||||
|
this.x === point.x
|
||||||
|
) && (
|
||||||
|
this.y === point.y
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 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
|
||||||
|
* vector components
|
||||||
|
*/
|
||||||
|
toString: function() {
|
||||||
|
return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
110
src/profiler.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class useful for developers to establish baseline performance
|
||||||
|
* metrics of rendering routines.
|
||||||
|
* @class
|
||||||
|
* @property {Boolean} midUpdate
|
||||||
|
* @property {Number} numUpdates
|
||||||
|
* @property {Number} lastBeginTime
|
||||||
|
* @property {Number} lastEndTime
|
||||||
|
* @property {Number} minUpdateTime
|
||||||
|
* @property {Number} avgUpdateTime
|
||||||
|
* @property {Number} maxUpdateTime
|
||||||
|
* @property {Number} minIdleTime
|
||||||
|
* @property {Number} avgIdleTime
|
||||||
|
* @property {Number} maxIdleTime
|
||||||
|
*/
|
||||||
|
$.Profiler = function() {
|
||||||
|
|
||||||
|
this.midUpdate = false;
|
||||||
|
this.numUpdates = 0;
|
||||||
|
|
||||||
|
this.lastBeginTime = null;
|
||||||
|
this.lastEndTime = null;
|
||||||
|
|
||||||
|
this.minUpdateTime = Infinity;
|
||||||
|
this.avgUpdateTime = 0;
|
||||||
|
this.maxUpdateTime = 0;
|
||||||
|
|
||||||
|
this.minIdleTime = Infinity;
|
||||||
|
this.avgIdleTime = 0;
|
||||||
|
this.maxIdleTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Profiler.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
beginUpdate: function() {
|
||||||
|
if (this.midUpdate) {
|
||||||
|
this.endUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.midUpdate = true;
|
||||||
|
this.lastBeginTime = new Date().getTime();
|
||||||
|
|
||||||
|
if (this.numUpdates < 1) {
|
||||||
|
return; // this is the first update
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = this.lastBeginTime - this.lastEndTime;
|
||||||
|
|
||||||
|
this.avgIdleTime = (this.avgIdleTime * (this.numUpdates - 1) + time) / this.numUpdates;
|
||||||
|
|
||||||
|
if (time < this.minIdleTime) {
|
||||||
|
this.minIdleTime = time;
|
||||||
|
}
|
||||||
|
if (time > this.maxIdleTime) {
|
||||||
|
this.maxIdleTime = time;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
endUpdate: function() {
|
||||||
|
if (!this.midUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastEndTime = new Date().getTime();
|
||||||
|
this.midUpdate = false;
|
||||||
|
|
||||||
|
var time = this.lastEndTime - this.lastBeginTime;
|
||||||
|
|
||||||
|
this.numUpdates++;
|
||||||
|
this.avgUpdateTime = (this.avgUpdateTime * (this.numUpdates - 1) + time) / this.numUpdates;
|
||||||
|
|
||||||
|
if (time < this.minUpdateTime) {
|
||||||
|
this.minUpdateTime = time;
|
||||||
|
}
|
||||||
|
if (time > this.maxUpdateTime) {
|
||||||
|
this.maxUpdateTime = time;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
clearProfile: function() {
|
||||||
|
this.midUpdate = false;
|
||||||
|
this.numUpdates = 0;
|
||||||
|
|
||||||
|
this.lastBeginTime = null;
|
||||||
|
this.lastEndTime = null;
|
||||||
|
|
||||||
|
this.minUpdateTime = Infinity;
|
||||||
|
this.avgUpdateTime = 0;
|
||||||
|
this.maxUpdateTime = 0;
|
||||||
|
|
||||||
|
this.minIdleTime = Infinity;
|
||||||
|
this.avgIdleTime = 0;
|
||||||
|
this.maxIdleTime = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
118
src/rectangle.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Rectangle really represents a 2x2 matrix where each row represents a
|
||||||
|
* 2 dimensional vector component, the first is (x,y) and the second is
|
||||||
|
* (width, height). The latter component implies the equation of a simple
|
||||||
|
* plane.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @param {Number} x The vector component 'x'.
|
||||||
|
* @param {Number} y The vector component 'y'.
|
||||||
|
* @param {Number} width The vector component 'height'.
|
||||||
|
* @param {Number} height The vector component 'width'.
|
||||||
|
* @property {Number} x The vector component 'x'.
|
||||||
|
* @property {Number} y The vector component 'y'.
|
||||||
|
* @property {Number} width The vector component 'width'.
|
||||||
|
* @property {Number} height The vector component 'height'.
|
||||||
|
*/
|
||||||
|
$.Rect = function( x, y, width, height ) {
|
||||||
|
this.x = typeof ( x ) == "number" ? x : 0;
|
||||||
|
this.y = typeof ( y ) == "number" ? y : 0;
|
||||||
|
this.width = typeof ( width ) == "number" ? width : 0;
|
||||||
|
this.height = typeof ( height ) == "number" ? height : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Rect.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The aspect ratio is simply the ratio of width to height.
|
||||||
|
* @function
|
||||||
|
* @returns {Number} The ratio of width to height.
|
||||||
|
*/
|
||||||
|
getAspectRatio: function() {
|
||||||
|
return this.width / this.height;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the coordinates of the upper-left corner of the rectanglea s a
|
||||||
|
* point.
|
||||||
|
* @function
|
||||||
|
* @returns {OpenSeadragon.Point} The coordinate of the upper-left corner of
|
||||||
|
* the rectangle.
|
||||||
|
*/
|
||||||
|
getTopLeft: function() {
|
||||||
|
return new $.Point( this.x, this.y );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the coordinates of the bottom-right corner of the rectangle as a
|
||||||
|
* point.
|
||||||
|
* @function
|
||||||
|
* @returns {OpenSeadragon.Point} The coordinate of the bottom-right corner of
|
||||||
|
* the rectangle.
|
||||||
|
*/
|
||||||
|
getBottomRight: function() {
|
||||||
|
return new $.Point(
|
||||||
|
this.x + this.width,
|
||||||
|
this.y + this.height
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the center of the rectangle.
|
||||||
|
* @function
|
||||||
|
* @returns {OpenSeadragon.Point} The center of the rectangle as represnted
|
||||||
|
* as represented by a 2-dimensional vector (x,y)
|
||||||
|
*/
|
||||||
|
getCenter: function() {
|
||||||
|
return new $.Point(
|
||||||
|
this.x + this.width / 2.0,
|
||||||
|
this.y + this.height / 2.0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the width and height component as a vector OpenSeadragon.Point
|
||||||
|
* @function
|
||||||
|
* @returns {OpenSeadragon.Point} The 2 dimensional vector represnting the
|
||||||
|
* the width and height of the rectangle.
|
||||||
|
*/
|
||||||
|
getSize: function() {
|
||||||
|
return new $.Point( this.width, this.height );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if two Rectanlges have equivalent components.
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
|
||||||
|
* @return {Boolean} 'true' if all components are equal, otherwise 'false'.
|
||||||
|
*/
|
||||||
|
equals: function( other ) {
|
||||||
|
return ( other instanceof $.Rect ) &&
|
||||||
|
( this.x === other.x ) &&
|
||||||
|
( this.y === other.y ) &&
|
||||||
|
( this.width === other.width ) &&
|
||||||
|
( this.height === other.height );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a string representation of the retangle which is useful for
|
||||||
|
* debugging.
|
||||||
|
* @function
|
||||||
|
* @returns {String} A string representation of the rectangle.
|
||||||
|
*/
|
||||||
|
toString: function() {
|
||||||
|
return "[" +
|
||||||
|
Math.round(this.x*100) + "," +
|
||||||
|
Math.round(this.y*100) + "," +
|
||||||
|
Math.round(this.width*100) + "x" +
|
||||||
|
Math.round(this.height*100) +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
521
src/referencestrip.js
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
// dictionary from id to private properties
|
||||||
|
var THIS = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CollectionDrawer is a reimplementation if the Drawer API that
|
||||||
|
* focuses on allowing a viewport to be redefined as a collection
|
||||||
|
* of smaller viewports, defined by a clear number of rows and / or
|
||||||
|
* columns of which each item in the matrix of viewports has its own
|
||||||
|
* source.
|
||||||
|
*
|
||||||
|
* This idea is a reexpression of the idea of dzi collections
|
||||||
|
* which allows a clearer algorithm to reuse the tile sources already
|
||||||
|
* supported by OpenSeadragon, in heterogenious or homogenious
|
||||||
|
* sequences just like mixed groups already supported by the viewer
|
||||||
|
* for the purpose of image sequnces.
|
||||||
|
*
|
||||||
|
* TODO: The difficult part of this feature is figuring out how to express
|
||||||
|
* this functionality as a combination of the functionality already
|
||||||
|
* provided by Drawer, Viewport, TileSource, and Navigator. It may
|
||||||
|
* require better abstraction at those points in order to effeciently
|
||||||
|
* reuse those paradigms.
|
||||||
|
*/
|
||||||
|
$.ReferenceStrip = function( options ){
|
||||||
|
|
||||||
|
var _this = this,
|
||||||
|
viewer = options.viewer,
|
||||||
|
viewerSize = $.getElementSize( viewer.element ),
|
||||||
|
miniViewer,
|
||||||
|
minPixelRatio,
|
||||||
|
element,
|
||||||
|
i;
|
||||||
|
|
||||||
|
//We may need to create a new element and id if they did not
|
||||||
|
//provide the id for the existing element
|
||||||
|
if( !options.id ){
|
||||||
|
options.id = 'referencestrip-' + (+new Date());
|
||||||
|
this.element = $.makeNeutralElement( "div" );
|
||||||
|
this.element.id = options.id;
|
||||||
|
this.element.className = 'referencestrip';
|
||||||
|
}
|
||||||
|
|
||||||
|
options = $.extend( true, {
|
||||||
|
sizeRatio: $.DEFAULT_SETTINGS.referenceStripSizeRatio,
|
||||||
|
position: $.DEFAULT_SETTINGS.referenceStripPosition,
|
||||||
|
scroll: $.DEFAULT_SETTINGS.referenceStripScroll,
|
||||||
|
clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold
|
||||||
|
}, options, {
|
||||||
|
//required overrides
|
||||||
|
element: this.element,
|
||||||
|
//These need to be overridden to prevent recursion since
|
||||||
|
//the navigator is a viewer and a viewer has a navigator
|
||||||
|
showNavigator: false,
|
||||||
|
mouseNavEnabled: false,
|
||||||
|
showNavigationControl: false,
|
||||||
|
showSequenceControl: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$.extend( this, options );
|
||||||
|
|
||||||
|
//Private state properties
|
||||||
|
THIS[ this.id ] = {
|
||||||
|
"animating": false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.minPixelRatio = this.viewer.minPixelRatio;
|
||||||
|
|
||||||
|
(function( style ){
|
||||||
|
style.marginTop = '0px';
|
||||||
|
style.marginRight = '0px';
|
||||||
|
style.marginBottom = '0px';
|
||||||
|
style.marginLeft = '0px';
|
||||||
|
style.left = '0px';
|
||||||
|
style.bottom = '0px';
|
||||||
|
style.border = '0px';
|
||||||
|
style.background = '#000';
|
||||||
|
style.position = 'relative';
|
||||||
|
}( this.element.style ));
|
||||||
|
|
||||||
|
$.setElementOpacity( this.element, 0.8 );
|
||||||
|
|
||||||
|
this.viewer = viewer;
|
||||||
|
this.innerTracker = new $.MouseTracker({
|
||||||
|
element: this.element,
|
||||||
|
dragHandler: $.delegate( this, onStripDrag ),
|
||||||
|
scrollHandler: $.delegate( this, onStripScroll ),
|
||||||
|
enterHandler: $.delegate( this, onStripEnter ),
|
||||||
|
exitHandler: $.delegate( this, onStripExit ),
|
||||||
|
keyHandler: $.delegate( this, onKeyPress )
|
||||||
|
}).setTracking( true );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Controls the position and orientation of the reference strip and sets the
|
||||||
|
//appropriate width and height
|
||||||
|
if( options.width && options.height ){
|
||||||
|
this.element.style.width = options.width + 'px';
|
||||||
|
this.element.style.height = options.height + 'px';
|
||||||
|
viewer.addControl(
|
||||||
|
this.element,
|
||||||
|
$.ControlAnchor.BOTTOM_LEFT
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if( "horizontal" == options.scroll ){
|
||||||
|
this.element.style.width = (
|
||||||
|
viewerSize.x *
|
||||||
|
options.sizeRatio *
|
||||||
|
viewer.tileSources.length
|
||||||
|
) + ( 12 * viewer.tileSources.length ) + 'px';
|
||||||
|
|
||||||
|
this.element.style.height = (
|
||||||
|
viewerSize.y *
|
||||||
|
options.sizeRatio
|
||||||
|
) + 'px';
|
||||||
|
|
||||||
|
viewer.addControl(
|
||||||
|
this.element,
|
||||||
|
$.ControlAnchor.BOTTOM_LEFT
|
||||||
|
);
|
||||||
|
}else {
|
||||||
|
this.element.style.height = (
|
||||||
|
viewerSize.y *
|
||||||
|
options.sizeRatio *
|
||||||
|
viewer.tileSources.length
|
||||||
|
) + ( 12 * viewer.tileSources.length ) + 'px';
|
||||||
|
|
||||||
|
this.element.style.width = (
|
||||||
|
viewerSize.x *
|
||||||
|
options.sizeRatio
|
||||||
|
) + 'px';
|
||||||
|
|
||||||
|
viewer.addControl(
|
||||||
|
this.element,
|
||||||
|
$.ControlAnchor.TOP_LEFT
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
|
||||||
|
this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
|
||||||
|
this.panels = [];
|
||||||
|
|
||||||
|
for( i = 0; i < viewer.tileSources.length; i++ ){
|
||||||
|
|
||||||
|
element = $.makeNeutralElement('div');
|
||||||
|
element.id = this.element.id + "-" + i;
|
||||||
|
|
||||||
|
(function(style){
|
||||||
|
style.width = _this.panelWidth + 'px';
|
||||||
|
style.height = _this.panelHeight + 'px';
|
||||||
|
style.display = 'inline';
|
||||||
|
style.float = 'left'; //Webkit
|
||||||
|
style.cssFloat = 'left'; //Firefox
|
||||||
|
style.styleFloat = 'left'; //IE
|
||||||
|
style.padding = '2px';
|
||||||
|
}(element.style));
|
||||||
|
|
||||||
|
element.innerTracker = new $.MouseTracker({
|
||||||
|
element: element,
|
||||||
|
clickTimeThreshold: this.clickTimeThreshold,
|
||||||
|
clickDistThreshold: this.clickDistThreshold,
|
||||||
|
pressHandler: function( tracker ){
|
||||||
|
tracker.dragging = +new Date;
|
||||||
|
},
|
||||||
|
releaseHandler: function( tracker, position, insideElementPress, insideElementRelease ){
|
||||||
|
var id = tracker.element.id,
|
||||||
|
page = Number( id.split( '-' )[ 2 ] ),
|
||||||
|
now = +new Date;
|
||||||
|
|
||||||
|
if ( insideElementPress &&
|
||||||
|
insideElementRelease &&
|
||||||
|
tracker.dragging &&
|
||||||
|
( now - tracker.dragging ) < tracker.clickTimeThreshold ){
|
||||||
|
tracker.dragging = null;
|
||||||
|
viewer.goToPage( page );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setTracking( true );
|
||||||
|
|
||||||
|
this.element.appendChild( element );
|
||||||
|
|
||||||
|
element.activePanel = false;
|
||||||
|
|
||||||
|
this.panels.push( element );
|
||||||
|
|
||||||
|
}
|
||||||
|
loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.y, 0);
|
||||||
|
this.setFocus( 0 );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.ReferenceStrip.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
|
||||||
|
|
||||||
|
setFocus: function( page ){
|
||||||
|
var element = $.getElement( this.element.id + '-' + page ),
|
||||||
|
viewerSize = $.getElementSize( this.viewer.canvas ),
|
||||||
|
scrollWidth = Number(this.element.style.width.replace('px','')),
|
||||||
|
scrollHeight = Number(this.element.style.height.replace('px','')),
|
||||||
|
offsetLeft = -Number(this.element.style.marginLeft.replace('px','')),
|
||||||
|
offsetTop = -Number(this.element.style.marginTop.replace('px','')),
|
||||||
|
offset;
|
||||||
|
|
||||||
|
if ( this.currentSelected !== element ){
|
||||||
|
if( this.currentSelected ){
|
||||||
|
this.currentSelected.style.background = '#000';
|
||||||
|
}
|
||||||
|
this.currentSelected = element;
|
||||||
|
this.currentSelected.style.background = '#999';
|
||||||
|
|
||||||
|
if( 'horizontal' == this.scroll ){
|
||||||
|
//right left
|
||||||
|
offset = (Number(page)) * ( this.panelWidth + 3 );
|
||||||
|
if( offset > offsetLeft + viewerSize.x - this.panelWidth){
|
||||||
|
offset = Math.min(offset, (scrollWidth - viewerSize.x));
|
||||||
|
this.element.style.marginLeft = -offset + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, -offset );
|
||||||
|
}else if( offset < offsetLeft ){
|
||||||
|
offset = Math.max(0, offset - viewerSize.x / 2);
|
||||||
|
this.element.style.marginLeft = -offset + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, -offset );
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
offset = (Number(page) ) * ( this.panelHeight + 3 );
|
||||||
|
if( offset > offsetTop + viewerSize.y - this.panelHeight){
|
||||||
|
offset = Math.min(offset, (scrollHeight - viewerSize.y));
|
||||||
|
this.element.style.marginTop = -offset + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, -offset );
|
||||||
|
}else if( offset < offsetTop ){
|
||||||
|
offset = Math.max(0, offset - viewerSize.y / 2);
|
||||||
|
this.element.style.marginTop = -offset + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, -offset );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPage = page;
|
||||||
|
$.getElement( element.id + '-displayregion' ).focus();
|
||||||
|
onStripEnter.call( this, this.innerTracker );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.Navigator.prototype.update
|
||||||
|
*/
|
||||||
|
update: function( viewport ){
|
||||||
|
|
||||||
|
if( THIS[ this.id ].animating ){
|
||||||
|
$.console.log('image reference strip update');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onStripDrag( tracker, position, delta, shift ) {
|
||||||
|
|
||||||
|
var offsetLeft = Number(this.element.style.marginLeft.replace('px','')),
|
||||||
|
offsetTop = Number(this.element.style.marginTop.replace('px','')),
|
||||||
|
scrollWidth = Number(this.element.style.width.replace('px','')),
|
||||||
|
scrollHeight = Number(this.element.style.height.replace('px','')),
|
||||||
|
viewerSize = $.getElementSize( this.viewer.canvas );
|
||||||
|
this.dragging = true;
|
||||||
|
if ( this.element ) {
|
||||||
|
if( 'horizontal' == this.scroll ){
|
||||||
|
if ( -delta.x > 0 ) {
|
||||||
|
//forward
|
||||||
|
if( offsetLeft > -(scrollWidth - viewerSize.x)){
|
||||||
|
this.element.style.marginLeft = ( offsetLeft + (delta.x * 2) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, offsetLeft + (delta.x * 2) );
|
||||||
|
}
|
||||||
|
} else if ( -delta.x < 0 ) {
|
||||||
|
//reverse
|
||||||
|
if( offsetLeft < 0 ){
|
||||||
|
this.element.style.marginLeft = ( offsetLeft + (delta.x * 2) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, offsetLeft + (delta.x * 2) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ( -delta.y > 0 ) {
|
||||||
|
//forward
|
||||||
|
if( offsetTop > -(scrollHeight - viewerSize.y)){
|
||||||
|
this.element.style.marginTop = ( offsetTop + (delta.y * 2) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, offsetTop + (delta.y * 2) );
|
||||||
|
}
|
||||||
|
} else if ( -delta.y < 0 ) {
|
||||||
|
//reverse
|
||||||
|
if( offsetTop < 0 ){
|
||||||
|
this.element.style.marginTop = ( offsetTop + (delta.y * 2) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, offsetTop + (delta.y * 2) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onStripScroll( tracker, position, scroll, shift ) {
|
||||||
|
var offsetLeft = Number(this.element.style.marginLeft.replace('px','')),
|
||||||
|
offsetTop = Number(this.element.style.marginTop.replace('px','')),
|
||||||
|
scrollWidth = Number(this.element.style.width.replace('px','')),
|
||||||
|
scrollHeight = Number(this.element.style.height.replace('px','')),
|
||||||
|
viewerSize = $.getElementSize( this.viewer.canvas );
|
||||||
|
if ( this.element ) {
|
||||||
|
if( 'horizontal' == this.scroll ){
|
||||||
|
if ( scroll > 0 ) {
|
||||||
|
//forward
|
||||||
|
if( offsetLeft > -(scrollWidth - viewerSize.x)){
|
||||||
|
this.element.style.marginLeft = ( offsetLeft - (scroll * 60) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, offsetLeft - (scroll * 60) );
|
||||||
|
}
|
||||||
|
} else if ( scroll < 0 ) {
|
||||||
|
//reverse
|
||||||
|
if( offsetLeft < 0 ){
|
||||||
|
this.element.style.marginLeft = ( offsetLeft - (scroll * 60) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.x, offsetLeft - (scroll * 60) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if ( scroll < 0 ) {
|
||||||
|
//scroll up
|
||||||
|
if( offsetTop > viewerSize.y - scrollHeight ){
|
||||||
|
this.element.style.marginTop = ( offsetTop + (scroll * 60) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, offsetTop + (scroll * 60) );
|
||||||
|
}
|
||||||
|
} else if ( scroll > 0 ) {
|
||||||
|
//scroll dowm
|
||||||
|
if( offsetTop < 0 ){
|
||||||
|
this.element.style.marginTop = ( offsetTop + (scroll * 60) ) + 'px';
|
||||||
|
loadPanels( this, viewerSize.y, offsetTop + (scroll * 60) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//cancels event
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function loadPanels(strip, viewerSize, scroll){
|
||||||
|
var panelSize,
|
||||||
|
activePanelsStart,
|
||||||
|
activePanelsEnd,
|
||||||
|
miniViewer,
|
||||||
|
i;
|
||||||
|
if( 'horizontal' == strip.scroll ){
|
||||||
|
panelSize = strip.panelWidth;
|
||||||
|
}else{
|
||||||
|
panelSize = strip.panelHeight;
|
||||||
|
}
|
||||||
|
activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5;
|
||||||
|
activePanelsEnd = Math.ceil( (Math.abs(scroll) + viewerSize ) / panelSize ) + 1;
|
||||||
|
activePanelsStart = activePanelsEnd - activePanelsStart;
|
||||||
|
activePanelsStart = activePanelsStart < 0 ? 0 : activePanelsStart;
|
||||||
|
|
||||||
|
for( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ){
|
||||||
|
element = strip.panels[ i ];
|
||||||
|
if ( !element.activePanel ){
|
||||||
|
miniViewer = new $.Viewer( {
|
||||||
|
id: element.id,
|
||||||
|
tileSources: [ strip.viewer.tileSources[ i ] ],
|
||||||
|
element: element,
|
||||||
|
navigatorSizeRatio: strip.sizeRatio,
|
||||||
|
minPixelRatio: strip.minPixelRatio,
|
||||||
|
showNavigator: false,
|
||||||
|
mouseNavEnabled: false,
|
||||||
|
showNavigationControl: false,
|
||||||
|
showSequenceControl: false
|
||||||
|
} );
|
||||||
|
|
||||||
|
miniViewer.displayRegion = $.makeNeutralElement( "textarea" );
|
||||||
|
miniViewer.displayRegion.id = element.id + '-displayregion';
|
||||||
|
miniViewer.displayRegion.className = 'displayregion';
|
||||||
|
|
||||||
|
(function( style ){
|
||||||
|
style.position = 'relative';
|
||||||
|
style.top = '0px';
|
||||||
|
style.left = '0px';
|
||||||
|
style.fontSize = '0px';
|
||||||
|
style.overflow = 'hidden';
|
||||||
|
style.float = 'left'; //Webkit
|
||||||
|
style.cssFloat = 'left'; //Firefox
|
||||||
|
style.styleFloat = 'left'; //IE
|
||||||
|
style.zIndex = 999999999;
|
||||||
|
style.cursor = 'default';
|
||||||
|
style.width = ( strip.panelWidth - 4 ) + 'px';
|
||||||
|
style.height = ( strip.panelHeight - 4 ) + 'px';
|
||||||
|
}( miniViewer.displayRegion.style ));
|
||||||
|
|
||||||
|
miniViewer.displayRegion.innerTracker = new $.MouseTracker({
|
||||||
|
element: miniViewer.displayRegion
|
||||||
|
});
|
||||||
|
|
||||||
|
element.getElementsByTagName('form')[ 0 ].appendChild(
|
||||||
|
miniViewer.displayRegion
|
||||||
|
);
|
||||||
|
|
||||||
|
element.activePanel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onStripEnter( tracker ) {
|
||||||
|
|
||||||
|
//$.setElementOpacity(tracker.element, 0.8);
|
||||||
|
|
||||||
|
//tracker.element.style.border = '1px solid #555';
|
||||||
|
//tracker.element.style.background = '#000';
|
||||||
|
|
||||||
|
if( 'horizontal' == this.scroll ){
|
||||||
|
|
||||||
|
//tracker.element.style.paddingTop = "0px";
|
||||||
|
tracker.element.style.marginBottom = "0px";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//tracker.element.style.paddingRight = "0px";
|
||||||
|
tracker.element.style.marginLeft = "0px";
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onStripExit( tracker ) {
|
||||||
|
|
||||||
|
var viewerSize = $.getElementSize( this.viewer.element );
|
||||||
|
|
||||||
|
//$.setElementOpacity(tracker.element, 0.4);
|
||||||
|
//tracker.element.style.border = 'none';
|
||||||
|
//tracker.element.style.background = '#fff';
|
||||||
|
|
||||||
|
|
||||||
|
if( 'horizontal' == this.scroll ){
|
||||||
|
|
||||||
|
//tracker.element.style.paddingTop = "10px";
|
||||||
|
tracker.element.style.marginBottom = "-" + ( $.getElementSize( tracker.element ).y / 2 ) + "px";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//tracker.element.style.paddingRight = "10px";
|
||||||
|
tracker.element.style.marginLeft = "-" + ( $.getElementSize( tracker.element ).x / 2 )+ "px";
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
function onKeyPress( tracker, keyCode, shiftKey ){
|
||||||
|
//console.log( keyCode );
|
||||||
|
|
||||||
|
switch( keyCode ){
|
||||||
|
case 61://=|+
|
||||||
|
onStripScroll.call(this, this.tracker, null, 1, null);
|
||||||
|
return false;
|
||||||
|
case 45://-|_
|
||||||
|
onStripScroll.call(this, this.tracker, null, -1, null);
|
||||||
|
return false;
|
||||||
|
case 48://0|)
|
||||||
|
case 119://w
|
||||||
|
case 87://W
|
||||||
|
case 38://up arrow
|
||||||
|
onStripScroll.call(this, this.tracker, null, 1, null);
|
||||||
|
return false;
|
||||||
|
case 115://s
|
||||||
|
case 83://S
|
||||||
|
case 40://down arrow
|
||||||
|
onStripScroll.call(this, this.tracker, null, -1, null);
|
||||||
|
return false;
|
||||||
|
case 97://a
|
||||||
|
case 37://left arrow
|
||||||
|
onStripScroll.call(this, this.tracker, null, -1, null);
|
||||||
|
return false;
|
||||||
|
case 100://d
|
||||||
|
case 39://right arrow
|
||||||
|
onStripScroll.call(this, this.tracker, null, 1, null);
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
//console.log( 'navigator keycode %s', keyCode );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
119
src/spring.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @param {Object} options - Spring configuration settings.
|
||||||
|
* @param {Number} options.initial - Initial value of spring, default to 0 so
|
||||||
|
* spring is not in motion initally by default.
|
||||||
|
* @param {Number} options.springStiffness - Spring stiffness.
|
||||||
|
* @param {Number} options.animationTime - Animation duration per spring.
|
||||||
|
*
|
||||||
|
* @property {Number} initial - Initial value of spring, default to 0 so
|
||||||
|
* spring is not in motion initally by default.
|
||||||
|
* @property {Number} springStiffness - Spring stiffness.
|
||||||
|
* @property {Number} animationTime - Animation duration per spring.
|
||||||
|
* @property {Object} current
|
||||||
|
* @property {Number} start
|
||||||
|
* @property {Number} target
|
||||||
|
*/
|
||||||
|
$.Spring = function( options ) {
|
||||||
|
var args = arguments;
|
||||||
|
|
||||||
|
if( typeof( options ) != 'object' ){
|
||||||
|
//allows backward compatible use of ( initialValue, config ) as
|
||||||
|
//constructor parameters
|
||||||
|
options = {
|
||||||
|
initial: args.length && typeof ( args[ 0 ] ) == "number" ?
|
||||||
|
args[ 0 ] :
|
||||||
|
0,
|
||||||
|
springStiffness: args.length > 1 ?
|
||||||
|
args[ 1 ].springStiffness :
|
||||||
|
5.0,
|
||||||
|
animationTime: args.length > 1 ?
|
||||||
|
args[ 1 ].animationTime :
|
||||||
|
1.5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( true, this, options);
|
||||||
|
|
||||||
|
|
||||||
|
this.current = {
|
||||||
|
value: typeof ( this.initial ) == "number" ?
|
||||||
|
this.initial :
|
||||||
|
0,
|
||||||
|
time: new Date().getTime() // always work in milliseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
this.start = {
|
||||||
|
value: this.current.value,
|
||||||
|
time: this.current.time
|
||||||
|
};
|
||||||
|
|
||||||
|
this.target = {
|
||||||
|
value: this.current.value,
|
||||||
|
time: this.current.time
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Spring.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} target
|
||||||
|
*/
|
||||||
|
resetTo: function( target ) {
|
||||||
|
this.target.value = target;
|
||||||
|
this.target.time = this.current.time;
|
||||||
|
this.start.value = this.target.value;
|
||||||
|
this.start.time = this.target.time;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} target
|
||||||
|
*/
|
||||||
|
springTo: function( target ) {
|
||||||
|
this.start.value = this.current.value;
|
||||||
|
this.start.time = this.current.time;
|
||||||
|
this.target.value = target;
|
||||||
|
this.target.time = this.start.time + 1000 * this.animationTime;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} delta
|
||||||
|
*/
|
||||||
|
shiftBy: function( delta ) {
|
||||||
|
this.start.value += delta;
|
||||||
|
this.target.value += delta;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
update: function() {
|
||||||
|
this.current.time = new Date().getTime();
|
||||||
|
this.current.value = (this.current.time >= this.target.time) ?
|
||||||
|
this.target.value :
|
||||||
|
this.start.value +
|
||||||
|
( this.target.value - this.start.value ) *
|
||||||
|
transform(
|
||||||
|
this.springStiffness,
|
||||||
|
( this.current.time - this.start.time ) /
|
||||||
|
( this.target.time - this.start.time )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function transform( stiffness, x ) {
|
||||||
|
return ( 1.0 - Math.exp( stiffness * -x ) ) /
|
||||||
|
( 1.0 - Math.exp( -stiffness ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
91
src/strings.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
//TODO: I guess this is where the i18n needs to be reimplemented. I'll look
|
||||||
|
// into existing patterns for i18n in javascript but i think that mimicking
|
||||||
|
// pythons gettext might be a reasonable approach.
|
||||||
|
var I18N = {
|
||||||
|
Errors: {
|
||||||
|
Failure: "Sorry, but Seadragon Ajax can't run on your browser!\n" +
|
||||||
|
"Please try using IE 7 or Firefox 3.\n",
|
||||||
|
Dzc: "Sorry, we don't support Deep Zoom Collections!",
|
||||||
|
Dzi: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
|
||||||
|
Xml: "Hmm, this doesn't appear to be a valid Deep Zoom Image.",
|
||||||
|
Empty: "You asked us to open nothing, so we did just that.",
|
||||||
|
ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.",
|
||||||
|
Security: "It looks like a security restriction stopped us from " +
|
||||||
|
"loading this Deep Zoom Image.",
|
||||||
|
Status: "This space unintentionally left blank ({0} {1}).",
|
||||||
|
Unknown: "Whoops, something inexplicably went wrong. Sorry!"
|
||||||
|
},
|
||||||
|
|
||||||
|
Messages: {
|
||||||
|
Loading: "Loading..."
|
||||||
|
},
|
||||||
|
|
||||||
|
Tooltips: {
|
||||||
|
FullPage: "Toggle full page",
|
||||||
|
Home: "Go home",
|
||||||
|
ZoomIn: "Zoom in",
|
||||||
|
ZoomOut: "Zoom out",
|
||||||
|
NextPage: "Next page",
|
||||||
|
PreviousPage: "Previous page"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.getString
|
||||||
|
* @param {String} property
|
||||||
|
*/
|
||||||
|
getString: function( prop ) {
|
||||||
|
|
||||||
|
var props = prop.split('.'),
|
||||||
|
string = null,
|
||||||
|
args = arguments,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for ( i = 0; i < props.length; i++ ) {
|
||||||
|
// in case not a subproperty
|
||||||
|
string = I18N[ props[ i ] ] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( typeof( string ) != "string" ) {
|
||||||
|
string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.replace(/\{\d+\}/g, function(capture) {
|
||||||
|
var i = parseInt( capture.match( /\d+/ ) ) + 1;
|
||||||
|
return i < args.length ?
|
||||||
|
args[ i ] :
|
||||||
|
"";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.setString
|
||||||
|
* @param {String} property
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
setString: function( prop, value ) {
|
||||||
|
|
||||||
|
var props = prop.split('.'),
|
||||||
|
container = $.Strings,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for ( i = 0; i < props.length - 1; i++ ) {
|
||||||
|
if ( !container[ props[ i ] ] ) {
|
||||||
|
container[ props[ i ] ] = {};
|
||||||
|
}
|
||||||
|
container = container[ props[ i ] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
container[ props[ i ] ] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
199
src/tile.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @param {Number} level The zoom level this tile belongs to.
|
||||||
|
* @param {Number} x The vector component 'x'.
|
||||||
|
* @param {Number} y The vector component 'y'.
|
||||||
|
* @param {OpenSeadragon.Point} bounds Where this tile fits, in normalized
|
||||||
|
* coordinates.
|
||||||
|
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
|
||||||
|
* this tile failed to load? )
|
||||||
|
* @param {String} url The URL of this tile's image.
|
||||||
|
*
|
||||||
|
* @property {Number} level The zoom level this tile belongs to.
|
||||||
|
* @property {Number} x The vector component 'x'.
|
||||||
|
* @property {Number} y The vector component 'y'.
|
||||||
|
* @property {OpenSeadragon.Point} bounds Where this tile fits, in normalized
|
||||||
|
* coordinates
|
||||||
|
* @property {Boolean} exists Is this tile a part of a sparse image? ( Also has
|
||||||
|
* this tile failed to load?
|
||||||
|
* @property {String} url The URL of this tile's image.
|
||||||
|
* @property {Boolean} loaded Is this tile loaded?
|
||||||
|
* @property {Boolean} loading Is this tile loading
|
||||||
|
* @property {Element} element The HTML element for this tile
|
||||||
|
* @property {Image} image The Image object for this tile
|
||||||
|
* @property {String} style The alias of this.element.style.
|
||||||
|
* @property {String} position This tile's position on screen, in pixels.
|
||||||
|
* @property {String} size This tile's size on screen, in pixels
|
||||||
|
* @property {String} blendStart The start time of this tile's blending
|
||||||
|
* @property {String} opacity The current opacity this tile should be.
|
||||||
|
* @property {String} distance The distance of this tile to the viewport center
|
||||||
|
* @property {String} visibility The visibility score of this tile.
|
||||||
|
* @property {Boolean} beingDrawn Whether this tile is currently being drawn
|
||||||
|
* @property {Number} lastTouchTime Timestamp the tile was last touched.
|
||||||
|
*/
|
||||||
|
$.Tile = function(level, x, y, bounds, exists, url) {
|
||||||
|
this.level = level;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.exists = exists;
|
||||||
|
this.url = url;
|
||||||
|
this.loaded = false;
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
this.element = null;
|
||||||
|
this.image = null;
|
||||||
|
|
||||||
|
this.style = null;
|
||||||
|
this.position = null;
|
||||||
|
this.size = null;
|
||||||
|
this.blendStart = null;
|
||||||
|
this.opacity = null;
|
||||||
|
this.distance = null;
|
||||||
|
this.visibility = null;
|
||||||
|
|
||||||
|
this.beingDrawn = false;
|
||||||
|
this.lastTouchTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Tile.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a string representation of this tiles level and (x,y)
|
||||||
|
* components.
|
||||||
|
* @function
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
toString: function() {
|
||||||
|
return this.level + "/" + this.x + "_" + this.y;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the tile in an html container.
|
||||||
|
* @function
|
||||||
|
* @param {Element} container
|
||||||
|
*/
|
||||||
|
drawHTML: function( container ) {
|
||||||
|
|
||||||
|
var containerSize = $.getElementSize( container );
|
||||||
|
|
||||||
|
if ( !this.loaded || !this.image ) {
|
||||||
|
$.console.warn(
|
||||||
|
"Attempting to draw tile %s when it's not yet loaded.",
|
||||||
|
this.toString()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* EXISTING IMPLEMENTATION
|
||||||
|
if ( !this.element ) {
|
||||||
|
this.element = $.makeNeutralElement("img");
|
||||||
|
this.element.src = this.url;
|
||||||
|
|
||||||
|
this.style = this.element.style;
|
||||||
|
this.style.position = "absolute";
|
||||||
|
this.style.msInterpolationMode = "nearest-neighbor";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.element.parentNode != container ) {
|
||||||
|
container.appendChild( this.element );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.style.top = position.y + "px";
|
||||||
|
this.style.left = position.x + "px";
|
||||||
|
this.style.height = size.y + "px";
|
||||||
|
this.style.width = size.x + "px";
|
||||||
|
*/
|
||||||
|
|
||||||
|
//EXPERIMENTAL - trying to figure out how to scale the container
|
||||||
|
// content during animation of the container size.
|
||||||
|
|
||||||
|
if ( !this.element ) {
|
||||||
|
this.element = $.makeNeutralElement("div");
|
||||||
|
this.image = $.makeNeutralElement("img");
|
||||||
|
this.image.src = this.url;
|
||||||
|
this.image.style.height = '100%';
|
||||||
|
this.image.style.width = '100%';
|
||||||
|
this.image.style.msInterpolationMode = "nearest-neighbor";
|
||||||
|
this.element.appendChild( this.image );
|
||||||
|
|
||||||
|
this.style = this.element.style;
|
||||||
|
this.style.position = "absolute";
|
||||||
|
}
|
||||||
|
if ( this.element.parentNode != container ) {
|
||||||
|
container.appendChild( this.element );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.style.top = 100 * ( this.position.y / containerSize.y ) + "%";
|
||||||
|
this.style.left = 100 * ( this.position.x / containerSize.x ) + "%";
|
||||||
|
this.style.height = 100 * ( this.size.y / containerSize.y ) + "%";
|
||||||
|
this.style.width = 100 * ( this.size.x / containerSize.x ) + "%";
|
||||||
|
|
||||||
|
$.setElementOpacity( this.image, this.opacity );
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the tile in a canvas-based context.
|
||||||
|
* @function
|
||||||
|
* @param {Canvas} context
|
||||||
|
*/
|
||||||
|
drawCanvas: function( context ) {
|
||||||
|
|
||||||
|
var position = this.position,
|
||||||
|
size = this.size;
|
||||||
|
|
||||||
|
if ( !this.loaded || !this.image ) {
|
||||||
|
$.console.warn(
|
||||||
|
"Attempting to draw tile %s when it's not yet loaded.",
|
||||||
|
this.toString()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.globalAlpha = this.opacity;
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
|
||||||
|
//if we are supposed to b rendering fully opaque rectangle,
|
||||||
|
//ie its done fading or fading is turned off, and if we are drawing
|
||||||
|
//an image with an alpha channel, then the only way
|
||||||
|
//to avoid seeing the tile underneath is to clear the rectangle
|
||||||
|
if( context.globalAlpha == 1 && this.image.src.match('.png') ){
|
||||||
|
//clearing only the inside of the rectangle occupied
|
||||||
|
//by the png prevents edge flikering
|
||||||
|
context.clearRect(
|
||||||
|
position.x+1,
|
||||||
|
position.y+1,
|
||||||
|
size.x-2,
|
||||||
|
size.y-2
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
context.drawImage( this.image, position.x, position.y, size.x, size.y );
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes tile from it's contianer.
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
unload: function() {
|
||||||
|
if ( this.element && this.element.parentNode ) {
|
||||||
|
this.element.parentNode.removeChild( this.element );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element = null;
|
||||||
|
this.image = null;
|
||||||
|
this.loaded = false;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
407
src/tilesource.js
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The TileSource contains the most basic implementation required to create a
|
||||||
|
* smooth transition between layer in an image pyramid. It has only a single key
|
||||||
|
* interface that must be implemented to complete it key functionality:
|
||||||
|
* 'getTileUrl'. It also has several optional interfaces that can be
|
||||||
|
* implemented if a new TileSource wishes to support configuration via a simple
|
||||||
|
* object or array ('configure') and if the tile source supports or requires
|
||||||
|
* configuration via retreival of a document on the network ala AJAX or JSONP,
|
||||||
|
* ('getImageInfo').
|
||||||
|
* <br/>
|
||||||
|
* By default the image pyramid is split into N layers where the images longest
|
||||||
|
* side in M (in pixels), where N is the smallest integer which satisfies
|
||||||
|
* <strong>2^(N+1) >= M</strong>.
|
||||||
|
* @class
|
||||||
|
* @extends OpenSeadragon.EventHandler
|
||||||
|
* @param {Number|Object|Array|String} width
|
||||||
|
* If more than a single argument is supplied, the traditional use of
|
||||||
|
* positional parameters is supplied and width is expected to be the width
|
||||||
|
* source image at it's max resolution in pixels. If a single argument is supplied and
|
||||||
|
* it is an Object or Array, the construction is assumed to occur through
|
||||||
|
* the extending classes implementation of 'configure'. Finally if only a
|
||||||
|
* single argument is supplied and it is a String, the extending class is
|
||||||
|
* expected to implement 'getImageInfo' and 'configure'.
|
||||||
|
* @param {Number} height
|
||||||
|
* Width of the source image at max resolution in pixels.
|
||||||
|
* @param {Number} tileSize
|
||||||
|
* The size of the tiles to assumed to make up each pyramid layer in pixels.
|
||||||
|
* Tile size determines the point at which the image pyramid must be
|
||||||
|
* divided into a matrix of smaller images.
|
||||||
|
* @param {Number} tileOverlap
|
||||||
|
* The number of pixels each tile is expected to overlap touching tiles.
|
||||||
|
* @param {Number} minLevel
|
||||||
|
* The minimum level to attempt to load.
|
||||||
|
* @param {Number} maxLevel
|
||||||
|
* The maximum level to attempt to load.
|
||||||
|
* @property {Number} aspectRatio
|
||||||
|
* Ratio of width to height
|
||||||
|
* @property {OpenSeadragon.Point} dimensions
|
||||||
|
* Vector storing x and y dimensions ( width and height respectively ).
|
||||||
|
* @property {Number} tileSize
|
||||||
|
* The size of the image tiles used to compose the image.
|
||||||
|
* @property {Number} tileOverlap
|
||||||
|
* The overlap in pixels each tile shares with it's adjacent neighbors.
|
||||||
|
* @property {Number} minLevel
|
||||||
|
* The minimum pyramid level this tile source supports or should attempt to load.
|
||||||
|
* @property {Number} maxLevel
|
||||||
|
* The maximum pyramid level this tile source supports or should attempt to load.
|
||||||
|
*/
|
||||||
|
$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
|
||||||
|
var _this = this,
|
||||||
|
callback = null,
|
||||||
|
readyHandler = null,
|
||||||
|
args = arguments,
|
||||||
|
options,
|
||||||
|
i;
|
||||||
|
|
||||||
|
if( $.isPlainObject( width ) ){
|
||||||
|
options = width;
|
||||||
|
}else{
|
||||||
|
options = {
|
||||||
|
width: args[0],
|
||||||
|
height: args[1],
|
||||||
|
tileSize: args[2],
|
||||||
|
tileOverlap: args[3],
|
||||||
|
minlevel: args[4],
|
||||||
|
maxLevel: args[5]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tile sources supply some events, namely 'ready' when they must be configured
|
||||||
|
//by asyncronously fetching their configuration data.
|
||||||
|
$.EventHandler.call( this );
|
||||||
|
|
||||||
|
//we allow options to override anything we dont treat as
|
||||||
|
//required via idiomatic options or which is functionally
|
||||||
|
//set depending on the state of the readiness of this tile
|
||||||
|
//source
|
||||||
|
$.extend( true, this, options );
|
||||||
|
|
||||||
|
//Any functions that are passed as arguments are bound to the ready callback
|
||||||
|
for( i = 0; i < arguments.length; i++ ){
|
||||||
|
if( $.isFunction( arguments[i] ) ){
|
||||||
|
callback = arguments[ i ];
|
||||||
|
this.addHandler( 'ready', function( placeHolderSource, readySource ){
|
||||||
|
callback( readySource );
|
||||||
|
});
|
||||||
|
//only one callback per constructor
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( 'string' == $.type( arguments[ 0 ] ) ){
|
||||||
|
//in case the getImageInfo method is overriden and/or implies an
|
||||||
|
//async mechanism set some safe defaults first
|
||||||
|
this.aspectRatio = 1;
|
||||||
|
this.dimensions = new $.Point( 10, 10 );
|
||||||
|
this.tileSize = 0;
|
||||||
|
this.tileOverlap = 0;
|
||||||
|
this.minLevel = 0;
|
||||||
|
this.maxLevel = 0;
|
||||||
|
this.ready = false;
|
||||||
|
//configuration via url implies the extending class
|
||||||
|
//implements and 'configure'
|
||||||
|
this.getImageInfo( arguments[ 0 ] );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//explicit configuration via positional args in constructor
|
||||||
|
//or the more idiomatic 'options' object
|
||||||
|
this.ready = true;
|
||||||
|
this.aspectRatio = ( options.width && options.height ) ?
|
||||||
|
( options.width / options.height ) : 1;
|
||||||
|
this.dimensions = new $.Point( options.width, options.height );
|
||||||
|
this.tileSize = options.tileSize ? options.tileSize : 0;
|
||||||
|
this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
|
||||||
|
this.minLevel = options.minLevel ? options.minLevel : 0;
|
||||||
|
this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
|
||||||
|
options.maxLevel : (
|
||||||
|
( options.width && options.height ) ? Math.ceil(
|
||||||
|
Math.log( Math.max( options.width, options.height ) ) /
|
||||||
|
Math.log( 2 )
|
||||||
|
) : 0
|
||||||
|
);
|
||||||
|
if( callback && $.isFunction( callback ) ){
|
||||||
|
callback( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$.TileSource.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
*/
|
||||||
|
getLevelScale: function( level ) {
|
||||||
|
return 1 / ( 1 << ( this.maxLevel - level ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
*/
|
||||||
|
getNumTiles: function( level ) {
|
||||||
|
var scale = this.getLevelScale( level ),
|
||||||
|
x = Math.ceil( scale * this.dimensions.x / this.tileSize ),
|
||||||
|
y = Math.ceil( scale * this.dimensions.y / this.tileSize );
|
||||||
|
|
||||||
|
return new $.Point( x, y );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
*/
|
||||||
|
getPixelRatio: function( level ) {
|
||||||
|
var imageSizeScaled = this.dimensions.times( this.getLevelScale( level ) ),
|
||||||
|
rx = 1.0 / imageSizeScaled.x,
|
||||||
|
ry = 1.0 / imageSizeScaled.y;
|
||||||
|
|
||||||
|
return new $.Point(rx, ry);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {OpenSeadragon.Point} point
|
||||||
|
*/
|
||||||
|
getTileAtPoint: function( level, point ) {
|
||||||
|
var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
|
||||||
|
tx = Math.floor( pixel.x / this.tileSize ),
|
||||||
|
ty = Math.floor( pixel.y / this.tileSize );
|
||||||
|
|
||||||
|
return new $.Point( tx, ty );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
getTileBounds: function( level, x, y ) {
|
||||||
|
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
|
||||||
|
px = ( x === 0 ) ? 0 : this.tileSize * x - this.tileOverlap,
|
||||||
|
py = ( y === 0 ) ? 0 : this.tileSize * y - this.tileOverlap,
|
||||||
|
sx = this.tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
|
||||||
|
sy = this.tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
|
||||||
|
scale = 1.0 / dimensionsScaled.x;
|
||||||
|
|
||||||
|
sx = Math.min( sx, dimensionsScaled.x - px );
|
||||||
|
sy = Math.min( sy, dimensionsScaled.y - py );
|
||||||
|
|
||||||
|
return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for retrieving, and caching the
|
||||||
|
* image metadata pertinent to this TileSources implementation.
|
||||||
|
* @function
|
||||||
|
* @param {String} url
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
getImageInfo: function( url ) {
|
||||||
|
var _this = this,
|
||||||
|
url = url,
|
||||||
|
error,
|
||||||
|
callbackName,
|
||||||
|
callback,
|
||||||
|
readySource,
|
||||||
|
options,
|
||||||
|
urlParts,
|
||||||
|
filename,
|
||||||
|
lastDot,
|
||||||
|
tilesUrl;
|
||||||
|
|
||||||
|
|
||||||
|
if( url ) {
|
||||||
|
urlParts = url.split( '/' );
|
||||||
|
filename = urlParts[ urlParts.length - 1 ];
|
||||||
|
lastDot = filename.lastIndexOf( '.' );
|
||||||
|
if ( lastDot > -1 ) {
|
||||||
|
urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = function( data ){
|
||||||
|
var $TileSource = $.TileSource.determineType( _this, data, url );
|
||||||
|
options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
|
||||||
|
readySource = new $TileSource( options );
|
||||||
|
_this.ready = true;
|
||||||
|
_this.raiseEvent( 'ready', readySource );
|
||||||
|
};
|
||||||
|
|
||||||
|
if( url.match(/\.js$/) ){
|
||||||
|
//TODO: Its not very flexible to require tile sources to end jsonp
|
||||||
|
// request for info with a url that ends with '.js' but for
|
||||||
|
// now it's the only way I see to distinguish uniformly.
|
||||||
|
callbackName = url.split( '/' ).pop().replace('.js','');
|
||||||
|
$.jsonp({
|
||||||
|
url: url,
|
||||||
|
async: false,
|
||||||
|
callbackName: callbackName,
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//TODO: struggling a little with TileSource rewrite to make info
|
||||||
|
// requests work asyncronously. For now I'm opting to make
|
||||||
|
// all xhr info request syncronous.
|
||||||
|
$.makeAjaxRequest( url, function( xhr ) {
|
||||||
|
var data = processResponse( xhr );
|
||||||
|
callback( data );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible determining if a the particular TileSource supports the
|
||||||
|
* data format ( and allowed to apply logic against the url the data was
|
||||||
|
* loaded from, if any ). Overriding implementations are expected to do
|
||||||
|
* something smart with data and / or url to determine support. Also
|
||||||
|
* understand that iteration order of TileSources is not guarunteed so
|
||||||
|
* please make sure your data or url is expressive enough to ensure a simple
|
||||||
|
* and sufficient mechanisim for clear determination.
|
||||||
|
* @function
|
||||||
|
* @param {String|Object|Array|Document} data
|
||||||
|
* @param {String} url - the url the data was loaded
|
||||||
|
* from if any.
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
supports: function( data, url ) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for parsing and configuring the
|
||||||
|
* image metadata pertinent to this TileSources implementation.
|
||||||
|
* This method is not implemented by this class other than to throw an Error
|
||||||
|
* announcing you have to implement it. Because of the variety of tile
|
||||||
|
* server technologies, and various specifications for building image
|
||||||
|
* pyramids, this method is here to allow easy integration.
|
||||||
|
* @function
|
||||||
|
* @param {String|Object|Array|Document} data
|
||||||
|
* @param {String} url - the url the data was loaded
|
||||||
|
* from if any.
|
||||||
|
* @return {Object} options - A dictionary of keyword arguments sufficient
|
||||||
|
* to configure this tile sources constructor.
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
configure: function( data, url ) {
|
||||||
|
throw new Error( "Method not implemented." );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for retriving the url which will return an image for the
|
||||||
|
* region speified by the given x, y, and level components.
|
||||||
|
* This method is not implemented by this class other than to throw an Error
|
||||||
|
* announcing you have to implement it. Because of the variety of tile
|
||||||
|
* server technologies, and various specifications for building image
|
||||||
|
* pyramids, this method is here to allow easy integration.
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
getTileUrl: function( level, x, y ) {
|
||||||
|
throw new Error( "Method not implemented." );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
tileExists: function( level, x, y ) {
|
||||||
|
var numTiles = this.getNumTiles( level );
|
||||||
|
return level >= this.minLevel &&
|
||||||
|
level <= this.maxLevel &&
|
||||||
|
x >= 0 &&
|
||||||
|
y >= 0 &&
|
||||||
|
x < numTiles.x &&
|
||||||
|
y < numTiles.y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$.extend( true, $.TileSource.prototype, $.EventHandler.prototype );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether to try to process the response as xml, json, or hand back
|
||||||
|
* the text
|
||||||
|
* @eprivate
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
* @param {XMLHttpRequest} xhr - the completed network request
|
||||||
|
*/
|
||||||
|
function processResponse( xhr ){
|
||||||
|
var responseText = xhr.responseText,
|
||||||
|
status = xhr.status,
|
||||||
|
statusText,
|
||||||
|
data;
|
||||||
|
|
||||||
|
if ( !xhr ) {
|
||||||
|
throw new Error( $.getString( "Errors.Security" ) );
|
||||||
|
} else if ( xhr.status !== 200 && xhr.status !== 0 ) {
|
||||||
|
status = xhr.status;
|
||||||
|
statusText = ( status == 404 ) ?
|
||||||
|
"Not Found" :
|
||||||
|
xhr.statusText;
|
||||||
|
throw new Error( $.getString( "Errors.Status", status, statusText ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( responseText.match(/\s*<.*/) ){
|
||||||
|
try{
|
||||||
|
data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
|
||||||
|
xhr.responseXML :
|
||||||
|
$.parseXml( responseText );
|
||||||
|
} catch (e){
|
||||||
|
data = xhr.responseText;
|
||||||
|
}
|
||||||
|
}else if( responseText.match(/\s*[\{\[].*/) ){
|
||||||
|
data = eval( responseText );
|
||||||
|
}else{
|
||||||
|
data = responseText;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the TileSource Implementation by introspection of OpenSeadragon
|
||||||
|
* namespace, calling each TileSource implementation of 'isType'
|
||||||
|
* @eprivate
|
||||||
|
* @inner
|
||||||
|
* @function
|
||||||
|
* @param {Object|Array} data - the tile source configuration object
|
||||||
|
* @param {String} url - the url where the tile source configuration object was
|
||||||
|
* loaded from, if any.
|
||||||
|
*/
|
||||||
|
$.TileSource.determineType = function( tileSource, data, url ){
|
||||||
|
var property;
|
||||||
|
for( property in OpenSeadragon ){
|
||||||
|
if( property.match(/.+TileSource$/) &&
|
||||||
|
$.isFunction( OpenSeadragon[ property ] ) &&
|
||||||
|
$.isFunction( OpenSeadragon[ property ].prototype.supports ) &&
|
||||||
|
OpenSeadragon[ property ].prototype.supports.call( tileSource, data, url )
|
||||||
|
){
|
||||||
|
return OpenSeadragon[ property ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
112
src/tilesourcecollection.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @extends OpenSeadragon.TileSourceCollection
|
||||||
|
*/
|
||||||
|
$.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
|
||||||
|
|
||||||
|
|
||||||
|
if( $.isPlainObject( tileSize ) ){
|
||||||
|
options = tileSize;
|
||||||
|
}else{
|
||||||
|
options = {
|
||||||
|
tileSize: arguments[ 0 ],
|
||||||
|
tileSources: arguments[ 1 ],
|
||||||
|
rows: arguments[ 2 ],
|
||||||
|
layout: arguments[ 3 ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !options.layout ){
|
||||||
|
options.layout = 'horizontal';
|
||||||
|
}
|
||||||
|
|
||||||
|
var minLevel = 0,
|
||||||
|
levelSize = 1.0,
|
||||||
|
tilesPerRow = Math.ceil( options.tileSources.length / options.rows ),
|
||||||
|
longSide = tilesPerRow >= options.rows ?
|
||||||
|
tilesPerRow :
|
||||||
|
options.rows
|
||||||
|
|
||||||
|
if( 'horizontal' == options.layout ){
|
||||||
|
options.width = ( options.tileSize ) * tilesPerRow;
|
||||||
|
options.height = ( options.tileSize ) * options.rows;
|
||||||
|
} else {
|
||||||
|
options.height = ( options.tileSize ) * tilesPerRow;
|
||||||
|
options.width = ( options.tileSize ) * options.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.tileOverlap = -options.tileMargin;
|
||||||
|
options.tilesPerRow = tilesPerRow;
|
||||||
|
|
||||||
|
//Set min level to avoid loading sublevels since collection is a
|
||||||
|
//different kind of abstraction
|
||||||
|
|
||||||
|
while( levelSize < ( options.tileSize ) * longSide ){
|
||||||
|
//$.console.log( '%s levelSize %s minLevel %s', options.tileSize * longSide, levelSize, minLevel );
|
||||||
|
levelSize = levelSize * 2.0;
|
||||||
|
minLevel++;
|
||||||
|
}
|
||||||
|
options.minLevel = minLevel;
|
||||||
|
|
||||||
|
//for( var name in options ){
|
||||||
|
// $.console.log( 'Collection %s %s', name, options[ name ] );
|
||||||
|
//}
|
||||||
|
|
||||||
|
$.TileSource.apply( this, [ options ] );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
getTileBounds: function( level, x, y ) {
|
||||||
|
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
|
||||||
|
px = this.tileSize * x - this.tileOverlap,
|
||||||
|
py = this.tileSize * y - this.tileOverlap,
|
||||||
|
sx = this.tileSize + 1 * this.tileOverlap,
|
||||||
|
sy = this.tileSize + 1 * this.tileOverlap,
|
||||||
|
scale = 1.0 / dimensionsScaled.x;
|
||||||
|
|
||||||
|
sx = Math.min( sx, dimensionsScaled.x - px );
|
||||||
|
sy = Math.min( sy, dimensionsScaled.y - py );
|
||||||
|
|
||||||
|
return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.TileSourceCollection.prototype.configure
|
||||||
|
*/
|
||||||
|
configure: function( data, url ){
|
||||||
|
return
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @name OpenSeadragon.TileSourceCollection.prototype.getTileUrl
|
||||||
|
* @param {Number} level
|
||||||
|
* @param {Number} x
|
||||||
|
* @param {Number} y
|
||||||
|
*/
|
||||||
|
getTileUrl: function( level, x, y ) {
|
||||||
|
//$.console.log([ level, '/', x, '_', y ].join( '' ));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|
1404
src/viewer.js
Normal file
554
src/viewport.js
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
/*globals OpenSeadragon */
|
||||||
|
|
||||||
|
(function( $ ){
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
$.Viewport = function( options ) {
|
||||||
|
|
||||||
|
//backward compatibility for positional args while prefering more
|
||||||
|
//idiomatic javascript options object as the only argument
|
||||||
|
var args = arguments;
|
||||||
|
if( args.length && args[ 0 ] instanceof $.Point ){
|
||||||
|
options = {
|
||||||
|
containerSize: args[ 0 ],
|
||||||
|
contentSize: args[ 1 ],
|
||||||
|
config: args[ 2 ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//options.config and the general config argument are deprecated
|
||||||
|
//in favor of the more direct specification of optional settings
|
||||||
|
//being pass directly on the options object
|
||||||
|
if ( options.config ){
|
||||||
|
$.extend( true, options, options.config );
|
||||||
|
delete options.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.extend( true, this, {
|
||||||
|
|
||||||
|
//required settings
|
||||||
|
containerSize: null,
|
||||||
|
contentSize: null,
|
||||||
|
|
||||||
|
//internal state properties
|
||||||
|
zoomPoint: null,
|
||||||
|
|
||||||
|
//configurable options
|
||||||
|
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
|
||||||
|
animationTime: $.DEFAULT_SETTINGS.animationTime,
|
||||||
|
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
|
||||||
|
maxZoomPixelRatio: $.DEFAULT_SETTINGS.maxZoomPixelRatio,
|
||||||
|
visibilityRatio: $.DEFAULT_SETTINGS.visibilityRatio,
|
||||||
|
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
|
||||||
|
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical
|
||||||
|
|
||||||
|
}, options );
|
||||||
|
|
||||||
|
this.centerSpringX = new $.Spring({
|
||||||
|
initial: 0,
|
||||||
|
springStiffness: this.springStiffness,
|
||||||
|
animationTime: this.animationTime
|
||||||
|
});
|
||||||
|
this.centerSpringY = new $.Spring({
|
||||||
|
initial: 0,
|
||||||
|
springStiffness: this.springStiffness,
|
||||||
|
animationTime: this.animationTime
|
||||||
|
});
|
||||||
|
this.zoomSpring = new $.Spring({
|
||||||
|
initial: 1,
|
||||||
|
springStiffness: this.springStiffness,
|
||||||
|
animationTime: this.animationTime
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetContentSize( this.contentSize );
|
||||||
|
this.goHome( true );
|
||||||
|
//this.fitHorizontally( true );
|
||||||
|
this.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
$.Viewport.prototype = {
|
||||||
|
|
||||||
|
resetContentSize: function( contentSize ){
|
||||||
|
this.contentSize = contentSize;
|
||||||
|
this.contentAspectX = this.contentSize.x / this.contentSize.y;
|
||||||
|
this.contentAspectY = this.contentSize.y / this.contentSize.x;
|
||||||
|
this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
|
||||||
|
this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY);
|
||||||
|
|
||||||
|
this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getHomeZoom: function() {
|
||||||
|
|
||||||
|
var aspectFactor =
|
||||||
|
this.contentAspectX / this.getAspectRatio();
|
||||||
|
|
||||||
|
return ( aspectFactor >= 1 ) ?
|
||||||
|
1 :
|
||||||
|
aspectFactor;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getMinZoom: function() {
|
||||||
|
var homeZoom = this.getHomeZoom(),
|
||||||
|
zoom = this.minZoomImageRatio * homeZoom;
|
||||||
|
|
||||||
|
return Math.min( zoom, homeZoom );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getMaxZoom: function() {
|
||||||
|
var zoom =
|
||||||
|
this.contentSize.x *
|
||||||
|
this.maxZoomPixelRatio /
|
||||||
|
this.containerSize.x;
|
||||||
|
return Math.max( zoom, this.getHomeZoom() );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getAspectRatio: function() {
|
||||||
|
return this.containerSize.x / this.containerSize.y;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getContainerSize: function() {
|
||||||
|
return new $.Point(
|
||||||
|
this.containerSize.x,
|
||||||
|
this.containerSize.y
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getBounds: function( current ) {
|
||||||
|
var center = this.getCenter( current ),
|
||||||
|
width = 1.0 / this.getZoom( current ),
|
||||||
|
height = width / this.getAspectRatio();
|
||||||
|
|
||||||
|
return new $.Rect(
|
||||||
|
center.x - ( width / 2.0 ),
|
||||||
|
center.y - ( height / 2.0 ),
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getCenter: function( current ) {
|
||||||
|
var centerCurrent = new $.Point(
|
||||||
|
this.centerSpringX.current.value,
|
||||||
|
this.centerSpringY.current.value
|
||||||
|
),
|
||||||
|
centerTarget = new $.Point(
|
||||||
|
this.centerSpringX.target.value,
|
||||||
|
this.centerSpringY.target.value
|
||||||
|
),
|
||||||
|
oldZoomPixel,
|
||||||
|
zoom,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bounds,
|
||||||
|
newZoomPixel,
|
||||||
|
deltaZoomPixels,
|
||||||
|
deltaZoomPoints;
|
||||||
|
|
||||||
|
if ( current ) {
|
||||||
|
return centerCurrent;
|
||||||
|
} else if ( !this.zoomPoint ) {
|
||||||
|
return centerTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
|
||||||
|
|
||||||
|
zoom = this.getZoom();
|
||||||
|
width = 1.0 / zoom;
|
||||||
|
height = width / this.getAspectRatio();
|
||||||
|
bounds = new $.Rect(
|
||||||
|
centerCurrent.x - width / 2.0,
|
||||||
|
centerCurrent.y - height / 2.0,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
|
||||||
|
newZoomPixel = this.zoomPoint.minus(
|
||||||
|
bounds.getTopLeft()
|
||||||
|
).times(
|
||||||
|
this.containerSize.x / bounds.width
|
||||||
|
);
|
||||||
|
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
|
||||||
|
deltaZoomPoints = deltaZoomPixels.divide( this.containerSize.x * zoom );
|
||||||
|
|
||||||
|
return centerTarget.plus( deltaZoomPoints );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
getZoom: function( current ) {
|
||||||
|
if ( current ) {
|
||||||
|
return this.zoomSpring.current.value;
|
||||||
|
} else {
|
||||||
|
return this.zoomSpring.target.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
applyConstraints: function( immediately ) {
|
||||||
|
var actualZoom = this.getZoom(),
|
||||||
|
constrainedZoom = Math.max(
|
||||||
|
Math.min( actualZoom, this.getMaxZoom() ),
|
||||||
|
this.getMinZoom()
|
||||||
|
),
|
||||||
|
bounds,
|
||||||
|
horizontalThreshold,
|
||||||
|
verticalThreshold,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
|
dx = 0,
|
||||||
|
dy = 0;
|
||||||
|
|
||||||
|
if ( actualZoom != constrainedZoom ) {
|
||||||
|
this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = this.getBounds();
|
||||||
|
|
||||||
|
horizontalThreshold = this.visibilityRatio * bounds.width;
|
||||||
|
verticalThreshold = this.visibilityRatio * bounds.height;
|
||||||
|
|
||||||
|
left = bounds.x + bounds.width;
|
||||||
|
right = 1 - bounds.x;
|
||||||
|
top = bounds.y + bounds.height;
|
||||||
|
bottom = this.contentAspectY - bounds.y;
|
||||||
|
|
||||||
|
if ( this.wrapHorizontal ) {
|
||||||
|
//do nothing
|
||||||
|
} else if ( left < horizontalThreshold ) {
|
||||||
|
dx = horizontalThreshold - left;
|
||||||
|
} else if ( right < horizontalThreshold ) {
|
||||||
|
dx = right - horizontalThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.wrapVertical ) {
|
||||||
|
//do nothing
|
||||||
|
} else if ( top < verticalThreshold ) {
|
||||||
|
dy = verticalThreshold - top;
|
||||||
|
} else if ( bottom < verticalThreshold ) {
|
||||||
|
dy = bottom - verticalThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( dx || dy ) {
|
||||||
|
bounds.x += dx;
|
||||||
|
bounds.y += dy;
|
||||||
|
this.fitBounds( bounds, immediately );
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
ensureVisible: function( immediately ) {
|
||||||
|
this.applyConstraints( immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Rect} bounds
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
fitBounds: function( bounds, immediately ) {
|
||||||
|
var aspect = this.getAspectRatio(),
|
||||||
|
center = bounds.getCenter(),
|
||||||
|
newBounds = new $.Rect(
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height
|
||||||
|
),
|
||||||
|
oldBounds,
|
||||||
|
oldZoom,
|
||||||
|
newZoom,
|
||||||
|
referencePoint;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
|
||||||
|
this.panTo( center, immediately );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
this.zoomTo( newZoom, referencePoint, immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
goHome: function( immediately ) {
|
||||||
|
return this.fitBounds( this.homeBounds, immediately );
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
fitVertically: function( immediately ) {
|
||||||
|
var center = this.getCenter();
|
||||||
|
|
||||||
|
if ( this.wrapHorizontal ) {
|
||||||
|
center.x = ( 1 + ( center.x % 1 ) ) % 1;
|
||||||
|
this.centerSpringX.resetTo( center.x );
|
||||||
|
this.centerSpringX.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.wrapVertical ) {
|
||||||
|
center.y = (
|
||||||
|
this.contentAspectY + ( center.y % this.contentAspectY )
|
||||||
|
) % this.contentAspectY;
|
||||||
|
this.centerSpringY.resetTo( center.y );
|
||||||
|
this.centerSpringY.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fitBounds( this.fitHeightBounds, immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
fitHorizontally: function( immediately ) {
|
||||||
|
var center = this.getCenter();
|
||||||
|
|
||||||
|
if ( this.wrapHorizontal ) {
|
||||||
|
center.x = (
|
||||||
|
this.contentAspectX + ( center.x % this.contentAspectX )
|
||||||
|
) % this.contentAspectX;
|
||||||
|
this.centerSpringX.resetTo( center.x );
|
||||||
|
this.centerSpringX.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.wrapVertical ) {
|
||||||
|
center.y = ( 1 + ( center.y % 1 ) ) % 1;
|
||||||
|
this.centerSpringY.resetTo( center.y );
|
||||||
|
this.centerSpringY.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fitBounds( this.fitWidthBounds, immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Point} delta
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
panBy: function( delta, immediately ) {
|
||||||
|
var center = new $.Point(
|
||||||
|
this.centerSpringX.target.value,
|
||||||
|
this.centerSpringY.target.value
|
||||||
|
);
|
||||||
|
this.panTo( center.plus( delta ), immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {OpenSeadragon.Point} center
|
||||||
|
* @param {Boolean} immediately
|
||||||
|
*/
|
||||||
|
panTo: function( center, immediately ) {
|
||||||
|
if ( immediately ) {
|
||||||
|
this.centerSpringX.resetTo( center.x );
|
||||||
|
this.centerSpringY.resetTo( center.y );
|
||||||
|
} else {
|
||||||
|
this.centerSpringX.springTo( center.x );
|
||||||
|
this.centerSpringY.springTo( center.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
zoomBy: function( factor, refPoint, immediately ) {
|
||||||
|
this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
zoomTo: function( zoom, refPoint, immediately ) {
|
||||||
|
|
||||||
|
this.zoomPoint = refPoint instanceof $.Point ?
|
||||||
|
refPoint :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if ( immediately ) {
|
||||||
|
this.zoomSpring.resetTo( zoom );
|
||||||
|
} else {
|
||||||
|
this.zoomSpring.springTo( zoom );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
resize: function( newContainerSize, maintain ) {
|
||||||
|
var oldBounds = this.getBounds(),
|
||||||
|
newBounds = oldBounds,
|
||||||
|
widthDeltaFactor = newContainerSize.x / this.containerSize.x;
|
||||||
|
|
||||||
|
this.containerSize = new $.Point(
|
||||||
|
newContainerSize.x,
|
||||||
|
newContainerSize.y
|
||||||
|
);
|
||||||
|
|
||||||
|
if (maintain) {
|
||||||
|
newBounds.width = oldBounds.width * widthDeltaFactor;
|
||||||
|
newBounds.height = newBounds.width / this.getAspectRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fitBounds( newBounds, true );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
update: function() {
|
||||||
|
var oldCenterX = this.centerSpringX.current.value,
|
||||||
|
oldCenterY = this.centerSpringY.current.value,
|
||||||
|
oldZoom = this.zoomSpring.current.value,
|
||||||
|
oldZoomPixel,
|
||||||
|
newZoomPixel,
|
||||||
|
deltaZoomPixels,
|
||||||
|
deltaZoomPoints;
|
||||||
|
|
||||||
|
if (this.zoomPoint) {
|
||||||
|
oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.zoomSpring.update();
|
||||||
|
|
||||||
|
if (this.zoomPoint && this.zoomSpring.current.value != oldZoom) {
|
||||||
|
newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
|
||||||
|
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
|
||||||
|
deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
|
||||||
|
|
||||||
|
this.centerSpringX.shiftBy( deltaZoomPoints.x );
|
||||||
|
this.centerSpringY.shiftBy( deltaZoomPoints.y );
|
||||||
|
} else {
|
||||||
|
this.zoomPoint = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.centerSpringX.update();
|
||||||
|
this.centerSpringY.update();
|
||||||
|
|
||||||
|
return this.centerSpringX.current.value != oldCenterX ||
|
||||||
|
this.centerSpringY.current.value != oldCenterY ||
|
||||||
|
this.zoomSpring.current.value != oldZoom;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
deltaPixelsFromPoints: function( deltaPoints, current ) {
|
||||||
|
return deltaPoints.times(
|
||||||
|
this.containerSize.x * this.getZoom( current )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
deltaPointsFromPixels: function( deltaPixels, current ) {
|
||||||
|
return deltaPixels.divide(
|
||||||
|
this.containerSize.x * this.getZoom( current )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
pixelFromPoint: function( point, current ) {
|
||||||
|
var bounds = this.getBounds( current );
|
||||||
|
return point.minus(
|
||||||
|
bounds.getTopLeft()
|
||||||
|
).times(
|
||||||
|
this.containerSize.x / bounds.width
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
pointFromPixel: function( pixel, current ) {
|
||||||
|
var bounds = this.getBounds( current );
|
||||||
|
return pixel.divide(
|
||||||
|
this.containerSize.x / bounds.width
|
||||||
|
).plus(
|
||||||
|
bounds.getTopLeft()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}( OpenSeadragon ));
|