diff --git a/.gitignore b/.gitignore
index f562c278..4b26396b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,3 @@
*.sublime-workspace
node_modules
build/
-openseadragon.zip
-openseadragon.tar
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 00000000..2c3f7d32
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,14 @@
+{
+ "browser": true,
+ "curly": true,
+ "eqeqeq": false,
+ "loopfunc": false,
+ "noarg": true,
+ "trailing": true,
+ "undef": true,
+ "unused": false,
+
+ "globals": {
+ "OpenSeadragon": true
+ }
+}
diff --git a/Gruntfile.js b/Gruntfile.js
index fda8fd6b..b077b431 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -10,15 +10,19 @@ module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-git-describe");
+ grunt.loadNpmTasks('grunt-text-replace');
// ----------
- var distribution = "build/openseadragon/openseadragon.js",
+ var packageJson = grunt.file.readJSON("package.json"),
+ distribution = "build/openseadragon/openseadragon.js",
minified = "build/openseadragon/openseadragon.min.js",
+ packageDirName = "openseadragon-bin-" + packageJson.version,
+ packageDir = "build/" + packageDirName + "/",
releaseRoot = "../site-build/built-openseadragon/",
sources = [
"src/openseadragon.js",
"src/fullscreen.js",
- "src/eventhandler.js",
+ "src/eventsource.js",
"src/mousetracker.js",
"src/control.js",
"src/controldock.js",
@@ -30,6 +34,7 @@ module.exports = function(grunt) {
"src/tilesource.js",
"src/dzitilesource.js",
"src/iiiftilesource.js",
+ "src/iiif1_1tilesource.js",
"src/osmtilesource.js",
"src/tmstilesource.js",
"src/legacytilesource.js",
@@ -49,9 +54,16 @@ module.exports = function(grunt) {
// ----------
// Project configuration.
grunt.initConfig({
- pkg: grunt.file.readJSON("package.json"),
+ pkg: packageJson,
+ osdVersion: {
+ versionStr: packageJson.version,
+ major: parseInt(packageJson.version.split('.')[0], 10),
+ minor: parseInt(packageJson.version.split('.')[1], 10),
+ revision: parseInt(packageJson.version.split('.')[2], 10)
+ },
clean: {
build: ["build"],
+ package: [packageDir],
release: {
src: [releaseRoot],
options: {
@@ -64,7 +76,8 @@ module.exports = function(grunt) {
banner: "//! <%= pkg.name %> <%= pkg.version %>\n"
+ "//! Built on <%= grunt.template.today('yyyy-mm-dd') %>\n"
+ "//! Git commit: <%= gitInfo %>\n"
- + "//! http://openseadragon.github.io\n\n",
+ + "//! http://openseadragon.github.io\n"
+ + "//! License: http://openseadragon.github.io/license/\n\n",
process: true
},
dist: {
@@ -72,9 +85,27 @@ module.exports = function(grunt) {
dest: distribution
}
},
+ replace: {
+ cleanPaths: {
+ src: ['build/openseadragon/*.map'],
+ overwrite: true,
+ replacements: [
+ {
+ from: /build\/openseadragon\//g,
+ to: ''
+ }
+ ]
+ }
+ },
uglify: {
options: {
- preserveComments: "some"
+ preserveComments: "some",
+ sourceMap: function (filename) {
+ return filename.replace(/\.js$/, '.js.map');
+ },
+ sourceMappingURL: function (filename) {
+ return filename.replace(/\.js$/, '.js.map').replace('build/openseadragon/', '');
+ },
},
openseadragon: {
src: [ distribution ],
@@ -84,24 +115,27 @@ module.exports = function(grunt) {
compress: {
zip: {
options: {
- archive: "build/openseadragon.zip"
+ archive: "build/releases/" + packageDirName + ".zip",
+ level: 9
},
files: [
- { expand: true, cwd: "build/", src: ["openseadragon/**"] }
+ { expand: true, cwd: "build/", src: [ packageDirName + "/**" ] }
]
},
tar: {
options: {
- archive: "build/openseadragon.tar"
+ archive: "build/releases/" + packageDirName + ".tar.gz",
+ level: 9
},
files: [
- { expand: true, cwd: "build/", src: [ "openseadragon/**" ] }
+ { expand: true, cwd: "build/", src: [ packageDirName + "/**" ] }
]
}
},
qunit: {
all: {
options: {
+ timeout: 10000,
urls: [ "http://localhost:8000/test/test.html" ]
}
}
@@ -120,12 +154,7 @@ module.exports = function(grunt) {
},
jshint: {
options: {
- browser: true,
- eqeqeq: false,
- loopfunc: false,
- globals: {
- OpenSeadragon: true
- }
+ jshintrc: '.jshintrc'
},
beforeconcat: sources,
afterconcat: [ distribution ]
@@ -144,10 +173,22 @@ module.exports = function(grunt) {
// Copies the image files into the appropriate location in the build folder.
grunt.registerTask("copy:build", function() {
grunt.file.recurse("images", function(abspath, rootdir, subdir, filename) {
- grunt.file.copy(abspath, "build/openseadragon/images/" + (subdir || "") + filename);
+ grunt.file.copy(abspath, "build/openseadragon/images/" + (subdir || "") + filename);
});
+ });
- grunt.file.copy("changelog.txt", "build/changelog.txt");
+ // ----------
+ // Copy:package task.
+ // Creates a directory tree to be compressed into a package.
+ grunt.registerTask("copy:package", function() {
+ grunt.file.recurse("build/openseadragon", function(abspath, rootdir, subdir, filename) {
+ var dest = packageDir
+ + (subdir ? subdir + "/" : '/')
+ + filename;
+ grunt.file.copy(abspath, dest);
+ });
+ grunt.file.copy("changelog.txt", packageDir + "changelog.txt");
+ grunt.file.copy("LICENSE.txt", packageDir + "LICENSE.txt");
});
// ----------
@@ -155,6 +196,10 @@ module.exports = function(grunt) {
// Copies the contents of the build folder into the release folder.
grunt.registerTask("copy:release", function() {
grunt.file.recurse("build", function(abspath, rootdir, subdir, filename) {
+ if (subdir === 'releases') {
+ return;
+ }
+
var dest = releaseRoot
+ (subdir ? subdir + "/" : '/')
+ filename;
@@ -167,7 +212,8 @@ module.exports = function(grunt) {
// Build task.
// Cleans out the build folder and builds the code and images into it, checking lint.
grunt.registerTask("build", [
- "clean:build", "jshint:beforeconcat", "git-describe", "concat", "jshint:afterconcat", "uglify", "copy:build"
+ "clean:build", "jshint:beforeconcat", "git-describe", "concat", "jshint:afterconcat",
+ "uglify", "replace:cleanPaths", "copy:build"
]);
// ----------
@@ -177,8 +223,8 @@ module.exports = function(grunt) {
// ----------
// Package task.
- // Builds and creates the .zip and .tar files.
- grunt.registerTask("package", ["build", "compress"]);
+ // Builds and creates the .zip and .tar.gz files.
+ grunt.registerTask("package", ["build", "copy:package", "compress", "clean:package"]);
// ----------
// Publish task.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..d4e25608
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,28 @@
+Copyright (C) 2009 CodePlex Foundation
+Copyright (C) 2010-2013 OpenSeadragon contributors
+
+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 CodePlex Foundation 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.
diff --git a/README.md b/README.md
index c3f66385..1ddabf86 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,20 @@
-# OpenSeadragon
+# OpenSeadragon
-This project is a fork of the OpenSeadragon project at http://openseadragon.codeplex.com/
+An open-source, web-based viewer for zoomable images, implemented in pure JavaScript.
-## On the Web
+See it in action and get started using it at http://openseadragon.github.io/.
-http://openseadragon.github.io/
+## Stable Builds
-## First Time Setup
+See the [GitHub releases page](https://github.com/openseadragon/openseadragon/releases).
-All command-line operations are scripted using [Grunt](http://gruntjs.com/) which is based on [Node.js](http://nodejs.org/). To get set up:
+## Development
+
+If you want to use OpenSeadragon in your own projects, you can find the latest stable build, API documentation, and example code at http://openseadragon.github.io/. If you want to modify OpenSeadragon and/or contribute to its development, read on.
+
+### First Time Setup
+
+All command-line operations for building and testing OpenSeadragon are scripted using [Grunt](http://gruntjs.com/) which is based on [Node.js](http://nodejs.org/). To get set up:
1. Install Node, if you haven't already (available at the link above)
1. Install the Grunt command line runner (if you haven't already); on the command line, run `npm install -g grunt-cli`
@@ -18,7 +24,7 @@ All command-line operations are scripted using [Grunt](http://gruntjs.com/) whic
You're set... continue reading for build and test instructions.
-## Building from Source
+### Building from Source
To build, just run (on the command line, in the openseadragon folder):
@@ -40,9 +46,9 @@ You can also publish the built version to the site-build repository. This assume
grunt publish
-... which will delete the existing openseadragon folder, along with the .zip and .tar files, out of the site-build folder and replace them with newly built ones from the source in this repository; you'll then need to commit the changes to site-build.
+... which will delete the existing openseadragon folder, along with the .zip and .tar.gz files, out of the site-build folder and replace them with newly built ones from the source in this repository; you'll then need to commit the changes to site-build.
-## Testing
+### Testing
Our tests are based on [QUnit](http://qunitjs.com/) and [PhantomJS](http://phantomjs.org/); they're both installed when you run `npm install`. At the moment we don't have much in the way of tests, but we're working to fix that. To run on the command line:
@@ -52,58 +58,27 @@ If you wish to work interactively with the tests or test your changes:
grunt connect watch
-and open `http://localhost:8000/` in your browser
+and open `http://localhost:8000/test/test.html` in your browser.
-## Contributing
+Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`.
+
+### Contributing
OpenSeadragon is truly a community project; we welcome your involvement!
When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For more thoughts on code style, see https://github.com/rwldrn/idiomatic.js/.
-## Licenses
+When fixing bugs and adding features, when appropriate please also:
-OpenSeadragon was initially released with a New BSD License ( preserved below ), while work done by Chris Thatcher is additionally licensed under the MIT License.
+* Update related doc comments (we use [JSDoc 3](http://usejsdoc.org/))
+* Add/update related unit tests
-### Original license preserved below
+If you're new to the project, check out our [good first bug](https://github.com/openseadragon/openseadragon/issues?labels=good+first+bug&page=1&state=open) issues for some places to dip your toe in the water.
--------------------------------------
-License: New BSD License (BSD)
-Copyright (c) 2010, OpenSeadragon
-All rights reserved.
+If you're new to open source in general, check out [GitHub's open source intro guide](https://guides.github.com/overviews/os-contributing/).
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+## License
-* 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.
+OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
[![Build Status](https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master)](http://travis-ci.org/openseadragon/openseadragon)
diff --git a/changelog.txt b/changelog.txt
index 871d7f84..26d56a2c 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,12 +1,171 @@
OPENSEADRAGON CHANGELOG
=======================
-0.9.125: In Progress
+1.1.0: (in progress)
-* Fully deprecated OpenSeadragon.createFromDZI, safely deprecated Viewer.openTileSource and
+* BREAKING CHANGE: the openseadragon-canvas element now has two child divs. This means: (#298)
+ * The drawer element is no longer accessible via viewer.canvas.firstChild but via viewer.drawersContainer.firstChild or viewer.drawer.canvas.
+ * The overlays elements are no longer accessible via viewer.canvas.childNodes but via viewer.overlaysContainer.childNodes or viewer.currentOverlays[i].element.
+* BREAKING CHANGE: Pseudo full screen mode on IE<11 using activex has been dropped. OpenSeadragon will run in full page if full screen mode is requested.
+* DEPRECATION: overlay functions have been moved from Drawer to Viewer (#331)
+* DEPRECATION: OpenSeadragon.cancelFullScreen has been renamed OpenSeadragon.exitFullScreen (#358)
+* Added layers support. Multiple images can now been displayed on top of each other with transparency via the Viewer.addLayer method (#298)
+* Improved overlay functions (#331)
+* Fixed: Nav button highlight states aren't quite aligned on Firefox (#303)
+* Added ControlAnchor options for default controls (#304)
+* Enabled basic cross-domain tile loading without tainting canvas (works in Chrome and Firefox) (#308)
+* Added crossOriginPolicy drawer configuration to enable or disable CORS image requests (#364)
+* Disabled CORS by default (#377)
+* Added a ControlAnchor.ABSOLUTE enumeration. Enables absolute positioning of control elements in the viewer (#310)
+* Added a 'navigator-scroll' event to Navigator. Fired when mousewheel/pinch events occur in the navigator (#310)
+* Added a navigatorMaintainSizeRatio option. If set to true, the navigator minimap resizes when the viewer element is resized (#310)
+* Added 'ABSOLUTE' as a navigatorPosition option, along with corresponding navigatorTop, navigatorLeft options. Allows the navigator minimap to be placed anywhere in the viewer (#310)
+* Enhanced the navigatorTop, navigatorLeft, navigatorHeight, and navigatorWidth options to allow a number for pixel units or a string for other element units (%, em, etc.) (#310)
+* Additional enhancements for IIIF support (#315)
+* Fixed: Setting degrees in Viewer constructor has no effect (#336)
+* Added pre-draw event for tiles to allow applications to alter the image (#348)
+* Added optional Rotate Left/Right buttons to standard controls (#341)
+* Added optimization for large numbers of overlays: `checkResize = false` option for OpenSeadragon.Overlay (#365)
+* Updated full screen API, adding support for Opera and IE11 and allowing keyboard input in Chrome (#358)
+* Various fixes to bring OpenSeadragon into W3C compliance (#375)
+* Added separate flags for turning off each of the nav buttons (#376)
+
+1.0.0:
+
+NOTE: This version has a number of breaking changes to the API, mostly in event handling. See below.
+
+* BREAKING CHANGE: All EventSource and MouseTracker event handler method signatures changed to 'handlerMethod(event)' where event == { eventSource, userData, ... } (#251) (Also fixes #23, #224, #239)
+ * The new eventSource property in the event object replaces the old eventSource parameter that was passed to handler methods.
+ * Where the event object duplicated the eventSource value, those properties have been removed. This affects the following events:
+ * All Button events - 'button' property removed
+ * All Viewer (Viewer, Drawer, Viewport) events - 'viewer' property removed
+* BREAKING CHANGE: Renamed EventHandler to EventSource (#225)
+* BREAKING CHANGE: Event names changed for consistency: changed to lower case, compound names hyphenated, and "on" prefixes removed (#226):
+ * Viewer "animationstart" changed to "animation-start"
+ * Viewer "animationfinish" changed to "animation-finish"
+ * Button "onPress" changed to "press"
+ * Button "onRelease" changed to "release"
+ * Button "onClick" changed to "click"
+ * Button "onEnter" changed to "enter"
+ * Button "onExit" changed to "exit"
+ * Button "onFocus" changed to "focus"
+ * Button "onBlur" changed to "blur"
+* BREAKING CHANGE: Numerous improvements to fullPage/fullScreen (#256):
+ * Retains zoom/pan position better when switching into and out of fullPage.
+ * Retains scroll position when switching back out.
+ * More resilient to styling variations on the page.
+ * setFullPage no longer automatically engages fullScreen; there's now a separate setFullScreen.
+ * 'fullpage' event is now 'full-page'.
+ * The `fullpage` property of the 'full-page' event is now `fullPage`.
+ * There is now a 'full-screen' event with a `fullScreen` property (true if it has gone to full screen).
+ * There are now 'pre-full-page' and 'pre-full-screen' events that include a `preventDefaultAction` property you can set in your handler to cancel. They also have `fullPage` and `fullScreen` properties respectively, to indicate if they are going into or out of the mode.
+* BREAKING CHANGE: Removed the 'onPageChange' callback from the viewer options. Viewer.goToPage() now raises the 'page' event only (#285)
+* Major documentation improvements (#281)
+* MouseTracker now passes the original event objects to its handler methods (#23)
+* MouseTracker now supports an optional 'moveHandler' method for tracking mousemove events (#215)
+* Added stopHandler to MouseTracker. (#262)
+* Fixed: Element-relative mouse coordinates now correct if the element and/or page is scrolled (using new OpenSeadragon.getElementOffset() method) (#131)
+* Fixed: Pinch zoom event issue, regressive issue from previous event system changes (#244)
+* Added IIIF Image API 1.1 Tile Source (#230)
+* IIIF 1.0 now uses pixel based syntax (#249)
+* Fixed: Touch event issue where no canvas-click events were being raised (#240)
+* Check that zoom reference point is valid before using it in zoomTo and zoomBy (#247)
+* Added a number of easier coordinate conversion methods to viewport (#243)
+* Added the ability to create a viewer and start at a specified page (#252)
+* Fixed image resolve issue with collection mode (#255)
+* DOM events are now passed through as 'event.originalEvent' in viewer and button events where appropriate. (#257) Affects the following events:
+ * Viewer: 'canvas-release', 'canvas-click', 'canvas-drag', 'canvas-scroll', 'container-enter', 'container-exit', 'container-release'
+ * Button: 'enter', 'exit', 'press', 'release', 'focus', 'blur', 'click'
+* Fixed: IE 10 not reading DZI file correctly in certain circumstances (#218)
+* Added support for the 'wheel' DOM mousewheel event (#261)
+* Fix for non-canvas tile rendering at large size (#264)
+* Drawer now uses an HTML5 canvas element whenever it's available. Can be overridden with the Viewer.useCanvas option (#191)
+* Added a boolean preventDefaultAction property (default false) to the event object passed to MouseTracker handler methods. (#270) Implemented in the following MouseTracker subscribers:
+ * Viewer.keyboardCommandArea.innerTracker.focusHandler: preventDefaultAction == true prevents scrolling viewer into view
+ * Viewer.keyboardCommandArea.innerTracker.keyHandler: preventDefaultAction == true prevents viewer keyboard navigation
+ * Viewer.innerTracker.clickHandler: preventDefaultAction == true prevents viewer zoom on click
+ * Viewer.innerTracker.dragHandler: preventDefaultAction == true prevents viewer panning with mouse/touch
+ * Viewer.innerTracker.scrollHandler: preventDefaultAction == true prevents viewer zooming on mousewheel/pinch
+* Fixed: IE8 error with custom buttons - "Object doesn't support this action" (#279)
+* Support IIIF servers that don't report tile dimensions (#286)
+* Added an autoResize option. Default is true. When set to false, the viewer takes no action when its container element is resized. (#291)
+* Added a static 'version' property to OpenSeadragon. Useful for plugins that require specific OpenSeadragon versions. (#292)
+
+0.9.131:
+
+* Fixed: canvas-click event shouldn't fire as you drag (#198)
+* Fixed: LegacyTileSource doesn't fail gracefully when no supported file formats are found (#202)
+* Added an optional userData argument to EventHandler.addHandler() which is passed unchanged to the handler method (#203)
+* Fixed AJAX error reporting on IE8 (#208)
+* Added viewportToImageRectangle method, and updated imageToViewportRectangle, imageToViewportCoordinates, and viewportToImageCoordinates to be more flexible with params (#212)
+* Fixed: Viewer is not responsive (css) after returning from full screen (#222)
+
+0.9.130:
+
+* Added partial support for rotation (just 90 degree increments for now). (#185)
+* Hiding and restoring broke the viewer; fixed (#177)
+* You can now provide an onDraw function for overlays to do custom overlay manipulation (#160)
+* Added a destroy function on the viewer to clean up and remove elements (#179)
+* Fixed: navigatorPosition option corrected. (#163)
+* OpenSeadragon.now() returned undefined the first time; fixed
+* onTouchEnd did not call the correct mouse up handler; fixed (#159)
+* Touch events no longer capture mouse (was causing issues on devices that support both) (#168)
+* Clicking on a button control no longer refreshes page (#184)
+* Drawer now works when the page is rtl (#187)
+* Fixed a situation that could throw errors in touch handling (#188)
+
+0.9.129:
+
+* Fixed: navigator image not updating when base zoom image is changed (#147)
+* Fixed tile rendering issue at lower zoom levels with the IIIF TileSource (#55)
+* On IE, ajax errors would cause an exception to be thrown; fixed (#144)
+* Faster and more consistent millisecond getter (#138)
+* Fixed an error when using navPrevNextWrap on single images (#135)
+* Various fixes to our timer handling (#133)
+* Now generating source map for openseadragon.min.js (#51)
+* Fix for calculating overlay width / height (#142)
+* JSHint tidying (#136)
+* Improved Ajax method (#149)
+* Overhauled AJAX error reporting (#151)
+
+0.9.128:
+
+* The navigator is now off by default (#102)
+* Reverted minPixelRatio to 0.5 for better quality (#116)
+* Sometimes tiles wouldn't resolve if you used the blendTime option; fixed. (#95)
+* You can now choose to have previous and next buttons wrap using the config.navPrevNextWrap. (#114)
+* You can now specify an ID for a div to hold the navigator (#46)
+* You can now click in the navigator to go to a new location (#46)
+* Keyboard handling is now done in the viewer rather than navigator (#46)
+* Additional navigator fixes (#46)
+* Drawer events now fire properly (#94)
+* Fixed an error in EventHandler.removeHandler() (#48)
+* Better requestAnimationFrame detection on older Firefox (#103)
+* More efficient navigator loading (#115)
+* Simplified element opacity setting implementation (#123)
+
+0.9.127:
+
+* Fixed a problem with getString when the string property is a sub-property. (#64)
+* Fixed: Tooltips for Navigation Controls not displaying (#63)
+* Cleaned up some diagnostic code that was broken.
+* Added fullpage class to viewer element when in fullpage mode (#61)
+* Reverted to original New BSD license; cleaned up license declarations (#89)
+
+0.9.126:
+
+* DZI JSONp was broken; fixed.
+
+0.9.125:
+
+* Fully deprecated OpenSeadragon.createFromDZI, safely deprecated Viewer.openTileSource and
Viewer.openDZI to use Viewer.open internally. (#53 & #54).
* Full page bug fix for when viewer is child of document body (#43).
-* Overlays for DZI bug fix (#45).
+* Overlays for DZI bug fix (#45).
+* DziTileSource: avoid changing relative paths (#56).
+* Fix typo in preserveViewport handling (#77).
+* Fix updateMulti timer leak after multiple Viewer.open() calls (#76).
+* Minor documentation fixes.
0.9.124:
diff --git a/images/fullpage_grouphover.png b/images/fullpage_grouphover.png
old mode 100755
new mode 100644
diff --git a/images/fullpage_hover.png b/images/fullpage_hover.png
old mode 100755
new mode 100644
diff --git a/images/fullpage_pressed.png b/images/fullpage_pressed.png
old mode 100755
new mode 100644
diff --git a/images/fullpage_rest.png b/images/fullpage_rest.png
old mode 100755
new mode 100644
diff --git a/images/home_grouphover.png b/images/home_grouphover.png
old mode 100755
new mode 100644
diff --git a/images/home_hover.png b/images/home_hover.png
old mode 100755
new mode 100644
diff --git a/images/home_pressed.png b/images/home_pressed.png
old mode 100755
new mode 100644
diff --git a/images/home_rest.png b/images/home_rest.png
old mode 100755
new mode 100644
diff --git a/images/rotateleft_grouphover.png b/images/rotateleft_grouphover.png
new file mode 100644
index 00000000..9aec7ac9
Binary files /dev/null and b/images/rotateleft_grouphover.png differ
diff --git a/images/rotateleft_hover.png b/images/rotateleft_hover.png
new file mode 100644
index 00000000..ba32c5a4
Binary files /dev/null and b/images/rotateleft_hover.png differ
diff --git a/images/rotateleft_pressed.png b/images/rotateleft_pressed.png
new file mode 100644
index 00000000..b968ebf8
Binary files /dev/null and b/images/rotateleft_pressed.png differ
diff --git a/images/rotateleft_rest.png b/images/rotateleft_rest.png
new file mode 100644
index 00000000..ebbf081b
Binary files /dev/null and b/images/rotateleft_rest.png differ
diff --git a/images/rotateright_grouphover.png b/images/rotateright_grouphover.png
new file mode 100644
index 00000000..86e8689c
Binary files /dev/null and b/images/rotateright_grouphover.png differ
diff --git a/images/rotateright_hover.png b/images/rotateright_hover.png
new file mode 100644
index 00000000..d22a728f
Binary files /dev/null and b/images/rotateright_hover.png differ
diff --git a/images/rotateright_pressed.png b/images/rotateright_pressed.png
new file mode 100644
index 00000000..fc2ead64
Binary files /dev/null and b/images/rotateright_pressed.png differ
diff --git a/images/rotateright_rest.png b/images/rotateright_rest.png
new file mode 100644
index 00000000..07219678
Binary files /dev/null and b/images/rotateright_rest.png differ
diff --git a/images/zoomin_grouphover.png b/images/zoomin_grouphover.png
old mode 100755
new mode 100644
diff --git a/images/zoomin_hover.png b/images/zoomin_hover.png
old mode 100755
new mode 100644
diff --git a/images/zoomin_pressed.png b/images/zoomin_pressed.png
old mode 100755
new mode 100644
diff --git a/images/zoomin_rest.png b/images/zoomin_rest.png
old mode 100755
new mode 100644
diff --git a/images/zoomout_grouphover.png b/images/zoomout_grouphover.png
old mode 100755
new mode 100644
diff --git a/images/zoomout_hover.png b/images/zoomout_hover.png
old mode 100755
new mode 100644
diff --git a/images/zoomout_pressed.png b/images/zoomout_pressed.png
old mode 100755
new mode 100644
diff --git a/images/zoomout_rest.png b/images/zoomout_rest.png
old mode 100755
new mode 100644
diff --git a/licenses/mit.txt b/licenses/mit.txt
deleted file mode 100644
index 24ad8aa6..00000000
--- a/licenses/mit.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-(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.
\ No newline at end of file
diff --git a/licenses/new-bsd.txt b/licenses/new-bsd.txt
deleted file mode 100644
index 0d24a9f9..00000000
--- a/licenses/new-bsd.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-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.
-
--------------------------------------
diff --git a/openseadragon.sublime-project b/openseadragon.sublime-project
index b7e30c92..1d6484e7 100644
--- a/openseadragon.sublime-project
+++ b/openseadragon.sublime-project
@@ -3,12 +3,19 @@
[
{
"path": ".",
- "file_exclude_patterns": ["*.sublime-project", "*.sublime-workspace"]
+ "file_exclude_patterns": [
+ "*.sublime-project",
+ "*.sublime-workspace"
+ ],
+ "folder_exclude_patterns": [
+ "node_modules"
+ ]
}
],
"settings":
{
"tab_size": 4,
- "translate_tabs_to_spaces": true
+ "translate_tabs_to_spaces": true,
+ "trim_trailing_white_space_on_save": true
}
}
diff --git a/package.json b/package.json
index fe3dff04..9c53ceb0 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,19 @@
{
"name": "OpenSeadragon",
- "version": "0.9.124",
+ "version": "1.0.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"devDependencies": {
- "grunt": "~0.4.0",
- "grunt-contrib-compress": "~0.4.0",
- "grunt-contrib-concat": "~0.1.2",
- "grunt-contrib-jshint": "~0.1.1",
- "grunt-contrib-uglify": "~0.1.1",
- "grunt-contrib-qunit": "~0.2.0",
- "grunt-contrib-connect": "~0.1.2",
- "grunt-contrib-watch": "~0.2.0",
- "grunt-contrib-clean": "~0.4.0",
- "grunt-git-describe": "~2.0.0"
+ "grunt": "~0.4.1",
+ "grunt-contrib-compress": "~0.5.2",
+ "grunt-contrib-concat": "~0.3.0",
+ "grunt-contrib-jshint": "~0.7.2",
+ "grunt-contrib-uglify": "~0.2.7",
+ "grunt-contrib-qunit": "~0.3.0",
+ "grunt-contrib-connect": "~0.5.0",
+ "grunt-contrib-watch": "~0.5.3",
+ "grunt-contrib-clean": "~0.5.0",
+ "grunt-git-describe": "~2.0.0",
+ "grunt-text-replace": "~0.3.9"
},
"scripts": {
"test": "grunt test"
diff --git a/src/button.js b/src/button.js
index 4b98faf3..a83d40a6 100644
--- a/src/button.js
+++ b/src/button.js
@@ -1,8 +1,49 @@
+/*
+ * OpenSeadragon - Button
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
/**
- * An enumeration of button states including, REST, GROUP, HOVER, and DOWN
+ * An enumeration of button states
+ * @member ButtonState
+ * @memberof OpenSeadragon
* @static
+ * @type {Object}
+ * @property {Number} REST
+ * @property {Number} GROUP
+ * @property {Number} HOVER
+ * @property {Number} DOWN
*/
$.ButtonState = {
REST: 0,
@@ -12,47 +53,39 @@ $.ButtonState = {
};
/**
- * Manages events, hover states for individual buttons, tool-tips, as well
- * as fading the bottons out when the user has not interacted with them
+ * @class Button
+ * @classdesc Manages events, hover states for individual buttons, tool-tips, as well
+ * as fading the buttons out when the user has not interacted with them
* for a specified period.
- * @class
- * @extends OpenSeadragon.EventHandler
+ *
+ * @memberof OpenSeadragon
+ * @extends OpenSeadragon.EventSource
* @param {Object} options
- * @param {String} options.tooltip Provides context help for the button we the
+ * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML <button> element is created.
+ * @param {String} [options.tooltip=null] Provides context help for the button when 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;
+ * @param {String} [options.srcRest=null] URL of image to use in 'rest' state.
+ * @param {String} [options.srcGroup=null] URL of image to use in 'up' state.
+ * @param {String} [options.srcHover=null] URL of image to use in 'hover' state.
+ * @param {String} [options.srcDown=null] URL of image to use in 'down' state.
+ * @param {Number} [options.fadeDelay=0] How long to wait before fading.
+ * @param {Number} [options.fadeLength=2000] How long should it take to fade the button.
+ * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}.
+ * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}.
+ * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}.
+ * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}.
+ * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}.
+ * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}.
+ * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}.
*/
$.Button = function( options ) {
var _this = this;
- $.EventHandler.call( this );
+ $.EventSource.call( this );
$.extend( true, this, {
-
+
tooltip: null,
srcRest: null,
srcGroup: null,
@@ -60,9 +93,17 @@ $.Button = function( options ) {
srcDown: null,
clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
- // begin fading immediately
- fadeDelay: 0,
- // fade over a period of 2 seconds
+ /**
+ * How long to wait before fading.
+ * @member {Number} fadeDelay
+ * @memberof OpenSeadragon.Button#
+ */
+ fadeDelay: 0,
+ /**
+ * How long should it take to fade the button.
+ * @member {Number} fadeLength
+ * @memberof OpenSeadragon.Button#
+ */
fadeLength: 2000,
onPress: null,
onRelease: null,
@@ -74,126 +115,240 @@ $.Button = function( options ) {
}, options );
- this.element = options.element || $.makeNeutralElement( "button" );
- this.element.href = this.element.href || '#';
-
+ /**
+ * The button element.
+ * @member {Element} element
+ * @memberof OpenSeadragon.Button#
+ */
+ this.element = options.element || $.makeNeutralElement( "div" );
+
//if the user has specified the element to bind the control to explicitly
//then do not add the default control images
- if( !options.element ){
+ if ( !options.element ) {
this.imgRest = $.makeTransparentImage( this.srcRest );
this.imgGroup = $.makeTransparentImage( this.srcGroup );
this.imgHover = $.makeTransparentImage( this.srcHover );
this.imgDown = $.makeTransparentImage( this.srcDown );
-
+
+ this.imgRest.alt =
+ this.imgGroup.alt =
+ this.imgHover.alt =
+ this.imgDown.alt =
+ this.tooltip;
+
+ this.element.style.position = "relative";
+
+ 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.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.addHandler( "press", this.onPress );
+ this.addHandler( "release", this.onRelease );
+ this.addHandler( "click", this.onClick );
+ this.addHandler( "enter", this.onEnter );
+ this.addHandler( "exit", this.onExit );
+ this.addHandler( "focus", this.onFocus );
+ this.addHandler( "blur", this.onBlur );
+ /**
+ * The button's current state.
+ * @member {OpenSeadragon.ButtonState} currentState
+ * @memberof OpenSeadragon.Button#
+ */
this.currentState = $.ButtonState.GROUP;
+ // When the button last began to fade.
this.fadeBeginTime = null;
+ // Whether this button should fade after user stops interacting with the viewport.
this.shouldFade = false;
this.element.style.display = "inline-block";
this.element.style.position = "relative";
this.element.title = this.tooltip;
+ /**
+ * Tracks mouse/touch/key events on the button.
+ * @member {OpenSeadragon.MouseTracker} tracker
+ * @memberof OpenSeadragon.Button#
+ */
this.tracker = new $.MouseTracker({
- element: this.element,
- clickTimeThreshold: this.clickTimeThreshold,
+ element: this.element,
+ clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
- enterHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
- if ( buttonDownElement ) {
+ enterHandler: function( event ) {
+ if ( event.insideElementPressed ) {
inTo( _this, $.ButtonState.DOWN );
- _this.raiseEvent( "onEnter", _this );
- } else if ( !buttonDownAny ) {
+ /**
+ * Raised when the cursor enters the Button element.
+ *
+ * @event enter
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "enter", { originalEvent: event.originalEvent } );
+ } else if ( !event.buttonDownAny ) {
inTo( _this, $.ButtonState.HOVER );
}
},
- focusHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
- this.enterHandler( tracker, position, buttonDownElement, buttonDownAny );
- _this.raiseEvent( "onFocus", _this );
+ focusHandler: function ( event ) {
+ this.enterHandler( event );
+ /**
+ * Raised when the Button element receives focus.
+ *
+ * @event focus
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "focus", { originalEvent: event.originalEvent } );
},
- exitHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
+ exitHandler: function( event ) {
outTo( _this, $.ButtonState.GROUP );
- if ( buttonDownElement ) {
- _this.raiseEvent( "onExit", _this );
+ if ( event.insideElementPressed ) {
+ /**
+ * Raised when the cursor leaves the Button element.
+ *
+ * @event exit
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "exit", { originalEvent: event.originalEvent } );
}
},
- blurHandler: function( tracker, position, buttonDownElement, buttonDownAny ) {
- this.exitHandler( tracker, position, buttonDownElement, buttonDownAny );
- _this.raiseEvent( "onBlur", _this );
+ blurHandler: function ( event ) {
+ this.exitHandler( event );
+ /**
+ * Raised when the Button element loses focus.
+ *
+ * @event blur
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "blur", { originalEvent: event.originalEvent } );
},
- pressHandler: function( tracker, position ) {
+ pressHandler: function ( event ) {
inTo( _this, $.ButtonState.DOWN );
- _this.raiseEvent( "onPress", _this );
+ /**
+ * Raised when a mouse button is pressed or touch occurs in the Button element.
+ *
+ * @event press
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "press", { originalEvent: event.originalEvent } );
},
- releaseHandler: function( tracker, position, insideElementPress, insideElementRelease ) {
- if ( insideElementPress && insideElementRelease ) {
+ releaseHandler: function( event ) {
+ if ( event.insideElementPressed && event.insideElementReleased ) {
outTo( _this, $.ButtonState.HOVER );
- _this.raiseEvent( "onRelease", _this );
- } else if ( insideElementPress ) {
+ /**
+ * Raised when the mouse button is released or touch ends in the Button element.
+ *
+ * @event release
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
+ } else if ( event.insideElementPressed ) {
outTo( _this, $.ButtonState.GROUP );
} else {
inTo( _this, $.ButtonState.HOVER );
}
},
- clickHandler: function( tracker, position, quick, shift ) {
- if ( quick ) {
- _this.raiseEvent("onClick", _this);
+ clickHandler: function( event ) {
+ if ( event.quick ) {
+ /**
+ * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
+ *
+ * @event click
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent("click", { originalEvent: event.originalEvent });
}
},
- keyHandler: function( tracker, key ){
- //console.log( "%s : handling key %s!", _this.tooltip, key);
- if( 13 === key ){
- _this.raiseEvent( "onClick", _this );
- _this.raiseEvent( "onRelease", _this );
+ keyHandler: function( event ){
+ //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode);
+ if( 13 === event.keyCode ){
+ /***
+ * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold.
+ *
+ * @event click
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "click", { originalEvent: event.originalEvent } );
+ /***
+ * Raised when the mouse button is released or touch ends in the Button element.
+ *
+ * @event release
+ * @memberof OpenSeadragon.Button
+ * @type {object}
+ * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( "release", { originalEvent: event.originalEvent } );
return false;
}
return true;
@@ -204,13 +359,12 @@ $.Button = function( options ) {
outTo( this, $.ButtonState.REST );
};
-$.extend( $.Button.prototype, $.EventHandler.prototype, {
+$.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.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 );
@@ -220,18 +374,23 @@ $.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.notifyGroupExit
*/
notifyGroupExit: function() {
outTo( this, $.ButtonState.REST );
},
+ /**
+ * @function
+ */
disable: function(){
this.notifyGroupExit();
this.element.disabled = true;
$.setElementOpacity( this.element, 0.2, true );
},
+ /**
+ * @function
+ */
enable: function(){
this.element.disabled = false;
$.setElementOpacity( this.element, 1.0, true );
@@ -253,7 +412,7 @@ function updateFade( button ) {
opacity;
if ( button.shouldFade ) {
- currentTime = +new Date();
+ currentTime = $.now();
deltaTime = currentTime - button.fadeBeginTime;
opacity = 1.0 - deltaTime / button.fadeLength;
opacity = Math.min( 1.0, opacity );
@@ -271,8 +430,8 @@ function updateFade( button ) {
function beginFading( button ) {
button.shouldFade = true;
- button.fadeBeginTime = +new Date() + button.fadeDelay;
- window.setTimeout( function(){
+ button.fadeBeginTime = $.now() + button.fadeDelay;
+ window.setTimeout( function(){
scheduleFade( button );
}, button.fadeDelay );
}
@@ -290,13 +449,13 @@ function inTo( button, newState ) {
return;
}
- if ( newState >= $.ButtonState.GROUP &&
+ if ( newState >= $.ButtonState.GROUP &&
button.currentState == $.ButtonState.REST ) {
stopFading( button );
button.currentState = $.ButtonState.GROUP;
}
- if ( newState >= $.ButtonState.HOVER &&
+ if ( newState >= $.ButtonState.HOVER &&
button.currentState == $.ButtonState.GROUP ) {
if( button.imgHover ){
button.imgHover.style.visibility = "";
@@ -304,7 +463,7 @@ function inTo( button, newState ) {
button.currentState = $.ButtonState.HOVER;
}
- if ( newState >= $.ButtonState.DOWN &&
+ if ( newState >= $.ButtonState.DOWN &&
button.currentState == $.ButtonState.HOVER ) {
if( button.imgDown ){
button.imgDown.style.visibility = "";
@@ -320,7 +479,7 @@ function outTo( button, newState ) {
return;
}
- if ( newState <= $.ButtonState.HOVER &&
+ if ( newState <= $.ButtonState.HOVER &&
button.currentState == $.ButtonState.DOWN ) {
if( button.imgDown ){
button.imgDown.style.visibility = "hidden";
@@ -328,7 +487,7 @@ function outTo( button, newState ) {
button.currentState = $.ButtonState.HOVER;
}
- if ( newState <= $.ButtonState.GROUP &&
+ if ( newState <= $.ButtonState.GROUP &&
button.currentState == $.ButtonState.HOVER ) {
if( button.imgHover ){
button.imgHover.style.visibility = "hidden";
@@ -336,7 +495,7 @@ function outTo( button, newState ) {
button.currentState = $.ButtonState.GROUP;
}
- if ( newState <= $.ButtonState.REST &&
+ if ( newState <= $.ButtonState.REST &&
button.currentState == $.ButtonState.GROUP ) {
beginFading( button );
button.currentState = $.ButtonState.REST;
diff --git a/src/buttongroup.js b/src/buttongroup.js
index 2d77d11b..b8e92119 100644
--- a/src/buttongroup.js
+++ b/src/buttongroup.js
@@ -1,41 +1,74 @@
+/*
+ * OpenSeadragon - ButtonGroup
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(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.
+ * @class ButtonGroup
+ * @classdesc Manages events on groups of buttons.
+ *
+ * @memberof OpenSeadragon
+ * @param {Object} options - A dictionary of settings applied against the entire group of buttons.
+ * @param {Array} options.buttons Array of buttons
+ * @param {Element} [options.element] Element to use as the container
**/
$.ButtonGroup = function( options ) {
$.extend( true, this, {
+ /**
+ * An array containing the buttons themselves.
+ * @member {Array} buttons
+ * @memberof OpenSeadragon.ButtonGroup#
+ */
buttons: [],
clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold,
clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold,
labelText: ""
}, options );
- // copy the botton elements
- var buttons = this.buttons.concat([]),
+ // copy the button elements TODO: Why?
+ var buttons = this.buttons.concat([]),
_this = this,
i;
- this.element = options.element || $.makeNeutralElement( "fieldgroup" );
-
+ /**
+ * The shared container for the buttons.
+ * @member {Element} element
+ * @memberof OpenSeadragon.ButtonGroup#
+ */
+ this.element = options.element || $.makeNeutralElement( "div" );
+
+ // TODO What if there IS an options.group specified?
if( !options.group ){
this.label = $.makeNeutralElement( "label" );
//TODO: support labels for ButtonGroups
@@ -47,33 +80,32 @@ $.ButtonGroup = function( options ) {
}
}
+ /**
+ * Tracks mouse/touch/key events accross the group of buttons.
+ * @member {OpenSeadragon.MouseTracker} tracker
+ * @memberof OpenSeadragon.ButtonGroup#
+ */
this.tracker = new $.MouseTracker({
- element: this.element,
- clickTimeThreshold: this.clickTimeThreshold,
+ element: this.element,
+ clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
- enterHandler: function() {
+ enterHandler: function ( event ) {
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 ) {
+ exitHandler: function ( event ) {
+ var i;
+ if ( !event.insideElementPressed ) {
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 ) {
+ releaseHandler: function ( event ) {
+ var i;
+ if ( !event.insideElementReleased ) {
for ( i = 0; i < _this.buttons.length; i++ ) {
_this.buttons[ i ].notifyGroupExit();
}
@@ -82,26 +114,26 @@ $.ButtonGroup = function( options ) {
}).setTracking( true );
};
-$.ButtonGroup.prototype = {
+$.ButtonGroup.prototype = /** @lends OpenSeadragon.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
+ * @private
*/
emulateEnter: function() {
- this.tracker.enterHandler();
+ this.tracker.enterHandler( { eventSource: this.tracker } );
},
/**
* 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
+ * @private
*/
emulateExit: function() {
- this.tracker.exitHandler();
+ this.tracker.exitHandler( { eventSource: this.tracker } );
}
};
diff --git a/src/control.js b/src/control.js
index cdb10cd1..f563ac58 100644
--- a/src/control.js
+++ b/src/control.js
@@ -1,62 +1,158 @@
+/*
+ * OpenSeadragon - Control
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(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
+ * An enumeration of supported locations where controls can be anchored.
+ * The anchoring is always relative to the container.
+ * @member ControlAnchor
+ * @memberof OpenSeadragon
* @static
+ * @type {Object}
+ * @property {Number} NONE
+ * @property {Number} TOP_LEFT
+ * @property {Number} TOP_RIGHT
+ * @property {Number} BOTTOM_LEFT
+ * @property {Number} BOTTOM_RIGHT
+ * @property {Number} ABSOLUTE
*/
$.ControlAnchor = {
NONE: 0,
TOP_LEFT: 1,
TOP_RIGHT: 2,
BOTTOM_RIGHT: 3,
- BOTTOM_LEFT: 4
+ BOTTOM_LEFT: 4,
+ ABSOLUTE: 5
};
/**
- * 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
+ * @class Control
+ * @classdesc 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
+ *
+ * @memberof OpenSeadragon
+ * @param {Element} element - the control element to be anchored in the container.
+ * @param {Object } options - All required and optional settings for configuring a control element.
+ * @param {OpenSeadragon.ControlAnchor} [options.anchor=OpenSeadragon.ControlAnchor.NONE] - 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.
+ * @param {Boolean} [options.attachToViewer=true] - Whether the control should be added directly to the viewer, or
+ * directly to the container
+ * @param {Boolean} [options.autoFade=true] - Whether the control should have the autofade behavior
+ * @param {Element} container - the element to control will be anchored too.
*/
-$.Control = function ( element, anchor, container ) {
+$.Control = function ( element, options, container ) {
+ var parent = element.parentNode;
+ if (typeof options === 'number')
+ {
+ $.console.error("Passing an anchor directly into the OpenSeadragon.Control constructor is deprecated; " +
+ "please use an options object instead. " +
+ "Support for this deprecated variant is scheduled for removal in December 2013");
+ options = {anchor: options};
+ }
+ options.attachToViewer = (typeof options.attachToViewer === 'undefined') ? true : options.attachToViewer;
+ /**
+ * True if the control should have autofade behavior.
+ * @member {Boolean} autoFade
+ * @memberof OpenSeadragon.Control#
+ */
+ this.autoFade = (typeof options.autoFade === 'undefined') ? true : options.autoFade;
+ /**
+ * The element providing the user interface with some type of control (e.g. a zoom-in button).
+ * @member {Element} element
+ * @memberof OpenSeadragon.Control#
+ */
this.element = element;
- this.anchor = anchor;
+ /**
+ * The position of the Control relative to its container.
+ * @member {OpenSeadragon.ControlAnchor} anchor
+ * @memberof OpenSeadragon.Control#
+ */
+ this.anchor = options.anchor;
+ /**
+ * The Control's containing element.
+ * @member {Element} container
+ * @memberof OpenSeadragon.Control#
+ */
this.container = container;
- this.wrapper = $.makeNeutralElement( "span" );
- this.wrapper.style.display = "inline-block";
+ /**
+ * A neutral element surrounding the control element.
+ * @member {Element} wrapper
+ * @memberof OpenSeadragon.Control#
+ */
+ if ( this.anchor == $.ControlAnchor.ABSOLUTE ) {
+ this.wrapper = $.makeNeutralElement( "div" );
+ this.wrapper.style.position = "absolute";
+ this.wrapper.style.top = typeof ( options.top ) == "number" ? ( options.top + 'px' ) : options.top;
+ this.wrapper.style.left = typeof ( options.left ) == "number" ? (options.left + 'px' ) : options.left;
+ this.wrapper.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height;
+ this.wrapper.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width;
+ this.wrapper.style.margin = "0px";
+ this.wrapper.style.padding = "0px";
+
+ this.element.style.position = "relative";
+ this.element.style.top = "0px";
+ this.element.style.left = "0px";
+ this.element.style.height = "100%";
+ this.element.style.width = "100%";
+ } else {
+ this.wrapper = $.makeNeutralElement( "div" );
+ this.wrapper.style.display = "inline-block";
+ if ( this.anchor == $.ControlAnchor.NONE ) {
+ // IE6 fix
+ this.wrapper.style.width = this.wrapper.style.height = "100%";
+ }
+ }
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
- );
+ if (options.attachToViewer ) {
+ 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 );
+ }
} else {
- this.container.appendChild( this.wrapper );
+ parent.appendChild( this.wrapper );
}
};
-$.Control.prototype = {
+$.Control.prototype = /** @lends OpenSeadragon.Control.prototype */{
/**
* Removes the control from the container.
@@ -82,8 +178,8 @@ $.Control.prototype = {
* @param {Boolean} visible - true to make visible, false to hide.
*/
setVisible: function( visible ) {
- this.wrapper.style.display = visible ?
- "inline-block" :
+ this.wrapper.style.display = visible ?
+ ( this.anchor == $.ControlAnchor.ABSOLUTE ? 'block' : 'inline-block' ) :
"none";
},
diff --git a/src/controldock.js b/src/controldock.js
index 5360463b..e7753743 100644
--- a/src/controldock.js
+++ b/src/controldock.js
@@ -1,26 +1,65 @@
-(function( $ ){
+/*
+ * OpenSeadragon - ControlDock
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
- //id hash for private properties;
- var THIS = {};
-
+(function( $ ){
/**
- * @class
+ * @class ControlDock
+ * @classdesc Provides a container element (a <form> element) with support for the layout of control elements.
+ *
+ * @memberof OpenSeadragon
*/
$.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'),
+ id: 'controldock-'+$.now()+'-'+Math.floor(Math.random()*1000000),
+ container: $.makeNeutralElement( 'div' ),
controls: []
}, options );
+ // Disable the form's submit; otherwise button clicks and return keys
+ // can trigger it.
+ this.container.onsubmit = function() {
+ return false;
+ };
+
if( this.element ){
this.element = $.getElement( this.element );
- this.element.appendChild( this.container );
- this.element.style.position = 'relative';
+ this.element.appendChild( this.container );
+ this.element.style.position = 'relative';
this.container.style.width = '100%';
this.container.style.height = '100%';
}
@@ -49,12 +88,12 @@
this.container.appendChild( this.controls.bottomleft );
};
- $.ControlDock.prototype = {
+ $.ControlDock.prototype = /** @lends OpenSeadragon.ControlDock.prototype */{
/**
* @function
*/
- addControl: function ( element, anchor ) {
+ addControl: function ( element, controlOptions ) {
element = $.getElement( element );
var div = null;
@@ -62,7 +101,7 @@
return; // they're trying to add a duplicate control
}
- switch ( anchor ) {
+ switch ( controlOptions.anchor ) {
case $.ControlAnchor.TOP_RIGHT:
div = this.controls.topright;
element.style.position = "relative";
@@ -87,6 +126,11 @@
element.style.paddingLeft = "0px";
element.style.paddingTop = "0px";
break;
+ case $.ControlAnchor.ABSOLUTE:
+ div = this.container;
+ element.style.margin = "0px";
+ element.style.padding = "0px";
+ break;
default:
case $.ControlAnchor.NONE:
div = this.container;
@@ -96,7 +140,7 @@
}
this.controls.push(
- new $.Control( element, anchor, div )
+ new $.Control( element, controlOptions, div )
);
element.style.display = "inline-block";
},
@@ -109,7 +153,7 @@
removeControl: function ( element ) {
element = $.getElement( element );
var i = getControlIndex( this, element );
-
+
if ( i >= 0 ) {
this.controls[ i ].destroy();
this.controls.splice( i, 1 );
@@ -126,7 +170,7 @@
while ( this.controls.length > 0 ) {
this.controls.pop().destroy();
}
-
+
return this;
},
@@ -137,7 +181,7 @@
*/
areControlsEnabled: function () {
var i;
-
+
for ( i = this.controls.length - 1; i >= 0; i-- ) {
if ( this.controls[ i ].isVisible() ) {
return true;
@@ -181,4 +225,4 @@
return -1;
}
-}( OpenSeadragon ));
\ No newline at end of file
+}( OpenSeadragon ));
diff --git a/src/displayrectangle.js b/src/displayrectangle.js
index fe5604be..0f401457 100644
--- a/src/displayrectangle.js
+++ b/src/displayrectangle.js
@@ -1,10 +1,46 @@
+/*
+ * OpenSeadragon - DisplayRect
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
/**
- * A display rectanlge is very similar to the OpenSeadragon.Rect but adds two
+ * @class DisplayRect
+ * @classdesc A display rectangle is very similar to {@link OpenSeadragon.Rect} but adds two
* fields, 'minLevel' and 'maxLevel' which denote the supported zoom levels
* for this rectangle.
- * @class
+ *
+ * @memberof OpenSeadragon
* @extends OpenSeadragon.Rect
* @param {Number} x The vector component 'x'.
* @param {Number} y The vector component 'y'.
@@ -12,13 +48,21 @@
* @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 ] );
+ /**
+ * The lowest zoom level supported.
+ * @member {Number} minLevel
+ * @memberof OpenSeadragon.DisplayRect#
+ */
this.minLevel = minLevel;
+ /**
+ * The highest zoom level supported.
+ * @member {Number} maxLevel
+ * @memberof OpenSeadragon.DisplayRect#
+ */
this.maxLevel = maxLevel;
};
diff --git a/src/drawer.js b/src/drawer.js
index 85bab42a..1c7ad229 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -1,5 +1,39 @@
+/*
+ * OpenSeadragon - Drawer
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
-
+
var DEVICE_SCREEN = $.getWindowSize(),
BROWSER = $.Browser.vendor,
BROWSER_VERSION = $.Browser.version,
@@ -10,71 +44,53 @@ var DEVICE_SCREEN = $.getWindowSize(),
( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) ||
( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) ||
( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 )
- ),
+ );
- USE_CANVAS = SUBPIXEL_RENDERING &&
- !( DEVICE_SCREEN.x <= 400 || DEVICE_SCREEN.y <= 400 ) &&
- !( navigator.appVersion.match( 'Mobile' ) ) &&
- $.isFunction( document.createElement( "canvas" ).getContext );
-
-//console.error( 'USE_CANVAS ' + USE_CANVAS );
/**
- * @class
+ * @class Drawer
+ * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
+ * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}).
+ *
+ * @memberof OpenSeadragon
* @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source.
* @param {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport.
- * @param {Element} element - Reference to Viewer 'canvas'.
- * @property {OpenSeadragon.TileSource} source - Reference to Viewer tile source.
- * @property {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport.
- * @property {Element} container - Reference to Viewer 'canvas'.
- * @property {Element|Canvas} canvas - TODO
- * @property {CanvasContext} context - TODO
- * @property {Object} config - Reference to Viewer config.
- * @property {Number} downloading - How many images are currently being loaded in parallel.
- * @property {Number} normHeight - Ratio of zoomable image height to width.
- * @property {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
- * @property {Array} tilesLoaded - An unordered list of Tiles with loaded images.
- * @property {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean.
- * @property {Array} overlays - An unordered list of Overlays added.
- * @property {Array} lastDrawn - An unordered list of Tiles drawn last frame.
- * @property {Number} lastResetTime - Last time for which the drawer was reset.
- * @property {Boolean} midUpdate - Is the drawer currently updating the viewport?
- * @property {Boolean} updateAgain - Does the drawer need to update the viewort again?
- * @property {Element} element - DEPRECATED Alias for container.
+ * @param {Element} element - Parent element.
*/
$.Drawer = function( options ) {
-
- //backward compatibility for positional args while prefering more
+
+ //backward compatibility for positional args while prefering more
//idiomatic javascript options object as the only argument
var args = arguments,
i;
if( !$.isPlainObject( options ) ){
options = {
- source: args[ 0 ],
- viewport: args[ 1 ],
- element: args[ 2 ]
+ source: args[ 0 ], // Reference to Viewer tile source.
+ viewport: args[ 1 ], // Reference to Viewer viewport.
+ element: args[ 2 ] // Parent element.
};
}
$.extend( true, this, {
//internal state properties
- downloading: 0,
- tilesMatrix: {},
- tilesLoaded: [],
- coverage: {},
- lastDrawn: [],
- lastResetTime: 0,
- midUpdate: false,
- updateAgain: true,
+ viewer: null,
+ downloading: 0, // How many images are currently being loaded in parallel.
+ tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
+ tilesLoaded: [], // An unordered list of Tiles with loaded images.
+ coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
+ lastDrawn: [], // An unordered list of Tiles drawn last frame.
+ lastResetTime: 0, // Last time for which the drawer was reset.
+ midUpdate: false, // Is the drawer currently updating the viewport?
+ updateAgain: true, // Does the drawer need to update the viewort again?
- //internal state / configurable settings
- overlays: [],
- collectionOverlays: {},
+ //internal state / configurable settings
+ collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer.
//configurable settings
+ opacity: $.DEFAULT_SETTINGS.opacity,
maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
imageLoaderLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
@@ -85,133 +101,116 @@ $.Drawer = function( options ) {
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
debugMode: $.DEFAULT_SETTINGS.debugMode,
- timeout: $.DEFAULT_SETTINGS.timeout
+ timeout: $.DEFAULT_SETTINGS.timeout,
+ crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy
}, options );
+ this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
+ /**
+ * The parent element of this Drawer instance, passed in when the Drawer was created.
+ * The parent of {@link OpenSeadragon.Drawer#canvas}.
+ * @member {Element} container
+ * @memberof OpenSeadragon.Drawer#
+ */
this.container = $.getElement( this.element );
- this.canvas = $.makeNeutralElement( USE_CANVAS ? "canvas" : "div" );
- this.context = USE_CANVAS ? this.canvas.getContext( "2d" ) : null;
+ /**
+ * A <canvas> element if the browser supports them, otherwise a <div> element.
+ * Child element of {@link OpenSeadragon.Drawer#container}.
+ * @member {Element} canvas
+ * @memberof OpenSeadragon.Drawer#
+ */
+ this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
+ /**
+ * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null.
+ * @member {Object} context
+ * @memberof OpenSeadragon.Drawer#
+ */
+ this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
+ // Ratio of zoomable image height to width.
this.normHeight = this.source.dimensions.y / this.source.dimensions.x;
+ /**
+ * @member {Element} element
+ * @memberof OpenSeadragon.Drawer#
+ * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
+ */
this.element = this.container;
-
+ // We force our container to ltr because our drawing math doesn't work in rtl.
+ // This issue only affects our canvas renderer, but we do it always for consistency.
+ // Note that this means overlays you want to be rtl need to be explicitly set to rtl.
+ this.container.dir = 'ltr';
+
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
this.canvas.style.position = "absolute";
-
+ $.setElementOpacity( this.canvas, this.opacity, true );
+
// explicit left-align
this.container.style.textAlign = "left";
this.container.appendChild( this.canvas );
- //create the correct type of overlay by convention if the overlays
- //are not already OpenSeadragon.Overlays
- for( i = 0; i < this.overlays.length; i++ ){
- if( $.isPlainObject( this.overlays[ i ] ) ){
-
- this.overlays[ i ] = addOverlayFromConfiguration( this, this.overlays[ i ]);
-
- } else if ( $.isFunction( this.overlays[ i ] ) ){
- //TODO
- }
- }
-
//this.profiler = new $.Profiler();
};
-$.Drawer.prototype = {
+$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
/**
* Adds an html element as an overlay to the current viewport. Useful for
* highlighting words or areas of interest on an image or other zoomable
* interface.
* @method
- * @param {Element|String} element - A reference to an element or an id for
- * the element which will overlayed.
- * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
+ * @param {Element|String|Object} element - A reference to an element or an id for
+ * the element which will overlayed. Or an Object specifying the configuration for the overlay
+ * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
- * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
- * viewport which the location coordinates will be treated as relative
- * to.
+ * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
+ * viewport which the location coordinates will be treated as relative
+ * to.
+ * @param {function} onDraw - If supplied the callback is called when the overlay
+ * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
+ * It is passed position, size and element.
+ * @fires OpenSeadragon.Viewer.event:add-overlay
+ * @deprecated - use {@link OpenSeadragon.Viewer#addOverlay} instead.
*/
- addOverlay: function( element, location, placement ) {
- element = $.getElement( element );
-
- if ( getOverlayIndex( this.overlays, element ) >= 0 ) {
- // they're trying to add a duplicate overlay
- return;
- }
-
- this.overlays.push( new $.Overlay( element, location, placement ) );
- this.updateAgain = true;
- if( this.viewer ){
- this.viewer.raiseEvent( 'add-overlay', {
- viewer: this.viewer,
- element: element,
- location: location,
- placement: placement
- });
- }
+ addOverlay: function( element, location, placement, onDraw ) {
+ $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
+ this.viewer.addOverlay( element, location, placement, onDraw );
return this;
},
/**
- * Updates the overlay represented by the reference to the element or
+ * Updates the overlay represented by the reference to the element or
* element id moving it to the new location, relative to the new placement.
* @method
- * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
+ * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
- * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
- * viewport which the location coordinates will be treated as relative
- * to.
+ * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
+ * viewport which the location coordinates will be treated as relative
+ * to.
* @return {OpenSeadragon.Drawer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:update-overlay
+ * @deprecated - use {@link OpenSeadragon.Viewer#updateOverlay} instead.
*/
updateOverlay: function( element, location, placement ) {
- var i;
-
- element = $.getElement( element );
- i = getOverlayIndex( this.overlays, element );
-
- if ( i >= 0 ) {
- this.overlays[ i ].update( location, placement );
- this.updateAgain = true;
- }
- if( this.viewer ){
- this.viewer.raiseEvent( 'update-overlay', {
- viewer: this.viewer,
- element: element,
- location: location,
- placement: placement
- });
- }
+ $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
+ this.viewer.updateOverlay( element, location, placement );
return this;
},
/**
- * Removes and overlay identified by the reference element or element id
+ * Removes and overlay identified by the reference element or element id
* and schedules and update.
* @method
- * @param {Element|String} element - A reference to the element or an
+ * @param {Element|String} element - A reference to the element or an
* element id which represent the ovelay content to be removed.
* @return {OpenSeadragon.Drawer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:remove-overlay
+ * @deprecated - use {@link OpenSeadragon.Viewer#removeOverlay} instead.
*/
removeOverlay: function( element ) {
- var i;
-
- element = $.getElement( element );
- i = getOverlayIndex( this.overlays, element );
-
- if ( i >= 0 ) {
- this.overlays[ i ].destroy();
- this.overlays.splice( i, 1 );
- this.updateAgain = true;
- }
- if( this.viewer ){
- this.viewer.raiseEvent( 'remove-overlay', {
- viewer: this.viewer,
- element: element
- });
- }
+ $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
+ this.viewer.removeOverlay( element );
return this;
},
@@ -220,27 +219,40 @@ $.Drawer.prototype = {
* and update.
* @method
* @return {OpenSeadragon.Drawer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:clear-overlay
+ * @deprecated - use {@link OpenSeadragon.Viewer#clearOverlays} instead.
*/
clearOverlays: function() {
- while ( this.overlays.length > 0 ) {
- this.overlays.pop().destroy();
- this.updateAgain = true;
- }
- if( this.viewer ){
- this.viewer.raiseEvent( 'clear-overlay', {
- viewer: this.viewer,
- element: element
- });
- }
+ $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
+ this.viewer.clearOverlays();
return this;
},
+ /**
+ * Set the opacity of the drawer.
+ * @method
+ * @param {Number} opacity
+ * @return {OpenSeadragon.Drawer} Chainable.
+ */
+ setOpacity: function( opacity ) {
+ this.opacity = opacity;
+ $.setElementOpacity( this.canvas, this.opacity, true );
+ return this;
+ },
/**
- * Returns whether the Drawer is scheduled for an update at the
+ * Get the opacity of the drawer.
+ * @method
+ * @returns {Number}
+ */
+ getOpacity: function() {
+ return this.opacity;
+ },
+ /**
+ * Returns whether the Drawer is scheduled for an update at the
* soonest possible opportunity.
* @method
- * @returns {Boolean} - Whether the Drawer is scheduled for an update at the
+ * @returns {Boolean} - Whether the Drawer is scheduled for an update at the
* soonest possible opportunity.
*/
needsUpdate: function() {
@@ -250,7 +262,7 @@ $.Drawer.prototype = {
/**
* Returns the total number of tiles that have been loaded by this Drawer.
* @method
- * @returns {Number} - The total number of tiles that have been loaded by
+ * @returns {Number} - The total number of tiles that have been loaded by
* this Drawer.
*/
numTilesLoaded: function() {
@@ -258,14 +270,14 @@ $.Drawer.prototype = {
},
/**
- * Clears all tiles and triggers an update on the next call to
+ * Clears all tiles and triggers an update on the next call to
* Drawer.prototype.update().
* @method
* @return {OpenSeadragon.Drawer} Chainable.
*/
reset: function() {
clearTiles( this );
- this.lastResetTime = +new Date();
+ this.lastResetTime = $.now();
this.updateAgain = true;
return this;
},
@@ -285,12 +297,12 @@ $.Drawer.prototype = {
},
/**
- * Used internally to load images when required. May also be used to
- * preload a set of images so the browser will have them available in
+ * Used internally to load images when required. May also be used to
+ * preload a set of images so the browser will have them available in
* the local cache to optimize user experience in certain cases. Because
* the number of parallel image loads is configurable, if too many images
- * are currently being loaded, the request will be ignored. Since by
- * default drawer.imageLoaderLimit is 0, the native browser parallel
+ * are currently being loaded, the request will be ignored. Since by
+ * default drawer.imageLoaderLimit is 0, the native browser parallel
* image loading policy will be used.
* @method
* @param {String} src - The url of the image to load.
@@ -307,14 +319,18 @@ $.Drawer.prototype = {
image,
jobid,
complete;
-
- if ( !this.imageLoaderLimit ||
+
+ if ( !this.imageLoaderLimit ||
this.downloading < this.imageLoaderLimit ) {
-
+
this.downloading++;
image = new Image();
+ if ( _this.crossOriginPolicy !== false ) {
+ image.crossOrigin = _this.crossOriginPolicy;
+ }
+
complete = function( imagesrc, resultingImage ){
_this.downloading--;
if (typeof ( callback ) == "function") {
@@ -322,7 +338,7 @@ $.Drawer.prototype = {
callback( resultingImage );
} catch ( e ) {
$.console.error(
- "%s while executing %s callback: %s",
+ "%s while executing %s callback: %s",
e.name,
src,
e.message,
@@ -349,102 +365,70 @@ $.Drawer.prototype = {
}
return loading;
+ },
+
+ /**
+ * Returns whether rotation is supported or not.
+ * @method
+ * @return {Boolean} True if rotation is supported.
+ */
+ canRotate: function() {
+ return this.useCanvas;
}
};
/**
* @private
* @inner
- */
- function addOverlayFromConfiguration( drawer, overlay ){
-
- var element = null,
- rect = ( overlay.height && overlay.width ) ? new $.Rect(
- overlay.x || overlay.px,
- overlay.y || overlay.py,
- overlay.width,
- overlay.height
- ) : new $.Point(
- overlay.x || overlay.px,
- overlay.y || overlay.py
- ),
- id = overlay.id ?
- overlay.id :
- "openseadragon-overlay-"+Math.floor(Math.random()*10000000);
-
- element = $.getElement(overlay.id);
- if( !element ){
- element = document.createElement("a");
- element.href = "#/overlay/"+id;
- }
- element.id = id;
- element.className = element.className + " " + ( overlay.className ?
- overlay.className :
- "openseadragon-overlay"
- );
-
-
- if(overlay.px !== undefined){
- //if they specified 'px' so its in pixel coordinates so
- //we need to translate to viewport coordinates
- rect = drawer.viewport.imageToViewportRectangle( rect );
- }
- if( overlay.placement ){
- return new $.Overlay(
- element,
- drawer.viewport.pointFromPixel(rect),
- $.OverlayPlacement[overlay.placement.toUpperCase()]
- );
- }else{
- return new $.Overlay( element, rect );
- }
-
-}
-
-/**
- * @private
- * @inner
- * Pretty much every other line in this needs to be documented so its clear
+ * Pretty much every other line in this needs to be documented so it's clear
* how each piece of this routine contributes to the drawing process. That's
* why there are so many TODO's inside this function.
*/
function updateViewport( drawer ) {
-
+
drawer.updateAgain = false;
if( drawer.viewer ){
- drawer.viewer.raiseEvent( 'update-viewport', {
- viewer: drawer.viewer
- });
+ /**
+ * - Needs documentation -
+ *
+ * @event update-viewport
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ drawer.viewer.raiseEvent( 'update-viewport', {} );
}
var tile,
level,
best = null,
haveDrawn = false,
- currentTime = +new Date(),
+ currentTime = $.now(),
viewportSize = drawer.viewport.getContainerSize(),
viewportBounds = drawer.viewport.getBounds( true ),
viewportTL = viewportBounds.getTopLeft(),
viewportBR = viewportBounds.getBottomRight(),
- zeroRatioC = drawer.viewport.deltaPixelsFromPoints(
- drawer.source.getPixelRatio( 0 ),
+ zeroRatioC = drawer.viewport.deltaPixelsFromPoints(
+ drawer.source.getPixelRatio( 0 ),
true
).x,
lowestLevel = Math.max(
- drawer.source.minLevel,
- Math.floor(
- Math.log( drawer.minZoomImageRatio ) /
+ drawer.source.minLevel,
+ Math.floor(
+ Math.log( drawer.minZoomImageRatio ) /
Math.log( 2 )
)
),
highestLevel = Math.min(
Math.abs(drawer.source.maxLevel),
- Math.abs(Math.floor(
- Math.log( zeroRatioC / drawer.minPixelRatio ) /
+ Math.abs(Math.floor(
+ Math.log( zeroRatioC / drawer.minPixelRatio ) /
Math.log( 2 )
))
),
+ degrees = drawer.viewport.degrees,
renderPixelRatioC,
renderPixelRatioT,
zeroRatioT,
@@ -460,7 +444,7 @@ function updateViewport( drawer ) {
//TODO
drawer.canvas.innerHTML = "";
- if ( USE_CANVAS ) {
+ if ( drawer.useCanvas ) {
if( drawer.canvas.width != viewportSize.x ||
drawer.canvas.height != viewportSize.y ){
drawer.canvas.width = viewportSize.x;
@@ -469,11 +453,18 @@ function updateViewport( drawer ) {
drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y );
}
- //TODO
- if ( !drawer.wrapHorizontal &&
+ //Change bounds for rotation
+ if (degrees === 90 || degrees === 270) {
+ var rotatedBounds = viewportBounds.rotate( degrees );
+ viewportTL = rotatedBounds.getTopLeft();
+ viewportBR = rotatedBounds.getBottomRight();
+ }
+
+ //Don't draw if completely outside of the viewport
+ if ( !drawer.wrapHorizontal &&
( viewportBR.x < 0 || viewportTL.x > 1 ) ) {
return;
- } else if
+ } else if
( !drawer.wrapVertical &&
( viewportBR.y < 0 || viewportTL.y > drawer.normHeight ) ) {
return;
@@ -493,11 +484,13 @@ function updateViewport( drawer ) {
lowestLevel = Math.min( lowestLevel, highestLevel );
//TODO
+ var drawLevel; // FIXME: drawLevel should have a more explanatory name
for ( level = highestLevel; level >= lowestLevel; level-- ) {
+ drawLevel = false;
//Avoid calculations for draw if we have already drawn this
renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints(
- drawer.source.getPixelRatio( level ),
+ drawer.source.getPixelRatio( level ),
true
).x;
@@ -509,42 +502,44 @@ function updateViewport( drawer ) {
continue;
}
+ //Perform calculations for draw if we haven't drawn this
renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints(
- drawer.source.getPixelRatio( level ),
+ drawer.source.getPixelRatio( level ),
false
).x;
- zeroRatioT = drawer.viewport.deltaPixelsFromPoints(
- drawer.source.getPixelRatio(
+ zeroRatioT = drawer.viewport.deltaPixelsFromPoints(
+ drawer.source.getPixelRatio(
Math.max(
drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1,
0
)
- ),
+ ),
false
).x;
-
- optimalRatio = drawer.immediateRender ?
- 1 :
+
+ optimalRatio = drawer.immediateRender ?
+ 1 :
zeroRatioT;
levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 );
-
- levelVisibility = optimalRatio / Math.abs(
- optimalRatio - renderPixelRatioT
+
+ levelVisibility = optimalRatio / Math.abs(
+ optimalRatio - renderPixelRatioT
);
//TODO
best = updateLevel(
- drawer,
+ drawer,
haveDrawn,
- level,
+ drawLevel,
+ level,
levelOpacity,
levelVisibility,
- viewportTL,
- viewportBR,
- currentTime,
- best
+ viewportTL,
+ viewportBR,
+ currentTime,
+ best
);
//TODO
@@ -555,20 +550,19 @@ function updateViewport( drawer ) {
//TODO
drawTiles( drawer, drawer.lastDrawn );
- drawOverlays( drawer.viewport, drawer.overlays, drawer.container );
//TODO
if ( best ) {
loadTile( drawer, best, currentTime );
// because we haven't finished drawing, so
- drawer.updateAgain = true;
+ drawer.updateAgain = true;
}
}
-function updateLevel( drawer, haveDrawn, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){
-
+function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){
+
var x, y,
tileTL,
tileBR,
@@ -577,15 +571,31 @@ function updateLevel( drawer, haveDrawn, level, levelOpacity, levelVisibility, v
if( drawer.viewer ){
- drawer.viewer.raiseEvent( 'update-level', {
- viewer: drawer.viewer,
+ /**
+ * - Needs documentation -
+ *
+ * @event update-level
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Object} havedrawn
+ * @property {Object} level
+ * @property {Object} opacity
+ * @property {Object} visibility
+ * @property {Object} topleft
+ * @property {Object} bottomright
+ * @property {Object} currenttime
+ * @property {Object} best
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ drawer.viewer.raiseEvent( 'update-level', {
havedrawn: haveDrawn,
- level: level,
+ level: level,
opacity: levelOpacity,
- visibility: levelVisibility,
- topleft: viewportTopLeft,
- bottomright: viewportBottomRight,
- currenttime: currentTime,
+ visibility: levelVisibility,
+ topleft: viewportTL,
+ bottomright: viewportBR,
+ currenttime: currentTime,
best: best
});
}
@@ -607,7 +617,7 @@ function updateLevel( drawer, haveDrawn, level, levelOpacity, levelVisibility, v
for ( x = tileTL.x; x <= tileBR.x; x++ ) {
for ( y = tileTL.y; y <= tileBR.y; y++ ) {
- best = updateTile(
+ best = updateTile(
drawer,
drawLevel,
haveDrawn,
@@ -628,22 +638,30 @@ function updateLevel( drawer, haveDrawn, level, levelOpacity, levelVisibility, v
}
function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){
-
- var tile = getTile(
- x, y,
- level,
+
+ var tile = getTile(
+ x, y,
+ level,
drawer.source,
drawer.tilesMatrix,
- currentTime,
- numberOfTiles,
- drawer.normHeight
+ currentTime,
+ numberOfTiles,
+ drawer.normHeight
),
- drawTile = drawLevel,
- newbest;
+ drawTile = drawLevel;
if( drawer.viewer ){
- drawer.viewer.raiseEvent( 'update-tile', {
- viewer: drawer.viewer,
+ /**
+ * - Needs documentation -
+ *
+ * @event update-tile
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {OpenSeadragon.Tile} tile
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ drawer.viewer.raiseEvent( 'update-tile', {
tile: tile
});
}
@@ -666,26 +684,29 @@ function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, le
return best;
}
- positionTile(
- tile,
+ positionTile(
+ tile,
drawer.source.tileOverlap,
drawer.viewport,
- viewportCenter,
- levelVisibility
+ viewportCenter,
+ levelVisibility
);
if ( tile.loaded ) {
-
- drawer.updateAgain = blendTile(
+ var needsUpdate = blendTile(
drawer,
- tile,
+ tile,
x, y,
level,
- levelOpacity,
- currentTime
+ levelOpacity,
+ currentTime
);
+
+ if ( needsUpdate ) {
+ drawer.updateAgain = true;
+ }
} else if ( tile.loading ) {
- // the tile is already in the download queue
+ // the tile is already in the download queue
// thanks josh1093 for finally translating this typo
} else {
best = compareTiles( best, tile );
@@ -720,11 +741,11 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeig
bounds.y += normHeight * ( y - yMod ) / numTiles.y;
tilesMatrix[ level ][ x ][ y ] = new $.Tile(
- level,
- x,
- y,
- bounds,
- exists,
+ level,
+ x,
+ y,
+ bounds,
+ exists,
url
);
}
@@ -806,7 +827,7 @@ function onTileLoad( drawer, tile, time, image ) {
prevLevel = prevTile.level;
worstLevel = worstTile.level;
- if ( prevTime < worstTime ||
+ if ( prevTime < worstTime ||
( prevTime == worstTime && prevLevel > worstLevel ) ) {
worstTile = prevTile;
worstTileIndex = i;
@@ -856,7 +877,7 @@ function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){
deltaTime = currentTime - tile.blendStart;
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
-
+
if ( drawer.alwaysBlend ) {
opacity *= levelOpacity;
}
@@ -886,7 +907,7 @@ function clearTiles( drawer ) {
* Returns true if the given tile provides coverage to lower-level tiles of
* lower resolution representing the same content. If neither x nor y is
* given, returns true if the entire visible level provides coverage.
- *
+ *
* Note that out-of-bounds tiles provide coverage in this sense, since
* there's no content that they would need to cover. Tiles at non-existent
* levels that are within the image bounds, however, do not.
@@ -951,7 +972,7 @@ function isCovered( coverage, level, x, y ) {
function setCoverage( coverage, level, x, y, covers ) {
if ( !coverage[ level ] ) {
$.console.warn(
- "Setting coverage for a tile before its level's coverage has been reset: %s",
+ "Setting coverage for a tile before its level's coverage has been reset: %s",
level
);
return;
@@ -969,7 +990,7 @@ function setCoverage( coverage, level, x, y, covers ) {
* @inner
* Resets coverage information for the given level. This should be called
* after every draw routine. Note that at the beginning of the next draw
- * routine, coverage for every visible tile should be explicitly set.
+ * routine, coverage for every visible tile should be explicitly set.
*/
function resetCoverage( coverage, level ) {
coverage[ level ] = {};
@@ -978,24 +999,7 @@ function resetCoverage( coverage, level ) {
/**
* @private
* @inner
- * Determines the 'z-index' of the given overlay. Overlays are ordered in
- * a z-index based on the order they are added to the Drawer.
- */
-function getOverlayIndex( overlays, element ) {
- var i;
- for ( i = overlays.length - 1; i >= 0; i-- ) {
- if ( overlays[ i ].element == element ) {
- return i;
- }
- }
-
- return -1;
-}
-
-/**
- * @private
- * @inner
- * Determines whether the 'last best' tile for the area is better than the
+ * Determines whether the 'last best' tile for the area is better than the
* tile in question.
*/
function compareTiles( previousBest, tile ) {
@@ -1029,30 +1033,8 @@ function finishLoadingImage( image, callback, successful, jobid ){
}
-
-function drawOverlays( viewport, overlays, container ){
- var i,
- length = overlays.length;
- for ( i = 0; i < length; i++ ) {
- drawOverlay( viewport, overlays[ i ], container );
- }
-}
-
-function drawOverlay( viewport, overlay, container ){
-
- overlay.position = viewport.pixelFromPoint(
- overlay.bounds.getTopLeft(),
- true
- );
- overlay.size = viewport.deltaPixelsFromPoints(
- overlay.bounds.getSize(),
- true
- );
- overlay.drawHTML( container );
-}
-
function drawTiles( drawer, lastDrawn ){
- var i,
+ var i,
tile,
tileKey,
viewer,
@@ -1061,30 +1043,52 @@ function drawTiles( drawer, lastDrawn ){
tileSource,
collectionTileSource;
+ // We need a callback to give image manipulation a chance to happen
+ var drawingHandler = function(args) {
+ if (drawer.viewer) {
+ /**
+ * This event is fired just before the tile is drawn giving the application a chance to alter the image.
+ *
+ * NOTE: This event is only fired when the drawer is using a
- *
- * Work done by Chris Thatcher adds an 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.
- * ---------------------------------------------------------------------------
- *
- *
- **/
+ *
+ */
- /**
- * The root namespace for OpenSeadragon, this function also serves as a single
- * point of instantiation for an {@link OpenSeadragon.Viewer}, including all
- * combinations of out-of-the-box configurable features. All utility methods
- * and classes are defined on or below this namespace.
+/**
+ * @module OpenSeadragon
+ *
+ */
+
+/**
+ * @namespace OpenSeadragon
+ *
+ * @classdesc The root namespace for OpenSeadragon. All utility methods
+ * and classes are defined on or below this namespace.
+ *
+ */
+
+
+// Typedefs
+
+ /**
+ * All required and optional settings for instantiating a new instance of an OpenSeadragon image viewer.
*
- * @namespace
- * @function
- * @name OpenSeadragon
- * @exports $ as OpenSeadragon
+ * @typedef {Object} Options
+ * @memberof OpenSeadragon
*
- * @param {Object} options All required and optional settings for instantiating
- * a new instance of an OpenSeadragon image viewer.
+ * @property {String} id
+ * Id of the element to append the viewer's container element to. If not provided, the 'element' property must be provided.
+ * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
*
- * @param {String} options.xmlPath
- * DEPRECATED. A relative path to load a DZI file from the server.
- * Prefer the newer options.tileSources.
+ * @property {Element} element
+ * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
+ * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
*
- * @param {Array|String|Function|Object[]|Array[]|String[]|Function[]} options.tileSources
- * As an Array, the tileSource can hold either be all Objects or mixed
- * types of Arrays of Objects, String, Function. When a value is a String,
- * the tileSource is used to create a {@link OpenSeadragon.DziTileSource}.
- * When a value is a Function, the function is used to create a new
- * {@link OpenSeadragon.TileSource} whose abstract method
- * getUrl( level, x, y ) is implemented by the function. Finally, when it
- * is an Array of objects, it is used to create a
+ * @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null]
+ * As an Array, the tileSource can hold either Objects or mixed
+ * types of Arrays of Objects, Strings, or Functions. When a value is a String,
+ * the tileSource is used to create a {@link OpenSeadragon.DziTileSource}.
+ * When a value is a Function, the function is used to create a new
+ * {@link OpenSeadragon.TileSource} whose abstract method
+ * getUrl( level, x, y ) is implemented by the function. Finally, when it
+ * is an Array of objects, it is used to create a
* {@link OpenSeadragon.LegacyTileSource}.
*
- * @param {Boolean} [options.debugMode=true]
- * Currently does nothing. TODO: provide an in-screen panel providing event
- * detail feedback.
+ * @property {Array} overlays Array of objects defining permanent overlays of
+ * the viewer. The overlays added via this option and later removed with
+ * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new
+ * image is opened.
+ * To add overlays which can be definitively removed, one must use
+ * {@link OpenSeadragon.Viewer#addOverlay}
+ * If displaying a sequence of images, the overlays can be associated
+ * with a specific page by passing the overlays array to the page's
+ * tile source configuration.
+ * Expected properties:
+ * * x, y, (or px, py for pixel coordinates) to define the location.
+ * * width, height in point if using x,y or in pixels if using px,py. If width
+ * and height are specified, the overlay size is adjusted when zooming,
+ * otherwise the size stays the size of the content (or the size defined by CSS).
+ * * className to associate a class to the overlay
+ * * id to set the overlay element. If an element with this id already exists,
+ * it is reused, otherwise it is created. If not specified, a new element is
+ * created.
+ * * placement a string to define the relative position to the viewport.
+ * Only used if no width and height are specified. Default: 'TOP_LEFT'.
+ * See {@link OpenSeadragon.OverlayPlacement} for possible values.
*
- * @param {Number} [options.animationTime=1.5]
+ * @property {String} [xmlPath=null]
+ * DEPRECATED. A relative path to load a DZI file from the server.
+ * Prefer the newer Options.tileSources.
+ *
+ * @property {String} [prefixUrl='/images/']
+ * Prepends the prefixUrl to navImages paths, which is very useful
+ * since the default paths are rarely useful for production
+ * environments.
+ *
+ * @property {OpenSeadragon.NavImages} [navImages]
+ * An object with a property for each button or other built-in navigation
+ * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.
+ * Each of those in turn provides an image path for each state of the button
+ * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the
+ * image paths, by default assume there is a folder on the servers root path
+ * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust
+ * these paths, prefer setting the option.prefixUrl rather than overriding
+ * every image path directly through this setting.
+ *
+ * @property {Object} [tileHost=null]
+ * TODO: Implement this. Currently not used.
+ *
+ * @property {Boolean} [debugMode=false]
+ * TODO: provide an in-screen panel providing event detail feedback.
+ *
+ * @property {String} [debugGridColor='#437AB2']
+ *
+ * @property {Number} [animationTime=1.2]
* Specifies the animation duration per each {@link OpenSeadragon.Spring}
* which occur when the image is dragged or zoomed.
*
- * @param {Number} [options.blendTime=0.5]
+ * @property {Number} [blendTime=0]
* Specifies the duration of animation as higher or lower level tiles are
* replacing the existing tile.
*
- * @param {Boolean} [options.alwaysBlend=false]
+ * @property {Boolean} [alwaysBlend=false]
* Forces the tile to always blend. By default the tiles skip blending
* when the blendTime is surpassed and the current animation frame would
* not complete the blend.
*
- * @param {Boolean} [options.autoHideControls=true]
- * If the user stops interacting with the viewport, fade the navigation
+ * @property {Boolean} [autoHideControls=true]
+ * If the user stops interacting with the viewport, fade the navigation
* controls. Useful for presentation since the controls are by default
* floated on top of the image the user is viewing.
*
- * @param {Boolean} [options.immediateRender=false]
+ * @property {Boolean} [immediateRender=false]
* Render the best closest level first, ignoring the lowering levels which
- * provide the effect of very blurry to sharp. It is recommended to change
+ * provide the effect of very blurry to sharp. It is recommended to change
* setting to true for mobile devices.
*
- * @param {Boolean} [options.wrapHorizontal=false]
+ * @property {Number} [defaultZoomLevel=0]
+ * Zoom level to use when image is first opened or the home button is clicked.
+ * If 0, adjusts to fit viewer.
+ *
+ * @property {Number} [opacity=1]
+ * Opacity of the drawer (1=opaque, 0=transparent)
+ *
+ * @property {Number} [layersAspectRatioEpsilon=0.0001]
+ * Maximum aspectRatio mismatch between 2 layers.
+ *
+ * @property {Number} [degrees=0]
+ * Initial rotation.
+ *
+ * @property {Number} [minZoomLevel=null]
+ *
+ * @property {Number} [maxZoomLevel=null]
+ *
+ * @property {Boolean} [panHorizontal=true]
+ * Allow horizontal pan.
+ *
+ * @property {Boolean} [panVertical=true]
+ * Allow vertical pan.
+ *
+ * @property {Boolean} [constrainDuringPan=false]
+ *
+ * @property {Boolean} [wrapHorizontal=false]
* Set to true to force the image to wrap horizontally within the viewport.
* Useful for maps or images representing the surface of a sphere or cylinder.
*
- * @param {Boolean} [options.wrapVertical=false]
+ * @property {Boolean} [wrapVertical=false]
* Set to true to force the image to wrap vertically within the viewport.
* Useful for maps or images representing the surface of a sphere or cylinder.
*
- * @param {Number} [options.minZoomImageRatio=0.8]
- * The minimum percentage ( expressed as a number between 0 and 1 ) of
+ * @property {Number} [minZoomImageRatio=0.9]
+ * The minimum percentage ( expressed as a number between 0 and 1 ) of
* the viewport height or width at which the zoom out will be constrained.
* Setting it to 0, for example will allow you to zoom out infinitly.
*
- * @param {Number} [options.maxZoomPixelRatio=2]
+ * @property {Number} [maxZoomPixelRatio=1.1]
* The maximum ratio to allow a zoom-in to affect the highest level pixel
* ratio. This can be set to Infinity to allow 'infinite' zooming into the
- * image though it is less effective visually if the HTML5 Canvas is not
+ * image though it is less effective visually if the HTML5 Canvas is not
* availble on the viewing device.
*
- * @param {Number} [options.visibilityRatio=0.5]
+ * @property {Boolean} [autoResize=true]
+ * Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
+ *
+ * @property {Number} [pixelsPerWheelLine=40]
+ * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
+ *
+ * @property {Number} [visibilityRatio=0.5]
* The percentage ( as a number from 0 to 1 ) of the source image which
* must be kept within the viewport. If the image is dragged beyond that
- * limit, it will 'bounce' back until the minimum visibility ration is
+ * limit, it will 'bounce' back until the minimum visibility ration is
* achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
* true will provide the effect of an infinitely scrolling viewport.
*
- * @param {Number} [options.springStiffness=5.0]
+ * @property {Number} [springStiffness=7.0]
*
- * @param {Number} [options.imageLoaderLimit=0]
+ * @property {Number} [imageLoaderLimit=0]
* The maximum number of image requests to make concurrently. By default
* it is set to 0 allowing the browser to make the maximum number of
* image requests in parallel as allowed by the browsers policy.
*
- * @param {Number} [options.clickTimeThreshold=200]
- * If multiple mouse clicks occurs within less than this number of
+ * @property {Number} [clickTimeThreshold=300]
+ * If multiple mouse clicks occurs within less than this number of
* milliseconds, treat them as a single click.
*
- * @param {Number} [options.clickDistThreshold=5]
+ * @property {Number} [clickDistThreshold=5]
* If a mouse or touch drag occurs and the distance to the starting drag
* point is less than this many pixels, ignore the drag event.
*
- * @param {Number} [options.zoomPerClick=2.0]
- * The "zoom distance" per mouse click or touch tap.
+ * @property {Number} [zoomPerClick=2.0]
+ * The "zoom distance" per mouse click or touch tap. Note: Setting this to 1.0 effectively disables the click-to-zoom feature.
*
- * @param {Number} [options.zoomPerScroll=1.2]
- * The "zoom distance" per mouse scroll or touch pinch.
+ * @property {Number} [zoomPerScroll=1.2]
+ * The "zoom distance" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature.
*
- * @param {Number} [options.zoomPerSecond=2.0]
+ * @property {Number} [zoomPerSecond=1.0]
* The number of seconds to animate a single zoom event over.
*
- * @param {Boolean} [options.showNavigationControl=true]
- * Set to false to prevent the appearance of the default navigation controls.
+ * @property {Boolean} [showNavigator=false]
+ * Set to true to make the navigator minimap appear.
*
- * @param {Number} [options.controlsFadeDelay=2000]
+ * @property {Boolean} [navigatorId=navigator-GENERATED DATE]
+ * The ID of a div to hold the navigator minimap.
+ * If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored.
+ * If an ID is not specified, a div element will be generated and placed on top of the main image.
+ *
+ * @property {String} [navigatorPosition='TOP_RIGHT']
+ * Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.
+ * If 'ABSOLUTE' is specified, then navigatorTop|Left|Height|Width determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
+ * For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigatorHeight|Width values determine the size of the navigator minimap.
+ *
+ * @property {Number} [navigatorSizeRatio=0.2]
+ * Ratio of navigator size to viewer size. Ignored if navigatorHeight|Width are specified.
+ *
+ * @property {Boolean} [navigatorMaintainSizeRatio=false]
+ * If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes.
+ *
+ * @property {Number|String} [navigatorTop=null]
+ * Specifies the location of the navigator minimap (see navigatorPosition).
+ *
+ * @property {Number|String} [navigatorLeft=null]
+ * Specifies the location of the navigator minimap (see navigatorPosition).
+ *
+ * @property {Number|String} [navigatorHeight=null]
+ * Specifies the size of the navigator minimap (see navigatorPosition).
+ * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
+ *
+ * @property {Number|String} [navigatorWidth=null]
+ * Specifies the size of the navigator minimap (see navigatorPosition).
+ * If specified, navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.
+ *
+ * @property {Boolean} [navigatorAutoResize=true]
+ * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
+ * Setting to false can also improve performance when the navigator is configured to a fixed size.
+ *
+ * @property {Number} [controlsFadeDelay=2000]
* The number of milliseconds to wait once the user has stopped interacting
* with the interface before begining to fade the controls. Assumes
* showNavigationControl and autoHideControls are both true.
*
- * @param {Number} [options.controlsFadeLength=1500]
+ * @property {Number} [controlsFadeLength=1500]
* The number of milliseconds to animate the controls fading out.
*
- * @param {Number} [options.maxImageCacheCount=100]
+ * @property {Number} [maxImageCacheCount=200]
* The max number of images we should keep in memory (per drawer).
*
- * @param {Number} [options.minPixelRatio=0.5]
+ * @property {Number} [timeout=30000]
+ *
+ * @property {Boolean} [useCanvas=true]
+ * Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
+ *
+ * @property {Number} [minPixelRatio=0.5]
* The higher the minPixelRatio, the lower the quality of the image that
* is considered sufficient to stop rendering a given zoom level. For
- * example, if you are targeting mobile devices with less bandwith you may
+ * example, if you are targeting mobile devices with less bandwith you may
* try setting this to 1.5 or higher.
*
- * @param {Boolean} [options.mouseNavEnabled=true]
- * Is the user able to interact with the image via mouse or touch. Default
+ * @property {Boolean} [mouseNavEnabled=true]
+ * Is the user able to interact with the image via mouse or touch. Default
* interactions include draging the image in a plane, and zooming in toward
* and away from the image.
*
- * @param {Boolean} [options.preserveViewport=false]
+ * @property {Boolean} [showNavigationControl=true]
+ * Set to false to prevent the appearance of the default navigation controls.
+ * Note that if set to false, the customs buttons set by the options
+ * zoomInButton, zoomOutButton etc, are rendered inactive.
+ *
+ * @property {OpenSeadragon.ControlAnchor} [navigationControlAnchor=TOP_LEFT]
+ * Placement of the default navigation controls.
+ * To set the placement of the sequence controls, see the
+ * sequenceControlAnchor option.
+ *
+ * @property {Boolean} [showZoomControl=true]
+ * If true then + and - buttons to zoom in and out are displayed.
+ * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
+ * this setting when set to false.
+ *
+ * @property {Boolean} [showHomeControl=true]
+ * If true then the 'Go home' button is displayed to go back to the original
+ * zoom and pan.
+ * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
+ * this setting when set to false.
+ *
+ * @property {Boolean} [showFullPageControl=true]
+ * If true then the 'Toggle full page' button is displayed to switch
+ * between full page and normal mode.
+ * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
+ * this setting when set to false.
+ *
+ * @property {Boolean} [showRotationControl=false]
+ * If true then the rotate left/right controls will be displayed as part of the
+ * standard controls. This is also subject to the browser support for rotate
+ * (e.g. viewer.drawer.canRotate()).
+ * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
+ * this setting when set to false.
+ *
+ * @property {Boolean} [showSequenceControl=true]
+ * If the viewer has been configured with a sequence of tile sources, then
+ * provide buttons for navigating forward and backward through the images.
+ *
+ * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT]
+ * Placement of the default sequence controls.
+ *
+ * @property {Boolean} [navPrevNextWrap=false]
+ * If true then the 'previous' button will wrap to the last image when
+ * viewing the first image and the 'next' button will wrap to the first
+ * image when viewing the last image.
+ *
+ * @property {String} zoomInButton
+ * Set the id of the custom 'Zoom in' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} zoomOutButton
+ * Set the id of the custom 'Zoom out' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} homeButton
+ * Set the id of the custom 'Go home' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} fullPageButton
+ * Set the id of the custom 'Toggle full page' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} rotateLeftButton
+ * Set the id of the custom 'Rotate left' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} rotateRightButton
+ * Set the id of the custom 'Rotate right' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} previousButton
+ * Set the id of the custom 'Previous page' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {String} nextButton
+ * Set the id of the custom 'Next page' button to use.
+ * This is useful to have a custom button anywhere in the web page.
+ * To only change the button images, consider using
+ * {@link OpenSeadragon.Options.navImages}
+ *
+ * @property {Number} [initialPage=0]
+ * If the viewer has been configured with a sequence of tile sources, display this page initially.
+ *
+ * @property {Boolean} [preserveViewport=false]
* If the viewer has been configured with a sequence of tile sources, then
* normally navigating to through each image resets the viewport to 'home'
* position. If preserveViewport is set to true, then the viewport position
* is preserved when navigating between images in the sequence.
*
- * @param {String} [options.prefixUrl='/images/']
- * Prepends the prefixUrl to navImages paths, which is very useful
- * since the default paths are rarely useful for production
- * environments.
+ * @property {Boolean} [showReferenceStrip=false]
+ * If the viewer has been configured with a sequence of tile sources, then
+ * display a scrolling strip of image thumbnails for navigating through the images.
*
- * @param {Object} [options.navImages=]
- * An object with a property for each button or other built-in navigation
- * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'.
- * Each of those in turn provides an image path for each state of the botton
- * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the
- * image paths, by default assume there is a folder on the servers root path
- * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust
- * these paths, prefer setting the option.prefixUrl rather than overriding
- * every image path directly through this setting.
+ * @property {String} [referenceStripScroll='horizontal']
*
+ * @property {Element} [referenceStripElement=null]
+ *
+ * @property {Number} [referenceStripHeight=null]
+ *
+ * @property {Number} [referenceStripWidth=null]
+ *
+ * @property {String} [referenceStripPosition='BOTTOM_LEFT']
+ *
+ * @property {Number} [referenceStripSizeRatio=0.2]
+ *
+ * @property {Boolean} [collectionMode=false]
+ *
+ * @property {Number} [collectionRows=3]
+ *
+ * @property {String} [collectionLayout='horizontal']
+ *
+ * @property {Number} [collectionTileSize=800]
+ *
+ * @property {String|Boolean} [crossOriginPolicy=false]
+ * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
+ * not use CORS, and the canvas will be tainted.
+ *
+ */
+
+/**
+ * The names for the image resources used for the image navigation buttons.
+ *
+ * @typedef {Object} NavImages
+ * @memberof OpenSeadragon
+ *
+ * @property {Object} zoomIn - Images for the zoom-in button.
+ * @property {String} zoomIn.REST
+ * @property {String} zoomIn.GROUP
+ * @property {String} zoomIn.HOVER
+ * @property {String} zoomIn.DOWN
+ *
+ * @property {Object} zoomOut - Images for the zoom-out button.
+ * @property {String} zoomOut.REST
+ * @property {String} zoomOut.GROUP
+ * @property {String} zoomOut.HOVER
+ * @property {String} zoomOut.DOWN
+ *
+ * @property {Object} home - Images for the home button.
+ * @property {String} home.REST
+ * @property {String} home.GROUP
+ * @property {String} home.HOVER
+ * @property {String} home.DOWN
+ *
+ * @property {Object} fullpage - Images for the full-page button.
+ * @property {String} fullpage.REST
+ * @property {String} fullpage.GROUP
+ * @property {String} fullpage.HOVER
+ * @property {String} fullpage.DOWN
+ *
+ * @property {Object} rotateleft - Images for the rotate left button.
+ * @property {String} rotateleft.REST
+ * @property {String} rotateleft.GROUP
+ * @property {String} rotateleft.HOVER
+ * @property {String} rotateleft.DOWN
+ *
+ * @property {Object} rotateright - Images for the rotate right button.
+ * @property {String} rotateright.REST
+ * @property {String} rotateright.GROUP
+ * @property {String} rotateright.HOVER
+ * @property {String} rotateright.DOWN
+ *
+ * @property {Object} previous - Images for the previous button.
+ * @property {String} previous.REST
+ * @property {String} previous.GROUP
+ * @property {String} previous.HOVER
+ * @property {String} previous.DOWN
+ *
+ * @property {Object} next - Images for the next button.
+ * @property {String} next.REST
+ * @property {String} next.GROUP
+ * @property {String} next.HOVER
+ * @property {String} next.DOWN
+ *
+ */
+
+
+ /**
+ * This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all
+ * combinations of out-of-the-box configurable features.
+ *
+ * @function OpenSeadragon
+ * @memberof module:OpenSeadragon
+ * @param {OpenSeadragon.Options} options - Viewer options.
* @returns {OpenSeadragon.Viewer}
*/
window.OpenSeadragon = window.OpenSeadragon || function( options ){
-
+
return new OpenSeadragon.Viewer( options );
};
+
(function( $ ){
-
+
+
+ /**
+ * The OpenSeadragon version.
+ *
+ * @member {Object} OpenSeadragon.version
+ * @property {String} versionStr - The version number as a string ('major.minor.revision').
+ * @property {Number} major - The major version number.
+ * @property {Number} minor - The minor version number.
+ * @property {Number} revision - The revision number.
+ * @since 1.0.0
+ */
+ /* jshint ignore:start */
+ $.version = {
+ versionStr: '<%= osdVersion.versionStr %>',
+ major: <%= osdVersion.major %>,
+ minor: <%= osdVersion.minor %>,
+ revision: <%= osdVersion.revision %>
+ };
+ /* jshint ignore:end */
+
/**
* Taken from jquery 1.6.1
@@ -254,29 +588,24 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
* @private
*/
var class2type = {
- '[object Boolean]': 'boolean',
- '[object Number]': 'number',
- '[object String]': 'string',
- '[object Function]': 'function',
- '[object Array]': 'array',
- '[object Date]': 'date',
- '[object RegExp]': 'regexp',
- '[object Object]': 'object'
- },
- // Save a reference to some core methods
- toString = Object.prototype.toString,
- hasOwn = Object.prototype.hasOwnProperty,
- push = Array.prototype.push,
- slice = Array.prototype.slice,
- trim = String.prototype.trim,
- indexOf = Array.prototype.indexOf;
-
+ '[object Boolean]': 'boolean',
+ '[object Number]': 'number',
+ '[object String]': 'string',
+ '[object Function]': 'function',
+ '[object Array]': 'array',
+ '[object Date]': 'date',
+ '[object RegExp]': 'regexp',
+ '[object Object]': 'object'
+ },
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty;
/**
* Taken from jQuery 1.6.1
- * @name $.isFunction
- * @function
- * @see jQuery
+ * @function isFunction
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.isFunction = function( obj ) {
return $.type(obj) === "function";
@@ -285,9 +614,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Taken from jQuery 1.6.1
- * @name $.isArray
- * @function
- * @see jQuery
+ * @function isArray
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.isArray = Array.isArray || function( obj ) {
return $.type(obj) === "array";
@@ -297,9 +626,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* A crude way of determining if an object is a window.
* Taken from jQuery 1.6.1
- * @name $.isWindow
- * @function
- * @see jQuery
+ * @function isWindow
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.isWindow = function( obj ) {
return obj && typeof obj === "object" && "setInterval" in obj;
@@ -308,9 +637,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Taken from jQuery 1.6.1
- * @name $.type
- * @function
- * @see jQuery
+ * @function type
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.type = function( obj ) {
return ( obj === null ) || ( obj === undefined ) ?
@@ -321,9 +650,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Taken from jQuery 1.6.1
- * @name $.isPlainObject
- * @function
- * @see jQuery
+ * @function isPlainObject
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.isPlainObject = function( obj ) {
// Must be an Object.
@@ -352,9 +681,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Taken from jQuery 1.6.1
- * @name $.isEmptyObject
- * @function
- * @see jQuery
+ * @function isEmptyObject
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.isEmptyObject = function( obj ) {
for ( var name in obj ) {
@@ -364,32 +693,46 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
};
+ /**
+ * True if the browser supports the HTML5 canvas element
+ * @member {Boolean} supportsCanvas
+ * @memberof OpenSeadragon
+ */
+ $.supportsCanvas = (function () {
+ var canvasElement = document.createElement( 'canvas' );
+ return !!( $.isFunction( canvasElement.getContext ) &&
+ canvasElement.getContext( '2d' ) );
+ }());
+
+
}( OpenSeadragon ));
/**
* This closure defines all static methods available to the OpenSeadragon
* namespace. Many, if not most, are taked directly from jQuery for use
- * to simplify and reduce common programming patterns. More static methods
+ * to simplify and reduce common programming patterns. More static methods
* from jQuery may eventually make their way into this though we are
- * attempting to avoid substaintial plagarism or the more explicit dependency
- * on jQuery only because OpenSeadragon is a broadly useful code base and
- * would be made less broad by requiring jQuery fully.
+ * attempting to avoid an explicit dependency on jQuery only because
+ * OpenSeadragon is a broadly useful code base and would be made less broad
+ * by requiring jQuery fully.
*
- * Some static methods have also been refactored from the original OpenSeadragon
+ * Some static methods have also been refactored from the original OpenSeadragon
* project.
*/
(function( $ ){
/**
* Taken from jQuery 1.6.1
- * @see jQuery
+ * @function extend
+ * @memberof OpenSeadragon
+ * @see {@link http://www.jquery.com/ jQuery}
*/
$.extend = function() {
- var options,
- name,
- src,
- copy,
- copyIsArray,
+ var options,
+ name,
+ src,
+ copy,
+ copyIsArray,
clone,
target = arguments[ 0 ] || {},
length = arguments.length,
@@ -453,21 +796,22 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// Return the modified object
return target;
};
-
- $.extend( $, {
+
+ $.extend( $, /** @lends OpenSeadragon */{
/**
- * These are the default values for the optional settings documented
- * in the {@link OpenSeadragon} constructor detail.
- * @name $.DEFAULT_SETTINGS
+ * The default values for the optional settings documented at {@link OpenSeadragon.Options}.
* @static
+ * @type {Object}
*/
DEFAULT_SETTINGS: {
//DATA SOURCE DETAILS
xmlPath: null,
- tileSources: null,
+ tileSources: null,
tileHost: null,
-
+ initialPage: 0,
+ crossOriginPolicy: false,
+
//PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true,
panVertical: true,
@@ -475,10 +819,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
wrapHorizontal: false,
wrapVertical: false,
visibilityRatio: 0.5, //-> how much of the viewer can be negative space
- minPixelRatio: 1, //->closer to 0 draws tiles meant for a higher zoom at this zoom
+ minPixelRatio: 0.5, //->closer to 0 draws tiles meant for a higher zoom at this zoom
defaultZoomLevel: 0,
minZoomLevel: null,
- maxZoomLevel: null,
+ maxZoomLevel: null,
//UI RESPONSIVENESS AND FEEL
springStiffness: 7.0,
@@ -494,25 +838,47 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
immediateRender: false,
minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
+ pixelsPerWheelLine: 40,
+ autoResize: true,
//DEFAULT CONTROL SETTINGS
- showSequenceControl: true, //SEQUENCE
- preserveViewport: false, //SEQUENCE
- showNavigationControl: true, //ZOOM/HOME/FULL/SEQUENCE
- controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
- controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
- mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
-
+ showSequenceControl: true, //SEQUENCE
+ sequenceControlAnchor: null, //SEQUENCE
+ preserveViewport: false, //SEQUENCE
+ navPrevNextWrap: false, //SEQUENCE
+ showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION
+ navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION
+ showZoomControl: true, //ZOOM
+ showHomeControl: true, //HOME
+ showFullPageControl: true, //FULL
+ showRotationControl: false, //ROTATION
+ controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
+ controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
+ mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
+
//VIEWPORT NAVIGATOR SETTINGS
- showNavigator: true, //promoted to default in 0.9.64
- navigatorElement: null,
- navigatorHeight: null,
- navigatorWidth: null,
- navigatorPosition: null,
- navigatorSizeRatio: 0.2,
+ showNavigator: false,
+ navigatorId: null,
+ navigatorPosition: null,
+ navigatorSizeRatio: 0.2,
+ navigatorMaintainSizeRatio: false,
+ navigatorTop: null,
+ navigatorLeft: null,
+ navigatorHeight: null,
+ navigatorWidth: null,
+ navigatorAutoResize: true,
+
+ // INITIAL ROTATION
+ degrees: 0,
+
+ // APPEARANCE
+ opacity: 1,
+
+ // LAYERS SETTINGS
+ layersAspectRatioEpsilon: 0.0001,
//REFERENCE STRIP SETTINGS
- showReferenceStrip: false,
+ showReferenceStrip: false,
referenceStripScroll: 'horizontal',
referenceStripElement: null,
referenceStripHeight: null,
@@ -526,13 +892,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
collectionMode: false,
collectionTileSize: 800,
- //EVENT RELATED CALLBACKS
- onPageChange: null,
-
//PERFORMANCE SETTINGS
imageLoaderLimit: 0,
maxImageCacheCount: 200,
timeout: 30000,
+ useCanvas: true, // Use canvas element for drawing if available
//INTERFACE RESOURCE SETTINGS
prefixUrl: "/images/",
@@ -561,6 +925,18 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
HOVER: 'fullpage_hover.png',
DOWN: 'fullpage_pressed.png'
},
+ rotateleft: {
+ REST: 'rotateleft_rest.png',
+ GROUP: 'rotateleft_grouphover.png',
+ HOVER: 'rotateleft_hover.png',
+ DOWN: 'rotateleft_pressed.png'
+ },
+ rotateright: {
+ REST: 'rotateright_rest.png',
+ GROUP: 'rotateright_grouphover.png',
+ HOVER: 'rotateright_hover.png',
+ DOWN: 'rotateright_pressed.png'
+ },
previous: {
REST: 'previous_rest.png',
GROUP: 'previous_grouphover.png',
@@ -591,11 +967,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
- * Invokes the the method as if it where a method belonging to the object.
- * @name $.delegate
+ * Returns a function which invokes the method as if it were a method belonging to the object.
* @function
- * @param {Object} object
+ * @param {Object} object
* @param {Function} method
+ * @returns {Function}
*/
delegate: function( object, method ) {
return function(){
@@ -606,13 +982,18 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return method.apply( object, args );
};
},
-
-
+
+
/**
- * An enumeration of Browser vendors including UNKNOWN, IE, FIREFOX,
- * SAFARI, CHROME, and OPERA.
- * @name $.BROWSERS
+ * An enumeration of Browser vendors.
* @static
+ * @type {Object}
+ * @property {Number} UNKNOWN
+ * @property {Number} IE
+ * @property {Number} FIREFOX
+ * @property {Number} SAFARI
+ * @property {Number} CHROME
+ * @property {Number} OPERA
*/
BROWSERS: {
UNKNOWN: 0,
@@ -627,11 +1008,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Returns a DOM Element for the given id or element.
* @function
- * @name OpenSeadragon.getElement
* @param {String|Element} element Accepts an id or element.
* @returns {Element} The element with the given id, null, or the element itself.
*/
- getElement: function( element ) {
+ getElement: function( element ) {
if ( typeof ( element ) == "string" ) {
element = document.getElementById( element );
}
@@ -642,9 +1022,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Determines the position of the upper-left corner of the element.
* @function
- * @name OpenSeadragon.getElementPosition
* @param {Element|String} element - the elemenet we want the position for.
- * @returns {Point} - the position of the upper left corner of the element.
+ * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element.
*/
getElementPosition: function( element ) {
var result = new $.Point(),
@@ -673,18 +1052,54 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
},
+ /**
+ * Determines the position of the upper-left corner of the element adjusted for current page and/or element scroll.
+ * @function
+ * @param {Element|String} element - the element we want the position for.
+ * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element adjusted for current page and/or element scroll.
+ */
+ getElementOffset: function( element ) {
+ element = $.getElement( element );
+
+ var doc = element && element.ownerDocument,
+ docElement,
+ win,
+ boundingRect = { top: 0, left: 0 };
+
+ if ( !doc ) {
+ return new $.Point();
+ }
+
+ docElement = doc.documentElement;
+
+ if ( typeof element.getBoundingClientRect !== typeof undefined ) {
+ boundingRect = element.getBoundingClientRect();
+ }
+
+ win = ( doc == doc.window ) ?
+ doc :
+ ( doc.nodeType === 9 ) ?
+ doc.defaultView || doc.parentWindow :
+ false;
+
+ return new $.Point(
+ boundingRect.left + ( win.pageXOffset || docElement.scrollLeft ) - ( docElement.clientLeft || 0 ),
+ boundingRect.top + ( win.pageYOffset || docElement.scrollTop ) - ( docElement.clientTop || 0 )
+ );
+ },
+
+
/**
* Determines the height and width of the given element.
* @function
- * @name OpenSeadragon.getElementSize
* @param {Element|String} element
- * @returns {Point}
+ * @returns {OpenSeadragon.Point}
*/
getElementSize: function( element ) {
element = $.getElement( element );
return new $.Point(
- element.clientWidth,
+ element.clientWidth,
element.clientHeight
);
},
@@ -693,16 +1108,15 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Returns the CSSStyle object for the given element.
* @function
- * @name OpenSeadragon.getElementStyle
* @param {Element|String} element
* @returns {CSSStyle}
*/
- getElementStyle:
- document.documentElement.currentStyle ?
+ getElementStyle:
+ document.documentElement.currentStyle ?
function( element ) {
element = $.getElement( element );
return element.currentStyle;
- } :
+ } :
function( element ) {
element = $.getElement( element );
return window.getComputedStyle( element, "" );
@@ -710,21 +1124,21 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
- * Gets the latest event, really only useful internally since its
- * specific to IE behavior. TODO: Deprecate this from the api and
- * use it internally.
+ * Gets the latest event, really only useful internally since its
+ * specific to IE behavior.
* @function
- * @name OpenSeadragon.getEvent
* @param {Event} [event]
* @returns {Event}
+ * @deprecated For internal use only
+ * @private
*/
getEvent: function( event ) {
if( event ){
- $.getEvent = function( event ){
+ $.getEvent = function( event ) {
return event;
};
} else {
- $.getEvent = function( event ){
+ $.getEvent = function() {
return window.event;
};
}
@@ -735,9 +1149,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Gets the position of the mouse on the screen for a given event.
* @function
- * @name OpenSeadragon.getMousePosition
* @param {Event} [event]
- * @returns {Point}
+ * @returns {OpenSeadragon.Point}
*/
getMousePosition: function( event ) {
@@ -756,13 +1169,13 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
var result = new $.Point();
event = $.getEvent( event );
- result.x =
- event.clientX +
- document.body.scrollLeft +
+ result.x =
+ event.clientX +
+ document.body.scrollLeft +
document.documentElement.scrollLeft;
- result.y =
- event.clientY +
- document.body.scrollTop +
+ result.y =
+ event.clientY +
+ document.body.scrollTop +
document.documentElement.scrollTop;
return result;
@@ -778,10 +1191,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
- * Determines the pages current scroll position.
+ * Determines the page's current scroll position.
* @function
- * @name OpenSeadragon.getPageScroll
- * @returns {Point}
+ * @returns {OpenSeadragon.Point}
*/
getPageScroll: function() {
var docElement = document.documentElement || {},
@@ -809,20 +1221,68 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
);
};
} else {
- $.getPageScroll = function(){
- return new $.Point(0,0);
- };
+ // We can't reassign the function yet, as there was no scroll.
+ return new $.Point(0,0);
}
return $.getPageScroll();
},
+ /**
+ * Set the page scroll position.
+ * @function
+ * @returns {OpenSeadragon.Point}
+ */
+ setPageScroll: function( scroll ) {
+ if ( typeof ( window.scrollTo ) !== "undefined" ) {
+ $.setPageScroll = function( scroll ) {
+ window.scrollTo( scroll.x, scroll.y );
+ };
+ } else {
+ var originalScroll = $.getPageScroll();
+ if ( originalScroll.x === scroll.x &&
+ originalScroll.y === scroll.y ) {
+ // We are already correctly positioned and there
+ // is no way to detect the correct method.
+ return;
+ }
+
+ document.body.scrollLeft = scroll.x;
+ document.body.scrollTop = scroll.y;
+ var currentScroll = $.getPageScroll();
+ if ( currentScroll.x !== originalScroll.x &&
+ currentScroll.y !== originalScroll.y ) {
+ $.setPageScroll = function( scroll ) {
+ document.body.scrollLeft = scroll.x;
+ document.body.scrollTop = scroll.y;
+ };
+ return;
+ }
+
+ document.documentElement.scrollLeft = scroll.x;
+ document.documentElement.scrollTop = scroll.y;
+ currentScroll = $.getPageScroll();
+ if ( currentScroll.x !== originalScroll.x &&
+ currentScroll.y !== originalScroll.y ) {
+ $.setPageScroll = function( scroll ) {
+ document.documentElement.scrollLeft = scroll.x;
+ document.documentElement.scrollTop = scroll.y;
+ };
+ return;
+ }
+
+ // We can't find anything working, so we do nothing.
+ $.setPageScroll = function( scroll ) {
+ };
+ }
+
+ return $.setPageScroll( scroll );
+ },
/**
* Determines the size of the browsers window.
* @function
- * @name OpenSeadragon.getWindowSize
- * @returns {Point}
+ * @returns {OpenSeadragon.Point}
*/
getWindowSize: function() {
var docElement = document.documentElement || {},
@@ -859,47 +1319,48 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Wraps the given element in a nest of divs so that the element can
- * be easily centered.
+ * be easily centered using CSS tables
* @function
- * @name OpenSeadragon.makeCenteredNode
* @param {Element|String} element
- * @returns {Element}
+ * @returns {Element} outermost wrapper element
*/
makeCenteredNode: function( element ) {
-
- var div = $.makeNeutralElement( "div" ),
- html = [],
- innerDiv,
- innerDivs;
-
+ // Convert a possible ID to an actual HTMLElement
element = $.getElement( element );
- //TODO: I dont understand the use of # inside the style attributes
- // below. Invetigate the results of the constructed html in
- // the browser and clean up the mark-up to make this clearer.
- html.push('
');
- html.push('
');
- html.push('
');
+ /*
+ CSS tables require you to have a display:table/row/cell hierarchy so we need to create
+ three nested wrapper divs:
+ */
- div.innerHTML = html.join( '' );
- div = div.firstChild;
+ var wrappers = [
+ $.makeNeutralElement( 'div' ),
+ $.makeNeutralElement( 'div' ),
+ $.makeNeutralElement( 'div' )
+ ];
- innerDiv = div;
- innerDivs = div.getElementsByTagName( "div" );
- while ( innerDivs.length > 0 ) {
- innerDiv = innerDivs[ 0 ];
- innerDivs = innerDiv.getElementsByTagName( "div" );
- }
+ // It feels like we should be able to pass style dicts to makeNeutralElement:
+ $.extend(wrappers[0].style, {
+ display: "table",
+ height: "100%",
+ width: "100%"
+ });
- innerDiv.appendChild( element );
+ $.extend(wrappers[1].style, {
+ display: "table-row"
+ });
- return div;
+ $.extend(wrappers[2].style, {
+ display: "table-cell",
+ verticalAlign: "middle",
+ textAlign: "center"
+ });
+
+ wrappers[0].appendChild(wrappers[1]);
+ wrappers[1].appendChild(wrappers[2]);
+ wrappers[2].appendChild(element);
+
+ return wrappers[0];
},
@@ -907,7 +1368,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
* Creates an easily positionable element of the given type that therefor
* serves as an excellent container element.
* @function
- * @name OpenSeadragon.makeNeutralElement
* @param {String} tagName
* @returns {Element}
*/
@@ -925,12 +1385,26 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
},
+ /**
+ * Returns the current milliseconds, using Date.now() if available
+ * @function
+ */
+ now: function( ) {
+ if (Date.now) {
+ $.now = Date.now;
+ } else {
+ $.now = function() { return new Date().getTime(); };
+ }
+
+ return $.now();
+ },
+
+
/**
* Ensures an image is loaded correctly to support alpha transparency.
- * Generally only IE has issues doing this correctly for formats like
+ * Generally only IE has issues doing this correctly for formats like
* png.
* @function
- * @name OpenSeadragon.makeTransparentImage
* @param {String} src
* @returns {Element}
*/
@@ -938,9 +1412,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
$.makeTransparentImage = function( src ){
var img = $.makeNeutralElement( "img" );
-
+
img.src = src;
-
+
return img;
};
@@ -964,13 +1438,13 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
img.src = src;
element.style.filter =
"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
- src +
+ src +
"', sizingMethod='scale')";
return element;
};
- }
+ }
return $.makeTransparentImage( src );
},
@@ -979,15 +1453,13 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Sets the opacity of the specified element.
* @function
- * @name OpenSeadragon.setElementOpacity
* @param {Element|String} element
* @param {Number} opacity
* @param {Boolean} [usesAlpha]
*/
setElementOpacity: function( element, opacity, usesAlpha ) {
- var previousFilter,
- ieOpacity,
+ var ieOpacity,
ieFilter;
element = $.getElement( element );
@@ -996,116 +1468,167 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
opacity = Math.round( opacity );
}
- if ( opacity < 1 ) {
- element.style.opacity = opacity;
+ if ( $.Browser.opacity ) {
+ element.style.opacity = opacity < 1 ? opacity : "";
} else {
- element.style.opacity = "";
- }
-
- if ( opacity == 1 ) {
- prevFilter = element.style.filter || "";
- element.style.filter = prevFilter.replace(/alpha\(.*?\)/g, "");
- return;
- }
-
- ieOpacity = Math.round( 100 * opacity );
- ieFilter = " alpha(opacity=" + ieOpacity + ") ";
-
- //TODO: find out why this uses a try/catch instead of a predetermined
- // routine or at least an if/elseif/else
- try {
- if ( element.filters && element.filters.alpha ) {
- element.filters.alpha.opacity = ieOpacity;
+ if ( opacity < 1 ) {
+ ieOpacity = Math.round( 100 * opacity );
+ ieFilter = "alpha(opacity=" + ieOpacity + ")";
+ element.style.filter = ieFilter;
} else {
- element.style.filter += ieFilter;
+ element.style.filter = "";
}
- } catch ( e ) {
- element.style.filter += ieFilter;
}
},
+ /**
+ * Add the specified CSS class to the element if not present.
+ * @function
+ * @param {Element|String} element
+ * @param {String} className
+ */
+ addClass: function( element, className ) {
+ element = $.getElement( element );
+
+ if ( ! element.className ) {
+ element.className = className;
+ } else if ( ( ' ' + element.className + ' ' ).
+ indexOf( ' ' + className + ' ' ) === -1 ) {
+ element.className += ' ' + className;
+ }
+ },
+
+ /**
+ * Find the first index at which an element is found in an array or -1
+ * if not present.
+ *
+ * Code taken and adapted from
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Compatibility
+ *
+ * @function
+ * @param {Array} array The array from which to find the element
+ * @param {Object} searchElement The element to find
+ * @param {Number} [fromIndex=0] Index to start research.
+ * @returns {Number} The index of the element in the array.
+ */
+ indexOf: function( array, searchElement, fromIndex ) {
+ if ( Array.prototype.indexOf ) {
+ this.indexOf = function( array, searchElement, fromIndex ) {
+ return array.indexOf( searchElement, fromIndex );
+ };
+ } else {
+ this.indexOf = function( array, searchElement, fromIndex ) {
+ var i,
+ pivot = ( fromIndex ) ? fromIndex : 0,
+ length;
+ if ( !array ) {
+ throw new TypeError( );
+ }
+
+ length = array.length;
+ if ( length === 0 || pivot >= length ) {
+ return -1;
+ }
+
+ if ( pivot < 0 ) {
+ pivot = length - Math.abs( pivot );
+ }
+
+ for ( i = pivot; i < length; i++ ) {
+ if ( array[i] === searchElement ) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ }
+ return this.indexOf( array, searchElement, fromIndex );
+ },
+
+ /**
+ * Remove the specified CSS class from the element.
+ * @function
+ * @param {Element|String} element
+ * @param {String} className
+ */
+ removeClass: function( element, className ) {
+ var oldClasses,
+ newClasses = [],
+ i;
+
+ element = $.getElement( element );
+ oldClasses = element.className.split( /\s+/ );
+ for ( i = 0; i < oldClasses.length; i++ ) {
+ if ( oldClasses[ i ] && oldClasses[ i ] !== className ) {
+ newClasses.push( oldClasses[ i ] );
+ }
+ }
+ element.className = newClasses.join(' ');
+ },
+
+
/**
* Adds an event listener for the given element, eventName and handler.
* @function
- * @name OpenSeadragon.addEvent
* @param {Element|String} element
* @param {String} eventName
* @param {Function} handler
* @param {Boolean} [useCapture]
- * @throws {Error}
*/
- addEvent: function( element, eventName, handler, useCapture ) {
- element = $.getElement( element );
-
- //TODO: Why do this if/else on every method call instead of just
- // defining this function once based on the same logic
- if ( element.addEventListener ) {
- $.addEvent = function( element, eventName, handler, useCapture ){
+ addEvent: (function () {
+ if ( window.addEventListener ) {
+ return function ( element, eventName, handler, useCapture ) {
element = $.getElement( element );
- element.addEventListener( eventName, handler, useCapture );
+ element.addEventListener( eventName, handler, useCapture );
};
- } else if ( element.attachEvent ) {
- $.addEvent = function( element, eventName, handler, useCapture ){
+ } else if ( window.attachEvent ) {
+ return function ( element, eventName, handler, useCapture ) {
element = $.getElement( element );
- element.attachEvent( "on" + eventName, handler );
+ element.attachEvent( 'on' + eventName, handler );
if ( useCapture && element.setCapture ) {
element.setCapture();
- }
+ }
};
} else {
- throw new Error(
- "Unable to attach event handler, no known technique."
- );
+ throw new Error( "No known event model." );
}
-
- return $.addEvent( element, eventName, handler, useCapture );
- },
+ }()),
/**
- * Remove a given event listener for the given element, event type and
+ * Remove a given event listener for the given element, event type and
* handler.
* @function
- * @name OpenSeadragon.removeEvent
* @param {Element|String} element
* @param {String} eventName
* @param {Function} handler
* @param {Boolean} [useCapture]
- * @throws {Error}
*/
- removeEvent: function( element, eventName, handler, useCapture ) {
- element = $.getElement( element );
-
- //TODO: Why do this if/else on every method call instead of just
- // defining this function once based on the same logic
- if ( element.removeEventListener ) {
- $.removeEvent = function( element, eventName, handler, useCapture ) {
+ removeEvent: (function () {
+ if ( window.removeEventListener ) {
+ return function ( element, eventName, handler, useCapture ) {
element = $.getElement( element );
element.removeEventListener( eventName, handler, useCapture );
};
- } else if ( element.detachEvent ) {
- $.removeEvent = function( element, eventName, handler, useCapture ) {
+ } else if ( window.detachEvent ) {
+ return function( element, eventName, handler, useCapture ) {
element = $.getElement( element );
- element.detachEvent("on" + eventName, handler);
+ element.detachEvent( 'on' + eventName, handler );
if ( useCapture && element.releaseCapture ) {
element.releaseCapture();
}
};
} else {
- throw new Error(
- "Unable to detach event handler, no known technique."
- );
+ throw new Error( "No known event model." );
}
- return $.removeEvent( element, eventName, handler, useCapture );
- },
+ }()),
/**
* Cancels the default browser behavior had the event propagated all
* the way up the DOM to the window object.
* @function
- * @name OpenSeadragon.cancelEvent
* @param {Event} [event]
*/
cancelEvent: function( event ) {
@@ -1132,24 +1655,23 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Stops the propagation of the event up the DOM.
* @function
- * @name OpenSeadragon.stopEvent
* @param {Event} [event]
*/
stopEvent: function( event ) {
event = $.getEvent( event );
- if ( event.stopPropagation ) {
+ if ( event.stopPropagation ) {
// W3C for stopping propagation
$.stopEvent = function( event ){
event.stopPropagation();
};
- } else {
+ } else {
// IE for stopping propagation
$.stopEvent = function( event ){
event = $.getEvent( event );
event.cancelBubble = true;
};
-
+
}
$.stopEvent( event );
@@ -1157,17 +1679,16 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
- * Similar to OpenSeadragon.delegate, but it does not immediately call
+ * Similar to OpenSeadragon.delegate, but it does not immediately call
* the method on the object, returning a function which can be called
* repeatedly to delegate the method. It also allows additonal arguments
* to be passed during construction which will be added during each
* invocation, and each invocation can add additional arguments as well.
- *
+ *
* @function
- * @name OpenSeadragon.createCallback
* @param {Object} object
* @param {Function} method
- * @param [args] any additional arguments are passed as arguments to the
+ * @param [args] any additional arguments are passed as arguments to the
* created callback
* @returns {Function}
*/
@@ -1196,7 +1717,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Retreives the value of a url parameter from the window.location string.
* @function
- * @name OpenSeadragon.getUrlParameter
* @param {String} key
* @returns {String} The value of the url parameter or null if no param matches.
*/
@@ -1209,13 +1729,15 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
createAjaxRequest: function(){
var request;
- if ( window.ActiveXObject ) {
- //TODO: very bad...Why check every time using try/catch when
- // we could determine once at startup which activeX object
- // was supported. This will have significant impact on
- // performance for IE Browsers DONE
+ if ( window.XMLHttpRequest ) {
+ $.createAjaxRequest = function( ){
+ return new XMLHttpRequest();
+ };
+ request = new XMLHttpRequest();
+ } else if ( window.ActiveXObject ) {
/*jshint loopfunc:true*/
- for ( i = 0; i < ACTIVEX.length; i++ ) {
+ /* global ActiveXObject:true */
+ for ( var i = 0; i < ACTIVEX.length; i++ ) {
try {
request = new ActiveXObject( ACTIVEX[ i ] );
$.createAjaxRequest = function( ){
@@ -1226,11 +1748,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
continue;
}
}
- } else if ( window.XMLHttpRequest ) {
- $.createAjaxRequest = function( ){
- return new XMLHttpRequest();
- };
- request = new XMLHttpRequest();
}
if ( !request ) {
@@ -1239,74 +1756,75 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return request;
},
+
+
/**
* Makes an AJAX request.
* @function
- * @name OpenSeadragon.makeAjaxRequest
- * @param {String} url - the url to request
- * @param {Function} [callback] - a function to call when complete
+ * @param {String} url - the url to request
+ * @param {Function} onSuccess - a function to call on a successful response
+ * @param {Function} onError - a function to call on when an error occurs
* @throws {Error}
*/
- makeAjaxRequest: function( url, callback ) {
+ makeAjaxRequest: function( url, onSuccess, onError ) {
+ var request = $.createAjaxRequest();
- var async = true,
- request = $.createAjaxRequest(),
- actual,
- options,
- i;
-
-
- if( $.isPlainObject( url ) ){
- options.async = options.async || async;
- }else{
- options = {
- url: url,
- async: $.isFunction( callback ),
- success: callback,
- error: null
- };
+ if ( !$.isFunction( onSuccess ) ) {
+ throw new Error( "makeAjaxRequest requires a success callback" );
}
- if ( options.async ) {
- /** @ignore */
- request.onreadystatechange = function() {
- if ( request.readyState == 4) {
- request.onreadystatechange = function(){};
- options.success( request );
+ request.onreadystatechange = function() {
+ // 4 = DONE (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties)
+ if ( request.readyState == 4 ) {
+ request.onreadystatechange = function(){};
+
+ if ( request.status == 200 ) {
+ onSuccess( request );
+ } else {
+ $.console.log( "AJAX request returned %s: %s", request.status, url );
+
+ if ( $.isFunction( onError ) ) {
+ onError( request );
+ }
}
- };
- }
+ }
+ };
try {
- request.open( "GET", options.url, options.async );
+ request.open( "GET", url, true );
request.send( null );
} catch (e) {
- $.console.log(
- "%s while making AJAX request: %s",
- e.name,
- e.message
- );
+ var msg = e.message;
- request.onreadystatechange = null;
- request = null;
+ /*
+ IE < 10 does not support CORS and an XHR request to a different origin will fail as soon
+ as send() is called. This is particularly easy to miss during development and appear in
+ production if you use a CDN or domain sharding and the security policy is likely to break
+ exception handlers since any attempt to access a property of the request object will
+ raise an access denied TypeError inside the catch block.
- if ( options.error && $.isFunction( options.error ) ) {
- options.error( request );
+ To be friendlier, we'll check for this specific error and add a documentation pointer
+ to point developers in the right direction. We test the exception number because IE's
+ error messages are localized.
+ */
+ var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10;
+ if ( oldIE && typeof( e.number ) != "undefined" && e.number == -2147024891 ) {
+ msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain";
+ }
+
+ $.console.log( "%s while making AJAX request: %s", e.name, msg );
+
+ request.onreadystatechange = function(){};
+
+ if ( $.isFunction( onError ) ) {
+ onError( request, e );
}
}
-
- if( !options.async && $.isFunction( options.success ) ){
- options.success( request );
- }
-
- return options.async ? null : request;
},
-
/**
* Taken from jQuery 1.6.1
* @function
- * @name OpenSeadragon.jsonp
* @param {Object} options
* @param {String} options.url
* @param {Function} options.callback
@@ -1318,10 +1836,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
jsonp: function( options ){
var script,
url = options.url,
- head = document.head ||
- document.getElementsByTagName( "head" )[ 0 ] ||
+ head = document.head ||
+ document.getElementsByTagName( "head" )[ 0 ] ||
document.documentElement,
- jsonpCallback = options.callbackName || 'openseadragon' + (+new Date()),
+ jsonpCallback = options.callbackName || 'openseadragon' + $.now(),
previous = window[ jsonpCallback ],
replace = "$1" + jsonpCallback + "$2",
callbackParam = options.param || 'callback',
@@ -1380,27 +1898,22 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
-
+
},
/**
* Fully deprecated. Will throw an error.
* @function
- * @name OpenSeadragon.createFromDZI
- * @param {String} xmlUrl
- * @param {String} xmlString
- * @param {Function} callback
- * @deprecated - use OpenSeadragon.Viewer.prototype.open
+ * @deprecated use {@link OpenSeadragon.Viewer#open}
*/
- createFromDZI: function( dzi, callback, tileHost ) {
+ createFromDZI: function() {
throw "OpenSeadragon.createFromDZI is deprecated, use Viewer.open.";
},
/**
* Parses an XML string into a DOM Document.
* @function
- * @name OpenSeadragon.parseXml
* @param {String} string
* @returns {Document}
*/
@@ -1411,8 +1924,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
if ( window.ActiveXObject ) {
$.parseXml = function( string ){
- var xmlDoc = null,
- parser;
+ var xmlDoc = null;
xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );
xmlDoc.async = false;
@@ -1421,7 +1933,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
};
} else if ( window.DOMParser ) {
-
+
$.parseXml = function( string ){
var xmlDoc = null,
parser;
@@ -1443,7 +1955,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
* Reports whether the image format is supported for tiling in this
* version.
* @function
- * @name OpenSeadragon.imageFormatSupported
* @param {String} [extension]
* @returns {Boolean}
*/
@@ -1456,12 +1967,14 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
- * The current browser vendor, version, and related information regarding
- * detected features. Features include
- * 'alpha' - Does the browser support image alpha
- * transparency.
- * @name $.Browser
+ * The current browser vendor, version, and related information regarding detected features.
+ * @member {Object} Browser
+ * @memberof OpenSeadragon
* @static
+ * @type {Object}
+ * @property {OpenSeadragon.BROWSERS} vendor - One of the {@link OpenSeadragon.BROWSERS} enumeration values.
+ * @property {Number} version
+ * @property {Boolean} alpha - Does the browser support image alpha transparency.
*/
$.Browser = {
vendor: $.BROWSERS.UNKNOWN,
@@ -1471,10 +1984,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
var ACTIVEX = [
- "Msxml2.XMLHTTP",
- "Msxml3.XMLHTTP",
+ "Msxml2.XMLHTTP",
+ "Msxml3.XMLHTTP",
"Microsoft.XMLHTTP"
- ],
+ ],
FILEFORMATS = {
"bmp": false,
"jpeg": true,
@@ -1486,7 +1999,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
URLPARAMS = {};
(function() {
- //A small auto-executing routine to determine the browser vendor,
+ //A small auto-executing routine to determine the browser vendor,
//version and supporting feature sets.
var app = navigator.appName,
ver = navigator.appVersion,
@@ -1498,13 +2011,13 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
switch( navigator.appName ){
case "Microsoft Internet Explorer":
- if( !!window.attachEvent &&
+ if( !!window.attachEvent &&
!!window.ActiveXObject ) {
$.Browser.vendor = $.BROWSERS.IE;
$.Browser.version = parseFloat(
- ua.substring(
- ua.indexOf( "MSIE" ) + 5,
+ ua.substring(
+ ua.indexOf( "MSIE" ) + 5,
ua.indexOf( ";", ua.indexOf( "MSIE" ) ) )
);
}
@@ -1517,12 +2030,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
ua.substring( ua.indexOf( "Firefox" ) + 8 )
);
} else if ( ua.indexOf( "Safari" ) >= 0 ) {
- $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ?
- $.BROWSERS.CHROME :
+ $.Browser.vendor = ua.indexOf( "Chrome" ) >= 0 ?
+ $.BROWSERS.CHROME :
$.BROWSERS.SAFARI;
$.Browser.version = parseFloat(
- ua.substring(
- ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1,
+ ua.substring(
+ ua.substring( 0, ua.indexOf( "Safari" ) ).lastIndexOf( "/" ) + 1,
ua.indexOf( "Safari" )
)
);
@@ -1553,16 +2066,22 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
}
//determine if this browser supports image alpha transparency
- $.Browser.alpha = !(
- (
- $.Browser.vendor == $.BROWSERS.IE &&
+ $.Browser.alpha = !(
+ (
+ $.Browser.vendor == $.BROWSERS.IE &&
$.Browser.version < 9
) || (
- $.Browser.vendor == $.BROWSERS.CHROME &&
+ $.Browser.vendor == $.BROWSERS.CHROME &&
$.Browser.version < 2
)
);
+ //determine if this browser supports element.style.opacity
+ $.Browser.opacity = !(
+ $.Browser.vendor == $.BROWSERS.IE &&
+ $.Browser.version < 9
+ );
+
})();
@@ -1572,7 +2091,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// I eventually was convinced that errors should naturally propogate in
// all but the most special cases.
/**
- * A convenient alias for console when available, and a simple null
+ * A convenient alias for console when available, and a simple null
* function when console is unavailable.
* @static
* @private
@@ -1588,38 +2107,37 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
warn: nullfunction,
error: nullfunction
};
-
- // Adding support for HTML5's requestAnimationFrame as suggested by acdha
- // implementation taken from matt synders post here:s
+
+ // Adding support for HTML5's requestAnimationFrame as suggested by acdha.
+ // Implementation taken from matt synder's post here:
// http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/
(function( w ) {
// most browsers have an implementation
- w.requestAnimationFrame = w.requestAnimationFrame ||
- w.mozRequestAnimationFrame ||
+ var requestAnimationFrame = w.requestAnimationFrame ||
+ w.mozRequestAnimationFrame ||
w.webkitRequestAnimationFrame ||
w.msRequestAnimationFrame;
- w.cancelAnimationFrame = w.cancelAnimationFrame ||
- w.mozCancelAnimationFrame ||
+ var cancelAnimationFrame = w.cancelAnimationFrame ||
+ w.mozCancelAnimationFrame ||
w.webkitCancelAnimationFrame ||
w.msCancelAnimationFrame;
-
// polyfill, when necessary
- if ( w.requestAnimationFrame ) {
- //we cant assign window.requestAnimationFrame directly to $.requestAnimationFrame
- //without getting Illegal Invocation errors in webkit so call in a
- //wrapper
- $.requestAnimationFrame = function( callback ){
- return w.requestAnimationFrame( callback );
+ if ( requestAnimationFrame && cancelAnimationFrame ) {
+ // We can't assign these window methods directly to $ because they
+ // expect their "this" to be "window", so we call them in wrappers.
+ $.requestAnimationFrame = function(){
+ return requestAnimationFrame.apply( w, arguments );
};
- $.cancelAnimationFrame = function( requestId ){
- return w.cancelAnimationFrame( requestId );
+ $.cancelAnimationFrame = function(){
+ return cancelAnimationFrame.apply( w, arguments );
};
} else {
var aAnimQueue = [],
+ processing = [],
iRequestId = 0,
iIntervalId;
@@ -1630,7 +2148,18 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
if ( !iIntervalId ) {
iIntervalId = setInterval( function() {
if ( aAnimQueue.length ) {
- aAnimQueue.shift( )[ 1 ](+new Date());
+ var time = $.now();
+ // Process all of the currently outstanding frame
+ // requests, but none that get added during the
+ // processing.
+ // Swap the arrays so we don't have to create a new
+ // array every frame.
+ var temp = processing;
+ processing = aAnimQueue;
+ aAnimQueue = temp;
+ while ( processing.length ) {
+ processing.shift()[ 1 ]( time );
+ }
} else {
// don't continue the interval, if unnecessary
clearInterval( iIntervalId );
@@ -1645,12 +2174,23 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// create a mock cancelAnimationFrame function
$.cancelAnimationFrame = function( requestId ) {
// find the request ID and remove it
- for ( var i = 0, j = aAnimQueue.length; i < j; i += 1 ) {
+ var i, j;
+ for ( i = 0, j = aAnimQueue.length; i < j; i += 1 ) {
if ( aAnimQueue[ i ][ 0 ] === requestId ) {
aAnimQueue.splice( i, 1 );
return;
}
}
+
+ // If it's not in the queue, it may be in the set we're currently
+ // processing (if cancelAnimationFrame is called from within a
+ // requestAnimationFrame callback).
+ for ( i = 0, j = processing.length; i < j; i += 1 ) {
+ if ( processing[ i ][ 0 ] === requestId ) {
+ processing.splice( i, 1 );
+ return;
+ }
+ }
};
}
})( window );
@@ -1659,7 +2199,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
* @private
* @inner
* @function
- * @param {Element} element
+ * @param {Element} element
* @param {Boolean} [isFixed]
* @returns {Element}
*/
@@ -1688,8 +2228,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
throw new Error( $.getString( "Errors.Security" ) );
} else if ( xhr.status !== 200 && xhr.status !== 0 ) {
status = xhr.status;
- statusText = ( status == 404 ) ?
- "Not Found" :
+ statusText = ( status == 404 ) ?
+ "Not Found" :
xhr.statusText;
throw new Error( $.getString( "Errors.Status", status, statusText ) );
}
@@ -1724,14 +2264,14 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
try {
return processDZI( root, tilesUrl );
} catch ( e ) {
- throw (e instanceof Error) ?
- 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 );
+ return $._processDZIError( root );
}
throw new Error( $.getString( "Errors.Dzi" ) );
@@ -1758,7 +2298,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
rectNode,
i;
- if ( !imageFormatSupported( fileFormat ) ) {
+ if ( !$.imageFormatSupported( fileFormat ) ) {
throw new Error(
$.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
);
@@ -1778,12 +2318,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
));
}
return new $.DziTileSource(
- width,
- height,
- tileSize,
+ width,
+ height,
+ tileSize,
tileOverlap,
- tilesUrl,
- fileFormat,
+ tilesUrl,
+ fileFormat,
dispRects
);
}
@@ -1808,7 +2348,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
rectData,
i;
- if ( !imageFormatSupported( fileFormat ) ) {
+ if ( !$.imageFormatSupported( fileFormat ) ) {
throw new Error(
$.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
);
@@ -1827,12 +2367,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
));
}
return new $.DziTileSource(
- width,
- height,
- tileSize,
+ width,
+ height,
+ tileSize,
tileOverlap,
- tilesUrl,
- fileFormat,
+ tilesUrl,
+ fileFormat,
dispRects
);
}
@@ -1845,11 +2385,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
* @throws {Error}
* @deprecated
*/
- function processDZIError( errorNode ) {
+ $._processDZIError = function ( errorNode ) {
var messageNode = errorNode.getElementsByTagName( "Message" )[ 0 ],
message = messageNode.firstChild.nodeValue;
throw new Error(message);
- }
+ };
}( OpenSeadragon ));
diff --git a/src/osmtilesource.js b/src/osmtilesource.js
index b8c43dea..cc3286bc 100644
--- a/src/osmtilesource.js
+++ b/src/osmtilesource.js
@@ -1,21 +1,63 @@
+/*
+ * OpenSeadragon - OsmTileSource
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
+/*
+ * Derived from the OSM tile source in Rainer Simon's seajax-utils project
+ * . Rainer Simon has contributed
+ * the included code to the OpenSeadragon project under the New BSD license;
+ * see .
+ */
+
+
(function( $ ){
-
+
/**
- * A tilesource implementation for OpenStreetMap. Adopted from Rainer Simon
- * project http://github.com/rsimon/seajax-utils.
+ * @class OsmTileSource
+ * @classdesc A tilesource implementation for OpenStreetMap.
*
- * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep
+ * Note 1. Zoomlevels. Deep Zoom and OSM define zoom levels differently. In Deep
* Zoom, level 0 equals an image of 1x1 pixels. In OSM, level 0 equals an image of
- * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a
- * difference of log2(256)=8 levels.
+ * 256x256 levels (see http://gasi.ch/blog/inside-deep-zoom-2). I.e. there is a
+ * difference of log2(256)=8 levels.
*
- * Note 2. Image dimension. According to the OSM Wiki
+ * Note 2. Image dimension. According to the OSM Wiki
* (http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels)
* the highest Mapnik zoom level has 256.144x256.144 tiles, with a 256x256
* pixel size. I.e. the Deep Zoom image dimension is 65.572.864x65.572.864
* pixels.
*
- * @class
+ * @memberof OpenSeadragon
* @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.
@@ -23,7 +65,7 @@
* @param {Number} tileSize
* @param {Number} tileOverlap
* @param {String} tilesUrl
- */
+ */
$.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
var options;
@@ -53,36 +95,34 @@ $.OsmTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
options.tilesUrl = "http://tile.openstreetmap.org/";
}
options.minLevel = 8;
-
+
$.TileSource.apply( this, [ options ] );
};
-$.extend( $.OsmTileSource.prototype, $.TileSource.prototype, {
+$.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.OsmTileSource.prototype */{
/**
* Determine if the data and/or url imply the image service is supported by
* this tile source.
* @function
- * @name OpenSeadragon.OsmTileSource.prototype.supports
* @param {Object|Array} data
* @param {String} optional - url
*/
supports: function( data, url ){
- return (
- data.type &&
+ return (
+ data.type &&
"openstreetmaps" == data.type
);
},
/**
- *
+ *
* @function
- * @name OpenSeadragon.OsmTileSource.prototype.configure
* @param {Object} 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
+ * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
*/
configure: function( data, url ){
@@ -92,7 +132,6 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, {
/**
* @function
- * @name OpenSeadragon.OsmTileSource.prototype.getTileUrl
* @param {Number} level
* @param {Number} x
* @param {Number} y
diff --git a/src/overlay.js b/src/overlay.js
index c39884b7..c59d8c35 100644
--- a/src/overlay.js
+++ b/src/overlay.js
@@ -1,10 +1,55 @@
+/*
+ * OpenSeadragon - Overlay
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(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.
+ * An enumeration of positions that an overlay may be assigned relative to
+ * the viewport.
+ * @member OverlayPlacement
+ * @memberof OpenSeadragon
* @static
+ * @type {Object}
+ * @property {Number} CENTER
+ * @property {Number} TOP_LEFT
+ * @property {Number} TOP
+ * @property {Number} TOP_RIGHT
+ * @property {Number} RIGHT
+ * @property {Number} BOTTOM_RIGHT
+ * @property {Number} BOTTOM
+ * @property {Number} BOTTOM_LEFT
+ * @property {Number} LEFT
*/
$.OverlayPlacement = {
CENTER: 0,
@@ -19,34 +64,76 @@
};
/**
- * An Overlay provides a
- * @class
+ * @class Overlay
+ * @classdesc Provides a way to float an HTML element on top of the viewer element.
+ *
+ * @memberof OpenSeadragon
+ * @param {Object} options
+ * @param {Element} options.element
+ * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
+ * location of the overlay on the image. If a {@link OpenSeadragon.Point}
+ * is specified, the overlay will keep a constant size independently of the
+ * zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will
+ * be adjusted when the zoom changes.
+ * @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT]
+ * Relative position to the viewport.
+ * Only used if location is a {@link OpenSeadragon.Point}.
+ * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
+ * @param {Boolean} [options.checkResize=true] Set to false to avoid to
+ * check the size of the overlay everytime it is drawn when using a
+ * {@link OpenSeadragon.Point} as options.location. It will improve
+ * performances but will cause a misalignment if the overlay size changes.
*/
$.Overlay = function( element, location, placement ) {
- this.element = element;
- this.scales = location instanceof $.Rect;
+
+ /**
+ * onDraw callback signature used by {@link OpenSeadragon.Overlay}.
+ *
+ * @callback OnDrawCallback
+ * @memberof OpenSeadragon.Overlay
+ * @param {OpenSeadragon.Point} position
+ * @param {OpenSeadragon.Point} size
+ * @param {Element} element
+ */
+
+ var options;
+ if ( $.isPlainObject( element ) ) {
+ options = element;
+ } else {
+ options = {
+ element: element,
+ location: location,
+ placement: placement
+ };
+ }
+
+ this.element = options.element;
+ this.scales = options.location instanceof $.Rect;
this.bounds = new $.Rect(
- location.x,
- location.y,
- location.width,
- location.height
+ options.location.x,
+ options.location.y,
+ options.location.width,
+ options.location.height
);
this.position = new $.Point(
- location.x,
- location.y
+ options.location.x,
+ options.location.y
);
this.size = new $.Point(
- location.width,
- location.height
+ options.location.width,
+ options.location.height
);
- this.style = element.style;
+ this.style = options.element.style;
// rects are always top-left
- this.placement = location instanceof $.Point ?
- placement :
- $.OverlayPlacement.TOP_LEFT;
+ this.placement = options.location instanceof $.Point ?
+ options.placement :
+ $.OverlayPlacement.TOP_LEFT;
+ this.onDraw = options.onDraw;
+ this.checkResize = options.checkResize === undefined ?
+ true : options.checkResize;
};
- $.Overlay.prototype = {
+ $.Overlay.prototype = /** @lends OpenSeadragon.Overlay.prototype */{
/**
* @function
@@ -100,9 +187,9 @@
element.parentNode.removeChild( element );
//this should allow us to preserve overlays when required between
//pages
- if( element.prevElementParent ){
+ if ( element.prevElementParent ) {
style.display = 'none';
- //element.prevElementParent.insertBefore(
+ //element.prevElementParent.insertBefore(
// element,
// element.prevNextSibling
//);
@@ -110,6 +197,9 @@
}
}
+ // clear the onDraw callback
+ this.onDraw = null;
+
style.top = "";
style.left = "";
style.position = "";
@@ -124,40 +214,80 @@
* @function
* @param {Element} container
*/
- drawHTML: function( container ) {
+ drawHTML: function( container, viewport ) {
var element = this.element,
style = this.style,
scales = this.scales,
- position,
- size;
+ degrees = viewport.degrees,
+ position = viewport.pixelFromPoint(
+ this.bounds.getTopLeft(),
+ true
+ ),
+ size,
+ overlayCenter;
if ( element.parentNode != container ) {
//save the source parent for later if we need it
element.prevElementParent = element.parentNode;
element.prevNextSibling = element.nextSibling;
container.appendChild( element );
- }
-
- if ( !scales ) {
this.size = $.getElementSize( element );
}
- position = this.position;
- size = this.size;
+ if ( scales ) {
+ size = viewport.deltaPixelsFromPoints(
+ this.bounds.getSize(),
+ true
+ );
+ } else if ( this.checkResize ) {
+ size = $.getElementSize( element );
+ } else {
+ size = this.size;
+ }
+
+ this.position = position;
+ this.size = 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";
- style.display = 'block';
+ // rotate the position of the overlay
+ // TODO only rotate overlays if in canvas mode
+ // TODO replace the size rotation with CSS3 transforms
+ // TODO add an option to overlays to not rotate with the image
+ // Currently only rotates position and size
+ if( degrees !== 0 && this.scales ) {
+ overlayCenter = new $.Point( size.x / 2, size.y / 2 );
- if ( scales ) {
- style.width = size.x + "px";
- style.height = size.y + "px";
+ var drawerCenter = new $.Point(
+ viewport.viewer.drawer.canvas.width / 2,
+ viewport.viewer.drawer.canvas.height / 2
+ );
+ position = position.plus( overlayCenter ).rotate(
+ degrees,
+ drawerCenter
+ ).minus( overlayCenter );
+
+ size = size.rotate( degrees, new $.Point( 0, 0 ) );
+ size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) );
+ }
+
+ // call the onDraw callback if it exists to allow one to overwrite
+ // the drawing/positioning/sizing of the overlay
+ if ( this.onDraw ) {
+ this.onDraw( position, size, element );
+ } else {
+ style.left = position.x + "px";
+ style.top = position.y + "px";
+ style.position = "absolute";
+ style.display = 'block';
+
+ if ( scales ) {
+ style.width = size.x + "px";
+ style.height = size.y + "px";
+ }
}
},
@@ -168,16 +298,16 @@
*/
update: function( location, placement ) {
this.scales = location instanceof $.Rect;
- this.bounds = new $.Rect(
- location.x,
- location.y,
- location.width,
+ 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;
+ placement :
+ $.OverlayPlacement.TOP_LEFT;
}
};
diff --git a/src/point.js b/src/point.js
index e1da1bcc..38aad1f1 100644
--- a/src/point.js
+++ b/src/point.js
@@ -1,21 +1,65 @@
+/*
+ * OpenSeadragon - Point
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
/**
- * A Point is really used as a 2-dimensional vector, equally useful for
+ * @class Point
+ * @classdesc 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
+ *
+ * @memberof OpenSeadragon
* @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 ) {
+ /**
+ * The vector component 'x'.
+ * @member {Number} x
+ * @memberof OpenSeadragon.Point#
+ */
this.x = typeof ( x ) == "number" ? x : 0;
+ /**
+ * The vector component 'y'.
+ * @member {Number} y
+ * @memberof OpenSeadragon.Point#
+ */
this.y = typeof ( y ) == "number" ? y : 0;
};
-$.Point.prototype = {
+$.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
/**
* Add another Point to this point and return a new Point.
@@ -26,58 +70,57 @@ $.Point.prototype = {
*/
plus: function( point ) {
return new $.Point(
- this.x + point.x,
+ this.x + point.x,
this.y + point.y
);
},
/**
- * Add another Point to this point and return a new Point.
+ * Substract another Point to this point and return a new Point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
+ * @param {OpenSeadragon.Point} point The point to substract vector components.
+ * @returns {OpenSeadragon.Point} A new point representing the substraction of the
* vector components
*/
minus: function( point ) {
return new $.Point(
- this.x - point.x,
+ this.x - point.x,
this.y - point.y
);
},
/**
- * Add another Point to this point and return a new Point.
+ * Multiply this point by a factor and return a new Point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {Number} factor The factor to multiply vector components.
+ * @returns {OpenSeadragon.Point} A new point representing the multiplication
+ * of the vector components by the factor
*/
times: function( factor ) {
return new $.Point(
- this.x * factor,
+ this.x * factor,
this.y * factor
);
},
/**
- * Add another Point to this point and return a new Point.
+ * Divide this point by a factor and return a new Point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {Number} factor The factor to divide vector components.
+ * @returns {OpenSeadragon.Point} A new point representing the division of the
+ * vector components by the factor
*/
divide: function( factor ) {
return new $.Point(
- this.x / factor,
+ this.x / factor,
this.y / factor
);
},
/**
- * Add another Point to this point and return a new Point.
+ * Compute the opposite of this point and return a new Point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
+ * @returns {OpenSeadragon.Point} A new point representing the opposite of the
* vector components
*/
negate: function() {
@@ -85,11 +128,10 @@ $.Point.prototype = {
},
/**
- * Add another Point to this point and return a new Point.
+ * Compute the distance between this point and another point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {OpenSeadragon.Point} point The point to compute the distance with.
+ * @returns {Number} The distance between the 2 points
*/
distanceTo: function( point ) {
return Math.sqrt(
@@ -99,39 +141,52 @@ $.Point.prototype = {
},
/**
- * Add another Point to this point and return a new Point.
+ * Apply a function to each coordinate of this point and return a new point.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {function} func The function to apply to each coordinate.
+ * @returns {OpenSeadragon.Point} A new point with the coordinates computed
+ * by the specified function
*/
apply: function( func ) {
return new $.Point( func( this.x ), func( this.y ) );
},
/**
- * Add another Point to this point and return a new Point.
+ * Check if this point is equal to another one.
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {OpenSeadragon.Point} point The point to compare this point with.
+ * @returns {Boolean} true if they are equal, false otherwise.
*/
equals: function( point ) {
- return (
- point instanceof $.Point
- ) && (
- this.x === point.x
- ) && (
- this.y === point.y
+ return (
+ point instanceof $.Point
+ ) && (
+ this.x === point.x
+ ) && (
+ this.y === point.y
);
},
/**
- * Add another Point to this point and return a new Point.
+ * Rotates the point around the specified pivot
+ * From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
* @function
- * @param {OpenSeadragon.Point} point The point to add vector components.
- * @returns {OpenSeadragon.Point} A new point representing the sum of the
- * vector components
+ * @param {Number} degress to rotate around the pivot.
+ * @param {OpenSeadragon.Point} pivot Point about which to rotate.
+ * @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
+ */
+ rotate: function ( degrees, pivot ) {
+ var angle = degrees * Math.PI / 180.0,
+ x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x,
+ y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y;
+ return new $.Point( x, y );
+ },
+
+ /**
+ * Convert this point to a string in the format (x,y) where x and y are
+ * rounded to the nearest integer.
+ * @function
+ * @returns {String} A string representation of this point.
*/
toString: function() {
return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")";
diff --git a/src/profiler.js b/src/profiler.js
index dda7d15c..5e6cfb7c 100644
--- a/src/profiler.js
+++ b/src/profiler.js
@@ -1,9 +1,45 @@
+/*
+ * OpenSeadragon - Profiler
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
/**
- * A utility class useful for developers to establish baseline performance
+ * @class Profiler
+ * @classdesc A utility class useful for developers to establish baseline performance
* metrics of rendering routines.
- * @class
+ *
+ * @memberof OpenSeadragon
* @property {Boolean} midUpdate
* @property {Number} numUpdates
* @property {Number} lastBeginTime
@@ -32,7 +68,7 @@ $.Profiler = function() {
this.maxIdleTime = 0;
};
-$.Profiler.prototype = {
+$.Profiler.prototype = /** @lends OpenSeadragon.Profiler.prototype */{
/**
* @function
@@ -43,7 +79,7 @@ $.Profiler.prototype = {
}
this.midUpdate = true;
- this.lastBeginTime = new Date().getTime();
+ this.lastBeginTime = $.now();
if (this.numUpdates < 1) {
return; // this is the first update
@@ -69,7 +105,7 @@ $.Profiler.prototype = {
return;
}
- this.lastEndTime = new Date().getTime();
+ this.lastEndTime = $.now();
this.midUpdate = false;
var time = this.lastEndTime - this.lastBeginTime;
diff --git a/src/rectangle.js b/src/rectangle.js
index 6e255610..99172e7d 100644
--- a/src/rectangle.js
+++ b/src/rectangle.js
@@ -1,29 +1,80 @@
+/*
+ * OpenSeadragon - Rect
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(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
+ * @class Rect
+ * @classdesc 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
+ * @memberof OpenSeadragon
* @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 ) {
+ /**
+ * The vector component 'x'.
+ * @member {Number} x
+ * @memberof OpenSeadragon.Rect#
+ */
this.x = typeof ( x ) == "number" ? x : 0;
+ /**
+ * The vector component 'y'.
+ * @member {Number} y
+ * @memberof OpenSeadragon.Rect#
+ */
this.y = typeof ( y ) == "number" ? y : 0;
+ /**
+ * The vector component 'width'.
+ * @member {Number} width
+ * @memberof OpenSeadragon.Rect#
+ */
this.width = typeof ( width ) == "number" ? width : 0;
+ /**
+ * The vector component 'height'.
+ * @member {Number} height
+ * @memberof OpenSeadragon.Rect#
+ */
this.height = typeof ( height ) == "number" ? height : 0;
};
-$.Rect.prototype = {
+$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
/**
* The aspect ratio is simply the ratio of width to height.
@@ -35,14 +86,17 @@ $.Rect.prototype = {
},
/**
- * Provides the coordinates of the upper-left corner of the rectanglea s a
+ * Provides the coordinates of the upper-left corner of the rectangle as 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 );
+ return new $.Point(
+ this.x,
+ this.y
+ );
},
/**
@@ -54,7 +108,35 @@ $.Rect.prototype = {
*/
getBottomRight: function() {
return new $.Point(
- this.x + this.width,
+ this.x + this.width,
+ this.y + this.height
+ );
+ },
+
+ /**
+ * Provides the coordinates of the top-right corner of the rectangle as a
+ * point.
+ * @function
+ * @returns {OpenSeadragon.Point} The coordinate of the top-right corner of
+ * the rectangle.
+ */
+ getTopRight: function() {
+ return new $.Point(
+ this.x + this.width,
+ this.y
+ );
+ },
+
+ /**
+ * Provides the coordinates of the bottom-left corner of the rectangle as a
+ * point.
+ * @function
+ * @returns {OpenSeadragon.Point} The coordinate of the bottom-left corner of
+ * the rectangle.
+ */
+ getBottomLeft: function() {
+ return new $.Point(
+ this.x,
this.y + this.height
);
},
@@ -62,7 +144,7 @@ $.Rect.prototype = {
/**
* Computes the center of the rectangle.
* @function
- * @returns {OpenSeadragon.Point} The center of the rectangle as represnted
+ * @returns {OpenSeadragon.Point} The center of the rectangle as represented
* as represented by a 2-dimensional vector (x,y)
*/
getCenter: function() {
@@ -75,7 +157,7 @@ $.Rect.prototype = {
/**
* Returns the width and height component as a vector OpenSeadragon.Point
* @function
- * @returns {OpenSeadragon.Point} The 2 dimensional vector represnting the
+ * @returns {OpenSeadragon.Point} The 2 dimensional vector representing the
* the width and height of the rectangle.
*/
getSize: function() {
@@ -83,31 +165,86 @@ $.Rect.prototype = {
},
/**
- * Determines if two Rectanlges have equivalent components.
+ * Determines if two Rectangles 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.x === other.x ) &&
( this.y === other.y ) &&
- ( this.width === other.width ) &&
+ ( this.width === other.width ) &&
( this.height === other.height );
},
/**
- * Provides a string representation of the retangle which is useful for
+ * Rotates a rectangle around a point. Currently only 90, 180, and 270
+ * degrees are supported.
+ * @function
+ * @param {Number} degrees The angle in degrees to rotate.
+ * @param {OpenSeadragon.Point} pivot The point about which to rotate.
+ * Defaults to the center of the rectangle.
+ * @return {OpenSeadragon.Rect}
+ */
+ rotate: function( degrees, pivot ) {
+ // TODO support arbitrary rotation
+ var width = this.width,
+ height = this.height,
+ newTopLeft;
+
+ degrees = ( degrees + 360 ) % 360;
+ if( degrees % 90 !== 0 ) {
+ throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
+ }
+
+ if( degrees === 0 ){
+ return new $.Rect(
+ this.x,
+ this.y,
+ this.width,
+ this.height
+ );
+ }
+
+ pivot = pivot || this.getCenter();
+
+ switch ( degrees ) {
+ case 90:
+ newTopLeft = this.getBottomLeft();
+ width = this.height;
+ height = this.width;
+ break;
+ case 180:
+ newTopLeft = this.getBottomRight();
+ break;
+ case 270:
+ newTopLeft = this.getTopRight();
+ width = this.height;
+ height = this.width;
+ break;
+ default:
+ newTopLeft = this.getTopLeft();
+ break;
+ }
+
+ newTopLeft = newTopLeft.rotate(degrees, pivot);
+
+ return new $.Rect(newTopLeft.x, newTopLeft.y, width, height);
+ },
+
+ /**
+ * Provides a string representation of the rectangle 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) + "," +
+ return "[" +
+ Math.round(this.x*100) + "," +
+ Math.round(this.y*100) + "," +
Math.round(this.width*100) + "x" +
- Math.round(this.height*100) +
+ Math.round(this.height*100) +
"]";
}
};
diff --git a/src/referencestrip.js b/src/referencestrip.js
index 80bf10d1..b7192517 100644
--- a/src/referencestrip.js
+++ b/src/referencestrip.js
@@ -1,15 +1,48 @@
+/*
+ * OpenSeadragon - ReferenceStrip
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
+(function ( $ ) {
-(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
+ * 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.
+ * source.
*
* This idea is a reexpression of the idea of dzi collections
* which allows a clearer algorithm to reuse the tile sources already
@@ -18,26 +51,29 @@ var THIS = {};
* 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
+ * 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 ){
+/**
+ * @class ReferenceStrip
+ * @memberof OpenSeadragon
+ * @param {Object} options
+ */
+$.ReferenceStrip = function ( options ) {
var _this = this,
viewer = options.viewer,
viewerSize = $.getElementSize( viewer.element ),
- miniViewer,
- minPixelRatio,
element,
style,
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());
+ if ( !options.id ) {
+ options.id = 'referencestrip-' + $.now();
this.element = $.makeNeutralElement( "div" );
this.element.id = options.id;
this.element.className = 'referencestrip';
@@ -57,17 +93,17 @@ $.ReferenceStrip = function( options ){
mouseNavEnabled: false,
showNavigationControl: false,
showSequenceControl: false
- });
+ } );
$.extend( this, options );
//Private state properties
- THIS[ this.id ] = {
- "animating": false
+ THIS[this.id] = {
+ "animating": false
};
this.minPixelRatio = this.viewer.minPixelRatio;
- style = this.element.style;
+ style = this.element.style;
style.marginTop = '0px';
style.marginRight = '0px';
style.marginBottom = '0px';
@@ -81,56 +117,56 @@ $.ReferenceStrip = function( options ){
$.setElementOpacity( this.element, 0.8 );
this.viewer = viewer;
- this.innerTracker = new $.MouseTracker({
+ 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 );
+ } ).setTracking( true );
- //Controls the position and orientation of the reference strip and sets the
+ //Controls the position and orientation of the reference strip and sets the
//appropriate width and height
- if( options.width && options.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
+ viewer.addControl(
+ this.element,
+ { anchor: $.ControlAnchor.BOTTOM_LEFT }
);
} else {
- if( "horizontal" == options.scroll ){
- this.element.style.width = (
- viewerSize.x *
- options.sizeRatio *
+ 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
+ this.element.style.height = (
+ viewerSize.y *
+ options.sizeRatio
) + 'px';
- viewer.addControl(
- this.element,
- $.ControlAnchor.BOTTOM_LEFT
+ viewer.addControl(
+ this.element,
+ { anchor: $.ControlAnchor.BOTTOM_LEFT }
);
- }else {
- this.element.style.height = (
- viewerSize.y *
- options.sizeRatio *
+ } 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
+ this.element.style.width = (
+ viewerSize.x *
+ options.sizeRatio
) + 'px';
- viewer.addControl(
- this.element,
- $.ControlAnchor.TOP_LEFT
+ viewer.addControl(
+ this.element,
+ { anchor: $.ControlAnchor.TOP_LEFT }
);
}
@@ -141,9 +177,9 @@ $.ReferenceStrip = function( options ){
this.panels = [];
/*jshint loopfunc:true*/
- for( i = 0; i < viewer.tileSources.length; i++ ){
-
- element = $.makeNeutralElement('div');
+ for ( i = 0; i < viewer.tileSources.length; i++ ) {
+
+ element = $.makeNeutralElement( 'div' );
element.id = this.element.id + "-" + i;
element.style.width = _this.panelWidth + 'px';
@@ -154,27 +190,28 @@ $.ReferenceStrip = function( options ){
element.style.styleFloat = 'left'; //IE
element.style.padding = '2px';
- element.innerTracker = new $.MouseTracker({
+ element.innerTracker = new $.MouseTracker( {
element: element,
- clickTimeThreshold: this.clickTimeThreshold,
+ clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
- pressHandler: function( tracker ){
- tracker.dragging = +new Date();
+ pressHandler: function ( event ) {
+ event.eventSource.dragging = $.now();
},
- releaseHandler: function( tracker, position, insideElementPress, insideElementRelease ){
- var id = tracker.element.id,
- page = Number( id.split( '-' )[ 2 ] ),
- now = +new Date();
-
- if ( insideElementPress &&
- insideElementRelease &&
+ releaseHandler: function ( event ) {
+ var tracker = event.eventSource,
+ id = tracker.element.id,
+ page = Number( id.split( '-' )[2] ),
+ now = $.now();
+
+ if ( event.insideElementPressed &&
+ event.insideElementReleased &&
tracker.dragging &&
- ( now - tracker.dragging ) < tracker.clickTimeThreshold ){
+ ( now - tracker.dragging ) < tracker.clickTimeThreshold ) {
tracker.dragging = null;
viewer.goToPage( page );
}
}
- }).setTracking( true );
+ } ).setTracking( true );
this.element.appendChild( element );
@@ -183,49 +220,52 @@ $.ReferenceStrip = function( options ){
this.panels.push( element );
}
- loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.y, 0);
+ loadPanels( this, this.scroll == 'vertical' ? viewerSize.y : viewerSize.y, 0 );
this.setFocus( 0 );
};
-$.extend( $.ReferenceStrip.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
+$.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.ReferenceStrip.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','')),
+ /**
+ * @function
+ */
+ 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 ){
+ if ( this.currentSelected !== element ) {
+ if ( this.currentSelected ) {
this.currentSelected.style.background = '#000';
}
this.currentSelected = element;
this.currentSelected.style.background = '#999';
- if( 'horizontal' == this.scroll ){
+ 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));
+ 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);
+ } 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));
+ } 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);
+ } else if ( offset < offsetTop ) {
+ offset = Math.max( 0, offset - viewerSize.y / 2 );
this.element.style.marginTop = -offset + 'px';
loadPanels( this, viewerSize.y, -offset );
}
@@ -233,24 +273,22 @@ $.extend( $.ReferenceStrip.prototype, $.EventHandler.prototype, $.Viewer.prototy
this.currentPage = page;
$.getElement( element.id + '-displayregion' ).focus();
- onStripEnter.call( this, this.innerTracker );
+ onStripEnter.call( this, { eventSource: this.innerTracker } );
}
},
+
/**
* @function
- * @name OpenSeadragon.ReferenceStrip.prototype.update
*/
- update: function( viewport ){
-
- if( THIS[ this.id ].animating ){
- $.console.log('image reference strip update');
+ update: function () {
+ if ( THIS[this.id].animating ) {
+ $.console.log( 'image reference strip update' );
return true;
}
return false;
-
}
-});
+} );
@@ -260,41 +298,41 @@ $.extend( $.ReferenceStrip.prototype, $.EventHandler.prototype, $.Viewer.prototy
* @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 );
+function onStripDrag( event ) {
+
+ 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 ) {
+ if ( 'horizontal' == this.scroll ) {
+ if ( -event.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) );
+ if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
+ this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
+ loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
}
- } else if ( -delta.x < 0 ) {
+ } else if ( -event.delta.x < 0 ) {
//reverse
- if( offsetLeft < 0 ){
- this.element.style.marginLeft = ( offsetLeft + (delta.x * 2) ) + 'px';
- loadPanels( this, viewerSize.x, offsetLeft + (delta.x * 2) );
+ if ( offsetLeft < 0 ) {
+ this.element.style.marginLeft = ( offsetLeft + ( event.delta.x * 2 ) ) + 'px';
+ loadPanels( this, viewerSize.x, offsetLeft + ( event.delta.x * 2 ) );
}
}
- }else{
- if ( -delta.y > 0 ) {
+ } else {
+ if ( -event.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) );
+ if ( offsetTop > -( scrollHeight - viewerSize.y ) ) {
+ this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
+ loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
}
- } else if ( -delta.y < 0 ) {
+ } else if ( -event.delta.y < 0 ) {
//reverse
- if( offsetTop < 0 ){
- this.element.style.marginTop = ( offsetTop + (delta.y * 2) ) + 'px';
- loadPanels( this, viewerSize.y, offsetTop + (delta.y * 2) );
+ if ( offsetTop < 0 ) {
+ this.element.style.marginTop = ( offsetTop + ( event.delta.y * 2 ) ) + 'px';
+ loadPanels( this, viewerSize.y, offsetTop + ( event.delta.y * 2 ) );
}
}
}
@@ -310,39 +348,39 @@ function onStripDrag( tracker, position, delta, shift ) {
* @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 );
+function onStripScroll( event ) {
+ 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 ) {
+ if ( 'horizontal' == this.scroll ) {
+ if ( event.scroll > 0 ) {
//forward
- if( offsetLeft > -(scrollWidth - viewerSize.x)){
- this.element.style.marginLeft = ( offsetLeft - (scroll * 60) ) + 'px';
- loadPanels( this, viewerSize.x, offsetLeft - (scroll * 60) );
+ if ( offsetLeft > -( scrollWidth - viewerSize.x ) ) {
+ this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
+ loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
}
- } else if ( scroll < 0 ) {
+ } else if ( event.scroll < 0 ) {
//reverse
- if( offsetLeft < 0 ){
- this.element.style.marginLeft = ( offsetLeft - (scroll * 60) ) + 'px';
- loadPanels( this, viewerSize.x, offsetLeft - (scroll * 60) );
+ if ( offsetLeft < 0 ) {
+ this.element.style.marginLeft = ( offsetLeft - ( event.scroll * 60 ) ) + 'px';
+ loadPanels( this, viewerSize.x, offsetLeft - ( event.scroll * 60 ) );
}
}
- }else{
- if ( scroll < 0 ) {
+ } else {
+ if ( event.scroll < 0 ) {
//scroll up
- if( offsetTop > viewerSize.y - scrollHeight ){
- this.element.style.marginTop = ( offsetTop + (scroll * 60) ) + 'px';
- loadPanels( this, viewerSize.y, offsetTop + (scroll * 60) );
+ if ( offsetTop > viewerSize.y - scrollHeight ) {
+ this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
+ loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
}
- } else if ( scroll > 0 ) {
+ } else if ( event.scroll > 0 ) {
//scroll dowm
- if( offsetTop < 0 ){
- this.element.style.marginTop = ( offsetTop + (scroll * 60) ) + 'px';
- loadPanels( this, viewerSize.y, offsetTop + (scroll * 60) );
+ if ( offsetTop < 0 ) {
+ this.element.style.marginTop = ( offsetTop + ( event.scroll * 60 ) ) + 'px';
+ loadPanels( this, viewerSize.y, offsetTop + ( event.scroll * 60 ) );
}
}
}
@@ -352,29 +390,30 @@ function onStripScroll( tracker, position, scroll, shift ) {
}
-function loadPanels(strip, viewerSize, scroll){
+function loadPanels( strip, viewerSize, scroll ) {
var panelSize,
activePanelsStart,
activePanelsEnd,
miniViewer,
style,
- i;
- if( 'horizontal' == strip.scroll ){
+ i,
+ element;
+ if ( 'horizontal' == strip.scroll ) {
panelSize = strip.panelWidth;
- }else{
+ } else {
panelSize = strip.panelHeight;
}
activePanelsStart = Math.ceil( viewerSize / panelSize ) + 5;
- activePanelsEnd = Math.ceil( (Math.abs(scroll) + viewerSize ) / panelSize ) + 1;
+ 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 ){
+ 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 ] ],
+ tileSources: [strip.viewer.tileSources[i]],
element: element,
navigatorSizeRatio: strip.sizeRatio,
showNavigator: false,
@@ -384,13 +423,13 @@ function loadPanels(strip, viewerSize, scroll){
immediateRender: true,
blendTime: 0,
animationTime: 0
- } );
+ } );
miniViewer.displayRegion = $.makeNeutralElement( "textarea" );
miniViewer.displayRegion.id = element.id + '-displayregion';
miniViewer.displayRegion.className = 'displayregion';
- style = miniViewer.displayRegion.style;
+ style = miniViewer.displayRegion.style;
style.position = 'relative';
style.top = '0px';
style.left = '0px';
@@ -404,12 +443,12 @@ function loadPanels(strip, viewerSize, scroll){
style.width = ( strip.panelWidth - 4 ) + 'px';
style.height = ( strip.panelHeight - 4 ) + 'px';
- miniViewer.displayRegion.innerTracker = new $.MouseTracker({
- element: miniViewer.displayRegion
- });
+ miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
+ element: miniViewer.displayRegion
+ } );
- element.getElementsByTagName('form')[ 0 ].appendChild(
- miniViewer.displayRegion
+ element.getElementsByTagName( 'div' )[0].appendChild(
+ miniViewer.displayRegion
);
element.activePanel = true;
@@ -423,22 +462,23 @@ function loadPanels(strip, viewerSize, scroll){
* @inner
* @function
*/
-function onStripEnter( tracker ) {
+function onStripEnter( event ) {
+ var element = event.eventSource.element;
+
+ //$.setElementOpacity(element, 0.8);
- //$.setElementOpacity(tracker.element, 0.8);
+ //element.style.border = '1px solid #555';
+ //element.style.background = '#000';
- //tracker.element.style.border = '1px solid #555';
- //tracker.element.style.background = '#000';
+ if ( 'horizontal' == this.scroll ) {
- if( 'horizontal' == this.scroll ){
-
- //tracker.element.style.paddingTop = "0px";
- tracker.element.style.marginBottom = "0px";
+ //element.style.paddingTop = "0px";
+ element.style.marginBottom = "0px";
} else {
-
- //tracker.element.style.paddingRight = "0px";
- tracker.element.style.marginLeft = "0px";
+
+ //element.style.paddingRight = "0px";
+ element.style.marginLeft = "0px";
}
return false;
@@ -450,25 +490,19 @@ function onStripEnter( tracker ) {
* @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';
+function onStripExit( event ) {
+ var element = event.eventSource.element;
+ if ( 'horizontal' == this.scroll ) {
+
+ //element.style.paddingTop = "10px";
+ element.style.marginBottom = "-" + ( $.getElementSize( element ).y / 2 ) + "px";
- 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";
-
+
+ //element.style.paddingRight = "10px";
+ element.style.marginLeft = "-" + ( $.getElementSize( element ).x / 2 ) + "px";
+
}
return false;
}
@@ -480,41 +514,41 @@ function onStripExit( tracker ) {
* @inner
* @function
*/
-function onKeyPress( tracker, keyCode, shiftKey ){
- //console.log( keyCode );
+function onKeyPress( event ) {
+ //console.log( event.keyCode );
- switch( keyCode ){
- case 61://=|+
- onStripScroll.call(this, this.tracker, null, 1, null);
+ switch ( event.keyCode ) {
+ case 61: //=|+
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
return false;
- case 45://-|_
- onStripScroll.call(this, this.tracker, null, -1, null);
+ case 45: //-|_
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
return false;
- case 48://0|)
- case 119://w
- case 87://W
- case 38://up arrow
- onStripScroll.call(this, this.tracker, null, 1, null);
+ case 48: //0|)
+ case 119: //w
+ case 87: //W
+ case 38: //up arrow
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
return false;
- case 115://s
- case 83://S
- case 40://down arrow
- onStripScroll.call(this, this.tracker, null, -1, null);
+ case 115: //s
+ case 83: //S
+ case 40: //down arrow
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
return false;
- case 97://a
- case 37://left arrow
- onStripScroll.call(this, this.tracker, null, -1, null);
+ case 97: //a
+ case 37: //left arrow
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
return false;
- case 100://d
- case 39://right arrow
- onStripScroll.call(this, this.tracker, null, 1, null);
+ case 100: //d
+ case 39: //right arrow
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
return false;
default:
- //console.log( 'navigator keycode %s', keyCode );
+ //console.log( 'navigator keycode %s', event.keyCode );
return true;
}
}
-}( OpenSeadragon ));
\ No newline at end of file
+} ( OpenSeadragon ) );
diff --git a/src/spring.js b/src/spring.js
index cdf96fa4..4f92dfcb 100644
--- a/src/spring.js
+++ b/src/spring.js
@@ -1,62 +1,116 @@
+/*
+ * OpenSeadragon - Spring
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
-
+
/**
- * @class
+ * @class Spring
+ * @memberof OpenSeadragon
* @param {Object} options - Spring configuration settings.
- * @param {Number} options.initial - Initial value of spring, default to 0 so
+ * @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
+ //allows backward compatible use of ( initialValue, config ) as
//constructor parameters
options = {
- initial: args.length && typeof ( args[ 0 ] ) == "number" ?
- args[ 0 ] :
+ initial: args.length && typeof ( args[ 0 ] ) == "number" ?
+ args[ 0 ] :
0,
- springStiffness: args.length > 1 ?
- args[ 1 ].springStiffness :
+ /**
+ * Spring stiffness.
+ * @member {Number} springStiffness
+ * @memberof OpenSeadragon.Spring#
+ */
+ springStiffness: args.length > 1 ?
+ args[ 1 ].springStiffness :
5.0,
- animationTime: args.length > 1 ?
- args[ 1 ].animationTime :
+ /**
+ * Animation duration per spring.
+ * @member {Number} animationTime
+ * @memberof OpenSeadragon.Spring#
+ */
+ animationTime: args.length > 1 ?
+ args[ 1 ].animationTime :
1.5
};
}
$.extend( true, this, options);
-
+ /**
+ * @member {Object} current
+ * @memberof OpenSeadragon.Spring#
+ * @property {Number} value
+ * @property {Number} time
+ */
this.current = {
- value: typeof ( this.initial ) == "number" ?
- this.initial :
+ value: typeof ( this.initial ) == "number" ?
+ this.initial :
0,
- time: new Date().getTime() // always work in milliseconds
+ time: $.now() // always work in milliseconds
};
+ /**
+ * @member {Object} start
+ * @memberof OpenSeadragon.Spring#
+ * @property {Number} value
+ * @property {Number} time
+ */
this.start = {
value: this.current.value,
time: this.current.time
};
+ /**
+ * @member {Object} target
+ * @memberof OpenSeadragon.Spring#
+ * @property {Number} value
+ * @property {Number} time
+ */
this.target = {
value: this.current.value,
time: this.current.time
};
};
-$.Spring.prototype = {
+$.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
/**
* @function
@@ -93,14 +147,14 @@ $.Spring.prototype = {
* @function
*/
update: function() {
- this.current.time = new Date().getTime();
- this.current.value = (this.current.time >= this.target.time) ?
+ this.current.time = $.now();
+ this.current.value = (this.current.time >= this.target.time) ?
this.target.value :
- this.start.value +
+ this.start.value +
( this.target.value - this.start.value ) *
- transform(
- this.springStiffness,
- ( this.current.time - this.start.time ) /
+ transform(
+ this.springStiffness,
+ ( this.current.time - this.start.time ) /
( this.target.time - this.start.time )
);
}
@@ -110,7 +164,7 @@ $.Spring.prototype = {
* @private
*/
function transform( stiffness, x ) {
- return ( 1.0 - Math.exp( stiffness * -x ) ) /
+ return ( 1.0 - Math.exp( stiffness * -x ) ) /
( 1.0 - Math.exp( -stiffness ) );
}
diff --git a/src/strings.js b/src/strings.js
index 22a4cb48..03f4ed42 100644
--- a/src/strings.js
+++ b/src/strings.js
@@ -1,26 +1,52 @@
+/*
+ * OpenSeadragon - getString/setString
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
(function( $ ){
-
-//TODO: I guess this is where the i18n needs to be reimplemented. I'll look
+
+//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..."
+ OpenFailed: "Unable to open {0}: {1}"
},
Tooltips: {
@@ -29,19 +55,20 @@ var I18N = {
ZoomIn: "Zoom in",
ZoomOut: "Zoom out",
NextPage: "Next page",
- PreviousPage: "Previous page"
+ PreviousPage: "Previous page",
+ RotateLeft: "Rotate left",
+ RotateRight: "Rotate right"
}
};
-$.extend( $, {
+$.extend( $, /** @lends OpenSeadragon */{
/**
* @function
- * @name OpenSeadragon.getString
* @param {String} property
*/
getString: function( prop ) {
-
+
var props = prop.split('.'),
string = null,
args = arguments,
@@ -55,27 +82,27 @@ $.extend( $, {
string = container[ props[ i ] ];
if ( typeof( string ) != "string" ) {
- string = "";
+ $.console.debug( "Untranslated source string:", prop );
+ string = ""; // FIXME: this breaks gettext()-style convention, which would return source
}
return string.replace(/\{\d+\}/g, function(capture) {
var i = parseInt( capture.match( /\d+/ ), 10 ) + 1;
- return i < args.length ?
- args[ i ] :
+ 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,
+ container = I18N,
i;
for ( i = 0; i < props.length - 1; i++ ) {
diff --git a/src/tile.js b/src/tile.js
index d6ba9f87..9aa4bde4 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -1,67 +1,181 @@
+/*
+ * OpenSeadragon - Tile
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
var TILE_CACHE = {};
/**
- * @class
+ * @class Tile
+ * @memberof OpenSeadragon
* @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
+ * @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
+ * @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) {
+ /**
+ * The zoom level this tile belongs to.
+ * @member {Number} level
+ * @memberof OpenSeadragon.Tile#
+ */
this.level = level;
+ /**
+ * The vector component 'x'.
+ * @member {Number} x
+ * @memberof OpenSeadragon.Tile#
+ */
this.x = x;
+ /**
+ * The vector component 'y'.
+ * @member {Number} y
+ * @memberof OpenSeadragon.Tile#
+ */
this.y = y;
+ /**
+ * Where this tile fits, in normalized coordinates
+ * @member {OpenSeadragon.Point} bounds
+ * @memberof OpenSeadragon.Tile#
+ */
this.bounds = bounds;
+ /**
+ * Is this tile a part of a sparse image? Also has this tile failed to load?
+ * @member {Boolean} exists
+ * @memberof OpenSeadragon.Tile#
+ */
this.exists = exists;
+ /**
+ * The URL of this tile's image.
+ * @member {String} url
+ * @memberof OpenSeadragon.Tile#
+ */
this.url = url;
+ /**
+ * Is this tile loaded?
+ * @member {Boolean} loaded
+ * @memberof OpenSeadragon.Tile#
+ */
this.loaded = false;
+ /**
+ * Is this tile loading?
+ * @member {Boolean} loading
+ * @memberof OpenSeadragon.Tile#
+ */
this.loading = false;
+ /**
+ * The HTML div element for this tile
+ * @member {Element} element
+ * @memberof OpenSeadragon.Tile#
+ */
this.element = null;
+ /**
+ * The HTML img element for this tile.
+ * @member {Element} imgElement
+ * @memberof OpenSeadragon.Tile#
+ */
+ this.imgElement = null;
+ /**
+ * The Image object for this tile.
+ * @member {Object} image
+ * @memberof OpenSeadragon.Tile#
+ */
this.image = null;
+ /**
+ * The alias of this.element.style.
+ * @member {String} style
+ * @memberof OpenSeadragon.Tile#
+ */
this.style = null;
+ /**
+ * This tile's position on screen, in pixels.
+ * @member {OpenSeadragon.Point} position
+ * @memberof OpenSeadragon.Tile#
+ */
this.position = null;
+ /**
+ * This tile's size on screen, in pixels.
+ * @member {OpenSeadragon.Point} size
+ * @memberof OpenSeadragon.Tile#
+ */
this.size = null;
+ /**
+ * The start time of this tile's blending.
+ * @member {Number} blendStart
+ * @memberof OpenSeadragon.Tile#
+ */
this.blendStart = null;
+ /**
+ * The current opacity this tile should be.
+ * @member {Number} opacity
+ * @memberof OpenSeadragon.Tile#
+ */
this.opacity = null;
+ /**
+ * The distance of this tile to the viewport center.
+ * @member {Number} distance
+ * @memberof OpenSeadragon.Tile#
+ */
this.distance = null;
+ /**
+ * The visibility score of this tile.
+ * @member {Number} visibility
+ * @memberof OpenSeadragon.Tile#
+ */
this.visibility = null;
+ /**
+ * Whether this tile is currently being drawn.
+ * @member {Boolean} beingDrawn
+ * @memberof OpenSeadragon.Tile#
+ */
this.beingDrawn = false;
+ /**
+ * Timestamp the tile was last touched.
+ * @member {Number} lastTouchTime
+ * @memberof OpenSeadragon.Tile#
+ */
this.lastTouchTime = 0;
};
-$.Tile.prototype = {
-
+$.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
+
/**
- * Provides a string representation of this tiles level and (x,y)
+ * Provides a string representation of this tiles level and (x,y)
* components.
* @function
* @returns {String}
@@ -76,9 +190,6 @@ $.Tile.prototype = {
* @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.",
@@ -87,33 +198,16 @@ $.Tile.prototype = {
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("img");
- this.element.src = this.url;
- this.element.style.msInterpolationMode = "nearest-neighbor";
+ this.element = $.makeNeutralElement( "div" );
+ this.imgElement = $.makeNeutralElement( "img" );
+ this.imgElement.src = this.url;
+ this.imgElement.style.msInterpolationMode = "nearest-neighbor";
+ this.imgElement.style.width = "100%";
+ this.imgElement.style.height = "100%";
this.style = this.element.style;
this.style.position = "absolute";
@@ -121,23 +215,26 @@ $.Tile.prototype = {
if ( this.element.parentNode != container ) {
container.appendChild( this.element );
}
+ if ( this.imgElement.parentNode != this.element ) {
+ this.element.appendChild( this.imgElement );
+ }
+
+ this.style.top = this.position.y + "px";
+ this.style.left = this.position.x + "px";
+ this.style.height = this.size.y + "px";
+ this.style.width = this.size.x + "px";
- 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.element, this.opacity );
-
-
},
/**
* Renders the tile in a canvas-based context.
* @function
* @param {Canvas} context
+ * @param {Function} method for firing the drawing event. drawingHandler({context, tile, rendered})
+ * where rendered is the context with the pre-drawn image.
*/
- drawCanvas: function( context ) {
+ drawCanvas: function( context, drawingHandler ) {
var position = this.position,
size = this.size,
@@ -162,11 +259,11 @@ $.Tile.prototype = {
if( context.globalAlpha == 1 && this.url.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.clearRect(
+ position.x+1,
+ position.y+1,
+ size.x-2,
+ size.y-2
);
}
@@ -184,18 +281,21 @@ $.Tile.prototype = {
}
rendered = TILE_CACHE[ this.url ];
-
+
+ // This gives the application a chance to make image manipulation changes as we are rendering the image
+ drawingHandler({context: context, tile: this, rendered: rendered});
+
//rendered.save();
- context.drawImage(
- rendered.canvas,
+ context.drawImage(
+ rendered.canvas,
0,
- 0,
- rendered.canvas.width,
- rendered.canvas.height,
- position.x,
- position.y,
- size.x,
- size.y
+ 0,
+ rendered.canvas.width,
+ rendered.canvas.height,
+ position.x,
+ position.y,
+ size.x,
+ size.y
);
//rendered.restore();
@@ -203,21 +303,25 @@ $.Tile.prototype = {
},
/**
- * Removes tile from it's contianer.
+ * Removes tile from its container.
* @function
*/
unload: function() {
+ if ( this.imgElement && this.imgElement.parentNode ) {
+ this.imgElement.parentNode.removeChild( this.imgElement );
+ }
if ( this.element && this.element.parentNode ) {
this.element.parentNode.removeChild( this.element );
- }
+ }
if ( TILE_CACHE[ this.url ]){
delete TILE_CACHE[ this.url ];
}
- this.element = null;
- this.image = null;
- this.loaded = false;
- this.loading = false;
+ this.element = null;
+ this.imgElement = null;
+ this.image = null;
+ this.loaded = false;
+ this.loading = false;
}
};
diff --git a/src/tilesource.js b/src/tilesource.js
index 650a9477..90a80f7c 100644
--- a/src/tilesource.js
+++ b/src/tilesource.js
@@ -1,25 +1,61 @@
+/*
+ * OpenSeadragon - TileSource
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(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
+ * @class TileSource
+ * @classdesc 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,
+ * 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').
*
* 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
+ * side in M (in pixels), where N is the smallest integer which satisfies
* 2^(N+1) >= M.
- * @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
+ *
+ * @memberof OpenSeadragon
+ * @extends OpenSeadragon.EventSource
+ * @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 its 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
@@ -28,7 +64,7 @@
* 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
+ * 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.
@@ -36,23 +72,9 @@
* 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,
+ var callback = null,
args = arguments,
options,
i;
@@ -71,8 +93,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
}
//Tile sources supply some events, namely 'ready' when they must be configured
- //by asyncronously fetching their configuration data.
- $.EventHandler.call( this );
+ //by asynchronously fetching their configuration data.
+ $.EventSource.call( this );
//we allow options to override anything we dont treat as
//required via idiomatic options or which is functionally
@@ -82,17 +104,53 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//Any functions that are passed as arguments are bound to the ready callback
/*jshint loopfunc:true*/
- for( i = 0; i < arguments.length; i++ ){
- if( $.isFunction( arguments[i] ) ){
+ for ( i = 0; i < arguments.length; i++ ) {
+ if ( $.isFunction( arguments[ i ] ) ) {
callback = arguments[ i ];
- this.addHandler( 'ready', function( placeHolderSource, readySource ){
- callback( readySource );
- });
+ this.addHandler( 'ready', function ( event ) {
+ callback( event );
+ } );
//only one callback per constructor
break;
}
}
+ /**
+ * Ratio of width to height
+ * @member {Number} aspectRatio
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ * Vector storing x and y dimensions ( width and height respectively ).
+ * @member {OpenSeadragon.Point} dimensions
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ * The size of the image tiles used to compose the image.
+ * @member {Number} tileSize
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ * The overlap in pixels each tile shares with its adjacent neighbors.
+ * @member {Number} tileOverlap
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ * The minimum pyramid level this tile source supports or should attempt to load.
+ * @member {Number} minLevel
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ * The maximum pyramid level this tile source supports or should attempt to load.
+ * @member {Number} maxLevel
+ * @memberof OpenSeadragon.TileSource#
+ */
+ /**
+ *
+ * @member {Boolean} ready
+ * @memberof OpenSeadragon.TileSource#
+ */
+
if( 'string' == $.type( arguments[ 0 ] ) ){
//in case the getImageInfo method is overriden and/or implies an
//async mechanism set some safe defaults first
@@ -103,26 +161,26 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.minLevel = 0;
this.maxLevel = 0;
this.ready = false;
- //configuration via url implies the extending class
+ //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 ) ?
+ 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 ) ?
+ 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 )
+ ( options.width && options.height ) ? Math.ceil(
+ Math.log( Math.max( options.width, options.height ) ) /
+ Math.log( 2 )
) : 0
);
if( callback && $.isFunction( callback ) ){
@@ -134,8 +192,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
};
-$.TileSource.prototype = {
-
+$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
+
/**
* @function
* @param {Number} level
@@ -230,7 +288,7 @@ $.TileSource.prototype = {
return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
},
-
+
/**
* Responsible for retrieving, and caching the
@@ -240,16 +298,14 @@ $.TileSource.prototype = {
* @throws {Error}
*/
getImageInfo: function( url ) {
- var _this = this,
- error,
+ var _this = this,
callbackName,
callback,
readySource,
options,
urlParts,
filename,
- lastDot,
- tilesUrl;
+ lastDot;
if( url ) {
@@ -260,13 +316,42 @@ $.TileSource.prototype = {
urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot );
}
}
-
+
callback = function( data ){
+ if( typeof(data) === "string" ) {
+ data = $.parseXml( data );
+ }
var $TileSource = $.TileSource.determineType( _this, data, url );
+ if ( !$TileSource ) {
+ /**
+ * Raised when an error occurs loading a TileSource.
+ *
+ * @event open-failed
+ * @memberof OpenSeadragon.TileSource
+ * @type {object}
+ * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
+ * @property {String} message
+ * @property {String} source
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } );
+ return;
+ }
+
options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
readySource = new $TileSource( options );
_this.ready = true;
- _this.raiseEvent( 'ready', readySource );
+ /**
+ * Raised when a TileSource is opened and initialized.
+ *
+ * @event ready
+ * @memberof OpenSeadragon.TileSource
+ * @type {object}
+ * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
+ * @property {Object} tileSource
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'ready', { tileSource: readySource } );
};
if( url.match(/\.js$/) ){
@@ -281,10 +366,46 @@ $.TileSource.prototype = {
callback: callback
});
} else {
- // request info via xhr asyncronously.
+ // request info via xhr asynchronously.
$.makeAjaxRequest( url, function( xhr ) {
var data = processResponse( xhr );
callback( data );
+ }, function ( xhr, exc ) {
+ var msg;
+
+ /*
+ IE < 10 will block XHR requests to different origins. Any property access on the request
+ object will raise an exception which we'll attempt to handle by formatting the original
+ exception rather than the second one raised when we try to access xhr.status
+ */
+ try {
+ msg = "HTTP " + xhr.status + " attempting to load TileSource";
+ } catch ( e ) {
+ var formattedExc;
+ if ( typeof( exc ) == "undefined" || !exc.toString ) {
+ formattedExc = "Unknown error";
+ } else {
+ formattedExc = exc.toString();
+ }
+
+ msg = formattedExc + " attempting to load TileSource";
+ }
+
+ /***
+ * Raised when an error occurs loading a TileSource.
+ *
+ * @event open-failed
+ * @memberof OpenSeadragon.TileSource
+ * @type {object}
+ * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
+ * @property {String} message
+ * @property {String} source
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'open-failed', {
+ message: msg,
+ source: url
+ });
});
}
@@ -300,7 +421,7 @@ $.TileSource.prototype = {
* and sufficient mechanisim for clear determination.
* @function
* @param {String|Object|Array|Document} data
- * @param {String} url - the url the data was loaded
+ * @param {String} url - the url the data was loaded
* from if any.
* @return {Boolean}
*/
@@ -312,14 +433,14 @@ $.TileSource.prototype = {
* 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
+ * 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
+ * @param {String} url - the url the data was loaded
* from if any.
- * @return {Object} options - A dictionary of keyword arguments sufficient
+ * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
* @throws {Error}
*/
@@ -328,10 +449,10 @@ $.TileSource.prototype = {
},
/**
- * Responsible for retriving the url which will return an image for the
+ * 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
+ * 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
@@ -352,23 +473,23 @@ $.TileSource.prototype = {
*/
tileExists: function( level, x, y ) {
var numTiles = this.getNumTiles( level );
- return level >= this.minLevel &&
+ return level >= this.minLevel &&
level <= this.maxLevel &&
- x >= 0 &&
- y >= 0 &&
- x < numTiles.x &&
+ x >= 0 &&
+ y >= 0 &&
+ x < numTiles.x &&
y < numTiles.y;
}
};
-$.extend( true, $.TileSource.prototype, $.EventHandler.prototype );
+$.extend( true, $.TileSource.prototype, $.EventSource.prototype );
/**
* Decides whether to try to process the response as xml, json, or hand back
* the text
- * @eprivate
+ * @private
* @inner
* @function
* @param {XMLHttpRequest} xhr - the completed network request
@@ -383,16 +504,16 @@ function processResponse( xhr ){
throw new Error( $.getString( "Errors.Security" ) );
} else if ( xhr.status !== 200 && xhr.status !== 0 ) {
status = xhr.status;
- statusText = ( status == 404 ) ?
- "Not Found" :
+ 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 :
+ data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
+ xhr.responseXML :
$.parseXml( responseText );
} catch (e){
data = xhr.responseText;
@@ -410,7 +531,7 @@ function processResponse( xhr ){
/**
* Determines the TileSource Implementation by introspection of OpenSeadragon
* namespace, calling each TileSource implementation of 'isType'
- * @eprivate
+ * @private
* @inner
* @function
* @param {Object|Array|Document} data - the tile source configuration object
@@ -428,6 +549,8 @@ $.TileSource.determineType = function( tileSource, data, url ){
return OpenSeadragon[ property ];
}
}
+
+ $.console.error( "No TileSource was able to open %s %s", url, data );
};
diff --git a/src/tilesourcecollection.js b/src/tilesourcecollection.js
index a64d6d0c..52dcbd67 100644
--- a/src/tilesourcecollection.js
+++ b/src/tilesourcecollection.js
@@ -1,12 +1,47 @@
+/*
+ * OpenSeadragon - TileSourceCollection
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
-
+
/**
- * @class
+ * @class TileSourceCollection
+ * @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
- */
+ */
$.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
-
-
+ var options;
+
if( $.isPlainObject( tileSize ) ){
options = tileSize;
}else{
@@ -51,18 +86,17 @@ $.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
options.minLevel = minLevel;
//for( var name in options ){
- // $.console.log( 'Collection %s %s', name, options[ name ] );
+ // $.console.log( 'Collection %s %s', name, options[ name ] );
//}
$.TileSource.apply( this, [ options ] );
};
-$.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
+$.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TileSourceCollection.prototype */{
/**
* @function
- * @name OpenSeadragon.TileSourceCollection.prototype.getTileBounds
* @param {Number} level
* @param {Number} x
* @param {Number} y
@@ -82,9 +116,8 @@ $.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
},
/**
- *
+ *
* @function
- * @name OpenSeadragon.TileSourceCollection.prototype.configure
*/
configure: function( data, url ){
return;
@@ -93,7 +126,6 @@ $.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
/**
* @function
- * @name OpenSeadragon.TileSourceCollection.prototype.getTileUrl
* @param {Number} level
* @param {Number} x
* @param {Number} y
@@ -104,7 +136,7 @@ $.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, {
}
-
+
});
diff --git a/src/tmstilesource.js b/src/tmstilesource.js
index 1ab550fc..4c3d0ab0 100644
--- a/src/tmstilesource.js
+++ b/src/tmstilesource.js
@@ -1,12 +1,54 @@
-(function( $ ){
-
-/**
- * A tilesource implementation for Tiled Map Services (TMS). Adopted from Rainer Simon
- * project http://github.com/rsimon/seajax-utils. TMS tile
- * scheme ( [ as supported by OpenLayers ] is described here
- * ( http://openlayers.org/dev/examples/tms.html ) )
+/*
+ * OpenSeadragon - TmsTileSource
*
- * @class
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
+/*
+ * Derived from the TMS tile source in Rainer Simon's seajax-utils project
+ * . Rainer Simon has contributed
+ * the included code to the OpenSeadragon project under the New BSD license;
+ * see .
+ */
+
+
+(function( $ ){
+
+/**
+ * @class TmsTileSource
+ * @classdesc A tilesource implementation for Tiled Map Services (TMS).
+ * TMS tile scheme ( [ as supported by OpenLayers ] is described here
+ * ( http://openlayers.org/dev/examples/tms.html ).
+ *
+ * @memberof OpenSeadragon
* @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.
@@ -14,7 +56,7 @@
* @param {Number} tileSize
* @param {Number} tileOverlap
* @param {String} tilesUrl
- */
+ */
$.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
var options;
@@ -45,19 +87,18 @@ $.TmsTileSource = function( width, height, tileSize, tileOverlap, tilesUrl ) {
options.tileSize = 256;
options.width = bufferedWidth;
options.height = bufferedHeight;
-
+
$.TileSource.apply( this, [ options ] );
};
-$.extend( $.TmsTileSource.prototype, $.TileSource.prototype, {
+$.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TmsTileSource.prototype */{
/**
* Determine if the data and/or url imply the image service is supported by
* this tile source.
* @function
- * @name OpenSeadragon.TmsTileSource.prototype.supports
* @param {Object|Array} data
* @param {String} optional - url
*/
@@ -66,12 +107,11 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, {
},
/**
- *
+ *
* @function
- * @name OpenSeadragon.TmsTileSource.prototype.configure
* @param {Object} 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
+ * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
*/
configure: function( data, url ){
@@ -81,7 +121,6 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, {
/**
* @function
- * @name OpenSeadragon.TmsTileSource.prototype.getTileUrl
* @param {Number} level
* @param {Number} x
* @param {Number} y
@@ -95,4 +134,4 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, {
});
-}( OpenSeadragon ));
\ No newline at end of file
+}( OpenSeadragon ));
diff --git a/src/viewer.js b/src/viewer.js
index a87c52e9..a77cd46f 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -1,10 +1,44 @@
+/*
+ * OpenSeadragon - Viewer
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
-
+
// dictionary from hash to private properties
var THIS = {},
// We keep a list of viewers so we can 'wake-up' each viewer on
// a page after toggling between fullpage modes
- VIEWERS = {};
+ VIEWERS = {};
/**
*
@@ -16,20 +50,15 @@ var THIS = {},
* The options below are given in order that they appeared in the constructor
* as arguments and we translate a positional call into an idiomatic call.
*
- * @class
- * @extends OpenSeadragon.EventHandler
- * @extends OpenSeadragon.ControlDock
- * @param {Object} options
- * @param {String} options.element Id of Element to attach to,
- * @param {String} options.xmlPath Xpath ( TODO: not sure! ),
- * @param {String} options.prefixUrl Url used to prepend to paths, eg button
- * images, etc.
- * @param {Seadragon.Controls[]} options.controls Array of Seadragon.Controls,
- * @param {Seadragon.Overlays[]} options.overlays Array of Seadragon.Overlays,
- * @param {Seadragon.Controls[]} options.overlayControls An Array of ( TODO:
- * not sure! )
+ * @class Viewer
+ * @classdesc The main OpenSeadragon viewer class.
*
- **/
+ * @memberof OpenSeadragon
+ * @extends OpenSeadragon.EventSource
+ * @extends OpenSeadragon.ControlDock
+ * @param {OpenSeadragon.Options} options - Viewer options.
+ *
+ **/
$.Viewer = function( options ) {
var args = arguments,
@@ -37,7 +66,7 @@ $.Viewer = function( options ) {
i;
- //backward compatibility for positional args while prefering more
+ //backward compatibility for positional args while prefering more
//idiomatic javascript options object as the only argument
if( !$.isPlainObject( options ) ){
options = {
@@ -45,8 +74,7 @@ $.Viewer = function( options ) {
xmlPath: args.length > 1 ? args[ 1 ] : undefined,
prefixUrl: args.length > 2 ? args[ 2 ] : undefined,
controls: args.length > 3 ? args[ 3 ] : undefined,
- overlays: args.length > 4 ? args[ 4 ] : undefined,
- overlayControls: args.length > 5 ? args[ 5 ] : undefined
+ overlays: args.length > 4 ? args[ 4 ] : undefined
};
}
@@ -57,23 +85,51 @@ $.Viewer = function( options ) {
$.extend( true, options, options.config );
delete options.config;
}
-
+
//Public properties
//Allow the options object to override global defaults
- $.extend( true, this, {
+ $.extend( true, this, {
//internal state and dom identifiers
id: options.id,
- hash: options.id,
+ hash: options.hash || options.id,
//dom nodes
+ /**
+ * The parent element of this Viewer instance, passed in when the Viewer was created.
+ * @member {Element} element
+ * @memberof OpenSeadragon.Viewer#
+ */
element: null,
- canvas: null,
+ /**
+ * A <div> element (provided by {@link OpenSeadragon.ControlDock}), the base element of this Viewer instance.
+ * Child element of {@link OpenSeadragon.Viewer#element}.
+ * @member {Element} container
+ * @memberof OpenSeadragon.Viewer#
+ */
container: null,
+ /**
+ * A <textarea> element, the element where keyboard events are handled.
+ * Child element of {@link OpenSeadragon.Viewer#container},
+ * positioned below {@link OpenSeadragon.Viewer#canvas}.
+ * @member {Element} keyboardCommandArea
+ * @memberof OpenSeadragon.Viewer#
+ */
+ keyboardCommandArea: null,
+ /**
+ * A <div> element, the element where user-input events are handled for panning and zooming.
+ * Child element of {@link OpenSeadragon.Viewer#container},
+ * positioned on top of {@link OpenSeadragon.Viewer#keyboardCommandArea}.
+ * The parent of {@link OpenSeadragon.Drawer#canvas} instances.
+ * @member {Element} canvas
+ * @memberof OpenSeadragon.Viewer#
+ */
+ canvas: null,
- //TODO: not sure how to best describe these
- overlays: [],
- overlayControls:[],
+ // Overlays list. An overlay allows to add html on top of the viewer.
+ overlays: [],
+ // Container inside the canvas where overlays are drawn.
+ overlaysContainer: null,
//private state properties
previousBody: [],
@@ -87,15 +143,31 @@ $.Viewer = function( options ) {
customControls: [],
//These are originally not part options but declared as members
- //in initialize. Its still considered idiomatic to put them here
+ //in initialize. It's still considered idiomatic to put them here
source: null,
- drawer: null,
- drawers: [],
+ /**
+ * Handles rendering of tiles in the viewer. Created for each TileSource opened.
+ * @member {OpenSeadragon.Drawer} drawer
+ * @memberof OpenSeadragon.Viewer#
+ */
+ drawer: null,
+ drawers: [],
+ // Container inside the canvas where drawers (layers) are drawn.
+ drawersContainer: null,
+ /**
+ * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
+ * @member {OpenSeadragon.Viewport} viewport
+ * @memberof OpenSeadragon.Viewer#
+ */
viewport: null,
- navigator: null,
+ /**
+ * @member {OpenSeadragon.Navigator} navigator
+ * @memberof OpenSeadragon.Viewer#
+ */
+ navigator: null,
- //A collection viewport is a seperate viewport used to provide
- //simultanious rendering of sets of tiless
+ //A collection viewport is a separate viewport used to provide
+ //simultaneous rendering of sets of tiles
collectionViewport: null,
collectionDrawer: null,
@@ -104,19 +176,26 @@ $.Viewer = function( options ) {
navImages: null,
//interface button controls
- buttons: null,
+ buttons: null,
//TODO: this is defunct so safely remove it
profiler: null
}, $.DEFAULT_SETTINGS, options );
+ if ( typeof( this.hash) === "undefined" ) {
+ throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
+ }
+ if ( typeof( THIS[ this.hash ] ) !== "undefined" ) {
+ // We don't want to throw an error here, as the user might have discarded
+ // the previous viewer with the same hash and now want to recreate it.
+ $.console.warn("Hash " + this.hash + " has already been used.");
+ }
+
//Private state properties
THIS[ this.hash ] = {
"fsBoundsDelta": new $.Point( 1, 1 ),
"prevContainerSize": null,
- "lastOpenStartTime": 0,
- "lastOpenEndTime": 0,
"animating": false,
"forceRedraw": false,
"mouseInside": false,
@@ -124,22 +203,30 @@ $.Viewer = function( options ) {
// whether we should be continuously zooming
"zooming": false,
// how much we should be continuously zooming by
- "zoomFactor": null,
+ "zoomFactor": null,
"lastZoomTime": null,
// did we decide this viewer has a sequence of tile sources
"sequenced": false,
"sequence": 0,
- "fullPage": false,
+ "fullPage": false,
"onfullscreenchange": null
};
+ this._updateRequestId = null;
+ this.currentOverlays = [];
+
//Inherit some behaviors and properties
- $.EventHandler.call( this );
+ $.EventSource.call( this );
+
+ this.addHandler( 'open-failed', function ( event ) {
+ var msg = $.getString( "Errors.OpenFailed", event.eventSource, event.message);
+ _this._showMessage( msg );
+ });
+
$.ControlDock.call( this, options );
//Deal with tile sources
- var initialTileSource,
- customTileSource;
+ var initialTileSource;
if ( this.xmlPath ){
//Deprecated option. Now it is preferred to use the tileSources option
@@ -148,52 +235,76 @@ $.Viewer = function( options ) {
if ( this.tileSources ){
// tileSources is a complex option...
- //
+ //
// It can be a string, object, or an array of any of strings and objects.
- // At this point we only care about if it is an Array or not.
+ // At this point we only care about if it is an Array or not.
//
if( $.isArray( this.tileSources ) ){
-
+
//must be a sequence of tileSource since the first item
//is a legacy tile source
- if( this.tileSources.length > 1 ){
+ if( this.tileSources.length > 1 ){
THIS[ this.hash ].sequenced = true;
- }
- initialTileSource = this.tileSources[ 0 ];
+ }
+
+ //Keeps the initial page within bounds
+ if ( this.initialPage > this.tileSources.length - 1 ){
+ this.initialPage = this.tileSources.length - 1;
+ }
+
+ initialTileSource = this.tileSources[ this.initialPage ];
+
+ //Update the sequence (aka currrent page) property
+ THIS[ this.hash ].sequence = this.initialPage;
} else {
initialTileSource = this.tileSources;
}
-
- this.open( initialTileSource );
}
- this.element = this.element || document.getElementById( this.id );
- this.canvas = $.makeNeutralElement( "div" );
+ this.element = this.element || document.getElementById( this.id );
+ this.canvas = $.makeNeutralElement( "div" );
+ this.keyboardCommandArea = $.makeNeutralElement( "textarea" );
+ this.drawersContainer = $.makeNeutralElement( "div" );
+ this.overlaysContainer = $.makeNeutralElement( "div" );
this.canvas.className = "openseadragon-canvas";
- (function( canvas ){
- canvas.width = "100%";
- canvas.height = "100%";
- canvas.overflow = "hidden";
- canvas.position = "absolute";
- canvas.top = "0px";
- canvas.left = "0px";
+ (function( style ){
+ style.width = "100%";
+ style.height = "100%";
+ style.overflow = "hidden";
+ style.position = "absolute";
+ style.top = "0px";
+ style.left = "0px";
}( this.canvas.style ));
//the container is created through applying the ControlDock constructor above
this.container.className = "openseadragon-container";
- (function( container ){
- container.width = "100%";
- container.height = "100%";
- container.position = "relative";
- container.overflow = "hidden";
- container.left = "0px";
- container.top = "0px";
- container.textAlign = "left"; // needed to protect against
+ (function( style ){
+ style.width = "100%";
+ style.height = "100%";
+ style.position = "relative";
+ style.overflow = "hidden";
+ style.left = "0px";
+ style.top = "0px";
+ style.textAlign = "left"; // needed to protect against
}( this.container.style ));
+ this.keyboardCommandArea.className = "keyboard-command-area";
+ (function( style ){
+ style.width = "100%";
+ style.height = "100%";
+ style.overflow = "hidden";
+ style.position = "absolute";
+ style.top = "0px";
+ style.left = "0px";
+ style.resize = "none";
+ }( this.keyboardCommandArea.style ));
+
this.container.insertBefore( this.canvas, this.container.firstChild );
+ this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild );
this.element.appendChild( this.container );
+ this.canvas.appendChild( this.drawersContainer );
+ this.canvas.appendChild( this.overlaysContainer );
//Used for toggling between fullscreen and default container size
//TODO: these can be closure private and shared across Viewer
@@ -203,9 +314,73 @@ $.Viewer = function( options ) {
this.bodyOverflow = document.body.style.overflow;
this.docOverflow = document.documentElement.style.overflow;
+ this.keyboardCommandArea.innerTracker = new $.MouseTracker({
+ _this : this,
+ element: this.keyboardCommandArea,
+ focusHandler: function( event ){
+ if ( !event.preventDefaultAction ) {
+ var point = $.getElementPosition( this.element );
+ window.scrollTo( 0, point.y );
+ }
+ },
+
+ keyHandler: function( event ){
+ if ( !event.preventDefaultAction ) {
+ switch( event.keyCode ){
+ case 61://=|+
+ _this.viewport.zoomBy(1.1);
+ _this.viewport.applyConstraints();
+ return false;
+ case 45://-|_
+ _this.viewport.zoomBy(0.9);
+ _this.viewport.applyConstraints();
+ return false;
+ case 48://0|)
+ _this.viewport.goHome();
+ _this.viewport.applyConstraints();
+ return false;
+ case 119://w
+ case 87://W
+ case 38://up arrow
+ if ( event.shift ) {
+ _this.viewport.zoomBy(1.1);
+ } else {
+ _this.viewport.panBy(new $.Point(0, -0.05));
+ }
+ _this.viewport.applyConstraints();
+ return false;
+ case 115://s
+ case 83://S
+ case 40://down arrow
+ if ( event.shift ) {
+ _this.viewport.zoomBy(0.9);
+ } else {
+ _this.viewport.panBy(new $.Point(0, 0.05));
+ }
+ _this.viewport.applyConstraints();
+ return false;
+ case 97://a
+ case 37://left arrow
+ _this.viewport.panBy(new $.Point(-0.05, 0));
+ _this.viewport.applyConstraints();
+ return false;
+ case 100://d
+ case 39://right arrow
+ _this.viewport.panBy(new $.Point(0.05, 0));
+ _this.viewport.applyConstraints();
+ return false;
+ default:
+ //console.log( 'navigator keycode %s', event.keyCode );
+ return true;
+ }
+ }
+ }
+ }).setTracking( true ); // default state
+
+
this.innerTracker = new $.MouseTracker({
- element: this.canvas,
- clickTimeThreshold: this.clickTimeThreshold,
+ element: this.canvas,
+ clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
clickHandler: $.delegate( this, onCanvasClick ),
dragHandler: $.delegate( this, onCanvasDrag ),
@@ -214,25 +389,33 @@ $.Viewer = function( options ) {
}).setTracking( this.mouseNavEnabled ? true : false ); // default state
this.outerTracker = new $.MouseTracker({
- element: this.container,
- clickTimeThreshold: this.clickTimeThreshold,
+ 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
-
+
if( this.toolbar ){
this.toolbar = new $.ControlDock({ element: this.toolbar });
}
-
+
this.bindStandardControls();
this.bindSequenceControls();
+ if ( initialTileSource ) {
+ this.open( initialTileSource );
+
+ if ( this.tileSources.length > 1 ) {
+ this._updateSequenceButtons( this.initialPage );
+ }
+ }
+
for ( i = 0; i < this.customControls.length; i++ ) {
this.addControl(
- this.customControls[ i ].id,
- this.customControls[ i ].anchor
+ this.customControls[ i ].id,
+ {anchor: this.customControls[ i ].anchor}
);
}
@@ -242,12 +425,11 @@ $.Viewer = function( options ) {
};
-$.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, {
+$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.isOpen
* @return {Boolean}
*/
isOpen: function () {
@@ -255,28 +437,26 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
},
/**
- * A deprecated function, renamed to 'open' to match event name and
- * match current 'close' method.
+ * A deprecated function, renamed to 'open' to match event name and
+ * match current 'close' method.
* @function
- * @name OpenSeadragon.Viewer.prototype.openDzi
* @param {String} dzi xml string or the url to a DZI xml document.
* @return {OpenSeadragon.Viewer} Chainable.
*
- * @deprecated - use 'open' instead.
+ * @deprecated - use {@link OpenSeadragon.Viewer#open} instead.
*/
openDzi: function ( dzi ) {
return this.open( dzi );
},
/**
- * A deprecated function, renamed to 'open' to match event name and
+ * A deprecated function, renamed to 'open' to match event name and
* match current 'close' method.
* @function
- * @name OpenSeadragon.Viewer.prototype.openTileSource
* @param {String|Object|Function} See OpenSeadragon.Viewer.prototype.open
* @return {OpenSeadragon.Viewer} Chainable.
*
- * @deprecated - use 'open' instead.
+ * @deprecated - use {@link OpenSeadragon.Viewer#open} instead.
*/
openTileSource: function ( tileSource ) {
return this.open( tileSource );
@@ -286,99 +466,135 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
* Open a TileSource object into the viewer.
*
* tileSources is a complex option...
- *
+ *
* It can be a string, object, function, or an array of any of these:
*
* - A String implies a url used to determine the tileSource implementation
* based on the file extension of url. JSONP is implied by *.js,
* otherwise the url is retrieved as text and the resulting text is
- * introspected to determine if its json, xml, or text and parsed.
- * - An Object implies an inline configuration which has a single
- * property sufficient for being able to determine tileSource
+ * introspected to determine if its json, xml, or text and parsed.
+ * - An Object implies an inline configuration which has a single
+ * property sufficient for being able to determine tileSource
* implementation. If the object has a property which is a function
* named 'getTileUrl', it is treated as a custom TileSource.
* @function
- * @name OpenSeadragon.Viewer.prototype.openTileSource
* @param {String|Object|Function}
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:open
+ * @fires OpenSeadragon.Viewer.event:open-failed
*/
open: function ( tileSource ) {
- var _this = this,
- customTileSource,
- readySource,
- $TileSource,
- options;
+ var _this = this;
- //allow plain xml strings or json strings to be parsed here
- if( $.type( tileSource ) == 'string' ){
- if( tileSource.match(/\s*<.*/) ){
- tileSource = $.parseXml( tileSource );
- }else if( tileSource.match(/\s*[\{\[].*/) ){
- /*jshint evil:true*/
- tileSource = eval( '('+tileSource+')' );
- }
- }
+ _this._hideMessage();
- setTimeout(function(){
- if ( $.type( tileSource ) == 'string') {
- //If its still a string it means it must be a url at this point
- tileSource = new $.TileSource( tileSource, function( readySource ){
- openTileSource( _this, readySource );
- });
-
- } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ){
- if( $.isFunction( tileSource.getTileUrl ) ){
- //Custom tile source
- customTileSource = new $.TileSource(tileSource);
- customTileSource.getTileUrl = tileSource.getTileUrl;
- openTileSource( _this, customTileSource );
- } else {
- //inline configuration
- $TileSource = $.TileSource.determineType( _this, tileSource );
- options = $TileSource.prototype.configure.apply( _this, [ tileSource ]);
- readySource = new $TileSource( options );
- openTileSource( _this, readySource );
- }
- } else {
- //can assume it's already a tile source implementation
- openTileSource( _this, tileSource );
- }
- }, 1);
+ getTileSourceImplementation( _this, tileSource, function( tileSource ) {
+ openTileSource( _this, tileSource );
+ }, function( event ) {
+ /**
+ * Raised when an error occurs loading a TileSource.
+ *
+ * @event open-failed
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {String} message
+ * @property {String} source
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'open-failed', event );
+ });
return this;
},
-
+
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.close
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:close
*/
close: function ( ) {
-
- if( this.drawer ){
- this.drawer.clearOverlays();
+ if ( this._updateRequestId !== null ) {
+ $.cancelAnimationFrame( this._updateRequestId );
+ this._updateRequestId = null;
}
+ if ( this.navigator ) {
+ this.navigator.close();
+ }
+
+ this.clearOverlays();
+ this.drawersContainer.innerHTML = "";
+ this.overlaysContainer.innerHTML = "";
+
this.source = null;
this.drawer = null;
+ this.drawers = [];
this.viewport = this.preserveViewport ? this.viewport : null;
- //this.profiler = null;
- this.canvas.innerHTML = "";
+
VIEWERS[ this.hash ] = null;
delete VIEWERS[ this.hash ];
- this.raiseEvent( 'close', { viewer: this } );
-
+ /**
+ * Raised when the viewer is closed (see {@link OpenSeadragon.Viewer#close}).
+ *
+ * @event close
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'close' );
+
return this;
},
+ /**
+ * Function to destroy the viewer and clean up everything created by
+ * OpenSeadragon.
+ * @function
+ */
+ destroy: function( ) {
+ this.close();
+
+ this.removeAllHandlers();
+
+ // Go through top element (passed to us) and remove all children
+ // Use removeChild to make sure it handles SVG or any non-html
+ // also it performs better - http://jsperf.com/innerhtml-vs-removechild/15
+ if (this.element){
+ while (this.element.firstChild) {
+ this.element.removeChild(this.element.firstChild);
+ }
+ }
+
+ // destroy the mouse trackers
+ if (this.keyboardCommandArea){
+ this.keyboardCommandArea.innerTracker.destroy();
+ }
+ if (this.innerTracker){
+ this.innerTracker.destroy();
+ }
+ if (this.outerTracker){
+ this.outerTracker.destroy();
+ }
+
+ // clear all our references to dom objects
+ this.canvas = null;
+ this.keyboardCommandArea = null;
+ this.container = null;
+
+ // clear our reference to the main element - they will need to pass it in again, creating a new viewer
+ this.element = null;
+ },
+
+
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.isMouseNavEnabled
* @return {Boolean}
*/
isMouseNavEnabled: function () {
@@ -387,19 +603,29 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.setMouseNavEnabled
+ * @param {Boolean} enabled - true to enable, false to disable
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:mouse-enabled
*/
setMouseNavEnabled: function( enabled ){
this.innerTracker.setTracking( enabled );
- this.raiseEvent( 'mouse-enabled', { enabled: enabled, viewer: this } );
+ /**
+ * Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}).
+ *
+ * @event mouse-enabled
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} enabled
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'mouse-enabled', { enabled: enabled } );
return this;
},
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.areControlsEnabled
* @return {Boolean}
*/
areControlsEnabled: function () {
@@ -413,9 +639,12 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
/**
+ * Shows or hides the controls (e.g. the default navigation buttons).
+ *
* @function
- * @name OpenSeadragon.Viewer.prototype.setDashboardEnabled
+ * @param {Boolean} true to show, false to hide.
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:controls-enabled
*/
setControlsEnabled: function( enabled ) {
if( enabled ){
@@ -423,14 +652,23 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
} else {
beginControlsAutoHide( this );
}
- this.raiseEvent( 'controls-enabled', { enabled: enabled, viewer: this } );
+ /**
+ * Raised when the navigation controls are shown or hidden (see {@link OpenSeadragon.Viewer#setControlsEnabled}).
+ *
+ * @event controls-enabled
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} enabled
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'controls-enabled', { enabled: enabled } );
return this;
},
-
+
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.isFullPage
* @return {Boolean}
*/
isFullPage: function () {
@@ -441,67 +679,91 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
/**
* Toggle full page mode.
* @function
- * @name OpenSeadragon.Viewer.prototype.setFullPage
* @param {Boolean} fullPage
* If true, enter full page mode. If false, exit full page mode.
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:pre-full-page
+ * @fires OpenSeadragon.Viewer.event:full-page
*/
setFullPage: function( fullPage ) {
- var body = document.body,
- bodyStyle = body.style,
- docStyle = document.documentElement.style,
- containerStyle = this.element.style,
- canvasStyle = this.canvas.style,
- _this = this,
- oldBounds,
- newBounds,
- viewer,
+ var body = document.body,
+ bodyStyle = body.style,
+ docStyle = document.documentElement.style,
+ _this = this,
hash,
nodes,
i;
-
+
//dont bother modifying the DOM if we are already in full page mode.
if ( fullPage == this.isFullPage() ) {
- return;
+ return this;
}
+ var fullPageEventArgs = {
+ fullPage: fullPage,
+ preventDefaultAction: false
+ };
+ /**
+ * Raised when the viewer is about to change to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
+ *
+ * @event pre-full-page
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} fullPage - True if entering full-page mode, false if exiting full-page mode.
+ * @property {Boolean} preventDefaultAction - Set to true to prevent full-page mode change. Default: false.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'pre-full-page', fullPageEventArgs );
+ if ( fullPageEventArgs.preventDefaultAction ) {
+ return this;
+ }
if ( fullPage ) {
-
- this.bodyOverflow = bodyStyle.overflow;
- this.docOverflow = docStyle.overflow;
- bodyStyle.overflow = "hidden";
- docStyle.overflow = "hidden";
- this.bodyWidth = bodyStyle.width;
- this.bodyHeight = bodyStyle.height;
- bodyStyle.width = "100%";
- bodyStyle.height = "100%";
+ this.elementSize = $.getElementSize( this.element );
+ this.pageScroll = $.getPageScroll();
- //canvasStyle.backgroundColor = "black";
- //canvasStyle.color = "white";
+ this.elementMargin = this.element.style.margin;
+ this.element.style.margin = "0";
+ this.elementPadding = this.element.style.padding;
+ this.element.style.padding = "0";
- //containerStyle.position = "fixed";
+ this.bodyMargin = bodyStyle.margin;
+ this.docMargin = docStyle.margin;
+ bodyStyle.margin = "0";
+ docStyle.margin = "0";
+
+ this.bodyPadding = bodyStyle.padding;
+ this.docPadding = docStyle.padding;
+ bodyStyle.padding = "0";
+ docStyle.padding = "0";
+
+ this.bodyWidth = bodyStyle.width;
+ this.bodyHeight = bodyStyle.height;
+ bodyStyle.width = "100%";
+ bodyStyle.height = "100%";
//when entering full screen on the ipad it wasnt sufficient to leave
- //the body intact as only only the top half of the screen would
+ //the body intact as only only the top half of the screen would
//respond to touch events on the canvas, while the bottom half treated
//them as touch events on the document body. Thus we remove and store
//the bodies elements and replace them when we leave full screen.
this.previousBody = [];
THIS[ this.hash ].prevElementParent = this.element.parentNode;
THIS[ this.hash ].prevNextSibling = this.element.nextSibling;
- THIS[ this.hash ].prevElementSize = $.getElementSize( this.element );
+ THIS[ this.hash ].prevElementWidth = this.element.style.width;
+ THIS[ this.hash ].prevElementHeight = this.element.style.height;
nodes = body.childNodes.length;
- for ( i = 0; i < nodes; i ++ ){
+ for ( i = 0; i < nodes; i++ ) {
this.previousBody.push( body.childNodes[ 0 ] );
body.removeChild( body.childNodes[ 0 ] );
}
-
+
//If we've got a toolbar, we need to enable the user to use css to
//preserve it in fullpage mode
- if( this.toolbar && this.toolbar.element ){
+ if ( this.toolbar && this.toolbar.element ) {
//save a reference to the parent so we can put it back
//in the long run we need a better strategy
this.toolbar.parentNode = this.toolbar.element.parentNode;
@@ -510,43 +772,16 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
//Make sure the user has some ability to style the toolbar based
//on the mode
- this.toolbar.element.setAttribute(
- 'class',
- this.toolbar.element.className +" fullpage"
- );
+ $.addClass( this.toolbar.element, 'fullpage' );
}
-
+
+ $.addClass( this.element, 'fullpage' );
body.appendChild( this.element );
-
- if( $.supportsFullScreen ){
- THIS[ this.hash ].onfullscreenchange = function( event ) {
- // The event object doesn't carry information about the
- // fullscreen state of the browser, but it is possible to
- // retrieve it through the fullscreen API
- if( $.isFullScreen() ){
- _this.setFullPage( true );
- } else {
- _this.setFullPage( false );
- }
- };
- $.requestFullScreen( document.body );
+ this.element.style.height = $.getWindowSize().y + 'px';
+ this.element.style.width = $.getWindowSize().x + 'px';
- // The target of the event is always the document,
- // but it is possible to retrieve the fullscreen element through the API
- // Note that the API is still vendor-prefixed in browsers implementing it
- document.addEventListener(
- $.fullScreenEventName,
- THIS[ this.hash ].onfullscreenchange
- );
- this.element.style.height = '100%';
- this.element.style.width = '100%';
- }else{
- this.element.style.height = $.getWindowSize().y + 'px';
- this.element.style.width = $.getWindowSize().x + 'px';
- }
-
- if( this.toolbar && this.toolbar.element ){
+ if ( this.toolbar && this.toolbar.element ) {
this.element.style.height = (
$.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
) + 'px';
@@ -555,119 +790,189 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
THIS[ this.hash ].fullPage = true;
// mouse will be inside container now
- $.delegate( this, onContainerEnter )();
-
+ $.delegate( this, onContainerEnter )( {} );
} else {
- if( $.supportsFullScreen ){
- document.removeEventListener(
- $.fullScreenEventName,
- THIS[ this.hash ].onfullscreenchange
- );
- $.cancelFullScreen( document );
- }
+ this.element.style.margin = this.elementMargin;
+ this.element.style.padding = this.elementPadding;
- bodyStyle.overflow = this.bodyOverflow;
- docStyle.overflow = this.docOverflow;
+ bodyStyle.margin = this.bodyMargin;
+ docStyle.margin = this.docMargin;
- bodyStyle.width = this.bodyWidth;
- bodyStyle.height = this.bodyHeight;
+ bodyStyle.padding = this.bodyPadding;
+ docStyle.padding = this.docPadding;
- canvasStyle.backgroundColor = "";
- canvasStyle.color = "";
-
- //containerStyle.position = "relative";
- //containerStyle.zIndex = "";
+ bodyStyle.width = this.bodyWidth;
+ bodyStyle.height = this.bodyHeight;
body.removeChild( this.element );
nodes = this.previousBody.length;
- for ( i = 0; i < nodes; i++ ){
+ for ( i = 0; i < nodes; i++ ) {
body.appendChild( this.previousBody.shift() );
}
+
+ $.removeClass( this.element, 'fullpage' );
THIS[ this.hash ].prevElementParent.insertBefore(
this.element,
THIS[ this.hash ].prevNextSibling
);
-
+
//If we've got a toolbar, we need to enable the user to use css to
- //reset it to its original state
- if( this.toolbar && this.toolbar.element ){
+ //reset it to its original state
+ if ( this.toolbar && this.toolbar.element ) {
body.removeChild( this.toolbar.element );
//Make sure the user has some ability to style the toolbar based
//on the mode
- this.toolbar.element.setAttribute(
- 'class',
- this.toolbar.element.className.replace('fullpage','')
- );
- //this.toolbar.element.style.position = 'relative';
- this.toolbar.parentNode.insertBefore(
+ $.removeClass( this.toolbar.element, 'fullpage' );
+
+ this.toolbar.parentNode.insertBefore(
this.toolbar.element,
this.toolbar.nextSibling
);
delete this.toolbar.parentNode;
delete this.toolbar.nextSibling;
-
- //this.container.style.top = 'auto';
}
- this.element.style.height = THIS[ this.hash ].prevElementSize.y + 'px';
- this.element.style.width = THIS[ this.hash ].prevElementSize.x + 'px';
+ this.element.style.width = THIS[ this.hash ].prevElementWidth;
+ this.element.style.height = THIS[ this.hash ].prevElementHeight;
+
+ // After exiting fullPage or fullScreen, it can take some time
+ // before the browser can actually set the scroll.
+ var restoreScrollCounter = 0;
+ var restoreScroll = function() {
+ $.setPageScroll( _this.pageScroll );
+ var pageScroll = $.getPageScroll();
+ restoreScrollCounter++;
+ if ( restoreScrollCounter < 10 &&
+ pageScroll.x !== _this.pageScroll.x ||
+ pageScroll.y !== _this.pageScroll.y ) {
+ $.requestAnimationFrame( restoreScroll );
+ }
+ };
+ $.requestAnimationFrame( restoreScroll );
THIS[ this.hash ].fullPage = false;
// mouse will likely be outside now
- $.delegate( this, onContainerExit )();
-
+ $.delegate( this, onContainerExit )( { } );
}
- this.raiseEvent( 'fullpage', { fullpage: fullPage, viewer: this } );
- if ( this.viewport ) {
- oldBounds = this.viewport.getBounds();
- this.viewport.resize( THIS[ this.hash ].prevContainerSize );
- newBounds = this.viewport.getBounds();
+ if ( this.navigator && this.viewport ) {
+ this.navigator.update( this.viewport );
+ }
- if ( fullPage ) {
- THIS[ this.hash ].fsBoundsDelta = new $.Point(
- newBounds.width / oldBounds.width,
- newBounds.height / oldBounds.height
- );
- } else {
- this.viewport.update();
- this.viewport.zoomBy(
- Math.max(
- THIS[ this.hash ].fsBoundsDelta.x,
- THIS[ this.hash ].fsBoundsDelta.y
- ),
- null,
- true
- );
- //Ensures that if multiple viewers are on a page, the viewers that
- //were hidden during fullpage are 'reopened'
- for( hash in VIEWERS ){
- viewer = VIEWERS[ hash ];
- if( viewer !== this && viewer != this.navigator ){
- viewer.open( viewer.source );
- if( viewer.navigator ){
- viewer.navigator.open( viewer.source );
- }
- }
- }
+ /**
+ * Raised when the viewer has changed to/from full-page mode (see {@link OpenSeadragon.Viewer#setFullPage}).
+ *
+ * @event full-page
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} fullPage - True if changed to full-page mode, false if exited full-page mode.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'full-page', { fullPage: fullPage } );
+
+ return this;
+ },
+
+ /**
+ * Toggle full screen mode if supported. Toggle full page mode otherwise.
+ * @function
+ * @param {Boolean} fullScreen
+ * If true, enter full screen mode. If false, exit full screen mode.
+ * @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:pre-full-screen
+ * @fires OpenSeadragon.Viewer.event:full-screen
+ */
+ setFullScreen: function( fullScreen ) {
+ var _this = this;
+
+ if ( !$.supportsFullScreen ) {
+ return this.setFullPage( fullScreen );
+ }
+
+ if ( $.isFullScreen() === fullScreen ) {
+ return this;
+ }
+
+ var fullScreeEventArgs = {
+ fullScreen: fullScreen,
+ preventDefaultAction: false
+ };
+ /**
+ * Raised when the viewer is about to change to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
+ *
+ * @event pre-full-screen
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} fullScreen - True if entering full-screen mode, false if exiting full-screen mode.
+ * @property {Boolean} preventDefaultAction - Set to true to prevent full-screen mode change. Default: false.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'pre-full-screen', fullScreeEventArgs );
+ if ( fullScreeEventArgs.preventDefaultAction ) {
+ return this;
+ }
+
+ if ( fullScreen ) {
+
+ this.setFullPage( true );
+ // If the full page mode is not actually entered, we need to prevent
+ // the full screen mode.
+ if ( !this.isFullPage() ) {
+ return this;
}
- THIS[ this.hash ].forceRedraw = true;
- updateOnce( this );
+ this.fullPageStyleWidth = this.element.style.width;
+ this.fullPageStyleHeight = this.element.style.height;
+ this.element.style.width = '100%';
+ this.element.style.height = '100%';
+ var onFullScreenChange = function() {
+ var isFullScreen = $.isFullScreen();
+ if ( !isFullScreen ) {
+ $.removeEvent( document, $.fullScreenEventName, onFullScreenChange );
+ $.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
+
+ _this.setFullPage( false );
+ if ( _this.isFullPage() ) {
+ _this.element.style.width = _this.fullPageStyleWidth;
+ _this.element.style.height = _this.fullPageStyleHeight;
+ }
+ }
+ if ( _this.navigator && _this.viewport ) {
+ _this.navigator.update( _this.viewport );
+ }
+ /**
+ * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
+ *
+ * @event full-screen
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} fullScreen - True if changed to full-screen mode, false if exited full-screen mode.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } );
+ };
+ $.addEvent( document, $.fullScreenEventName, onFullScreenChange );
+ $.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
+
+ $.requestFullScreen( document.body );
+
+ } else {
+ $.exitFullScreen();
}
return this;
},
-
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.isVisible
* @return {Boolean}
*/
isVisible: function () {
@@ -677,23 +982,284 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.setVisible
+ * @param {Boolean} visible
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:visible
*/
setVisible: function( visible ){
this.container.style.visibility = visible ? "" : "hidden";
- this.raiseEvent( 'visible', { visible: visible, viewer: this } );
+ /**
+ * Raised when the viewer is shown or hidden (see {@link OpenSeadragon.Viewer#setVisible}).
+ *
+ * @event visible
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Boolean} visible
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'visible', { visible: visible } );
return this;
},
+ /**
+ * Add a layer.
+ * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open}
+ * supports except arrays of images as layers cannot be sequences.
+ * @function
+ * @param {Object} options
+ * @param {String|Object|Function} options.tileSource The TileSource of the layer.
+ * @param {Number} [options.opacity=1] The opacity of the layer.
+ * @param {Number} [options.level] The level of the layer. Added on top of
+ * all other layers if not specified.
+ * @returns {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:add-layer
+ * @fires OpenSeadragon.Viewer.event:add-layer-failed
+ */
+ addLayer: function( options ) {
+ var _this = this,
+ tileSource = options.tileSource;
+
+ if ( !this.isOpen() ) {
+ throw new Error( "An image must be loaded before adding layers." );
+ }
+ if ( !tileSource ) {
+ throw new Error( "No tile source provided as new layer." );
+ }
+ if ( this.collectionMode ) {
+ throw new Error( "Layers not supported in collection mode." );
+ }
+
+ function raiseAddLayerFailed( event ) {
+ /**
+ * Raised when an error occurs while adding a layer.
+ * @event add-layer-failed
+ * @memberOf OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {String} message
+ * @property {String} source
+ * @property {Object} options The options passed to the addLayer method.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'add-layer-failed', event );
+ }
+
+ getTileSourceImplementation( this, tileSource, function( tileSource ) {
+
+ if ( tileSource instanceof Array ) {
+ raiseAddLayerFailed({
+ message: "Sequences can not be added as layers.",
+ source: tileSource,
+ options: options
+ });
+ return;
+ }
+
+ for ( var i = 0; i < _this.drawers.length; i++ ) {
+ var otherAspectRatio = _this.drawers[ i ].source.aspectRatio;
+ var diff = otherAspectRatio - tileSource.aspectRatio;
+ if ( Math.abs( diff ) > _this.layersAspectRatioEpsilon ) {
+ raiseAddLayerFailed({
+ message: "Aspect ratio mismatch with layer " + i + ".",
+ source: tileSource,
+ options: options
+ });
+ return;
+ }
+ }
+
+ var drawer = new $.Drawer({
+ viewer: _this,
+ source: tileSource,
+ viewport: _this.viewport,
+ element: _this.drawersContainer,
+ opacity: options.opacity !== undefined ?
+ options.opacity : _this.opacity,
+ maxImageCacheCount: _this.maxImageCacheCount,
+ imageLoaderLimit: _this.imageLoaderLimit,
+ minZoomImageRatio: _this.minZoomImageRatio,
+ wrapHorizontal: _this.wrapHorizontal,
+ wrapVertical: _this.wrapVertical,
+ immediateRender: _this.immediateRender,
+ blendTime: _this.blendTime,
+ alwaysBlend: _this.alwaysBlend,
+ minPixelRatio: _this.minPixelRatio,
+ timeout: _this.timeout,
+ debugMode: _this.debugMode,
+ debugGridColor: _this.debugGridColor
+ });
+ _this.drawers.push( drawer );
+ if ( options.level !== undefined ) {
+ _this.setLayerLevel( drawer, options.level );
+ }
+ THIS[ _this.hash ].forceRedraw = true;
+ /**
+ * Raised when a layer is successfully added.
+ * @event add-layer
+ * @memberOf OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Object} options The options passed to the addLayer method.
+ * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'add-layer', {
+ options: options,
+ drawer: drawer
+ });
+ }, function( event ) {
+ event.options = options;
+ raiseAddLayerFailed(event);
+ } );
+
+ return this;
+ },
+
+ /**
+ * Get the layer at the specified level.
+ * @param {Number} level The layer to retrieve level.
+ * @returns {OpenSeadragon.Drawer} The layer at the specified level.
+ */
+ getLayerAtLevel: function( level ) {
+ if ( level >= this.drawers.length ) {
+ throw new Error( "Level bigger than number of layers." );
+ }
+ return this.drawers[ level ];
+ },
+
+ /**
+ * Get the level of the layer associated with the given drawer or -1 if not
+ * present.
+ * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer.
+ * @returns {Number} The level of the layer or -1 if not present.
+ */
+ getLevelOfLayer: function( drawer ) {
+ return $.indexOf( this.drawers, drawer );
+ },
+
+ /**
+ * Get the number of layers used.
+ * @returns {Number} The number of layers used.
+ */
+ getLayersCount: function() {
+ return this.drawers.length;
+ },
+
+ /**
+ * Change the level of a layer so that it appears over or under others.
+ * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing
+ * level layer.
+ * @param {Number} level The new level
+ * @returns {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:layer-level-changed
+ */
+ setLayerLevel: function( drawer, level ) {
+ var oldLevel = this.getLevelOfLayer( drawer );
+
+ if ( level >= this.drawers.length ) {
+ throw new Error( "Level bigger than number of layers." );
+ }
+ if ( level === oldLevel || oldLevel === -1 ) {
+ return this;
+ }
+ if ( level === 0 || oldLevel === 0 ) {
+ if ( THIS[ this.hash ].sequenced ) {
+ throw new Error( "Cannot reassign base level when in sequence mode." );
+ }
+ // We need to re-assign the base drawer and the source
+ this.drawer = level === 0 ? drawer : this.getLayerAtLevel( level );
+ this.source = this.drawer.source;
+ }
+ this.drawers.splice( oldLevel, 1 );
+ this.drawers.splice( level, 0, drawer );
+ this.drawersContainer.removeChild( drawer.canvas );
+ if ( level === 0 ) {
+ var nextLevelCanvas = this.drawers[ 1 ].canvas;
+ nextLevelCanvas.parentNode.insertBefore( drawer.canvas,
+ nextLevelCanvas );
+ } else {
+ // Insert right after layer at level - 1
+ var prevLevelCanvas = this.drawers[level - 1].canvas;
+ prevLevelCanvas.parentNode.insertBefore( drawer.canvas,
+ prevLevelCanvas.nextSibling );
+ }
+
+ /**
+ * Raised when the order of the layers has been changed.
+ * @event layer-level-changed
+ * @memberOf OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {OpenSeadragon.Drawer} drawer - The drawer which level has
+ * been changed
+ * @property {Number} previousLevel - The previous level of the drawer
+ * @property {Number} newLevel - The new level of the drawer
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'layer-level-changed', {
+ drawer: drawer,
+ previousLevel: oldLevel,
+ newLevel: level
+ } );
+
+ return this;
+ },
+
+ /**
+ * Remove a layer. If there is only one layer, close the viewer.
+ * @function
+ * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer
+ * to remove
+ * @returns {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:remove-layer
+ */
+ removeLayer: function( drawer ) {
+ var index = this.drawers.indexOf( drawer );
+ if ( index === -1 ) {
+ return this;
+ }
+ if ( index === 0 ) {
+ if ( THIS[ this.hash ].sequenced ) {
+ throw new Error( "Cannot remove base layer when in sequence mode." );
+ }
+ if ( this.drawers.length === 1 ) {
+ this.close();
+ return this;
+ }
+ this.drawer = this.drawers[ 1 ];
+ }
+
+ this.drawers.splice( index, 1 );
+ this.drawersContainer.removeChild( drawer.canvas );
+ /**
+ * Raised when a layer is removed.
+ * @event remove-layer
+ * @memberOf OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'remove-layer', { drawer: drawer } );
+ return this;
+ },
+
+ /**
+ * Force the viewer to redraw its drawers.
+ * @returns {OpenSeadragon.Viewer} Chainable.
+ */
+ forceRedraw: function() {
+ THIS[ this.hash ].forceRedraw = true;
+ return this;
+ },
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.bindSequenceControls
* @return {OpenSeadragon.Viewer} Chainable.
*/
bindSequenceControls: function(){
-
+
//////////////////////////////////////////////////////////////////////////
// Image Sequence Controls
//////////////////////////////////////////////////////////////////////////
@@ -702,18 +1268,17 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
onNextHandler = $.delegate( this, onNext ),
onPreviousHandler = $.delegate( this, onPrevious ),
navImages = this.navImages,
- buttons = [],
useGroup = true ;
if( this.showSequenceControl && THIS[ this.hash ].sequenced ){
-
+
if( this.previousButton || this.nextButton ){
- //if we are binding to custom buttons then layout and
+ //if we are binding to custom buttons then layout and
//grouping is the responsibility of the page author
useGroup = false;
}
- this.previousButton = new $.Button({
+ this.previousButton = new $.Button({
element: this.previousButton ? $.getElement( this.previousButton ) : null,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
@@ -727,7 +1292,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
onBlur: onBlurHandler
});
- this.nextButton = new $.Button({
+ this.nextButton = new $.Button({
element: this.nextButton ? $.getElement( this.nextButton ) : null,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
@@ -741,13 +1306,15 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
onBlur: onBlurHandler
});
- this.previousButton.disable();
+ if( !this.navPrevNextWrap ){
+ this.previousButton.disable();
+ }
if( useGroup ){
this.paging = new $.ButtonGroup({
- buttons: [
- this.previousButton,
- this.nextButton
+ buttons: [
+ this.previousButton,
+ this.nextButton
],
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold
@@ -756,14 +1323,14 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
this.pagingControl = this.paging.element;
if( this.toolbar ){
- this.toolbar.addControl(
- this.pagingControl,
- $.ControlAnchor.BOTTOM_RIGHT
+ this.toolbar.addControl(
+ this.pagingControl,
+ {anchor: $.ControlAnchor.BOTTOM_RIGHT}
);
}else{
- this.addControl(
- this.pagingControl,
- $.ControlAnchor.TOP_LEFT
+ this.addControl(
+ this.pagingControl,
+ {anchor: this.sequenceControlAnchor || $.ControlAnchor.TOP_LEFT}
);
}
}
@@ -774,7 +1341,6 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
/**
* @function
- * @name OpenSeadragon.Viewer.prototype.bindStandardControls
* @return {OpenSeadragon.Viewer} Chainable.
*/
bindStandardControls: function(){
@@ -787,7 +1353,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
onHomeHandler = $.delegate( this, onHome ),
- onFullPageHandler = $.delegate( this, onFullPage ),
+ onFullScreenHandler = $.delegate( this, onFullScreen ),
+ onRotateLeftHandler = $.delegate( this, onRotateLeft ),
+ onRotateRightHandler = $.delegate( this, onRotateRight ),
onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ),
navImages = this.navImages,
@@ -795,79 +1363,118 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
useGroup = true ;
- if( this.showNavigationControl ){
+ if ( this.showNavigationControl ) {
- if( this.zoomInButton || this.zoomOutButton || this.homeButton || this.fullPageButton ){
- //if we are binding to custom buttons then layout and
+ if( this.zoomInButton || this.zoomOutButton ||
+ this.homeButton || this.fullPageButton ||
+ this.rotateLeftButton || this.rotateRightButton ) {
+ //if we are binding to custom buttons then layout and
//grouping is the responsibility of the page author
useGroup = false;
}
-
- buttons.push( this.zoomInButton = new $.Button({
- element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
- clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- tooltip: $.getString( "Tooltips.ZoomIn" ),
- srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
- srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
- srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
- srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
- onPress: beginZoomingInHandler,
- onRelease: endZoomingHandler,
- onClick: doSingleZoomInHandler,
- onEnter: beginZoomingInHandler,
- onExit: endZoomingHandler,
- onFocus: onFocusHandler,
- onBlur: onBlurHandler
- }));
- buttons.push( this.zoomOutButton = new $.Button({
- element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
- clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- tooltip: $.getString( "Tooltips.ZoomOut" ),
- srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
- srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
- srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
- srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
- onPress: beginZoomingOutHandler,
- onRelease: endZoomingHandler,
- onClick: doSingleZoomOutHandler,
- onEnter: beginZoomingOutHandler,
- onExit: endZoomingHandler,
- onFocus: onFocusHandler,
- onBlur: onBlurHandler
- }));
+ if ( this.showZoomControl ) {
+ buttons.push( this.zoomInButton = new $.Button({
+ element: this.zoomInButton ? $.getElement( this.zoomInButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.ZoomIn" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.zoomIn.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.zoomIn.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.zoomIn.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.zoomIn.DOWN ),
+ onPress: beginZoomingInHandler,
+ onRelease: endZoomingHandler,
+ onClick: doSingleZoomInHandler,
+ onEnter: beginZoomingInHandler,
+ onExit: endZoomingHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
- buttons.push( this.homeButton = new $.Button({
- element: this.homeButton ? $.getElement( this.homeButton ) : null,
- clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- tooltip: $.getString( "Tooltips.Home" ),
- srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ),
- srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ),
- srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ),
- srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ),
- onRelease: onHomeHandler,
- onFocus: onFocusHandler,
- onBlur: onBlurHandler
- }));
+ buttons.push( this.zoomOutButton = new $.Button({
+ element: this.zoomOutButton ? $.getElement( this.zoomOutButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.ZoomOut" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.zoomOut.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.zoomOut.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.zoomOut.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.zoomOut.DOWN ),
+ onPress: beginZoomingOutHandler,
+ onRelease: endZoomingHandler,
+ onClick: doSingleZoomOutHandler,
+ onEnter: beginZoomingOutHandler,
+ onExit: endZoomingHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+ }
- buttons.push( this.fullPageButton = new $.Button({
- element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
- clickTimeThreshold: this.clickTimeThreshold,
- clickDistThreshold: this.clickDistThreshold,
- tooltip: $.getString( "Tooltips.FullPage" ),
- srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
- srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
- srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
- srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
- onRelease: onFullPageHandler,
- onFocus: onFocusHandler,
- onBlur: onBlurHandler
- }));
+ if ( this.showHomeControl ) {
+ buttons.push( this.homeButton = new $.Button({
+ element: this.homeButton ? $.getElement( this.homeButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.Home" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.home.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.home.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.home.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.home.DOWN ),
+ onRelease: onHomeHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+ }
- if( useGroup ){
+ if ( this.showFullPageControl ) {
+ buttons.push( this.fullPageButton = new $.Button({
+ element: this.fullPageButton ? $.getElement( this.fullPageButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.FullPage" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.fullpage.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.fullpage.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.fullpage.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.fullpage.DOWN ),
+ onRelease: onFullScreenHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+ }
+
+ if ( this.showRotationControl ) {
+ buttons.push( this.rotateLeftButton = new $.Button({
+ element: this.rotateLeftButton ? $.getElement( this.rotateLeftButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.RotateLeft" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.rotateleft.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.rotateleft.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.rotateleft.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.rotateleft.DOWN ),
+ onRelease: onRotateLeftHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ buttons.push( this.rotateRightButton = new $.Button({
+ element: this.rotateRightButton ? $.getElement( this.rotateRightButton ) : null,
+ clickTimeThreshold: this.clickTimeThreshold,
+ clickDistThreshold: this.clickDistThreshold,
+ tooltip: $.getString( "Tooltips.RotateRight" ),
+ srcRest: resolveUrl( this.prefixUrl, navImages.rotateright.REST ),
+ srcGroup: resolveUrl( this.prefixUrl, navImages.rotateright.GROUP ),
+ srcHover: resolveUrl( this.prefixUrl, navImages.rotateright.HOVER ),
+ srcDown: resolveUrl( this.prefixUrl, navImages.rotateright.DOWN ),
+ onRelease: onRotateRightHandler,
+ onFocus: onFocusHandler,
+ onBlur: onBlurHandler
+ }));
+
+ }
+
+ if ( useGroup ) {
this.buttons = new $.ButtonGroup({
buttons: buttons,
clickTimeThreshold: this.clickTimeThreshold,
@@ -878,96 +1485,379 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype,
this.addHandler( 'open', $.delegate( this, lightUp ) );
if( this.toolbar ){
- this.toolbar.addControl(
- this.navControl,
- $.ControlAnchor.TOP_LEFT
+ this.toolbar.addControl(
+ this.navControl,
+ {anchor: $.ControlAnchor.TOP_LEFT}
);
- }else{
- this.addControl(
- this.navControl,
- $.ControlAnchor.TOP_LEFT
+ } else {
+ this.addControl(
+ this.navControl,
+ {anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
);
}
}
-
+
+ }
+ return this;
+ },
+
+ /**
+ * Gets the active page of a sequence
+ * @function
+ * @return {Number}
+ */
+ currentPage: function() {
+ return THIS[ this.hash ].sequence;
+ },
+
+ /**
+ * @function
+ * @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:page
+ */
+ goToPage: function( page ){
+ if( page >= 0 && page < this.tileSources.length ){
+ /**
+ * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}).
+ *
+ * @event page
+ * @memberof OpenSeadragon.Viewer
+ * @type {Object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Number} page - The page index.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'page', { page: page } );
+
+ THIS[ this.hash ].sequence = page;
+
+ this._updateSequenceButtons( page );
+
+ this.open( this.tileSources[ page ] );
+
+ if( this.referenceStrip ){
+ this.referenceStrip.setFocus( page );
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Adds an html element as an overlay to the current viewport. Useful for
+ * highlighting words or areas of interest on an image or other zoomable
+ * interface. The overlays added via this method are removed when the viewport
+ * is closed which include when changing page.
+ * @method
+ * @param {Element|String|Object} element - A reference to an element or an id for
+ * the element which will overlayed. Or an Object specifying the configuration for the overlay
+ * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
+ * rectangle which will be overlayed.
+ * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
+ * viewport which the location coordinates will be treated as relative
+ * to.
+ * @param {function} onDraw - If supplied the callback is called when the overlay
+ * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
+ * It is passed position, size and element.
+ * @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:add-overlay
+ */
+ addOverlay: function( element, location, placement, onDraw ) {
+ var options;
+ if( $.isPlainObject( element ) ){
+ options = element;
+ } else {
+ options = {
+ element: element,
+ location: location,
+ placement: placement,
+ onDraw: onDraw
+ };
+ }
+
+ element = $.getElement( options.element );
+
+ if ( getOverlayIndex( this.currentOverlays, element ) >= 0 ) {
+ // they're trying to add a duplicate overlay
+ return this;
+ }
+ this.currentOverlays.push( getOverlayObject( this, options ) );
+ THIS[ this.hash ].forceRedraw = true;
+ /**
+ * Raised when an overlay is added to the viewer (see {@link OpenSeadragon.Viewer#addOverlay}).
+ *
+ * @event add-overlay
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {Element} element - The overlay element.
+ * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
+ * @property {OpenSeadragon.OverlayPlacement} placement
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'add-overlay', {
+ element: element,
+ location: options.location,
+ placement: options.placement
+ });
+ return this;
+ },
+
+ /**
+ * Updates the overlay represented by the reference to the element or
+ * element id moving it to the new location, relative to the new placement.
+ * @method
+ * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
+ * rectangle which will be overlayed.
+ * @param {OpenSeadragon.OverlayPlacement} placement - The position of the
+ * viewport which the location coordinates will be treated as relative
+ * to.
+ * @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:update-overlay
+ */
+ updateOverlay: function( element, location, placement ) {
+ var i;
+
+ element = $.getElement( element );
+ i = getOverlayIndex( this.currentOverlays, element );
+
+ if ( i >= 0 ) {
+ this.currentOverlays[ i ].update( location, placement );
+ THIS[ this.hash ].forceRedraw = true;
+ /**
+ * Raised when an overlay's location or placement changes
+ * (see {@link OpenSeadragon.Viewer#updateOverlay}).
+ *
+ * @event update-overlay
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the
+ * Viewer which raised the event.
+ * @property {Element} element
+ * @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
+ * @property {OpenSeadragon.OverlayPlacement} placement
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'update-overlay', {
+ element: element,
+ location: location,
+ placement: placement
+ });
}
return this;
},
+ /**
+ * Removes an overlay identified by the reference element or element id
+ * and schedules an update.
+ * @method
+ * @param {Element|String} element - A reference to the element or an
+ * element id which represent the ovelay content to be removed.
+ * @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:remove-overlay
+ */
+ removeOverlay: function( element ) {
+ var i;
+
+ element = $.getElement( element );
+ i = getOverlayIndex( this.currentOverlays, element );
+
+ if ( i >= 0 ) {
+ this.currentOverlays[ i ].destroy();
+ this.currentOverlays.splice( i, 1 );
+ THIS[ this.hash ].forceRedraw = true;
+ /**
+ * Raised when an overlay is removed from the viewer
+ * (see {@link OpenSeadragon.Viewer#removeOverlay}).
+ *
+ * @event remove-overlay
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the
+ * Viewer which raised the event.
+ * @property {Element} element - The overlay element.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'remove-overlay', {
+ element: element
+ });
+ }
+ return this;
+ },
/**
- * @function
- * @name OpenSeadragon.Viewer.prototype.goToPage
+ * Removes all currently configured Overlays from this Viewer and schedules
+ * an update.
+ * @method
* @return {OpenSeadragon.Viewer} Chainable.
+ * @fires OpenSeadragon.Viewer.event:clear-overlay
*/
- goToPage: function( page ){
- //page is a 1 based index so normalize now
- //page = page;
- this.raiseEvent( 'page', { page: page, viewer: this } );
+ clearOverlays: function() {
+ while ( this.currentOverlays.length > 0 ) {
+ this.currentOverlays.pop().destroy();
+ }
+ THIS[ this.hash ].forceRedraw = true;
+ /**
+ * Raised when all overlays are removed from the viewer (see {@link OpenSeadragon.Drawer#clearOverlays}).
+ *
+ * @event clear-overlay
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'clear-overlay', {} );
+ return this;
+ },
- if( this.tileSources.length > page ){
-
- THIS[ this.hash ].sequence = page;
+ /**
+ * Updates the sequence buttons.
+ * @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
+ * @private
+ * @param {Number} Sequence Value
+ */
+ _updateSequenceButtons: function( page ) {
- if( this.nextButton ){
- if( ( this.tileSources.length - 1 ) === page ){
+ if ( this.nextButton ) {
+ if( ( this.tileSources.length - 1 ) === page ) {
//Disable next button
- this.nextButton.disable();
+ if ( !this.navPrevNextWrap ) {
+ this.nextButton.disable();
+ }
} else {
this.nextButton.enable();
}
}
- if( this.previousButton ){
- if( page > 0 ){
+ if ( this.previousButton ) {
+ if ( page > 0 ) {
//Enable previous button
this.previousButton.enable();
} else {
- this.previousButton.disable();
+ if ( !this.navPrevNextWrap ) {
+ this.previousButton.disable();
+ }
}
}
+ },
+
+ /**
+ * Display a message in the viewport
+ * @function OpenSeadragon.Viewer.prototype._showMessage
+ * @private
+ * @param {String} text message
+ */
+ _showMessage: function ( message ) {
+ this._hideMessage();
- this.open( this.tileSources[ page ] );
- }
+ var div = $.makeNeutralElement( "div" );
+ div.appendChild( document.createTextNode( message ) );
- if( $.isFunction( this.onPageChange ) ){
- this.onPageChange({
- page: page,
- viewer: this
- });
+ this.messageDiv = $.makeCenteredNode( div );
+
+ $.addClass(this.messageDiv, "openseadragon-message");
+
+ this.container.appendChild( this.messageDiv );
+ },
+
+ /**
+ * Hide any currently displayed viewport message
+ * @function OpenSeadragon.Viewer.prototype._hideMessage
+ * @private
+ */
+ _hideMessage: function () {
+ var div = this.messageDiv;
+ if (div) {
+ div.parentNode.removeChild(div);
+ delete this.messageDiv;
}
- if( this.referenceStrip ){
- this.referenceStrip.setFocus( page );
+ }
+});
+
+
+/**
+ * _getSafeElemSize is like getElementSize(), but refuses to return 0 for x or y,
+ * which was causing some calling operations in updateOnce and openTileSource to
+ * return NaN.
+ * @returns {Point}
+ * @private
+ */
+function _getSafeElemSize (oElement) {
+ oElement = $.getElement( oElement );
+
+ return new $.Point(
+ (oElement.clientWidth === 0 ? 1 : oElement.clientWidth),
+ (oElement.clientHeight === 0 ? 1 : oElement.clientHeight)
+ );
+}
+
+/**
+ * @function
+ * @private
+ */
+function getTileSourceImplementation( viewer, tileSource, successCallback,
+ failCallback ) {
+ var _this = viewer;
+
+ //allow plain xml strings or json strings to be parsed here
+ if ( $.type( tileSource ) == 'string' ) {
+ if ( tileSource.match( /\s*<.*/ ) ) {
+ tileSource = $.parseXml( tileSource );
+ } else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
+ /*jshint evil:true*/
+ tileSource = eval( '(' + tileSource + ')' );
}
- return this;
}
-});
+ setTimeout( function() {
+ if ( $.type( tileSource ) == 'string' ) {
+ //If its still a string it means it must be a url at this point
+ tileSource = new $.TileSource( tileSource, function( event ) {
+ successCallback( event.tileSource );
+ });
+ tileSource.addHandler( 'open-failed', function( event ) {
+ failCallback( event );
+ } );
+
+ } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) {
+ if ( $.isFunction( tileSource.getTileUrl ) ) {
+ //Custom tile source
+ var customTileSource = new $.TileSource( tileSource );
+ customTileSource.getTileUrl = tileSource.getTileUrl;
+ successCallback( customTileSource );
+ } else {
+ //inline configuration
+ var $TileSource = $.TileSource.determineType( _this, tileSource );
+ if ( !$TileSource ) {
+ failCallback( {
+ message: "Unable to load TileSource",
+ source: tileSource
+ });
+ return;
+ }
+ var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
+ var readySource = new $TileSource( options );
+ successCallback( readySource );
+ }
+ } else {
+ //can assume it's already a tile source implementation
+ successCallback( tileSource );
+ }
+ }, 1 );
+}
/**
* @function
* @private
*/
function openTileSource( viewer, source ) {
- var _this = viewer,
- overlay,
- i;
+ var i,
+ _this = viewer;
if ( _this.source ) {
_this.close( );
}
-
- // to ignore earlier opens
- THIS[ _this.hash ].lastOpenStartTime = +new Date();
- window.setTimeout( function () {
- if ( THIS[ _this.hash ].lastOpenStartTime > THIS[ _this.hash ].lastOpenEndTime ) {
- THIS[ _this.hash ].setMessage( $.getString( "Messages.Loading" ) );
- }
- }, 2000);
-
- THIS[ _this.hash ].lastOpenEndTime = +new Date();
- _this.canvas.innerHTML = "";
- THIS[ _this.hash ].prevContainerSize = $.getElementSize( _this.container );
+ THIS[ _this.hash ].prevContainerSize = _getSafeElemSize( _this.container );
if( _this.collectionMode ){
@@ -981,25 +1871,26 @@ function openTileSource( viewer, source ) {
_this.viewport = _this.viewport ? _this.viewport : new $.Viewport({
collectionMode: true,
collectionTileSource: _this.source,
- containerSize: THIS[ _this.hash ].prevContainerSize,
- contentSize: _this.source.dimensions,
+ containerSize: THIS[ _this.hash ].prevContainerSize,
+ contentSize: _this.source.dimensions,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
showNavigator: false,
minZoomImageRatio: 1,
maxZoomPixelRatio: 1,
- viewer: _this //,
+ viewer: _this,
+ degrees: _this.degrees //,
//TODO: figure out how to support these in a way that makes sense
//minZoomLevel: this.minZoomLevel,
//maxZoomLevel: this.maxZoomLevel
});
- }else{
+ } else {
if( source ){
_this.source = source;
}
_this.viewport = _this.viewport ? _this.viewport : new $.Viewport({
- containerSize: THIS[ _this.hash ].prevContainerSize,
- contentSize: _this.source.dimensions,
+ containerSize: THIS[ _this.hash ].prevContainerSize,
+ contentSize: _this.source.dimensions,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
minZoomImageRatio: _this.minZoomImageRatio,
@@ -1010,21 +1901,23 @@ function openTileSource( viewer, source ) {
defaultZoomLevel: _this.defaultZoomLevel,
minZoomLevel: _this.minZoomLevel,
maxZoomLevel: _this.maxZoomLevel,
- viewer: _this
+ viewer: _this,
+ degrees: _this.degrees
});
}
-
- if( _this.preserveVewport ){
+
+ if( _this.preserveViewport ){
_this.viewport.resetContentSize( _this.source.dimensions );
- }
+ }
_this.source.overlays = _this.source.overlays || [];
_this.drawer = new $.Drawer({
- source: _this.source,
- viewport: _this.viewport,
- element: _this.canvas,
- overlays: [].concat( _this.overlays ).concat( _this.source.overlays ),
+ viewer: _this,
+ source: _this.source,
+ viewport: _this.viewport,
+ element: _this.drawersContainer,
+ opacity: _this.opacity,
maxImageCacheCount: _this.maxImageCacheCount,
imageLoaderLimit: _this.imageLoaderLimit,
minZoomImageRatio: _this.minZoomImageRatio,
@@ -1036,23 +1929,49 @@ function openTileSource( viewer, source ) {
minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio,
timeout: _this.timeout,
debugMode: _this.debugMode,
- debugGridColor: _this.debugGridColor
+ debugGridColor: _this.debugGridColor,
+ crossOriginPolicy: _this.crossOriginPolicy
});
+ _this.drawers = [_this.drawer];
+
+ // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons
+ if (!_this.drawer.canRotate()) {
+ // Disable/remove the rotate left/right buttons since they aren't supported
+ if (_this.rotateLeft) {
+ i = _this.buttons.buttons.indexOf(_this.rotateLeft);
+ _this.buttons.buttons.splice(i, 1);
+ _this.buttons.element.removeChild(_this.rotateLeft.element);
+ }
+ if (_this.rotateRight) {
+ i = _this.buttons.buttons.indexOf(_this.rotateRight);
+ _this.buttons.buttons.splice(i, 1);
+ _this.buttons.element.removeChild(_this.rotateRight.element);
+ }
+ }
//Instantiate a navigator if configured
- if ( _this.showNavigator && ! _this.navigator && !_this.collectionMode ){
- _this.navigator = new $.Navigator({
- id: _this.navigatorElement,
- position: _this.navigatorPosition,
- sizeRatio: _this.navigatorSizeRatio,
- height: _this.navigatorHeight,
- width: _this.navigatorWidth,
- tileSources: _this.tileSources,
- tileHost: _this.tileHost,
- prefixUrl: _this.prefixUrl,
- overlays: _this.overlays,
- viewer: _this
- });
+ if ( _this.showNavigator && !_this.collectionMode ){
+ // Note: By passing the fully parsed source, the navigator doesn't
+ // have to load it again.
+ if ( _this.navigator ) {
+ _this.navigator.open( source );
+ } else {
+ _this.navigator = new $.Navigator({
+ id: _this.navigatorId,
+ position: _this.navigatorPosition,
+ sizeRatio: _this.navigatorSizeRatio,
+ maintainSizeRatio: _this.navigatorMaintainSizeRatio,
+ top: _this.navigatorTop,
+ left: _this.navigatorLeft,
+ width: _this.navigatorWidth,
+ height: _this.navigatorHeight,
+ autoResize: _this.navigatorAutoResize,
+ tileSources: source,
+ tileHost: _this.tileHost,
+ prefixUrl: _this.prefixUrl,
+ viewer: _this
+ });
+ }
}
//Instantiate a referencestrip if configured
@@ -1067,7 +1986,6 @@ function openTileSource( viewer, source ) {
tileSources: _this.tileSources,
tileHost: _this.tileHost,
prefixUrl: _this.prefixUrl,
- overlays: _this.overlays,
viewer: _this
});
}
@@ -1076,74 +1994,134 @@ function openTileSource( viewer, source ) {
THIS[ _this.hash ].animating = false;
THIS[ _this.hash ].forceRedraw = true;
- scheduleUpdate( _this, updateMulti );
+ _this._updateRequestId = scheduleUpdate( _this, updateMulti );
- //Assuming you had programatically created a bunch of overlays
- //and added them via configuration
- for ( i = 0; i < _this.overlayControls.length; i++ ) {
-
- overlay = _this.overlayControls[ i ];
-
- if ( overlay.point ) {
-
- _this.drawer.addOverlay(
- overlay.id,
- new $.Point(
- overlay.point.X,
- overlay.point.Y
- ),
- $.OverlayPlacement.TOP_LEFT
- );
-
- } else {
-
- _this.drawer.addOverlay(
- overlay.id,
- new $.Rect(
- overlay.rect.Point.X,
- overlay.rect.Point.Y,
- overlay.rect.Width,
- overlay.rect.Height
- ),
- overlay.placement
- );
-
- }
- }
VIEWERS[ _this.hash ] = _this;
- if( _this.navigator ){
- _this.navigator.open( source );
- }
+ loadOverlays( _this );
- _this.raiseEvent( 'open', { source: source, viewer: _this } );
+ /**
+ * Raised when the viewer has opened and loaded one or more TileSources.
+ *
+ * @event open
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
+ * @property {OpenSeadragon.TileSource} source
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ _this.raiseEvent( 'open', { source: source } );
return _this;
}
+function loadOverlays( _this ) {
+ _this.currentOverlays = [];
+ for ( var i = 0; i < _this.overlays.length; i++ ) {
+ _this.currentOverlays[ i ] = getOverlayObject( _this, _this.overlays[ i ] );
+ }
+ for ( var j = 0; j < _this.source.overlays.length; j++ ) {
+ _this.currentOverlays[ i + j ] =
+ getOverlayObject( _this, _this.source.overlays[ j ] );
+ }
+}
+function getOverlayObject( viewer, overlay ) {
+ if ( overlay instanceof $.Overlay ) {
+ return overlay;
+ }
+ var element = null;
+ if ( overlay.element ) {
+ element = $.getElement( overlay.element );
+ } else {
+ var id = overlay.id ?
+ overlay.id :
+ "openseadragon-overlay-" + Math.floor( Math.random() * 10000000 );
+
+ element = $.getElement( overlay.id );
+ if ( !element ) {
+ element = document.createElement( "a" );
+ element.href = "#/overlay/" + id;
+ }
+ element.id = id;
+ $.addClass( element, overlay.className ?
+ overlay.className :
+ "openseadragon-overlay"
+ );
+ }
+
+ var location = overlay.location;
+ if ( !location ) {
+ if ( overlay.width && overlay.height ) {
+ location = overlay.px !== undefined ?
+ viewer.viewport.imageToViewportRectangle( new $.Rect(
+ overlay.px,
+ overlay.py,
+ overlay.width,
+ overlay.height
+ ) ) :
+ new $.Rect(
+ overlay.x,
+ overlay.y,
+ overlay.width,
+ overlay.height
+ );
+ } else {
+ location = overlay.px !== undefined ?
+ viewer.viewport.imageToViewportCoordinates( new $.Point(
+ overlay.px,
+ overlay.py
+ ) ) :
+ new $.Point(
+ overlay.x,
+ overlay.y
+ );
+ }
+ }
+
+ var placement = overlay.placement;
+ if ( placement && ( $.type( placement ) === "string" ) ) {
+ placement = $.OverlayPlacement[ overlay.placement.toUpperCase() ];
+ }
+
+ return new $.Overlay({
+ element: element,
+ location: location,
+ placement: placement,
+ onDraw: overlay.onDraw,
+ checkResize: overlay.checkResize
+ });
+}
+
+/**
+ * @private
+ * @inner
+ * Determines the index of the given overlay in the given overlays array.
+ */
+function getOverlayIndex( overlays, element ) {
+ var i;
+ for ( i = overlays.length - 1; i >= 0; i-- ) {
+ if ( overlays[ i ].element === element ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function drawOverlays( viewport, overlays, container ) {
+ var i,
+ length = overlays.length;
+ for ( i = 0; i < length; i++ ) {
+ overlays[ i ].drawHTML( container, viewport );
+ }
+}
///////////////////////////////////////////////////////////////////////////////
// Schedulers provide the general engine for animation
///////////////////////////////////////////////////////////////////////////////
-function scheduleUpdate( viewer, updateFunc, prevUpdateTime ){
- var currentTime,
- targetTime,
- deltaTime;
-
- if ( THIS[ viewer.hash ].animating ) {
- return $.requestAnimationFrame( function(){
- updateFunc( viewer );
- } );
- }
-
- currentTime = +new Date();
- prevUpdateTime = prevUpdateTime ? prevUpdateTime : currentTime;
- // 60 frames per second is ideal
- targetTime = prevUpdateTime + 1000 / 60;
- deltaTime = Math.max( 1, targetTime - currentTime );
-
+function scheduleUpdate( viewer, updateFunc ){
return $.requestAnimationFrame( function(){
updateFunc( viewer );
} );
@@ -1164,8 +2142,8 @@ function beginControlsAutoHide( viewer ) {
return;
}
viewer.controlsShouldFade = true;
- viewer.controlsFadeBeginTime =
- +new Date() +
+ viewer.controlsFadeBeginTime =
+ $.now() +
viewer.controlsFadeDelay;
window.setTimeout( function(){
@@ -1181,7 +2159,7 @@ function updateControlsFade( viewer ) {
opacity,
i;
if ( viewer.controlsShouldFade ) {
- currentTime = new Date().getTime();
+ currentTime = $.now();
deltaTime = currentTime - viewer.controlsFadeBeginTime;
opacity = 1.0 - deltaTime / viewer.controlsFadeLength;
@@ -1189,12 +2167,14 @@ function updateControlsFade( viewer ) {
opacity = Math.max( 0.0, opacity );
for ( i = viewer.controls.length - 1; i >= 0; i--) {
- viewer.controls[ i ].setOpacity( opacity );
+ if (viewer.controls[ i ].autoFade) {
+ viewer.controls[ i ].setOpacity( opacity );
+ }
}
if ( opacity > 0 ) {
// fade again
- scheduleControlsFade( viewer );
+ scheduleControlsFade( viewer );
}
}
}
@@ -1220,124 +2200,229 @@ function onFocus(){
function onBlur(){
beginControlsAutoHide( this );
-
+
}
-function onCanvasClick( tracker, position, quick, shift ) {
- var zoomPreClick,
+function onCanvasClick( event ) {
+ var zoomPerClick,
factor;
- if ( this.viewport && quick ) { // ignore clicks where mouse moved
+ if ( !event.preventDefaultAction && this.viewport && event.quick ) { // ignore clicks where mouse moved
zoomPerClick = this.zoomPerClick;
- factor = shift ? 1.0 / zoomPerClick : zoomPerClick;
+ factor = event.shift ? 1.0 / zoomPerClick : zoomPerClick;
this.viewport.zoomBy(
- factor,
- this.viewport.pointFromPixel( position, true )
+ factor,
+ this.viewport.pointFromPixel( event.position, true )
);
this.viewport.applyConstraints();
}
- this.raiseEvent( 'canvas-click', {
- tracker: tracker,
- position: position,
- quick: quick,
- shift: shift
+ /**
+ * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
+ *
+ * @event canvas-click
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Boolean} quick - True only if the clickDistThreshold and clickDeltaThreshold are both passed. Useful for differentiating between clicks and drags.
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'canvas-click', {
+ tracker: event.eventSource,
+ position: event.position,
+ quick: event.quick,
+ shift: event.shift,
+ originalEvent: event.originalEvent
});
}
-function onCanvasDrag( tracker, position, delta, shift ) {
- if ( this.viewport ) {
+function onCanvasDrag( event ) {
+ if ( !event.preventDefaultAction && this.viewport ) {
if( !this.panHorizontal ){
- delta.x = 0;
+ event.delta.x = 0;
}
if( !this.panVertical ){
- delta.y = 0;
+ event.delta.y = 0;
}
- this.viewport.panBy(
- this.viewport.deltaPointsFromPixels(
- delta.negate()
- )
+ this.viewport.panBy(
+ this.viewport.deltaPointsFromPixels(
+ event.delta.negate()
+ )
);
if( this.constrainDuringPan ){
this.viewport.applyConstraints();
}
}
- this.raiseEvent( 'canvas-click', {
- tracker: tracker,
- position: position,
- delta: delta,
- shift: shift
+ /**
+ * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
+ *
+ * @event canvas-drag
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'canvas-drag', {
+ tracker: event.eventSource,
+ position: event.position,
+ delta: event.delta,
+ shift: event.shift,
+ originalEvent: event.originalEvent
});
}
-function onCanvasRelease( tracker, position, insideElementPress, insideElementRelease ) {
- if ( insideElementPress && this.viewport ) {
+function onCanvasRelease( event ) {
+ if ( event.insideElementPressed && this.viewport ) {
this.viewport.applyConstraints();
}
- this.raiseEvent( 'canvas-release', {
- tracker: tracker,
- position: position,
- insideElementPress: insideElementPress,
- insideElementRelease: insideElementRelease
+ /**
+ * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
+ *
+ * @event canvas-release
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
+ * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'canvas-release', {
+ tracker: event.eventSource,
+ position: event.position,
+ insideElementPressed: event.insideElementPressed,
+ insideElementReleased: event.insideElementReleased,
+ originalEvent: event.originalEvent
});
}
-function onCanvasScroll( tracker, position, scroll, shift ) {
+function onCanvasScroll( event ) {
var factor;
- if ( this.viewport ) {
- factor = Math.pow( this.zoomPerScroll, scroll );
- this.viewport.zoomBy(
- factor,
- this.viewport.pointFromPixel( position, true )
+ if ( !event.preventDefaultAction && this.viewport ) {
+ factor = Math.pow( this.zoomPerScroll, event.scroll );
+ this.viewport.zoomBy(
+ factor,
+ this.viewport.pointFromPixel( event.position, true )
);
this.viewport.applyConstraints();
}
- this.raiseEvent( 'canvas-scroll', {
- tracker: tracker,
- position: position,
- scroll: scroll,
- shift: shift
+ /**
+ * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel, touch pinch, etc.).
+ *
+ * @event canvas-scroll
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Number} scroll - The scroll delta for the event.
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'canvas-scroll', {
+ tracker: event.eventSource,
+ position: event.position,
+ scroll: event.scroll,
+ shift: event.shift,
+ originalEvent: event.originalEvent
});
//cancels event
return false;
}
-function onContainerExit( tracker, position, buttonDownElement, buttonDownAny ) {
- if ( !buttonDownElement ) {
+function onContainerExit( event ) {
+ if ( !event.insideElementPressed ) {
THIS[ this.hash ].mouseInside = false;
if ( !THIS[ this.hash ].animating ) {
beginControlsAutoHide( this );
}
}
- this.raiseEvent( 'container-exit', {
- tracker: tracker,
- position: position,
- buttonDownElement: buttonDownElement,
- buttonDownAny: buttonDownAny
+ /**
+ * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
+ *
+ * @event container-exit
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'container-exit', {
+ tracker: event.eventSource,
+ position: event.position,
+ insideElementPressed: event.insideElementPressed,
+ buttonDownAny: event.buttonDownAny,
+ originalEvent: event.originalEvent
});
}
-function onContainerRelease( tracker, position, insideElementPress, insideElementRelease ) {
- if ( !insideElementRelease ) {
+function onContainerRelease( event ) {
+ if ( !event.insideElementReleased ) {
THIS[ this.hash ].mouseInside = false;
if ( !THIS[ this.hash ].animating ) {
beginControlsAutoHide( this );
}
}
- this.raiseEvent( 'container-release', {
- tracker: tracker,
- position: position,
- insideElementPress: insideElementPress,
- insideElementRelease: insideElementRelease
+ /**
+ * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element.
+ *
+ * @event container-release
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
+ * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'container-release', {
+ tracker: event.eventSource,
+ position: event.position,
+ insideElementPressed: event.insideElementPressed,
+ insideElementReleased: event.insideElementReleased,
+ originalEvent: event.originalEvent
});
}
-function onContainerEnter( tracker, position, buttonDownElement, buttonDownAny ) {
+function onContainerEnter( event ) {
THIS[ this.hash ].mouseInside = true;
abortControlsAutoHide( this );
- this.raiseEvent( 'container-enter', {
- tracker: tracker,
- position: position,
- buttonDownElement: buttonDownElement,
- buttonDownAny: buttonDownAny
+ /**
+ * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
+ *
+ * @event container-enter
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'container-enter', {
+ tracker: event.eventSource,
+ position: event.position,
+ insideElementPressed: event.insideElementPressed,
+ buttonDownAny: event.buttonDownAny,
+ originalEvent: event.originalEvent
});
}
@@ -1347,16 +2432,17 @@ function onContainerEnter( tracker, position, buttonDownElement, buttonDownAny )
///////////////////////////////////////////////////////////////////////////////
function updateMulti( viewer ) {
-
- var beginTime;
-
if ( !viewer.source ) {
+ viewer._updateRequestId = null;
return;
}
- beginTime = +new Date();
updateOnce( viewer );
- scheduleUpdate( viewer, arguments.callee, beginTime );
+
+ // Request the next frame, unless we've been closed during the updateOnce()
+ if ( viewer.source ) {
+ viewer._updateRequestId = scheduleUpdate( viewer, updateMulti );
+ }
}
function updateOnce( viewer ) {
@@ -1370,11 +2456,16 @@ function updateOnce( viewer ) {
//viewer.profiler.beginUpdate();
- containerSize = $.getElementSize( viewer.container );
- if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
- // maintain image position
- viewer.viewport.resize( containerSize, true );
- THIS[ viewer.hash ].prevContainerSize = containerSize;
+ if ( viewer.autoResize ) {
+ containerSize = _getSafeElemSize( viewer.container );
+ if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
+ // maintain image position
+ var oldBounds = viewer.viewport.getBounds();
+ var oldCenter = viewer.viewport.getCenter();
+ resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
+ THIS[ viewer.hash ].prevContainerSize = containerSize;
+ THIS[ viewer.hash ].forceRedraw = true;
+ }
}
animated = viewer.viewport.update();
@@ -1384,26 +2475,55 @@ function updateOnce( viewer ) {
}
if ( !THIS[ viewer.hash ].animating && animated ) {
- viewer.raiseEvent( "animationstart" );
+ /**
+ * Raised when any spring animation starts (zoom, pan, etc.).
+ *
+ * @event animation-start
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ viewer.raiseEvent( "animation-start" );
abortControlsAutoHide( viewer );
}
if ( animated ) {
- viewer.drawer.update();
+ updateDrawers( viewer );
+ drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer );
if( viewer.navigator ){
viewer.navigator.update( viewer.viewport );
}
+ /**
+ * Raised when any spring animation update occurs (zoom, pan, etc.).
+ *
+ * @event animation
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
viewer.raiseEvent( "animation" );
- } else if ( THIS[ viewer.hash ].forceRedraw || viewer.drawer.needsUpdate() ) {
- viewer.drawer.update();
+ } else if ( THIS[ viewer.hash ].forceRedraw || drawersNeedUpdate( viewer ) ) {
+ updateDrawers( viewer );
+ drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer );
if( viewer.navigator ){
viewer.navigator.update( viewer.viewport );
}
THIS[ viewer.hash ].forceRedraw = false;
- }
+ }
if ( THIS[ viewer.hash ].animating && !animated ) {
- viewer.raiseEvent( "animationfinish" );
+ /**
+ * Raised when any spring animation ends (zoom, pan, etc.).
+ *
+ * @event animation-finish
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ viewer.raiseEvent( "animation-finish" );
if ( !THIS[ viewer.hash ].mouseInside ) {
beginControlsAutoHide( viewer );
@@ -1415,7 +2535,45 @@ function updateOnce( viewer ) {
//viewer.profiler.endUpdate();
}
+// This function resizes the viewport and recenters the image
+// as it was before resizing.
+// TODO: better adjust width and height. The new width and height
+// should depend on the image dimensions and on the dimensions
+// of the viewport before and after switching mode.
+function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter ) {
+ var viewport = viewer.viewport;
+ viewport.resize( containerSize, true );
+
+ // We try to remove blanks as much as possible
+ var imageHeight = 1 / viewer.source.aspectRatio;
+ var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1;
+ var newHeight = oldBounds.height <= imageHeight ?
+ oldBounds.height : imageHeight;
+
+ var newBounds = new $.Rect(
+ oldCenter.x - ( newWidth / 2.0 ),
+ oldCenter.y - ( newHeight / 2.0 ),
+ newWidth,
+ newHeight
+ );
+ viewport.fitBounds( newBounds, true );
+}
+
+function updateDrawers( viewer ) {
+ for (var i = 0; i < viewer.drawers.length; i++ ) {
+ viewer.drawers[i].update();
+ }
+}
+
+function drawersNeedUpdate( viewer ) {
+ for (var i = 0; i < viewer.drawers.length; i++ ) {
+ if (viewer.drawers[i].needsUpdate()) {
+ return true;
+ }
+ }
+ return false;
+}
///////////////////////////////////////////////////////////////////////////////
// Navigation Controls
@@ -1427,7 +2585,7 @@ function resolveUrl( prefix, url ) {
function beginZoomingIn() {
- THIS[ this.hash ].lastZoomTime = +new Date();
+ THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
@@ -1435,7 +2593,7 @@ function beginZoomingIn() {
function beginZoomingOut() {
- THIS[ this.hash ].lastZoomTime = +new Date();
+ THIS[ this.hash ].lastZoomTime = $.now();
THIS[ this.hash ].zoomFactor = 1.0 / this.zoomPerSecond;
THIS[ this.hash ].zooming = true;
scheduleZoom( this );
@@ -1455,10 +2613,10 @@ function scheduleZoom( viewer ) {
function doZoom() {
var currentTime,
deltaTime,
- adjustFactor;
+ adjustedFactor;
if ( THIS[ this.hash ].zooming && this.viewport) {
- currentTime = +new Date();
+ currentTime = $.now();
deltaTime = currentTime - THIS[ this.hash ].lastZoomTime;
adjustedFactor = Math.pow( THIS[ this.hash ].zoomFactor, deltaTime / 1000 );
@@ -1473,8 +2631,8 @@ function doZoom() {
function doSingleZoomIn() {
if ( this.viewport ) {
THIS[ this.hash ].zooming = false;
- this.viewport.zoomBy(
- this.zoomPerClick / 1.0
+ this.viewport.zoomBy(
+ this.zoomPerClick / 1.0
);
this.viewport.applyConstraints();
}
@@ -1505,10 +2663,15 @@ function onHome() {
}
-function onFullPage() {
- this.setFullPage( !this.isFullPage() );
+function onFullScreen() {
+ if ( this.isFullPage() && !$.isFullScreen() ) {
+ // Is fullPage but not fullScreen
+ this.setFullPage( false );
+ } else {
+ this.setFullScreen( !this.isFullPage() );
+ }
// correct for no mouseout event on change
- if( this.buttons ){
+ if ( this.buttons ) {
this.buttons.emulateExit();
}
this.fullPageButton.element.focus();
@@ -1517,15 +2680,53 @@ function onFullPage() {
}
}
+/**
+ * Note: The current rotation feature is limited to 90 degree turns.
+ */
+function onRotateLeft() {
+ if ( this.viewport ) {
+ var currRotation = this.viewport.getRotation();
+ if (currRotation === 0) {
+ currRotation = 270;
+ }
+ else {
+ currRotation -= 90;
+ }
+ this.viewport.setRotation(currRotation);
+ }
+}
+
+/**
+ * Note: The current rotation feature is limited to 90 degree turns.
+ */
+function onRotateRight() {
+ if ( this.viewport ) {
+ var currRotation = this.viewport.getRotation();
+ if (currRotation === 270) {
+ currRotation = 0;
+ }
+ else {
+ currRotation += 90;
+ }
+ this.viewport.setRotation(currRotation);
+ }
+}
+
function onPrevious(){
var previous = THIS[ this.hash ].sequence - 1;
+ if(this.navPrevNextWrap && previous < 0){
+ previous += this.tileSources.length;
+ }
this.goToPage( previous );
}
function onNext(){
var next = THIS[ this.hash ].sequence + 1;
+ if(this.navPrevNextWrap && next >= this.tileSources.length){
+ next = 0;
+ }
this.goToPage( next );
}
diff --git a/src/viewport.js b/src/viewport.js
index d5800cf1..aadb81e9 100644
--- a/src/viewport.js
+++ b/src/viewport.js
@@ -1,12 +1,50 @@
+/*
+ * OpenSeadragon - Viewport
+ *
+ * Copyright (C) 2009 CodePlex Foundation
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
+ *
+ * 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 CodePlex Foundation 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.
+ */
+
(function( $ ){
/**
- * @class
+ * @class Viewport
+ * @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.) for an {@link OpenSeadragon.Viewer}.
+ * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#viewport}).
+ *
+ * @memberof OpenSeadragon
*/
$.Viewport = function( options ) {
- //backward compatibility for positional args while prefering more
+ //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 ){
@@ -19,14 +57,14 @@ $.Viewport = function( options ) {
//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
+ //being passed 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,
@@ -45,22 +83,23 @@ $.Viewport = function( options ) {
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
- maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel
+ maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
+ degrees: $.DEFAULT_SETTINGS.degrees
}, options );
this.centerSpringX = new $.Spring({
- initial: 0,
+ initial: 0,
springStiffness: this.springStiffness,
animationTime: this.animationTime
});
this.centerSpringY = new $.Spring({
- initial: 0,
+ initial: 0,
springStiffness: this.springStiffness,
animationTime: this.animationTime
});
this.zoomSpring = new $.Spring({
- initial: 1,
+ initial: 1,
springStiffness: this.springStiffness,
animationTime: this.animationTime
});
@@ -70,11 +109,12 @@ $.Viewport = function( options ) {
this.update();
};
-$.Viewport.prototype = {
+$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* @function
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:reset-size
*/
resetContentSize: function( contentSize ){
this.contentSize = contentSize;
@@ -86,12 +126,21 @@ $.Viewport.prototype = {
this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
if( this.viewer ){
- this.viewer.raiseEvent( 'reset-size', {
- contentSize: contentSize,
- viewer: this.viewer
+ /**
+ * Raised when the viewer's content size is reset (see {@link OpenSeadragon.Viewport#resetContentSize}).
+ *
+ * @event reset-size
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.Point} contentSize
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'reset-size', {
+ contentSize: contentSize
});
}
-
+
return this;
},
@@ -99,14 +148,14 @@ $.Viewport.prototype = {
* @function
*/
getHomeZoom: function() {
- var aspectFactor =
+ var aspectFactor =
this.contentAspectX / this.getAspectRatio();
if( this.defaultZoomLevel ){
return this.defaultZoomLevel;
} else {
- return ( aspectFactor >= 1 ) ?
- 1 :
+ return ( aspectFactor >= 1 ) ?
+ 1 :
aspectFactor;
}
},
@@ -120,9 +169,9 @@ $.Viewport.prototype = {
height = width / this.getAspectRatio();
return new $.Rect(
- center.x - ( width / 2.0 ),
+ center.x - ( width / 2.0 ),
center.y - ( height / 2.0 ),
- width,
+ width,
height
);
},
@@ -130,12 +179,22 @@ $.Viewport.prototype = {
/**
* @function
* @param {Boolean} immediately
+ * @fires OpenSeadragon.Viewer.event:home
*/
goHome: function( immediately ) {
if( this.viewer ){
- this.viewer.raiseEvent( 'home', {
- immediately: immediately,
- viewer: this.viewer
+ /**
+ * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).
+ *
+ * @event home
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {Boolean} immediately
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'home', {
+ immediately: immediately
});
}
return this.fitBounds( this.getHomeBounds(), immediately );
@@ -146,8 +205,8 @@ $.Viewport.prototype = {
*/
getMinZoom: function() {
var homeZoom = this.getHomeZoom(),
- zoom = this.minZoomLevel ?
- this.minZoomLevel :
+ zoom = this.minZoomLevel ?
+ this.minZoomLevel :
this.minZoomImageRatio * homeZoom;
return Math.min( zoom, homeZoom );
@@ -176,13 +235,14 @@ $.Viewport.prototype = {
*/
getContainerSize: function() {
return new $.Point(
- this.containerSize.x,
+ this.containerSize.x,
this.containerSize.y
);
},
/**
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
getBounds: function( current ) {
var center = this.getCenter( current ),
@@ -190,15 +250,16 @@ $.Viewport.prototype = {
height = width / this.getAspectRatio();
return new $.Rect(
- center.x - ( width / 2.0 ),
+ center.x - ( width / 2.0 ),
center.y - ( height / 2.0 ),
- width,
+ width,
height
);
},
/**
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
getCenter: function( current ) {
var centerCurrent = new $.Point(
@@ -231,8 +292,8 @@ $.Viewport.prototype = {
height = width / this.getAspectRatio();
bounds = new $.Rect(
centerCurrent.x - width / 2.0,
- centerCurrent.y - height / 2.0,
- width,
+ centerCurrent.y - height / 2.0,
+ width,
height
);
@@ -249,6 +310,7 @@ $.Viewport.prototype = {
/**
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
getZoom: function( current ) {
if ( current ) {
@@ -261,11 +323,12 @@ $.Viewport.prototype = {
/**
* @function
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:constrain
*/
applyConstraints: function( immediately ) {
var actualZoom = this.getZoom(),
constrainedZoom = Math.max(
- Math.min( actualZoom, this.getMaxZoom() ),
+ Math.min( actualZoom, this.getMaxZoom() ),
this.getMinZoom()
),
bounds,
@@ -275,10 +338,8 @@ $.Viewport.prototype = {
right,
top,
bottom,
- center,
dx = 0,
- dy = 0,
- dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
+ dy = 0;
if ( actualZoom != constrainedZoom ) {
this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
@@ -299,9 +360,9 @@ $.Viewport.prototype = {
} else {
if ( left < horizontalThreshold ) {
dx = horizontalThreshold - left;
- }
+ }
if ( right < horizontalThreshold ) {
- dx = dx ?
+ dx = dx ?
( dx + right - horizontalThreshold ) / 2 :
( right - horizontalThreshold );
}
@@ -312,9 +373,9 @@ $.Viewport.prototype = {
} else {
if ( top < verticalThreshold ) {
dy = ( verticalThreshold - top );
- }
+ }
if ( bottom < verticalThreshold ) {
- dy = dy ?
+ dy = dy ?
( dy + bottom - verticalThreshold ) / 2 :
( bottom - verticalThreshold );
}
@@ -333,9 +394,18 @@ $.Viewport.prototype = {
}
if( this.viewer ){
- this.viewer.raiseEvent( 'constrain', {
- immediately: immediately,
- viewer: this.viewer
+ /**
+ * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).
+ *
+ * @event constrain
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {Boolean} immediately
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'constrain', {
+ immediately: immediately
});
}
@@ -360,9 +430,9 @@ $.Viewport.prototype = {
var aspect = this.getAspectRatio(),
center = bounds.getCenter(),
newBounds = new $.Rect(
- bounds.x,
- bounds.y,
- bounds.width,
+ bounds.x,
+ bounds.y,
+ bounds.width,
bounds.height
),
oldBounds,
@@ -388,20 +458,20 @@ $.Viewport.prototype = {
return this.panTo( center, immediately );
}
- referencePoint = oldBounds.getTopLeft().times(
- this.containerSize.x / oldBounds.width
+ referencePoint = oldBounds.getTopLeft().times(
+ this.containerSize.x / oldBounds.width
).minus(
- newBounds.getTopLeft().times(
- this.containerSize.x / newBounds.width
+ newBounds.getTopLeft().times(
+ this.containerSize.x / newBounds.width
)
).divide(
- this.containerSize.x / oldBounds.width -
+ this.containerSize.x / oldBounds.width -
this.containerSize.x / newBounds.width
);
return this.zoomTo( newZoom, referencePoint, immediately );
},
-
+
/**
* @function
@@ -437,8 +507,8 @@ $.Viewport.prototype = {
var center = this.getCenter();
if ( this.wrapHorizontal ) {
- center.x = (
- this.contentAspectX + ( center.x % this.contentAspectX )
+ center.x = (
+ this.contentAspectX + ( center.x % this.contentAspectX )
) % this.contentAspectX;
this.centerSpringX.resetTo( center.x );
this.centerSpringX.update();
@@ -459,12 +529,14 @@ $.Viewport.prototype = {
* @param {OpenSeadragon.Point} delta
* @param {Boolean} immediately
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:pan
*/
panBy: function( delta, immediately ) {
var center = new $.Point(
this.centerSpringX.target.value,
this.centerSpringY.target.value
);
+ delta = delta.rotate( -this.degrees, new $.Point( 0, 0 ) );
return this.panTo( center.plus( delta ), immediately );
},
@@ -473,6 +545,7 @@ $.Viewport.prototype = {
* @param {OpenSeadragon.Point} center
* @param {Boolean} immediately
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:pan
*/
panTo: function( center, immediately ) {
if ( immediately ) {
@@ -484,10 +557,20 @@ $.Viewport.prototype = {
}
if( this.viewer ){
- this.viewer.raiseEvent( 'pan', {
+ /**
+ * Raised when the viewport is panned (see {@link OpenSeadragon.Viewport#panBy} and {@link OpenSeadragon.Viewport#panTo}).
+ *
+ * @event pan
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.Point} center
+ * @property {Boolean} immediately
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'pan', {
center: center,
- immediately: immediately,
- viewer: this.viewer
+ immediately: immediately
});
}
@@ -497,33 +580,54 @@ $.Viewport.prototype = {
/**
* @function
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:zoom
*/
zoomBy: function( factor, refPoint, immediately ) {
+ if( refPoint instanceof $.Point && !isNaN( refPoint.x ) && !isNaN( refPoint.y ) ) {
+ refPoint = refPoint.rotate(
+ -this.degrees,
+ new $.Point( this.centerSpringX.target.value, this.centerSpringY.target.value )
+ );
+ }
return this.zoomTo( this.zoomSpring.target.value * factor, refPoint, immediately );
},
/**
* @function
* @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:zoom
*/
zoomTo: function( zoom, refPoint, immediately ) {
- this.zoomPoint = refPoint instanceof $.Point ?
- refPoint :
+ this.zoomPoint = refPoint instanceof $.Point &&
+ !isNaN(refPoint.x) &&
+ !isNaN(refPoint.y) ?
+ refPoint :
null;
-
+
if ( immediately ) {
this.zoomSpring.resetTo( zoom );
- } else {
+ } else {
this.zoomSpring.springTo( zoom );
}
if( this.viewer ){
- this.viewer.raiseEvent( 'zoom', {
+ /**
+ * Raised when the viewport zoom level changes (see {@link OpenSeadragon.Viewport#zoomBy} and {@link OpenSeadragon.Viewport#zoomTo}).
+ *
+ * @event zoom
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {Number} zoom
+ * @property {OpenSeadragon.Point} refPoint
+ * @property {Boolean} immediately
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'zoom', {
zoom: zoom,
refPoint: refPoint,
- immediately: immediately,
- viewer: this.viewer
+ immediately: immediately
});
}
@@ -531,29 +635,73 @@ $.Viewport.prototype = {
},
/**
+ * Currently only 90 degree rotation is supported and it only works
+ * with the canvas. Additionally, the navigator does not rotate yet,
+ * debug mode doesn't rotate yet, and overlay rotation is only
+ * partially supported.
* @function
* @return {OpenSeadragon.Viewport} Chainable.
*/
+ setRotation: function( degrees ) {
+ if( !( this.viewer && this.viewer.drawer.canRotate() ) ) {
+ return this;
+ }
+
+ degrees = ( degrees + 360 ) % 360;
+ if( degrees % 90 !== 0 ) {
+ throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
+ }
+ this.degrees = degrees;
+ this.viewer.forceRedraw();
+
+ return this;
+ },
+
+ /**
+ * Gets the current rotation in degrees.
+ * @function
+ * @return {Number} The current rotation in degrees.
+ */
+ getRotation: function() {
+ return this.degrees;
+ },
+
+ /**
+ * @function
+ * @return {OpenSeadragon.Viewport} Chainable.
+ * @fires OpenSeadragon.Viewer.event:resize
+ */
resize: function( newContainerSize, maintain ) {
var oldBounds = this.getBounds(),
newBounds = oldBounds,
- widthDeltaFactor = newContainerSize.x / this.containerSize.x;
+ widthDeltaFactor;
this.containerSize = new $.Point(
- newContainerSize.x,
+ newContainerSize.x,
newContainerSize.y
);
- if (maintain) {
+ if ( maintain ) {
+ widthDeltaFactor = newContainerSize.x / this.containerSize.x;
newBounds.width = oldBounds.width * widthDeltaFactor;
newBounds.height = newBounds.width / this.getAspectRatio();
}
if( this.viewer ){
- this.viewer.raiseEvent( 'resize', {
+ /**
+ * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
+ *
+ * @event resize
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.Point} newContainerSize
+ * @property {Boolean} maintain
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.viewer.raiseEvent( 'resize', {
newContainerSize: newContainerSize,
- maintain: maintain,
- viewer: this.viewer
+ maintain: maintain
});
}
@@ -599,7 +747,9 @@ $.Viewport.prototype = {
/**
+ * Convert a delta (translation vector) from pixels coordinates to viewport coordinates
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
deltaPixelsFromPoints: function( deltaPoints, current ) {
return deltaPoints.times(
@@ -608,7 +758,9 @@ $.Viewport.prototype = {
},
/**
+ * Convert a delta (translation vector) from viewport coordinates to pixels coordinates.
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
deltaPointsFromPixels: function( deltaPixels, current ) {
return deltaPixels.divide(
@@ -617,7 +769,9 @@ $.Viewport.prototype = {
},
/**
+ * Convert image pixel coordinates to viewport coordinates.
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
pixelFromPoint: function( point, current ) {
var bounds = this.getBounds( current );
@@ -629,7 +783,9 @@ $.Viewport.prototype = {
},
/**
+ * Convert viewport coordinates to image pixel coordinates.
* @function
+ * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/
pointFromPixel: function( pixel, current ) {
var bounds = this.getBounds( current );
@@ -641,34 +797,65 @@ $.Viewport.prototype = {
},
/**
- * Translates from Seajax viewer coordinate
- * system to image coordinate system
+ * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
+ * This method can be called either by passing X,Y coordinates or an
+ * OpenSeadragon.Point
+ * @function
+ * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system.
+ * @param {Number} viewerX X coordinate in viewport coordinate system.
+ * @param {Number} viewerY Y coordinate in viewport coordinate system.
+ * @return {OpenSeadragon.Point} a point representing the coordinates in the image.
*/
- viewportToImageCoordinates: function(viewerX, viewerY) {
- return new $.Point(viewerX * this.contentSize.x, viewerY * this.contentSize.y * this.contentAspectX);
+ viewportToImageCoordinates: function( viewerX, viewerY ) {
+ if ( arguments.length == 1 ) {
+ //they passed a point instead of individual components
+ return this.viewportToImageCoordinates( viewerX.x, viewerX.y );
+ }
+ return new $.Point( viewerX * this.contentSize.x, viewerY * this.contentSize.y * this.contentAspectX );
},
/**
- * Translates from image coordinate system to
- * Seajax viewer coordinate system
+ * Translates from image coordinate system to OpenSeadragon viewer coordinate system
+ * This method can be called either by passing X,Y coordinates or an
+ * OpenSeadragon.Point
+ * @function
+ * @param {OpenSeadragon.Point} imageX the point in image coordinate system.
+ * @param {Number} imageX X coordinate in image coordinate system.
+ * @param {Number} imageY Y coordinate in image coordinate system.
+ * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport.
*/
imageToViewportCoordinates: function( imageX, imageY ) {
- return new $.Point( imageX / this.contentSize.x, imageY / this.contentSize.y / this.contentAspectX);
+ if ( arguments.length == 1 ) {
+ //they passed a point instead of individual components
+ return this.imageToViewportCoordinates( imageX.x, imageX.y );
+ }
+ return new $.Point( imageX / this.contentSize.x, imageY / this.contentSize.y / this.contentAspectX );
},
/**
- * Translates from a rectanlge which describes a portion of
- * the image in pixel coordinates to OpenSeadragon viewport
- * rectangle coordinates.
+ * Translates from a rectangle which describes a portion of the image in
+ * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
+ * This method can be called either by passing X,Y,width,height or an
+ * OpenSeadragon.Rect
+ * @function
+ * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system.
+ * @param {Number} imageX the X coordinate of the top left corner of the rectangle
+ * in image coordinate system.
+ * @param {Number} imageY the Y coordinate of the top left corner of the rectangle
+ * in image coordinate system.
+ * @param {Number} pixelWidth the width in pixel of the rectangle.
+ * @param {Number} pixelHeight the height in pixel of the rectangle.
*/
imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) {
var coordA,
coordB,
rect;
- if( arguments.length == 1 ){
+ if( arguments.length == 1 ) {
//they passed a rectangle instead of individual components
rect = imageX;
- return this.imageToViewportRectangle(rect.x, rect.y, rect.width, rect.height);
+ return this.imageToViewportRectangle(
+ rect.x, rect.y, rect.width, rect.height
+ );
}
coordA = this.imageToViewportCoordinates(
imageX, imageY
@@ -676,12 +863,169 @@ $.Viewport.prototype = {
coordB = this.imageToViewportCoordinates(
pixelWidth, pixelHeight
);
- return new $.Rect(
+ return new $.Rect(
coordA.x,
coordA.y,
- coordA.x + coordB.x,
- coordA.y + coordB.y
+ coordB.x,
+ coordB.y
);
+ },
+
+ /**
+ * Translates from a rectangle which describes a portion of
+ * the viewport in point coordinates to image rectangle coordinates.
+ * This method can be called either by passing X,Y,width,height or an
+ * OpenSeadragon.Rect
+ * @function
+ * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system.
+ * @param {Number} viewerX the X coordinate of the top left corner of the rectangle
+ * in viewport coordinate system.
+ * @param {Number} imageY the Y coordinate of the top left corner of the rectangle
+ * in viewport coordinate system.
+ * @param {Number} pointWidth the width of the rectangle in viewport coordinate system.
+ * @param {Number} pointHeight the height of the rectangle in viewport coordinate system.
+ */
+ viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) {
+ var coordA,
+ coordB,
+ rect;
+ if ( arguments.length == 1 ) {
+ //they passed a rectangle instead of individual components
+ rect = viewerX;
+ return this.viewportToImageRectangle(
+ rect.x, rect.y, rect.width, rect.height
+ );
+ }
+ coordA = this.viewportToImageCoordinates( viewerX, viewerY );
+ coordB = this.viewportToImageCoordinates( pointWidth, pointHeight );
+ return new $.Rect(
+ coordA.x,
+ coordA.y,
+ coordB.x,
+ coordB.y
+ );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the viewer element to image
+ * coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ viewerElementToImageCoordinates: function( pixel ) {
+ var point = this.pointFromPixel( pixel, true );
+ return this.viewportToImageCoordinates( point );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the image to
+ * viewer element coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ imageToViewerElementCoordinates: function( pixel ) {
+ var point = this.imageToViewportCoordinates( pixel );
+ return this.pixelFromPoint( point, true );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the window to image coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ windowToImageCoordinates: function( pixel ) {
+ var viewerCoordinates = pixel.minus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ return this.viewerElementToImageCoordinates( viewerCoordinates );
+ },
+
+ /**
+ * Convert image coordinates to pixel coordinates relative to the window.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ imageToWindowCoordinates: function( pixel ) {
+ var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
+ return viewerCoordinates.plus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ },
+
+ /**
+ * Convert pixel coordinates relative to the viewer element to viewport
+ * coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ viewerElementToViewportCoordinates: function( pixel ) {
+ return this.pointFromPixel( pixel, true );
+ },
+
+ /**
+ * Convert viewport coordinates to pixel coordinates relative to the
+ * viewer element.
+ * @param {OpenSeadragon.Point} point
+ * @returns {OpenSeadragon.Point}
+ */
+ viewportToViewerElementCoordinates: function( point ) {
+ return this.pixelFromPoint( point, true );
+ },
+
+ /**
+ * Convert pixel coordinates relative to the window to viewport coordinates.
+ * @param {OpenSeadragon.Point} pixel
+ * @returns {OpenSeadragon.Point}
+ */
+ windowToViewportCoordinates: function( pixel ) {
+ var viewerCoordinates = pixel.minus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ return this.viewerElementToViewportCoordinates( viewerCoordinates );
+ },
+
+ /**
+ * Convert viewport coordinates to pixel coordinates relative to the window.
+ * @param {OpenSeadragon.Point} point
+ * @returns {OpenSeadragon.Point}
+ */
+ viewportToWindowCoordinates: function( point ) {
+ var viewerCoordinates = this.viewportToViewerElementCoordinates( point );
+ return viewerCoordinates.plus(
+ OpenSeadragon.getElementPosition( this.viewer.element ));
+ },
+
+ /**
+ * Convert a viewport zoom to an image zoom.
+ * Image zoom: ratio of the original image size to displayed image size.
+ * 1 means original image size, 0.5 half size...
+ * Viewport zoom: ratio of the displayed image's width to viewport's width.
+ * 1 means identical width, 2 means image's width is twice the viewport's width...
+ * @function
+ * @param {Number} viewportZoom The viewport zoom
+ * target zoom.
+ * @returns {Number} imageZoom The image zoom
+ */
+ viewportToImageZoom: function( viewportZoom ) {
+ var imageWidth = this.viewer.source.dimensions.x;
+ var containerWidth = this.getContainerSize().x;
+ var viewportToImageZoomRatio = containerWidth / imageWidth;
+ return viewportZoom * viewportToImageZoomRatio;
+ },
+
+ /**
+ * Convert an image zoom to a viewport zoom.
+ * Image zoom: ratio of the original image size to displayed image size.
+ * 1 means original image size, 0.5 half size...
+ * Viewport zoom: ratio of the displayed image's width to viewport's width.
+ * 1 means identical width, 2 means image's width is twice the viewport's width...
+ * @function
+ * @param {Number} imageZoom The image zoom
+ * target zoom.
+ * @returns {Number} viewportZoom The viewport zoom
+ */
+ imageToViewportZoom: function( imageZoom ) {
+ var imageWidth = this.viewer.source.dimensions.x;
+ var containerWidth = this.getContainerSize().x;
+ var viewportToImageZoomRatio = imageWidth / containerWidth;
+ return imageZoom * viewportToImageZoomRatio;
}
};
diff --git a/test/basic.js b/test/basic.js
index ca4556c8..946a8ff3 100644
--- a/test/basic.js
+++ b/test/basic.js
@@ -1,115 +1,303 @@
+/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
+
(function() {
+ var viewer;
- // TODO: Tighten up springs and use "immediate" where possible, so tests run faster
- // TODO: Test drag
+ module('Basic', {
+ setup: function () {
+ var example = $('').appendTo("#qunit-fixture");
- var viewer = null;
+ testLog.reset();
- // ----------
- asyncTest('Open', function() {
- $(document).ready(function() {
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
- tileSources: '/test/data/testpattern.dzi',
- showNavigator: true
+ springStiffness: 100 // Faster animation = faster tests
});
+ },
+ teardown: function () {
+ if (viewer && viewer.close) {
+ viewer.close();
+ }
- ok(viewer, 'Viewer exists');
+ viewer = null;
+ }
+ });
- var openHandler = function(eventSender, eventData) {
- viewer.removeHandler('open', openHandler);
- ok(true, 'Open event was sent');
- equal(eventSender, viewer, 'Sender of open event was viewer');
- ok(eventData, 'Handler also received event data');
- ok(viewer.viewport, 'Viewport exists');
- start();
- };
+ // ----------
+ asyncTest('Open', function() {
+ ok(viewer, 'Viewer exists');
- viewer.addHandler('open', openHandler);
+ var openHandler = function(event) {
+ viewer.removeHandler('open', openHandler);
+ ok(true, 'Open event was sent');
+ ok(event, 'Handler received event data');
+ equal(event.eventSource, viewer, 'Sender of open event was viewer');
+ ok(viewer.viewport, 'Viewport exists');
+ ok(viewer.source, 'source exists');
+ ok(viewer._updateRequestId, 'timer is on');
+ start();
+ };
+
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('Open Error Handling', function() {
+ ok(viewer, 'Viewer exists');
+
+ viewer.addHandler('open', function(event) {
+ ok(false, "The open event should not fire for failed opens");
+ start();
});
+
+ viewer.addHandler('open-failed', function(event) {
+ ok(true, "The open-failed event should be fired when the source 404s");
+
+ equal($(".openseadragon-message").length, 1, "Open failures should display a message");
+
+ ok(testLog.log.contains('["AJAX request returned %s: %s",404,"/test/data/not-a-real-file"]'),
+ "AJAX failures should be logged to the console");
+
+ start();
+ });
+
+ viewer.open('/test/data/not-a-real-file');
});
// ----------
asyncTest('Zoom', function() {
- var viewport = viewer.viewport;
- equal(viewport.getZoom(), 1, 'We start out unzoomed');
+ viewer.addHandler("open", function () {
+ var viewport = viewer.viewport;
- var zoomHandler = function() {
- viewer.removeHandler('animationfinish', zoomHandler);
- equal(viewport.getZoom(), 2, 'Zoomed correctly');
- start();
- };
+ equal(viewport.getZoom(), 1, 'We start out unzoomed');
- viewer.addHandler('animationfinish', zoomHandler);
- viewport.zoomTo(2);
+ var zoomHandler = function() {
+ viewer.removeHandler('animation-finish', zoomHandler);
+ equal(viewport.getZoom(), 2, 'Zoomed correctly');
+ start();
+ };
+
+ viewer.addHandler('animation-finish', zoomHandler);
+ viewport.zoomTo(2);
+ });
+ viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('Pan', function() {
- var viewport = viewer.viewport;
- var center = viewport.getCenter();
- ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned');
+ viewer.addHandler("open", function () {
+ var viewport = viewer.viewport,
+ center = viewport.getCenter();
- var panHandler = function() {
- viewer.removeHandler('animationfinish', panHandler);
- center = viewport.getCenter();
- ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly');
- start();
- };
+ ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned');
- viewer.addHandler('animationfinish', panHandler);
- viewport.panTo(new OpenSeadragon.Point(0.1, 0.1));
+ var panHandler = function() {
+ viewer.removeHandler('animation-finish', panHandler);
+ center = viewport.getCenter();
+ ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly');
+ start();
+ };
+
+ viewer.addHandler('animation-finish', panHandler);
+ viewport.panTo(new OpenSeadragon.Point(0.1, 0.1));
+ });
+
+ viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('Home', function() {
- var viewport = viewer.viewport;
- var center = viewport.getCenter();
- ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned');
- notEqual(viewport.getZoom(), 1, 'We start out zoomed');
+ // Test setup:
+ function opener() {
+ var viewport = viewer.viewport;
+ viewport.panTo(new OpenSeadragon.Point(0.1, 0.1));
+ viewport.zoomTo(2);
+ }
- var homeHandler = function() {
- viewer.removeHandler('animationfinish', homeHandler);
- center = viewport.getCenter();
- ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned');
- equal(viewport.getZoom(), 1, 'We end up unzoomed');
- start();
- };
+ function stage1() {
+ var viewport = viewer.viewport,
+ center = viewport.getCenter();
- viewer.addHandler('animationfinish', homeHandler);
- viewport.goHome(true);
+ viewer.removeHandler('animation-finish', stage1);
+
+ ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned');
+ notEqual(viewport.getZoom(), 1, 'We start out zoomed');
+
+ var homeHandler = function() {
+ viewer.removeHandler('animation-finish', homeHandler);
+ center = viewport.getCenter();
+ ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned');
+ equal(viewport.getZoom(), 1, 'We end up unzoomed');
+ start();
+ };
+
+ viewer.addHandler('animation-finish', homeHandler);
+ viewport.goHome(true);
+ }
+
+ viewer.addHandler("open", opener);
+ viewer.addHandler("animation-finish", stage1);
+
+ viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('Click', function() {
- var viewport = viewer.viewport;
- center = viewport.getCenter();
- ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned');
- equal(viewport.getZoom(), 1, 'We start out unzoomed');
-
- var clickHandler = function() {
- viewer.removeHandler('animationfinish', clickHandler);
+ viewer.addHandler("open", function () {
+ var viewport = viewer.viewport,
center = viewport.getCenter();
- ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly');
- equal(viewport.getZoom(), 2, 'Zoomed correctly');
- start();
- };
- viewer.addHandler('animationfinish', clickHandler);
- Util.simulateViewerClick(viewer, 0.25, 0.25);
+ ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned');
+ equal(viewport.getZoom(), 1, 'We start out unzoomed');
+
+ var clickHandler = function() {
+ viewer.removeHandler('animation-finish', clickHandler);
+ center = viewport.getCenter();
+ ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly');
+ equal(viewport.getZoom(), 2, 'Zoomed correctly');
+ start();
+ };
+
+ viewer.addHandler('animation-finish', clickHandler);
+ Util.simulateViewerClickWithDrag( {
+ viewer: viewer,
+ widthFactor: 0.25,
+ heightFactor: 0.25,
+ dragCount: 0,
+ dragDx: 0,
+ dragDy: 0
+ } );
+ } );
+
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ // ----------
+ asyncTest('FullPage', function() {
+ viewer.addHandler("open", function () {
+ ok(!viewer.isFullPage(), 'Started out not fullpage');
+ ok(!$(viewer.element).hasClass('fullpage'),
+ 'No fullpage class on div');
+
+ var checkEnteringPreFullPage = function(event) {
+ viewer.removeHandler('pre-full-page', checkEnteringPreFullPage);
+ ok(event.fullPage, 'Switching to fullpage');
+ ok(!viewer.isFullPage(), 'Not yet fullpage');
+ };
+
+ var checkEnteringFullPage = function(event) {
+ viewer.removeHandler('full-page', checkEnteringFullPage);
+ ok(event.fullPage, 'Switched to fullpage');
+ ok(viewer.isFullPage(), 'Enabled fullpage');
+ ok($(viewer.element).hasClass('fullpage'),
+ 'Fullpage class added to div');
+
+ var checkExitingPreFullPage = function(event) {
+ viewer.removeHandler('pre-full-page', checkExitingPreFullPage);
+ ok(!event.fullPage, 'Exiting fullpage');
+ ok(viewer.isFullPage(), 'Still fullpage');
+ };
+
+ var checkExitingFullPage = function(event) {
+ viewer.removeHandler('full-page', checkExitingFullPage);
+ ok(!event.fullPage, 'Exiting fullpage');
+ ok(!viewer.isFullPage(), 'Disabled fullpage');
+ ok(!$(viewer.element).hasClass('fullpage'),
+ 'Fullpage class removed from div');
+ start();
+ };
+
+ viewer.addHandler("pre-full-page", checkExitingPreFullPage);
+ viewer.addHandler("full-page", checkExitingFullPage);
+ viewer.setFullPage(false);
+ };
+ viewer.addHandler("pre-full-page", checkEnteringPreFullPage);
+ viewer.addHandler("full-page", checkEnteringFullPage);
+ viewer.setFullPage(true);
+ });
+
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('FullScreen', function() {
+
+ if (!OpenSeadragon.supportsFullScreen) {
+ expect(0);
+ start();
+ return;
+ }
+
+ viewer.addHandler("open", function () {
+ ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen');
+
+ var checkEnteringPreFullScreen = function(event) {
+ viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen);
+ ok(event.fullScreen, 'Switching to fullscreen');
+ ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen');
+ };
+
+ // The fullscreen mode is always denied during tests so we are
+ // exiting directly.
+ var checkExitingFullScreen = function(event) {
+ viewer.removeHandler('full-screen', checkExitingFullScreen);
+ ok(!event.fullScreen, 'Exiting fullscreen');
+ ok(!OpenSeadragon.isFullScreen(), 'Disabled fullscreen');
+ start();
+ };
+ viewer.addHandler("pre-full-screen", checkEnteringPreFullScreen);
+ viewer.addHandler("full-screen", checkExitingFullScreen);
+ viewer.setFullScreen(true);
+ });
+
+ viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('Close', function() {
- var closeHandler = function() {
- viewer.removeHandler('close', closeHandler);
- ok(true, 'Close event was sent');
- start();
- };
+ viewer.addHandler("open", function () {
+ var closeHandler = function() {
+ viewer.removeHandler('close', closeHandler);
+ ok(!viewer.source, 'no source');
+ ok(true, 'Close event was sent');
+ ok(!viewer._updateRequestId, 'timer is off');
+ setTimeout(function() {
+ ok(!viewer._updateRequestId, 'timer is still off');
+ start();
+ }, 100);
+ };
- viewer.addHandler('close', closeHandler);
- viewer.close();
+ viewer.addHandler('close', closeHandler);
+ viewer.close();
+ });
+ viewer.open('/test/data/testpattern.dzi');
});
-
+
+ // ----------
+ asyncTest('Destroy', function() {
+ viewer.addHandler("open", function () {
+ // Check that the DOM has been modified
+ notEqual(0, $('#example').children().length);
+
+ var closeCalled = false;
+ var closeHandler = function() {
+ viewer.removeHandler('close', closeHandler);
+ closeCalled = true;
+ };
+
+ viewer.addHandler('close', closeHandler);
+ viewer.destroy();
+
+ // Check that the DOM has been cleaned up
+ equal(0, $('#example').children().length);
+ equal(null, viewer.canvas);
+ equal(null, viewer.keyboardCommandArea);
+ equal(null, viewer.container);
+ equal(null, viewer.element);
+ equal(true, closeCalled);
+ start();
+ });
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
})();
diff --git a/test/controls.js b/test/controls.js
new file mode 100644
index 00000000..a2d7d998
--- /dev/null
+++ b/test/controls.js
@@ -0,0 +1,383 @@
+/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
+
+(function () {
+ var viewer;
+
+ module('Controls', {
+ setup: function () {
+ var example = $('').appendTo("#qunit-fixture");
+
+ testLog.reset();
+
+ },
+ teardown: function () {
+ if (viewer && viewer.close) {
+ viewer.close();
+ }
+
+ viewer = null;
+ }
+ });
+
+ asyncTest('ZoomControlOff', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(!viewer.showZoomControl, 'showZoomControl should be off');
+ ok(!viewer.zoomInButton, "zoomIn button should be null");
+ ok(!viewer.zoomOutButton, "zoomOut button should be null");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showZoomControl: false
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('ZoomControlOn', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(viewer.showZoomControl, 'showZoomControl should be on');
+ ok(!!viewer.zoomInButton, "zoomIn button should not be null");
+ ok(!!viewer.zoomOutButton, "zoomOut button should not be null");
+ notEqual(viewer.buttons.buttons.indexOf(viewer.zoomInButton), -1,
+ "The zoomIn button should be present");
+ notEqual(viewer.buttons.buttons.indexOf(viewer.zoomOutButton), -1,
+ "The zoomOut button should be present");
+
+ var oldZoom = viewer.viewport.getZoom();
+ viewer.zoomInButton.onClick();
+ var newZoom = viewer.viewport.getZoom();
+ ok(oldZoom < newZoom, "OSD should have zoomed in.");
+ oldZoom = newZoom;
+ viewer.zoomOutButton.onClick();
+ newZoom = viewer.viewport.getZoom();
+ ok(oldZoom > newZoom, "OSD should have zoomed out.");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showZoomControl: true
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('HomeControlOff', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(!viewer.showHomeControl, 'showHomeControl should be off');
+ ok(!viewer.homeButton, "Home button should be null");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showHomeControl: false
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('HomeControlOn', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(viewer.showHomeControl, 'showHomeControl should be on');
+ ok(!!viewer.homeButton, "Home button should not be null");
+ notEqual(viewer.buttons.buttons.indexOf(viewer.homeButton), -1,
+ "The home button should be present");
+
+ viewer.viewport.zoomBy(1.1);
+ var bounds = viewer.viewport.getBounds();
+ var homeBounds = viewer.viewport.getHomeBounds();
+ ok(bounds.x !== homeBounds.x ||
+ bounds.y !== homeBounds.y ||
+ bounds.width !== homeBounds.width ||
+ bounds.height !== homeBounds.height,
+ "OSD should not be at home.");
+ viewer.homeButton.onRelease();
+ bounds = viewer.viewport.getBounds();
+ ok(bounds.x === homeBounds.x &&
+ bounds.y === homeBounds.y &&
+ bounds.width === homeBounds.width &&
+ bounds.height === homeBounds.height, "OSD should have get home.");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showHomeControl: true
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('FullPageControlOff', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(!viewer.showFullPageControl, 'showFullPageControl should be off');
+ ok(!viewer.fullPageButton, "FullPage button should be null");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showFullPageControl: false
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('FullPageControlOn', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(viewer.showHomeControl, 'showFullPageControl should be on');
+ ok(!!viewer.fullPageButton, "FullPage button should not be null");
+ notEqual(viewer.buttons.buttons.indexOf(viewer.fullPageButton), -1,
+ "The full page button should be present");
+
+ ok(!viewer.isFullPage(), "OSD should not be in full page.");
+ viewer.fullPageButton.onRelease();
+ ok(viewer.isFullPage(), "OSD should be in full page.");
+ viewer.fullPageButton.onRelease();
+ ok(!viewer.isFullPage(), "OSD should not be in full page.");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showHomeControl: true
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('RotateControlOff', function () {
+
+ var openHandler = function (event) {
+ viewer.removeHandler('open', openHandler);
+ ok(true, 'Open event was sent');
+ ok(viewer.drawer, 'Drawer exists');
+ ok(viewer.drawer.canRotate(), 'drawer.canRotate needs to be true');
+ ok(!viewer.showRotationControl, 'showRotationControl should be off');
+ ok(!viewer.rotateLeftButton, "rotateLeft button should be null");
+ ok(!viewer.rotateRightButton, "rotateRight button should be null");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showRotationControl: false
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('RotateControlOn', function () {
+
+ var openHandler = function (event) {
+ viewer.removeHandler('open', openHandler);
+ ok(true, 'Open event was sent');
+ ok(viewer.drawer, 'Drawer exists');
+ ok(viewer.drawer.canRotate(), 'drawer.canRotate needs to be true');
+ ok(viewer.showRotationControl, 'showRotationControl should be true');
+ notEqual(viewer.buttons.buttons.indexOf(viewer.rotateLeftButton), -1,
+ "rotateLeft should be found");
+ notEqual(viewer.buttons.buttons.indexOf(viewer.rotateRightButton), -1,
+ "rotateRight should be found");
+
+ // Now simulate the left/right button clicks.
+ // TODO: re-factor simulateViewerClickWithDrag so it'll accept any element, and use that.
+ equal(viewer.viewport.degrees, 0, "Image should start at 0 degrees rotation");
+ viewer.rotateLeftButton.onRelease();
+ equal(viewer.viewport.degrees, 270, "Image should be 270 degrees rotation (left)");
+ viewer.rotateRightButton.onRelease();
+ equal(viewer.viewport.degrees, 0, "Image should be 270 degrees rotation (right)");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ springStiffness: 100, // Faster animation = faster tests
+ showRotationControl: true
+ });
+ viewer.addHandler('open', openHandler);
+ viewer.open('/test/data/testpattern.dzi');
+ });
+
+ asyncTest('SequenceControlOff', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(!viewer.showSequenceControl, 'showSequenceControl should be off');
+ ok(!viewer.previousButton, "Previous button should be null");
+ ok(!viewer.nextButton, "Next button should be null");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ tileSources: [
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi'
+ ],
+ springStiffness: 100, // Faster animation = faster tests
+ showSequenceControl: false
+ });
+ viewer.addHandler('open', openHandler);
+ });
+
+ asyncTest('SequenceControlOnPrevNextWrapOff', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(viewer.showSequenceControl, 'showSequenceControl should be on');
+ ok(!!viewer.previousButton, "Previous button should not be null");
+ ok(!!viewer.nextButton, "Next button should not be null");
+ notEqual(viewer.paging.buttons.indexOf(viewer.previousButton), -1,
+ "The previous button should be present");
+ notEqual(viewer.paging.buttons.indexOf(viewer.nextButton), -1,
+ "The next button should be present");
+
+ equal(viewer.currentPage(), 0, "OSD should open on first page.");
+ ok(viewer.previousButton.element.disabled,
+ "Previous should be disabled on first page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on first page.");
+
+ viewer.nextButton.onRelease();
+ equal(viewer.currentPage(), 1, "OSD should be on second page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on second page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on second page.");
+
+ viewer.nextButton.onRelease();
+ equal(viewer.currentPage(), 2, "OSD should be on third page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on third page.");
+ ok(viewer.nextButton.element.disabled,
+ "Next should be disabled on third page.");
+
+ viewer.previousButton.onRelease();
+ equal(viewer.currentPage(), 1, "OSD should be on second page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on second page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on second page.");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ tileSources: [
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi'
+ ],
+ springStiffness: 100, // Faster animation = faster tests
+ showSequenceControl: true,
+ navPrevNextWrap: false
+ });
+ viewer.addHandler('open', openHandler);
+ });
+
+ asyncTest('SequenceControlOnPrevNextWrapOn', function () {
+
+ var openHandler = function () {
+ viewer.removeHandler('open', openHandler);
+ ok(viewer.showSequenceControl, 'showSequenceControl should be on');
+ ok(!!viewer.previousButton, "Previous button should not be null");
+ ok(!!viewer.nextButton, "Next button should not be null");
+ notEqual(viewer.paging.buttons.indexOf(viewer.previousButton), -1,
+ "The previous button should be present");
+ notEqual(viewer.paging.buttons.indexOf(viewer.nextButton), -1,
+ "The next button should be present");
+
+ equal(viewer.currentPage(), 0, "OSD should open on first page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on first page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on first page.");
+
+ viewer.previousButton.onRelease();
+ equal(viewer.currentPage(), 2, "OSD should be on third page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on third page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on third page.");
+
+ viewer.nextButton.onRelease();
+ equal(viewer.currentPage(), 0, "OSD should be on first page.");
+ ok(!viewer.previousButton.element.disabled,
+ "Previous should be enabled on first page.");
+ ok(!viewer.nextButton.element.disabled,
+ "Next should be enabled on first page.");
+
+ viewer.close();
+ start();
+ };
+
+ viewer = OpenSeadragon({
+ id: 'controlsTests',
+ prefixUrl: '/build/openseadragon/images/',
+ tileSources: [
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi',
+ '/test/data/testpattern.dzi'
+ ],
+ springStiffness: 100, // Faster animation = faster tests
+ showSequenceControl: true,
+ navPrevNextWrap: true
+ });
+ viewer.addHandler('open', openHandler);
+ });
+
+})();
diff --git a/test/data/A.png b/test/data/A.png
new file mode 100644
index 00000000..dde07443
Binary files /dev/null and b/test/data/A.png differ
diff --git a/test/data/BBlue.png b/test/data/BBlue.png
new file mode 100644
index 00000000..055d7ff4
Binary files /dev/null and b/test/data/BBlue.png differ
diff --git a/test/data/CCyan.png b/test/data/CCyan.png
new file mode 100644
index 00000000..40e56de2
Binary files /dev/null and b/test/data/CCyan.png differ
diff --git a/test/data/DDandelion.png b/test/data/DDandelion.png
new file mode 100644
index 00000000..fdc849f2
Binary files /dev/null and b/test/data/DDandelion.png differ
diff --git a/test/data/iiif1_0.json b/test/data/iiif1_0.json
new file mode 100644
index 00000000..73b91061
--- /dev/null
+++ b/test/data/iiif1_0.json
@@ -0,0 +1,26 @@
+{
+ "identifier": "iiif_1_0_files",
+ "width": 775,
+ "height": 1024,
+ "scale_factors": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "tile_width": 256,
+ "tile_height": 256,
+ "formats": [
+ "jpg",
+ "png"
+ ],
+ "qualities": [
+ "native",
+ "bitonal",
+ "grey",
+ "color"
+ ],
+ "profile": "http://library.stanford.edu/iiif/image-api/compliance.html#level1"
+}
diff --git a/test/data/iiif1_0.xml b/test/data/iiif1_0.xml
new file mode 100644
index 00000000..6dab2b4f
--- /dev/null
+++ b/test/data/iiif1_0.xml
@@ -0,0 +1,26 @@
+
+ iiif_1_0_files
+ 775
+ 1024
+
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+
+ 256
+ 256
+
+ jpg
+ png
+
+
+ native
+ bitonal
+ grey
+ color
+
+ http://library.stanford.edu/iiif/image-api/compliance.html#level1
+
diff --git a/test/data/iiif_1_0_files/0,0,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/0,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..a29c5568
Binary files /dev/null and b/test/data/iiif_1_0_files/0,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,0,512,512/256,/0/native.jpg b/test/data/iiif_1_0_files/0,0,512,512/256,/0/native.jpg
new file mode 100644
index 00000000..edb856cc
Binary files /dev/null and b/test/data/iiif_1_0_files/0,0,512,512/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,0,775,1024/194,/0/native.jpg b/test/data/iiif_1_0_files/0,0,775,1024/194,/0/native.jpg
new file mode 100644
index 00000000..be76af06
Binary files /dev/null and b/test/data/iiif_1_0_files/0,0,775,1024/194,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,256,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/0,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..cb38ba74
Binary files /dev/null and b/test/data/iiif_1_0_files/0,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,512,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/0,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..0b44da15
Binary files /dev/null and b/test/data/iiif_1_0_files/0,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,512,512,512/256,/0/native.jpg b/test/data/iiif_1_0_files/0,512,512,512/256,/0/native.jpg
new file mode 100644
index 00000000..250e76d1
Binary files /dev/null and b/test/data/iiif_1_0_files/0,512,512,512/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/0,768,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/0,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..8cf078fa
Binary files /dev/null and b/test/data/iiif_1_0_files/0,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/256,0,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/256,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..36ecb717
Binary files /dev/null and b/test/data/iiif_1_0_files/256,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/256,256,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/256,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..b57469a8
Binary files /dev/null and b/test/data/iiif_1_0_files/256,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/256,512,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/256,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..7fd76a78
Binary files /dev/null and b/test/data/iiif_1_0_files/256,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/256,768,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/256,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..cf96a405
Binary files /dev/null and b/test/data/iiif_1_0_files/256,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,0,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/512,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..ca605428
Binary files /dev/null and b/test/data/iiif_1_0_files/512,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,0,263,512/132,/0/native.jpg b/test/data/iiif_1_0_files/512,0,263,512/132,/0/native.jpg
new file mode 100644
index 00000000..ca48f792
Binary files /dev/null and b/test/data/iiif_1_0_files/512,0,263,512/132,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,256,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/512,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..41dd14bb
Binary files /dev/null and b/test/data/iiif_1_0_files/512,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,512,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/512,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..1b42d53c
Binary files /dev/null and b/test/data/iiif_1_0_files/512,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,512,263,512/132,/0/native.jpg b/test/data/iiif_1_0_files/512,512,263,512/132,/0/native.jpg
new file mode 100644
index 00000000..baa4ee5d
Binary files /dev/null and b/test/data/iiif_1_0_files/512,512,263,512/132,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/512,768,256,256/256,/0/native.jpg b/test/data/iiif_1_0_files/512,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..b3e0f5cb
Binary files /dev/null and b/test/data/iiif_1_0_files/512,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/768,0,7,256/7,/0/native.jpg b/test/data/iiif_1_0_files/768,0,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..7f08427a
Binary files /dev/null and b/test/data/iiif_1_0_files/768,0,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/768,256,7,256/7,/0/native.jpg b/test/data/iiif_1_0_files/768,256,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..b21f307b
Binary files /dev/null and b/test/data/iiif_1_0_files/768,256,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/768,512,7,256/7,/0/native.jpg b/test/data/iiif_1_0_files/768,512,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..4a92be8c
Binary files /dev/null and b/test/data/iiif_1_0_files/768,512,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/768,768,7,256/7,/0/native.jpg b/test/data/iiif_1_0_files/768,768,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..f167fa29
Binary files /dev/null and b/test/data/iiif_1_0_files/768,768,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/1,/0/native.jpg b/test/data/iiif_1_0_files/full/1,/0/native.jpg
new file mode 100644
index 00000000..6b3670c8
Binary files /dev/null and b/test/data/iiif_1_0_files/full/1,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/13,/0/native.jpg b/test/data/iiif_1_0_files/full/13,/0/native.jpg
new file mode 100644
index 00000000..f9cb6979
Binary files /dev/null and b/test/data/iiif_1_0_files/full/13,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/2,/0/native.jpg b/test/data/iiif_1_0_files/full/2,/0/native.jpg
new file mode 100644
index 00000000..a335cd34
Binary files /dev/null and b/test/data/iiif_1_0_files/full/2,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/25,/0/native.jpg b/test/data/iiif_1_0_files/full/25,/0/native.jpg
new file mode 100644
index 00000000..18a43a03
Binary files /dev/null and b/test/data/iiif_1_0_files/full/25,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/4,/0/native.jpg b/test/data/iiif_1_0_files/full/4,/0/native.jpg
new file mode 100644
index 00000000..ce882561
Binary files /dev/null and b/test/data/iiif_1_0_files/full/4,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/49,/0/native.jpg b/test/data/iiif_1_0_files/full/49,/0/native.jpg
new file mode 100644
index 00000000..68b8eb9c
Binary files /dev/null and b/test/data/iiif_1_0_files/full/49,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/7,/0/native.jpg b/test/data/iiif_1_0_files/full/7,/0/native.jpg
new file mode 100644
index 00000000..4c234847
Binary files /dev/null and b/test/data/iiif_1_0_files/full/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_0_files/full/97,/0/native.jpg b/test/data/iiif_1_0_files/full/97,/0/native.jpg
new file mode 100644
index 00000000..4afad0bf
Binary files /dev/null and b/test/data/iiif_1_0_files/full/97,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048.json b/test/data/iiif_1_1_no_tiles_1048.json
new file mode 100644
index 00000000..e5789846
--- /dev/null
+++ b/test/data/iiif_1_1_no_tiles_1048.json
@@ -0,0 +1,18 @@
+{
+ "profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1",
+ "height": 870,
+ "width": 1048,
+ "qualities": [
+ "native",
+ "color",
+ "grey",
+ "bitonal"
+ ],
+ "formats": [
+ "jpg",
+ "png",
+ "gif"
+ ],
+ "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_1_1_no_tiles_1048"
+}
diff --git a/test/data/iiif_1_1_no_tiles_1048/0,0,1024,870/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/0,0,1024,870/512,/0/native.jpg
new file mode 100644
index 00000000..2a103534
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/0,0,1024,870/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/0,0,512,512/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/0,0,512,512/512,/0/native.jpg
new file mode 100644
index 00000000..abe1eab4
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/0,0,512,512/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/0,512,512,358/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/0,512,512,358/512,/0/native.jpg
new file mode 100644
index 00000000..2e3a12c9
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/0,512,512,358/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/1024,0,24,512/24,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/1024,0,24,512/24,/0/native.jpg
new file mode 100644
index 00000000..55fffb23
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/1024,0,24,512/24,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/1024,0,24,870/12,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/1024,0,24,870/12,/0/native.jpg
new file mode 100644
index 00000000..ed7a45c1
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/1024,0,24,870/12,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/1024,512,24,358/24,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/1024,512,24,358/24,/0/native.jpg
new file mode 100644
index 00000000..2ad1ab7a
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/1024,512,24,358/24,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/512,0,512,512/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/512,0,512,512/512,/0/native.jpg
new file mode 100644
index 00000000..ec687491
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/512,0,512,512/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/512,512,512,358/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/512,512,512,358/512,/0/native.jpg
new file mode 100644
index 00000000..9609e3e0
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/512,512,512,358/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/131,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/131,/0/native.jpg
new file mode 100644
index 00000000..b2d34820
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/131,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/17,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/17,/0/native.jpg
new file mode 100644
index 00000000..e66b6ef7
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/17,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/262,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/262,/0/native.jpg
new file mode 100644
index 00000000..b6a4288d
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/262,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/33,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/33,/0/native.jpg
new file mode 100644
index 00000000..c8dd168c
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/33,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/66,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/66,/0/native.jpg
new file mode 100644
index 00000000..65d0a09d
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/66,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_1048/full/9,/0/native.jpg b/test/data/iiif_1_1_no_tiles_1048/full/9,/0/native.jpg
new file mode 100644
index 00000000..f51d6103
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_1048/full/9,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255.json b/test/data/iiif_1_1_no_tiles_255.json
new file mode 100644
index 00000000..12a09460
--- /dev/null
+++ b/test/data/iiif_1_1_no_tiles_255.json
@@ -0,0 +1,18 @@
+{
+ "profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1",
+ "height": 212,
+ "width": 255,
+ "qualities": [
+ "native",
+ "color",
+ "grey",
+ "bitonal"
+ ],
+ "formats": [
+ "jpg",
+ "png",
+ "gif"
+ ],
+ "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_1_1_no_tiles_255"
+}
diff --git a/test/data/iiif_1_1_no_tiles_255/0,0,212,212/212,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/0,0,212,212/212,/0/native.jpg
new file mode 100644
index 00000000..b41466da
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/0,0,212,212/212,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/212,0,43,212/43,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/212,0,43,212/43,/0/native.jpg
new file mode 100644
index 00000000..03cc9619
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/212,0,43,212/43,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/128,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/128,/0/native.jpg
new file mode 100644
index 00000000..69c602b8
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/128,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/16,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/16,/0/native.jpg
new file mode 100644
index 00000000..42b54ec6
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/16,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/32,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/32,/0/native.jpg
new file mode 100644
index 00000000..c341f30b
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/32,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/4,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/4,/0/native.jpg
new file mode 100644
index 00000000..62fdf391
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/4,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/64,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/64,/0/native.jpg
new file mode 100644
index 00000000..83629b8e
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/64,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_255/full/8,/0/native.jpg b/test/data/iiif_1_1_no_tiles_255/full/8,/0/native.jpg
new file mode 100644
index 00000000..087db0a1
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_255/full/8,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384.json b/test/data/iiif_1_1_no_tiles_384.json
new file mode 100644
index 00000000..112a0be6
--- /dev/null
+++ b/test/data/iiif_1_1_no_tiles_384.json
@@ -0,0 +1,18 @@
+{
+ "profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1",
+ "height": 319,
+ "width": 384,
+ "qualities": [
+ "native",
+ "color",
+ "grey",
+ "bitonal"
+ ],
+ "formats": [
+ "jpg",
+ "png",
+ "gif"
+ ],
+ "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_1_1_no_tiles_384"
+}
diff --git a/test/data/iiif_1_1_no_tiles_384/0,0,256,256/256,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/0,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..f2affd34
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/0,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/0,256,256,63/256,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/0,256,256,63/256,/0/native.jpg
new file mode 100644
index 00000000..a44db124
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/0,256,256,63/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/256,0,128,256/128,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/256,0,128,256/128,/0/native.jpg
new file mode 100644
index 00000000..61f8d677
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/256,0,128,256/128,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/256,256,128,63/128,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/256,256,128,63/128,/0/native.jpg
new file mode 100644
index 00000000..a1002c86
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/256,256,128,63/128,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/12,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/12,/0/native.jpg
new file mode 100644
index 00000000..2677d6c1
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/12,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/192,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/192,/0/native.jpg
new file mode 100644
index 00000000..00593f8a
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/192,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/24,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/24,/0/native.jpg
new file mode 100644
index 00000000..ee21015d
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/24,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/48,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/48,/0/native.jpg
new file mode 100644
index 00000000..5597681c
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/48,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/6,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/6,/0/native.jpg
new file mode 100644
index 00000000..8c4134df
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/6,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_384/full/96,/0/native.jpg b/test/data/iiif_1_1_no_tiles_384/full/96,/0/native.jpg
new file mode 100644
index 00000000..38a07820
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_384/full/96,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768.json b/test/data/iiif_1_1_no_tiles_768.json
new file mode 100644
index 00000000..743d6128
--- /dev/null
+++ b/test/data/iiif_1_1_no_tiles_768.json
@@ -0,0 +1,18 @@
+{
+ "profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2",
+ "height": 637,
+ "width": 768,
+ "qualities": [
+ "native",
+ "color",
+ "grey",
+ "bitonal"
+ ],
+ "formats": [
+ "jpg",
+ "png",
+ "gif"
+ ],
+ "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_1_1_no_tiles_768"
+}
diff --git a/test/data/iiif_1_1_no_tiles_768/0,0,512,512/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/0,0,512,512/512,/0/native.jpg
new file mode 100644
index 00000000..c547135f
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/0,0,512,512/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/0,512,512,125/512,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/0,512,512,125/512,/0/native.jpg
new file mode 100644
index 00000000..88717a4d
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/0,512,512,125/512,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/512,0,256,512/256,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/512,0,256,512/256,/0/native.jpg
new file mode 100644
index 00000000..3423bc3a
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/512,0,256,512/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/512,512,256,125/256,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/512,512,256,125/256,/0/native.jpg
new file mode 100644
index 00000000..8fee2e2e
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/512,512,256,125/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/12,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/12,/0/native.jpg
new file mode 100644
index 00000000..97ddd626
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/12,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/192,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/192,/0/native.jpg
new file mode 100644
index 00000000..0f786ee9
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/192,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/24,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/24,/0/native.jpg
new file mode 100644
index 00000000..151eed0f
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/24,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/384,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/384,/0/native.jpg
new file mode 100644
index 00000000..9dcd340f
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/384,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/48,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/48,/0/native.jpg
new file mode 100644
index 00000000..6ceb6842
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/48,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/6,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/6,/0/native.jpg
new file mode 100644
index 00000000..20139e5e
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/6,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_no_tiles_768/full/96,/0/native.jpg b/test/data/iiif_1_1_no_tiles_768/full/96,/0/native.jpg
new file mode 100644
index 00000000..299589b6
Binary files /dev/null and b/test/data/iiif_1_1_no_tiles_768/full/96,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled.json b/test/data/iiif_1_1_tiled.json
new file mode 100644
index 00000000..641d8511
--- /dev/null
+++ b/test/data/iiif_1_1_tiled.json
@@ -0,0 +1,28 @@
+{
+ "profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level2",
+ "scale_factors": [
+ 1,
+ 2,
+ 4,
+ 8,
+ 16,
+ 32
+ ],
+ "tile_height": 256,
+ "height": 1024,
+ "width": 775,
+ "tile_width": 256,
+ "qualities": [
+ "native",
+ "bitonal",
+ "grey",
+ "color"
+ ],
+ "formats": [
+ "jpg",
+ "png",
+ "gif"
+ ],
+ "@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
+ "@id": "http://localhost:8000/test/data/iiif_1_1_tiled"
+}
diff --git a/test/data/iiif_1_1_tiled/0,0,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..a29c5568
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,0,512,512/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,0,512,512/256,/0/native.jpg
new file mode 100644
index 00000000..edb856cc
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,0,512,512/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,0,775,1024/194,/0/native.jpg b/test/data/iiif_1_1_tiled/0,0,775,1024/194,/0/native.jpg
new file mode 100644
index 00000000..be76af06
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,0,775,1024/194,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,256,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..cb38ba74
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,512,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..0b44da15
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,512,512,512/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,512,512,512/256,/0/native.jpg
new file mode 100644
index 00000000..250e76d1
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,512,512,512/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/0,768,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/0,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..8cf078fa
Binary files /dev/null and b/test/data/iiif_1_1_tiled/0,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/256,0,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/256,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..36ecb717
Binary files /dev/null and b/test/data/iiif_1_1_tiled/256,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/256,256,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/256,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..b57469a8
Binary files /dev/null and b/test/data/iiif_1_1_tiled/256,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/256,512,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/256,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..7fd76a78
Binary files /dev/null and b/test/data/iiif_1_1_tiled/256,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/256,768,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/256,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..cf96a405
Binary files /dev/null and b/test/data/iiif_1_1_tiled/256,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,0,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/512,0,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..ca605428
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,0,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,0,263,512/132,/0/native.jpg b/test/data/iiif_1_1_tiled/512,0,263,512/132,/0/native.jpg
new file mode 100644
index 00000000..ca48f792
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,0,263,512/132,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,256,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/512,256,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..41dd14bb
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,256,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,512,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/512,512,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..1b42d53c
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,512,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,512,263,512/132,/0/native.jpg b/test/data/iiif_1_1_tiled/512,512,263,512/132,/0/native.jpg
new file mode 100644
index 00000000..baa4ee5d
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,512,263,512/132,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/512,768,256,256/256,/0/native.jpg b/test/data/iiif_1_1_tiled/512,768,256,256/256,/0/native.jpg
new file mode 100644
index 00000000..b3e0f5cb
Binary files /dev/null and b/test/data/iiif_1_1_tiled/512,768,256,256/256,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/768,0,7,256/7,/0/native.jpg b/test/data/iiif_1_1_tiled/768,0,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..7f08427a
Binary files /dev/null and b/test/data/iiif_1_1_tiled/768,0,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/768,256,7,256/7,/0/native.jpg b/test/data/iiif_1_1_tiled/768,256,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..b21f307b
Binary files /dev/null and b/test/data/iiif_1_1_tiled/768,256,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/768,512,7,256/7,/0/native.jpg b/test/data/iiif_1_1_tiled/768,512,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..4a92be8c
Binary files /dev/null and b/test/data/iiif_1_1_tiled/768,512,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/768,768,7,256/7,/0/native.jpg b/test/data/iiif_1_1_tiled/768,768,7,256/7,/0/native.jpg
new file mode 100644
index 00000000..f167fa29
Binary files /dev/null and b/test/data/iiif_1_1_tiled/768,768,7,256/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/1,/0/native.jpg b/test/data/iiif_1_1_tiled/full/1,/0/native.jpg
new file mode 100644
index 00000000..6b3670c8
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/1,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/13,/0/native.jpg b/test/data/iiif_1_1_tiled/full/13,/0/native.jpg
new file mode 100644
index 00000000..f9cb6979
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/13,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/2,/0/native.jpg b/test/data/iiif_1_1_tiled/full/2,/0/native.jpg
new file mode 100644
index 00000000..a335cd34
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/2,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/25,/0/native.jpg b/test/data/iiif_1_1_tiled/full/25,/0/native.jpg
new file mode 100644
index 00000000..18a43a03
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/25,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/4,/0/native.jpg b/test/data/iiif_1_1_tiled/full/4,/0/native.jpg
new file mode 100644
index 00000000..ce882561
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/4,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/49,/0/native.jpg b/test/data/iiif_1_1_tiled/full/49,/0/native.jpg
new file mode 100644
index 00000000..68b8eb9c
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/49,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/7,/0/native.jpg b/test/data/iiif_1_1_tiled/full/7,/0/native.jpg
new file mode 100644
index 00000000..4c234847
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/7,/0/native.jpg differ
diff --git a/test/data/iiif_1_1_tiled/full/97,/0/native.jpg b/test/data/iiif_1_1_tiled/full/97,/0/native.jpg
new file mode 100644
index 00000000..4afad0bf
Binary files /dev/null and b/test/data/iiif_1_1_tiled/full/97,/0/native.jpg differ
diff --git a/test/data/testpattern.js b/test/data/testpattern.js
new file mode 100644
index 00000000..dc936252
--- /dev/null
+++ b/test/data/testpattern.js
@@ -0,0 +1,12 @@
+testpattern({
+ Image: {
+ xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
+ Format: 'jpg',
+ Overlap: 1,
+ TileSize: 254,
+ Size:{
+ Height: 1000,
+ Width: 1000
+ }
+ }
+});
diff --git a/test/data/testpattern.xml b/test/data/testpattern.xml
new file mode 100644
index 00000000..6d93c763
--- /dev/null
+++ b/test/data/testpattern.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/demo/basic.html b/test/demo/basic.html
new file mode 100644
index 00000000..19572b86
--- /dev/null
+++ b/test/demo/basic.html
@@ -0,0 +1,33 @@
+
+
+
+ OpenSeadragon Basic Demo
+
+
+
+
+
+
+ Simple demo page to show a default OpenSeadragon viewer.
+